#!/bin/bash

# This script is designed to integrate AppImage applications into the system.
# This script is free software distributed without any warranty. You can use
# modify and redistribute it under the terms of the CeCILL licence (GPL compatible).
# Licence: [CeCILL](https://cecill.info/licences/Licence_CeCILL_V2.1-en.txt)
# Author: Frank ENDRES <first@last.fr>
# Date: 2025-05-28
# Version: 0.1

set -euo pipefail #strict mode


#Constants
readonly PARAM_ERROR=1
readonly SCRAP_ERROR=2
readonly DOWNLOAD_ERROR=3
readonly MISSING_APPIMAGE_ERROR=4
readonly MOUNT_ERROR=5
readonly MISSING_DESKTOP_ERROR=6
readonly MISSING_APP_ERROR=7


function print_usage() {
	echo "usage: $0 selection" >&2
	echo "usage: $0 install selection_app_name" >&2
	echo "usage: $0 install https://github.com/organisation/project [filter]" >&2
	echo "usage: $0 install https://example.com/organisation/project [filter]" >&2
	echo "usage: $0 install https://example.com/file.AppImage" >&2
	echo "usage: $0 install path/to/file.AppImage" >&2
	echo "usage: $0 list" >&2
	echo "usage: $0 remove name" >&2
	exit $PARAM_ERROR
}

