#!/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.2.1"
version_name="Lemon-Lime-Flavored Non-Caffeinated Soft Drink"
declare -A version_color=([r]="0" [g]="186" [b]="71")

# Configuration
export LC_ALL=C
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 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}"
    local MESSAGE="${2}"

    case ${MESSAGE_TYPE} in
        info) echo -e "[${BGreen}+${NC}] ${BOLD}INFO${NC}: ${MESSAGE}" ;;
        warn) echo >&2 -e "[${BYellow}*${NC}] ${BOLD}WARNING${NC}: ${MESSAGE}" ;;
        error) echo >&2 -e "[${BRed}!${NC}] ${BOLD}ERROR${NC}: ${MESSAGE}" ;;
        sub) echo -e "\t[${BBlue}>${NC}] ${MESSAGE}" ;;
        *) echo >&2 -e "[${BOLD}?${NC}] ${BOLD}UNKNOWN${NC}: ${MESSAGE}" ;;
    esac
}

# 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() {
    local prompt default reply

    if [[ ${2-} == 'Y' ]]; then
        prompt="${BIGreen}Y${NC}/${RED}n${NC}"
        default='Y'
    elif [[ ${2-} == '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)
    echo -ne "$1 [$prompt] "

    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
                return 0 #return code for backwards compatibility
                break
                ;;
            N* | n*)
                export answer=0
                return 1 #return code
                break
                ;;
            *)
                echo -ne "$1 [$prompt] "
                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() {
    if [[ -z $PACSTALL_SUPPRESS_SOLUTIONS ]]; then
        local inputs=("${@}")
        if ((${#inputs[@]} > 1)); then
            local text="Suggested solutions are:"
        else
            local text="Suggested solution is:"
        fi
        echo -e "[${BOLD}${BPurple}⠿${NC}] ${text}"
        for i in "${inputs[@]}"; do
            echo -e "    ${BOLD}|${NC} $i"
        done
    fi
}

function check_url() {
    if [[ ${1} == "file://"* ]]; then
        if ! [[ -f ${1/"file://"/} ]]; then
            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 '${UGreen}$1${NC}' exists"
                else
                    fancy_message error "Failed to download file, check your connection"
                fi
                error_log 1 "get ${PACKAGE} pacscript"
                return 1
                ;;
            404)
                fancy_message error "The URL cannot be found"
                suggested_solution "Confirm that '${UGreen}$1${NC}' exists"
                return 1
                ;;
            200 | 301 | 302)
                return 0
                ;;
            *)
                fancy_message error "Failed with http code ${http_code}"
                suggested_solution "Confirm that '${UGreen}$1${NC}' is accessible"
                return 1
                ;;
        esac
    fi
}

# use axel if available
function download() {
    local src="$1" out="${2:-${src##*/}}"
    if [[ $PACSTALL_DOWNLOADER != "payload" ]]; then
        sudo rm -f "$out"
    fi
    if [[ -z $PACSTALL_DOWNLOADER || -f "/tmp/pacstall-pacdeps-$PACKAGE" ]]; then
        if command -v axel &> /dev/null; then
            PACSTALL_DOWNLOADER=axel
        elif command -v wget &> /dev/null; 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" || return 1
            ;;
        axel)
            axel -a -q -o "$out" "$src" || return 1
            ;;
        verbose-curl)
            curl -L -# -o "$out" "$src" || return 1
            ;;
        curl)
            curl -sSL -o "$out" "$src" || return 1
            ;;
        verbose-wget)
            wget -q --show-progress --progress=bar:force -O "$out" -- "$src" 2>&1 || return 1
            ;;
        payload) ;;
        wget | *)
            wget -q -O "$out" -- "$src" 2>&1 || return 1
            ;;
    esac
}

# source this code block and run like so:
# 	$ select_options "My message I want to send" "${#array[@]}"
# This will then output the options given by the user to /tmp/pacstall-select-options, which you can then turn into another array
function select_options() {
    rm -f /tmp/pacstall-select-options
    local message="${1}"
    local length="${2}"

    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 /tmp/pacstall-select-options > /dev/null
    elif ((input == 0)) || [[ $input =~ ^[Nn]$ ]]; then
        echo "n" | tee /tmp/pacstall-select-options > /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"; 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"; fi
                        ;;
                    *)
                        select_options "$message" "$length"
                        ;;
                esac
                out+=($(seq "${split_arr[0]}" "${split_arr[1]}"))
                continue
            else
                out+=("$i")
            fi
        done
        echo "${out[@]}" | tee /tmp/pacstall-select-options > /dev/null
    else
        select_options "$message" "$length"
    fi
}

# Return 0 if exists, 1 if not
function is_package_installed() {
    local input="${1}"
    while read -r line; do
        if [[ ${line} == "${input}" ]]; then
            return 0
        fi
    done < <(pacstall -L)
    return 1
}

# Returns 0 if exists, 1 if not
function is_apt_package_installed() {
    if [[ $(dpkg-query -W --showformat='${db:Status-Status}' "${1}" 2> /dev/null) == "installed" ]]; then
        return 0
    else
        return 1
    fi
}

function is_array() {
    local input raw
    input="${1}"
    raw="$(declare -p "${input}" 2> /dev/null)"
    if [[ ${raw} == "declare -a ${input}"* ]]; then
        return 0
    else
        return 1
    fi
}

function is_function() {
    if [[ $(type -t "${1}") == "function" ]]; then
        return 0
    else
        return 1
    fi
}

function array.contains() {
    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
    return 1
}

