#!/bin/bash
# -*- tab-width:4;indent-tabs-mode:nil -*-
# ex: ts=4 sw=4 et

set -euo pipefail

BASE="$(cd "$(dirname "$(readlink "$0" || echo "$0")")"/..; pwd -P)"
# shellcheck disable=SC1090,SC1091
source "$BASE/bin/common_defs.sh"
# shellcheck disable=SC1090,SC1091
source "$BASE/bin/common_functions.sh"

# Make sure log directory exists
mkdir -p "$RUNNER_LOG_DIR"

# Make sure data directory exists
mkdir -p "$RUNNER_DATA_DIR"

# Make sure data/configs exists
mkdir -p "$CONFIGS_DIR"

usage() {
    local command="$1"

    case "$command" in
    start)
        echo "Start EMQ X service in daemon mode"
        ;;
    stop)
        echo "Stop the running EMQ X program"
        ;;
    restart|reboot)
        echo "Restart $EMQX_DESCRIPTION"
        ;;
    pid)
        echo "Print out $EMQX_DESCRIPTION process identifier"
        ;;
    ping)
        echo "Check if the $EMQX_DESCRIPTION node is up and running"
        echo "This command exit with 0 silently if node is running"
        ;;
    escript)
        echo "Execute a escript using the Erlang runtime from EMQ X package installation"
        echo "For example $REL_NAME escript /path/to/my/escript my_arg1 my_arg2"
        ;;
    attach)
        echo "This command is applicable when $EMQX_DESCRIPTION is started in daemon"
        echo "mode. it attaches the current shell to EMQ X's control console"
        echo "through a named pipe"
        echo "WARNING: try to use the safer alternative, remote_console command."
        ;;
    remote_console)
        echo "Start a dummy Erlang node and hidden-connect $EMQX_DESCRIPTION to"
        echo "with an interactive Erlang shell"
        ;;
    console)
        echo "Boot up $EMQX_DESCRIPTION service in an interactive Erlang shell"
        echo "This command is useful for troubleshooting"
        ;;
    console_clean)
        echo "This command does NOT boot up the $EMQX_DESCRIPTION service"
        echo "It only starts an interactive Erlang console with all the"
        echo "EMQ X code available"
        ;;
    foreground)
        echo "Start $EMQX_DESCRIPTION in foreground mode"
        ;;
    ertspath)
        echo "Print path to Erlang runtime dir"
        ;;
    rpc)
        echo "Usage $REL_NAME rpc MODULE FUNCTION [ARGS, ...]"
        echo "Connect to the $EMQX_DESCRIPTION node and make an Erlang RPC"
        echo "The result of the RPC call must be 'ok'"
        echo "This command blocks for at most 60 seconds in case the node"
        echo "does not reply the call in time"
        ;;
    rpcterms)
        echo "Usage $REL_NAME rpcterms MODULE FUNCTION [ARGS, ...]"
        echo "Connect to the $EMQX_DESCRIPTION node and make an Erlang RPC"
        echo "The result of the RPC call is pretty-printed as an Erlang term"
        ;;
    root_dir)
        echo "Print EMQ X installation root dir"
        ;;
    eval)
        echo "Evaluate an Erlang expression in the EMQ X node"
        ;;
    versions)
        echo "List installed EMQ X versions and their status"
        ;;
    unpack)
        echo "Usage: $REL_NAME unpack [VERSION]"
        echo "Unpacks a release package VERSION, it assumes that this"
        echo "release package tarball has already been deployed at one"
        echo "of the following locations:"
        echo "      releases/<relname>-<version>.tar.gz"
        echo "      releases/<relname>-<version>.zip"
        ;;
    install)
        echo "Usage: $REL_NAME install [VERSION]"
        echo "Installs a release package VERSION, it assumes that this"
        echo "release package tarball has already been deployed at one"
        echo "of the following locations:"
        echo "      releases/<relname>-<version>.tar.gz"
        echo "      releases/<relname>-<version>.zip"
        echo ""
        echo "     --no-permanent   Install release package VERSION but"
        echo "                      don't make it permanent"
        ;;
    uninstall)
        echo "Usage: $REL_NAME uninstall [VERSION]"
        echo "Uninstalls a release VERSION, it will only accept"
        echo "versions that are not currently in use"
        ;;
    upgrade)
        echo "Usage: $REL_NAME upgrade [VERSION]"
        echo "Upgrades the currently running release to VERSION, it assumes"
        echo "that a release package tarball has already been deployed at one"
        echo "of the following locations:"
        echo "      releases/<relname>-<version>.tar.gz"
        echo "      releases/<relname>-<version>.zip"
        echo ""
        echo "     --no-permanent   Install release package VERSION but"
        echo "                      don't make it permanent"
        ;;
    downgrade)
        echo "Usage: $REL_NAME downgrade [VERSION]"
        echo "Downgrades the currently running release to VERSION, it assumes"
        echo "that a release package tarball has already been deployed at one"
        echo "of the following locations:"
        echo "      releases/<relname>-<version>.tar.gz"
        echo "      releases/<relname>-<version>.zip"
        echo ""
        echo "     --no-permanent   Install release package VERSION but"
        echo "                      don't make it permanent"
        ;;
    *)
        echo "Usage: $REL_NAME {start|ertspath|foreground|stop|pid|ping|console|console_clean|attach|remote_console|upgrade|downgrade|install|uninstall|versions|escript|ctl|rpc|rpcterms|eval|root_dir} <help>"
    ;;
    esac
}

