# Check if the given archive is available
# USAGE: archive_is_available $archive
# RETURN: 0 if the archive is available,
#         1 if it is not
archive_is_available() {
	local archive
	archive="$1"

	local archive_path
	archive_path=$(archive_path "$archive" 2>/dev/null || true)

	test -n "$archive_path"
}

# Get the file name of a given archive
# USAGE: archive_name $archive
# RETURN: the archive name,
#         throws an error if no name can be found
archive_name() {
	local archive
	archive="$1"

	# The file name should be set using the ARCHIVE_xxx_NAME variable.
	local archive_name
	archive_name=$(get_value "${archive}_NAME")

	# Try to get a name from the archive path.
	# We can not use the archive_path function here, to prevent a loop between archive_path and archive_name.
	local archive_path
	archive_path=$(get_value "${archive}_PATH")
	if [ -n "$archive_path" ]; then
		archive_name=$(basename "$archive_path")
	fi

	# Throw an error if no name is found for the given archive.
	if [ -z "$archive_name" ]; then
		error_missing_variable "${archive}_NAME"
		return 1
	fi

	printf '%s' "$archive_name"
}

# Get the path to a given archive
# WARNING: No check is done that this path actually exists.
# USAGE: archive_path $archive
# RETURN: the absolute path to the archive,
#         or an empty path if the given archive is not available
archive_path() {
	local archive
	archive="$1"

	# If the path to the archive has already been computed, it has been cached in the ARCHIVE_xxx_PATH variable.
	local archive_path
	archive_path=$(get_value "${archive}_PATH")

	# Compute the path from the archive name
	if [ -z "$archive_path" ]; then
		local archives_path_base archive_name
		archives_path_base=$(archives_path_base)
		archive_name=$(archive_name "$archive" 2>/dev/null || true)
		## Do not try to compute a path if the archive does not seem to be available.
		## We can only reach this situation if errexit has been disabled due to calling `archive_path (…) || true`.
		if [ -z "$archive_name" ]; then
			return 0
		fi
		archive_path="${archives_path_base}/${archive_name}"
	fi

	# An absolute path should always be used, to allow using the archive path even after a directory change.
	archive_path=$(realpath --canonicalize-missing --no-symlinks "$archive_path")

	printf '%s' "$archive_path"
}

# Get the size of the archive contents, in kilobytes.
# This is not the size of the archive file itself,
# but the total size of the files it includes, after decompression.
# USAGE: archive_size $archive
archive_size() {
	## An archive identifier
	local archive="$1"

	local archive_size
	archive_size=$(get_value "${archive}_SIZE")

	## TODO: If the size is not explicitly set,
	##       try to guess it by scanning the archive contents.

	# If no size is set, assuming a size of 0.
	if [ -z "$archive_size" ]; then
		archive_size=0
	fi

	printf '%s' "$archive_size"
}

# Get the list of valid MD5 hashes for a given archive.
# This list can be empty.
# USAGE: archive_hashes_list_md5 $archive
archive_hashes_list_md5() {
	local archive
	archive="$1"

	local hashes_list
	hashes_list=$(get_value "${archive}_MD5")

	printf '%s' "$hashes_list" | list_clean
}

# Get the computed MD5 hash of a given archive.
# If a value is already stored in cache, this value is fetched; otherwise the hash is computed, then stored in cache.
# The cache is stored in a file instead of a variable, as this function is usually called from a subshell.
# USAGE: archive_hash_md5_computed $archive_name
archive_hash_md5_computed() {
	## A file name, found in the same directory than the main archive.
	local archive_name
	archive_name="$1"

	# If the hash has already been computed, use the cached value.
	local hash_md5_computed=''
	if [ -n "${PLAYIT_WORKDIR:-}" ]; then
		local cache_directory cache_file_hashes
		cache_directory="${PLAYIT_WORKDIR}/cache"
		cache_file_hashes="${cache_directory}/hashes"
		if [ -f "$cache_file_hashes" ]; then
			hash_md5_computed=$(sed --silent --expression="s/^${archive_name} | \([0-9a-f]\{32\}\)$/\1/p" "$cache_file_hashes")
		fi
	fi

	# If no cached value has been found, compute the hash.
	if [ -z "$hash_md5_computed" ]; then
		local archives_path_base archive_path
		archives_path_base=$(archives_path_base)
		archive_path="${archives_path_base}/${archive_name}"
		## Print the message to the error output, to prevent messing up with the current function output.
		info_archive_hash_computation "$archive_name" >/dev/stderr
		hash_md5_computed=$(md5sum "$archive_path" | awk '{print $1}')

		# Cache the hash to prevent computing it again.
		if [ -n "${PLAYIT_WORKDIR:-}" ]; then
			mkdir --parents "$cache_directory"
			cat >> "$cache_file_hashes" <<- EOF
			$archive_name | $hash_md5_computed
			EOF
		fi
	fi

	printf '%s' "$hash_md5_computed"
}

