#!/hint/bash
#
# This may be included with or without `set -euE`
#
# SPDX-License-Identifier: GPL-3.0-or-later

[[ -z ${DEVTOOLS_INCLUDE_COMMON_SH:-} ]] || return 0
DEVTOOLS_INCLUDE_COMMON_SH="$(set +o|grep nounset)"

set +u +o posix
# shellcheck disable=1091
. /usr/share/makepkg/util.sh
$DEVTOOLS_INCLUDE_COMMON_SH

# Avoid any encoding problems
export LANG=C.UTF-8

# Avoid systemd trying to color the terminal on systemd-nspawn
export SYSTEMD_TINT_BACKGROUND=no

# Avoid diffoscope looking at remote debug info through readelf
unset DEBUGINFOD_URLS

# Set buildtool properties
export BUILDTOOL=devtools
export BUILDTOOLVER=1:1.5.1-1-any

# Set common properties
export PACMAN_KEYRING_DIR=/etc/pacman.d/gnupg
export GITLAB_HOST=gitlab.archlinux.org
export GIT_REPO_SPEC_VERSION=2
export GIT_PACKAGING_NAMESPACE=archlinux/packaging/packages
export GIT_PACKAGING_NAMESPACE_ID=11323
export GIT_PACKAGING_URL_SSH="git@${GITLAB_HOST}:${GIT_PACKAGING_NAMESPACE}"
export GIT_PACKAGING_URL_HTTPS="https://${GITLAB_HOST}/${GIT_PACKAGING_NAMESPACE}"
export PACKAGING_REPO_RELEASE_HOST=repos.archlinux.org
export PKGBASE_MAINTAINER_URL=https://archlinux.org/packages/pkgbase-maintainer
export AUR_URL_SSH=aur@aur.archlinux.org

# Create or reuse a shared SSH control socket with ControlMaster=auto. The
# connection is initialized on the first use and persisted for some time, so
# multiple invokations of devtools can share it.
# shellcheck disable=SC2016
export SSH_OPTS=(
  -o ControlMaster=auto
  -o ControlPersist=60s
  -o ControlPath='${XDG_RUNTIME_DIR}/devtools-%r@%h:%p'
)

export RSYNC_OPTS=(
  --rsh="ssh ${SSH_OPTS[*]}"
  --checksum
  --copy-links
  --human-readable
  --progress
  --partial
  # suffix the partial dir with the PID in order to avoid clashes
  --partial-dir=.partial.$$
  --delay-updates
)

# ensure TERM is set with a fallback to dumb
export TERM=${TERM:-dumb}

# check if messages are to be printed using color
if [[ -t 2 && "$TERM" != dumb ]] || [[ ${DEVTOOLS_COLOR} == always ]]; then
	colorize
	if tput setaf 0 &>/dev/null; then
		PURPLE="$(tput setaf 5)"
		DARK_GREEN="$(tput setaf 2)"
		DARK_RED="$(tput setaf 1)"
		DARK_BLUE="$(tput setaf 4)"
		DARK_YELLOW="$(tput setaf 3)"
		UNDERLINE="$(tput smul)"
		GRAY=$(tput setaf 242)
	else
		PURPLE="\e[35m"
		DARK_GREEN="\e[32m"
		DARK_RED="\e[31m"
		DARK_BLUE="\e[34m"
		DARK_YELLOW="\e[33m"
		UNDERLINE="\e[4m"
		GRAY=""
	fi
else
	# shellcheck disable=2034
	declare -gr ALL_OFF='' BOLD='' BLUE='' GREEN='' RED='' YELLOW='' PURPLE='' DARK_RED='' DARK_GREEN='' DARK_BLUE='' DARK_YELLOW='' UNDERLINE='' GRAY=''
fi

stat_busy() {
	local mesg=$1; shift
	# shellcheck disable=2059
	printf "${GREEN}==>${ALL_OFF}${BOLD} ${mesg}...${ALL_OFF}" "$@" >&2
}

stat_progress() {
	# shellcheck disable=2059
	printf "${BOLD}.${ALL_OFF}" >&2
}

stat_done() {
	# shellcheck disable=2059
	printf "${BOLD}done${ALL_OFF}\n" >&2
}

stat_failed() {
	# shellcheck disable=2059
	printf "${BOLD}${RED}failed${ALL_OFF}\n" >&2
}

msg_success() {
	local msg=$1
	local padding
	padding=$(echo "${msg}"|sed -E 's/( *).*/\1/')
	msg=$(echo "${msg}"|sed -E 's/ *(.*)/\1/')
	printf "%s %s\n" "${padding}${GREEN}✓${ALL_OFF}" "${msg}" >&2
}

