#!/bin/bash

# una - a complete mpr helper
# Copyright (C) 2022  Rudra Saraswat

# This program 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, either version 3 of the License, or
# (at your option) any later version.

# This program 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 this program.  If not, see <https://www.gnu.org/licenses/>.

bold="$(tput bold)"
normal="$(tput sgr0)"

red=$'\e[1;31m'
green=$'\e[1;32m'
yellow=$'\e[1;33m'
blue=$'\e[1;34m'
magenta=$'\e[1;35m'
cyan=$'\e[1;36m'

version='v3.1.1'

set -e

echo "${cyan}una ${version}${normal}" 1>&2

fatal() {
    echo -e "${bold}${red}>>${normal}${bold} $*${normal}" 1>&2
    kill $$
}

error() {
    echo -e "${bold}${red}>>${normal}${bold} $*${normal}" 1>&2
}

sshell_exit() {
    kill $$
}

info() {
    echo -e "${bold}${blue}>>${normal}${bold} $*${normal}" 1>&2
}

yes_no() {
    read -p "${bold}${green}>>${normal}${bold} $* ${cyan}[${blue}y/${red}n${cyan}]${normal}: " -n 1 -r -s yn
    echo "$yn"
}

choose_option() {
    if [[ "$#" -eq 1 ]]; then
        echo "$1";
        exit
    fi
    i=1
    for option in "$@"; do
        echo -e "${bold}${cyan}${i}.${normal} ${bold}${option}${normal}" 1>&2
        ((i++))
    done
    optionnum=''
    while [[ ! "$optionnum" =~ ^[0-9]+$ ]] || [[ "$optionnum" -gt "$#" ]]; do
        read -p "${bold}${green}>>${normal}${bold} enter the option number: ${normal}" optionnum 1>&2
    done
    echo "${!optionnum}"
    num='^[0-9]+$'
}

_spinner() {
    pid="$!"
    while kill -0 "$pid" 2>/dev/null; do
        echo -en "${bold}${blue}>>${normal}${bold} $* ${normal}" 1>&2
        BCK=''
        for char in ⣾ ⣽ ⣻ ⢿ ⡿ ⣟ ⣯ ⣷; do
            echo -en "${BCK}${char}" 1>&2
            sleep 0.1; BCK=$'\b'
        done
        echo -en "\033[2K\r" 1>&2
    done
}

upgrade_command() {
    local pkg="$1"

    python3 - "${pkg}" <<'EOF'
import os, sys, json, requests

try:
    with open(os.path.expanduser('~/.cache/una/cache')) as pakfile:
        paklist = json.load(pakfile)

        all_packages = [ x["Name"] for x in paklist ]
        all_versions = [ x["Version"] for x in paklist ]

        data = sys.argv[1]

        try:
            local_version = data.split(' ')[1]
            server_version = all_versions[all_packages.index(data.split(' ')[0])]
            local_version = local_version.split('-')[0]
            server_version = server_version.split('-')[0]
            try:
                local_version = local_version.split(':')[1]
                server_version = server_version.split(':')[1]
            except:
                print('')
            if local_version != '' and local_version != server_version:
                print(f"cmd_install {data.split(' ')[0]} force mpr")
        except:
            print('')
except:
    print('')
EOF
}

_help() {
    echo
    echo "Syntax: una <COMMAND> [PACKAGES]"
    echo
    echo "commands:"
    echo
    echo "  install     Install a package."
    echo
    echo "              --edit before a package's name to edit"
    echo "              or view the PKGBUILD file for that package."
    echo
    echo "  update      Update the APT and MPR cache."
    echo "  upgrade     Upgrade all installed packages."
    echo "  remove      Remove a package."
    echo "  autoremove  Remove auto-installed deps which are no longer required."
    echo "  list        List installed packages."
    echo "  info        Show package information."
    echo "  search      Search for packages."
    echo "  clone       Clone a package from the MPR."
    echo "  help        Show the usage."
}

