# Copyright 2012 Google Inc.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
# * Redistributions of source code must retain the above copyright
#   notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright
#   notice, this list of conditions and the following disclaimer in the
#   documentation and/or other materials provided with the distribution.
# * Neither the name of Google Inc. nor the names of its contributors
#   may be used to endorse or promote products derived from this software
#   without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

shtk_import bool
shtk_import cli


# List of valid configuration variables.
#
# This is initialized by shtk_config_init and should remain unchanged thorough
# the execution of the program.
_Shtk_ConfigVars=


# List of overrides to apply by shtk_config_apply_overrides.
#
# The overrides are recorded and applied separately because they need to happen
# after a configuration file has been loaded.  The contents of this list are
# words of the form: set:<variable> or unset:<variable>.  For those variables
# listed in set:, there is a corresponding shtk_config_override_var_<variable>
# variable containing the new value.
_Shtk_ConfigOverrides=


# Directory in which the currently-parsed configuration file lives.
#
# This is used by shtk_config_include to determine the path to included files
# when they are specified as relative paths.  The value of this variable
# potentially changes with each call to shtk_config_include.
_Shtk_ConfigDirectory=


# Applies recorded overrides to the current configuration.
#
# This is just a helper function for shtk_config_load and should not be used
# directly.
#
# The configuration data in memory is modified according to the data
# recorded in the overrides.
#
# The contents of _Shtk_ConfigOverrides is cleared.
_shtk_config_apply_overrides() {
    for override in ${_Shtk_ConfigOverrides}; do
        case "${override}" in
            set:*)
                local var="$(echo ${override} | cut -d : -f 2)"
                local value
                eval value="\"\${shtk_config_override_var_${var}}\""
                shtk_config_set "${var}" "${value}"
                ;;
            unset:*)
                local var="$(echo ${override} | cut -d : -f 2)"
                unset "shtk_config_var_${var}" || true
                ;;
        esac
    done
    _Shtk_ConfigOverrides=
}


# Checks if a configuration variable is known.
#
# Returns true if the variable was registered by shtk_config_init, false
# otherwise.
_shtk_config_is_valid() {
    local var="${1}"; shift

    local known_var
    for known_var in ${_Shtk_ConfigVars}; do
        if [ "${known_var}" = "${var}" ]; then
            return 0
        fi
    done
    return 1
}


shtk_config_init() {
    _Shtk_ConfigVars="${@}"
}


shtk_config_has() {
    local var="${1}"; shift

    local is_unset
    eval is_unset="\${shtk_config_var_${var}-yes_this_is_unset}"
    if [ "${is_unset}" = yes_this_is_unset ]; then
        return 1
    else
        return 0
    fi
}


shtk_config_get() {
    local var="${1}"; shift

    if shtk_config_has "${var}"; then
        eval echo "\${shtk_config_var_${var}}"
    else
        shtk_cli_error "Required configuration variable ${var} not set"
    fi
}


shtk_config_get_bool() {
    local var="${1}"; shift

    if shtk_config_has "${var}"; then
        shtk_bool_check "$(shtk_config_get "${var}")" \
            "Invalid boolean value in variable ${var}" || return 1
        return 0
    else
        return 1
    fi
}


shtk_config_get_default() {
    local var="${1}"; shift
    local default="${1}"; shift

    if shtk_config_has "${var}"; then
        shtk_config_get "${var}"
    else
        echo "${default}"
    fi
}


shtk_config_set() {
    local var="${1}"; shift
    local value="${1}"; shift

    _shtk_config_is_valid "${var}" \
        || shtk_cli_usage_error "Unknown configuration variable ${var}"
    eval "shtk_config_var_${var}=\"\${value}\""
}


shtk_config_unset() {
    local var="${1}"; shift

    _shtk_config_is_valid "${var}" \
        || shtk_cli_usage_error "Unknown configuration variable ${var}"
    eval unset "shtk_config_var_${var}"
}


shtk_config_load() {
    local config_file="${1}"; shift

    [ -e "${config_file}" ] || shtk_cli_error "Configuration file" \
        "${config_file} does not exist"

    # Ensure all configuration variables are unset from the point of view of the
    # user.  Any defaults that the caller has set are not clobbered by this.
    local var
    for var in ${_Shtk_ConfigVars}; do
        eval local "${var}"
        unset "${var}" || true
    done

    local real_config_file
    case "${config_file}" in
        */*) real_config_file="${config_file}" ;;
        *) real_config_file="./${config_file}" ;;
    esac
    local config_file_dirname="$(dirname "${real_config_file}")"
    _Shtk_ConfigDirectory="${config_file_dirname}"
    ( . "${real_config_file}" ) || shtk_cli_error "Failed to load" \
        "configuration file ${config_file}"
    . "${real_config_file}"
    _Shtk_ConfigDirectory=

    for var in ${_Shtk_ConfigVars}; do
        local value
        eval value="\${${var}-unset}"
        [ "${value-unset}" != unset ] || continue

        if [ -n "${value}" ]; then
            shtk_config_set "${var}" "${value}"
        else
            unset "shtk_config_var_${var}" || true
        fi
    done

    _shtk_config_apply_overrides
}


shtk_config_include() {
    local file="${1}"; shift

    local file_dirname="$(dirname "${file}")"
    local old_configdirectory="${_Shtk_ConfigDirectory}"
    case "${file_dirname}" in
        /*)
            _Shtk_ConfigDirectory="${file_dirname}"
            . "${file}"
            ;;
        *)
            _Shtk_ConfigDirectory="${_Shtk_ConfigDirectory}/${file_dirname}"
            . "${old_configdirectory}/${file}"
            ;;
    esac
    _Shtk_ConfigDirectory="${old_configdirectory}"
}


shtk_config_override() {
    local arg="${1}"; shift

    case "${arg}" in
        *=*)
            ;;
        *)
            shtk_cli_usage_error "Invalid configuration override" \
                "${arg}; must be of the form variable=value"
            ;;
    esac
    local var="$(echo "${arg}" | cut -d = -f 1)"
    local value="$(echo "${arg}" | cut -d = -f 2-)"

    [ -n "${var}" ] || shtk_cli_usage_error "Invalid configuration override" \
        "${arg}; must be of the form variable=value"
    _shtk_config_is_valid "${var}" \
        || shtk_cli_usage_error "Unknown configuration variable ${var}"

    if [ -n "${value}" ]; then
        eval "shtk_config_override_var_${var}=\"${value}\""
        _Shtk_ConfigOverrides="${_Shtk_ConfigOverrides} set:${var}"
    else
        _Shtk_ConfigOverrides="${_Shtk_ConfigOverrides} unset:${var}"
    fi
}


shtk_config_run_hook() {
    local hook="${1}"; shift

    (
        for var in ${_Shtk_ConfigVars}; do
            if shtk_config_has "${var}"; then
                eval "${var}"=\"$(shtk_config_get "${var}")\"
            else
                unset "${var}"
            fi
        done
        "${hook}" "${@}"
    ) || shtk_cli_error "The hook ${hook} returned an error"
}
