#!/usr/bin/env bash
# shellcheck disable=SC2155

# Copyright (c) 2018 Robin Broda <robin@broda.me>
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.

declare -i format=1 builddate
declare -a buildenv options installed
declare -A data

shopt -s extglob

# Desc: Parses line into current global state
# 1: line
function parse_line () {
	IFS=$' = ' read -r key val <<< "${1}"

	case "${key}" in
	"format")
		if [[ ${format} -ne ${val} ]]; then
			echo "incompatible format (want ${format}, got ${val})" >&2
			exit 1
		fi
		data["${key}"]="${val}"
		;;
	"buildenv") buildenv+=("${val}");;
	"options") options+=("${val}");;
	"installed") installed+=("${val}");;
	*)
		data["${key}"]="${val}"
		;;
	esac
}

# Desc: Parses buildinfo from string into current global state
# STDIN: .BUILDINFO
function parse () {
	while read -r line; do
		parse_line "${line}"
	done
}

# Desc: Parses buildinfo from file into current global state
# 1: .BUILDINFO path
function parse_buildinfo () {
	parse < "${1}"
}

# Desc: Parses buildinfo from provided package into current global state
# 1: Package path
function parse_package () {
	parse <<< "$(tar xOf "${1}" .BUILDINFO 2>/dev/null)"
}

readonly archive_url="${ARCH_ARCHIVE_CACHE:-https://archive.archlinux.org/packages}"

# Desc: get ALA link for given package
# 1: Package
# 2: Package compression
function get_archive_link () {
	local pkg="$(rev <<< "${1}")"
	local ext="${2}"
	local pkgname="$(cut -d'-' -f4- <<< "${pkg}" | rev)"
	echo "${archive_url}/${1:0:1}/${pkgname}/${1}.pkg.tar.${ext}"
}

# Desc: check validity of archive URL
# 1: Package archive link
# Return: check result
function verify_archive_link () {
	curl -IfLlso /dev/null "${1}"
	return $?
}

# Desc: Download package to $2 if not present and check signature using pacman's keyring
# 1: Package archive link
# 2: Target directory
function download_archive_package () {
	local cachename
	local cachedir=$(readlink -e "${2}")
	if [ -f "${2}/${1}.pkg.tar.xz" ] && [ -f "${2}/${1}.pkg.tar.xz.sig" ]; then
		echo "Hit cache for ${2}/${1}.pkg.tar.xz" >&2
		echo "${2}/${1}.pkg.tar.xz"
	elif [ -f "${2}/${1}.pkg.tar.zst" ] && [ -f "${2}/${1}.pkg.tar.zst.sig" ]; then
		echo "Hit cache for ${2}/${1}.pkg.tar.zst" >&2
		echo "${2}/${1}.pkg.tar.zst"
	else
		local pwd="$(pwd)"
		local workdir="$(mktemp -d)"
		cd "${workdir}" || exit 1
		for ext in zst xz; do
			local target="$(get_archive_link "${1}" "$ext")"
			if verify_archive_link "${target}"; then
				local filename="$(basename ${target})"
				echo "Downloading ${filename}" >&2
				if curl -sSfL --remote-name-all "${target}" "${target}.sig"; then
					mv "${filename}"{,.sig} "${cachedir}/"
					echo "${2}/${filename}"
				else
					echo "Couldn't download ${filename}" >&2
					echo "check ${workdir}" >&2
					exit 1
				fi
			fi
		done
		cd "${pwd}" || exit 1
		rm -r "${workdir}"
	fi
}

declare action
while [ "$#" -gt 1 ]; do
	case "${1}" in
	-i)
		action="info"
		shift 1
		;;
	-p)
		action="packages"
		shift 1
		;;
	-d)
		action="download"
		shift 1
		;;
	-f)
		action="field"
		shift 1
		;;
	-ff)
		action="field_all"
		shift 1
		;;
	-*)
		echo "unknown option: ${1}" >&2
		exit 1
		;;
	*)
		break
		;;
	esac
done

if [[ "${action}" == "" ]]; then
	echo "no option given" >&2
	exit 1
fi

hash tar zstd 2>/dev/null || { echo "Require tar and zstd in path! Aborting..."; exit 1; }

declare file="$(readlink -e "${@: -1}")"
set -- "${@:1:$(($#-1))}"
if [[ "${file}" =~ \.pkg ]]; then
	parse_package "${file}"
else
	parse_buildinfo "${file}"
fi

case "${action}" in
"info")
	echo -e "Name               : ${data[pkgname]}"
	echo -e "Base Name          : ${data[pkgbase]}"
	echo -e "Version            : ${data[pkgver]}"
	echo -e "Checksum (sha256)  : ${data[pkgbuild_sha256sum]}"
	echo -e "Packager           : ${data[packager]}"
	echo -e "Build Date         : ${data[builddate]}"
	echo -e "Build Directory    : ${data[builddir]}"
	echo -e "Build Environment  : ${buildenv[*]}"
	echo -e "Options            : ${options[*]}"
	echo -e "Installed Packages : ${#installed[@]}"
	;;
"packages")
	for ipkg in "${installed[@]}"; do
		select_archive_link "${ipkg}"
	done
	;;
"field")
	case "${1}" in
	"buildenv")
		echo -e "${buildenv[*]}"
		;;
	"options")
		echo -e "${options[*]}"
		;;
	"installed")
		echo -e "${installed[*]}"
		;;
	*)
	    echo -e "${data[${1}]}"
		;;
	esac
	;;
"field_all")
	for field in pkgname pkgbase pkgver pkgbuild_sha256sum packager builddate builddir format; do
		echo -e "${field}=${data[${field}]}"
	done
	echo -e "buildenv=${buildenv[*]}"
	echo -e "options=${options[*]}"
	echo -e "installed=${installed[*]}"
	;;
"download")
	if [ "$#" -gt 0 ]; then
		readonly target_dir="${1}"
		shift 1
		if [[ -d "${target_dir}" ]]; then
			for ipkg in "${installed[@]}"; do
				download_archive_package "${ipkg}" "${target_dir}"
			done
		else
			echo "Target directory doesn't exist" >&2
			exit 1
		fi
	else
		echo "Target directory not specified" >&2
		exit 1
	fi
	;;
esac
