# List the directories from the game prefix that should be diverted to a persistent path
# USAGE: persistent_list_directories
# RETURNS: a list of paths to directories, separated by line breaks
#          glob patterns can be included
persistent_list_directories() {
	context_value 'USER_PERSISTENT_DIRECTORIES' | list_clean
}

# List the files from the game prefix that should be diverted to a persistent path
# USAGE: persistent_list_files
# RETURNS: a list of paths to files, separated by line breaks
#          glob patterns can be included
persistent_list_files() {
	context_value 'USER_PERSISTENT_FILES' | list_clean
}

# Setup the paths diversion to persistent storage.
# USAGE: persistent_storage_prerun $application
persistent_storage_prerun() {
	local application=$1

	# Some actions differ based on the application type.
	local application_type
	application_type=$(application_type "$application")

	# Some actions are only required for some types of diversions.
	local persistent_directories persistent_files fake_home_persistent_directories wine_persistent_directories
	persistent_directories=$(persistent_list_directories)
	persistent_files=$(persistent_list_files)
	fake_home_persistent_directories=$(fake_home_persistent_directories)
	wine_persistent_directories=$(wine_persistent_directories)

	# Patterns expansion is only required if USER_PERSISTENT_xxx is used.
	if
		[ -n "$persistent_directories" ] ||
		[ -n "$persistent_files" ]
	then
		persistent_storage_common
	fi

	# Paths diversion is required by the following:
	# - USER_PERSISTENT_DIRECTORIES
	# - FAKE_HOME_PERSISTENT_DIRECTORIES
	# - WINE_PERSISTENT_DIRECTORIES
	if
		[ -n "$persistent_directories" ] ||
		[ -n "$fake_home_persistent_directories" ] ||
		[ -n "$wine_persistent_directories" ]
	then
		persistent_path_diversion
	fi

	# Paths diversion from USER_PERSISTENT_DIRECTORIES
	if [ -n "$persistent_directories" ]; then
		persistent_storage_update_directories
	fi
	# Paths diversion from USER_PERSISTENT_FILES
	if [ -n "$persistent_files" ]; then
		persistent_storage_update_files
	fi
	# Paths diversion from FAKE_HOME_PERSISTENT_DIRECTORIES
	if [ -n "$fake_home_persistent_directories" ]; then
		fake_home_persistent
	fi

	# The initialisation is always required.
	# It must be called after diverted directories have already been replaced by links,
	# to prevent errors when a diverted directory is already existing in the base game files,
	# even prior to the first game launch.
	persistent_storage_initialization
}

# Update persistent storage from current prefix.
# USAGE: persistent_storage_postrun
persistent_storage_postrun() {
	local persistent_files
	persistent_files=$(persistent_list_files)

	# Paths diversion from USER_PERSISTENT_FILES
	if [ -n "$persistent_files" ]; then
		persistent_storage_update_files
	fi
}

# Populate the volatile prefix from persistent files.
# USAGE: persistent_storage_initialization
persistent_storage_initialization() {
	cat <<- 'EOF'
	# Populate the volatile prefix from persistent files.
	if [ -e "$PATH_PERSISTENT" ]; then
	    find "$PATH_PERSISTENT" \( -type f -o -type l \) ! -path "${PATH_PERSISTENT}/wine/*" ! -path "${PATH_PERSISTENT}/wineprefix/*" |
	        while read -r file_path_persistent; do
	            file_path_prefix="${PATH_PREFIX}/${file_path_persistent#"${PATH_PERSISTENT}/"}"
	            # Skip aliased paths.
	            file_realpath_persistent=$(realpath "$file_path_persistent")
	            file_realpath_prefix=$(realpath "$file_path_prefix")
	            if [ "$file_realpath_persistent" = "$file_realpath_prefix" ]; then
	                continue
	            fi
	            # Override prefix paths with links to persistent paths.
	            directory_prefix=$(dirname "$file_realpath_prefix")
	            mkdir --parents "$directory_prefix"
	            ln --symbolic --force --no-target-directory \
	                "$file_path_persistent" \
	                "$file_path_prefix"
	    done
	fi

	EOF
}

# Set the common actions required for directories and files diversion to persistent storage
# This is required for the diversions using one of the following variables:
# - USER_PERSISTENT_DIRECTORIES
# - USER_PERSISTENT_FILES
# USAGE: persistent_storage_common
persistent_storage_common() {
	cat <<- 'EOF'
	# Expand a path pattern into a list of existing paths.
	# If the pattern can not be expanded, it is printed as-is instead.
	expand_path_pattern() {
	    pattern="$1"

	    ## Silently skip empty patterns
	    if [ -z "$pattern" ]; then
	        return 0
	    fi

	    expanded_paths=$(find . -path "./${pattern#./}")
	    if [ -n "$expanded_paths" ]; then
	        printf '%s\n' "$expanded_paths"
	    else
	        printf '%s\n' "$pattern"
	    fi

	    unset pattern expanded_paths
	}

	EOF
}

# Set the action used to divert a given path to persistent storage
# USAGE: persistent_path_diversion
# TODO: This function should be expanded to handle both directories and files,
#       so it could replace most of persistent_storage_update_files.
persistent_path_diversion() {
	## TODO: env --chdir could be used instead of cd in a subshell.
	cat <<- 'EOF'
	# Replace a given directory in a prefix by a link to another directory in persistent storage
	# USAGE: persistent_path_diversion $path_source $path_destination $directory
	persistent_path_diversion() {
	    path_source="$1"
	    path_destination="$2"
	    directory="$3"

	    ## If the target directory does not already exist in persistent storage,
	    ## copy it from the prefix (if existing) or create a new empty one.
	    if [ ! -e "${path_destination}/${directory}" ]; then
	        if [ -e "${path_source}/${directory}" ]; then
	            (
	                cd "$path_source"
	                mkdir --parents "$path_destination"
	                cp --dereference --parents --recursive \
	                    "$directory" \
	                    "$path_destination"
	            )
	        else
	            mkdir --parents "${path_destination}/${directory}"
	        fi
	    fi

	    ## Replace the directory in the prefix by a link to the one in persistent storage.
	    if [ ! -h "${path_source}/${directory}" ]; then
	        directory_parent=$(dirname "${path_source}/${directory}")
	        rm --recursive --force "${path_source:?}/${directory}"
	        mkdir --parents "$directory_parent"
	        ln --symbolic "${path_destination}/${directory}" "${path_source}/${directory}"
	    fi
	    unset directory_parent

	    unset path_source path_destination directory
	}

	EOF
}

# Update directories diversions to persistent storage
# USAGE: persistent_storage_update_directories
persistent_storage_update_directories() {
	local persistent_list_directories
	persistent_list_directories=$(persistent_list_directories)
	cat <<- EOF
	# Update directories diversions to persistent storage
	USER_PERSISTENT_DIRECTORIES='${persistent_list_directories}'
	EOF
	## TODO: env --chdir could be used instead of cd in a subshell.
	cat <<- 'EOF'
	(
	    cd "$PATH_PREFIX"
	    while read -r directory_pattern; do
	        ## Skip empty patterns
	        if [ -z "$directory_pattern" ]; then
	            continue
	        fi

	        while read -r directory; do
	            persistent_path_diversion "$PATH_PREFIX" "$PATH_PERSISTENT" "$directory"
	        done <<- EOL
	        $(expand_path_pattern "$directory_pattern")
	        EOL
	    done <<- EOL
	    $(printf '%s' "$USER_PERSISTENT_DIRECTORIES")
	    EOL
	    unset directory_pattern
	)

	EOF
}

# Divert files from the volatile prefix to persistent storage.
# USAGE: persistent_storage_update_files
# TODO: This is copied before and after running the game,
#       it should be written only once as a function, then called twice.
persistent_storage_update_files() {
	local persistent_list_files
	persistent_list_files=$(persistent_list_files)
	cat <<- EOF
	# Divert files from the volatile prefix to persistent storage.
	USER_PERSISTENT_FILES='${persistent_list_files}'
	EOF
	cat <<- 'EOF'
	(
	    # The following must be run directly from the prefix,
	    # to ensure correct patterns expansion.
	    cd "$PATH_PREFIX"
	    while read -r file_pattern; do
	        while read -r file; do
	            # Copy files from the prefix to persistent storage.
	            if [ -e "$file" ]; then
	                # Copy files that exist in the prefix but not in persistent storage.
	                if [ ! -e "${PATH_PERSISTENT}/${file}" ]; then
	                    mkdir --parents "$PATH_PERSISTENT"
	                    cp --dereference --parents \
	                        "$file" \
	                        "$PATH_PERSISTENT"
	                fi
	                # Copy files that exist in the prefix and are not symbolic links
	                # (overriding already existing files in persistent storage).
	                if [ -f "$file" ] && [ ! -h "$file" ]; then
	                    mkdir --parents "$PATH_PERSISTENT"
	                    cp --parents --remove-destination \
	                        "$file" \
	                        "$PATH_PERSISTENT"
	                fi
	            fi
	            # Links files from persistent storage to the prefix
	            # (overriding files/links already existing in the prefix).
	            if [ -e "${PATH_PERSISTENT}/${file}" ]; then
	                file_parent=$(dirname "$file")
	                mkdir --parents "$file_parent"
	                ln --force --symbolic "${PATH_PERSISTENT}/${file}" "$file"
	                unset file_parent
	            fi
	        done <<- EOL
	        $(expand_path_pattern "$file_pattern")
	        EOL
	        unset file
	    done <<- EOL
	    $(printf '%s' "$USER_PERSISTENT_FILES")
	    EOL
	    unset file_pattern
	)

	EOF
}

