#!/bin/bash
# -*- mode: sh; c-basic-offset: 4; tab-width: 8; indent-tabs-mode: nil -*-
# vi: set shiftwidth=4 tabstop=8 softtabstop=4 expandtab:
# :indentSize=4:tabSize=8:noTabs=true:
# vim: filetype=sh

# ---- error handling
set -o errexit;
set -o posix;
set -o pipefail;
set -o errtrace;
set -o nounset;

unexpected_error() {
    local errstat=$?
    echo "${g_prog:-$(basename "$0")}: Error! Encountered unexpected error at 'line $(caller)', bailing out..." 1>&2
    exit $errstat;
}
trap unexpected_error ERR;

# ---- error functions

merror() {
    echo "$g_prog: Error! ""$@" 1>&2;
    exit 1;
}
minterror() {
    echo "$g_prog: Internal Error! ""$@" 1>&2;
    exit 1;
}
mwarn() {
    echo "$g_prog: Warning! ""$@" 1>&2;
}

# ---- usage

usage() {
    local exitstat=2;
    if [[ ! -z "${2:-}" ]] ; then
        if [[ ! $2 =~ [[:digit:]]+ ]] ; then
            minterror "usage(): exitstat ($2) must be numeric."
        fi
        exitstat=$2;
    fi

    # Only redirect to stderr on non-zero exit status
    if [[ $exitstat -ne 0 ]] ; then
        exec 1>&2;
    fi

    if [[ ! -z "${1:-}" ]] ; then
        echo "$g_prog: Error! $1"
    fi

    echo "Usage: $g_prog [--help] [-n|--noexec] [-v|--verbose] \\"
    echo "               [--url url] [--timeout] \\"
    echo "               --bundle --upload"
    echo "       $g_prog [--help] [-n|--noexec] [-v|--verbose] \\"
    echo "               [--url url] [--timeout] \\"
    echo "               --bundle"
    echo "       $g_prog [--help] [-n|--noexec] [-v|--verbose] \\"
    echo "               [--url url] [--timeout] \\"
    echo "               --upload"
    echo "       $g_prog [--help] [--runnable]"
    echo ""
    echo "         --bundle      -- create the install troubleshooting bundle"
    echo "         --upload      -- upload the install troubleshooting bundle"
    echo "         --url         -- specify alternate upload url"
    echo "         --timeout     -- timeout for upload request (default: 300s)"
    echo "         -n|--noexec   -- no exec mode, print commands that would execute"
    echo "         -v|--verbose  -- verbose mode, print commands that are executed"
    echo "         --runnable    -- sanity check to verify all prerequisites for"
    echo "                          running this program are available"
    echo "         -h|--help     -- print this usage";
    echo ""
    echo "         Default is '--bundle --upload' if uploading enabled in SMRT Link,"
    echo "         just '--bundle' otherwise"

    echo ""
    echo ""
    echo "    Typically, the following command will be used in order to create a"
    echo "    tarball bundle of installer troubleshooting log and configuration files"
    echo "    and send it to PacBio Technical Support via the SMRT Link Event Service:"
    echo ""
    echo "          $g_prog --bundle --update"

    if [[ $exitstat -ne 0 ]] ; then
        # Print error again, useful for long usages messages
        if [[ ! -z "${1:-}" ]] ; then
            echo ""
            echo "$g_prog: Error! $1"
        fi
    fi

    # bash only:
    if [[ $exitstat -ne 0 ]] ; then
        echo "  at: $(caller)";
    fi
    exit $exitstat;
}

# ---- argument parsing

achk() {
    if [[ $1 -eq 0 ]] ; then usage "Missing argument to $2 option"; fi
}

parseargs() {
    opt_bundle=false;
    opt_upload=false;
    opt_url="";
    opt_noexec=false;
    opt_verbose=false;
    opt_timeout="";
    opt_runnable=false;

    while [[ $# != 0 ]]; do
        opt="$1"; shift;
        case "$opt" in
            # Flag with no argument example:
            #   --flag|--fla|--fl|--f)
            #     opt_flag=true;;
            # Option with argument example:
            #   --arg|--ar|--a)
            #     achk $# $opt; opt_somearg=$1; shift;;
            --bundle) opt_bundle=true;;
            --upload) opt_upload=true;;
            --url) achk $# $opt; opt_url=$1; shift;;
            -n|--noexec) opt_noexec=true;;
            -v|--verbose) opt_verbose=true;;
            --timeout) achk $# $opt; opt_timeout=$1; shift;;
            --runnable) opt_runnable=true;;
            -h|-help|--help|--hel|--he|--h) usage "" 0;;
            -*) usage "Unrecognized option: $opt";;
            *)
                local argstr=${1+"$@"};
                usage "Unexpected extraneous arguments detected: $opt $argstr"
                ;;
        esac
    done

    if ! $opt_runnable; then
        if ! $opt_bundle && ! $opt_upload; then
            usage "Must specify at least one of --bundle or --upload options".
        fi
    fi

    if  [[ ! -z "$opt_timeout" ]] &&
        [[ ! $opt_timeout =~ ^[[:digit:]]+$ ]] ; then
        usage "Argument to --timeout must be numeric"
    fi
}

