#! /usr/bin/env bash

set -eo pipefail

SITE_DOMAIN=

# The site domain must be known to do anything other than --list.
load_site_domain() {
	if ! SITE_DOMAIN="$(<~/.pqccomms-client/site.json jq -r .domain 2>/dev/null)"; then
		echo "No site configured.  Select a site with pqccomms-client to install PWAs." >&2
		exit 1
	fi
}

# Use json_array_iterate to emit each element of an array on a single line, for
# iteration.
json_array_iterate() {
	local array count idx
	array="$1"

	count="$(echo "$array" | jq length)"
	idx=0
	while [ "$idx" -lt "$count" ]; do
		echo "$array" | jq -c ".[$idx]"
		((++idx))
	done
}

# Enable the user services required to use the installed firefox-esr as the
# PWA runtime.
enable_runtime() {
	systemctl --user enable --now --quiet firefoxesr-pwa-runtime.service
	systemctl --user enable --now --quiet firefoxesr-pwa-runtime-restart.path
}

# Check if a PWA with this document_url is already installed.
pwa_installed() {
	local documentUrl="$1"

	local num_pwas

	# The only way to list apps from firefoxpwa is 'firefoxpwa profile list'
	# (it includes apps for each profile), and this output is difficult to
	# parse correctly.  Check firefoxpwa's JSON config directly.

	# This doesn't exist until we do something with firefoxpwa.  If it
	# doesn't exist yet, the app isn't installed.
	if ! [ -f ~/.local/share/firefoxpwa/config.json ]; then
		return 1
	fi

	num_pwas="$(<~/.local/share/firefoxpwa/config.json \
		jq --arg documentUrl "$documentUrl" \
		'.sites | map(select(.config.document_url == $documentUrl)) | length')"
	[ "$num_pwas" -gt 0 ]
}

find_pwa() {
	local documentUrl="$1"

	local pwasJson
	pwasJson="$(<~/.local/share/firefoxpwa/config.json \
		jq --arg documentUrl "$documentUrl" \
		'.sites | map(select(.config.document_url == $documentUrl))')"
	local numPwas
	numPwas="$(echo "$pwasJson" | jq length)"
	if [ "$numPwas" -eq 0 ]; then
		echo "Could not find an installed app for $documentUrl" >&2
		echo "Check if it is installed, or try --install to install it." >&2
		return 1
	fi
	if [ "$numPwas" -ne 1 ]; then
		echo "Found $numPwas apps for $documentUrl" >&2
		echo "Can't determine the app for this manifest.  Inspect the situation manually" >&2
		return 1
	fi

	echo "$pwasJson" | jq '.[0]'
}

pwa_profile_has_apps() {
	local profileId="$1"

	local awk_get_profile_apps="
		BEGIN {in_profile=0}
		/^ID:/ {in_profile=0}
		/^ID: $1\$/ { in_profile=1 }
		(/^Apps:\$/ || /^- /) && in_profile {print}"
	if [ -z "$(firefoxpwa profile list | awk "$awk_get_profile_apps")" ]; then
		return 1 # does not have any apps
	fi
	return 0 # has apps
}

# Install a PWA from a manifest file, icon, and document URL
install_prepared_pwa() {
	local manifestFile="$1"
	local iconFile="$2"
	local documentUrl="$3"
	local shortName="$4"

	# Create a profile specific to this app
	local profileId
	profileId="$(firefoxpwa profile create --name "$shortName" \
		--description "$shortName - PQC Communications app" | \
		gawk 'match($0, /Profile created: (.*)$/, m) {print m[1]}')"

	ICON_ARGS=()
	if [[ "$iconFile" =~ \.png$ ]]; then
		ICON_ARGS=(--icon-url "data:image/png;base64,$(base64 "$iconFile" | tr -d '\n')")
	elif [[ "$iconFile" =~ \.svg$ ]]; then
		ICON_ARGS=(--icon-url "data:image/svg+xml;base64,$(base64 "$iconFile" | tr -d '\n')")
	fi
	local appId
	appId="$(firefoxpwa site install "${ICON_ARGS[@]}" \
		"data:application/json;base64,$(base64 "$manifestFile" | tr -d '\n')" \
		--document-url "$documentUrl" --profile "$profileId" | \
		gawk 'match($0, /Web app installed: (.*)$/, m) {print m[1]}')"
	# Make the app adaptive
	echo 'X-Purism-FormFactor=Workstation;Mobile;' >>~/.local/share/applications/FFPWA-"$appId".desktop
}

