#!/bin/bash
#
# THIS FILE IS PART OF THE CYLC SUITE ENGINE.
# Copyright (C) 2008-2018 NIWA & British Crown (Met Office) & Contributors.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
CATEGORIES=('control' 'con' 'information' 'info' 'all' 'task' 'license' \
            'GPL' 'admin' 'prep' 'preparation' 'hook' 'discovery' 'utility' \
            'util')
                
HELP_OPTS=('help' '--help' '-h' 'h' '?')

get_version() {
    # If there is a version file present, use that
    if [[ -f "${CYLC_DIR}/VERSION" ]]; then
        CYLC_VERSION="$(cat "${CYLC_DIR}/VERSION")"
    # Otherwise, use git (if in a git repo and not a bare clone)
    # to determine version
    elif git --git-dir "${CYLC_DIR}/.git" rev-parse && \
            [[ -n "$(git --git-dir "${CYLC_DIR}/.git" rev-parse \
                --show-toplevel)" ]]; then
        CYLC_VERSION="$(cd "${CYLC_DIR}" && git describe --abbrev=4 --always)"
        # Append "-dirty" if there are uncommitted changes.
        if [[ -n "$(cd "${CYLC_DIR}" && git status \
                --untracked-files=no --porcelain)" ]]; then
            CYLC_VERSION="${CYLC_VERSION}-dirty"
        fi
    # Else we must be in a standalone cylc folder/tarball with no verion info.
    # (make has not yet been run)
    else
        echo "No version is set, cylc must be built first. Aborting..." >&2
        exit 1
    fi
}

print_version() {
    get_version
    if [[ "$#" -eq 0 ]]; then
        echo "$CYLC_VERSION"
    fi
    if [[ "$@" == 'long' || "$@" == '--long' ]]; then
    echo "Cylc ${CYLC_VERSION} (${CYLC_DIR})"    
    fi
}

init_cylc() {
    set -eu

    CYLC_HOME_BIN=$(cd "$(dirname "$0")" && pwd -P)
    CYLC_DIR="$(dirname "${CYLC_HOME_BIN}")"

    PATH="$(path_lead "${PATH:-}" "${CYLC_HOME_BIN}")"
    PYTHONPATH="$(path_lead "${PYTHONPATH:-}" "${CYLC_DIR}/lib/")"
    PYTHONUNBUFFERED='true'
    
    export PATH PYTHONPATH PYTHONUNBUFFERED CYLC_DIR
}