_install() {
    sudo mkdir -p /var/lib/una

    local pkgname="${1:?}"

    echo
    info 'clearing old build directory'
    rm -rf "${HOME}/.cache/una/pkgs/${pkgname}" "${HOME}/.cache/una/pkgs/${pkgname}-git"
    echo
    info 'creating build directory'
    mkdir -p ~/.cache/una/pkgs && cd ~/.cache/una/pkgs

    if [[ "$3" == "git" ]]; then
        info "cloning ${pkgname}"
        LC_ALL=C git clone "https://mpr.makedeb.org/${pkgname}.git" 2>&1 | grep -q 'empty repository' \
            && { echo; fatal "this package doesn't exist"; } || true
    else
        force="$2"

        LC_ALL=C git clone "https://mpr.makedeb.org/${pkgname}.git" &>/dev/null & _spinner "cloning ${pkgname}"

        if [[ "$force" != "force" ]]; then
            LC_ALL=C dpkg-query -W -f='${Status}' "${pkgname}" 2>/dev/null | grep -q "ok installed" && return 0 || true;
            LC_ALL=C dpkg-query -W -f='${Status}' "${pkgname}-git" 2>/dev/null | grep -q "ok installed" && return 0 || true;
        elif [[ "$2" == "force" ]] && [[ -f "/tmp/${pkgname}.lock" ]]; then
            rm -f "/tmp/${pkgname}.lock"; return 0;
        fi
    fi

    source /etc/os-release

    depends=()
    makedepends=()
    checkdepends=()
    pkgver=""
    codename_depends="${VERSION_CODENAME}_depends"
    codename_makedepends="${VERSION_CODENAME}_makedepends"
    eval "${codename_depends}=()"
    eval "${codename_makedepends}=()"

    if [[ "${EDIT}" == "true" ]]; then
        { sensible-editor "${pkgname}"/PKGBUILD || nano "${pkgname}"/PKGBUILD; } || vi "${pkgname}"/PKGBUILD
        unset EDIT
    fi

    source "${pkgname}/PKGBUILD"

    codename_depends="${VERSION_CODENAME}_depends"
    codename_makedepends="${VERSION_CODENAME}_makedepends"
    [[ "${!codename_depends}" != "" ]] && eval "depends=(\${$codename_depends[*]})"
    [[ "${!codename_makedepends}" != "" ]] && eval "makedepends=(\${$codename_makedepends[*]})"

    echo
    info "installing dependencies of ${pkgname}"
    for depend in "${depends[@]}" "${makedepends[@]}" "${checkdepends[@]}"; do # "${new_optdepends[@]}"; do
        dpkg-query -W -f='${Status}' "${depend}" 2>/dev/null | grep -q "ok installed" && true || { cmd_install "$depend" depend || echo lol; }
    done

    pkgname="${1:?}"

    echo
    info "building ${pkgname}"
    echo
    cd "${HOME}/.cache/una/pkgs/${pkgname}" && { makedeb -H "MPR-Package: ${pkgname}" -H "MPR-Version: ${pkgver}" -s 2>/dev/null \
        || { makedeb -H "MPR-Version: ${pkgver}" -s 2>/dev/null || { makedeb -s || { [[ "$2" == "depend" ]] && exit 1 || { error 'an error occurred when building the package'; return 1; }; } }; }; }
    if [[ "$2" == "depend" ]]; then
        LC_ALL=C dpkg-query -W -f='${Status}' "${pkgname}" 2>/dev/null || mark=auto
    fi
    for pkg in ./${pkgname}_*.deb; do sudo apt-get "${re}install" -y --no-install-recommends "$pkg" || { error 'an error occurred when building the package'; return 1; }; done
    [[ "$mark" == "auto" ]] && { sudo apt-mark auto "$pkgname"; unset mark; } || true
}

_upgrade() {
    if [[ "$1" != "" ]] && [[ "$1" != "updates" ]]; then
        cmd_install "$1" force; return 0;
    fi

    [[ "$1" == "updates" ]] || info 'upgrading MPR packages'
    grep -v '^$' /var/lib/una/status | while IFS= read -r line; do
        [[ $(upgrade_command "$line") == "" ]] && continue
        [[ "$1" == "updates" ]] && upgrade_command "$line" || true
        [[ "$1" == "updates" ]] || $(upgrade_command "$line")
    done
    [[ "$1" == "updates" ]] && return 0 || true

    echo
    info 'upgrading APT packages'
    sudo apt-get dist-upgrade -y --auto-remove
}

_remove() {
    pkgname="${1:?}"

    sudo apt-get purge -y "${pkgname}"
}

_search() {
    pkgname="${1:?}"

    [[ "${#pkgname}" -lt 2 ]] && fatal 'search string too short (the length should be greater than 1)'

    curl -s "https://mpr.makedeb.org/rpc?v=5&type=search&arg=${pkgname}" | jq -cr ".results[] | \"${blue}\(.Name)/\(.Version) ${cyan}[MPR]${normal}\n  \(.Description)\""

    for pkgname in $(apt-cache pkgnames hello 2>/dev/null); do
        echo "${blue}${pkgname}/$(LANG=C apt-cache show ${pkgname} | grep '^Version: ' | sed '0,/Version: /{s/Version: //}') ${cyan}[APT]${normal}"
        echo "  $(LANG=C apt-cache show ${pkgname} | grep '^Description-en: ' | sed '0,/Description-en: /{s/Description-en: //}')"
    done
}

_info() {
    pkgname="${1:?}"

    python3 - "${pkgname}" <<'EOF' || ( apt-cache show "${pkgname}" 2>/dev/null || fatal "this package doesn't exist or you're not connected to a network" )
import requests, sys, os

from datetime import datetime

class color:
   PURPLE = '\033[95m'
   CYAN = '\033[96m'
   DARKCYAN = '\033[36m'
   BLUE = '\033[94m'
   GREEN = '\033[92m'
   YELLOW = '\033[93m'
   RED = '\033[91m'
   BOLD = '\033[1m'
   UNDERLINE = '\033[4m'
   END = '\033[0m'

try:
    resp = requests.get(f"https://mpr.makedeb.org/rpc?v=5&type=info&arg={sys.argv[1]}")
    pkginfo = resp.json()
    print(color.BOLD + 'Name:' + color.END, pkginfo['results'][0]['Name'])
    print(color.BOLD + 'Maintainer:' + color.END, pkginfo['results'][0]['Maintainer'])
    print(color.BOLD + 'Last modified:' + color.END, datetime.fromtimestamp(pkginfo['results'][0]['LastModified']))
    print(color.BOLD + 'Description:' + color.END, pkginfo['results'][0]['Description'])
    print(color.BOLD + 'Upstream URL:' + color.END, pkginfo['results'][0]['URL'])
    print(color.BOLD + 'Latest Version:' + color.END, pkginfo['results'][0]['Version'])
    print(color.BOLD + 'Git Clone URL:' + color.END, 'https://mpr.makedeb.org/' + pkginfo['results'][0]['Name'] + '.git')
    print(color.BOLD + 'Depends:' + color.END)
    try:
        for i, depend in enumerate(pkginfo['results'][0]['Depends']):
            if i == 5:
                print('    ...')
                break
            else:
                print(f"{color.BOLD}  - {color.END}{depend}")
    except:
        print()
except:
    sys.exit(1)
EOF
}

_update() {
    if [[ "$1" == "user" ]]; then
        { curl -qs 'https://mpr.makedeb.org/packages-meta-ext-v1.json.gz' | gunzip | tee ~/.cache/una/cache &>/dev/null; } || fatal 'are you sure you are connected to a network?'
    else
        echo
        sudo echo -n
        ( sudo apt-get update &>/dev/null || true; ) & _spinner 'updating APT cache'
        tput civis
        (
            { curl -qs 'https://mpr.makedeb.org/packages-meta-ext-v1.json.gz' | gunzip | sudo tee /var/lib/una/cache &>/dev/null; } || fatal 'are you sure you are connected to a network?'
            { curl -qs 'https://mpr.makedeb.org/packages-meta-ext-v1.json.gz' | gunzip | tee ~/.cache/una/cache &>/dev/null; } || fatal 'are you sure you are connected to a network?'
        ) & _spinner 'updating MPR cache'
        info "${bold}updated cache successfully${normal}"
    fi
}

_installable() {
    local pkgname="${1:?}"

    sources=()

    LC_ALL=C apt-cache show -q "${pkgname}" &>/dev/null && sources+=(apt)

    sources+=($(python3 - "${pkgname}" <<'EOF'
import requests, json, sys, os

try:
    with open(os.path.expanduser('~/.cache/una/cache')) as pakfile:
        paklist = json.load(pakfile)

        all_packages = [ x["Name"] for x in paklist ]

        if sys.argv[1] in all_packages:
            print('mpr')
        else:
            sys.exit()
except:
    sys.exit()
EOF
    ))

    sources+=($(python3 - "${pkgname}" <<'EOF'
import requests, json, sys, os

try:
    with open(os.path.expanduser('~/.cache/una/cache')) as pakfile:
        paklist = json.load(pakfile)

        all_packages = [ x["Name"] for x in paklist ]

        if sys.argv[1] + '-git' in all_packages:
            print('mpr-git')
        else:
            sys.exit()
except:
    sys.exit()
EOF
    ))

    sources+=($(python3 - "${pkgname}" <<'EOF'
import requests, json, sys, os

try:
    with open(os.path.expanduser('~/.cache/una/cache')) as pakfile:
        paklist = json.load(pakfile)

        all_packages = [ x["Name"] for x in paklist ]

        if sys.argv[1] + '-bin' in all_packages:
            print('mpr-bin')
        else:
            sys.exit()
except:
    sys.exit()
EOF
    ))

    if [[ "${#sources[@]}" -eq "0" ]]; then
        return 1
    else
        return 0
    fi
}

_installed() {
    local pkgname="$1"

    if { LC_ALL=C dpkg-query -W -f='${Status}' "${pkgname}" 2>/dev/null | grep -q "ok installed"; } ||
        { LC_ALL=C dpkg-query -W -f='${Status}' "${pkgname}-git" 2>/dev/null | grep -q "ok installed"; } ||
        { LC_ALL=C dpkg-query -W -f='${Status}' "${pkgname}-bin" 2>/dev/null | grep -q "ok installed"; }; then
        echo -n installed
    else
        echo -n not-installed
    fi
}

_clone() {
    pkgname="$1"

    [[ -d "$pkgname" ]] && { info "The directory ${pkgname} already exists. Do you want to overwrite it? (press ENTER to proceed, or ^C to exit)"; }
    read; rm -rf "$pkgname"
    LC_ALL=C git clone "https://mpr.makedeb.org/${pkgname}.git" 2>&1 | grep -q 'empty repository' && \
        { rm -rf "${pkgname}"; fatal "this MPR package doesn't exist or an error occured"; } || info "Cloned the git repo"
}

_status_file_update() {
    sudo cp /var/lib/una/status /var/lib/una/status.old
    LC_ALL=C dpkg-query -W --showformat='${MPR-Package} ${MPR-Version}\n' | sed -r '/^\s*$/d' | sudo tee /var/lib/una/status &>/dev/null
}

cmd_install() {
    local pkgname="$(echo ${1} | sed 's/ //g')"

    if [[ -z "$pkgname" ]]; then
        return 0
    fi

    if [[ "$(echo "$pkgname" | grep -o -i '|' | wc -l)" != 0 ]] && [[ ! -z "$(echo "${pkgname}" | cut -d '|' -f2)" ]]; then
        success=0

        for i in $(seq 1 $(("$(echo "$pkgname" | grep -o -i '|' | wc -l)"+1))); do
            local pkg="$(echo "${pkgname}" | cut -d '|' -f"$i")"
            [[ -z "$pkg" ]] && continue || true
            [[ "$(_installed "$pkg")" == "installed" ]] && { success=1; break; } || true
            _installable "$pkg" && cmd_install "$pkg" || continue
        done

        [[ ! "$success" -eq 1 ]] && fatal "failed to install ${1}"

        return 0
    fi

    if [[ "$2" == "depend" ]]; then
        [[ "$(_installed "$pkg")" == 'installed' ]] && return 0 || true
    fi

    if [[ "$2" != "force" ]]; then
        [[ "$(_installed "$pkg")" == 'installed' ]] && fatal "${pkgname} is already installed" || true
    fi

    sources=()

    { apt-cache show "${pkgname}" &>/dev/null && [[ "$(_installed "$pkgname")" == "not-installed" ]]; } && sources+=(apt) || true

    sources+=($(python3 - "${pkgname}" <<'EOF'
import requests, json, sys, os

try:
    with open(os.path.expanduser('~/.cache/una/cache')) as pakfile:
        paklist = json.load(pakfile)

        all_packages = [ x["Name"] for x in paklist ]

        if sys.argv[1] in all_packages:
            print('mpr')
        else:
            sys.exit()
except:
    sys.exit()
EOF
    ))

    sources+=($(python3 - "${pkgname}" <<'EOF'
import requests, json, sys, os

try:
    with open(os.path.expanduser('~/.cache/una/cache')) as pakfile:
        paklist = json.load(pakfile)

        all_packages = [ x["Name"] for x in paklist ]

        if sys.argv[1] + '-git' in all_packages:
            print('mpr-git')
        else:
            sys.exit()
except:
    sys.exit()
EOF
    ))

    sources+=($(python3 - "${pkgname}" <<'EOF'
import requests, json, sys, os

try:
    with open(os.path.expanduser('~/.cache/una/cache')) as pakfile:
        paklist = json.load(pakfile)

        all_packages = [ x["Name"] for x in paklist ]

        if sys.argv[1] + '-bin' in all_packages:
            print('mpr-bin')
        else:
            sys.exit()
except:
    sys.exit()
EOF
    ))
    if [[ "${#sources[@]}" -eq "0" ]]; then
        error "the package ${pkgname} does not exist"
        [[ "$2" == "depend" ]] && exit 1 || return 0
    fi
    [[ "$2" == "force" ]] && local re=re
    [[ "$2" == "force" ]] && local opt=force
    [[ "$2" == "depend" ]] && local opt=depend
    if [[ "$3" == "mpr" ]]; then
        _install "$pkgname" $opt
    else
        echo
        info "select the source from which ${pkgname} should be installed"
        case "$(choose_option "${sources[@]}")" in
            apt)
                if [[ "$2" == "depend" ]]; then
                    [[ "$(_installed "${pkgname}")" == "not-installed" ]] && mark=auto || true
                fi
                sudo apt-get "${re}install" -y "$pkgname"
                [[ "$mark" == "auto" ]] && { sudo apt-mark auto "$pkgname"; unset mark; } || true
                ;;
            mpr)
                _install "$pkgname" $opt
                ;;
            mpr-git)
                _install "${pkgname}-git" $opt
                ;;
            mpr-bin)
                _install "${pkgname}-bin" $opt
                ;;
        esac
    fi
}

