#!/usr/bin/bash

function pecho() {
	if [[ "$1" = "debug" ]] && [[ "${PORTABLE_LOGGING}" = "debug" ]]; then
		echo "[Debug]		$2"
	elif [[ "$1" = "info" ]] && [[ ! "${PORTABLE_LOGGING}" = "warn" ]]; then
		echo "[Info]		$2"
	elif [[ "$1" = "warn" ]]; then
		echo "[Warn]		$2"
	elif [[ "$1" = "crit" ]]; then
		echo "[Critical]	$2"
	fi
}

function printHelp() {
	echo "This is Portable packer, a tool to build sandboxed package"
	echo "Visit https://github.com/Kraftland/portable for documentation and information."
	echo "Supported arguments:"
	echo "	-v	-	-	-> Verbose output"
	echo "	--distro [distro name]	-> Specify the distribution."
	echo "	--mode [copy <[pkg]>]	-> Modes of operation"
	echo "	--hash [true / false]	-> Enables hashing of configuration file. Currently has no effect. (optional)"
	echo "	--config [path]	-	-> Specify the configuration source for sandbox"
	echo "	--desktop-file [path]	-> Specify the desktop file path for sandbox"
	echo "	--dbus-activation	-> Enables the activation from D-Bus (optional)"
	echo "	--dbus-arguments	-> Specify arguments to use when being activated"
	echo "Exit codes:"
	echo "	1	-	-	-> Syntax / argument error"
	echo "	110	-	-	-> Arch specific error"
	echo "	200	-	-	-> Configuration error"
	echo "	201	-	-	-> Non-existent package"
	exit 0
}

function busCheck() {
	local busOwn="${appID}"
	if [[ "${busOwn}" = org.mpris.MediaPlayer2$ ]]; then
		pecho crit "appID invalid: prohibited to own entire org.mpris.MediaPlayer2"
		exit 200
	elif [[ "${busOwn}" =~ org.freedesktop.impl.* ]]; then
		pecho crit "appID invalid: sandbox escape not allowed"
		exit 200
	elif [[ "${busOwn}" =~ org.gtk.vfs.* ]]; then
		pecho crit "appID invalid: full filesystem access not allowed"
		exit 200
	fi
}

function cmdlineDispatcher() {
	declare -i argumentCount=0
	while true; do
		if [[ $1 = "-v" ]]; then
			export PORTABLE_LOGGING=debug
			pecho debug "Enabled verbose logging"
			declare -g installVerbose="v"
		elif [[ $1 = "--distro" ]]; then
			pecho debug "Resolving distribution..."
			shift
			if [[ $1 =~ ^arch$|^archlinux$|^Arch ]]; then
				pecho info "Distribution set to \"Arch Linux\""
				declare -g _distribution="archlinux"
			else
				pecho crit "Unrecognized distribution: \"${1}\""
				exit 1
			fi
		elif [[ $1 = "--config" ]]; then
			pecho debug "Verifying configuration..."
			shift
			declare -r configPath="$(realpath --quiet $1)"
			if [[ $? -eq 0 ]]; then
				if [[ ! -r "${configPath}" || ! -f "${configPath}" ]]; then
					pecho crit "Failed reading configuration: expecting PATH to file, found \"$1\""
					exit 200
				fi
			else
				pecho crit "Failed reading configuration: "$(realpath $1)""
				exit 200
			fi
			declare -g configPath="${configPath}"
		elif [[ $1 = "--desktop-file" ]]; then
			pecho debug "Verifying desktop file..."
			shift
			declare -r desktopPath="$(realpath --quiet $1)"
			if [[ $? -eq 0 ]]; then
				if [[ ! -r "${desktopPath}" || ! -f "${desktopPath}" ]]; then
					pecho crit "Failed reading desktop file: expecting PATH to file, found \"$1\""
					exit 200
				fi
			else
				pecho crit "Failed reading desktop file: "$(realpath $1)""
				exit 200
			fi
			declare -g desktopPath="${desktopPath}"
		elif [[ $1 = "--mode" ]]; then
			shift
			if [[ $1 = "copy" ]]; then
				declare -g packerMode="$1"
				shift
				if [[ -n "${1}" ]]; then
					declare -g archPackage="$1"
				else
					pecho crit "Specify package name after --mode copy!"
					exit 1
				fi
				pecho debug "Mode set to: Copy from existing package: $1"

			elif [[ $1 = "post" ]]; then
				declare -g packerMode="$1"
				pecho debug "Mode set to: Post"
			fi
		elif [[ $1 = "--help" ]]; then
			pecho debug "Printing help on explicit request"
			printHelp
		elif [[ $1 = "--dbus-activation" ]]; then
			pecho debug "D-Bus activation enabled"
			declare -g dbusActivation=true
			if [[ $2 = "--dbus-arguments" ]]; then
				shift
				shift
				pecho debug "Specified bus activation argument: $1"
				declare -g dbusArg="$1"
			fi
		elif [[ -z $* ]]; then
			declare trailS="s"
			if [[ "${argumentCount}" -eq 1 ]]; then
				unset trailS
			fi
			pecho debug "Resolution of command line arguments finished with ${argumentCount} argument${trailS}."
			if [[ "${argumentCount}" -eq 0 ]]; then
				printHelp
			fi
			break
		else
			pecho warn "Unrecognized option: $1"
			argumentCount+=-1
		fi
		argumentCount+=1
		shift
	done
}

