#!/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
resolve_optdepends=0

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", "OptDepends"
        | . 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" -v odeps="$4" '
        $5 ~ /^Self$/                  {print}
        $5 ~ /^Depends$/      && deps  {print}
        $5 ~ /^MakeDepends$/  && mdeps {print}
        $5 ~ /^CheckDepends$/ && cdeps {print}
        $5 ~ /^OptDepends$/   && odeps {print}
    ' "$5"
}

# $1 pkgname $2 depends $3 pkgbase
package_graph() {
    awk -v type="$1" 'FNR == NR {
        map[$1] = $3
        next
    }
    $2 in map {
        if (type == "pkgname") {
            printf("%s\t%s\n", $1, $2)
        } else if (type == "pkgbase") {
            printf("%s\t%s\n", $3, map[$2])
        }
    }' "${@:2}"
}

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

# shellcheck disable=SC2086
chain() {
    local a num sub
    local select_args=("$1" "$2" "$3" "$4")
    shift 4

    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 | select_deps "${select_args[@]}" | 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
}

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 [-abGnt]\n' "$argv0"
    exit 1
}

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

opt_short='abGnt'
opt_long=('table' 'pkgbase' 'pkgname' 'pkgname-all' 'graph' 'optdepends'
          '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
        -b|--pkgbase)
            mode=pkgbase ;;
        -n|--pkgname)
            mode=pkgname ;;
        -a|--pkgname-all)
            mode=pkgname_all ;;
        -G|--graph)
            mode=pkgbase_graph ;;
        -t|--table)
            mode=table ;;
        --no-depends)
            resolve_depends=0 ;;
        --no-makedepends)
            resolve_makedepends=0 ;;
        --no-checkdepends)
            resolve_checkdepends=0 ;;
        --optdepends)
            resolve_optdepends=1 ;;
        --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
    # tsv/n: pkgname\tdepends\tpkgbase\pkgver
    chain "$resolve_depends" "$resolve_makedepends" "$resolve_checkdepends" "$resolve_optdepends" "$@" || rings=$?
    deptable=tsv/n

    # 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

    case $mode in
        # pkgname (AUR, total order)
        pkgname)
            grep -Fxf <(cut -f1 "$deptable") <(cut -f1,2 "$deptable" | tsort | tac)
            ;;
        # pkgname (all, total order)
        pkgname_all)
            cut -f1,2 "$deptable" | tsort | tac
            ;;
        # pkgbase (AUR, total order)
        pkgbase)
            package_graph 'pkgbase' "$deptable" "$deptable" | tsort | tac
            ;;
        # pkgbase (AUR, graph)
        pkgbase_graph)
            package_graph 'pkgbase' "$deptable" "$deptable" | sort -k1b,1 -k1 -u
            ;;
        # all information
        table)
            cat "$deptable"
            ;;
        *)
            printf >&2 '%s: invalid argument' "$argv0"
            exit 22 ;;
    esac
else
    exit      
fi

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