#!/bin/bash

STOPTIMEOUT=10
FMT="%a %b %d %Y %H:%M:%S GMT%z (%Z)"

# This is the full path for supervisor in it's SCL context
supervisor_exe(){
  nodejs_context "which supervisor"
}

# All PIDs running node (including supervisor)
# Need to combine double slashes in case our PATH contains them
node_pids(){
  ps -u $(id -u) -o pid= -o cmd= | grep -e '[0-9]\{1,\}\snode\s' | replace '//' '/'
}

# Only PIDs with node running supervisor
supervisor_pid(){
  pids=$(node_pids)
  sup_exe=$(supervisor_exe)
  echo "${pids}" | awk "/${sup_exe//\//\/}/ {print \$1}"
}

# Only node PIDs without supervisor
node_pid(){
  pids=$(node_pids)
  sup_exe=$(supervisor_exe)
  echo "${pids}" | awk "!/${sup_exe//\//\/}/ {print \$1}"
}

function check_hot_deploy_marker() {
  if hot_deploy_enabled_for_latest_deployment; then
    echo "hot_deploy marker found. Switching to using supervisor. Subsequent pushes won't require restart until the marker is removed."
  fi
}

function print_missing_package_json_warning() {
       cat <<DEPRECATED
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
  It is highly recommended that you add a package.json
  file to your application.
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
DEPRECATED
}

function print_deprecation_warning() {
       cat <<DEPRECATED
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

  The use of deplist.txt is being deprecated and will soon
  go away. For the short term, we will continue to support
  installing the Node modules specified in the deplist.txt
  file. But please be aware that this will soon go away.

  It is highly recommended that you use the package.json
  file to specify dependencies on other Node modules.

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
DEPRECATED
}

function is_node_module_installed() {
    module_name=${1:-""}
    if [ -n "$module_name" ]; then
        pushd "$OPENSHIFT_NODEJS_DIR" > /dev/null
        if [ -d $m ] ; then
            popd > /dev/null
            return 0
        fi
        popd > /dev/null
    fi

    return 1
}

function status() {
    if is_supervisor_running; then
        client_result "Application is running"
    else
        client_result "Application is not running"
    fi
}  #  End of function  status.


function get_main_script_from_package_json() {
    nodejs_context "node" <<NODE_EOF
try {
  var zmain = require('$OPENSHIFT_REPO_DIR/package.json').main;
  if (typeof zmain === 'undefined') {
    console.log('server.js');
  }
  else {
    console.log(zmain);
  }
} catch(ex) {
  console.log('server.js');
}
NODE_EOF

}  #  End of function  get_main_script_from_package_json.


function start() {
    echo "Starting NodeJS cartridge"

    if is_supervisor_running; then
        echo "Application is already running"
        return 0
    fi

    envf="$OPENSHIFT_NODEJS_DIR/configuration/node.env"
    logf="$OPENSHIFT_NODEJS_LOG_DIR/node.log"

    #  Source environment if it exists.
    [ -f "$envf" ]  &&  source "$envf"

    #  Ensure we have script file.
    node_app=${node_app:-"server.js"}

    pushd "$OPENSHIFT_REPO_DIR" > /dev/null
    {
        echo "`date +"$FMT"`: Starting application '$OPENSHIFT_APP_NAME' ..."
        if [ ! -f "$OPENSHIFT_REPO_DIR/package.json" ]; then
            echo "    Script       = $node_app"
            echo "    Script Args  = $node_app_args"
            echo "    Node Options = $node_opts"
        fi
    } >> $logf


    if [ -f "$OPENSHIFT_REPO_DIR/package.json" ]; then
        script_n_opts="$(get_main_script_from_package_json)"
    else
        #  Backward compatibility.
        print_missing_package_json_warning
        script_n_opts="$node_opts $node_app $node_app_args"
    fi

    # TODO: supervisor resource usage fixed in version 0.5.4
    nodejs_context "nohup supervisor -e 'node|js|coffee' -p ${OPENSHIFT_NODEJS_POLL_INTERVAL:-1000} -- $script_n_opts  >> $logf 2>&1 &"

    retries=3
    while [ $retries -gt 0 ]; do
      spid=$(supervisor_pid)
      [ -n "${spid}" ] && break
      sleep 1
      let retries=${retries}-1
    done

    popd > /dev/null
    if [ -n "${spid}" ]; then
        echo "$spid" > "$OPENSHIFT_NODEJS_PID_DIR/supervisor.pid"
    else
        error "Application '$OPENSHIFT_APP_NAME' failed to start" 1>&2 1
    fi
}  #  End of function  start.


