#!/bin/bash
#
# THIS FILE IS PART OF THE CYLC SUITE ENGINE.
# Copyright (C) 2008-2017 NIWA
#
# 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) to determine version
    elif [[ -d "${CYLC_DIR}/.git" ]]; then
        local DESC=
        DESC="$(cd ${CYLC_DIR} && git describe --abbrev=4 2>/dev/null)"
        CYLC_VERSION="${DESC}"
        # 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..."
        exit 1
    fi
}

print_version() {
    get_version
    if [[ "$#" -eq 0 ]]; then
        echo "$CYLC_VERSION"
    fi
    if [[ "$@" == '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
        local COMMAND="${CYLC_HOME_BIN}/cylc-$2"
        exec "${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
        local COMMAND="${CYLC_HOME_BIN}/cylc-${UTIL}"
        exec "${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
        local COMMAND="${CYLC_HOME_BIN}/cylc-help"
        exec "${COMMAND}" "$@"
    fi

    # Deal with cases like 'cylc --help [COMMAND/CATEGORY] or cylc [CATEGORY]'   
    if (( $# >= 1 )); then
        if [[  -f "$(ls cylc-${1}* 2>/dev/null)" ]]; then
        local COMMAND="${CYLC_HOME_BIN}/$(ls "cylc-$1"*)"
        exec "${COMMAND}" "--help"
        elif (( "$(wc -l <<<"$(ls cylc-${1}* 2>/dev/null)")" > 1 )); then
            abort_ambiguous_command "$1"
        else
            abort_bad_command "$1"
        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 [[ ! -f "$(ls "cylc-${UTIL}"*)" ]]; 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
}

abort_bad_command() {
    CYLC_NS="$(basename $0)"
    echo "${CYLC_NS}: $1: unknown utility. Abort." >&2
    echo "Type \"${CYLC_NS} help all\" for a list of utilities." >&2
    exit 1
}

abort_ambiguous_command() {
    echo "\"$1\" is ambiguous."
    echo "These commands match the abbreviation \"$1\":"
    sed -r "s,${CYLC_HOME_BIN}/cylc-,," \
        <<<"$(ls "${CYLC_HOME_BIN}/cylc-$1"* 2>/dev/null)"
    exit 1
}

run_cylc_command_matched() {
    CYLC_UTIL="$(sed -r "s,${CYLC_HOME_BIN}/cylc-,," <<<"${commands_match}")"
    export CYLC_UTIL
    exec "${commands_match}" "$@"
}

run_cylc_command() {
    CYLC_UTIL="${UTIL}"
    export CYLC_UTIL
    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"* \
            || $# -ge 2 && "${HELP_OPTS[*]} " == *"$2"* ]]; 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 "$@"
    exit 0
    :;;
version|--version|-V|-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.

# Store any matching file glob as a number for later use
# Minimises re-running ls
commands_match="$(ls "${CYLC_HOME_BIN}/cylc-${UTIL}"* 2>/dev/null || true )"
if [[ -z $commands_match ]]; then
    num_commands_match=0
else
    num_commands_match="$(wc -l <<<"${commands_match}")"
fi

# Abort if no matches to command abbrev, list matches if multiple matches
if (( "$num_commands_match" == 0 )); then
    abort_bad_command "${UTIL}"
# Now try and match abbreviated commands.
elif (( "$num_commands_match" == 1 )); then
    run_cylc_command_matched "$@"
# Abort if the command wildcard match is ambiguous, unless it is itself a 
# command, e.g. "cylc graph" matches "cylc-graph" and "cylc-graph-diff",
# but the command should not abort in this case.  
elif [[ "$num_commands_match" -gt 1 \
        && -f "${CYLC_HOME_BIN}/cylc-${UTIL}" ]] ; then
    exec "${CYLC_HOME_BIN}/cylc-${UTIL}" "@"
else
    abort_ambiguous_command "${UTIL}"
fi

