#!/usr/bin/env sh

{ # this ensures the entire script is downloaded #

set -u

SB_UPDATE_ROOT="https://igor.sbgenomics.com/downloads/sb"

usage() {
    cat 1>&2 <<EOF
install.sh
The installer for Seven Bridges Command Line Interface (SB CLI)

USAGE:
    install.sh [FLAGS]

FLAGS:
    -v, --verbose               Enable verbose output
    -y                          Disable confirmation prompt
        --no-bash-completion    Don't configure bash command completion
        --no-man-pages          Don't extract man pages
        --uninstall             Performs uninstall
    -h, --help                  Prints help information

EOF
}

main() {
    step "Performing environment sanity checks..."

    need_cmd uname
    need_cmd curl
    need_cmd mktemp
    need_cmd chmod
    need_cmd mkdir
    need_cmd rm
    need_cmd rmdir
    need_cmd manpath
    need_cmd tr
    need_cmd shasum

    # check if we have to use /dev/tty to prompt the user
    local need_tty=yes
    local verbose=no
    local add_completion=yes
    local add_man_pages=yes
    for arg in "$@"; do
        case "$arg" in
            -h|--help)
                usage
                exit 0
                ;;
            -y)
                # user wants to skip the prompt -- we don't need /dev/tty
                need_tty=no
                ;;
            -v|--verbose)
                # user wants to see verbose output
                verbose=yes
                ;;
            --no-bash-completion)
                # user doesn't want bash completion
                add_completion=no
                ;;
            --no-man-pages)
                # user doesn't want man pages
                add_man_pages=no
                ;;
            --uninstall)
                do_uninstall "$verbose" "$need_tty"
                exit 0
                ;;
            *)
                ;;
        esac
    done

    get_architecture || return 1
    local _arch="$RETVAL"
    assert_nz "$_arch" "arch"

    if [ "$verbose" = "yes" ]; then
        msg_verbose "Discovered architecture: '$_arch'"
    fi

    local _ext=""
    case "$_arch" in
        *windows*)
            _ext=".exe"
            ;;
    esac

    local _url="$SB_UPDATE_ROOT/$_arch/sb$_ext"
    local _shasum_url="$SB_UPDATE_ROOT/$_arch/sb.shasum"

    if [ "$verbose" = "yes" ]; then
        msg_verbose "Application url: '$_url'"
    fi

    local _dir="$(mktemp -d 2>/dev/null || ensure mktemp -d -t sb)"
    local _file="$_dir/sb$_ext"
    local _shasum_file="$_dir/sb.shasum"

    if [ "$verbose" = "yes" ]; then
        msg_verbose "Downloading to: '$_dir'"
    fi

    step "Installing SB CLI..."

    # Download
    {
        substep "Downloading installer: "

        ensure_substep mkdir -p "$_dir"
        ensure_substep curl -sSfL "$_url" -o "$_file"
        ensure_substep curl -sSfL "$_shasum_url" -o "$_shasum_file"

        substep_ok
    }

    # Validate shasum
    {
        if [ "$verbose" = "yes" ]; then
            msg_verbose "Validating SHA-1 sum for the downloaded file"
        fi

        _remote_shasum_str=$(cat $_shasum_file | awk {'print $1'})

        if [ "$verbose" = "yes" ]; then
            msg_verbose "Expected SHA-1 sum for the file: '$_remote_shasum_str'"
        fi

        _local_shasum_str=$(shasum "$_file" | awk {'print $1'})

        if [ "$verbose" = "yes" ]; then
            msg_verbose "Calculated SHA-1 sum for downloaded file: '$_local_shasum_str'"
        fi

        substep "Validating SHA-1 sum: "

        if [ "$_remote_shasum_str" != "$_local_shasum_str" ]; then
            substep_err
            msg ""
            msg "Verification of downloaded file failed:"
            msg "* expected SHA-1 sum:   '$_remote_shasum_str'"
            msg "* calculated SHA-1 sum: '$_local_shasum_str'"
            msg ""
            exit 1
        fi

        substep_ok
    }

    local destination_file=/usr/local/bin/`basename "$_file"`
    # Install
    {
        if [ "$verbose" = "yes" ]; then
            msg_verbose "Installing to '/usr/local/bin'"
        fi

        substep "Installing SB CLI binary: "

        ensure_substep cp "$_file" /usr/local/bin/
        ensure_substep chmod a+x "$destination_file"
        if [ ! -x "$destination_file" ]; then
            substep_err
            msg ""
            msg "Cannot execute $destination_file (likely because file system is mounted as noexec)."
            msg "Please copy the file to a location where you can execute binaries and run chmod u+x sb"
            msg ""
            exit 1
        fi

        substep_ok
    }

    # Bash completion
    bash_completion_substep() {
        substep "Adding bash completion: "

        local _completion_location="/etc/bash_completion.d/sb.bash-completion"
        if [ "$OS" = darwin ]; then
            _completion_location="/usr/local/etc/bash_completion.d/sb.bash-completion"
        fi

        if [ ! -d `dirname $_completion_location` ]; then
            substep_skip "Directory `dirname $_completion_location` does not exist."
            return 0
        fi

        generate_completion_script() {
            "$destination_file" --quiet gen completion --completionfile "$_completion_location"
        }
        ensure_substep generate_completion_script

        if [ ! -f "$_completion_location" ]; then
            substep_err
            msg ""
            msg "Can not reach generated completion file."
            msg ""
            exit 1
        fi

        substep_ok

        if [ "$verbose" = "yes" ]; then
            msg_verbose "Added bash completion file to: $_completion_location"
        fi

        return 0
    }
    if [ "$add_completion" = "yes" ]; then
        bash_completion_substep
    fi

    # Man pages
    man_pages_substep() {
        substep "Adding man pages: "

        local _man_directory_destination=""
        for location in `manpath | tr ':' ' '`; do
            local _candidate_dir="$location/man1"
            if [ -d "$_candidate_dir" ]; then
                _man_directory_destination="$_candidate_dir"
                break
            fi
        done

        if [ "$_man_directory_destination" = "" ]; then
            # manpath is likely empty or wrong. Trying with default directories for manpages.
            if [ -d "/usr/share/man/man1" ]; then
                _man_directory_destination="/usr/share/man/man1"
            else
                if [ -d "/usr/local/share/man/man1" ]; then
                    _man_directory_destination="/usr/local/share/man/man1"
                fi
            fi
        fi

        if [ "$_man_directory_destination" = "" ]; then
            # No suitable directory for man pages found. Giving up.
            substep_skip "Cannot locate directory for man pages."
            return 0
        fi

        generate_man_pages_script() {
            "$destination_file" --quiet gen man --dir "$_man_directory_destination"
        }
        ensure_substep generate_man_pages_script

        substep_ok

        if [ "$verbose" = "yes" ]; then
            msg_verbose "Added man pages to: $_man_directory_destination"
        fi

        return 0
    }
    if [ "$add_man_pages" = "yes" ]; then
        man_pages_substep
    fi

    local _retval=$?

    ignore rm "$_file"
    ignore rm "$_shasum_file"
    ignore rmdir "$_dir"

    if [ "$verbose" = "yes" ]; then
        msg_verbose "Exit code: '$_retval'"
    fi

    return "$_retval"
}