msg_error() {
	local msg=$1
	local padding
	padding=$(echo "${msg}"|sed -E 's/( *).*/\1/')
	msg=$(echo "${msg}"|sed -E 's/ *(.*)/\1/')
	printf "%s %s\n" "${padding}${RED}x${ALL_OFF}" "${msg}" >&2
}

msg_warn() {
	local msg=$1
	local padding
	padding=$(echo "${msg}"|sed -E 's/( *).*/\1/')
	msg=$(echo "${msg}"|sed -E 's/ *(.*)/\1/')
	printf "%s %s\n" "${padding}${YELLOW}!${ALL_OFF}" "${msg}" >&2
}

print_workdir_error() {
	if [[ ! -f "${WORKDIR}"/error ]]; then
		return
	fi
	while read -r LINE; do
		error '%s' "${LINE}"
	done < "${WORKDIR}/error"
}

_setup_workdir=false
# Ensure that there is no outside value for WORKDIR leaking in
unset WORKDIR
setup_workdir() {
	[[ -z ${WORKDIR:-} ]] && WORKDIR=$(mktemp -d --tmpdir "${0##*/}.XXXXXXXXXX")
	_setup_workdir=true
	trap 'trap_abort' INT QUIT TERM HUP
	trap 'trap_exit' EXIT
}

cleanup() {
	if [[ -n ${WORKDIR:-} ]] && $_setup_workdir; then
		rm -rf "$WORKDIR"
	fi
	if tput setaf 0 &>/dev/null; then
		tput cnorm >&2
	fi
	exit "${1:-0}"
}

abort() {
	error 'Aborting...'
	cleanup 255
}

trap_abort() {
	trap - EXIT INT QUIT TERM HUP
	abort
}

trap_exit() {
	local r=$?
	trap - EXIT INT QUIT TERM HUP
	cleanup $r
}

