# -*- mode: sh; sh-shell: bash -*-
# vim: set ft=bash:
# shellcheck shell=bash

require --opt tty_lib

### Library of general functions which are always present

# TODO: add [-s|--state "extrastate"]

function memo ## [-c|-d] [cmd args..].. - memoize the result/stdout/stderr of commands, will always return the same result again
{
    ## Memoizes for the current run only. See memodb for persistent memoization.
    ## Memoized functions must be pure and only depend on their arguments and the current working directory.
    ## Can not handle null bytes in stdout/stderr, use memodb when that is required.
    if [[ $1 == "-c" ]]; then
        unset MEMO_RC MEMO_STDOUT MEMO_STDERR
        shift
    fi

    local delete=false
    if [[ $1 == "-d" ]]; then
        delete=true
        shift
    fi

    declare -gAi MEMO_RC
    declare -gA MEMO_STDOUT MEMO_STDERR

    # shellcheck disable=SC2155
    local key="$(hash_args "$$ $PWD $*")"

    if [[ $delete = true && -v MEMO_RC["$key"] ]]; then
        unset "MEMO_RC[$key]" "MEMO_STDOUT[$key]" "MEMO_STDERR[$key]"
    fi

    if [[ -v MEMO_RC["$key"] ]]; then
        echo -n "${MEMO_STDOUT[$key]}"
        echo -n "${MEMO_STDERR[$key]}" 1>&2
        return ${MEMO_RC["$key"]}
    elif [[ $# -ge 1 ]]; then
        touch "$TMPDIR/$key.stdout" "$TMPDIR/$key.stderr"
        local rc=0
        eval "$*" > >(tee "$TMPDIR/$key.stdout") 2> >(tee "$TMPDIR/$key.stderr" 1>&2) || rc=$?
        MEMO_RC["$key"]=$rc
        MEMO_STDOUT["$key"]=$(<"$TMPDIR/$key.stdout")
        MEMO_STDERR["$key"]=$(<"$TMPDIR/$key.stderr")
        rm "$TMPDIR/$key.stdout" "$TMPDIR/$key.stderr"
        return "$rc"
    fi
}

function memofn ## <functionnames..> - rewrites a function into a function that uses 'memo'
{
    ## This renames the given functions to 'nomemo_<functionname>' and creates a wrapper
    ## function with the original name that memoizes the result.
    for fn in "$@"; do
        # rename the original function as nomemo_*
        eval "nomemo_$(declare -f "$fn")"
        # create a new function that calls memo with the original function
        eval "function $fn () { memo nomemo_$fn \"\$@\" ; }"
    done
}

function is_scalar ## <name> - Checks that a variable 'name' is a scalar (non array) variable.
{
    # shellcheck disable=2155
    local type="$(declare -p "$1")"
    [[ $type == 'declare --'* || $type == 'declare -i'* || $type == 'declare -x'* ]]
}

function called_as ## [pattern..] - Checks whenever bar is called as a symlink matching the given patterns.
{
    ## This is usable as a rule dependency.
    local pat
    for pat in "$@"; do
        # shellcheck disable=2053
        [[ "$BAR_CALLED_AS" = $pat ]] && return 0
    done
    return 1
}

function progress # [message..] - elusive progress notice, will be overwritten by the next output.
{
    [[ "${TERM:-dumb}" = "dumb" ]] && return 0
    if (( BAR_CHECK_LEVEL > 0 )); then
        (( "${BAR_VERBOSITY_LEVEL:-5}" > 2 )) && {
            tty_newline
            tty_echo "${TTYERR[K_B]:-}  RUN: $*${TTYERR[n]:-}"
        }
    else
        (( "${BAR_VERBOSITY_LEVEL:-5}" > 1 )) && {
            tty_newline
            tty_echo -n "${TTYERR[K_B]:-}  RUN: $*${TTYERR[ce]:-}${TTYERR[cr]:-}${TTYERR[n]:-}"
        }
    fi >&2
}

function success # [message..] - Message for success.
{
    if (( BAR_CHECK_LEVEL > 0 )); then
        (( "${BAR_VERBOSITY_LEVEL:-5}" > 2 )) && {
            tty_newline
            echo -e "${TTYERR[g_B]:-}CHECK:${TTYERR[n]:-} $*"
        }
    else
        (( "${BAR_VERBOSITY_LEVEL:-5}" > 1 )) && {
            tty_newline
            echo -e "${TTYERR[g_B]:-}   OK:${TTYERR[n]:-} $*"
        }
    fi >&2
}

function failure # [message..] - Message for failure.
{
    if (( BAR_CHECK_LEVEL > 0 )); then
        (( "${BAR_VERBOSITY_LEVEL:-5}" > 2 )) && {
            tty_newline
            echo -e "${TTYERR[K_B]:-}CHECK: ${TTYERR[__S]:-}$*${TTYERR[n]:-}"
        }
    else
        (( "${BAR_VERBOSITY_LEVEL:-5}" > 1 )) && {
            tty_newline
            echo -e "${TTYERR[r_B]:-} FAIL:${TTYERR[n]:-} $*"
        }
    fi >&2
}

function uncond_call # <rc> [message..] - Message for unconditional dependency calls
{
    declare -i rc="$1"
    shift
    if (( "${BAR_VERBOSITY_LEVEL:-5}" > 2 )); then
        tty_newline
        if (( rc == 0 )); then
            echo -e "${TTYERR[g_B]:-} EVAL:${TTYERR[n]:-} $*"
        else
            echo -e "${TTYERR[K_B]:-} EVAL:${TTYERR[n]:-} $*"
        fi
    fi >&2
}

function rule_autoload # <name> - module autoloader
{
    [[ "$1" = -* ]] && return 0
    trace "$*"
    local modname="${1#always_}"
    modname="${modname#is_}"
    modname="${modname#has_}"
    modname="${modname#try_}"
    modname="${modname%%_*}"
    require --opt "$modname" "${modname}_lib"

    # when $name exists as command or function then create a rule for that
    if ! rule_exists "$1" && command -v "$1" >/dev/null ; then
        rule "$1:"
    fi
    rule_exists "$1" || die "no rule, command or function '$1' defined"
}

function is_command_installed ## <command> - Checks whenever a function exists or a shell command is in PATH.
{
    if command -v "$1" >/dev/null; then
        return 0
    else
        info "$1 not installed"
        return 1
    fi
}
memofn is_command_installed

function bar_now ## Returns the current timestamp in microseconds since epoch.
{
    echo "${EPOCHREALTIME//[^0-9]/}"
}

function hash_args ## <arguments..> - returns the sha1hash of all supplied arguments
{
    # shellcheck disable=SC2155
    local hash="$(sha1sum <<<"$*")"
    echo "${hash:0:40}"
}

function iset ## <target> [values].. - indirect assign values to a variable given by name, assigns to an array when more values are given.
{
    ## When target is empty or '[*]' (missing name) then this is a no-op. Otherwise target
    ## must be a declared variable When no values are given then target is cleared.
    [[ -z "${1:-}" || "$1" = "["*"]" ]] && return 0

    local target="$1"
    shift
    declare -p "${target%\[*}" &>/dev/null || {
        error "$target is not defined"
        return 1
    }

    if (( $# == 0 )); then
        eval "$target=()"
    elif (( $# == 1 )); then
        printf -v "$target" '%s' "${1:-}"
    else
        eval "$target=( \"\$@\" )"
    fi
}
