#!/bin/bash

if [[ -z ${__UTIL_SH_INCLUDED__:-} ]]; then
__UTIL_SH_INCLUDED__=x

declare -gra EMPTY_ARRAY=()
declare -grA EMPTY_MAP=()

# Works like dirname, but without spawning new processes.
#
# Params:
#
# 1 - path to operate on
# 2 - name of a variable which will contain a dirname of the path
function dirname_out() {
    local path dir_var_name
    path=${1}; shift
    dir_var_name=${1}; shift
    local -n dir_ref=${dir_var_name}

    if [[ -z ${path} ]]; then
        dir_ref='.'
        return 0
    fi
    local cleaned_up dn
    # strip trailing slashes
    cleaned_up=${path%%*(/)}
    # strip duplicated slashes
    cleaned_up=${cleaned_up//+(\/)/\/}
    # strip last component
    dn=${cleaned_up%/*}
    if [[ -z ${dn} ]]; then
        dir_ref='/'
        return 0
    fi
    if [[ ${cleaned_up} = "${dn}" ]]; then
        dir_ref='.'
        return 0
    fi
    dir_ref=${dn}
}

# Works like basename, but without spawning new processes.
#
# Params:
#
# 1 - path to operate on
# 2 - name of a variable which will contain a basename of the path
function basename_out() {
    local path base_var_name
    path=${1}; shift
    base_var_name=${1}; shift
    local -n base_ref=${base_var_name}

    if [[ -z ${path} ]]; then
        base_ref=''
        return 0
    fi
    local cleaned_up dn
    # strip trailing slashes
    cleaned_up=${path%%*(/)}
    if [[ -z ${cleaned_up} ]]; then
        base_ref='/'
        return 0
    fi
    # strip duplicated slashes
    cleaned_up=${cleaned_up//+(\/)/\/}
    # keep last component
    dn=${cleaned_up##*/}
    base_ref=${dn}
}

if [[ ${BASH_SOURCE[-1]##*/} = 'util.sh' ]]; then
    THIS=${BASH}
    basename_out "${THIS}" THIS_NAME
    THIS_DIR=.
else
    THIS=${BASH_SOURCE[-1]}
    basename_out "${THIS}" THIS_NAME
    dirname_out "${THIS}" THIS_DIR
fi

THIS=$(realpath "${THIS}")
THIS_DIR=$(realpath "${THIS_DIR}")
dirname_out "${BASH_SOURCE[0]}" PKG_AUTO_IMPL_DIR
PKG_AUTO_IMPL_DIR=$(realpath "${PKG_AUTO_IMPL_DIR}")
PKG_AUTO_DIR=$(realpath "${PKG_AUTO_IMPL_DIR}/..")

# Prints an info line.
#
# Params:
#
# @ - strings to print
function info() {
    printf '%s: %s\n' "${THIS_NAME}" "${*}"
}

# Prints info lines.
#
# Params:
#
# @ - lines to print
function info_lines() {
    printf '%s\n' "${@/#/"${THIS_NAME}: "}"
}

# Prints an info to stderr and fails the execution.
#
# Params:
#
# @ - strings to print
function fail() {
    info "${@}" >&2
    exit 1
}

# Prints infos to stderr and fails the execution.
#
# Params:
#
# @ - lines to print
function fail_lines() {
    info_lines "${@}" >&2
    exit 1
}

# Yells a message.
#
# Params:
#
# @ - strings to yell
function yell() {
    echo
    echo '!!!!!!!!!!!!!!!!!!'
    echo "    ${*}"
    echo '!!!!!!!!!!!!!!!!!!'
    echo
}

# Prints help. Help is taken from the lines prefixed with double
# hashes in the top sourcer of this file.
function print_help() {
    if [[ ${THIS} != "${BASH}" ]]; then
        grep '^##' "${THIS}" | sed -e 's/##[[:space:]]\?//'
    fi
}

# Joins passed strings with a given delimiter.
#
# Params:
#
# 1 - name of a variable that will contain the joined result
# 2 - delimiter
# @ - strings to join
function join_by() {
    local output_var_name delimiter first

    output_var_name=${1}; shift
    delimiter=${1}; shift
    first=${1-}
    if shift; then
        printf -v "${output_var_name}" '%s' "${first}" "${@/#/${delimiter}}";
    else
        local -n output_ref=${output_var_name}
        output_ref=''
    fi
}

# Checks if directory is empty, returns true if so, otherwise false.
#
# Params:
#
# 1 - path to a directory
function dir_is_empty() {
    local dir
    dir=${1}; shift

    [[ -z $(echo "${dir}"/*) ]]
}

# Just like diff, but ignores the return value.
function xdiff() {
    diff "${@}" || :
}

# Just like grep, but ignores the return value.
function xgrep() {
    grep "${@}" || :
}

# Strips leading and trailing whitespace from the passed parameter.
#
# Params:
#
# 1 - string to strip
# 2 - name of a variable where the result of stripping will be stored
function strip_out() {
    local l
    l=${1}; shift
    local -n out_ref=${1}; shift

    local t
    t=${l}
    t=${t/#+([[:space:]])}
    t=${t/%+([[:space:]])}
    out_ref=${t}
}

# Gets supported architectures.
#
# Params:
#
# 1 - name of an array variable, where the architectures will be stored
function get_valid_arches() {
    local -n arches_ref=${1}; shift

    arches_ref=( 'amd64' 'arm64' )
}

# Generates all pairs from a given sequence of strings. Each pair will
# be stored in the given variable and items in the pair will be
# separated by the given separator. For N strings, (N * N - N) / 2
# pairs will be generatated.
#
# Params:
#
# 1 - name of an array variable where the pairs will be stored
# 2 - separator string
# @ - strings
function all_pairs() {
    local -n pairs_ref=${1}; shift
    local sep=${1}; shift

    # indices in ${@} are 1-based, 0 gives script name or something
    local idx=1 next_idx

    pairs_ref=()
    while [[ ${idx} -lt ${#} ]]; do
        next_idx=$((idx + 1))
        while [[ ${next_idx} -le ${#} ]]; do
            pairs_ref+=( "${!idx}${sep}${!next_idx}" )
            next_idx=$((next_idx + 1))
        done
        idx=$((idx+1))
    done
}

# Does the set operation on two passed sets - both set differences and
# an intersection.
#
# Params:
#
# 1 - name of the first set variable
# 2 - name of the second set variable
# 3 - name of the set variable that will contain elements that exist
#     in first set, but not the second
# 4 - name of the set variable that will contain elements that exist
#     in second set, but not the first
# 5 - name of the set variable that will contain elements that exist
#     in both first and second sets
function sets_split() {
    local -n first_set_ref=${1}; shift
    local -n second_set_ref=${1}; shift
    local -n only_in_first_set_ref=${1}; shift
    local -n only_in_second_set_ref=${1}; shift
    local -n common_set_ref=${1}; shift

    only_in_first_set_ref=()
    only_in_second_set_ref=()
    common_set_ref=()

    local item mark

    for item in "${!first_set_ref[@]}"; do
        mark=${second_set_ref["${item}"]:-}
        if [[ -z ${mark} ]]; then
            only_in_first_set_ref["${item}"]=x
        else
            common_set_ref["${item}"]=x
        fi
    done

    for item in "${!second_set_ref[@]}"; do
        mark=${first_set_ref["${item}"]:-}
        if [[ -z ${mark} ]]; then
            only_in_second_set_ref["${item}"]=x
        fi
    done
}

declare -gi __UTIL_SH_COUNTER=0

# Generates a globally unique name for a variable. Can be given a
# prefix to override the default __PA_VAR one.
#
# Params:
#
# (optional) a prefix
# 1 - name of a variable, where the generated name will be stored
function gen_varname() {
    local prefix='__PA_VAR' # pa = pkg-auto
    if [[ ${#} -gt 1 ]]; then
        # we passed a prefix
        prefix=${1}; shift
    fi
    local -n name_ref=${1}; shift

    name_ref="${prefix}_${__UTIL_SH_COUNTER}"
    __UTIL_SH_COUNTER=$((__UTIL_SH_COUNTER + 1))
}

# Declares variables with a given initializer.
#
# Params:
#
# @: flags passed to declare, followed by variable names, followed by
# an initializer
function struct_declare() {
    local -a args=()
    while [[ $# -gt 0 ]]; do
        if [[ ${1} != -* ]]; then
            break
        fi
        args+=( "${1}" )
        shift
    done
    if [[ ${#} -lt 2 ]]; then
        fail "bad use of struct_declare"
    fi
    local definition=${*: -1}
    set -- "${@:1:$((${#} - 1))}"
    set -- "${@/%/=${definition}}"
    declare "${args[@]}" "${@}"
}

fi
