#!/usr/bin/env bash

readonly -a known_hash_algos=(
    ck
    md5
    sha1
    sha224
    sha256
    sha384
    sha512
    b2
)

# The list of all global keywords that are architecture unspecific.
readonly -a non_architecture_global_vars=(
    arch
    backup
    changelog
    groups
    epoch
    install
    license
    options
    pkgbase
    pkgdesc
    pkgname
    pkgrel
    pkgver
    url
    validpgpkeys
)

# The list of all global keywords that may be specific to a certain architecture.
readonly -a architecture_global_vars=(
    checkdepends
    conflicts
    depends
    makedepends
    noextract
    optdepends
    provides
    replaces
    source
    "${known_hash_algos[@]/%/sums}"
)

# Build a list of "baseline" keys that are encountered in a global section
# **without** any architecture specific keywords.
global_vars+=("${non_architecture_global_vars[@]}")
global_vars+=("${architecture_global_vars[@]}")

# The list of all function keywords that are architecture unspecific.
readonly -a non_architecture_function_vars=(
    arch
    backup
    changelog
    groups
    install
    license
    options
    pkgdesc
    url
)

# The list of all function keywords that may be specific to a certain architecture.
readonly -a architecture_function_vars=(
    conflicts
    depends
    optdepends
    provides
    replaces
)

# Build a list of "baseline" keys that are encountered in a `pkgname` section
# **without** any architecture specific keywords.
function_vars+=("${non_architecture_function_vars[@]}")
function_vars+=("${architecture_function_vars[@]}")

source_safe() {
    local file="$1"
    local shellopts
    shellopts=$(shopt -p extglob)
    shopt -u extglob

    # shellcheck source=/usr/share/pacman/PKGBUILD.proto
    if ! source "$file"; then
        exit 1
    fi

    eval "$shellopts"
}

escape() {
    local val="$1"
    val="${val//\\/\\\\}"
    val="${val//\"/\\\"}"
    val="${val//$'\n'/\\\\n}"
    printf -- "%s" "$val"
}

# Add architecture specific keywords for all known architectures.
#
# I.e. If an architecture `x86_64` is given, add potential keywords for
# `depends_x86_64` and so on on both global and function vars.
expand_pkgbuild_vars() {
    local a

    if [[ $(typeof_var arch) == ARRAY ]]; then
        for a in "${arch[@]}"; do
            global_vars+=("${architecture_global_vars[@]/%/_$a}")
            function_vars+=("${architecture_function_vars[@]/%/_$a}")
        done
    fi

    readonly -a global_vars
}

typeof_var() {
    local type
    type=$(declare -p "$1" 2>/dev/null)

    if [[ "$type" == "declare --"* ]]; then
        printf "STRING"
    elif [[ "$type" == "declare -a"* ]]; then
        printf "ARRAY"
    elif [[ "$type" == "declare -A"* ]]; then
        printf "MAP"
    else
        printf "NONE"
    fi
}

# Take a string value and a prefix such as `VAR GLOBAL` and print it to stdout.
dump_string() {
    local varname=$1 prefix="$2"
    local val
    val="$(escape "${!varname}")"

    printf -- '%s STRING %s "%s"\n' "$prefix" "$varname" "$val"

}

# Take an array value and a prefix such as `VAR GLOBAL` and print it to stdout.
dump_array() {
    local val varname=$1 prefix="$2"
    local arr="$varname"'[@]'

    printf -- '%s ARRAY %s' "$prefix" "$varname"

    for val in "${!arr}"; do
        val="$(escape "$val")"
        printf -- ' "%s"' "$val"
    done

    printf '\n'
}

dump_map() {
    local key varname=$1 prefix="$2"
    declare -n map=$varname

    printf -- '%s MAP %s' "$prefix" "$varname"

    for key in "${!map[@]}"; do
        val="${map[$key]}"

        key="$(escape "$key")"
        val="$(escape "$val")"

        printf -- ' "%s" "%s"' "$key" "$val"
    done

    printf '\n'
}

dump_var() {
    local varname=$1 prefix="${2:-"VAR GLOBAL"}"
    local type
    type=$(typeof_var "$varname")

    if [[ $type == STRING ]]; then
        dump_string "$varname" "$prefix"
    elif [[ $type == ARRAY ]]; then
        dump_array "$varname" "$prefix"
    elif [[ $type == MAP ]]; then
        dump_map "$varname" "$prefix"
    fi
}

grep_function() {
    local funcname="$1" regex="$2"

    declare -f "$funcname" 2>/dev/null | grep -E "$regex"
}

dump_function_vars() {
    local funcname="$1" varname attr_regex decl new_vars

    # Overwrite the pkgname in case this is a split package.
    # This is necessary to enable split-package specific variable usage in package functions.
    #
    # Otherwise expressions such as this always only resolve to the first entry in `pkgname`:
    # `install=$pkgname.install`
    if [[ -n "$2" ]]; then
        local pkgname="$2"
    fi

    declare -A new_vars
    printf -v attr_regex '^[[:space:]]* [a-z1-9_]*\+?='

    # Make sure the function exists by checking if it's declared.
    if ! declare -f "$funcname" >/dev/null; then
        return
    fi

    # this function requires extglob - save current status to restore later
    local shellopts
    shellopts=$(shopt -p extglob)
    shopt -s extglob

    while read -r; do
        # strip leading whitespace and any usage of declare
        decl=${REPLY##*([[:space:]])}
        varname=${decl%%[+=]*}

        local -I "$varname"
        new_vars[$varname]=1
        eval "$decl"
    done < <(grep_function "$funcname" "$attr_regex")

    for varname in "${global_vars[@]}"; do
        if [[ -v "new_vars[$varname]" ]]; then
            dump_var "$varname" "VAR FUNCTION $funcname"
        fi
    done

    eval "$shellopts"
}

dump_functions_vars() {
    local name

    dump_function_vars "package"

    for name in "${pkgname[@]}"; do
        dump_function_vars "package_${name}" "$name"
    done
}

dump_global_vars() {
    local varname

    for varname in "${global_vars[@]}"; do
        dump_var "$varname"
    done
}

dump_function_names() {
    local funcname

    # Print all function names that're either `package` or start with `package_`.
    for funcname in $(declare -F | cut -d ' ' -f3); do
        if [[ "$funcname" == "package" || "$funcname" == package_* ]]; then
            printf -- "FUNCTION %s\n" "$funcname"
        fi
    done
}

dump_pkgbuild() {
    source_safe "$1"

    # If no $pkgbase is set, we must default to the very first entry in `pkgname`.
    # Otherwise some variable substitution logic will fail.
    if [[ -z "$pkgbase" ]]; then
        # Ensure pkgname is set and has at least one value
        if [[ -n "${pkgname[*]}" ]]; then
            pkgbase="${pkgname[0]}"
        else
            echo "Error: No pkgbase provided and pkgname is not set or is empty."
            exit 1
        fi
    fi

    expand_pkgbuild_vars
    dump_global_vars
    dump_functions_vars
    dump_function_names
}

# usage:
# alpm-pkgbuild-bridge.sh <path/to/pkgbuild>

dump_pkgbuild "$@"

exit 0
