#!/data/data/com.qflistudio.azure/files/usr/bin/bash
set -eu

# Root Check
if [[ "$(id -u)" == "0" ]]; then
	echo "Error: Cannot run 'pkg' command as root"
	exit 1
fi

# Setup TERMUX_APP_PACKAGE_MANAGER
source "/data/data/com.termux/files/usr/bin/termux-setup-package-manager" || exit 1

MIRROR_BASE_DIR="/data/data/com.termux/files/usr/etc/termux/mirrors"

show_help() {
	local cache_size
	local cache_dir=""
	if [ "$TERMUX_APP_PACKAGE_MANAGER" = "apt" ]; then
		cache_dir="/data/data/com.termux/cache/apt/archives"
	elif [ "$TERMUX_APP_PACKAGE_MANAGER" = "pacman" ]; then
		cache_dir="/data/data/com.termux/files/usr/var/cache/pacman/pkg"
	fi
	cache_size=$(du -sh "$cache_dir" 2>/dev/null | cut -f1)

	echo 'Usage: pkg [--check-mirror] command [arguments]'
	echo
	echo "A tool for managing $TERMUX_APP_PACKAGE_MANAGER packages."
	echo '  --check-mirror forces a re-check of availability of mirrors'
	echo
	echo 'Commands:'
	echo
	echo "  autoclean            - Remove all outdated packages from $TERMUX_APP_PACKAGE_MANAGER"
	echo '                         cache.'
	echo
	echo "  clean                - Remove all packages from $TERMUX_APP_PACKAGE_MANAGER cache."
	[ -n "$cache_size" ] &&
	echo "                         Using $cache_size now."
	echo
	echo '  files <packages>     - Show all files installed by packages.'
	echo
	echo '  install <packages>   - Install specified packages.'
	echo
	echo '  list-all             - List all packages available in repositories.'
	echo
	echo '  list-installed       - List installed packages.'
	echo
	echo '  reinstall <packages> - Reinstall specified installed packages at the'
	echo '                         latest version.'
	echo
	echo '  search <query>       - Search package by query, for example by name or'
	echo '                         description part.'
	echo
	echo '  show <packages>      - Show basic metadata, such as dependencies.'
	echo
	echo '  uninstall <packages> - Uninstall specified packages. Configuration files'
	echo '                         will be left intact.'
	echo
	echo '  upgrade              - Upgrade all installed packages to the latest'
	echo '                         version.'
	echo
	echo "  update               - Update $TERMUX_APP_PACKAGE_MANAGER databases from configured"
	echo '                         repositories.'
	echo
	exit 1
}

check_mirror() {
	local mirror="${1%/}"
	local timeout="${2-5}"

	timeout "$((timeout + 1))" curl \
		--head \
		--fail \
		--connect-timeout "$timeout" \
		--location \
		--user-agent "Termux-PKG/2.0 mirror-checker (termux-tools 1.45.0) Termux (com.termux; install-prefix:/data/data/com.termux/files/usr)" \
		"$mirror/dists/stable/Release" >/dev/null 2>&1
}

check_command() {
	local command="$1"

	local errors
	if ! errors="$("$@" 2>&1 1>/dev/null)"; then
		echo "$errors" 1>&2
		echo "Failed to run the '$command' command." 1>&2
		if [[ "$errors" == *"CANNOT LINK EXECUTABLE"* ]]; then
			echo -n "To fix the '$command' command, manually upgrade all packages by running: " 1>&2
			case "$TERMUX_APP_PACKAGE_MANAGER" in
				apt) echo "\`apt update && apt full-upgrade\`" 1>&2;;
				pacman) echo "\`pacman -Syu\`" 1>&2;;
			esac
		fi
		exit 1
	fi
}

hostname() {
	echo "$1" | awk -F'[/:]' '{print $4}'
}

last_modified() {
	local mtime
	local now

	mtime=$(date -r "$1" '+%s')
	now=$(date '+%s')
	echo $((now - mtime))
}

has_repo() {
	# Check if root-repo or x11-repo are installed
	repo="$1"

	if [ -f "/data/data/com.termux/files/usr/etc/apt/sources.list.d/$repo.list" ]; then
		echo true
	else
		echo false
	fi
}

unset_mirror_variables() {
	unset MAIN
	unset X11
	unset ROOT
	unset WEIGHT
}

