# Write launcher scripts and menu entries.
# This function can take several applications as extra arguments,
# and default to handle all applications if none is explicitely given.
# USAGE: launchers_generation [$package [$application…]]
launchers_generation() {
	local package
	if [ $# -ge 1 ]; then
		package=$1
		shift 1
	else
		package=$(current_package)
	fi

	# If no applications are explicitely listed,
	# write launchers and menu entries for all applications.
	if [ $# -eq 0 ]; then
		applications_list=$(
			# The package context is required if a package-specific list is used.
			# This can be used for some games supported by multiple engines,
			# or when multiple games are provided by a single archive.
			set_current_package "$package"
			applications_list
		)
		# If called with no argument, the applications list should not be empty.
		if [ -z "$applications_list" ]; then
			error_applications_list_empty
			return 1
		fi
		launchers_generation "$package" $applications_list
		return 0
	fi

	information_launchers_generation

	local application
	for application in "$@"; do
		launcher_generation_checks "$package" "$application"
		launcher_write_script "$package" "$application"
		launcher_write_desktop "$package" "$application"
	done
}

# Run checks to ensure all required information for the launcher generation is set
# USAGE: launcher_generation_checks $package $application
launcher_generation_checks() {
	local package application
	package="$1"
	application="$2"

	# Ensure that the application type is set, or can be guessed
	local application_type
	application_type=$(
		set_current_package "$package"
		application_type "$application"
	)
	if [ -z "$application_type" ]; then
		# If the type could not be found despite a binary path being set,
		# this binary could be missing.
		local application_exe
		application_exe=$(
			set_current_package "$package"
			application_exe "$application"
		)
		if [ -n "${application_exe:-}" ]; then
			if ! launcher_target_presence_check "$package" "$application"; then
				error_launcher_missing_binary "$application_exe"
				return 1
			fi
		fi
		error_no_application_type "$application"
		return 1
	fi

	# If the current application type relies on a game binary, ensure its path is set
	case "$application_type" in
		('custom')
			## Custom launchers might not rely on a provided binary.
		;;
		('renpy')
			## Ren'Py games do not rely on a provided binary.
		;;
		('scummvm')
			## ScummVM games do not rely on a provided binary,
			## but they expect a ScummVM game ID to be set.
			local application_scummid
			application_scummid=$(
				set_current_package "$package"
				application_scummvm_scummid "$application"
			)
			if [ -z "$application_scummid" ]; then
				error_application_scummid_invalid "$application" "$application_scummid"
				return 1
			fi
		;;
		(*)
			local application_exe
			application_exe=$(
				set_current_package "$package"
				application_exe "$application"
			)
			if [ -z "$application_exe" ]; then
				error_application_exe_empty "$application" 'launcher_generation_checks'
				return 1
			fi
		;;
	esac

	# If the current application type relies on a game binary, ensure that it can be found
	if [ -n "${application_exe:-}" ]; then
		## A dedicated function is used here to make it easier to override from game scripts.
		if ! launcher_target_presence_check "$package" "$application"; then
			error_launcher_missing_binary "$application_exe"
			return 1
		fi
	fi
}

# Check that the launcher target exists.
# USAGE: launcher_target_presence_check $package $application
launcher_target_presence_check() {
	local package application
	package="$1"
	application="$2"

	local application_exe application_exe_path
	application_exe=$(
		set_current_package "$package"
		application_exe "$application"
	)
	application_exe_path=$(
		set_current_package "$package"
		application_exe_path "$application_exe"
	)

	# Failure to compute a path to the launcher target is a failure of the current test.
	if [ -z "$application_exe_path" ]; then
		return 1
	fi

	test -f "$application_exe_path"
}

# Write the launcher script for the given application.
# USAGE: launcher_write_script $package $application
launcher_write_script() {
	local \
		package=$1 \
		application=$2

	local launcher_path launcher_directory
	launcher_path=$(launcher_path "$package" "$application")
	launcher_directory=$(dirname "$launcher_path")
	mkdir --parents "$launcher_directory"
	touch "$launcher_path"
	chmod 755 "$launcher_path"

	## The *_launcher functions are called on their own first, to avoid being spawned in a subshell. This prevents their return code from being lost.
	## The package context is always set to ensure context-sensitive values for runtime options (and for the path to game data) are used.
	local application_type launcher_content
	application_type=$(
		set_current_package "$package"
		application_type "$application"
	)
	case "$application_type" in
		('custom')
			launcher_content=$(
				set_current_package "$package"
				custom_launcher "$application"
			)
			printf '%s' "$launcher_content" | snippet_clean > "$launcher_path"
		;;
		('dosbox')
			launcher_content=$(
				set_current_package "$package"
				dosbox_launcher "$application"
			)
			printf '%s' "$launcher_content" | snippet_clean > "$launcher_path"
			dependencies_add_command "$package" 'dosbox'
		;;
		('java')
			launcher_content=$(
				set_current_package "$package"
				java_launcher "$application"
			)
			printf '%s' "$launcher_content" | snippet_clean > "$launcher_path"
			dependencies_add_command "$package" 'java'
		;;
		('mono')
			launcher_content=$(
				set_current_package "$package"
				mono_launcher "$application"
			)
			printf '%s' "$launcher_content" | snippet_clean > "$launcher_path"
			dependencies_add_command "$package" 'mono'
		;;
		('native')
			launcher_content=$(
				set_current_package "$package"
				native_launcher "$application"
			)
			printf '%s' "$launcher_content" | snippet_clean > "$launcher_path"

			# Build the .so preload shim, if one is set
			local ld_preload_source
			ld_preload_source=$(ld_preload_source)
			if [ -n "$ld_preload_source" ]; then
				ld_preload_build "$package"
			fi

			# Add execution permissions to the game binary file
			local application_exe application_exe_path
			application_exe=$(
				set_current_package "$package"
				application_exe "$application"
			)
			application_exe_path=$(
				set_current_package "$package"
				application_exe_path "$application_exe"
			)
			## No need to check that a non-empty path is computed, this has been checked earlier already.
			chmod +x "$application_exe_path"

			# Add dependencies on some system-provided libraries based on the game engine.
			local game_engine
			game_engine=$(game_engine)
			case "$game_engine" in
				('unity3d')
					dependencies_add_native_libraries "$package" 'libSDL2-2.0.so.0'
				;;
				('visionaire')
					dependencies_add_native_libraries "$package" 'libSDL2-2.0.so.0'
				;;
			esac
		;;
		('renpy')
			launcher_content=$(
				set_current_package "$package"
				renpy_launcher "$application"
			)
			printf '%s' "$launcher_content" | snippet_clean > "$launcher_path"
			dependencies_add_command "$package" 'renpy'
		;;
		('scummvm')
			launcher_content=$(
				set_current_package "$package"
				scummvm_launcher "$application"
			)
			printf '%s' "$launcher_content" | snippet_clean > "$launcher_path"
			dependencies_add_command "$package" 'scummvm'
		;;
		('web')
			launcher_content=$(
				set_current_package "$package"
				web_launcher "$application"
			)
			printf '%s' "$launcher_content" | snippet_clean > "$launcher_path"
			dependencies_add_command "$package" 'firefox'
			dependencies_add_command "$package" 'python3'
		;;
		('wine')
			# Check the package architecture, and add the correct dependency.
			local package_architecture
			package_architecture=$(package_architecture "$package")
			case "$package_architecture" in
				('64')
					dependencies_add_command "$package" 'wine64'
				;;
				('32')
					dependencies_add_command "$package" 'wine32'
				;;
				(*)
					# Error out if the architecture is not set.
					error_wine_architecture_required "$package"
					return 1
				;;
			esac
			# Add a package dependency on winetricks if the current game relies on some winetricks verbs.
			local winetricks_verbs
			winetricks_verbs=$(wine_winetricks_verbs)
			if [ -n "$winetricks_verbs" ]; then
				dependencies_add_command "$package" 'winetricks'
				dependencies_add_command "$package" 'terminal_wrapper'
			fi
			# Add package dependencies on winetricks and rendering libraries if a non-default Direct3D renderer is required.
			local direct3d_renderer
			direct3d_renderer=$(wine_renderer_name)
			case "$direct3d_renderer" in
				('wined3d/'*)
					dependencies_add_command "$package" 'winetricks'
					dependencies_add_command "$package" 'terminal_wrapper'
					local wined3d_backend
					wined3d_backend=$(printf '%s' "$direct3d_renderer" | cut --delimiter='/' --fields=2)
					case "$wined3d_backend" in
						('gl')
							dependencies_add_native_libraries "$package" 'libGL.so.1'
						;;
						('vulkan')
							dependencies_add_native_libraries "$package" 'libvulkan.so.1'
						;;
					esac
				;;
				('dxvk')
					local option_package
					option_package=$(option_value 'package')
					case "$option_package" in
						('deb')
							dependencies_add_command "$package" 'dxvk-setup | winetricks'
							dependencies_add_command "$package" 'terminal_wrapper'
						;;
						(*)
							dependencies_add_command "$package" 'winetricks'
							dependencies_add_command "$package" 'terminal_wrapper'
						;;
					esac
					dependencies_add_native_libraries "$package" 'libvulkan.so.1'
				;;
				('vkd3d')
					dependencies_add_command "$package" 'winetricks'
					dependencies_add_command "$package" 'terminal_wrapper'
					dependencies_add_native_libraries "$package" 'libvulkan.so.1'
				;;
			esac

			# Generate the init .reg script, if set from the game script
			wine_registry_script_write "$package"

			launcher_content=$(
				set_current_package "$package"
				wine_launcher "$application"
			)
			printf '%s' "$launcher_content" | snippet_clean > "$launcher_path"
		;;
	esac
	# Always end the generated file with an end-of-line character.
	printf '\n' >> "$launcher_path"
}

# Print the path to the launcher script for the given application.
# USAGE: launcher_path $package $application
# RETURN: The absolute path to the launcher
launcher_path() {
	local package application
	package="$1"
	application="$2"

	local package_path path_binaries application_id
	package_path=$(package_path "$package")
	path_binaries=$(path_binaries)
	application_id=$(
		set_current_package "$package"
		application_id "$application"
	)

	printf '%s%s/%s' "$package_path" "$path_binaries" "$application_id"
}

# Print the headers common to all launcher scripts
# USAGE: launcher_headers
launcher_headers() {
	cat <<- EOF
	#!/bin/sh
	# script generated by ./play.it $PLAYIT_LIBRARY_VERSION - https://dotslashplay.it/
	set -o errexit

	EOF
}

# Exit with the game status code.
# USAGE: launcher_exit
launcher_exit() {
	cat <<- 'EOF'
	# Exit with the game status code.
	exit "${game_exit_status:-0}"
	EOF
}

# Print the line starting the game
# USAGE: game_exec_line $application
# RETURN: the command to execute, including its command line options
game_exec_line() {
	local application
	application="$1"

	local application_type
	application_type=$(application_type "$application")
	case "$application_type" in
		('dosbox')
			dosbox_exec_line "$application"
		;;
		('java')
			java_exec_line "$application"
		;;
		('mono')
			mono_exec_line "$application"
		;;
		('native')
			native_exec_line "$application"
		;;
		('renpy')
			renpy_exec_line "$application"
		;;
		('scummvm')
			scummvm_exec_line "$application"
		;;
		('wine')
			wine_exec_line "$application"
		;;
	esac
}