if [[ "$EUID" == "0" ]] && [[ "$3" != "root" ]]; then
    fatal 'running as root can cause problems'
fi

# check if lockfile exists
LOCKFILE=/tmp/una.lock
if [[ -e /tmp/una.lock ]] && kill -0 "$(cat "${LOCKFILE}")" 2>/dev/null; then
    fatal "already running as PID $(cat "${LOCKFILE}")"
    exit
fi

# create lockfile
trap "rm -f "${LOCKFILE:?}"; tput cnorm; exit" INT TERM EXIT
echo $$ > ${LOCKFILE}

if [[ ! -d '/var/lib/una' ]] || { [[ ! -f '/var/lib/una/status' ]] || { [[ ! -f "$HOME/.config/autostart/una-updater.desktop" ]] || { [[ ! -f '/var/lib/una/cache' ]] || [[ ! -d "${HOME}/.cache/una" ]]; } } }; then
    echo
    info 'creating una directory and config files'
    mkdir -p "${HOME}/.config/autostart"
    cat <<'EOF' >"${HOME}/.config/autostart/una-updater.desktop"
[Desktop Entry]
Type=Application
Exec=una-updater
Hidden=false
NoDisplay=false
X-GNOME-Autostart-enabled=true
Name=Una Updater
Comment=Auto-updater for APT and MPR packages.
EOF
    sudo mkdir -p /var/lib/una
    mkdir -p ~/.cache/una
    sudo touch /var/lib/una/status /var/lib/una/cache
    grep -q '^una-bin ' /var/lib/una/status 2>/dev/null || \
        { echo "una-bin $(LC_ALL=C dpkg-query --show --showformat '${Version}\n' una-bin)" | sudo tee /var/lib/una/status &>/dev/null; }
    echo
    info 'looks like you are a new user or una was updated'
    info "run una update every once in a while to update una's package cache"
    info "you might face issues if it's outdated"
    sleep 3

    _update
fi

if ! { [[ "$1" == "updates" ]] || { [[ "$1" == update ]] && [[ "$2" == user ]]; } || { [[ "$1" == help ]] || [[ "$1" == "" ]]; }; }; then
    sudo echo -n
    _status_file_update & _spinner 'updating MPR status'
fi

if [[ "$1" == "install" ]] && [[ "$2" != "" ]]; then
    for app in "${@:2}"; do
        if [[ "$app" == --edit ]]; then
            export EDIT="true"
        else
            cmd_install "$app"
        fi
    done
elif [[ "$1" == "update" ]]; then
    _update "$2";
elif [[ "$1" == "updates" ]]; then
    updates="$(_upgrade updates)"
    [[ -z "${updates}" ]] && exit 1 || echo "${updates}"
elif [[ "$1" == "list" ]]; then
    dpkg -l;
elif [[ "$1" == "autoremove" ]]; then
    sudo apt-get autoremove --purge;
elif [[ "$1" == "clone" ]]; then
    _clone "$2";
elif [[ "$1" == "remove" ]] || [[ "$1" == "purge" ]]; then
    for app in "${@:2}"; do _remove "$app"; done
elif [[ "$1" == "upgrade" ]]; then
    apps_i=0
    for app in "${@:2}"; do _upgrade "$app"; ((apps_i++)); done
    [[ "$apps_i" == 0 ]] && _upgrade || true
elif [[ "$1" == "search" ]] && [[ "$2" != "" ]]; then
    echo
    tput civis; results="$(_search "$2" & _spinner ${green}searching${normal})"; tput cnorm
    echo -e "$results" | less;
elif { [[ "$1" == "info" ]] || [[ "$1" == "show" ]]; } && [[ "$2" != "" ]]; then
    _info "$2";
elif [[ -z "$1" ]] || [[ "$1" == "help" ]]; then
    _help;
fi

# remove lockfile
rm -f "${LOCKFILE:?}"