function stop() {
    echo "Stopping NodeJS cartridge"
    if [ -f $OPENSHIFT_NODEJS_PID_DIR/supervisor.pid ]; then
      s_pid=$( cat $OPENSHIFT_NODEJS_PID_DIR/supervisor.pid 2> /dev/null )
      if [ "${s_pid}" != "$(supervisor_pid)" ]; then
        error "Warning: Application '$OPENSHIFT_APP_NAME' supervisor PID does not match '\$OPENSHIFT_NODEJS_PID_DIR/supervisor.pid'.  Use force-stop to kill." 141
      else
        logf="$OPENSHIFT_NODEJS_LOG_DIR/node.log"
        echo "`date +"$FMT"`: Stopping application '$OPENSHIFT_APP_NAME' ..." >> $logf
        /bin/kill $s_pid
        ret=$?
        if [ $ret -eq 0 ]; then
          TIMEOUT="$STOPTIMEOUT"
          while [ $TIMEOUT -gt 0 ]  && is_supervisor_running ; do
            /bin/kill -0 "$s_pid" >/dev/null 2>&1 || break
            sleep 1
            let TIMEOUT=${TIMEOUT}-1
          done
        fi

        # Make Node go down forcefully if it is still running.
        if is_supervisor_running ; then
          pkill -f "$(supervisor_exe)" 2>&1 || :
        fi

        if is_supervisor_running ; then
          error "Warning: Application '$OPENSHIFT_APP_NAME' unable to stop. Use force-stop to kill." 141
        else
          echo "`date +"$FMT"`: Stopped Node application '$OPENSHIFT_APP_NAME'" >> $logf
          rm -f $OPENSHIFT_NODEJS_PID_DIR/supervisor.pid
        fi
      fi
    else
      if [ -n "$(supervisor_pid)" ]; then
        error "Warning: Application '$OPENSHIFT_APP_NAME' supervisor exists without a pid file.  Use force-stop to kill." 141
      elif [ -n "$(node_pid)" ]; then
        error "Warning: Application '$OPENSHIFT_APP_NAME' Node server exists without a pid file.  Use force-stop to kill." 141
      fi
    fi
}  #  End of function stop.

function restart() {
    if is_supervisor_running; then
      stop
    fi
    start

}  #  End of function  restart.