# ---- globals

set_preglobals() {
    # Force the path to only what we need (/sbin needed for ifconfig)
    PATH_ORIG=$PATH
    PATH="/usr/bin:/bin"

    g_prog=$(basename "$0");
    g_progdir=$(dirname "$0");
    g_progdir_abs=$(dirname "$(readlink -f "$0")");

    # ---- global env

    . "$g_progdir_abs/../../private/runtime-common/lib/globalenv.ish"
}

set_globals() {
    g_topdir=$(readlink -f "$g_progdir_abs/../..")
    g_javahome="$g_topdir/bundles/smrtlink-analysisservices-gui/current/private/thirdparty/java/jre/jre_8u192b12-hotspot"
    g_sltoolsbin="$g_topdir/bundles/smrtlink-analysisservices-gui/current/private/pacbio/smrtlink-analysisservices-gui/tools/bin"

    g_path="/usr/bin:/bin"

    g_tsbundler_exe="$g_sltoolsbin/tech-support-bundler"
    g_tsuploader_exe="$g_sltoolsbin/tech-support-uploader"

    g_userdata_dir="$g_progdir/../../../..//userdata"
    g_userdata_dirabs=$(readlink -f "$g_userdata_dir")
    g_tsreport_dir="$g_userdata_dirabs/tsreport"

    g_tsreport_logdir="$g_tsreport_dir/log"
    g_tsreport_datadir="$g_tsreport_dir/data"

    g_tsreport_sluuid_file="$g_tsreport_dir/sluuid"
    g_tsreport_dnsname_file="$g_tsreport_dir/dnsname"
    g_tsreport_eve_url_file="$g_tsreport_dir/eve_url"

    g_datestr=$(date +%Y%m%d_%H%M%S)


    g_bundler_logfile="$g_tsreport_logdir/ts-bundler.$g_datestr.log"
    g_bundler_logfile_link="$g_tsreport_logdir/ts-bundler.log"

    g_uploader_logfile="$g_tsreport_logdir/ts-uploader.$g_datestr.log"
    g_uploader_logfile_link="$g_tsreport_logdir/ts-uploader.log"

    g_tarball="$g_tsreport_datadir/ts-install.$g_datestr.tgz"
    g_tarball_link="$g_tsreport_datadir/ts-install.tgz"

    # verbose
    g_verbose_level=0;
    if $opt_verbose; then
        g_verbose_level=1;
    fi

    # noexec
    g_noexec=$opt_noexec;
}


# ---- subroutines

is_runnable() {
    local verbose=false;
    [[ x"${1:-}" != x"quiet" ]] && verbose=true;
    local runnable=true;
    local unextracted=false;

    $verbose && echo "Checking prerequisites..."

    if [[ ! -x "$g_tsbundler_exe" ]] ; then
        # $verbose && echo "  Bundler executable ($g_tsbundler_exe) does not exist..."
        $verbose && echo "  Bundler executable does not exist..."
        runnable=false;
        unextracted=true;
    fi
    if [[ ! -x "$g_tsuploader_exe" ]] ; then
        # $verbose && echo "  Uploader executable ($g_tsuploader_exe) does not exist..."
        $verbose && echo "  Uploader executable does not exist..."
        runnable=false;
        unextracted=true;
    fi

    # Check if we have java available
    if [[ ! -d "$g_javahome" ]]; then
        # $verbose && echo "  Java ($g_javahome) does not exist..."
        $verbose && echo "  Java JRE does not exist..."
        runnable=false;
        unextracted=true;
    fi

    # Check that we have a uuid file and it looks like a uuid
    if [[ -r "$g_tsreport_sluuid_file" ]]; then
        local sluuid;
        sluuid=$(cat "$g_tsreport_sluuid_file");
            if [[ ! $sluuid =~ ^[\-a-f0-9]+$ ]] ; then
            echo "  Invalid SMRT Link uuid ($sluuid)..."
            runnable=false;
        fi
    else
        # $verbose && echo "  SMRT Link uuid file ($g_tsreport_sluuid_file) does not exist..."
        $verbose && echo "  SMRT Link uuid file does not exist..."
        runnable=false;
    fi

    if $unextracted; then
        echo "  SMRT Link installation directory not properly extracted."
    fi

    if ! $runnable; then
        $verbose && echo "    Prerequisites not available, unable to run."
        return 1;
    fi
    $verbose && echo "    All prerequisites available."
    return 0;
}