# Get the type of a given archive
# USAGE: archive_type $archive
# RETURNS: an archive type,
#          or an empty string if no type is set and none can be guessed
archive_type() {
	local archive
	archive="$1"

	local archive_type
	archive_type=$(get_value "${archive}_TYPE")

	# Guess the archive type from its file name
	if [ -z "$archive_type" ]; then
		local archive_name
		archive_name=$(archive_name "$archive")
		archive_type=$(archive_guess_type_from_name "$archive_name")
	fi

	# Guess the archive type from its headers
	if [ -z "$archive_type" ]; then
		local archive_path
		archive_path=$(archive_path "$archive")
		archive_type=$(archive_guess_type_from_headers "$archive_path")
	fi

	# Fall back on using the type of the parent archive, if there is one
	if
		[ -z "$archive_type" ] &&
		printf '%s' "$archive" |
		grep --quiet --word-regexp '^ARCHIVE_.*_PART[0-9]\+$'
	then
		local parent_archive
		parent_archive=$(printf '%s' "$archive" | sed 's/^\(ARCHIVE_.*\)_PART[0-9]\+$/\1/')
		archive_type=$(archive_type "$parent_archive")
	fi

	printf '%s' "$archive_type"
}

# Guess the archive type from its file name
# USAGE: archive_guess_type_from_name $archive_file
# RETURNS: the archive type,
#          or an empty string is none could be guessed
archive_guess_type_from_name() {
	local archive_file
	archive_file="$1"

	local archive_type
	case "$archive_file" in
		(*'.AppImage')
			archive_type='appimage'
		;;
		(*'.cab')
			archive_type='cabinet'
		;;
		(*'.deb')
			archive_type='debian'
		;;
		('setup_'*'.exe'|'patch_'*'.exe')
			# TODO: This should trigger a deprecation warning.
			archive_type='innosetup'
		;;
		(*'.iso')
			archive_type='iso'
		;;
		(*'.msi')
			archive_type='msi'
		;;
		(*'.rar')
			archive_type='rar'
		;;
		(*'.tar')
			archive_type='tar'
		;;
		(*'.tar.bz2'|*'.tbz2')
			archive_type='tar.bz2'
		;;
		(*'.tar.gz'|*'.tgz')
			archive_type='tar.gz'
		;;
		(*'.tar.xz'|*'.txz')
			archive_type='tar.xz'
		;;
		(*'.zip')
			archive_type='zip'
		;;
		(*'.7z')
			archive_type='7z'
		;;
		(*)
			# No type could be guessed from the archive file name
			archive_type=''
		;;
	esac

	printf '%s' "$archive_type"
}

# Guess the archive type from its headers
# USAGE: archive_guess_type_from_headers $archive_path
# RETURNS: the archive type,
#          or an empty string is none could be guessed
archive_guess_type_from_headers() {
	local archive_path
	archive_path="$1"

	local archive_type
	if head --lines=20 "$archive_path" | grep --quiet 'Makeself'; then
		if head --lines=50 "$archive_path" | grep --quiet 'script="./startmojo.sh"'; then
			archive_type='mojosetup'
		else
			archive_type='makeself'
		fi
	fi

	printf '%s' "${archive_type:-}"
}

# get the extractor for the given archive
# USAGE: archive_extractor $archive_identifier
# RETURNS: the specific extractor to use for the given archive (as a single word string),
#          or an empty string if none has been explicitely set.
archive_extractor() {
	local archive_identifier
	archive_identifier="$1"

	# Return archive extractor early if it is already set
	local archive_extractor
	archive_extractor=$(get_value "${archive_identifier}_EXTRACTOR")
	if [ -n "$archive_extractor" ]; then
		printf '%s' "$archive_extractor"
		return 0
	fi

	# Fall back on using the extractor of the parent archive, if there is one
	if
		printf '%s' "$archive_identifier" |
			grep --quiet --word-regexp '^ARCHIVE_.*_PART[0-9]\+$'
	then
		local parent_archive
		parent_archive=$(printf '%s' "$archive_identifier" | sed 's/^\(ARCHIVE_.*\)_PART[0-9]\+$/\1/')
		archive_extractor=$(archive_extractor "$parent_archive")
		if [ -n "$archive_extractor" ]; then
			printf '%s' "$archive_extractor"
			return 0
		fi
	fi

	# No failure if no extractor could be found
	return 0
}

# get the extractor options string for the given archive
# USAGE: archive_extractor_options $archive
# RETURNS: the options string to pass to the specific extractor to use for the given archive,
#          or an empty string if no options string has been explicitely set.
archive_extractor_options() {
	local archive
	archive="$1"

	get_value "${archive}_EXTRACTOR_OPTIONS"
}

# Get the game version provided by the given archive
# USAGE: archive_version $archive
archive_version() {
	local archive
	archive="$1"

	local archive_version
	archive_version=$(get_value "${archive}_VERSION")

	# Fall back on "1.0-1" if no version string is explicitly set.
	if [ -z "$archive_version" ]; then
		archive_version='1.0-1'
	fi

	# Check that the version string:
	# - starts with a number
	# - includes only digits, letters, dots and hyphens
	# - always includes an hyphen
	local version_pattern
	version_pattern='^[0-9][-.a-z0-9]*-[-.a-z0-9]\+$'
	if
		! printf '%s' "$archive_version" |
		grep --quiet "$version_pattern"
	then
		error_archive_version_format "$archive_version"
		return 1
	fi

	printf '%s' "$archive_version"
}

# Get the URL to the download page for a given archive.
# USAGE: archive_url $archive
archive_url() {
	local archive=$1

	# The URL should be set using the ARCHIVE_xxx_URL variable.
	get_value "${archive}_URL"
}