get_mirror_url() {
	local -r _mirror="$1"
	local -r _has_repo_x11="$2"
	local -r _has_repo_root="$3"

	unset_mirror_variables
	source "$_mirror"

	if [[ -z "${MAIN:-}" ]]; then
		echo "Warn: Ignoring mirror '$_mirror' without main channel url" >&2
		return 0
	elif [[ ! "${MAIN:-}" =~ ^https?://[^[:space:]]+$ ]]; then
		echo "Warn: Ignoring mirror '$_mirror' with invalid main channel url '${MAIN:-}'" >&2
		return 0
	fi

	if [[ "$_has_repo_x11" == "true" ]]; then
		if [[ -z "${X11:-}" ]]; then
			echo "Warn: Ignoring mirror '$_mirror' without x11 channel url" >&2
			return 0
		elif [[ ! "${X11:-}" =~ ^https?://[^[:space:]]+$ ]]; then
			echo "Warn: Ignoring mirror '$_mirror' with invalid x11 channel url '${X11:-}'" >&2
			return 0
		fi
	fi

	if [[ "$_has_repo_root" == "true" ]]; then
		if [[ -z "${ROOT:-}" ]]; then
			echo "Warn: Ignoring mirror '$_mirror' without root channel url" >&2
			return 0
		elif [[ ! "${ROOT:-}" =~ ^https?://[^[:space:]]+$ ]]; then
			echo "Warn: Ignoring mirror '$_mirror' with invalid root channel url '${ROOT:-}'" >&2
			return 0
		fi
	fi

	if [[ ! "${WEIGHT:-}" =~ ^[0-9]+$ ]]; then
		echo "Warn: Ignoring mirror '$_mirror' with invalid weight '${WEIGHT:-}'" >&2
		return 0
	fi

	echo "$MAIN"
}

get_mirror_weight() {
	unset_mirror_variables
	source "$1"
	echo "$WEIGHT"
}

select_mirror() {
	local current_mirror
	current_mirror=$(grep -oE 'https?://[^ ]+' <(grep -m 1 -E '^[[:space:]]*deb[[:space:]]+' /data/data/com.termux/files/usr/etc/apt/sources.list) || true)

	# Do not update mirror if $TERMUX_PKG_NO_MIRROR_SELECT was set.
	if [ -n "${TERMUX_PKG_NO_MIRROR_SELECT-}" ] && [ -n "$current_mirror" ]; then
		return
	fi

	local default_repo="${MIRROR_BASE_DIR}/default"

	if [ -d "/data/data/com.termux/files/usr/etc/termux/chosen_mirrors" ]; then
		# Mirror group selected
		mirrors=($(find /data/data/com.termux/files/usr/etc/termux/chosen_mirrors/ -type f ! -name "*\.dpkg-old" ! -name "*\.dpkg-new" ! -name "*~"))
	elif [ -f "/data/data/com.termux/files/usr/etc/termux/chosen_mirrors" ]; then
		# Single mirror selected
		mirrors=("$(realpath /data/data/com.termux/files/usr/etc/termux/chosen_mirrors)")
	elif [ -L "/data/data/com.termux/files/usr/etc/termux/chosen_mirrors" ]; then
		# Broken symlink, use all mirrors
		mirrors=("${MIRROR_BASE_DIR}/default")
		mirrors+=($(find ${MIRROR_BASE_DIR}/{asia,chinese_mainland,europe,north_america,oceania,russia}/ -type f ! -name "*\.dpkg-old" ! -name "*\.dpkg-new" ! -name "*~"))
	else
		echo "No mirror or mirror group selected. You might want to select one by running 'termux-change-repo'"
		mirrors=("${MIRROR_BASE_DIR}/default")
		mirrors+=($(find ${MIRROR_BASE_DIR}/{asia,chinese_mainland,europe,north_america,oceania,russia}/ -type f ! -name "*\.dpkg-old" ! -name "*\.dpkg-new" ! -name "*~"))
	fi

	# Ensure `curl` can execute, otherwise all mirror checks will fail with `bad`.
	check_command curl --version

	# Mirrors are rotated if 6 hours timeout has been passed or mirror is no longer accessible.
	local pkgcache="/data/data/com.termux/cache/apt/pkgcache.bin"
	if [ -e "$pkgcache" ] && (( $(last_modified "$pkgcache") <= 6 * 3600 )) && [ "$force_check_mirror" = "false" ]; then
		if [ -n "$current_mirror" ]; then
			echo "Checking availability of current mirror:"
			echo -n "[*] $current_mirror: "
			if check_mirror "$current_mirror"; then
				echo "ok"
				return
			else
				echo "bad"
			fi
		fi
	fi

	# Test mirror availability, remove unaccessible mirrors from list.
	echo "Testing the available mirrors:"
	local parallel_jobs_max_count=10

	if [[ ! "$parallel_jobs_max_count" =~ ^[0-9]+$ ]] || \
		[[ "$parallel_jobs_max_count" -lt 1 ]] || \
			[[ "$parallel_jobs_max_count" -gt 1000 ]]; then
		parallel_jobs_max_count=1
	fi

	declare -a parallel_jobs_mirrors=()
	declare -a parallel_jobs_weights=()
	declare -a parallel_jobs_urls=()
	declare -a parallel_jobs_numbers=()
	declare -a parallel_jobs_pids=()
	declare -a parallel_jobs_return_values=()

	local i j mirror url job_number job_pid return_value
	local total_mirrors=${#mirrors[@]}
	local parallel_jobs_current_count=1

	has_repo_x11="$(has_repo x11)"
	has_repo_root="$(has_repo root)"

	set +e
	i=0
	for mirror in "${!mirrors[@]}"; do
		url="$(get_mirror_url "${mirrors[$mirror]}" "$has_repo_x11" "$has_repo_root")"
		if [ -z "$url" ]; then
			unset "mirrors[$mirror]"
			continue
		fi

		job_number=$parallel_jobs_current_count
		parallel_jobs_current_count=$((parallel_jobs_current_count + 1))

		# Start mirror check in background
		check_mirror "$url" &
		job_pid=$!

		parallel_jobs_mirrors=("${parallel_jobs_mirrors[@]}" "$mirror")
		parallel_jobs_weights=("${parallel_jobs_weights[@]}" "$(get_mirror_weight ${mirrors[$mirror]})")
		parallel_jobs_urls=("${parallel_jobs_urls[@]}" "$url")
		parallel_jobs_numbers=("${parallel_jobs_numbers[@]}" "$job_number")
		parallel_jobs_pids=("${parallel_jobs_pids[@]}" "$job_pid")

		# If current job count has reached max value or is the last mirror, wait for already started jobs to finish
		if [ "$job_number" -ge $parallel_jobs_max_count ] || \
			[ "$i" -ge $((total_mirrors - 1)) ]; then

			j=0
			# For pids of all jobs
			for job_pid in "${parallel_jobs_pids[@]}"; do
				# Wait for job with matching pid to return
				# echo "waiting for check_mirror job ${parallel_jobs_numbers[j]} for mirror \"${parallel_jobs_urls[j]}\" with pid ${parallel_jobs_pids[j]}"
				wait "$job_pid"
				return_value=$?

				parallel_jobs_return_values=("${parallel_jobs_return_values[@]}" "$return_value")
				j=$((j + 1))
			done

			j=0
			# For return_values of all jobs
			for return_value in "${parallel_jobs_return_values[@]}"; do
				echo -n "[*] (${parallel_jobs_weights[j]}) ${parallel_jobs_urls[j]}: "
				if [ "$return_value" -eq 0 ]; then
					echo "ok"
				else
					echo "bad"
					# echo "check_mirror job ${parallel_jobs_numbers[j]} for mirror \"${parallel_jobs_urls[j]}\" with pid ${parallel_jobs_pids[j]} failed with exit code $return_value"
					unset "mirrors[${parallel_jobs_mirrors[j]}]"
				fi

				j=$((j + 1))
			done

			# Reset job related variables
			parallel_jobs_current_count=1
			parallel_jobs_mirrors=()
			parallel_jobs_weights=()
			parallel_jobs_urls=()
			parallel_jobs_numbers=()
			parallel_jobs_pids=()
			parallel_jobs_return_values=()
		fi

		i=$((i + 1))
	done
	set -e

	# Build weighted array of valid mirrors
	declare -a weighted_mirrors
	local total_mirror_weight=0
	local weight
	for mirror in "${!mirrors[@]}"; do
		# Check if mirror was unset in parallel check
		if [ -z "${mirrors[$mirror]-}" ]; then
			continue
		fi
		weight="$(get_mirror_weight ${mirrors[$mirror]})"
		total_mirror_weight=$((total_mirror_weight + weight))
		j=0
		while [ "$j" -lt "$weight" ]; do
			weighted_mirrors+=(${mirrors[$mirror]})
			j=$((j + 1))
		done
	done

	# Select random mirror
	local selected_mirror=""
	if ((total_mirror_weight > 0)); then
		local random_weight
		random_weight=$(( (RANDOM % total_mirror_weight + 1) - 1 ))
		echo "Picking mirror: (${random_weight}) ${weighted_mirrors[${random_weight}]}"
		selected_mirror="${weighted_mirrors[${random_weight}]}"
	fi

	if [ -n "$selected_mirror" ]; then
		(
			unset_mirror_variables
			source "$selected_mirror"
			echo "deb $MAIN stable main" > /data/data/com.termux/files/usr/etc/apt/sources.list
			if [[ "$has_repo_x11" == "true" ]]; then
				echo "deb $X11 x11 main" > /data/data/com.termux/files/usr/etc/apt/sources.list.d/x11.list
			fi
			if [[ "$has_repo_root" == "true" ]]; then
				echo "deb $ROOT root stable" > /data/data/com.termux/files/usr/etc/apt/sources.list.d/root.list
			fi
		)
	else
		# Should not happen unless there is some issue with
		# the script, or the mirror files
		echo "Error: None of the mirrors are accessible"
		exit 1
	fi
}

update_apt_cache() {
	local current_host
	current_host=$(head -n 1 <(sed -nE -e 's|^\s*deb\s+https?://(.+)\s+stable\s+main$|\1|p' /data/data/com.termux/files/usr/etc/apt/sources.list) || true)

	if [ -z "$current_host" ]; then
		# No primary repositories configured?
		apt update
		return
	fi

	local metadata_file
	metadata_file=$(
		list_prefix=$(echo "$current_host" | sed 's|/|_|g')
		arch=$(dpkg --print-architecture)
		echo "/data/data/com.termux/files/usr/var/lib/apt/lists/${list_prefix}_dists_stable_main_binary-${arch}_Packages" | sed 's|__|_|g'
	)

	if [ ! -e "/data/data/com.termux/cache/apt/pkgcache.bin" ] || [ ! -e "$metadata_file" ]; then
		apt update
		return
	fi

	local cache_modified
	cache_modified=$(last_modified "/data/data/com.termux/cache/apt/pkgcache.bin")

	local sources_modified
	sources_modified=$(last_modified "/data/data/com.termux/files/usr/etc/apt/sources.list")

	if (( sources_modified <= cache_modified )) || (( cache_modified > 1200 )); then
		apt update
	fi
}

force_check_mirror=false
if [ "${1-}" = "--check-mirror" ]; then
    force_check_mirror=true
    shift 1
fi

if [[ $# = 0 || $(echo "$1" | grep "^h") ]]; then
	show_help
fi

CMD="$1"
shift 1
ERROR=false

case "$TERMUX_APP_PACKAGE_MANAGER" in
	apt)
		case "$CMD" in
			f*) dpkg -L "$@";;
			sh*|inf*) apt show "$@";;
			add|i*) select_mirror; update_apt_cache; apt install "$@";;
			autoc*) apt autoclean;;
			cl*) apt clean;;
			list-a*) apt list "$@";;
			list-i*) apt list --installed "$@";;
			rei*) apt install --reinstall "$@";;
			se*) select_mirror; update_apt_cache; apt search "$@";;
			un*|rem*|rm|del*) apt remove "$@";;
                        upd*) select_mirror; apt update;;
			up|upg*) select_mirror; apt update; apt full-upgrade "$@";;
			*) ERROR=true;;
		esac;;
	pacman)
		case "$CMD" in
			f*) pacman -Ql "$@";;
			sh*|inf*) pacman -Qi "$@";;
			add|i*) pacman -Sy --needed "$@";;
			autoc*) pacman -Sc;;
			cl*) pacman -Scc;;
			list-a*) pacman -Sl "$@";;
			list-i*) pacman -Q "$@";;
			rei*) pacman -S "$@";;
			se*) pacman -Sys "$@";;
			un*|rem*|rm|del*) pacman -Rcns "$@";;
                        upd*) pacman -Sy "$@";;
			up|upg*) pacman -Syu "$@";;
			*) ERROR=true;;
		esac;;
esac

if $ERROR; then
	echo "Unknown command: '$CMD' (run 'pkg help' for usage information)"; exit 1
fi