COMMAND="${1:-}"

if [ "${2:-}" = 'help' ]; then
    ## 'ctl' command has its own usage info
    if [ "$COMMAND" != 'ctl' ]; then
        usage "$COMMAND"
        exit 0
    fi
fi

if ! check_erlang_start >/dev/null 2>&1; then
    BUILT_ON="$(head -1 "${REL_DIR}/BUILT_ON")"
    ## failed to start, might be due to missing libs, try to be portable
    export LD_LIBRARY_PATH="$DYNLIBS_DIR:$LD_LIBRARY_PATH"
    if ! check_erlang_start; then
        ## it's hopeless
        echoerr "FATAL: Unable to start Erlang."
        echoerr "Please make sure openssl-1.1.1 (libcrypto) and libncurses are installed."
        echoerr "Also ensure it's running on the correct platform,"
        echoerr "this EMQ X release is built for $BUILT_ON"
        exit 1
    fi
    echoerr "WARNING: There seem to be missing dynamic libs from the OS. Using libs from ${DYNLIBS_DIR}"
fi

# Make sure the user running this script is the owner and/or su to that user
check_user "$@"
ES=$?
if [ "$ES" -ne 0 ]; then
    exit $ES
fi

## IS_BOOT_COMMAND is set to be later used by `common_defs2.sh` to
## inspect node name and cookie from hocon config (or env variable),
## which also resides in `common_defs2.sh`.
case "${COMMAND}" in
    start|console|console_clean|foreground)
        IS_BOOT_COMMAND='yes'
        ;;
    *)
        IS_BOOT_COMMAND='no'
        ;;
esac
export IS_BOOT_COMMAND

# shellcheck disable=SC1090,SC1091
source "$BASE/bin/common_defs2.sh"

cd "$ROOTDIR"

