--[[

    games.lua
    --------
    Defines the games menu, which lists all installed games.

--]]

CreateElement(
    "GAMES_MENU",
    "MENU OBJECT",
    {
        "IS_FULLSCREEN",
        MINTS = {
            TITLE_MINT = {
                TYPE = "LABEL",
                POSITION = "(1 TILE, 12 PIXELS)",
                DIMENSIONS = "(360 SLICES - 2 TILES x 8 PIXELS)",
                TEXT = "Installed Games",
                APPEARANCE = {
                    TEXT = {
                        POSITION = "(-2 PIXELS, 0 PIXELS)"
                    }
                }
            },
            GAME_NAME_MINT = {
                TYPE = "LABEL",
                POSITION = "(1 TILE, 360 SLICES - 77 PIXELS)",
                DIMENSIONS = "(360 SLICES - 2 TILES x 1 TILE)",
                APPEARANCE = {
                    INACTIVE_COLOR = CommonPalette.Text,
                    TEXT = {
                        INACTIVE_COLOR = CommonPalette.MenuBG
                    }
                }
            },
            GAME_DESCRIPTION_MINT = {
                TYPE = "LABEL",
                POSITION = "(1 TILE + 1 PIXEL, 360 SLICES - 61 PIXELS)",
                DIMENSIONS = "(360 SLICES - 2 TILES - 2 PIXELS x 2 TILES + 3 PIXELS)",
                APPEARANCE = {
                    INACTIVE_COLOR = CommonPalette.MenuBG,
                    BORDER = {
                        WIDTH = "1 PIXEL",
                        INACTIVE_COLOR = CommonPalette.Text
                    },
                    TEXT = {
                        POSITION = "(3 PIXELS, 4 PIXELS)"
                    }
                }
            },
            HELP_MINT_PRIMARY = {
                TYPE = "LABEL",
                TEXT = "[ENTER] Launch",
                POSITION = "(1 TILE, 360 SLICES - 18 PIXELS)",
                DIMENSIONS = "(360 SLICES - 2 TILESx 8 PIXELS)",
                APPEARANCE = {
                    TEXT = {
                        POSITION = "(-2 PIXELS, 0 PIXELS)",
                        MARGIN = "0 PIXELS"
                    }
                }
            },
            HELP_MINT_SECONDARY = {
                TYPE = "LABEL",
                TEXT = "[ESC] Back",
                POSITION = "(1 TILE, 360 SLICES - 18 PIXELS)",
                DIMENSIONS = "(360 SLICES - 2 TILESx 8 PIXELS)",
                APPEARANCE = {
                    TEXT = {
                        ALIGNMENT = "RIGHT",
                        POSITION = "(-2 PIXELS, 0 PIXELS)",
                        MARGIN = "0 PIXELS"
                    }
                }
            }
        },
        INIT_SCRIPT = function ()
            LaunchMenu('GAMES_GRID_MENU',
                       {parentMints = self.mints})
        end,
        RETURN_SCRIPT = "CloseSelf()"
    }
)

----------------------------------
-- Sorting & History Functions  --
----------------------------------
-- Below functions are used to sort the games listed in the games menu.
-- They use the storage file game_history.lua, which is expected
--   to return a table containing game names as keys, and recency
--   scores as values.
-- Games with lower recency scores are prioritized.
-- Every time the menu is launched, recency scores of newly installed games
--   are set to zero, and the scores of existing games are incremented by one.
-- When a game is launched, its recency score is set to zero and
--   the scores of all other games are incremented by one.
-- If the recency scores of two games are equal, they're sorted
--   alphanumerically.

local function save_game_history(historyTable)
    -- Saves the given historyTable to game_history.lua
    local lines = {"return {"}
    for game, recencyScore in pairs(historyTable) do
        table.insert(lines, string.format("[ [[%s]] ]= %d,", game, recencyScore))
    end
    table.insert(lines, "}")
    Storage["game_history.lua"] = table.concat(lines, "\n")
end

local function update_game_history(launchedGame)
    -- Updates the user's game history by noting that the given game
    --   has been launched
    local historyTable = loadstring(Storage["game_history.lua"])()
    for game, recencyScore in pairs(historyTable) do
        if game ~= launchedGame.name then
            historyTable[game] = recencyScore + 1
        else
            historyTable[game] = 0
        end
    end
    save_game_history(historyTable)
end

local function sort_games(gamesArray, historyTable)
    -- Sorts the games in the given array according to their recency
    --   scores in the given history table.
    -- Prioritizes newly installed games over existing games, and sorts
    --   existing games by recency.
    for game, recencyScore in pairs(historyTable) do
        historyTable[game] = recencyScore + 1
    end
    local function sort_function (game1, game2)
        if not historyTable[game1.name] then
            historyTable[game1.name] = 0
        end
        if not historyTable[game2.name] then
            historyTable[game2.name] = 0
        end
        if historyTable[game1.name] == historyTable[game2.name] then
            return game1.name < game2.name
        else
            return historyTable[game1.name] < historyTable[game2.name]
        end
    end
    table.sort(gamesArray, sort_function)
    -- Save the new history table
    save_game_history(historyTable)
end

local function remove_uninstalled_games (games, historyTable)
    -- Removes uninstalled games from given historyTable
    local availableGameNames = {}
    for _, v in ipairs(games) do
        availableGameNames[v.name] = true
    end
    for i, _ in pairs(historyTable) do
        if not availableGameNames[i] then
            historyTable[i] = nil
        end
    end
    -- Not saving altered history on the assumption that it will be
    --   handled by a sort_games call following this function.
