#!/bin/bash
# aur-depends - retrieve package dependencies using AurJson
[[ -v AUR_DEBUG ]] && set -o xtrace
argv0=depends
PS4='+(${BASH_SOURCE}:${LINENO}): ${FUNCNAME[0]:+${FUNCNAME[0]}(): }'

# default options
max_request=30
mode=pkgname
resolve_depends=1
resolve_makedepends=1
resolve_checkdepends=1

count() {
    jq -r '.resultcount' "$1" | awk '{s += $1} END {print s}'
}

tabulate() {
    jq -r -f /dev/stdin "$1" <<'EOF'
.results[]
  | . as $result

  # the package itself
  | [[$result.Name, $result.Name, $result.PackageBase, $result.Version, "Self"]]
    +
    # enumerate dependencies per kind
      [
        "Depends", "MakeDepends", "CheckDepends"
        | . as $kind

        | $result[$kind][]?
        | [$result.Name, ., $result.PackageBase, $result.Version, $kind]
      ]
  | .[]
  | @tsv
EOF
}

# $1 pkgname $2 depends $3 pkgbase $4 pkgver $5 depends_type
select_deps() {
    awk -v deps="$1" -v mdeps="$2" -v cdeps="$3" '
        $5 ~ /^Self$/                  {print}
        $5 ~ /^Depends$/      && deps  {print}
        $5 ~ /^MakeDepends$/  && mdeps {print}
        $5 ~ /^CheckDepends$/ && cdeps {print}
    ' "$4"
}

tr_ver() {
    sed -r 's/[<>=].*$//g'
}

# shellcheck disable=SC2086
chain() {
    local a num sub

    aur query -t info "$@" > json/0 || exit
    num=$(count json/0)

    if (( num < 1 )); then
        printf >&2 '%s: no packages found\n' "$argv0"
        exit 1
    fi

    # In the below, all intermediary results are stored (originally done to
    # simplify debugging). Strictly speaking, only the current and previous
    # step are required. With a limited amount of requests (e.g. ~7 total
    # for large meta-packages such as ros-indigo-desktop and ~250 AUR
    # dependencies) the difference is unlikely to be noticeable.
    for (( a = 1; a <= max_request; ++a )); do
        sub=$(( a - 1 ))
        tabulate json/$sub | tee -a tsv/n > tsv/$sub

        # Avoid querying duplicates (#4)
        cut -f1 tsv/$sub >> seen # pkgname
        cut -f2 tsv/$sub | tr_ver | grep -Fxvf seen | \
            aur query -t info - > json/$a || exit # rpc error

        if [[ -s json/$a ]]; then
            num=$(count json/$a)
        else
            return $a # no unique results (seen \ tsv == [empty])
        fi

        if (( num >= 1 )); then
            cut -f2 tsv/$sub >> seen # depends
        else
            return $a # no results, recursion complete
        fi
    done

    # recursion limit exceeded
    return $max_request
}

order() {
    cut -f1,2 "$1" | tsort | tac
}

trap_exit() {
    if [[ ! -v AUR_DEBUG ]]; then
        rm -rf -- "$tmp"
    else
        printf >&2 'AUR_DEBUG: %s: temporary files at %s\n' "$argv0" "$tmp"
    fi
}

usage() {
    printf >&2 'usage: %s [-abnt]\n' "$argv0"
    exit 1
}

source /usr/share/makepkg/util/parseopts.sh

opt_short='abnt'
opt_long=('table' 'pkgbase' 'pkgname' 'pkgname-all' 'no-depends'
          'no-makedepends' 'no-checkdepends')
opt_hidden=('dump-options')

if ! parseopts "$opt_short" "${opt_long[@]}" "${opt_hidden[@]}" -- "$@"; then
    usage
fi
set -- "${OPTRET[@]}"

while true; do
    case "$1" in
        -a|--pkgname-all)
            mode=pkgname_all ;;
        -b|--pkgbase)
            mode=pkgbase ;;
        -n|--pkgname)
            mode=pkgname ;;
        -t|--table)
            mode=table ;;
        --no-depends)
            resolve_depends=0 ;;
        --no-makedepends)
            resolve_makedepends=0 ;;
        --no-checkdepends)
            resolve_checkdepends=0 ;;
        --dump-options)
            printf -- '--%s\n' "${opt_long[@]}" ${AUR_DEBUG+"${opt_hidden[@]}"}
            printf -- '%s' "${opt_short}" | sed 's/.:\?/-&\n/g'
            exit ;;
        --) shift; break ;;
    esac
    shift
done

# shellcheck disable=SC2174
mkdir -pm 0700 "${TMPDIR:-/tmp}/aurutils-$UID"
tmp=$(mktemp -d --tmpdir "aurutils-$UID/$argv0.XXXXXXXX") || exit
trap 'trap_exit' EXIT

rings=0
if cd "$tmp" && mkdir json tsv; then
    chain "$@" || rings=$? # tsv/n: pkgname\tdepends\tpkgbase\pkgver

    # check iteration number
    case $rings in
        1) true # no dependencies
           ;;
        "$max_request")
            printf >&2 '%s: total requests: %d (out of range)\n' "$argv0" $(( max_request + 1 ))
            exit 34 ;;
    esac

    # filter dependencies by type
    if (( resolve_depends == 0 || resolve_makedepends == 0 || resolve_checkdepends == 0 )); then
        select_deps "$resolve_depends" "$resolve_makedepends" "$resolve_checkdepends" tsv/n > tsv/x
        deptable=tsv/x
    else
        deptable=tsv/n
    fi

    case $mode in
        # pkgbase (AUR, total order)
        pkgbase)
            # associate pkgname to pkgbase
            declare -A map map_seen

            while read -r pkgname pkgbase; do
                map[$pkgname]=$pkgbase
            done < <(cut -f1,3 "$deptable")

            # pkgbase is ignored for repo packages (as unknown)
            while read -r pkgname; do
                pkgbase=${map[$pkgname]}

                if [[ $pkgbase ]] && [[ ! ${map_seen[$pkgbase]} ]]; then
                    printf '%s\n' "${map[$pkgname]}"
                    map_seen[$pkgbase]=1
                fi
            done < <(order "$deptable")
            ;;
        # pkgname (AUR, total order)
        pkgname)
            grep -Fxf <(cut -f1 "$deptable") <(order "$deptable")
            ;;
        # pkgname (all, total order)
        pkgname_all)
            order "$deptable"
            ;;
        table)
            cat "$deptable"
            ;;
        *)
            printf >&2 '%s: invalid argument' "$argv0"
            exit 22 ;;
    esac
else
    exit      
fi

# vim: set et sw=4 sts=4 ft=sh:
