#!/bin/bash

#     ____                  __        ____
#    / __ \____ ___________/ /_____ _/ / /
#   / /_/ / __ `/ ___/ ___/ __/ __ `/ / /
#  / ____/ /_/ / /__(__  ) /_/ /_/ / / /
# /_/    \__,_/\___/____/\__/\__,_/_/_/
#
# Copyright (C) 2020-present
#
# This file is part of Pacstall
#
# Pacstall is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, version 3 of the License
#
# Pacstall is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Pacstall. If not, see <https://www.gnu.org/licenses/>.

# Pacstall version
version_number="5.5.0"
version_name="Rust"
declare -A version_color=([r]="183" [g]="65" [b]="14")

# Configuration
export METADIR="/var/lib/pacstall/metadata"
export LOGDIR="/var/log/pacstall/error_log"
printf -v LOGFILE "${LOGDIR}/%(%F_%T)T.log"
export LOGFILE
export PACDIR="/tmp/pacstall"
export SCRIPTDIR="/usr/share/pacstall"
export STAGEDIR="/usr/src/pacstall"
export TEXTDOMAIN="pacstall"
export TEXTDOMAINDIR="/usr/share/locale"

export PACSTALL_USER=$(logname 2> /dev/null || echo "${SUDO_USER:-${USER:-$(whoami)}}")

# (( )) doesn't like uninitialized vars
export PACSTALL_INSTALL=1

# declare verbose debug output
declare -gx PS4=$'\E[0;10m\E[1m\033[1;31m\033[1;37m[\033[1;35m${BASH_SOURCE[0]##*/}:\033[1;34m${FUNCNAME[0]:-NOFUNC}():\033[1;33m${LINENO}\033[1;37m] - \033[1;33mDEBUG: \E[0;10m'

function def_colors() {
    # Colors
    export BOLD='\033[1m'
    export NC='\033[0m'
    # Courtesy of https://stackoverflow.com/a/28938235/13449010

    if [[ -z $NO_COLOR ]]; then
        # Regular Colors
        export BLACK='\033[0;30m'  # Black
        export RED='\033[0;31m'    # Red
        export GREEN='\033[0;32m'  # Green
        export YELLOW='\033[0;33m' # Yellow
        export BLUE='\033[0;34m'   # Blue
        export PURPLE='\033[0;35m' # Purple
        export CYAN='\033[0;36m'   # Cyan
        export WHITE='\033[0;37m'  # White

        # Bold
        export BBlack='\033[1;30m'  # Black
        export BRed='\033[1;31m'    # Red
        export BGreen='\033[1;32m'  # Green
        export BYellow='\033[1;33m' # Yellow
        export BBlue='\033[1;34m'   # Blue
        export BPurple='\033[1;35m' # Purple
        export BCyan='\033[1;36m'   # Cyan
        export BWhite='\033[1;37m'  # White

        # Underline
        export UBlack='\033[4;30m'  # Black
        export URed='\033[4;31m'    # Red
        export UGreen='\033[4;32m'  # Green
        export UYellow='\033[4;33m' # Yellow
        export UBlue='\033[4;34m'   # Blue
        export UPurple='\033[4;35m' # Purple
        export UCyan='\033[4;36m'   # Cyan
        export UWhite='\033[4;37m'  # White

        # Background
        export On_Black='\033[40m'  # Black
        export On_Red='\033[41m'    # Red
        export On_Green='\033[42m'  # Green
        export On_Yellow='\033[43m' # Yellow
        export On_Blue='\033[44m'   # Blue
        export On_Purple='\033[45m' # Purple
        export On_Cyan='\033[46m'   # Cyan
        export On_White='\033[47m'  # White

        # High Intensity
        export IBlack='\033[0;90m'  # Black
        export IRed='\033[0;91m'    # Red
        export IGreen='\033[0;92m'  # Green
        export IYellow='\033[0;93m' # Yellow
        export IBlue='\033[0;94m'   # Blue
        export IPurple='\033[0;95m' # Purple
        export ICyan='\033[0;96m'   # Cyan
        export IWhite='\033[0;97m'  # White

        # Bold High Intensity
        export BIBlack='\033[1;90m'  # Black
        export BIRed='\033[1;91m'    # Red
        export BIGreen='\033[1;92m'  # Green
        export BIYellow='\033[1;93m' # Yellow
        export BIBlue='\033[1;94m'   # Blue
        export BIPurple='\033[1;95m' # Purple
        export BICyan='\033[1;96m'   # Cyan
        export BIWhite='\033[1;97m'  # White

        # High Intensity backgrounds
        export On_IBlack='\033[0;100m'  # Black
        export On_IRed='\033[0;101m'    # Red
        export On_IGreen='\033[0;102m'  # Green
        export On_IYellow='\033[0;103m' # Yellow
        export On_IBlue='\033[0;104m'   # Blue
        export On_IPurple='\033[0;105m' # Purple
        export On_ICyan='\033[0;106m'   # Cyan
        export On_IWhite='\033[0;107m'  # White
    fi
}
def_colors

# Pac-roll
pac_text=" _________________________
< you just got Pac-rolled >
 -------------------------"

pac_body="
  ⠀⠀⠀⠀⣀⠀⡀⠀⠀⠀⠀⠀⢀⠀⠀⠀⠀
      ⣞⠑⡄⢠⠎⠀⠋⠀⠀⣜⠴⠒⡀⠀⡜⠁⠱⡀
      ⡇⠀⡈⠁⠀⠀⠀⠀⠀⠁⠀⠘⠀⠲⠅⠀⠀⡇
        ⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠀⢆
    ⠀⢠⠃⠀⠄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡜⠀⠀
    ⠀⢘⡃⠣⡀⠀⢰⢄⣧⠀⠀⠀⠀⠀⡆⡀⢀⡤⠀⡽⠀⠀
    ⠀⢺⠀⠀⠀⠀⢀⡀⠑⢦⠀⢀⢌⡼⠁⢀⣀⠀⠘⢢⠀⠀
    ⠀⢜⠁⠀⠀⡔⣿⣯⢇⠀⢀⠤⠤⢀⠐⢙⣿⢧⠀⠀⡇⠀
    ⠠⠇⠀⠀⠀⠑⢵⣿⠖⡇⠈⠂⡈⠁⡗⠦⠥⠊⠀⡸⠀⠀
    ⠀⠸⡀⠀⠀⠀⠀⠀⠀⠱⡤⠔⠙⢲⠁⠀⠀⠀⠀⡇⠀⠀
    ⠀⠀⢰⠀⠀⠀⠀⠀⠀⠀⠱⣒⢲⡏⠀⠀⠀⠀⠠⣱⠀⠀
    ⠀⠀⢀⠇⠀⠀⠀⠀⠀⠀⠀⠁⠊⠀⠀⠀⠀⠀⠀⠃⠀⠀
    ⠀ ⠼⠇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢇
    ⠀⠀⣿⢀⣠⠐⣰⢿⣯⡄⠀⠀⣠⡾⠁⣿⣿⣿⣶⣶⣶⣦⣤⣤⣀⣀⠀
    ⣤⣴⣾⣿⣿⣰⣿⠛⡿⣇⣤⡾⠋⠀⣰⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⡀
⠀⠀⢠⣶⣾⣿⣿⣿⣿⣿⣿⣿⣟⢀⡷⣮⣭⣤⣤⣴⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⠀
⠀⠀⣼⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣾⣿⣟⣛⣛⣻⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡄
⠀⠀⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡟⠈⣟⣚⣒⣒⣲⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡄
⠀⠀⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡇⠀⡿⠶⠾⠯⠿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡄
⠀⠀⣿⢿⣿⣿⣿⣿⣿⣿⣿⣿⣷⣶⢿⣭⣿⣿⣟⣿⣿⣿⡏⠀⠉⠉⢻⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷
⠀⢠⣿⣿⣿⣿⣿⡿⢻⣿⣿⣿⡿⣻⣛⣛⣛⣛⢻⣿⣿⣿⣇⠀⠀⠀⠈⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡄
⠀⠻⣿⣿⣿⣿⠅⠂⠰⣿⣿⣿⡇⣿⡿⢿⠯⠭⠭⣽⣿⣿⣿⣤⡀⠀⠀⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣄
⠀⣠⣿⣿⡿⠃⠀⠀⠀⢹⣿⣿⡷⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⣤⣴⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿
⣰⣿⣿⣿⣣⣷⣄⠀⢠⣾⣿⣿⣇⣿⣿⣓⣒⣒⣺⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡇
⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡟⣿⣿⣶⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣯⣿⣿⣿⣿⣿⣿⣿⣿⡟
⠈⣿⣿⣿⣽⣿⣿⣿⣿⣿⣿⣿⡇⣿⣿⣿⠿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡇⠉⠉⠉⠉⠉
⠀⠈⠋⠛⠛⠛⢻⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣻⣏⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⣾⣿⣿⣿⣿⣿⣿⣿⣿⡟⠉⠉⠉⠉⠉⢻⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⡀"

# fancy_message allows visually appealing output.
# Source the code block and run:
#
# `fancy_message {info,warn,error,sub} "What you want to say"`
function fancy_message() {
    local MESSAGE_TYPE="${1}" MESSAGE="${2}" FANCYTEXT
    shift 2
    local PRINTARGS=("${@}")
    case ${MESSAGE_TYPE} in
        info) FANCYTEXT="[${BGreen}+${NC}] ${BOLD}INFO${NC}:" ;;
        warn) FANCYTEXT="[${BYellow}*${NC}] ${BOLD}WARNING${NC}:" ;;
        error) FANCYTEXT="[${BRed}!${NC}] ${BOLD}ERROR${NC}:" ;;
        sub) FANCYTEXT="\t[${BBlue}>${NC}]" ;;
        *) FANCYTEXT="[${BOLD}?${NC}] ${BOLD}UNKNOWN${NC}:" ;;
    esac
    case ${MESSAGE_TYPE} in
        info|sub) printf "${FANCYTEXT} ${MESSAGE}\n" "${PRINTARGS[@]}" ;;
        *) printf "${FANCYTEXT} ${MESSAGE}\n" "${PRINTARGS[@]}" >&2 ;;
    esac
}

function decl_scriptvars() {
    local _distros _vars _archs _sums distros \
        vars="source depends makedepends optdepends pacdeps checkdepends provides conflicts breaks replaces enhances recommends suggests makeconflicts checkconflicts" \
        archs="amd64 x86_64 arm64 aarch64 armel arm armhf armv7h i386 i686 mips64el ppc64el riscv64 s390x" \
        sums="b2 sha512 sha384 sha256 sha224 sha1 md5"
    pacstallvars=(pkgbase pkgname repology pkgver git_pkgver epoch source_url source depends makedepends checkdepends conflicts
        breaks replaces gives pkgdesc hash optdepends ppa arch maintainer pacdeps NOBUILDDEP provides incompatible
        compatible srcdir url backup pkgrel mask pac_functions repo priority noextract nosubmodules _archive license
        bwrapenv safeenv external_connection enhances recommends suggests custom_fields makeconflicts checkconflicts bugs)
    mapfile -t distros < <(awk -F ',' -v current_date="$(date +'%Y-%m-%d')" '
      BEGIN {
        is_first = 1
      }
      $3 == "series" {
        if (is_first) {
          print "ubuntu"
          is_first = 0
        } else {
          print "devel\ndebian"
        }
        next
      }
      NR > 1 && (($6 > current_date) || ($7 > current_date) || ($6 == "")) &&
      ($4 <= current_date) && $3 != "experimental" {
        print $3
      }' /usr/share/distro-info/{ubuntu,debian}.csv)
    export PACSTALL_KNOWN_DISTROS=("${distros[@]}")
    eval "PACSTALL_KNOWN_ARCH=(${archs}) PACSTALL_KNOWN_SUMS=(${sums})"
    distros="${distros[*]}"
    _distros="{${distros// /,}}" _vars="{${vars// /,}}" _archs="{${archs// /,}}" _sums="{${sums// /,}}"
    eval "pacstallvars+=(${_vars}_${_distros} ${_vars}_${_archs} ${_vars}_${_distros}_${_archs} ${_sums}sums ${_sums}sums_${_distros} ${_sums}sums_${_archs} ${_sums}sums_${_distros}_${_archs} gives_${_distros} gives_${_archs} gives_${_distros}_${_archs})"
}
decl_scriptvars

function cleanup() {
    if [[ -n ${PACDIR} ]]; then
        if [[ -n ${KEEP} && -n ${pacname} ]]; then
            sudo rm -rf "${PACDIR}-keep/${pacname}"
            mkdir -p "${PACDIR}-keep/${pacname}"
            #shellcheck disable=SC2153
            sudo mv "${PACDIR}/${PACKAGE}.pacscript" "${PACDIR}-keep/${pacname}"
            sudo mv "${PACDIR}/${PACKAGE}.SRCINFO" "${PACDIR}-keep/${pacname}/.SRCINFO"
            sudo mv "${PACDIR}/${pacname}~${pkgver}" "${PACDIR}-keep/${pacname}"
        fi
        if [[ -n ${pacname} ]]; then
            if [[ -f "${PACDIR}-pacdeps-${pacname}" ]]; then
                sudo rm -rf "${PACDIR}-pacdeps-${pacname}"
            else
                sudo rm -rf "${PACDIR}/"*
            fi
            sudo rm -rf "${STAGEDIR:-/usr/src/pacstall}/${pacname}"
            for i in "deps" "gives" "missing-deps" "not-satisfied-deps" "suggested-optdeps" "missing-optdeps" "not-satisfied-optdeps" "already-installed-optdeps" "selectopts-optdeps" "needed-builddepends" "missing-builddepends" "unsatisfied-builddepends" "needed-checkdepends" "missing-checkdepends" "unsatisfied-checkdepends"; do
                if [[ -f "${PACDIR}-${i}-${pacname}" ]]; then
                    sudo rm -rf "${PACDIR}-${i}-${pacname}"
                fi
            done
        fi
        [[ -n ${pkgbase} ]] && sudo rm -rf "${PACDIR}-selectopts-pkgbase-${pkgbase}"
        # shellcheck disable=SC2153
        [[ -n ${STAGEDIR} && -n ${PACKAGE} ]] && sudo rm -rf "${STAGEDIR}/${pacname:-$PACKAGE}"
        [[ -n ${pacfile} ]] && sudo rm -f "${pacfile}"
        [[ -n ${srcinfile} ]] && sudo rm -f "${srcinfile}"
        [[ -n ${bwrapenv} ]] && sudo rm -f "${bwrapenv}"
        [[ -n ${safeenv} ]] && sudo rm -f "${safeenv}"
    fi
    unset "${pacstallvars[@]}" 2> /dev/null
    unset -f pre_install pre_upgrade pre_remove post_install post_upgrade post_remove prepare build check package 2> /dev/null
}

function stacktrace() {
    local catch=$?
    if ((catch==1)) && ! ${ignore_stack}; then
        local i stack_size=${#FUNCNAME[@]} func linen src trace content stack_color color_idx \
            colors=(196 197 198 199 200 201 165 129 93 57 21 27 33 39 45 51 50 49 48 47 46 82 118 154 190 226 220 214 208 202)
        echo -e "[${BRed}!${NC}] ${BOLD}ERROR${NC}: Stacktrace (most recent call last)" >&2
        for ((i = stack_size - 1; i >= 1; i--)); do
            color_idx=$(( (stack_size - 1 - i) % ${#colors[@]} ))
            stack_color="\033[38;5;${colors[color_idx]}m"
            ((i != stack_size - 1)) && func="${FUNCNAME[i - 1]}"
            [[ -z ${func} ]] && func='MAIN'
            [[ ${func} == "stacktrace" ]] && { unset func; trace="${RED}TRACEBACK${NC}"; }
            linen="${BASH_LINENO[i - 1]}"
            src="${BASH_SOURCE[i]}"
            [[ -z ${src} ]] && src=non_file_source
            echo -e " ${stack_color}${func:+├}${trace:+╰}─➤${GREEN}${func}${NC}${trace}${NC}${func:+()}${trace:+:} ${src%/*}/${PURPLE}${src##*/}${NC}:${YELLOW}${linen}${NC}" >&2
            # shellcheck disable=SC2027
            echo -e " ${stack_color}${func:+│}${trace:+ }${NC}  ${CYAN}╰───➤${NC} \033[38;5;242m"$(tail -n +"${linen}" "${src}" | head -n1)"${NC}" >&2
        done
        fancy_message info $"Cleaning up"
        cleanup
        exit 1
    else
        export ignore_stack=false
        return "${catch}"
    fi
}
{ export ignore_stack=false; set -o pipefail; trap stacktrace ERR RETURN; }

# This is the ask function. You can source this code block and then run something like:
# ask "Do you like the color blue? " Y
# if ((answer == 1)); then
#   echo "You like blue"
# else
#   echo "You don't like blue"
# fi
#
# Y=1 and N=0
# You can specify {Y,N} or leave it out to prevent entering the default but this is not allowed in pacstall because of the -P flag which gives unattended install
function ask() {
    # stacktrace can't handle whatever sorcery happens with 'echo N | pacstall'
    local prompt default reply template="${1}"
    shift 1
    # shellcheck disable=SC2124
    local yn="${@: -1}"
    local rest=("${@:1:$(($# - 1))}")

    if [[ ${yn} == 'Y' ]]; then
        prompt="${BIGreen}Y${NC}/${RED}n${NC}"
        default='Y'
    elif [[ ${yn} == 'N' ]]; then
        prompt="${GREEN}y${NC}/${BIRed}N${NC}"
        default='N'
    else
        prompt="${GREEN}y${NC}/${RED}n${NC}"
    fi

    # Ask the question (not using "read -p" as it uses stderr not stdout)
    printf "${template} [$prompt] " "${rest[@]}"

    if [[ ${DISABLE_PROMPTS:-z} == "z" ]]; then
        export DISABLE_PROMPTS="no"
    fi

    if [[ $DISABLE_PROMPTS == "no" ]]; then
        read -r reply <&0
        # Detect if script is running non-interactively
        # Which implies that the input is being piped into the script
        if [[ $NON_INTERACTIVE ]]; then
            if [[ -z $reply ]]; then
                echo -n "$default"
            fi
            echo "$reply"
        fi
    else
        echo "$default"
        reply=$default
    fi

    # Default?
    if [[ -z $reply ]]; then
        reply=$default
    fi

    while :; do
        # Check if the reply is valid
        case "$reply" in
            Y* | y*)
                export answer=1
                break
                ;;
            N* | n*)
                export answer=0
                break
                ;;
            *)
                printf "${template} [$prompt] " "${rest[@]}"
                read -r reply < /dev/tty
                ;;
        esac
    done
}

# Used for providing possible solutions to errors
# The following color codes are used for specific scenarios
#   UGreen: files/URLS
#   UCyan: commands to run
#   UPurple: text
# Make sure to quote ('') commands and files/URLS
function suggested_solution() {
    { ignore_stack=false; set -o pipefail; trap stacktrace ERR RETURN; }
    if [[ -z $PACSTALL_SUPPRESS_SOLUTIONS ]]; then
        local inputs=("${@}") input text args first=true
        printf "%b %s:\n" "[${BOLD}${BPurple}⠿${NC}]" $"Suggested solution(s)"
        for input in "${inputs[@]}"; do
            if [[ "${input}" == "ZZZZ" ]]; then
                if [[ -n "${text}" ]]; then
                    printf "%b ${text}\n" "    ${BOLD}|${NC}" "${args[@]}"
                fi
                first=true
                unset text args
                continue
            fi
            if ${first}; then
                text="${input}"
                first=false
            else
                args+=("${input}")
            fi
        done
        if [[ -n "${text}" ]]; then
            printf "%b ${text}\n" "    ${BOLD}|${NC}" "${args[@]}"
        fi
    fi
}

function check_url() {
    { ignore_stack=false; set -o pipefail; trap stacktrace ERR RETURN; }
    if [[ ${1} == "file://"* ]]; then
        if ! [[ -f ${1/"file://"/} ]]; then
            { ignore_stack=true; return 1; }
        fi
    else
        local http_code="$(curl --location -o /dev/null -s --head --write-out '%{http_code}\n' -- "${1}")"
        case "${http_code}" in
            000)
                if [[ ${1} == *"packagelist" ]]; then
                    fancy_message error $"Packagelist not found"
                    suggested_solution $"Confirm that '%b' exists" "${UGreen}$1${NC}"
                else
                    fancy_message error $"Failed to download file, check your connection"
                fi
                error_log 1 "get ${PACKAGE} pacscript"
                { ignore_stack=true; return 1; }
                ;;
            404)
                fancy_message error $"The URL cannot be found"
                suggested_solution $"Confirm that '%b' exists" "${UGreen}$1${NC}"
                { ignore_stack=true; return 1; }
                ;;
            200 | 301 | 302)
                return 0
                ;;
            *)
                fancy_message error $"Failed with http code %s" "${http_code}"
                suggested_solution $"Confirm that '%b' is accessible" "${UGreen}$1${NC}"
                { ignore_stack=true; return 1; }
                ;;
        esac
    fi
}

# Checks if a command is available.
# Plain "command -v" succeeds if the argument is in PATH, even if not executable.
function has_command() {
    { ignore_stack=false; set -o pipefail; trap stacktrace ERR RETURN; }
    if [[ -x $(command -v "$1" 2> /dev/null) ]]; then
        return 0
    else
        { ignore_stack=true; return 1; }
    fi
}

# use axel if available
function download() {
    { ignore_stack=false; set -o pipefail; trap stacktrace ERR RETURN; }
    local src="$1" out="${2:-${src##*/}}"
    if [[ $PACSTALL_DOWNLOADER != "payload" ]]; then
        sudo rm -f "${out:?}"
    fi
    if [[ -z $PACSTALL_DOWNLOADER || -f "${PACDIR}-pacdeps-$PACKAGE" ]]; then
        if has_command axel; then
            PACSTALL_DOWNLOADER=axel
        elif has_command wget; then
            PACSTALL_DOWNLOADER=wget
        else
            PACSTALL_DOWNLOADER=curl
        fi
    fi
    ${PACSTALL_VERBOSE} && [[ ${PACSTALL_DOWNLOADER} != "verbose-"* ]] && PACSTALL_DOWNLOADER="verbose-${PACSTALL_DOWNLOADER}"

    case "$PACSTALL_DOWNLOADER" in
        verbose-axel)
            axel -ao "$out" "$src" || { ignore_stack=true; return 1; }
            ;;
        axel)
            axel -a -q -o "$out" "$src" || { ignore_stack=true; return 1; }
            ;;
        verbose-curl)
            curl -L -# -o "$out" "$src" || { ignore_stack=true; return 1; }
            ;;
        curl)
            curl -sSL -o "$out" "$src" || { ignore_stack=true; return 1; }
            ;;
        verbose-wget)
            wget -q --show-progress --progress=bar:force -O "$out" -- "$src" 2>&1 || { ignore_stack=true; return 1; }
            ;;
        payload) ;;
        wget | *)
            wget -q -O "$out" -- "$src" 2>&1 || { ignore_stack=true; return 1; }
            ;;
    esac
}

# source this code block and run like so:
#   $ select_options "My message I want to send" "${#array[@]}" "message"
# This will then output the options given by the user to ${PACDIR}-selectopts-${varname}-${pacname}, which you can then turn into another array
function select_options() {
    { ignore_stack=false; set -o pipefail; trap stacktrace ERR RETURN; }
    local message="${1}" length="${2}" varname="${3}" optname="${pkgbase:-${pacname}}"
    rm -f "${PACDIR}-selectopts-${varname}-${optname}"
    if ((length >= 6)); then
        echo -ne "${message} [${BOLD}1-$length${NC} or ${BIGreen}Y${NC}] "
    else
        echo -ne "${message} [${BOLD}$(seq -s ' ' 1 "$length")${NC} or ${BIGreen}Y${NC}] "
    fi
    if [[ $DISABLE_PROMPTS == "no" ]]; then
        read -ra input <&0
        if [[ $NON_INTERACTIVE ]]; then
            if [[ -z $input ]]; then
                echo "Y"
            fi
            echo "$input"
        fi
    else
        echo "Y"
        input="Y"
    fi
    if [[ -z $input ]] || [[ $input =~ ^[Yy]$ ]]; then
        seq -s ' ' 1 "$length" | tee "${PACDIR}-selectopts-${varname}-${optname}" > /dev/null
    elif ((input == 0)) || [[ $input =~ ^[Nn]$ ]]; then
        echo "n" | tee "${PACDIR}-selectopts-${varname}-${optname}" > /dev/null
    elif ! [[ $input =~ [a-zA-Z]+ ]] || [[ $input =~ ^[0-9]+$ ]]; then
        for i in "${input[@]}"; do
            unset split_arr
            local out split_arr
            if [[ $i =~ [0-9]+-[0-9]+ ]] || [[ $i =~ [0-9]+..[0-9]+ ]]; then
                case "$i" in
                    *-*)
                        for line in ${i//-/ }; do
                            split_arr+=("$line")
                        done
                        if ((${split_arr[0]} > ${split_arr[-1]} || ${split_arr[0]} == ${split_arr[-1]})); then select_options "$message" "$length" "$varname"; fi
                        ;;
                    *..*)
                        for line in ${i//../ }; do
                            split_arr+=("$line")
                        done
                        if ((${split_arr[0]} > ${split_arr[-1]} || ${split_arr[0]} == ${split_arr[-1]})); then select_options "$message" "$length" "$varname"; fi
                        ;;
                    *)
                        select_options "$message" "$length" "$varname"
                        ;;
                esac
                out+=($(seq "${split_arr[0]}" "${split_arr[1]}"))
                continue
            else
                out+=("$i")
            fi
        done
        echo "${out[@]}" | tee "${PACDIR}-selectopts-${varname}-${optname}" > /dev/null
    else
        select_options "$message" "$length" "$varname"
    fi
}

# Return 0 if exists, 1 if not
function is_package_installed() {
    { ignore_stack=false; set -o pipefail; trap stacktrace ERR RETURN; }
    local input="${1}"
    while read -r line; do
        if [[ ${line} == "${input}" ]]; then
            return 0
        fi
    done < <(pacstall -L)
    { ignore_stack=true; return 1; }
}

# Returns 0 if exists, 1 if not
function is_apt_package_installed() {
    { ignore_stack=false; set -o pipefail; trap stacktrace ERR RETURN; }
    if [[ $(dpkg-query -W --showformat='${db:Status-Status}' "${1}" 2> /dev/null) == "installed" ]]; then
        return 0
    else
        { ignore_stack=true; return 1; }
    fi
}

function is_array() {
    { ignore_stack=false; set -o pipefail; trap stacktrace ERR RETURN; }
    if [[ ${!1@a} == *a* ]]; then
        return 0
    else
        { ignore_stack=true; return 1; }
    fi
}

function is_function() {
    { ignore_stack=false; set -o pipefail; trap stacktrace ERR RETURN; }
    if [[ $(type -t "${1}") == "function" ]]; then
        return 0
    else
        { ignore_stack=true; return 1; }
    fi
}

function array.contains() {
    { ignore_stack=false; set -o pipefail; trap stacktrace ERR RETURN; }
    local check
    local -n arra="${1:?No array passed to array.contains}"
    local input="${2:?No input given to array.contains}"
    for check in "${arra[@]}"; do
        if [[ ${check} == "${input}" ]]; then
            return 0
        fi
    done
    { ignore_stack=true; return 1; }
}

