#!/usr/bin/env bash

# fail hard
set -o pipefail
# fail harder
set -eu
# for patterns further down
shopt -s extglob

php_passthrough() {
    local dir=$(dirname $1)
    local file=$(basename $1)
    local out=$(basename $file .php)
    if [[ "$out" != "$file" ]]; then
        echo "Interpreting ${1#$HEROKU_APP_DIR/} to $out" >&2
        out="$dir/$out"
        php $1 > $out
        echo $out
    else
        echo $1
    fi
}

check_exists() {
    if [[ ! -f "$HEROKU_APP_DIR/$1" ]]; then
        echo "Cannot read -$2 '$1' (relative to '$HEROKU_APP_DIR')" >&2
        exit 1
    else
        echo "$HEROKU_APP_DIR/$1"
    fi
}

print_help() {
echo "\
Boots HHVM together with Apache2 on Heroku and for local development.

Usage:
  heroku-hhvm-apache2 [options] [<DOCUMENT_ROOT>]

Options:
  -C <httpd.inc.conf>     The path to the configuration file to include inside
                          the Apache2 VHost config (see option -c below). Will
                          be included inside the '<VirtualHost>' section just
                          after the '<Directory>' & 'ProxyPassMatch' directives.
                          Recommended approach when customizing Apache2's config
                          in most cases, unless you need to set fundamental
                          server level options.
                          [default: $COMPOSER_VENDOR_DIR/heroku/heroku-buildpack-php/conf/apache2/default_include.conf]
  -c <httpd.conf>         The path to the full VHost configuration file that is
                          included after Heroku's (or your local) Apache2 config
                          is loaded. Must contain a 'Listen \${PORT}' directive
                          and should have a '<VirtualHost>' and likely also a
                          '<Directory>' section (see option -C above).
                          [default: $COMPOSER_VENDOR_DIR/heroku/heroku-buildpack-php/conf/apache2/heroku.conf]
  -h                      Display this help screen and exit.
  -i <php.ini>            The path to the php.ini file to use.
                          [default: $COMPOSER_VENDOR_DIR/heroku/heroku-buildpack-php/conf/hhvm/php.ini]

Any file name that ends in '.php' will be run through the PHP interpreter first.
You may use this for templating although this is less useful than e.g. for Nginx
where unlike in Apache2, you cannot reference environment variables in config
files using a '\${VARNAME}' syntax.
All file paths must be relative to '$HEROKU_APP_DIR'.

If you would like to use the -C and -c options together, make sure you retain
the appropriate include mechanisms (see default configs for details).
" >&2
}

# we need this in configs
export HEROKU_APP_DIR=$(pwd)
export DOCUMENT_ROOT="$HEROKU_APP_DIR"
# set a default port if none is given
export PORT=${PORT:-$(( $RANDOM+1024 ))}
COMPOSER_VENDOR_DIR=$(composer config vendor-dir)

while getopts ":C:c:i:h" opt; do
    case $opt in
        C)
            httpd_config_include=$(check_exists "$OPTARG" "C")
            ;;
        c)
            httpd_config=$(check_exists "$OPTARG" "c")
            ;;
        i)
            php_config=$(check_exists "$OPTARG" "i")
            ;;
        h)
            print_help 2>&1
            exit
            ;;
        \?)
            echo "Invalid option: -$OPTARG" >&2
            exit 2
            ;;
        :)
            echo "Option -$OPTARG requires an argument" >&2
            exit 2
            ;;
    esac
done
# clear processed arguments
shift $((OPTIND-1))

if [[ "$#" -gt "1" ]]; then
    print_help
    exit 2
fi

hhvm --php -r 'exit((int)version_compare(HHVM_VERSION, "3.0.1", "<"));' || { echo "This program requires HHVM 3.0.1 or newer" >&2; exit 1; }
{ { httpd -v | php -r 'exit((int)version_compare(preg_replace("#^Server version: Apache/([\d\\.]+).+$#sm", "\\1", file_get_contents("php://stdin")), "2.4.8", "<"));'; } && { httpd -t -D DUMP_MODULES 2> /dev/null | grep "proxy_fcgi_module" > /dev/null; }; } || { echo "This program requires Apache 2.4.8 or newer with mod_proxy_fcgi enabled" >&2; exit 1; }

echo "Booting on port $PORT..." >&2