end

----------------------
-- Layout Functions --
----------------------

local function generate_game_grid_cell (game, i, gameIsNew)
    -- Generates a grid cell for given game by:
    -- - Creating a button mint for it that launches the game on press
    -- - Placing the game's cover art onto the menu's canvas
    -- - Creating a label mint indicating the game is new if signaled as such
    -- - Positioning the above listed GUI elements according to given index i
    local mintPos = string.format("(%d TILES, 0 PIXELS)", (i-1) * 6)
    local canvasPos = string.format("%d TILES", (i-1) * 6)
    -- Create the game button and insert it into the menu
    local gameMint = {
        TYPE = "BUTTON",
        POSITION = mintPos,
        DIMENSIONS = "(5 TILES x 7 TILES)",
        SCRIPTS = {
            SELECTION = function ()
                local name, description
                name = game.name
                if game.metadata and game.metadata.DESCRIPTION then
                    description = game.metadata.DESCRIPTION
                else
                    description = "No description available"
                end
                self.args.parentMints.GAME_NAME_MINT.text = name
                self.args.parentMints.GAME_DESCRIPTION_MINT.text = description
            end,
            PRESS = function ()
                LaunchGame(game.name)
                update_game_history(game)
            end
        }
    }
    table.insert(self.mints, CreateMint(gameMint))
    -- Place the game cover onto the menu's canvas
    if game.art.tiles.COVER then
	self.canvas:Place(game.name .. " COVER", canvasPos, "0 PIXELS", 1)
    else
        self.canvas:Place("COMMON_UI_TILESET DEFAULT_GAME_COVER", canvasPos, "0 PIXELS", 1)
    end
    -- Create new install label if signaled to do so
    if gameIsNew then
        local newInstallMint = CreateMint{
            TYPE = "LABEL",
            POSITION = mintPos,
            DIMENSIONS = "(5 TILES x 9 PIXELS)",
            TEXT = "NEW!",
            APPEARANCE = {
                INACTIVE_COLOR = "(0,0,0,127)",
                BORDER = {
                    WIDTH = "0 PIXELS"
                },
                TEXT = {
                    POSITION = "(-30 PIXELS, 1 PIXELS)",
                    INACTIVE_COLOR = "(255,215,0)"
                }
            }
        }
        local function new_install_label_scroll (frameCount)
            if newInstallMint.appearance.text.position.x.pixels == 80 then
                newInstallMint.appearance.text.position.x.pixels = -30
            end
            newInstallMint.appearance.text.position.x.pixels =
                newInstallMint.appearance.text.position.x.pixels + 1
        end
        -- TODO: Replace the number below with an indefinite duration keyword
        newInstallMint.scripts:Install{100000, new_install_label_scroll}
        table.insert(self.mints, newInstallMint)
    end
end

CreateElement(
    "GAMES_GRID_MENU",
    "MENU OBJECT",
    {
        POSITION = "(1 TILE, 180 SLICES - 86 PIXELS)",
        DIMENSIONS = "(360 SLICES - 2 TILES x 7 TILES + 2 PIXELS)",
        CAMERA = {
            NAME = "COMMON_GAME_CAROUSEL_CAM",
            BOUNDS = "((-1 PIXEL,-1 PIXEL),(+INF,+1 PIXEL))"
        },
        INIT_SCRIPT = function ()
            -- Get available game objects
            local games = SearchObjects(nil, "GAME OBJECTS")
            -- Get game history and sort the available games
            local historyTable = {}
            if Storage["game_history.lua"] then
                historyTable = loadstring(Storage["game_history.lua"])()
                remove_uninstalled_games(games, historyTable)
                sort_games(games, historyTable)
            else
                -- No game history exists, use a dummy table and leave
                --   history as empty to avoid indicating all games as
                --   new installs.
                sort_games(games, {})
            end
            -- Generate a grid cell for each game except COMMONS
            local gameCount = 0
            for _, game in pairs(games) do
                if game.name ~= "COMMONS" then
                    gameCount = gameCount + 1
                    generate_game_grid_cell(game, gameCount, historyTable[game.name] == 0)
                end
            end
            if gameCount ~= 0 then
                -- Adjust menu cam bounds as appropriate
                self.camBounds.x2 = string.format("%s TILES + 1 PIXEL",
                                                  (gameCount * 6) - 1)
            else
                -- No games found, display an appropriate message
                self.args.parentMints.GAME_NAME_MINT.text = "No games found"
                self.args.parentMints.GAME_DESCRIPTION_MINT.text = "Visit monospace.games to download games"
                self.args.parentMints.HELP_MINT_PRIMARY.text = ""
                self.args.parentMints.HELP_MINT_SECONDARY.appearance.text.alignment = "LEFT"
                self.canvas:Place("COMMON_UI_TILESET NO_GAMES", "180 SLICES - 6 TILE", "10 PIXELS", 0)
            end
        end,
        APPEARANCE = {
            MINTS = {
                ACTIVE_COLOR = "(0,0,0,0)",
                INACTIVE_COLOR = "(0,0,0,0)",
                BORDER = {
                    WIDTH = "1 PIXEL",
                    ACTIVE_COLOR = CommonPalette.PrimaryColor,
                    INACTIVE_COLOR = CommonPalette.Text
                }
            }
        }
    }
)