case "${COMMAND}" in
    start)
        # Make sure a node IS not running
        if relx_nodetool "ping" >/dev/null 2>&1; then
            die "node_is_already_running!"
        fi

        # this flag passes down to console mode
        # so we know it's intended to be run in daemon mode
        export _EMQX_START_MODE="$COMMAND"

        case "$COMMAND" in
            start)
                shift
                START_OPTION="console"
                HEART_OPTION="start"
                ;;
        esac
        export START_OPTION
        RUN_PARAM="$*"

        # Set arguments for the heart command
        set -- "$RUNNER_SCRIPT" "$HEART_OPTION"
        [ "$RUN_PARAM" ] && set -- "$@" "$RUN_PARAM"

        # Export the HEART_COMMAND
        HEART_COMMAND="$RUNNER_SCRIPT $COMMAND"
        export HEART_COMMAND

        ## See: http://erlang.org/doc/man/run_erl.html
        # Export the RUN_ERL_LOG_GENERATIONS
        export RUN_ERL_LOG_GENERATIONS=${RUN_ERL_LOG_GENERATIONS:-"5"}

        # Export the RUN_ERL_LOG_MAXSIZE
        export RUN_ERL_LOG_MAXSIZE=${RUN_ERL_LOG_MAXSIZE:-"10485760"}

        mkdir -p "$PIPE_DIR"

        "$BINDIR/run_erl" -daemon "$PIPE_DIR" "$RUNNER_LOG_DIR" \
                          "$(relx_start_command)"

        WAIT_TIME=${WAIT_FOR_ERLANG:-15}
        if wait_for "$WAIT_TIME" 'relx_nodetool' 'ping'; then
            echo "$EMQX_DESCRIPTION $REL_VSN is started successfully!"
            exit 0
        else
            echo "$EMQX_DESCRIPTION $REL_VSN failed to start within ${WAIT_TIME} seconds,"
            echo "see the output of '$0 console' for more information."
            echo "If you want to wait longer, set the environment variable"
            echo "WAIT_FOR_ERLANG to the number of seconds to wait."
            exit 1
        fi
        ;;

    stop)
        # Wait for the node to completely stop...
        PID="$(relx_get_pid)"
        if ! relx_nodetool "stop"; then
            echoerr "Graceful shutdown failed PID=[$PID]"
            exit 1
        fi
        WAIT_TIME="${WAIT_FOR_ERLANG_STOP:-60}"
        if ! wait_for "$WAIT_TIME" 'is_down' "$PID"; then
            msg="dangling after ${WAIT_TIME} seconds"
            # also log to syslog
            logger -t "${REL_NAME}[${PID}]" "STOP: $msg"
            # log to user console
            echoerr "stop failed, $msg"
            echo "ERROR: $PID is still around"
            ps -p "$PID"
            exit 1
        fi
        logger -t "${REL_NAME}[${PID}]" "STOP: OK"
        ;;

    restart|reboot)
        echo "$EMQX_DESCRIPTION $REL_VSN is stopped: $("$RUNNER_BIN_DIR/$REL_NAME" stop)"
        "$RUNNER_BIN_DIR/$REL_NAME" start
        ;;

    pid)
        ## Get the VM's pid
        if ! relx_get_pid; then
            exit 1
        fi
        ;;

    ping)
        assert_node_alive
        echo pong
        ;;

    escript)
        ## Run an escript under the node's environment
        if ! relx_escript "$@"; then
            exit 1
        fi
        ;;

    attach)
        assert_node_alive

        shift
        exec "$BINDIR/to_erl" "$PIPE_DIR"
        ;;

    remote_console)
        assert_node_alive

        shift
        relx_rem_sh
        ;;

    upgrade|downgrade|install|unpack|uninstall)
        if [ -z "${2:-}" ]; then
            echo "Missing version argument"
            echo "Usage: $REL_NAME $COMMAND {version}"
            exit 1
        fi

        shift

        assert_node_alive

        ERL_FLAGS="${ERL_FLAGS:-} $EPMD_ARG" \
        exec "$BINDIR/escript" "$ROOTDIR/bin/install_upgrade.escript" \
             "$COMMAND" "{'$REL_NAME', \"$NAME_TYPE\", '$NAME', '$COOKIE'}" "$@"
        ;;

    versions)
        assert_node_alive

        shift

        ERL_FLAGS="${ERL_FLAGS:-} $EPMD_ARG" \
        exec "$BINDIR/escript" "$ROOTDIR/bin/install_upgrade.escript" \
             "versions" "{'$REL_NAME', \"$NAME_TYPE\", '$NAME', '$COOKIE'}" "$@"
        ;;

    console|console_clean)
        # .boot file typically just $REL_NAME (ie, the app name)
        # however, for debugging, sometimes start_clean.boot is useful.
        # For e.g. 'setup', one may even want to name another boot script.
        case "$COMMAND" in
            console)
                if [ -f "$REL_DIR/$REL_NAME.boot" ]; then
                  BOOTFILE="$REL_DIR/$REL_NAME"
                else
                  BOOTFILE="$REL_DIR/start"
                fi
                ;;
            console_clean)
                BOOTFILE="$REL_DIR/start_clean"
                ;;
        esac

        # set before generate_config
        if [ "${_EMQX_START_MODE:-}" = '' ]; then
            export EMQX_LOG__CONSOLE_HANDLER__ENABLE="${EMQX_LOG__CONSOLE_HANDLER__ENABLE:-true}"
        fi

        #generate app.config and vm.args
        generate_config "$NAME_TYPE" "$NAME"

        # Setup beam-required vars
        EMU="beam"
        PROGNAME="${0#*/}"

        export EMU
        export PROGNAME

        # Store passed arguments since they will be erased by `set`
        ARGS="$*"

        # shellcheck disable=SC2086 # $CONFIG_ARGS $EPMD_ARG are supposed to be split by whitespace
        # Build an array of arguments to pass to exec later on
        # Build it here because this command will be used for logging.
        if [ "$IS_ELIXIR" = yes ]
        then
          set -- "$REL_DIR/iex" \
              --boot "$BOOTFILE" \
              --erl "-mode $CODE_LOADING_MODE" \
              --boot-var RELEASE_LIB "$ERTS_LIB_DIR" \
              --erl "-mnesia dir \"${MNESIA_DATA_DIR}\"" \
              --erl "$CONFIG_ARGS" \
              --erl "$EPMD_ARG" \
              --werl
        else
          set -- "$BINDIR/erlexec" \
              -boot "$BOOTFILE" -mode "$CODE_LOADING_MODE" \
              -boot_var ERTS_LIB_DIR "$ERTS_LIB_DIR" \
              -mnesia dir "\"${MNESIA_DATA_DIR}\"" \
              $CONFIG_ARGS $EPMD_ARG
        fi

        # Log the startup
        logger -t "${REL_NAME}[$$]" "EXEC: $* -- ${1+$ARGS}"

        # Start the VM
        exec "$@" -- ${1+$ARGS}
        ;;

    foreground)
        # start up the release in the foreground for use by runit
        # or other supervision services

        # set before generate_config
        export EMQX_LOG__CONSOLE_HANDLER__ENABLE="${EMQX_LOG__CONSOLE_HANDLER__ENABLE:-true}"

        #generate app.config and vm.args
        generate_config "$NAME_TYPE" "$NAME"

        [ -f "$REL_DIR/$REL_NAME.boot" ] && BOOTFILE="$REL_NAME" || BOOTFILE=start
        FOREGROUNDOPTIONS="-noshell -noinput +Bd"

        # Setup beam-required vars
        EMU=beam
        PROGNAME="${0#*/}"

        export EMU
        export PROGNAME

        # Store passed arguments since they will be erased by `set`
        ARGS="$*"

        # shellcheck disable=SC2086 # $CONFIG_ARGS $EPMD_ARG are supposed to be split by whitespace
        # Build an array of arguments to pass to exec later on
        # Build it here because this command will be used for logging.
        if [ "$IS_ELIXIR" = yes ]
        then
          set -- "$REL_DIR/elixir" \
              --boot "$REL_DIR/start" \
              --erl "$FOREGROUNDOPTIONS" \
              --erl "-mode $CODE_LOADING_MODE" \
              --boot-var RELEASE_LIB "$ERTS_LIB_DIR" \
              --boot-var ERTS_LIB_DIR "$ERTS_LIB_DIR" \
              --erl "-mnesia dir \"${MNESIA_DATA_DIR}\"" \
              --erl "$CONFIG_ARGS" \
              --erl "$EPMD_ARG" \
              --no-halt
        else
          set -- "$BINDIR/erlexec" $FOREGROUNDOPTIONS \
              -boot "$REL_DIR/$BOOTFILE" -mode "$CODE_LOADING_MODE" \
              -boot_var ERTS_LIB_DIR "$ERTS_LIB_DIR" \
              -mnesia dir "\"${MNESIA_DATA_DIR}\"" \
              $CONFIG_ARGS $EPMD_ARG
        fi

        # Log the startup
        logger -t "${REL_NAME}[$$]" "EXEC: $* -- ${1+$ARGS}"

        # Start the VM
        exec "$@" -- ${1+$ARGS}
        ;;
    ertspath)
        echo "$ERTS_PATH"
        ;;
    ctl)
        assert_node_alive

        shift

        relx_nodetool rpc_infinity emqx_ctl run_command "$@"
        ;;
    rpc)
        assert_node_alive

        shift

        relx_nodetool rpc "$@"
        ;;
    rpcterms)
        assert_node_alive

        shift

        relx_nodetool rpcterms "$@"
        ;;
    root_dir)
        assert_node_alive

        shift
        relx_nodetool "eval" 'code:root_dir()'
        ;;
    eval)
        assert_node_alive

        shift
        if [ "$IS_ELIXIR" = "yes" ]
        then
          "$REL_DIR/elixir" \
              --hidden \
              --cookie "$COOKIE" \
              --boot "$REL_DIR/start_clean" \
              --boot-var RELEASE_LIB "$ERTS_LIB_DIR" \
              --vm-args "$(latest_vm_args 'EMQX_NODE__NAME')"\
              --rpc-eval "$NAME" "$@"
        else
          relx_nodetool "eval" "$@"
        fi
        ;;
    *)
        usage "$COMMAND"
        exit 1
        ;;
esac

exit 0