do_uninstall() {
    local _verbose="$1"
    local _need_tty="$2"

    get_architecture || return 1
    local _arch="$RETVAL"

    local _ext=""
    case "$_arch" in
        *windows*)
            _ext=".exe"
            ;;
    esac

    if [ "$_need_tty" = "yes" ]; then
        # The installer is going to want to ask for confirmation by
        # reading stdin.  This script was piped into `sh` though and
        # doesn't have stdin to pass to its children. Instead we're going
        # to explicitly connect /dev/tty to the installer's stdin.
        if [ ! -t 1 ]; then
            err "Unable to run interactively. Run with -y to accept defaults, --help for additional options."
        fi

        if [ -t 0 ]; then
            # We're in an interactive terminal. Prompt for uninstall.
            msg ""
            msg "  You are about to uninstall SB CLI. Please press 'Enter' to continue or 'Ctrl-C' to cancel."
            read _confirm_uninstall
        fi
    fi

    step "Uninstalling SB CLI..."

    local destination_file="/usr/local/bin/sb$_ext"

    # Removing SB CLI binary
    {
        if [ "$_verbose" = "yes" ]; then
            msg_verbose "Removing $destination_file"
        fi

        substep "Removing SB CLI binary: "

        ensure_substep_ignore rm "$destination_file"

        substep_ok
    }

    # Removing bash completion script
    {
        remove_bash_completion_substep_start() {
            substep "Removing bash completion: "
        }

        local _completion_location="/etc/bash_completion.d/sb.bash-completion"
        if [ "$OS" = darwin ]; then
            _completion_location="/usr/local/etc/bash_completion.d/sb.bash-completion"
        fi

        if [ ! -f "$_completion_location" ]; then
            # Completion directory does not exist. Skipping this step.
            if [ "$_verbose" = "yes" ]; then
                msg_verbose "Directory $_completion_location not found. Assuming completion is not installed..."
            fi

            remove_bash_completion_substep_start

            substep_skip "Bash completion script not found."
        else
            if [ "$_verbose" = "yes" ]; then
                msg_verbose "Removing $_completion_location and it's content..."
            fi

            remove_bash_completion_substep_start

            ensure_substep_ignore rm "$_completion_location"

            substep_ok
        fi
    }

    # Removing man pages
    {
        remove_man_pages_substep_start() {
            substep "Removing man pages: "
        }

        local _man_directory_destination=""
        local _man_pages_deleted="no"

        for location in `manpath | tr ':' ' '`; do
            local _candidate_files="$location/man1/sb-*"
            local _main_file="$location/man1/sb.1"
            if [ -f "$_main_file" ]; then
                if [ "$_verbose" = "yes" ]; then
                    msg_verbose "Removing $location/man1/sb-*..."
                fi
                remove_man_pages_substep_start
                for _next_file in $_candidate_files; do
                    ensure_substep_ignore rm "$_next_file"
                done
                ignore rm "$_main_file"
                _man_pages_deleted="yes"
                break
            fi
        done

        if [ "$_man_pages_deleted" = "no" ]; then
            # manpath is likely empty or wrong. Trying with default directories for man pages.
            if [ -d "/usr/share/man/man1" ]; then
                _man_directory_destination="/usr/share/man/man1"
            else
                if [ -d "/usr/local/share/man/man1" ]; then
                    _man_directory_destination="/usr/local/share/man/man1"
                fi
            fi

            if [ "$_man_directory_destination" != "" ]; then
                if [ "$_verbose" = "yes" ]; then
                    msg_verbose "Removing $_man_directory_destination/sb-*..."
                fi
                remove_man_pages_substep_start
                if [ -f "$_man_directory_destination/sb.1" ]; then
                    ensure_substep_ignore rm "$_man_directory_destination/sb-*"
                    ignore rm "$_man_directory_destination/sb.1"
                    _man_pages_deleted="yes"
                else
                    substep_skip "Man pages not found."
                fi
            fi
        fi

        if [ "$_man_pages_deleted" = "yes" ]; then
            substep_ok
        fi
    }

    msg ""
    msg "  SB CLI uninstalled successfully."
    msg ""
}