function getMasks() {
    { ignore_stack=false; set -o pipefail; trap stacktrace ERR RETURN; }
    local pkgs pkg
    local -n masks="${1}"
    mapfile -t pkgs < <(pacstall -L 2> /dev/null)
    if ((${#pkgs[@]} == 0)); then
        return 0
    fi
    for pkg in "${pkgs[@]}"; do
        source "${METADIR}/${pkg}"
        if [[ -n ${_mask[*]} ]]; then
            masks+=("${_mask[@]}")
        fi
        unset _pacstall_depends _pacdeps _name _version _install_date _date _ppa _homepage _gives _remoterepo _remotebranch _mask 2> /dev/null
    done
}

function getMasks_offending_pkg() {
    { ignore_stack=false; set -o pipefail; trap stacktrace ERR RETURN; }
    local the_name="${1}"
    mapfile -t pkgs < <(pacstall -L)
    if ((${#pkgs[@]} == 0)); then
        return 0
    fi
    for pkg in "${pkgs[@]}"; do
        source "${METADIR}/${pkg}"
        if [[ -n ${_mask[*]} ]]; then
            if array.contains _mask "${the_name}"; then
                echo "${pkg}"
                return 0
            fi
        fi
        unset _pacstall_depends _pacdeps _name _version _install_date _date _ppa _homepage _gives _remoterepo _remotebranch _mask 2> /dev/null
    done
    { ignore_stack=true; return 1; }
}

# run sudo apt update if it's been more than a week
eval "$(apt-config shell State Dir::State)"
eval "$(apt-config shell List Dir::State::Lists)"
[[ -z "$(find -H "/${State}/${List}" -maxdepth 0 -mtime -7)" ]] && sudo apt-get update -qq --allow-releaseinfo-change
unset State List

# shellcheck source=./misc/scripts/error-log.sh
source "$SCRIPTDIR/scripts/error-log.sh"
# shellcheck source=./misc/scripts/bwrap.sh
source "$SCRIPTDIR/scripts/bwrap.sh"

if [[ ! -t 0 ]]; then
    NON_INTERACTIVE=true
    fancy_message warn $"Reading input from pipe"
fi

# Separate grouped short options
argument_list=()

for i in "${@}"; do
    # Just add argument if doesn't start with exactly one hyphen.
    if ! [[ ${i} =~ ^-[^-] ]]; then
        # if argument is '-B', '-P', '-K' or '-Nc', add to beginning of argument list.
        if [[ ${i} == "--build" || ${i} == "--disable-prompts" || ${i} == "--keep" || ${i} == "--nocheck" || ${i} == "--quiet" || ${i} == "--nosandbox" ]]; then
            argument_list=("${i}" "${argument_list[@]}")
        else
            argument_list+=("${i}")
        fi
        continue
    fi

    # Add all arguments to the list of arguments.
    # We remove the '-' prefix as we'll add it later.
    # Arguments start with uppercase except 'h'.
    for j in $(sed -E 's/[[:upper:]]|h/ &/g' <<< "${i:1}"); do
        # If current string is 'B', 'P', 'K' or 'Nc', add to beginning of argument list.
        if [[ ${j} == "B" || ${j} == "P" || ${j} == "K" || ${j} == "Nc" || ${j} == "Q" || ${j} == "Ns" ]]; then
            argument_list=("-${j}" "${argument_list[@]}")
        else
            argument_list+=("-${j}")
        fi
    done
    unset j
done

# Remove duplicates
declare -A arg_map=(
    ["--install"]="-I" ["--search"]="-S" ["--remove"]="-R" ["--download"]="-D"
    ["--add-repo"]="-A" ["--remove-repo"]="-Rr" ["--update"]="-U" ["--list"]="-L" ["--upgrade"]="-Up"
    ["--search-description"]="-Sd" ["--search-info"]="-Si" ["--cache-info"]="-Ci"
    ["--quality-assurance"]="-Qa" ["--tree"]="-T" ["--version"]="-V" ["--help"]="-h" ["--build"]="-B"
    ["--keep"]="-K" ["--disable-prompts"]="-P" ["--nocheck"]="-Nc" ["--quiet"]="-Q" ["--nosandbox"]="-Ns"
)
declare -A hashed_arguments
unique_argument_list=()
for arg in "${argument_list[@]}"; do
    mapped="${arg_map[$arg]:=$arg}"
    if ! [[ -v "hashed_arguments[$mapped]" ]]; then
        unique_argument_list+=($arg)
        hashed_arguments[$mapped]=0
    fi
done

# Check if only one command flag is being used
short_commands=("-I" "-S" "-R" "-D" "-A" "-Rr" "-U" "-L" "-Up" "-Qa" "-Sd" "-Si" "-Ci" "-T" "-V" "-h")
long_commands=("--install" "--search" "--remove" "--download" "--add-repo" "--remove-repo" "--update" "--list" "--upgrade" "--quality-assurance" "--search-description" "--search-info" "--cache-info" "--tree" "--version" "--help")
commands=("${short_commands[@]}" "${long_commands[@]}")
matches=($(comm -12 <(sort <(printf "%s\n" "${unique_argument_list[@]}")) <(sort <(printf "%s\n" "${commands[@]}"))))
if ((${#matches[@]} <= 1)); then
    # Set the new list of arguments
    set -- "${unique_argument_list[@]}"
else
    fancy_message warn $"Only one command flag can be used at a time"
    set -- "-h"
fi

unset short_commands long_commands commands matches arguments_list unique_arguments_list hashed_arguments

function lock() {
    { ignore_stack=false; set -o pipefail; trap stacktrace ERR RETURN; }
    # Total number of options flags, increase when needed
    local option_flags=5
    local ignore_short="-S -D -A -Rr -V -L -T -Sd -Si -Ci -h"
    local ignore_long="--search --download --add-repo --remove-repo --version --tree --search-description --search-info --cache-info --help"
    local ignore="$ignore_short $ignore_long"

    for ((i = 1; i <= option_flags + 1; i++)); do
        if [[ $ignore =~ (^|[[:space:]])"${!i}"($|[[:space:]]) ]]; then
            { ignore_stack=true; return 1; }
        fi
        j=$((i + 1))
        if [[ -f "${PACDIR}-pacdeps-${!j%%@*}" ]]; then
            { ignore_stack=true; return 1; }
        fi
    done

    if [[ -f "$SCRIPTDIR/repo/pacstallrepo.pacstall-qa.bak" ]]; then
        instances=($(pidof -o %PPID -x "$0"))
        if [[ ${#instances[@]} -lt 2 ]]; then
            { ignore_stack=true; return 1; }
        fi
        return 0
    fi

    pidof -o %PPID -x "$0" > /dev/null && return 0 || { ignore_stack=true; return 1; }
}

while lock $@; do
    if [[ -z $first ]]; then
        first=1
        fancy_message warn $"Pacstall is already running another instance"
    fi
    sleep 1
done
if [[ -n $first ]]; then
    unset first
    fancy_message info $"The other instance has finished running, unlocking"
fi

# don't allow nosandbox to be run with envars
unset NOSANDBOX

while [[ $1 != "--" ]]; do
    case "$1" in
        -P | --disable-prompts)
            fancy_message warn $"Prompts are disabled"
            export DISABLE_PROMPTS=yes
            export DEBIAN_FRONTEND=noninteractive
            ;;

        -B | --build-only)
            export PACDEB_DIR="$PWD"
            fancy_message info $"Package will be built and not installed"
            export PACSTALL_INSTALL=0
            ;;

        -h | --help)
            echo -e "Usage: pacstall [-h] {-I,-S,-R,-D,-A,-Rr,-U,-L,-Up,-Qa,-Sd,-Si,-Ci,-T,-V} [-P] [-K] [-B] [-Nc] [-Q] [-Ns]

An AUR inspired package manager for Ubuntu.

Commands:
    ${BOLD}-I${NC}, ${BOLD}--install${NC} <package>
        Install a package.
    ${BOLD}-S${NC}, ${BOLD}--search${NC} <package>
        Search for a package.
    ${BOLD}-R${NC}, ${BOLD}--remove${NC} <package>
        Remove a package.
    ${BOLD}-D${NC}, ${BOLD}--download${NC} <package>
        Download a pacscript.
    ${BOLD}-A${NC}, ${BOLD}--add-repo${NC} <repo>
        Add a repository.
    ${BOLD}-Rr${NC}, ${BOLD}--remove-repo${NC} <repo>
        Remove a repository.
    ${BOLD}-U${NC}, ${BOLD}--update${NC} [user] [branch]
        Update Pacstall.
    ${BOLD}-L${NC}, ${BOLD}--list${NC}
        List all installed packages.
    ${BOLD}-Up${NC}, ${BOLD}--upgrade${NC}
        Upgrade all installed packages.
    ${BOLD}-Qa${NC}, ${BOLD}--quality-assurance${NC} <package>#<number>
        Test a package PR from downstream. Optional: @[provider]:[owner]/[repo]
    ${BOLD}-Sd${NC}, ${BOLD}--search-description${NC} <package>
        Search for a package or description keyword.
    ${BOLD}-Si${NC}, ${BOLD}--search-info${NC} <package>
        Query information about a remote package.
    ${BOLD}-Ci${NC}, ${BOLD}--cache-info${NC} <package>
        Query information about an installed package.
    ${BOLD}-T${NC}, ${BOLD}--tree${NC} <package>
        Display a tree graph of an installed package.
    ${BOLD}-V${NC}, ${BOLD}--version${NC}
        Display the version number.
    ${BOLD}-h${NC}, ${BOLD}--help${NC}
        Display this help message.

Options:
    ${BOLD}-P${NC}, ${BOLD}--disable-prompts${NC}
        Disable prompts.
    ${BOLD}-K${NC}, ${BOLD}--keep${NC}
        Keep the build files.
    ${BOLD}-B${NC}, ${BOLD}--build-only${NC}
        Build the deb but do not install.
    ${BOLD}-Nc${NC}, ${BOLD}--nocheck${NC}
        Skip the check() function if present.
    ${BOLD}-Q${NC}, ${BOLD}--quiet${NC}
        Download package entries quietly.
    ${BOLD}-Ns${NC}, ${BOLD}--nosandbox${NC}
        Build the package without bwrap.

Helpful links:
    ${BOLD}https://github.com/pacstall/pacstall${NC}
        Official Pacstall GitHub page.
    ${BOLD}https://github.com/pacstall/pacstall-programs/issues${NC}
        If you find a broken package, create an issue here.
    ${BOLD}https://github.com/pacstall/pacstall/releases/latest${NC}
        Link to the latest release of Pacstall."
            exit 0
            ;;

        -I | --install)
            unset CHILD PKGPATH
            if [[ -z $2 ]]; then
                fancy_message error $"You failed to specify a package"
                exit 1
            fi

            function trap_ctrlc() {
                fancy_message warn $"The installation of %s was interrupted, removing files" "${PACKAGE:-package}"
                rm -rf "${PACDIR:?}"/* # :? makes bash error out in case PACDIR is empty, saving us from yoinking /* directory by mistake
                exit 2
            }
            # Begin trapping
            trap "trap_ctrlc" 2

            while [[ -n $2 ]]; do

                if [[ $2 == "pac" ]]; then
                    echo -ne "$pac_text"
                    echo -e "$pac_body"
                    echo -e "~ Rick Pacsley ~"
                    exit 0
                else
                    unset pac_body pac_text
                fi
                if [[ ${2##*.} == "pacscript" || ${2##*.} == "pacscript:"* ]]; then
                    if [[ ${2##*.} =~ ^pacscript(:([a-zA-Z0-9_.-]+))$ ]]; then
                        export CHILD="${BASH_REMATCH[2]/\:/}"
                        PACKAGE="${2%.pacscript:${CHILD}}"
                    else
                        PACKAGE="${2%.pacscript}"
                    fi
                    export PACKAGE="${PACKAGE##*/}"
                    export type="install"
                    export local="yes"
                    PKGPATH="${2%/*}"
                    # Check if we need to cd into the directory first
                    if [[ $PKGPATH == "." && ! -f "$PACKAGE".pacscript && -d $PACKAGE ]]; then
                        cd "$PACKAGE"
                    elif [[ -d $PKGPATH ]]; then
                        cd "$PKGPATH"
                    fi
                    export PKGPATH="${PWD}"
                    # Check if the file exist
                    if [[ ! -f "$PACKAGE".pacscript ]]; then
                        fancy_message error $"%s does not exist" "$2"
                        shift
                        continue
                    fi
                else
                    export type="install"
                    export local="no"
                    if [[ $2 =~ ^([a-zA-Z0-9_.-]+)(:([a-zA-Z0-9_.-]+))(@(.+))$ ]]; then
                        PACKAGE="${BASH_REMATCH[1]}:pkgbase"
                        if [[ ${BASH_REMATCH[2]} == ":"* ]]; then
                            CHILD="${BASH_REMATCH[2]/\:/}"
                        else
                            CHILD="${BASH_REMATCH[3]}"
                        fi
                        if [[ ${BASH_REMATCH[3]} =~ '@' ]]; then
                            PACKAGE+="${BASH_REMATCH[3]}"
                        elif [[ -n ${BASH_REMATCH[5]} ]]; then
                            PACKAGE+="@${BASH_REMATCH[5]}"
                        fi
                    elif [[ $2 =~ ^([a-zA-Z0-9_.-]+)(@(.+))$ ]]; then
                        PACKAGE="${2}"
                    elif [[ ${2} =~ ':' ]]; then
                        PACKAGE="${2%:*}:pkgbase"
                        CHILD="${2#*:}"
                    else
                        PACKAGE="${2}"
                    fi

                    [[ ${CHILD} == "pkgbase" ]] && unset CHILD
                    export PACKAGE CHILD
                    if [[ -z $PACKAGE ]]; then
                        fancy_message error $"You failed to specify a package"
                        exit 1
                    fi

                    if [[ -n $PACSTALL_PAYLOAD && ! -f "${PACDIR}-pacdeps-${PACKAGE/:pkgbase/}" ]]; then
                        export PACSTALL_DOWNLOADER="payload"
                        if [[ ! -f $PACSTALL_PAYLOAD ]]; then
                            fancy_message error $"Payload not found"
                            exit 1
                        fi
                    fi

                    # Make the directory if not exist
                    if [[ ! -e "$SCRIPTDIR/repo/" ]]; then
                        sudo mkdir -p "$SCRIPTDIR/repo"
                        sudo touch "$SCRIPTDIR/repo/pacstallrepo"
                        pacstall -A
                    fi

                    check_url "https://github.com" 2> /dev/null || check_url "https://gitlab.com" 2> /dev/null || {
                        fancy_message error $"Could not connect to the internet"
                        suggested_solution $"Confirm that the URLs '%b' or '%b' are accessible" "${UGreen}https://github.com${NC}" "${UGreen}https://gitlab.com${NC}"
                        exit 1
                    }

                    # shellcheck source=./misc/scripts/search.sh
                    if ! source "$SCRIPTDIR/scripts/search.sh"; then
                        continue
                    fi
                    export PACKAGE="${PACKAGE/:pkgbase/}"
                    repo.specify "$REPO"
                    # sanity write
                    export REPO="${REPO}"
                    export URL="${REPO}/packages/$PACKAGE/$PACKAGE.pacscript"

                    # shellcheck source=./misc/scripts/get-pacscript.sh
                    if ! source "$SCRIPTDIR/scripts/get-pacscript.sh"; then
                        fancy_message error $"Failed to download the %b pacscript" "$GREEN$PACKAGE$NC"
                        suggested_solution $"Confirm that the package exists by running '%b'" "${UCyan}pacstall -S $PACKAGE${NC}" "ZZZZ" $"Check your internet connection"
                        continue
                    fi
                fi
                # shellcheck source=./misc/scripts/package-base.sh
                if ! source "$SCRIPTDIR/scripts/package-base.sh"; then
                    fancy_message error $"Failed to install %b" "$GREEN$PACKAGE$NC"
                    if ! [[ -f "${PACDIR}-pacdeps-$PACKAGE" ]]; then
                        sudo rm -rf "${PACDIR:?}"
                    fi
                    exit 1
                fi
                shift
            done
            exit 0
            ;;

        -S | --search | -Sd | --search-description)
            if [[ ${1} == "-Sd" || ${1} == --search-description ]]; then
                DESCON=true
            fi

            export SEARCH=$2
            if [[ -z $SEARCH ]]; then
                fancy_message error $"You failed to specify a package"
                exit 1
            fi

            check_url "https://github.com" 2> /dev/null || check_url "https://gitlab.com" 2> /dev/null || {
                fancy_message error $"Could not connect to the internet"
                suggested_solution $"Confirm that the URLs '%b' or '%b' are accessible" "${UGreen}https://github.com${NC}" "${UGreen}https://gitlab.com${NC}"
                exit 1
            }

            # shellcheck source=./misc/scripts/search.sh
            source "$SCRIPTDIR/scripts/search.sh"
            exit 0
            ;;

        -Si | --search-info)
            unset CHILD
            INFOQUERY="${2}"
            if [[ $2 =~ ^([a-zA-Z0-9_.-]+)(:([a-zA-Z0-9_.-]+))(@(.+))$ ]]; then
                PACKAGE="${BASH_REMATCH[1]}:pkgbase"
                if [[ ${BASH_REMATCH[2]} == ":"* ]]; then
                    CHILD="${BASH_REMATCH[2]/\:/}"
                else
                    CHILD="${BASH_REMATCH[3]}"
                fi
                if [[ ${BASH_REMATCH[3]} =~ '@' ]]; then
                    PACKAGE+="${BASH_REMATCH[3]}"
                elif [[ -n ${BASH_REMATCH[5]} ]]; then
                    PACKAGE+="@${BASH_REMATCH[5]}"
                fi
            elif [[ $2 =~ ^([a-zA-Z0-9_.-]+)(@(.+))$ ]]; then
                PACKAGE="${2}"
            elif [[ ${2} =~ ':' ]]; then
                PACKAGE="${2%:*}:pkgbase"
                CHILD="${2#*:}"
            else
                PACKAGE="${2}"
            fi
            [[ ${CHILD} == "pkgbase" ]] && unset CHILD
            export PACKAGE CHILD
            if [[ -z $PACKAGE ]]; then
                fancy_message error $"You failed to specify a package"
                exit 1
            fi
            check_url "https://github.com" 2> /dev/null || check_url "https://gitlab.com" 2> /dev/null || {
                fancy_message error $"Could not connect to the internet"
                suggested_solution $"Confirm that the URLs '%b' or '%b' are accessible" "${UGreen}https://github.com${NC}" "${UGreen}https://gitlab.com${NC}"
                exit 1
            }

            SEARCHINFO=true
            # shellcheck source=./misc/scripts/search.sh
            source "$SCRIPTDIR/scripts/search.sh"
            exit 0
            ;;

        -R | --remove)

            if [[ -z $2 ]]; then
                fancy_message error $"You failed to specify a package"
                exit 1
            fi

            while [[ -n $2 ]]; do
                PACKAGE=$2
                shift
                # shellcheck source=./misc/scripts/remove.sh
                if ! source "$SCRIPTDIR/scripts/remove.sh"; then
                    fancy_message error $"Failed to remove %b" "$GREEN$PACKAGE$NC"
                fi
            done
            exit 0
            ;;

        -A | --add-repo)
            REPO="${2%/}"
            ALIAS="${3#*@}"
            REPOCMD="add"

            if [[ ! -f "$SCRIPTDIR/repo/pacstallrepo" ]]; then
                echo 'https://raw.githubusercontent.com/pacstall/pacstall-programs/master' | sudo tee "$SCRIPTDIR/repo/pacstallrepo" > /dev/null
                return 0
            fi

            if [[ -n $REPO ]]; then
                # shellcheck source=./misc/scripts/add-repo.sh
                source "$SCRIPTDIR/scripts/add-repo.sh"
                exit 0
            else
                fancy_message error $"You failed to specify a repo to add"
                suggested_solution $"Add a repository in the following format:" "ZZZZ" "'${UCyan}pacstall -A https://github.com/username/repository-name${NC}'" "ZZZZ" $"Consult the pacstall man page by running '%b', and searching for the term '%b'" "${UCyan}man pacstall${NC}" "${UPurple}-A${NC}"
                exit 1
            fi
            ;;

        -Rr | --remove-repo)
            REPO="${2%/}"
            ALIAS="${3#*@}"
            REPOCMD="remove"

            if [[ ! -f "$SCRIPTDIR/repo/pacstallrepo" ]]; then
                echo 'https://raw.githubusercontent.com/pacstall/pacstall-programs/master' | sudo tee "$SCRIPTDIR/repo/pacstallrepo" > /dev/null
                return 0
            fi

            if [[ -n $REPO ]]; then
                # shellcheck source=./misc/scripts/add-repo.sh
                source "$SCRIPTDIR/scripts/add-repo.sh"
                exit 0
            else
                fancy_message error $"You failed to specify a repo to remove"
                suggested_solution $"Remove a repository in the following format:" "ZZZZ" "'${UCyan}pacstall -A https://github.com/username/repository-name${NC}'" "ZZZZ" $"Consult the pacstall man page by running '%b', and searching for the term '%b'" "${UCyan}man pacstall${NC}" "${UPurple}-Rr${NC}"
                exit 1
            fi
            ;;

        -V | --version)
            if [[ -f "$SCRIPTDIR/repo/update" ]]; then
                remote="$(< "$SCRIPTDIR/repo/update")"
                USERNAME="${remote%% *}"
                BRANCH="${remote##* }"
                if [[ $USERNAME != "pacstall" || $BRANCH != "master" ]]; then
                    version_develop="$USERNAME:$BRANCH"
                fi
            fi
            echo -e "${version_number} \033[1m\x1b[38;2;${version_color[r]:-255};${version_color[g]:-255};${version_color[b]:-255}m${version_name}${NC} ${version_develop}"
            exit 0
            ;;

        -U | --update)
            # If the input is `.`, is a directory, and there is no other input
            if [[ $2 == "." && -d $2 && -z $3 ]]; then
                cd "${2}"
                if ! git -C . rev-parse 2> /dev/null; then
                    fancy_message error $"%s is not a git repository" "$PWD"
                    exit 1
                fi
                mapfile -d":" GIT_USER < <(git remote get-url origin)
                USERNAME="${GIT_USER[1]%%/*}"
                BRANCH="$(git rev-parse --abbrev-ref HEAD 2> /dev/null)"
            else
                USERNAME="$2"
                BRANCH="$3"
            fi
            # This stuff gives the ability for persistent updates
            if [[ -z $BRANCH && -z $USERNAME ]]; then
                if [[ -f "$SCRIPTDIR/repo/update" ]]; then
                    remote="$(< "$SCRIPTDIR/repo/update")"
                    USERNAME="${remote%% *}"
                    BRANCH="${remote##* }"
                else
                    USERNAME="pacstall"
                    BRANCH="master"
                fi
            fi
            if [[ -z $BRANCH ]]; then
                if [[ $USERNAME == *":"* ]]; then
                    BRANCH="${USERNAME#*:}"
                    USERNAME="${USERNAME%:*}"
                else
                    BRANCH="master"
                fi
            fi

            check_url "https://github.com" 2> /dev/null || check_url "https://gitlab.com" 2> /dev/null || {
                fancy_message error $"Could not connect to the internet"
                suggested_solution $"Confirm that the URLs '%b' or '%b' are accessible" "${UGreen}https://github.com${NC}" "${UGreen}https://gitlab.com${NC}"
                exit 1
            }

            if curl --output /dev/null --silent --head --fail https://raw.githubusercontent.com/"$USERNAME"/pacstall/"$BRANCH"/pyproject.toml || curl --output /dev/null --silent --head --fail https://raw.githubusercontent.com/"$USERNAME"/pacstall/"$BRANCH"/Cargo.toml; then
                fancy_message error $"The repository you are trying to upgrade to contains a version of pacstall that cannot be updated from %b" "$(pacstall -V)"
                fancy_message error $"Visit %s for more information on how to reinstall. Most packages will also need to be reinstalled" "https://github.com/$USERNAME/pacstall/tree/$BRANCH"
                exit 1
            fi
            sudo wget -q -N https://raw.githubusercontent.com/"$USERNAME"/pacstall/"$BRANCH"/misc/scripts/update.sh -P "$SCRIPTDIR/scripts" 2> /dev/null
            # shellcheck source=./misc/scripts/update.sh
            source "$SCRIPTDIR/scripts/update.sh"
            exit 0
            ;;

        -L | --list)
            shift
            if [[ -n $* ]]; then
                fancy_message error $"Invalid argument '%s'" "$*"
                exit 1
            fi

            if [[ ! -d ${METADIR} ]]; then
                exit 1
            fi

            cd "${METADIR}" || exit 1
            shopt -s nullglob
            packages_installed=(*)
            if ((${#packages_installed[@]} == 0)) && [[ -t 1 ]]; then
                fancy_message error $"Nothing installed yet"
                exit 1
            elif ((${#packages_installed[@]} == 0)); then
                exit 1
            fi

            if [[ -t 1 ]]; then
                for pkg in "${packages_installed[@]}"; do
                    unset _pacstall_depends _pacdeps _name _version _install_date _date _ppa _homepage _gives _remoterepo _remotebranch _maintainer _mask 2> /dev/null
                    source ./"${pkg}"
                    echo -e "${BYellow}~${NC} ${BGreen}${_name}${BPurple}@${BCyan}${_version}${NC}"
                    if [[ -n ${_gives} ]]; then
                        echo -e "   ${BBlue}gives${NC}        : ${BOLD}${_gives}${NC}"
                    fi
                    echo -e "   ${BBlue}maintainer${NC}   : ${BOLD}${_maintainer:-$(dpkg-query '--showformat=${Maintainer}\n' --show "${_gives:-$_name}")}${NC}"
                    echo -e "   ${BBlue}size${NC}         : ${BOLD}${_install_size:-unknown}${NC}"
                    echo -e "   ${BBlue}repository${NC}   : ${BOLD}${_remoterepo:-local}${NC}"
                    echo -e "   ${BBlue}install date${NC} : ${BOLD}${_date:-unknown}${NC}"
                    if [[ ${pkg} != "${packages_installed[-1]}" ]]; then
                        echo
                    fi
                done
            else
                printf '%s\n' "${packages_installed[@]}"
            fi
            exit 0
            ;;

        -D | --download)

            if [[ -z $2 ]]; then
                fancy_message error $"You failed to specify a package"
                exit 1
            fi

            while [[ -n $2 ]]; do
                PACKAGE=$2
                shift
                export type="download"

                check_url "https://github.com" 2> /dev/null || check_url "https://gitlab.com" 2> /dev/null || {
                    fancy_message error $"Could not connect to the internet"
                    suggested_solution $"Confirm that the URLs '%b' or '%b' are accessible" "${UGreen}https://github.com${NC}" "${UGreen}https://gitlab.com${NC}"
                    exit 1
                }

                # shellcheck source=./misc/scripts/search.sh
                if ! source "$SCRIPTDIR/scripts/search.sh"; then
                    continue
                fi

                repo.specify "$REPO"
                export URL="$REPO/packages/$PACKAGE/$PACKAGE.pacscript"

                # shellcheck source=./misc/scripts/get-pacscript.sh
                if ! source "$SCRIPTDIR/scripts/get-pacscript.sh"; then
                    fancy_message error $"Failed to download the %b pacscript" "$GREEN$PACKAGE$NC"
                    suggested_solution $"Confirm that the package exists by running '%b'" "${UCyan}pacstall -S $PACKAGE${NC}" "ZZZZ" $"Check your internet connection"
                    continue
                fi

                fancy_message info $"%b pacscript is downloaded to %b" "$GREEN$PACKAGE$NC" "$GREEN$PWD/$PACKAGE.pacscript$NC"
            done
            exit 0
            ;;

        -Up | --upgrade)
            shift
            if [[ -n $* ]]; then
                fancy_message error $"Invalid argument '%s'" "$*"
                exit 1
            fi

            check_url "https://github.com" 2> /dev/null || check_url "https://gitlab.com" 2> /dev/null || {
                fancy_message error $"Could not connect to the internet"
                suggested_solution $"Confirm that the URLs '%b' or '%b' are accessible" "${UGreen}https://github.com${NC}" "${UGreen}https://gitlab.com${NC}"
                exit 1
            }
            # shellcheck source=./misc/scripts/upgrade.sh
            source "$SCRIPTDIR/scripts/upgrade.sh"
            exit 0
            ;;

        -T | --tree)
            PACKAGE="$2"

            if [[ -z $PACKAGE ]]; then
                fancy_message error $"You failed to specify a package"
                exit 1
            fi
            if ! [[ -f $METADIR/$PACKAGE ]]; then
                fancy_message error $"Package is not installed"
                exit 1
            fi

            source "$METADIR/$PACKAGE"

            dpkg --listfiles "${_gives:-$_name}" | sed -e "s/[^-][^\/]*\//  │/g" -e "s/│\([^ ]\)/│──\1/"
            exit 0
            ;;

        -Ci | --cache-info)
            PACKAGE=$2
            QUERY=$3
            # shellcheck source=./misc/scripts/query-info.sh
            source "$SCRIPTDIR/scripts/query-info.sh"
            exit 0
            ;;

        -Qa | --quality-assurance)
            if [[ $2 =~ ^([a-zA-Z0-9_.-]+)?(@(.+))?(#([0-9]+))$ ]]; then
                PACKAGE="${BASH_REMATCH[1]}"
                METAURL="${BASH_REMATCH[3]}"
                PRNUM="${BASH_REMATCH[5]}"
            elif [[ $2 =~ ^([a-zA-Z0-9_.-]+)(#([0-9]+))?(@(.+))?$ ]]; then
                PACKAGE="${BASH_REMATCH[1]}"
                PRNUM="${BASH_REMATCH[3]}"
                METAURL="${BASH_REMATCH[5]}"
            fi
            # shellcheck source=./misc/scripts/quality-assurance.sh
            source "$SCRIPTDIR/scripts/quality-assurance.sh"
            exit 0
            ;;

        -K | --keep)
            fancy_message info $"Keeping build files"
            KEEP=true
            ;;

        -Nc | --nocheck)
            fancy_message info $"Skipping check() if present"
            NOCHECK=true
            ;;

        -Q | --quiet)
            fancy_message info $"Downloading package entries quietly"
            PACSTALL_VERBOSE=false
            ;;

        -Ns | --nosandbox)
            fancy_message warn $"Building package without bwrap sandbox"
            fancy_message sub $"Be cautious, this exposes your system to potential harm"
            NOSANDBOX=true
            ;;

        *)
            pacstall -h
            exit 3
            ;;
    esac
    shift
done
if [[ $1 == '--' ]]; then shift; fi

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