if [[ "$#" == "1" ]]; then
    DOCUMENT_ROOT="$HEROKU_APP_DIR/$1"
    if [[ ! -d "$DOCUMENT_ROOT" ]]; then
        echo "DOCUMENT_ROOT '$1' does not exist"
        exit 1
    else
        # strip trailing slashes if present
        DOCUMENT_ROOT=${DOCUMENT_ROOT%%*(/)}
        echo "DOCUMENT_ROOT changed to '$DOCUMENT_ROOT'" >&2
    fi
fi

php_config=${php_config:-"$HEROKU_APP_DIR/$COMPOSER_VENDOR_DIR/heroku/heroku-buildpack-php/conf/hhvm/php.ini"}
echo "Using HHVM configuration (php.ini) file '${php_config#$HEROKU_APP_DIR/}'" >&2
php_config=$(php_passthrough "$php_config")

httpd_config_include=${httpd_config_include:-"$HEROKU_APP_DIR/$COMPOSER_VENDOR_DIR/heroku/heroku-buildpack-php/conf/apache2/default_include.conf"}
echo "Using Apache2 VirtualHost-level configuration include '${httpd_config_include#$HEROKU_APP_DIR/}'" >&2
httpd_config_include=$(php_passthrough "$httpd_config_include")
export HEROKU_PHP_HTTPD_CONFIG_INCLUDE="$httpd_config_include"

httpd_config=${httpd_config:-"$HEROKU_APP_DIR/$COMPOSER_VENDOR_DIR/heroku/heroku-buildpack-php/conf/apache2/heroku.conf"}
echo "Using Apache2 configuration file '${httpd_config#$HEROKU_APP_DIR/}'" >&2
httpd_config=$(php_passthrough "$httpd_config")

# make a shared pipe; we'll write the name of the process that exits to it once that happens, and wait for that event below
# this particular call works on Linux and Mac OS (will create a literal ".XXXXXX" on Mac, but that doesn't matter).
wait_pipe=$(mktemp -t "heroku.waitpipe-$PORT.XXXXXX" -u)
rm -rf $wait_pipe
mkfifo $wait_pipe

# trap SIGINT/SIGQUIT (ctrl+c or ctrl+\ on the console), SIGTERM, and EXIT (upon failure of any command due to set -e, or because of the exit 1 at the very end), kill subshell child processes, then subshells
# 1) restore EXIT trap immediately, or the exit at the end of the line will trigger this trap again
# 2) kill childrens' child processes (the stuff running inside the sub-shells) using xargs because this is easier (-P expects a comma separated list); the || true prevents premature exit (set -e) if one of those doesn't have children anymore (it's likely that's why we're hitting this bit of code in the first place)
# 3) kill child processes (that's the sub-shells); it's likely that some of them have already disappeared, so xarg || true it too and suppress "no such process" complaints by sending them to /dev/null
# FIXME: this doesn't currently fire when the subshells themselves are terminated, and apparently also not when killing the tail command
# TODO: for extra brownie points, move to a function and curry for each given signal, passing the signal in as an arg, so we can use different exit codes or messages
trap 'trap - EXIT; echo "Going down, terminating child processes..." >&2; jobs -p | xargs -n1 pkill -TERM -P || true; jobs -p | xargs -n1 kill -TERM 2> /dev/null || true; exit' SIGINT SIGQUIT SIGTERM EXIT

# redirect logs to STDERR; write "tail ..." to the shared pipe if it exits
echo "Starting log redirection..." >&2
( touch "/tmp/heroku.apache2_error.$PORT.log" "/tmp/heroku.apache2_access.$PORT.log"; tail -qF -n 0 /tmp/heroku.*.$PORT.log 1>&2; echo "tail heroku.*.$PORT.log"; ) > $wait_pipe &
# start HHVM; write "hhvm" to the shared pipe if it exits
# (it writes to STDOUT, we need to redirect to STDERR)
echo "Starting hhvm..." >&2
( hhvm --mode server -vServer.Type=fastcgi -vServer.FileSocket=/tmp/heroku.fcgi.$PORT.sock -c "$php_config" >&2; echo "hhvm"; ) > $wait_pipe &
# wait a few seconds for HHVM to finish initializing; otherwise an early request might break Apache with the FastCGI pipe not being ready
# start apache; write "httpd" to the shared pipe if it exits
echo "Starting httpd..." >&2
( sleep 2; httpd -D NO_DETACH -c "Include $httpd_config" || true; echo "httpd"; ) > $wait_pipe &

# wait for something to come from the shared pipe, which means that the given process was killed or has failed
# we'll only reach this if one of the processes above has terminated
read exitproc < $wait_pipe
echo "Process exited unexpectedly: $exitproc"

# this will trigger the trap and kill all remaining children
exit 1