runcmd() {
    local -a envs;
    local -a args;

    # Separate the leading environment variable settings from the command args
    local envdone=false;
    for i in ${1+"$@"}; do
        if ! $envdone && [[ $i =~ ^([[:alpha:]_][[:alnum:]_]*)=([\"\'].*) ]]; then
            envs+=( "${BASH_REMATCH[1]}=${BASH_REMATCH[2]}" )
        elif ! $envdone && [[ $i =~ ^([[:alpha:]_][[:alnum:]_]*)=(.*) ]]; then
            envs+=( "${BASH_REMATCH[1]}='${BASH_REMATCH[2]}'" )
        else
            envdone=true;
            # NOTE: with "set -u nounset" set, we have to use += or do
            #       something like this (since "${args[@]}" and "${#args[@]}"
            #       will give errors if the array is empty).  Another option
            #       is:
            #           args=( "${args[@]+${args[@]}}" "$i" );
            args+=( "$i" );
        fi
    done

    # Determine the command in a format that can be printed out and
    # copy-and-pasted directly into a shell and can be run with an "eval".
    # NOTE: Another option is to use:
    #          cmd=$(printf '%q ' ${1+"$@"})
    #       But it will use backslashes instead of quotes, which is fine for
    #       executing, but it doesn't look as natural as with quotes.  Plus
    #       using quotes make it look like the trace output under "set -x"
    #       or "set -o xtrace"
    local cmd;
    cmd=$( PS4=; set -x; ( true "${args[@]}" ) 2>&1 );
    cmd=${cmd#true };
    [[ ! -z "${envs[@]+set}" ]] && cmd="${envs[@]} $cmd"

    # Print out the command if neceassry.  The output should be
    # copy-and-pastable directly into a shell.
    if [[ $g_verbose_level -ge 1 ]] || $g_noexec; then
        $g_noexec && echo -n "noexec: "
        echo "$cmd"
    fi

    if ! $g_noexec; then
        # The 'eval "$cmd"' will cause an error in bash-3.2.25 (on centos-5)
        # if $cmd exits with non-zero status (it does not in bash 4.x).  Need
        # to put the '|| stat=$?' inside the "eval" to make it work for both.
        # But also note that in order for this to work properly, the "$cmd"
        # part must be double quoted, the "||" must be quoted or escaped, and
        # the '$?' must be single quoted or escaped (so tht it does not get
        # evaluated before the eval get executed).
        # So in order properly handle a commadn line this:
        #
        #    cmd=cmd="bash -c 'echo foo; echo \"foo bar\" blah\ blah; exit 23'"
        #
        # The eval statement should somthing look like one of these:
        #
        #    eval "$cmd || stat=\$?"
        #    eval "$cmd"' || stat=$?'
        #    eval "$cmd" \|\| stat='$?'
        #    eval "$cmd" "||" stat='$?'
        stat=0;
        eval "$cmd || stat=\$?"
        return $stat
    fi
    return 0

    # Run the command
    if ! $g_noexec; then
        eval "$cmd"
    fi
}

# ---- main

# Save off original cmdline args, use as "${g_origargs[@]}" (with doublequotes)
g_origargs=( ${1+"$@"} )

set_preglobals ${1+"$@"};
parseargs ${1+"$@"};
set_globals;

stat=0;
is_runnable || stat=$?;

if $opt_runnable; then
    exit $stat;
fi

if [[ $stat -ne 0 ]]; then
    echo
    echo "For troubleshooting help, contact the PacBio Tech Support Team"
    echo
    merror "Prequisites for running not available, exiting..."
fi

# Compute 'sluser'
g_sluser=$(stat --format "%U" "$g_progdir/installprompter")
# Check to see if current user matches install user
g_sluid=$(stat --format "%u" "$g_progdir/installprompter")
g_uid=$(id -u)
if [[ $g_sluid -ne $g_uid ]] ; then
    merror "Current user '$(id -n -u)' does not match SMRT Link install user '$g_sluser'"
fi


# Compute 'dnsname'
g_dnsname=""
if [[ -r "$g_tsreport_dnsname_file" ]] ; then
    g_dnsname=$(cat "$g_tsreport_dnsname_file")
fi
if [[ -z "$g_dnsname" ]] ; then
    hostname_short=$(hostname 2> /dev/null || true)
    hostname_fqdn=$(hostname --fqdn 2> /dev/null || true)
    hostname_short_len=$(echo "$hostname_short" | wc -c)
    hostname_fqdn_len=$(echo "$hostname_fqdn" | wc -c)

    hostname=$hostname_short;
    if [[ $hostname_fqdn_len -gt $hostname_short_len ]] ; then
        hostname=$hostname_fqdn;
    fi
    g_dnsname="UNKNOWN--$hostname"
fi

# Compute 'sluuid'
if [[ ! -e "$g_tsreport_sluuid_file" ]] ; then
    merror "Could not find the smrtlink uuid file ($g_tsreport_sluuid_file)."
fi
g_sluuid=$(cat "$g_tsreport_sluuid_file");

# Compute 'slversion'
g_slversion=$(cat "$g_topdir/etc/versionstr.txt")
g_slversion=${g_slversion#*_}

# Compute 'timeout'
g_timeoutarg=""
if [[ ! -z "$opt_timeout" ]] ; then
    g_timeoutarg="--timeout $opt_timeout"
fi

# Determine if running bundler and/or uploader
g_run_bundler=false;
if $opt_bundle; then
    g_run_bundler=true;
fi

g_run_uploader=false;
if $opt_upload; then
    g_run_uploader=true;
fi


runcmd mkdir -p "$g_tsreport_logdir"
runcmd mkdir -p "$g_tsreport_datadir"

if $g_run_bundler; then

    echo ""
    echo "Generating install troubleshooting tarball..."

    runcmd env -i PATH="$g_path" JAVA_HOME="$g_javahome" JAVA_OPTS="-Djava.library.path=" "$g_tsbundler_exe" \
        --debug \
        --log-file "$g_bundler_logfile" \
        --user "$g_sluser" \
        --smrtlink-id "$g_sluuid" \
        --smrtlink-version "$g_slversion" \
        --dns "$g_dnsname" \
        --output "$g_tarball" \
        "$g_userdata_dirabs"

    if ! $g_noexec; then
        if [[ ! -e "$g_tarball" ]] ; then
            minterror "Tarball '$g_tarball' not created."
        fi
    fi

    runcmd rm -f "$g_tarball_link"
    runcmd ln -s "$(basename "$g_tarball")" "$g_tarball_link";

    runcmd rm -f "$g_bundler_logfile_link"
    runcmd ln -s "$(basename "$g_bundler_logfile")" "$g_bundler_logfile_link";

fi

if $g_run_uploader; then

    g_eve_url=""
    if [[ ! -z "$opt_url" ]] ; then
        g_eve_url=$opt_url;
    elif [[ -e "$g_tsreport_eve_url_file" ]] ; then
        g_eve_url=$(cat "$g_tsreport_eve_url_file");
    fi
    if [[ -z "$g_eve_url" ]] ; then
        merror "Could not determine upload url."
    fi

    if [[ ! -e "$g_tarball_link" ]] ; then
        merror "No current tarball exists, use --bundle to create.  Exiting..."
    fi

    echo ""
    echo "Uploading install troubleshooting tarball..."

    exitstat=0;
    stat=0
    runcmd env -i PATH="$g_path" JAVA_HOME="$g_javahome" JAVA_OPTS="-Djava.library.path=" "$g_tsuploader_exe" \
        --debug \
        --log-file "$g_uploader_logfile" \
        --url "$g_eve_url" \
        $g_timeoutarg \
        "$g_tarball_link" || stat=$?

    if [[ $stat -ne 0 ]] ; then
        echo "Error detected in uploading tarball.  Upload not successful." 1>&2
        exitstat=$stat
    fi

    runcmd rm -f "$g_uploader_logfile_link"
    runcmd ln -s "$(basename "$g_uploader_logfile")" "$g_uploader_logfile_link";
    exit $exitstat
fi