help_util() {
    # Deals with form 'cylc [COMMAND] --help'
    cd "${CYLC_HOME_BIN}"
    # deal with graph which is a weird edge case...
    if [[ "$@" == "graph" ]]; then
        local COMMAND="${CYLC_HOME_BIN}/cylc-graph"
        exec "${COMMAND}" "--help"
    fi
    # For help command/option with no args
    if [[ $# == 0 && "${HELP_OPTS[*]} " == *"$UTIL"* ]]; then
        exec "${CYLC_HOME_BIN}/cylc-help"
    fi
    # for cases 'cylc --help CATEGORY COMMAND'
    if [[ $# -gt 1 &&  "${HELP_OPTS[*]} " == *"$UTIL"* && \
          " ${CATEGORIES[*]} " == *" $1 "* ]]; then
        UTIL="$(get_command_from_abbr "$2")"
        run_cylc_command '--help'
    fi
    # Check if this is a help command, not containing a category qualifier
    if [[ $# -gt 1  && "${HELP_OPTS[*]} " == *"${UTIL}"* && 
          " ${CATEGORIES[*]} " != *" $1 "* &&
          " ${CATEGORIES[*]} " != *" $2 "* ]]; then
        run_cylc_command '--help'
    fi
    # If category name is used, call the help func with the category
    # make this deal with 'cylc help CATEGORY' only
    if [[ " ${CATEGORIES[*]} " == *" ${UTIL} "* \
          || " ${HELP_OPTS[*]} " == *" ${UTIL} "* \
          && $# -gt 0 && ( " ${CATEGORIES[*]} " == *" $1 "* \
          || " ${HELP_OPTS[*]} " == *" $1 "* ) ]]; then
        UTIL='help'
        run_cylc_command "$@"
    fi
    # Deal with cases like 'cylc --help [COMMAND/CATEGORY] or cylc [CATEGORY]'   
    if (( $# >= 1 )); then
        if [[ "$1" == 'version' ]]; then
            cat <<'__HELP__'
Usage: cylc version [--long]

Print version and exit.

Options:
  --long            print cylc version and location of cylc installation
__HELP__
            return
        else
            UTIL="$(get_command_from_abbr "$1")"
            run_cylc_command '--help'
        fi
    fi
    # If category name is used as arg, call the help func with the category
    if [[ $# -gt 1  &&  ( "${CATEGORIES[*]} " == *" $1 "* 
            || " ${HELP_OPTS[*]} " == *" $1 "* ) ]]; then
        local COMMAND="${CYLC_HOME_BIN}/cylc-help"
        exec "${COMMAND}" "$@"
    fi
    # Deal with cases like 'cylc --help [COMMAND/CATEGORY]'
    if [[ $# -gt 1 && -f "$(ls "cylc-$2"* 2>'/dev/null')" ]]; then
        local COMMAND="${CYLC_HOME_BIN}/$(ls "cylc-$2"*)"
        exec "${COMMAND}" "--help"
    fi
    # If not a category or not an actual command in the bin dir, exit
    if ! ls "cylc-${UTIL}"* 2>'/dev/null'; then
        echo "${UTIL}: unknown utility. Abort." >&2
        echo "Type 'cylc help' for a list of utilities."
        return 1
    fi
    
    echo "Something has gone terribly wrong if you are here..."
    return 1 
}

category_help_command() {
# Special case for printing categories and the gcylc gui
    local COMMAND="${CYLC_HOME_BIN}/cylc-help"
    exec "${COMMAND}" "categories"
}

command_help_command() {
# Special case for printing all commands (used in CUG Makefile)
    local COMMAND="${CYLC_HOME_BIN}/cylc-help"
    exec "${COMMAND}" "commands"
}

path_lead() {
# Ensure that ITEM_STR is at the beginning of PATH_STR
    local PATH_STR="$1"
    local ITEM_STR="$2"
    if [[ -z "${PATH_STR:-}" ]]; then
        echo "${ITEM_STR}"
    elif [[ "${PATH_STR}" != "${ITEM_STR}" \
            && "${PATH_STR}" != "${ITEM_STR}":* ]]; then
        while [[ "${PATH_STR}" == *:"${ITEM_STR}" ]]; do
            PATH_STR=${PATH_STR%:$ITEM_STR}
        done
        while [[ "${PATH_STR}" == *:"${ITEM_STR}":* ]]; do
            local PATH_HEAD="${PATH_STR%:$ITEM_STR:*}"
            local PATH_TAIL="${PATH_STR##*:$ITEM_STR:}"
            PATH_STR="${PATH_HEAD}:${PATH_TAIL}"
        done
        echo "${ITEM_STR}:${PATH_STR}"
    else
        echo "${PATH_STR}"
    fi
}

get_command_from_abbr() {
    local COMMAND="$(cd "${CYLC_HOME_BIN}" && ls "cylc-$1"* 2>'/dev/null')"
    if [[ -z "${COMMAND}" ]]; then
        # Abbreviation has no match, bad
        echo "cylc $1: unknown utility. Abort." >&2
        echo "Type \"cylc help all\" for a list of utilities." >&2
    elif (("$(wc -l <<<"${COMMAND}")" != 1)); then
        # Abbreviation has multiple matches, bad
        echo "cylc $1: is ambiguous for:" >&2
        sed 's/^cylc-/    cylc /' <<<"${COMMAND}" >&2
    else
        # Abbreviation has one match, good
        sed 's/^cylc-//' <<<"${COMMAND}"
        return
    fi
    return 1
}

run_cylc_command() {
    exec "${CYLC_HOME_BIN}/cylc-${UTIL}" "$@"
}

init_cylc

UTIL="help"
if (( $# > 0 )); then
    UTIL="$1"
    shift 1
fi
# For speed, assume a correct command has been provided first of all. 
if [[ -f "${CYLC_HOME_BIN}/cylc-${UTIL}" && "${UTIL}" != 'help'  ]]; then
    run_cylc_command "$@"
fi

# Now process help and command matching.
# Check for help or version options.
case "${UTIL}" in
# Deal with categories
control|information|all|task|license|GPL|admin|preparation|hook|discovery\
        |utility|util|prep|con|info)
    if (( $# == 0 )); then
        help_util "${UTIL}"
    fi
    if [[ $# -ge 1 && "${HELP_OPTS[*]} " == *"$1"* ]]; then
        help_util "${UTIL}"
    fi
    UTIL="$1"
    # Discard the category qualifier argument by shifting
    # the arguments along by 1.
    shift 1
    :;;
help|h|\?|--help|-h)
    help_util "$@" | ${PAGER:-less}
    exit 0
    :;;
version|--version|-V)
    print_version "$@"
    exit 0
    :;;
categories)
    category_help_command "$@"
    exit 0
    :;;
commands)
    command_help_command "$@"
    exit 0
    :;;
esac

# User has not given a help, category, or version option.
# So now deal with matching commands and disambiguation.
UTIL="$(get_command_from_abbr "${UTIL}")"
run_cylc_command "$@"