die() {
	(( $# )) && error "$@"
	cleanup 255
}

##
#  usage : lock( $fd, $file, $message, [ $message_arguments... ] )
##
lock() {
	# Only reopen the FD if it wasn't handed to us
	if ! [[ "/dev/fd/$1" -ef "$2" ]]; then
		mkdir -p -- "$(dirname -- "$2")"
		eval "exec $1>>"'"$2"'
	fi

	if ! flock -n "$1"; then
		stat_busy "${@:3}"
		flock "$1"
		stat_done
	fi
}

##
#  usage : slock( $fd, $file, $message, [ $message_arguments... ] )
##
slock() {
	# Only reopen the FD if it wasn't handed to us
	if ! [[ "/dev/fd/$1" -ef "$2" ]]; then
		mkdir -p -- "$(dirname -- "$2")"
		eval "exec $1>"'"$2"'
	fi

	if ! flock -sn "$1"; then
		stat_busy "${@:3}"
		flock -s "$1"
		stat_done
	fi
}

##
#  usage : lock_close( $fd )
##
lock_close() {
	local fd=$1
	# https://github.com/koalaman/shellcheck/issues/862
	# shellcheck disable=2034
	exec {fd}>&-
}

##
# usage: pkgver_equal( $pkgver1, $pkgver2 )
##
pkgver_equal() {
	if [[ $1 = *-* && $2 = *-* ]]; then
		# if both versions have a pkgrel, then they must be an exact match
		[[ $1 = "$2" ]]
	else
		# otherwise, trim any pkgrel and compare the bare version.
		[[ ${1%%-*} = "${2%%-*}" ]]
	fi
}

##
#  usage: find_cached_package( $pkgname, $pkgver, $arch )
#
#    $pkgver can be supplied with or without a pkgrel appended.
#    If not supplied, any pkgrel will be matched.
##
shopt -s extglob
find_cached_package() {
	local searchdirs=("$PWD" "$PKGDEST") results=()
	local targetname=$1 targetver=$2 targetarch=$3
	local dir pkg packages pkgbasename name ver rel arch r results

	for dir in "${searchdirs[@]}"; do
		[[ -d $dir ]] || continue

		shopt -s extglob nullglob
		mapfile -t packages < <(printf "%s\n" "$dir"/"${targetname}"-"${targetver}"-*"${targetarch}".pkg.tar?(.!(sig|*.*)))
		shopt -u extglob nullglob

		for pkg in "${packages[@]}"; do
			[[ -f $pkg ]] || continue

			# avoid adding duplicates of the same inode
			for r in "${results[@]}"; do
				[[ $r -ef $pkg ]] && continue 2
			done

			# split apart package filename into parts
			pkgbasename=${pkg##*/}
			pkgbasename=${pkgbasename%.pkg.tar*}

			arch=${pkgbasename##*-}
			pkgbasename=${pkgbasename%-"$arch"}

			rel=${pkgbasename##*-}
			pkgbasename=${pkgbasename%-"$rel"}

			ver=${pkgbasename##*-}
			name=${pkgbasename%-"$ver"}

			if [[ $targetname = "$name" && $targetarch = "$arch" ]] &&
					pkgver_equal "$targetver" "$ver-$rel"; then
				results+=("$pkg")
			fi
		done
	done

	case ${#results[*]} in
		0)
			return 1
			;;
		1)
			printf '%s\n' "${results[0]}"
			return 0
			;;
		*)
			error 'Multiple packages found:'
			printf '\t%s\n' "${results[@]}" >&2
			return 1
	esac
}
shopt -u extglob

check_package_validity(){
	local pkgfile=$1
	if grep -q "packager = Unknown Packager" <(bsdtar -xOqf "$pkgfile" .PKGINFO); then
		die "PACKAGER was not set when building package"
	fi
	hashsum=sha256sum
	pkgbuild_hash=$(awk -v"hashsum=$hashsum" -F' = ' '$1 == "pkgbuild_"hashsum {print $2}' <(bsdtar -xOqf "$pkgfile" .BUILDINFO))
	if [[ "$pkgbuild_hash" != "$($hashsum PKGBUILD|cut -d' ' -f1)" ]]; then
		die "PKGBUILD $hashsum mismatch: expected $pkgbuild_hash"
	fi
}


# usage: grep_pkginfo pkgfile pattern
grep_pkginfo() {
	local _ret=()
	mapfile -t _ret < <(bsdtar -xOqf "$1" ".PKGINFO" | grep "^${2} = ")
	printf '%s\n' "${_ret[@]#${2} = }"
}


# Get the package name
getpkgname() {
	local _name

	_name="$(grep_pkginfo "$1" "pkgname")"
	if [[ -z $_name ]]; then
		error "Package '%s' has no pkgname in the PKGINFO. Fail!" "$1"
		exit 1
	fi

	echo "$_name"
}


# Get the package base or name as fallback
getpkgbase() {
	local _base

	_base="$(grep_pkginfo "$1" "pkgbase")"
	if [[ -z $_base ]]; then
		getpkgname "$1"
	else
		echo "$_base"
	fi
}


getpkgdesc() {
	local _desc

	_desc="$(grep_pkginfo "$1" "pkgdesc")"
	if [[ -z $_desc ]]; then
		error "Package '%s' has no pkgdesc in the PKGINFO. Fail!" "$1"
		exit 1
	fi

	echo "$_desc"
}


get_tag_from_pkgver() {
	local pkgver=$1
	local tag=${pkgver}

	tag=${tag/:/-}
	tag=${tag//~/.}
	echo "${tag}"
}


is_debug_package() {
	local pkgfile=${1} pkgbase pkgname pkgdesc
	pkgbase="$(getpkgbase "${pkgfile}")"
	pkgname="$(getpkgname "${pkgfile}")"
	pkgdesc="$(getpkgdesc "${pkgfile}")"
	[[ ${pkgdesc} == "Detached debugging symbols for "* && ${pkgbase}-debug = "${pkgname}" ]]
}

# Proxy function to check if a file exists. Using [[ -f ... ]] directly is not
# always wanted because we might want to expand bash globs first. This way we
# can pass unquoted globs to is_globfile() and have them expanded as function
# arguments before being checked.
is_globfile() {
	[[ -f $1 ]]
}

join_by() {
	local IFS="	"
	local sep=$1
	local split
	shift
	split=$(printf "%s" "$*")
	echo "${split//${IFS}/"${sep}"}"
}

trim_string() {
	local max_length=$1
	local string=$2

	if (( ${#string} > max_length )); then
		# Subtract 3 from max_length to accommodate "..."
		max_length=$((max_length - 3))
		string="${string:0:max_length}..."
	fi

	printf "%s" "${string}"
}

relative_date_unit() {
	local target_date=$1
	local now diff value units unit names

	target_date=$(date -d "$1" +%s)
	now=$(date +%s)
	diff=$((now - target_date))

	local names=(year month week day hour minute second)
	declare -A units=(
		[year]=$((60 * 60 * 24 * 365))
		[month]=$((60 * 60 * 24 * 30))
		[week]=$((60 * 60 * 24 * 7))
		[day]=$((60 * 60 * 24))
		[hour]=$((60 * 60))
		[minute]=60
		[second]=1
	)

	for unit in "${names[@]}"; do
		local value=$((diff / ${units[${unit}]}))
		if (( value > 1 )); then
			printf "%s %ss" "${value}" "${unit}"
			return
		elif (( value == 1 )); then
			printf "%s %s" "${value}" "${unit}"
			return
		fi
	done
	printf "1 second"
}


# escapes regex metacharacters in a given string
regex_escape() {
	# shellcheck disable=SC2001,SC2016
	sed 's/[\^.\[$()|*+?{\\]/\\&/g' <<<"$1"
}