get_architecture() {
    local _ostype="$(uname -s)"
    local _cputype="$(uname -m)"

    if [ "$_ostype" = Darwin -a "$_cputype" = i386 ]; then
        # Darwin `uname -s` lies
        if sysctl hw.optional.x86_64 | grep -q ': 1'; then
            local _cputype=amd64
        fi
    fi

    case "$_ostype" in

        Linux)
            local _ostype=linux
            ;;

        FreeBSD)
            local _ostype=freebsd
            ;;

        Darwin)
            local _ostype=darwin
            ;;

        MINGW* | MSYS* | CYGWIN*)
            local _ostype=windows
            ;;

        *)
            err "unrecognized OS type: $_ostype"
            ;;
    esac

    case "$_cputype" in

        i386 | i486 | i686 | i786 | x86)
            local _cputype=386
            ;;

        xscale | arm)
            local _cputype=arm
            ;;

        aarch64 | arm64)
            local _cputype=arm64
            ;;

        x86_64 | x86-64 | x64 | amd64)
            local _cputype=amd64
            ;;

        *)
            err "unknown CPU type: $_cputype"
    esac

    local _arch="$_ostype-$_cputype"

    OS="$_ostype"
    RETVAL="$_arch"
}

bold=""
underline=""
standout=""
normal=""
black=""
red=""
green=""
yellow=""
blue=""
magenta=""
cyan=""
white=""

# Check if stdout is a terminal...
if [ -t 1 ]; then

    # See if it supports colors...
    ncolors=$(tput colors)

    if [ -n "$ncolors" ] && [ $ncolors -ge 8 ]; then
        bold="$(tput bold)"
        underline="$(tput smul)"
        standout="$(tput smso)"
        normal="$(tput sgr0)"
        black="$(tput setaf 0)"
        red="$(tput setaf 1)"
        green="$(tput setaf 2)"
        yellow="$(tput setaf 3)"
        blue="$(tput setaf 4)"
        magenta="$(tput setaf 5)"
        cyan="$(tput setaf 6)"
        white="$(tput setaf 7)"
    fi
fi

# Define a bunch of pretty output helpers
output() {
    fmt="$1"
    text="$2"
    printf -- "$fmt" "$text"
}

msg() {
    output "%s\n" "$1"
}

msg_verbose() {
    output "${bold}debug: ${normal}%s\n" "$1"
}

msg_err() {
    output "%s\n" "$1" 1>&2
}

err() {
    msg_err "$@"
    exit 1
}

step() {
    output "--> %s\n" "$1"
}

substep() {
    output "-->  %s" "$1"
}

substep_ok() {
    output "${green}OK${normal}\n" ""
}

substep_err() {
    output "${red}FAIL${normal}\n" ""
}

substep_skip() {
    output "${yellow}SKIPPED${normal}\n      $1\n" $1
}

need_cmd() {
    if ! command -v "$1" > /dev/null 2>&1
    then err "need '$1' (command not found)"
    fi
}

need_ok() {
    if [ $? != 0 ]; then err "$1"; fi
}

need_ok_substep() {
    if [ $? != 0 ]; then substep_err; err "$1"; fi
}

assert_nz() {
    if [ -z "$1" ]; then err "assert_nz $2"; fi
}

# Run a command that should never fail. If the command fails execution
# will immediately terminate with an error showing the failing
# command.
ensure() {
    "$@"
    need_ok "command failed: $*"
}

ensure_substep() {
    "$@"
    need_ok_substep "command failed: $*"
}

ensure_substep_ignore() {
    ignore "$@"
    need_ok_substep "command failed: $*"
}

# This is just for indicating that commands' results are being
# intentionally ignored. Usually, because it's being executed
# as part of error handling.
ignore() {
    "$@" > /dev/null 2>&1
}

main "$@" || exit 1

msg ""
msg "  SB CLI installed successfully."
msg ""

} # this ensures the entire script is downloaded #