function getMasks() {
    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() {
    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
    return 1
}

function decl_scriptvars() {
    local _distros _vars _archs _sums distros \
        vars="source depends makedepends optdepends pacdeps checkdepends provides conflicts breaks replaces enhances recommends 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=(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 custom_fields makeconflicts checkconflicts)
    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

# run sudo apt update if it's been more than a week
[[ -z "$(find -H /var/lib/apt/lists -maxdepth 0 -mtime -7)" ]] && sudo apt-get update -qq --allow-releaseinfo-change

# 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"
    ["--query-info"]="-Qi" ["--tree"]="-T" ["--version"]="-V" ["--help"]="-h"
    ["--build"]="-B" ["--keep"]="-K" ["--disable-prompts"]="-P" ["--nocheck"]="-Nc"
    ["--quality-assurance"]="-Qa" ["--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" "-Qi" "-Qa" "-T" "-V" "-h")
long_commands=("--install" "--search" "--remove" "--download" "--add-repo" "--remove-repo" "--update" "--list" "--upgrade" "--query-info" "--quality-assurance" "--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() {
    # Total number of options flags, increase when needed
    local option_flags=5
    local ignore_short="-S -D -A -Rr -V -L -Qi -T -h"
    local ignore_long="--search --download --add-repo --remove-repo --version --query-info --tree --help"
    local ignore="$ignore_short $ignore_long"

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

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

    pidof -o %PPID -x "$0" > /dev/null && return 0 || 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,-Qi,-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}-Qi${NC}, ${BOLD}--query-info${NC} <package>
		Query information about a package.
	${BOLD}-T${NC}, ${BOLD}--tree${NC} <package>
		Display a tree graph of a 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)

            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 ${PACKAGE:-package} was interrupted, removing files"
                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" ]]; then
                    export PACKAGE="${2%.pacscript}"
                    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

                    # Check if the file exist
                    if [[ ! -f "$PACKAGE".pacscript ]]; then
                        fancy_message error "$2 does not exist"
                        shift
                        continue
                    fi
                else
                    export type="install"
                    export local="no"
                    export PACKAGE=$2
                    if [[ -z $PACKAGE ]]; then
                        fancy_message error "You failed to specify a package"
                        exit 1
                    fi

                    if [[ -n $PACSTALL_PAYLOAD && ! -f "/tmp/pacstall-pacdeps-$PACKAGE" ]]; 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 '${UGreen}https://github.com${NC}' or '${UGreen}https://gitlab.com${NC}' are accessible"
                        exit 1
                    }

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

                    specifyRepo "$REPO"
                    export REPO
                    export URL="${REPO:-https://raw.githubusercontent.com/pacstall/pacstall-programs/master}/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 ${GREEN}${PACKAGE}${NC} pacscript"
                        suggested_solution "Confirm that the package exists by running '${UCyan}pacstall -S $PACKAGE${NC}'" "Check your internet connection"
                        continue
                    fi
                fi
                # shellcheck source=./misc/scripts/package.sh
                if ! source "$SCRIPTDIR/scripts/package.sh"; then
                    fancy_message error "Failed to install ${GREEN}${PACKAGE}${NC}"
                    if ! [[ -f "/tmp/pacstall-pacdeps-$PACKAGE" ]]; then
                        sudo rm -rf "${PACDIR:?}"
                    fi
                    exit 1
                fi
                shift
            done
            exit 0
            ;;

        -S | --search)

            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 '${UGreen}https://github.com${NC}' or '${UGreen}https://gitlab.com${NC}' are accessible"
                exit 1
            }

            # 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 ${GREEN}${PACKAGE}${NC}"
                fi
            done
            exit 0
            ;;

        -A | --add-repo)
            REPO="$2"
            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:" "'${UCyan}pacstall -A https://github.com/username/repository-name${NC}'" "Consult the pacstall man page by running '${UCyan}man pacstall${NC}', and searching for the term '${UPurple}-A${NC}'"
                exit 1
            fi
            ;;

        -Rr | --remove-repo)
            REPO="$2"
            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:" "'${UCyan}pacstall -Rr https://github.com/username/repository-name${NC}'" "Consult the pacstall man page by running '${UCyan}man pacstall${NC}', and searching for the term '${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 "$PWD is not a git repository"
                    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 '${UGreen}https://github.com${NC}' or '${UGreen}https://gitlab.com${NC}' are accessible"
                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 $(pacstall -V)"
                fancy_message error "Visit https://github.com/$USERNAME/pacstall/tree/$BRANCH for more information on how to reinstall. Most packages will also need to be reinstalled"
                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 '$*'"
                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 '${UGreen}https://github.com${NC}' or '${UGreen}https://gitlab.com${NC}' are accessible"
                    exit 1
                }

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

                specifyRepo "$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 ${GREEN}${PACKAGE}${NC} pacscript"
                    suggested_solution "Confirm that the package exists by running '${UCyan}pacstall -S $PACKAGE${NC}'" "Check your internet connection"
                    continue
                fi

                fancy_message info "${GREEN}${PACKAGE}${NC}'s pacscript is downloaded to ${GREEN}${PWD}/$PACKAGE.pacscript${NC}"
            done
            exit 0
            ;;

        -Up | --upgrade)
            shift
            if [[ -n $* ]]; then
                fancy_message error "Invalid argument '$*'"
                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 '${UGreen}https://github.com${NC}' or '${UGreen}https://gitlab.com${NC}' are accessible"
                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
            ;;

        -Qi | --query-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 noet:
