#!/usr/bin/env bash

license() {
    cat << EOF
    ■The contents of this file are subject to the Common Public Attribution
    License Version 1.0 (the ■License■); you may not use this file except in
    compliance with the License. You may obtain a copy of the License at
    https://opensource.org/licenses/CPAL-1.0. The License is based on the Mozilla Public License Version
    1.1 but Sections 14 and 15 have been added to cover use of software over a
    computer network and provide for limited attribution for the Original
    Developer. In addition, Exhibit A has been modified to be consistent with
    Exhibit B.

    Software distributed under the License is distributed on an ■AS IS■ basis,
    WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
    for the specific language governing rights and limitations under the
    License.

    The Original Code is linux-game-manager.

    The Original Developer is not the Initial Developer and is . If
    left blank, the Original Developer is the Initial Developer.

    The Initial Developer of the Original Code is Billy "Storm Dragon" Wolfe. All portions of
    the code written by Billy Wolfe are Copyright (c) 2020. All Rights
    Reserved.

    Contributor Michael Taboada.

    Attribution Copyright Notice: linux-game-manager copyright 2022 Storm Dragon. All rights reserved.

    Attribution Phrase (not exceeding 10 words): A Stormux project

    Attribution URL: https://stormgames.wolfe.casa

    Graphic Image as provided in the Covered Code, if any.

    Display of Attribution Information is required in Larger
    Works which are defined in the CPAL as a work which combines Covered Code
    or portions thereof with code not governed by the terms of the CPAL.
EOF
}

# Dialog accessibility
export DIALOGOPTS='--no-lines --visit-items'


# Check for updates
check_update() {
    local url="$(git ls-remote --get-url)"
    if [[ "$url" =~ ^[[:alnum:]]+@ ]] || [[ -z "$url" ]]; then
        return
    fi
    git remote update > /dev/null 2>&1
    local upstream='@{u}'
    local home="$(git rev-parse @)"
    local remote="$(git rev-parse "$upstream")"
    if [[ "$home" == "$remote" ]]; then
        return
    fi
    dialog --backtitle "Linux Game manager" \
        --yesno "Updates are available. Would you like to update now?" -1 -1 --stdout || return
    # Store the current commit before pulling
    local beforePull=$(git rev-parse HEAD)
    git pull
    # Show changes between the stored commit and current HEAD
    git log "$beforePull..HEAD" --pretty=format:'%an: %s' | tac
    exit $?
}


# Check architecture compatibility
check_architecture() {
    if command -v FEXLoader &> /dev/null ; then
        export fex="FEXLoader -- "
        return
    fi
    local architecture="$(uname -m)"
    for i in "$@" ; do
        if [[ "${architecture}" == "$i" ]]; then
            return
        fi
    done
    dialog --backtitle "Linux Game Manager" \
        --infobox "This game is not compatible with $architecture architecture." -1 -1
    exit 1
}

# Check dependencies required for games
check_dependencies() {
    local dependencies
    for i in "${@}"; do
        if [[ "$i" =~ ^python- ]]; then
            if ! python3 -c "import ${i#*:}" &> /dev/null ; then
                dependencies+=("${i%:*}")
            fi
        elif ! command -v "$i" > /dev/null 2>&1 ; then
            dependencies+=("$i")
        fi
    done
    if [[ "${#dependencies[@]}" -eq 0 ]]; then
        return
    fi
    echo "missing dependencies. Please install the following:"
    echo
    for i in "${dependencies[@]}" ; do
        echo "$i"
    done
    exit 1
}

# Function to open a terminal emulator
terminal_emulator() {
    # Arguments workingDirectory, rest of arguments
    local workingDir="$1"
    shift
    terminals=(
        "lxterminal"
        "mate-terminal"
        "gnome-terminal"
    )
    for i in "${terminals[@]}" ; do
        if command $i --working-directory="${workingDir}" -e "$@" ; then
            return
        fi
    done
    echo "No suitable terminal emulators found, please install one of:"
    for i in "${terminals[@]}" ; do
        echo "$i"
    done
}

# Function to open urls
open_url() {
    xdg-open "${*}" 2> /dev/null
}

# Create desktop launcher file
desktop_launcher() {
    local desktopFile="${HOME}/linux-game-manager.desktop"
    if [[ -e "${desktopFile}" ]]; then
        echo "the file ${desktopFile} exists. Cannot create the launcher."
        exit 1
    fi
    local dotDesktop
    local terminal
    # Try to find an accessible terminal
    for i in mate-terminal lxterminal terminator gnome-terminal ; do
        if command -v $i > /dev/null 2>&1 ; then
            terminal="$i"
            break
        fi
    done
    dotDesktop=('[Desktop Entry]'
        'Name=Linux game manager'
        'GenericName=Linux game Manager'
        'Comment=Install and launch games that are accessible to the blind'
        "Exec=${terminal} -t \"Linux Game Manager\" -e \"/usr/bin/bash -c 'pushd $(readlink -e "${0%/*}");nohup ./"${0##*/}" 2> /dev/null'\""
        'Terminal=false'
        'Type=Application'
        'StartupNotify=false'
        'Keywords=game;'
        'Categories=Game;'
    'Version=1.0')
    for i in "${dotDesktop[@]}" ; do
        echo "$i" >> "${desktopFile}"
    done
    desktop-file-install --dir "${HOME}/.local/share/applications" -m 755 "${desktopFile}"
    xdg-desktop-icon install ~/.local/share/applications/linux-game-manager.desktop
    rm "${desktopFile}"
    exit 0
}

# Alerts, for when user needs to read something.
alert() {
    play -qnV0 synth 3 pluck D3 pluck A3 pluck D4 pluck F4 pluck A4 delay 0 .1 .2 .3 .4 remix - chorus 0.9 0.9 38 0.75 0.3 0.5 -t
    echo
    read -rp "Press enter to continue." continue
}

clear_cache() {
    local answer
    if [[ ! -d "${cache}" ]]; then
        echo "No cache found at ${cache}."
        return
    fi
    while ! [[ "${answer,,}" =~ ^yes$|^no$ ]]; do
        echo "This will delete all contents of ${cache}. Are you sure you want to continue?"
        echo "Please type yes or no."
        echo
        read -r answer
    done
    if [[ "$answer" == "no" ]]; then
        return
    fi
    # All safety checks done. Delete the cache.
    rm -rfv "${cache}"
    echo "Cache deleted."
}

download() {
   local source=($@)
   for i in "${source[@]}" ; do
       local dest="${i##*/}"
       dest="${dest//%20/ }"
       dest="${dest#*\?filename=}"
       dest="${dest%\?*}"
       # Remove the destination file if it is empty.
       [[ -s "${cache}/${dest}" ]] || rm -f "${cache}/${dest}" 2> /dev/null
       if [[ "${redownload}" == "true" ]] && [[ -e "${cache}/${dest}" ]]; then
           rm -v "${cache}/${dest}"
       fi
       # Skip if the item is in cache.
       [[ -e "${cache}/${dest}" ]] && continue
       { if ! curl -L4 -C - --retry 10 --output "${cache}/${dest}" "${i}" ; then
           echo "Could not download \"$i\"..."
           exit 1
       fi; } | dialog --backtitle "Linux Game Manager" \
           --progressbox "Downloading \"$dest\" from \"$i\"" -1 -1
       local downloadError=1
       case "${dest##*.}" in
           "pk3"|"zip")
               unzip -tq "${cache}/${dest}"  | dialog --backtitle "Linux Game Manager" \
                   --progressbox "Validating ${dest##*.} file" -1 -1 --stdout
                   downloadError=$?
           ;;
           "7z")
               7z t "${cache}/${dest}" | dialog --backtitle "Linux Game Manager" \
                   --progressbox "Validating 7z file" -1 -1 --stdout
                   downloadError=$?
           ;;
           "exe")
               # Check if it's a valid Windows executable by looking at the MZ header
               if ! hexdump -n 2 -v -e '/1 "%02X"' "${cache}/${dest}" | grep -q "4D5A"; then
                   downloadError=0
               fi
           ;;
           "wad")
               if [[ "$(file -b --mime-type "${cache}/${dest}")" == "application/octet-stream" ]]; then
                   downloadError=0
               fi
           ;;
           *)
               # Add HTML check for other file types
               if file -b "${cache}/${dest}" | grep -q "HTML document" ; then
                   downloadError=1
               fi
           ;;
       esac
       if [[ $downloadError -ne 0 ]]; then
           rm -fv "${cache}/${dest}"
                   dialog --backtitle "Linux Game Manager" \
                       --infobox "Error downloading \"${dest}\". Installation cannot continue." -1 -1 --stdout
                   alert
                   exit 1
       fi
   done
}

download_named() {
    # Only needed if url breaks the name, e.g. downloads/?filename=1234
    # Required arguments: filename url
    # Only works with one file at a time.
    local dest="$1"
    # Remove the destination file if it is empty.
    test -s "${cache}/${dest}" || rm -f "${cache}/${dest}" 2> /dev/null
    if [[ "${redownload}" == "true" ]] && [[ -e "${cache}/${dest}" ]]; then
        rm -v "${cache}/${dest}"
    fi
    # Skip if the item is in cache.
    test -e "${cache}/${dest}" && return
    if ! curl -L4 --output "${cache}/${dest}" "${2}" ; then
        echo "Could not download \"$dest\"..."
        exit 1
    fi
}

get_installer() {
    trap "exit 0" SIGINT
    # If the file is in cache nothing else needs to be done.
    if [[ -f "${cache}/$1" ]]; then
        return
    fi
    # Create message for dialog.
    local message="Make sure $1 is available in either your Downloads or Desktop directory and press enter to continue."
    if [[ -n "$2" ]]; then
        message+="\n\nThe last good known URL for $game is:"
        message+="\n$2"
    fi
    if echo "$2" | xclip -selection clipboard 2> /dev/null ; then
        message+="\n\nThe URL has been copied to the clipboard."
    fi
    echo "Manual intervention required..."
    alert
    dialog --ok-label "Continue" \
        --backtitle "Linux Game Manager" \
        --msgbox "$message" -1 -1
    # Search the Desktop and Downloads directories for the installation file
    for i in ~/Downloads ~/Desktop ; do
        find $i -type f -name "$1" -exec mv -v {} "${cache}/" \;
    done
    # If the file is still not available abort.
    if [[ ! -f "${cache}/$1" ]]; then
        echo "couldn't find $1. Please download the file and try again."
        exit 1
    fi
}

help() {
    echo "${0##*/}"
    echo "Released under the terms of the Common Public Attribution License Version 1.0"
    echo -e "This is a Stormux project: https://stormux.org\n"
    echo -e "Usage:\n"
    echo "With no arguments, open the game launcher."
    for i in "${!command[@]}" ; do
        echo "-${i/:/ <parameter>}: ${command[${i}]}"
    done | sort
    echo
    echo "Some settings that are often used can be stored in a settings.conf file."
    echo "If wanted, place it at the following location:"
    echo "${configFile%/*}/settings.conf"
    echo "The syntax is variable=\"value\""
    echo
    echo "doomLanguage=\"en\" # 2 letter language code for translation."
    echo "ipfsGateway=\"https://ipfs.stormux.org\" # Gateway to be used for ipfs downloads."
    echo "noCache=\"true\" # Do not keep downloaded items in the cache."
    echo "spd_module=\<module_name>\" # set speech-dispatcher module."
    echo "spd_pitch=\<number>\" # set speech-dispatcher speech pitch."
    echo "spd_rate=\<number>\" # set speech-dispatcher speech rate."
    echo "spd_voice=\<voice_name>\" # set speech-dispatcher voice. Be sure module is correct."
    echo "spd_volume=\<number>\" # set speech-dispatcher speech volume."
    exit 0
}


# main script


# Install games
game_installer() {
    # Create the menu of available games by reading from .install directory
    declare -a menuList
    # Get all .sh files from .install directory, excluding those starting with #, and sort them
    mapfile -t sortedGames < <(for f in "${0%/*}/.install/"*.sh; do
        # Skip if first line starts with #
        [[ $(head -n1 "$f") == "#"* ]] && continue
        # Output filename without .sh extension
        echo "${f##*/}"
    done | sort)
    for i in "${sortedGames[@]}"; do
        local menuItem="${i%.sh}"
        # Check if game is already installed
        for j in "${installedGames[@]}"; do
            if [[ "$j" == "$menuItem" ]]; then
                unset menuItem
                break
            fi
        done
        # Add to menu if not installed
        if [[ -n "$menuItem" ]]; then
            menuList+=("$menuItem" "$menuItem")
        fi
    done
    # If all games are installed, exit
    if [[ ${#menuList[@]} -eq 0 ]]; then
        echo "All games are already installed."
        exit 0
    fi
    # Add donation option at the end
    menuList+=("Donate" "Donate")
    # Show game selection dialog
    game="$(dialog --backtitle "Game Installer" \
        --clear \
        --no-tags \
        --menu "Please select a game to install" 0 0 0 "${menuList[@]}" --stdout)"
    # Handle selection
    if [[ -n "$game" ]]; then
        if [[ "$game" == "Donate" ]]; then
            open_url "https://ko-fi.com/stormux"
            exit 0
        fi
        # Convert game name to filename format
        local installScript="${0%/*}/.install/${game}.sh"
        # Check if install script exists
        if [[ -f "$installScript" ]]; then
            # Source and execute the install script
            source "$installScript"
        else
            dialog --backtitle "Game Installer" \
                --msgbox "Installation script not found for ${game}" -1 -1
            exit 1
        fi
    fi
    if [[ -e "${0%/*}/.launch/${game}.game" ]] && ! [[ -L "${0%/*}/.launch/${game}.sh" ]]; then
        ln -srf "${0%/*}/.launch/${game}.game" "${0%/*}/.launch/${game}.sh"
    fi
    exit 0
}


# remove games
game_removal() {
    # Initialize array for menu construction
    mapfile -t menuList < <(
        if [[ -d ".launch" ]]; then
            find -L "${0%/*}/.launch" -maxdepth 1 -type f -iname "*.sh" -print0 | sort -z | xargs -0 bash -c '
                for f; do
                        name="${f##*/}"
                        echo "$f"
                        echo "${name%.sh}"
                done' _
        fi
    )
    if [[ ${#menuList} -eq 0 ]]; then
        dialog --backtitle "Linux Game Manager" \
            --msgbox "No games found." -1 -1
        exit 0
    fi
    # Create the menu of installed games
    local selectedGame
    selectedGame=$(dialog --backtitle "Linux Game Manager" \
        --clear \
        --no-tags \
        --menu "Please select a game to delete" 0 0 0 "${menuList[@]}" --stdout)
    exitCode=$?
    [[ $exitCode -ne 0 ]] && exit 0
    # Get the actual game file paths
    local gameName="${selectedGame##*/}"
    gameName="${gameName%.sh}"
    local gameFile="$(readlink -f "${0%/*}/.launch/${gameName}.sh")"
    # Get the actual installation path from the .game file
    local gameInstallPath
    gameInstallPath="$(grep -F "installPath" "$gameFile" | grep -v 'pushd' | head -n1)"
    gameInstallPath="${gameInstallPath#*/}"
    gameInstallPath="${installPath}/${gameInstallPath%/*}"
    if [[ -z "$gameInstallPath" ]] || [[ "${gameInstallPath%%/}" == "$installPath" ]]; then
        # No install path found, just remove from list
        dialog --backtitle "Linux Game Manager" \
            --yesno "This will remove the game from your game list, but will not remove any files. Do you want to continue?" -1 -1 || exit 0
        # Remove only the .sh symlink
        rm -fv "${0%/*}/.launch/${gameName}.sh" | \
            dialog --backtitle "Linux Game Manager" \
            --progressbox "Removing game from list..." -1 -1
    else
        # Found install path, can remove game files
        dialog --backtitle "Linux Game Manager" \
            --yesno "This will remove the directory \"${gameInstallPath}\" and all of its contents. Do you want to continue?" -1 -1 || exit 0
        # Remove the game directory and symlink
        { rm -rfv "${gameInstallPath}"
            rm -fv "${0%/*}/.launch/${gameName}.sh";
        } | dialog --backtitle "Linux Game Manager" \
            --progressbox "Removing game..." -1 -1
    fi
    exit 0
}


# update games
game_update() {
    mapfile -t lines < <(find .update -type f -iname '*.sh' 2> /dev/null)
    if [[ ${#lines} -eq 0 ]]; then
        echo "No games found."
        exit 0
    fi
    # Create the menu of updatable games
    declare -a menuList
    for i in "${lines[@]}" ; do
        menuList+=("${i}" "${i##*/}")
    done
    menuList+=("Donate" "Donate")
    menuList+=("Become a Patron" "Become a Patron")
    local game="$(dialog --backtitle "Audio Game Updater" \
        --clear \
        --no-tags \
        --menu "Please select a game to update" 0 0 0 "${menuList[@]}" --stdout)"
    if [[ ${#game} -gt 0 ]]; then
        if [[ "$game" == "Donate" ]]; then
            open_url "https://ko-fi.com/stormux"
            exit 0
        fi
        if [[ "$game" == "Become a Patron" ]]; then
            open_url "https://2mb.games/product/2mb-patron/"
            exit 0
        fi
    fi
    source "${game}"
    run_update
    exit 0
}


# launch games that are installed
game_launcher() {
    # Initialize array for menu construction
    mapfile -t menuList < <(
        if [[ -d ".launch" ]]; then
            find -L "${0%/*}/.launch" -maxdepth 1 -type f -iname "*.sh" -print0 | sort -z | xargs -0 bash -c '
                for f; do
                        [[ $(head -n1 "$f") =~ ^#$ ]] && continue
                        name="${f##*/}"
                        echo "$f"
                        echo "${name%.sh}"
                done' _
        fi
    )
    if [[ ${#menuList} -eq 0 ]]; then
        dialog --backtitle "Linux Game Manager" \
            --msgbox "No games found." 5 20
        exit 0
    fi
    # Create the menu of all games
    selectedGame="$(dialog --backtitle "Linux Game Launcher" \
            --clear \
            --no-tags \
            --menu "Please select a game to play" 0 0 0 "${menuList[@]}" --stdout)"
    local menuCode=$?
    if [[ $menuCode -eq 1 ]]; then
        exit 0
    fi
    . "${selectedGame}"
    exit 0
}


migrate_launcher() {
    # Check if config file exists
    [[ -f "${configFile}" ]] || return
    # Process each line of the config file
    while IFS= read -r line; do
        # Skip empty lines
        [[ -z "${line}" ]] && continue
        # Extract game name and path
        gameName="${line%|*}"
        # Create launcher script if it doesn't exist
        if [[ ! -L "${0%/*}/.launch/${gameName}.sh" ]]; then
            ln -srf "${0%/*}/.launch/${gameName}.game" "${0%/*}/.launch/${gameName}.sh"
        fi
    done < <(sed '/^$/d' "${configFile}")
    # Move the old config file and notify user
    mv "${configFile}" "${configFile}.bak"
    dialog --backtitle "Linux Game manager" --msgbox \
        "Games have been converted to the new launch system.\nThe old game launch information has been moved to ${configFile}.bak\nIf everything works like you expect, feel free to delete ${configFile}" -1 -1
}


# If display isn't set assume we are launching from console and an X environment is running using display :0
# Warning, launching games from console is not recommended.
if [[ -z "$DISPLAY" ]]; then
    export DISPLAY=":0"
fi
# Settings file
cache="${XDG_CACHE_HOME:-$HOME/.cache}/linux-game-manager"
configFile="${XDG_CONFIG_HOME:-$HOME/.config}/storm-games/linux-game-manager/games.conf"
mkdir -p "${cache}"
mkdir -p "${configFile%/*}"
# Load any arguments from settings.conf file
if [[ -r "${configFile%/*}/settings.conf" ]]; then
    source "${configFile%/*}/settings.conf"
fi
unset noCache
export doomLanguage="${doomLanguage:-en}"
export installPath="${HOME}/.local/games"
export ipfsGateway="${ipfsGateway:-https://ipfs.stormux.org}"
export spd_module="${spd_module:+ -o ${spd_module}}"
export spd_pitch="${spd_pitch:+ -p ${spd_pitch}}"
export spd_rate="${spd_rate:+ -r ${spd_rate}}"
export spd_voice="${spd_voice:+ -y ${spd_voice}}"
export spd_volume="${spd_volume:+ -i ${spd_volume}}"
mkdir -p "${installPath}"
migrate_launcher


# Check for required packages
requiredPackages=(
    "7z"
    "curl"
    "dialog"
    "unzip"
)
for i in "${requiredPackages[@]}" ; do
    if ! command -v $i > /dev/null 2>&1 ; then
        echo "Please install ${i/7z/p7zip} before continuing."
        exit 1
    fi
done
check_update
# With no arguments, open the game launcher.
if [[ $# -eq 0 ]]; then
    game_launcher
fi

# Array of command line arguments
declare -A command=(
    [C]="Clear the cache. All game installers will be deleted."
    [D]="Create desktop shortcut. You can launch Linux Game Manager from the desktop or applications menu."
    [h]="This help screen."
    [i]="Install games."
    [L]="Display license information."
    [N]="No cache, delete the installer after it has been extracted."
    [R]="Redownload. Removes old versions of packages from cache before installing."
    [r]="Remove game. Remove a game and its menu entry."
    [t]="Total games. Show how many games are currently available."
    [u]="Update games. Run available game update scripts."
)

# Convert the keys of the associative array to a format usable by getopts
args="${!command[*]}"
args="${args//[[:space:]]/}"
while getopts "${args}" i ; do
    case "$i" in
        C) clear_cache ;;
        D) desktop_launcher ;;
        h) help ;;
        i) game_installer ;;
        L) license ;;
        N) noCache="true" ;;
        R) redownload="true" ;;
        r) game_removal ;;
        t)
            gameCount=$(find .install -type f -iname "*.sh" | wc -l)
            dialog --backtitle "Linux Game Manager" \
                --infobox "There are currently ${gameCount} games available." -1 -1
            exit 0
        ;;
        u) game_update ;;
    esac
done


exit 0