# Substitute {{SITE_DOMAIN}} for the domain given in stdin.
# (The domain cannot contain characters interpreted by sed: slash, backslash, or
# ampersand.)
subst_domain() {
	sed -e "s/{{SITE_DOMAIN}}/$SITE_DOMAIN/g"
}

# Check if a glob results in a single file, fail if not.
#
# For example: cat "$(single_file log.*.txt)" # there should be exactly one log
single_file() {
	if [ "$#" -eq 1 ] && [ -f "$1" ]; then
		echo "$1"
	else
		return 1
	fi
}

# Prepare a PWA for installation.  Substitute the domain into the templates and
# fetch the manifest/icon (if needed)
#
# PWA information is obtained from the current directory (which the script
# sets to /usr/share/pqccomms-client/pwas).  The prepared PWA files are placed
# in the directory specified.
prepare_pwa() {
	local basename="$1"
	local resultsDir="$2"
	local caCert="$3"
	local clientCert="$4"
	local clientKey="$5"

	mkdir -p "$resultsDir"

	# Substitute the domain in the manifest URL
	subst_domain <"$basename.manifest.url" \
		>"$resultsDir/$basename.manifest.url"

	local manifestUrl
	manifestUrl="$(cat "$resultsDir/$basename.manifest.url")"

	# If there's a manifest file provided, substitute the domain in it
	if [ -f "$basename.manifest.json" ]; then
		subst_domain <"$basename.manifest.json" \
			>"$resultsDir/$basename.manifest.json"
	# Otherwise, download the manifest
	else
		curl -L --cert "$clientCert" --key "$clientKey" \
			--cacert "$caCert" \
			-o "$resultsDir/$basename.manifest.json" \
			"$manifestUrl"
	fi

	# If there's an icon provided, use it
	local iconFile
	if iconFile="$(single_file "$basename".icon.*)"; then
		cp "$iconFile" "$resultsDir/$(basename "$iconFile")"
	else
		# Download an icon.  Find the first image/png icon, this is
		# simple but reasonably effective.  This also assumes there is
		# such an icon, which is tolerable here since this script only
		# runs on specific web apps identified ahead of time.
		local iconUrl
		iconUrl="$(<"$resultsDir/$basename.manifest.json" jq -r \
			'.icons | map(select(.type == "image/png")) | .[0].src')"
		iconFile="$resultsDir/$basename.icon.png"
		# Crudely assume that the URL is host-relative
		if [[ "$manifestUrl" =~ ^([a-z]+://[^/]+)/.* ]]; then
			iconUrl="${BASH_REMATCH[1]}/$iconUrl"
			curl -L --cert "$clientCert" --key "$clientKey" \
				--cacert "$caCert" -o "$iconFile" "$iconUrl"
		else
			echo "ERROR: Could not parse base URL $manifestUrl to combine with iconUrl $iconUrl" >&2
			return 1
		fi
	fi
}

install_pwa() {
	local basename="$1"
	local caCert="$2"
	local clientCert="$3"
	local clientKey="$4"

	local pwaManifestDir=~/.pqccomms-client/pwa
	rm -rf "$pwaManifestDir"
	mkdir -p "$pwaManifestDir"

	prepare_pwa "$basename" ~/.pqccomms-client/pwa "$caCert" "$clientCert" \
		"$clientKey"

	local shortName
	shortName="$(<"$pwaManifestDir/$basename.manifest.json" jq -r .short_name)"
	install_prepared_pwa "$pwaManifestDir/$basename.manifest.json" \
		"$(single_file "$pwaManifestDir/$basename".icon.*)" \
		"$(cat "$pwaManifestDir/$basename.manifest.url")" \
		"$(get_app_shortname "$basename")"
}

# Set an installed PWA to launch on login
enable_pwa_on_login() {
	local documentUrl="$1"

	local pwaJson
	if ! pwaJson="$(find_pwa "$documentUrl")"; then
		# error output from find_pwa
		return 1
	fi

	local appId
	appId="$(echo "$pwaJson" | jq -r '.ulid')"

	if ! [ -L ~/.config/autostart/"FFPWA-$appId.desktop" ]; then
		mkdir -p ~/.config/autostart
		ln -s ../../.local/share/applications/"FFPWA-$appId.desktop" ~/.config/autostart/
	fi
}

disable_pwa_on_login() {
	local documentUrl="$1"

	local pwaJson
	if ! pwaJson="$(find_pwa "$documentUrl")"; then
		# error output from find_pwa
		return 1
	fi

	local appId
	appId="$(echo "$pwaJson" | jq -r '.ulid')"

	if [ -L ~/.config/autostart/"FFPWA-$appId.desktop" ]; then
		rm ~/.config/autostart/"FFPWA-$appId.desktop"
	fi
}

uninstall_pwa() {
	local documentUrl="$1"

	local pwaJson
	if ! pwaJson="$(find_pwa "$documentUrl")"; then
		# error output from find_pwa
		return 1
	fi

	local appId profileId
	appId="$(echo "$pwaJson" | jq -r '.ulid')"
	profileId="$(echo "$pwaJson" | jq -r '.profile')"

	# If the app was set to start automatically, remove this symlink
	if [ -L ~/.config/autostart/"FFPWA-$appId.desktop" ]; then
		rm ~/.config/autostart/"FFPWA-$appId.desktop"
	fi

	# Uninstall the app
	firefoxpwa site uninstall -q "$appId" >/dev/null

	# Make sure there are no other apps using this profile (there shouldn't
	# be, but avoid destroying data in case there are)
	if ! pwa_profile_has_apps "$profileId"; then
		firefoxpwa profile remove -q "$profileId" >/dev/null
	else
		echo "Other apps are using the profile from $documentUrl." >&2
		echo "Not removing profile $profileId" >&2
	fi
}

usage() {
cat <<USAGE_END
usage:
	$0 --list	# list available PWA apps
	$0 --install	# install PWA apps
	$0 --uninstall	# uninstall PWA apps
	$0 --on-login <name>	# launch installed PWA app on login
	$0 --no-login <name>	# don't launch installed PWA app on login

	Manage web apps for PQC Communications services.

parameters:
	--list: List available PWA apps.  Does not make any changes.
	--install: Install web apps.  Already-installed web apps aren't
		affected; no data is lost.
	--list-installed: List installed PWA apps.  If none are installed,
		output is empty.
	--uninstall: Uninstall web apps
		**This will delete local data for these apps.**
	--on-login <name>: Set an installed web app to launch on login.
		<name> is the basename of an available web app, use --list to
		find the available names.
	--no-login <name>: Don't launch the named web app on login, undoes a
		previous --on-login.
USAGE_END
}

# Print a result group.  The heading line is only printed if there's at least
# one result in the group.
print_result_group() {
	local heading="$1"
	shift

	if [ "$#" -lt 1 ]; then
		return 0
	fi

	echo "$heading"
	while [ "$#" -ge 1 ]; do
		echo "  $1"
		shift
	done
}

set_action() {
	if [ -n "$ACTION" ]; then
		# Already have an action
		usage
		exit 1
	fi
	ACTION="${1#--}" # delete -- prefix
}

# Get the document URL for a PWA by basename.  The document URL is the URL used
# to identify that PWA as an installed app.
get_app_document_url() {
	local manifestUrl
	if ! manifestUrl="$(cat "$1.manifest.url")"; then
		echo "Couldn't find this app: $1" >&2
		echo "Check the available apps with --list" >&2
		exit 1
	fi
	echo "$manifestUrl" | subst_domain
}

# Get a short display name for this application by basename
get_app_shortname() {
	cat "$1".name
}

action_list() {
	echo $'Name\tManifest URL'
	for f in *.manifest.url; do
		manifestUrl="$(cat "$f")"
		echo -n "$(basename "$f" .manifest.url)"
		echo -n $'\t'
		echo "$manifestUrl"
	done
}

action_list_installed() {
	INSTALLED_PWAS=()

	for f in *.manifest.url; do
		basename="${f%.manifest.url}"
		documentUrl="$(get_app_document_url "$basename")"
		shortName="$(get_app_shortname "$basename")"

		if pwa_installed "$documentUrl"; then
			INSTALLED_PWAS+=("$shortName")
		fi
	done

	print_result_group "Installed:" "${INSTALLED_PWAS[@]}"
}

action_install() {
	EXISTING_PWAS=() # Already existed in install action, nothing to do
	INSTALLED_PWAS=() # Installed

	enable_runtime

	# We need the CA certificate, client certificate, and client private
	# key to be able to download manifests/icons.
	local caCert=/usr/local/share/ca-certificates/pqccomms-client-ca.crt
	local cn clientCert clientKey
	if cn="$(cat ~/.pqccomms-client/cn)"; then
		clientCert=~/.pqccomms-client/pki/issued/"$cn".crt
		clientKey=~/.pqccomms-client/pki/private/"$cn".key
	fi

	if [ ! -f "$caCert" ] || [ ! -f "$clientCert" ] || [ ! -f "$clientKey" ]; then
		echo "Select a site and authorize a client certificate to install PWAs." >&2
		exit 1
	fi

	for f in *.manifest.url; do
		basename="${f%.manifest.url}"
		documentUrl="$(get_app_document_url "$basename")"
		shortName="$(get_app_shortname "$basename")"

		if pwa_installed "$documentUrl"; then
			EXISTING_PWAS+=("$shortName")
		else
			install_pwa "$basename" "$caCert" "$clientCert" "$clientKey"
			INSTALLED_PWAS+=("$shortName")
		fi
	done

	print_result_group "Installed:" "${INSTALLED_PWAS[@]}"
	print_result_group "Already installed, no action needed:" "${EXISTING_PWAS[@]}"
}

action_uninstall() {
	NONEXISTING_PWAS=() # Didn't exist during uninstall action, nothing to do
	UNINSTALLED_PWAS=() # Uninstalled

	for f in *.manifest.url; do
		basename="${f%.manifest.url}"
		documentUrl="$(get_app_document_url "$basename")"
		shortName="$(get_app_shortname "$basename")"

		if pwa_installed "$documentUrl"; then
			uninstall_pwa "$documentUrl"
			UNINSTALLED_PWAS+=("$shortName")
		else
			NONEXISTING_PWAS+=("$shortName")
		fi
	done

	print_result_group "Uninstalled:" "${UNINSTALLED_PWAS[@]}"
	print_result_group "Not installed, no action needed:" "${NONEXISTING_PWAS[@]}"
}

action_on_login()
{
	enable_pwa_on_login "$(get_app_document_url "$WEB_APP_NAME")"
	echo "Launch on login: $(get_app_shortname "$WEB_APP_NAME")"
}

action_no_login()
{
	disable_pwa_on_login "$(get_app_document_url "$WEB_APP_NAME")"
	echo "Don't launch on login: $(get_app_shortname "$WEB_APP_NAME")"
}

ACTION=
WEB_APP_NAME=

while [ "$#" -gt 0 ]; do
	case "$1" in
	--help)
		usage
		exit 0
		;;
	--list|--list-installed|--install|--uninstall)
		set_action "$1"
		shift
		;;
	--on-login|--no-login)
		set_action "$1"
		WEB_APP_NAME="$2"
		shift 2
		;;
	*)
		echo "Unknown parameter: $1" >&2
		usage
		exit 1
		;;
	esac
done

if [ -z "$ACTION" ]; then
	usage
	exit 1
fi

# 'list' does not require a site domain; everything else does.
if [ "$ACTION" != list ]; then
	load_site_domain
fi

cd "/usr/share/pqccomms-client/pwas"

action_"${ACTION/-/_}"