function build() {
    echo "Building NodeJS cartridge"
    node_modules_dir="${OPENSHIFT_REPO_DIR}node_modules/"
    saved_modules_dir="${OPENSHIFT_NODEJS_DIR}/tmp/saved.node_modules"

    # Ensure that we have the node_modules directory.
    mkdir -p $node_modules_dir

    if force_clean_build_enabled_for_latest_deployment; then
        echo "Force-clean builds are enabled. Recreating npm modules" 1>&2

        # Remove saved modules, if any.
        rm -rf $saved_modules_dir

        # Clean the npm cache. (This will clean the ~/.npm directory).
        npm cache clean

        # Link back the global modules.
        link_global_modules $OPENSHIFT_NODEJS_VERSION
    else
        # Restore the saved node_modules from prior builds.
        if [ -d "${OPENSHIFT_NODEJS_DIR}/tmp/saved.node_modules" ]; then
            for d in `ls -a ${OPENSHIFT_NODEJS_DIR}/tmp/saved.node_modules`; do
                [ -e "${node_modules_dir}$d" ]  ||  \
                  mv "${OPENSHIFT_NODEJS_DIR}/tmp/saved.node_modules/$d" "$node_modules_dir"
            done
            rm -rf "${OPENSHIFT_NODEJS_DIR}/tmp/saved.node_modules"
        fi
    fi

    #  Newer versions of Node set tmp to $HOME/tmp, which is not available
    nodejs_context "npm config set tmp $OPENSHIFT_TMP_DIR"

    if [ -f "${OPENSHIFT_REPO_DIR}"/deplist.txt ]; then
        mods=$(perl -ne 'print if /^\s*[^#\s]/' "${OPENSHIFT_REPO_DIR}"/deplist.txt)
        [ -n "$mods" ]  &&  print_deprecation_warning
        for m in $mods; do
            echo "Checking npm module: $m"
            echo
            if is_node_module_installed "$m"; then
                (cd "${OPENSHIFT_NODEJS_DIR}"; nodejs_context "npm update '$m'")
            else
                (cd "${OPENSHIFT_NODEJS_DIR}"; nodejs_context "npm install '$m'")
            fi
        done
    fi

    # Workaround for failure in npm install when a package in package.json
    # points to a git commit.
    # This issue occurs because we are running in the context of a
    # git post receive-hook
    unset GIT_DIR
    unset GIT_WORK_TREE

    nodejs_context /bin/bash
    if [ -f "${OPENSHIFT_REPO_DIR}"/package.json ]; then
        (cd "${OPENSHIFT_REPO_DIR}"; nodejs_context "npm install -d")
    fi

}

function is_supervisor_running() {
    #  Have a pid file, use it - otherwise return not supervisor.
    [ -f "$OPENSHIFT_NODEJS_PID_DIR/supervisor.pid" ]  ||  return 1

    #  Have a valid pid, use it - otherwise return not supervisor.
    nodepid=$(cat "${OPENSHIFT_NODEJS_PID_DIR}/supervisor.pid")
    [ -n "$nodepid" ]  ||  return 1

    #  Is the pid a supervisor process.
    if [[ $(ps --no-heading -ocmd -p $nodepid | replace '//' '/') =~ $(supervisor_exe) ]]; then
       #  Yes, the app server is a supervisor process.
       return 0
    fi

    return 1

}  #  End of function  is_supervisor_running.

function post-deploy() {
    check_hot_deploy_marker

    # Check if supervisor is already running. If not do a restart.
    if hot_deploy_enabled_for_latest_deployment && ! is_supervisor_running ; then
      restart
    fi
}

function pre-repo-archive() {
    check_hot_deploy_marker

    rm -rf ${OPENSHIFT_NODEJS_DIR}/tmp/{node_modules,saved.node_modules}

    # If the node_modules/ directory exists, then "stash" it away for redeploy.
    node_modules_dir="${OPENSHIFT_REPO_DIR}node_modules"
    if [ -d "$node_modules_dir" ]; then
      echo 'Saving away previously installed Node modules'
      mv "$node_modules_dir" "${OPENSHIFT_NODEJS_DIR}/tmp/saved.node_modules"
      mkdir -p "$node_modules_dir"
    fi
}

# Clean up any log files
function tidy() {
  client_message "Emptying log dir: $OPENSHIFT_NODEJS_LOG_DIR"
  shopt -s dotglob
  rm -rf $OPENSHIFT_NODEJS_LOG_DIR/*
  rm -rf ${OPENSHIFT_NODEJS_DIR}tmp/*
}

#
#  main():
#

# Ensure arguments.
if ! [ $# -eq 1 ]; then
    echo "Usage: $0 [start|restart|graceful|graceful-stop|stop|status]"
    exit 1
fi

# Source utility functions.
source $OPENSHIFT_CARTRIDGE_SDK_BASH
source "${OPENSHIFT_NODEJS_DIR}/lib/util"
source "${OPENSHIFT_NODEJS_DIR}/lib/nodejs_context"

# Handle commands.
case "$1" in
    start)               start       ;;
    restart|graceful)    restart     ;;
    graceful-stop|stop)  stop        ;;
    status)              status      ;;
    build)               build       ;;
    post-deploy)         post-deploy ;;
    tidy)                tidy        ;;
    pre-repo-archive)    pre-repo-archive ;;
    *) exit 0;
esac