function configCheck() {
	pecho debug "Processing configuration..."
	declare -i configLength=$(wc -l < "${1}")
	configLength=$((${configLength} + 1))
	pecho debug "Calculated configuration length: ${configLength}"
	declare -i lineCount=1
	while true; do
		if [[ "${lineCount}" -gt "${configLength}" ]]; then
			pecho debug "Finished parsing of configuration file"
			break
		fi
		declare lineContent
		lineContent="$(sed -n "${lineCount}p" "${1}")"
		pecho debug "Processing configuration @${lineCount}"
		if [[ "${lineContent}" =~ ^# ]]; then
			pecho debug "	=> Skipping comments"
		elif [[ "${lineContent}" =~ .*=.* ]]; then
			pecho debug "	=> KEY=VAL discovered: ${lineContent}"
			if [[ "${lineContent}" =~ .*=.*=.* ]] && [[ ! "${lineContent}" =~ ^launchTarget|busLaunchTarget ]]; then
				pecho crit "	=> Rejecting malformed configuration file! Expected KEY=VAL pair. found multiple \"=\" operators"
				exit 200
			fi
		elif [[ -z "${lineContent}" ]]; then
			pecho debug "	=> Skipping empty line"
		else
			pecho crit "	=> Rejecting line for syntax error: Only comments starting with\"#\" and simple KEY=VAL assignment is allowed"
			exit 200
		fi
		lineCount+=1
		unset lineContent
	done
	pecho info "Done verifying configuration"
}

function main() {
	pecho debug "Starting main function"
	if [[ ! "${configPath}" ]]; then
		pecho crit "Configuration not found"
		exit 1
	fi
	if [[ ! "${packerMode}" ]]; then
		pecho crit "Mode of operation not defined"
		exit 1
	fi
	if [[ ! "${desktopPath}" ]]; then
		pecho crit "Desktop file not found"
		exit 1
	fi
	if [[ ! "${_distribution}" ]]; then
		pecho crit "Distribution not set"
		exit 1
	fi
	configCheck "${configPath}"
	source "${configPath}"
	busCheck

	if [[ "${_distribution}" = archlinux ]]; then
		if [[ "${packerMode}" = copy ]]; then
			pecho info "Entering copy mode: Arch Linux"
			copyArchFiles
		elif [[ "${packerMode}" = post ]]; then
			pecho info "Entering post mode: Arch Linux"
		fi
		archPost
	fi

	if [[ "${dbusActivation}" = true ]]; then
		genDBusService
		pecho info "Generating D-Bus service"
		if [[ "${_distribution}" = archlinux ]]; then
			install \
				"-${installVerbose}Dm644" \
				busService \
				"${pkgdir}/usr/share/dbus-1/services/${appID}.service"
		else
			pecho warn "	=> Distribution not yet supported"
		fi
	fi
}

function genDBusService() {

	echo '''[D-BUS Service]
Name=appID
Exec=/usr/bin/env _portableConfig=appID portable --dbus-activation -- ''' >busService
	sed -i "s|appID|${appID}|g" ./busService
	if [[ ${dbusArg} ]]; then
		sed -i "s#--dbus-activation --#--dbus-activation -- ${dbusArg}#g"
	fi

}

function copyArchFiles() {
	if [[ ! ${srcdir} || ! ${pkgdir} || ! ${pkgname} ]]; then
		pecho crit "srcdir and pkgdir not present! Did you forgot to export them before invoking packer?"
		exit 110
	fi
	if pacman -Qi "${archPackage}" 1>/dev/null 2>/dev/null; then
		pecho debug "Package present and verified"
	else
		pecho crit "Package does not exist"
		exit 201
	fi
	pacman -Ql "${archPackage}" >file.list

	while IFS= read -r line; do
		file="$(echo "$line" | awk '{print $2}')"
		if [[ -d ${file} ]]; then
			pecho debug "Omitting Directory"
		else
			if [[ -L "${file}" ]]; then
				mkdir -p "$(dirname "${pkgdir}/${file}")"
				ln -${installVerbose}sf "$(readlink -f "${file}")" "${pkgdir}/${file}"
			else
				install -${installVerbose}Dm755 "${file}" "${pkgdir}/${file}"
			fi
		fi
	done < file.list
}

function archPost() {
	preCleanArch
	install "-${installVerbose}Dm644" "${configPath}" \
		"${pkgdir}/usr/lib/portable/info/${appID}/config"
	echo '#!/usr/bin/bash' >exec.sh
	echo "export _portableConfig=${appID}" >>exec.sh
	echo 'exec portable -- $@' >>exec.sh
	install \
		"-${installVerbose}Dm755" \
		exec.sh \
		"${pkgdir}/usr/bin/${pkgname}"
	install \
		"-${installVerbose}Dm644" \
		"${desktopPath}" \
		"${pkgdir}/usr/share/applications/${appID}.desktop"
	rm exec.sh
}

function preCleanArch() {
	rm -rf "${pkgdir}/usr/share/applications"
	rm -rf "${pkgdir}/usr/bin"
	rm -rf "${pkgdir}/etc/xdg/autostart"
	rm -rf "${pkgdir}/usr/share/dbus-1"
	rm -rf "${pkgdir}/usr/share/menu"
}

cmdlineDispatcher $@

main