if (( $# < 1 )); then
	print_usage
fi


#App selection
declare -A zeroad=([url]='https://github.com/0ad-matters/0ad-appimage' [filter]='')
declare -A chromium=([url]='https://github.com/ivan-hc/Chromium-Web-Browser-appimage' [filter]='stable')
declare -A firefox=([url]='https://github.com/ivan-hc/Firefox-appimage' [filter]='')
declare -A gimp=([url]='https://www.gimp.org/downloads/' [filter]=$(uname -m)) #mandatory trailing '/' in url
declare -A inkscape=([url]='https://inkscape.org/release/all/gnulinux/appimage/' [filter]=-1) #mandatory trailing '/' in url
#~ declare -A libreoffice=([url]='https://www.libreoffice.org/download/appimage' [filter]='fresh.standard')
declare -A libreoffice=([url]='https://appimages.libreitalia.org/LibreOffice-fresh.standard-x86_64.AppImage' [filter]='')
declare -A onlyoffice=([url]='https://www.onlyoffice.com/fr/download-desktop.aspx' [filter]='')
declare -A tenacity=([url]='https://codeberg.org/tenacityteam/tenacity/releases' [filter]=1)

declare -a apps=(zeroad chromium firefox gimp inkscape libreoffice onlyoffice tenacity)


if [ "$(id -u)" == "0" ]; then #as root
	readonly APP_PATH=/usr/local/share/AppImages
	readonly BIN_PATH=/usr/local/bin
	readonly DESKTOP_PATH=/usr/local/share/applications
	readonly ICON_PATH=/usr/local/share/icons
else
	readonly APP_PATH=$HOME/.local/AppImages
	readonly BIN_PATH=$HOME/.local/bin
	readonly DESKTOP_PATH=$HOME/.local/share/applications
	readonly ICON_PATH=$HOME/.local/share/icons
fi
mkdir -p $APP_PATH $BIN_PATH $DESKTOP_PATH $ICON_PATH


function scrap_appimage_url() {
	local url=$1
	local filter="${2:-}"
	local links; local link=''
	if echo "$url" | grep -q '^https://github.com/'; then
		local api=$(echo "$url" | sed 's@github.com@api.github.com/repos@')"/releases/latest"
		links=$(curl -s $api | grep 'browser_download_url":.*AppImage"$' | cut -f4 -d'"' || true)
	else
		links=$(curl -s $url | sed -e "s/</\n</g" | grep ' href="[^"]*.AppImage"' | sed 's/.*href="\([^"]*\)".*/\1/' || true)
	fi
	if [ ! -z "$filter" ]; then
		if ! echo $filter | grep -q "[^0-9-]"; then #filter is the link index: positive (from start) or negative (from end)
			if echo $filter | grep -q "-"; then
				filter="${filter#-}"
				links=$(echo -e "$links" | tail -n $filter | head -n 1) #echo -e => ""
			else
				links=$(echo -e "$links" | sed -n $filter\p)
			fi
		else #filter is a string
			links=$(echo -e "$links" | grep "$filter")
		fi
	fi
	if (( $(echo -e "$links" | wc -l) == 1 )); then
		link=$links
	else
		local i=0; for link in $(echo -e "$links"); do
			let i=i+1; echo "$i: $link" >&2
		done
		echo -n "choice (1-$i): " >&2; read filter
		link=$(echo -e "$links" | sed -n $filter\p 2>/dev/null || true)
	fi
	if [[ "${link:0:2}" == "//" ]]; then #fix missing 'https:' (unneeded for github)
		link="https:$link"
	elif [[ "${link:0:8}" != "https://" ]]; then #fix relative URL (unneeded for github)
		link=$(echo $url | cut -f1-3 -d/)$link
	fi
	echo $link
}


function download_app() {
	local url=$1
	local filter="${2:-}"
	
	if [ "${url:0:8}" != "https://" ] && [ "${url: -9}" != ".AppImage" ]; then	#install from app name -> retrieve URL
		if ! echo " ${apps[*]} " | grep -q " $url " ; then
			#TODO: search with ddgr (?) in github / gitlab / codeberg / pling / ... (?)
			#~ results=$(ddgr --np -x -w github.com "+$url +Appimage" | grep -i "^ *https.*$url" | cut -c6-)
			#~ results=$(echo -e "$results" | cut -f1-5 -d/ | sort -u)
			echo "error: $url is not in the selection" >&2
			print_usage
			exit $PARAM_ERROR
		fi
		local -n app=$url
		url=${app[url]}
		if [ -z "$filter" ]; then filter="${app[filter]}"; fi
	fi
	
	if [ "${url: -9}" != ".AppImage" ]; then									#incomplete URL
		url=$(scrap_appimage_url $url "$filter")
		if [ -z "$url" ]; then
			echo "error: could not retrieve AppImage" >&2
			exit $SCRAP_ERROR
		fi
	fi
	
	if [ "${url:0:8}" == "https://" ] && [ ! -f $(basename $url) ]; then		#download AppImage
		wget $url || { echo "error: could not download $url" >&2; exit $DOWNLOAD_ERROR; }
	fi
	
	echo $url
}


function install_app() {
	local url=$1
	local app=$(basename $url)
	local pid=-1
	
	chmod +x $app																#mount AppImage
	local tmp_file=$(mktemp)
	./$app --appimage-mount > $tmp_file & pid=$! #background process -> bypass strict mode
	local mount_point=''
	while [ ! -z "$pid" ] && [ -d /proc/$pid ] && ([ -z "$mount_point" ] || [ ! -d $mount_point ]); do
		sleep 1; mount_point=$(cat $tmp_file)
	done
	rm -f $tmp_file
	if [ -z "$mount_point" ]; then
		echo "error: could not mount $app" >&2
		exit $MOUNT_ERROR
	fi
	
	desktop_file=$(ls $mount_point/*.desktop 2>/dev/null | head -n1 || true)	#determine application name
	if [ -z "$desktop_file" ]; then
		echo "error: missing desktop file" >&2
		kill $pid
		exit $MISSING_DESKTOP_ERROR
	fi
	for cmd in $(cat $desktop_file | grep "Exec=" | cut -f2 -d=); do
		if ! echo $cmd | grep -q "="; then break; fi #skip VAR=value
	done
	local name=$(basename $cmd)
	
	cp $desktop_file $DESKTOP_PATH/$name.desktop								#install desktop file and icon
	sed -e /^TryExec=/d -e "s@^\(Exec=.*\)$cmd\(.*\)@\1$BIN_PATH/$name\2@" -i $DESKTOP_PATH/$name.desktop
	local icon=$(ls $mount_point/*.svg $mount_point/*.png 2>/dev/null | head -n1 || true)
	if [ ! -z "$icon" ]; then
		local ext=$(echo $icon | rev | cut -f1 -d. | rev)
		cp $icon $ICON_PATH/$name.$ext
		sed "s/Icon=.*/Icon=$name/" -i $DESKTOP_PATH/$name.desktop
	fi
	
	kill $pid																	#umounts AppImage
	
	if [ -L $BIN_PATH/$name ]; then												#remove previous AppImage (update)
		local target=$(readlink -f $BIN_PATH/$name)
		rm -f $target
	fi
	
	mv $app $APP_PATH/															#install AppImage
	ln -sf $APP_PATH/$app $BIN_PATH/$name
	if [ "${url:0:8}" == "https://" ]; then echo $url > $APP_PATH/$app.url; fi
}


if [ "$1" == "selection" ]; then
	for name in ${apps[@]}; do
		declare -n app=$name
		echo "$name: ${app[url]}" >&2
	done

elif [ "$1" == "install" ] && (( $# >= 2 )); then
	url=$2
	filter="${3:-}"
	url=$(download_app $url $filter)
	app=$(basename $url)
	if [ ! -f $app ]; then
		echo "error: $app does not exist"
		exit $MISSING_APPIMAGE_ERROR
	fi
	install_app $url

elif [ "$1" == "list" ]; then
	for link in $BIN_PATH/*; do
		if [ -L $link ]; then
			target=$(readlink -f $link)
			if [ "$(dirname $target)" == "$APP_PATH" ]; then
				name=$(basename $link)
				if [ -f $target.url ]; then
					echo "$name ($(cat $target.url))"
				else
					echo $name
				fi
			fi
		fi
	done

elif [ "$1" == "remove" ] && (( $# == 2 )); then
	name=$2
	if [ -L $BIN_PATH/$name ]; then
		target=$(readlink -f $BIN_PATH/$name)
		if [ "$(dirname $target)" == "$APP_PATH" ]; then
			rm -f $BIN_PATH/$name $DESKTOP_PATH/$name.desktop $ICON_PATH/$name.*
			rm -f $target $target.url
		fi
	else
		echo "error: $name does not exist" >&2
		exit $MISSING_APP_ERROR
	fi

else
	print_usage
fi
