-- This file is part of FS25_Through_The_Years.
--
-- FS25_Through_The_Years is free software: you can redistribute it and/or
-- modify it under the terms of the GNU General Public License as published by
-- the Free Software Foundation, either version 3 of the License, or
-- (at your option) any later version.
--
-- FS25_Through_The_Years is distributed in the hope that it will be useful, but
-- WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
-- FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
-- details.
--
-- You should have received a copy of the GNU General Public License along with
-- FS25_Through_The_Years. If not, see <https://www.gnu.org/licenses/>.

source(g_currentModDirectory .. "events/settings_change_event.lua")
source(g_currentModDirectory .. "events/load_settings_for_client_event.lua")
source(g_currentModDirectory .. "type_control_options.lua")


ThroughTheYears = {}

local function ttyprint(value)
    if value ~= nil then
        print("ThroughTheYears: " .. value)
    else
        print("ThroughTheYears: nil")
    end
end

local function log_inf(...)
    ttyprint(...)
end
local function log_dbg(...)
    if ThroughTheYears.debugEnabled then
        ttyprint(...)
    end
end

local function log_dbg_verbose(...)
    if ThroughTheYears.verboseDebugEnabled then
        ttyprint(...)
    end
end

local function dump(value, depth, indent)
    local maxDepth = depth or 1
    DebugUtil.printTableRecursively(value, indent, 0, maxDepth)
end

local function logSettings()
    log_dbg("baseYear: " .. ThroughTheYears.baseYear)
    log_dbg("baseYearState: " .. ThroughTheYears.baseYearState)
    log_dbg("filterNoYearState: " .. ThroughTheYears.filterNoYearState)
    log_dbg("vehicleShopExpirationState: " .. ThroughTheYears.vehicleShopExpirationState)
    for category, value in pairs(ThroughTheYears.typeControlOptions) do
        log_dbg_verbose(category .. ": " .. value.state)
    end
end
local function sendSettingsChangedEvent()
    log_dbg("sendSettingsChangedEvent")

    logSettings()
    g_client:getServerConnection():sendEvent(
        SettingsChangeEvent.new(
            ThroughTheYears.baseYear, ThroughTheYears.baseYearState, ThroughTheYears.filterNoYearState,
            ThroughTheYears.vehicleShopExpirationState, ThroughTheYears.typeControlOptions))
end


function ThroughTheYears.SettingsChanged(baseYear, baseYearState, filterNoYearState, vehicleShopExpirationState,
                                         typeControlOptions)
    log_dbg("SettingsChanged")

    ThroughTheYears.baseYear = baseYear
    ThroughTheYears.baseYearState = baseYearState
    ThroughTheYears.filterNoYearState = filterNoYearState
    ThroughTheYears.filterNoYear = filterNoYearState == 1
    ThroughTheYears.vehicleShopExpirationState = vehicleShopExpirationState
    -- May become 0 to disable since vehicleShopExpirationState == 1 == "Off"
    if vehicleShopExpirationState > 0 then
        ThroughTheYears.vehicleShopExpiration = vehicleShopExpirationState - 1
    else
        ThroughTheYears.vehicleShopExpiration = 0
    end
    ThroughTheYears.typeControlOptions = typeControlOptions

    logSettings()
end

function ThroughTheYears.loadSettingsForClient()
    log_dbg("loadSettingsForClient")
    logSettings()

    g_client:getServerConnection():sendEvent(
        LoadSettingsForClientEvent.new(
            ThroughTheYears.baseYear, ThroughTheYears.baseYearState, ThroughTheYears.filterNoYearState,
            ThroughTheYears.vehicleShopExpirationState,
            ThroughTheYears.typeControlOptions))
end

function ThroughTheYears:updateItemAvailability(item, state)
    log_dbg_verbose("updateItemAvailability")
    item.showInStore = state
end

local function storeItemHasYear(storeItem)
    return storeItem ~= nil and storeItem.specs ~= nil and storeItem.specs.year ~= nil
end

function ThroughTheYears:itemMatchesYear(storeItem)
    log_dbg_verbose("itemMatchesYear")

    if storeItem == nil then
        log_dbg("storeItem is nil")

        return false
    end

    -- Any category that we do not have a filter for we don't file. This covers buildings and consumables among other things.
    if not TypeControlOptions.matchesCategory(storeItem) then
        return true
    end
    if storeItem.specs == nil then
        -- Trigger the getValueFunc of all items to get the year from the
        -- FS25_Vehicle_Years mod
        local desc = g_storeManager:getSpecTypeByName("year")
        if desc == nil then
            ttyprint("Could not get years - Is FS25_Vehicle_Years missing?")
            return false
        end

        StoreItemUtil.loadSpecsFromXML(storeItem)
        desc.getValueFunc(storeItem, nil, nil, nil, nil, nil)
    end

    -- Only modify showInStore for those vehicles that have the year spec to avoid
    -- modifying any defaults
    if storeItemHasYear(storeItem) then
        local year = tonumber(storeItem.specs.year)

        -- If year is nil, then it has failed to parse the year
        if year ~= nil then
            -- Only disable vehicles if the category filtering is enabled
            local categoryFilterIsEnabled = false
            local categoryOption = ThroughTheYears.typeControlOptions
                [TypeControlOptions.getCategory(storeItem)]

            if categoryOption == nil then
                -- Treat these similar to the ones without a year
                return not ThroughTheYears.filterNoYear
            end

            log_dbg_verbose(string.format("Name: %s, year: %d, state %d", storeItem.name,
                year, categoryOption.state))

            categoryFilterIsEnabled = categoryOption.state ~= TypeControlOptions.STATE_DISABLED
            if categoryFilterIsEnabled then
                if categoryOption.state ~= TypeControlOptions.STATE_ENABLED_NO_MAX and
                    ThroughTheYears.vehicleShopExpiration > 0 then
                    local min_year = ThroughTheYears.realCurrentYear - ThroughTheYears.vehicleShopExpiration

                    return year >= min_year and year <= ThroughTheYears.realCurrentYear
                else
                    return year <= ThroughTheYears.realCurrentYear
                end
            else
                return true
            end
        else
            log_dbg(string.format("Invalid year: %s", storeItem.specs.year))
        end
    end

    return not ThroughTheYears.filterNoYear
end

local function addToMinimumItems(item, items)
    log_dbg_verbose("addToMinimumItems")

    local categoryName = item.categoryName
    local categoryOption = ThroughTheYears.typeControlOptions[categoryName]

    if categoryOption == nil then
        return
    end

    local categoryMinOne = categoryOption.state == TypeControlOptions.STATE_MIN_ONE

    -- Ensure that is at least one filtered item in the shop if the category enables it
    -- (unless there is an unfiltered item)
    -- Will choose the oldest item, and if there are multiple items of same year,
    -- choose the cheapest
    -- Do not consider any vehicles from extraContent as that may not be available to everyone
    if categoryMinOne and item.extraContentId == nil and item.showInStoreOrig then
        if item.specs ~= nil and item.specs.year ~= nil then
            local year = tonumber(item.specs.year)

            if year ~= nil and item.showInStore == false then
                local storedItem = items[categoryName]

                if storedItem == nil then
                    items[categoryName] = item
                else
                    local storedItemYear = tonumber(storedItem.specs.year)

                    if storedItemYear == nil then
                        -- Ignore items with invalid year
                    elseif storedItemYear > year then
                        items[categoryName] = item
                    elseif storedItemYear == year and storedItem.price > item.price then
                        items[categoryName] = item
                    end
                end
            else
                items[categoryName] = item
            end
        else
            if items[categoryName] == nil then
                items[categoryName] = item
            else
                -- Overwrite the stored item by any item that does not have a year spec
                -- This ensures that we do not unnecessary add filtered items if there are items
                -- in the category that is not filtered by year
                if items[categoryName].showInStore == false and item.showInStore then
                    items[categoryName] = item
                end
            end
        end
    end
end

-- Keep a copy of the original showInStore value so that we can modify it without adding items that
-- should never have been added
local function addShowInStoreCopy()
    log_dbg("addShowInStoreCopy")

    local shopItems = g_storeManager:getItems();

    for index, item in pairs(shopItems) do
        if item ~= nil then
            item.showInStoreOrig = item.showInStore
        end
    end
end

local function saleItemMatchesYear(saleItem)
    log_dbg_verbose("saleItemMatchesYear")

    local shopItems = g_storeManager:getItems()
    local storeItem = nil

    -- Find storeItem based on the XML file (easiest way to do it) to then compare years
    for _, item in pairs(shopItems) do
        if saleItem.xmlFilename == item.xmlFilename then
            storeItem = item
            break
        end
    end

    if storeItem == nil then
        log_dbg(string.format("Did not find store item for %s", saleItem.xmlFilename))
        return false
    end

    if ThroughTheYears:itemMatchesYear(storeItem) then
        if storeItemHasYear(storeItem) then
            -- Apply age of sale item (in months) as well so that a vehicle from 1980 with age 24
            -- cannot be for sale in 1981
            local year = tonumber(storeItem.specs.year)
            -- Device age by number of month and round up to get the latest year it could be from
            local year_of_purchase = ThroughTheYears.realCurrentYear - ((saleItem.age + 12 - 1) // 12)

            log_dbg_verbose("year: " .. year)
            log_dbg_verbose("year_of_purchase: " .. year_of_purchase)

            return year <= year_of_purchase
        else
            -- Don't filter items without the year spec
            return true
        end
    end

    return false
end

local function updateSales()
    log_dbg(string.format("updateSales for year %d", ThroughTheYears.realCurrentYear))

    -- Gather indexes for removal - Removing items from the table while iterating it can cause
    -- issues with nil access
    if g_currentMission.vehicleSaleSystem.items ~= nil then

        local items_to_keep = {}

        for _, salesItem in pairs(g_currentMission.vehicleSaleSystem.items) do
            if saleItemMatchesYear(salesItem) then
                table.insert(items_to_keep, salesItem)
            end
        end

        -- Copy back the accepted salesItems
        g_currentMission.vehicleSaleSystem.items = items_to_keep
    end
end

local function getNearestVehicle(storeItem)
    -- Returns the vehicle that most closely resembles the provided vehicle
    -- For now it will just return the oldest item of the same type.
    -- Future versions should take specs into account (at least HP requirements)
    -- Bale wrapping missions need balers that can do that
    -- Bale missions require either round or square bales - Cannot mix
    -- Harvesters need to support the target crop
    -- Each mission has a field size size of field: 'small', 'medium', 'large'
    -- Some categories like SPRAYERS contain both drivable vehicles as well as tanks...

    local shopItems = g_storeManager:getItems();

    local return_item = nil

    for index, item in pairs(shopItems) do
        if item ~= nil and item.showInStore and TypeControlOptions.getCategory(storeItem) == TypeControlOptions.getCategory(item) then
            if return_item == nil then
                return_item = item
            elseif item.specs.year ~= nil then
                if return_item.specs.year == nil or tonumber(item.specs.year) > tonumber(return_item.specs.year) then
                    -- Return the most recent vehicle
                    return_item = item
                end
            end
        end
    end

    return return_item
end


local function addMission(self, mission)
    log_dbg("addMission")
    -- Called whenever there is a a mission added

    if ThroughTheYears.initian_load_done and mission.vehiclesToLoad ~= nil then
        local items_to_keep = {}

        for _, mvehicle in ipairs(mission.vehiclesToLoad) do
            -- mvehicle.filename may be nil in some cases
            if mvehicle.filename ~= nil then
                -- For some reason getItemByXMLFilename doesn't work. so get it directly
                local vehicle = g_storeManager.xmlFilenameToItem
                    [string.lower(mvehicle.filename)]

                if vehicle ~= nil then
                    log_dbg_verbose("Loaded vehicle from" .. mvehicle.filename)
                    if vehicle.showInStore then
                        table.insert(items_to_keep, mvehicle)
                    else
                        local nearest_vehicle = getNearestVehicle(vehicle)

                        if nearest_vehicle ~= nil and nearest_vehicle.xmlFilename ~= nil then
                            -- Replace with nearest_vehicle
                            mvehicle.filename = nearest_vehicle.xmlFilename
                            -- Clear any potential configurations as we just replaced the vehicle
                            mvehicle.configurations = {}

                            table.insert(items_to_keep, mvehicle)
                        end
                    end
                else
                    log_dbg_verbose("Could not load vehicle from" .. mvehicle.filename)
                end
            end
        end

        mission.vehiclesToLoad = items_to_keep
    end
end

local function updateMissions()
    log_dbg("updateMissions")

    for index, mission in ipairs(g_missionManager.missions) do
        addMission(ThroughTheYears, mission)
    end
end

--- Updates the showInStore field for all vehicles
-- @param notify Boolean to whether to send a notification of the change
local function updateShowInStore(notify)
    log_dbg("updateShowInStore")

    local shopItems = g_storeManager:getItems();
    local minimumOneCategoryItems = {}

    -- Iterate on all storeItems and update the showInStore based on the current year
    -- and the vehicle year
    for index, item in pairs(shopItems) do
        if item ~= nil and item.showInStoreOrig then
            if ThroughTheYears:itemMatchesYear(item) then
                if item.showInStore == false then
                    ThroughTheYears:updateItemAvailability(item, true)
                    if notify then
                        -- Look up brand and send a notification with brand name and item name
                        local brand = g_brandManager:getBrandByIndex(item.brandIndex)
                        local category = g_storeManager:getCategoryByName(item.categoryName)

                        local notify_str = string.format("%s%s%s now available",
                            brand and brand.title .. " " or "", item.name,
                            category and " (" .. category.title .. ")" or "")

                        log_dbg_verbose(notify_str)

                        g_currentMission:addIngameNotification(FSBaseMission.INGAME_NOTIFICATION_OK, notify_str)
                    end
                end
            else
                ThroughTheYears:updateItemAvailability(item, false)
            end

            addToMinimumItems(item, minimumOneCategoryItems)
            -- also contains non-vehicle categories (bigbags, selling stations)
        end
    end

    -- Update showInStore for each filtered item to ensure at least 1 per category exist
    for key, item in pairs(minimumOneCategoryItems) do
        local categoryOption = ThroughTheYears.typeControlOptions
            [TypeControlOptions.getCategory(item)]

        if categoryOption ~= nil then
            categoryOption.xmlFilename = nil

            if item.showInStore == false then
                log_dbg(string.format("Added %s as only item in category %s", item.name, key))

                item.showInStore = true
                categoryOption.xmlFilename = item.xmlFilename
            end
        end
    end

    -- Only update sales and missions when it's not the initial load, as the changes cannot be
    -- reversed when the actual year is load
    if ThroughTheYears.initian_load_done then
        updateSales()
        updateMissions()
    end
end

local function updateGameCurrentYear()
    -- Years in FS25 start at 1 so subtract 1
    ThroughTheYears.gameCurrentYear = g_currentMission.environment.currentYear - 1

    -- currentPeriod 11 is January currentPeriod 12 is February. Update mod year when we reach January,
    -- but also treat January, February and March as the same year, even though the game increments the year in March
    if g_currentMission.environment.currentPeriod == 11 or g_currentMission.environment.currentPeriod == 12 then
        ThroughTheYears.gameCurrentYear = ThroughTheYears.gameCurrentYear + 1
    end
end
local function updateRealCurrentYear(notify)
    log_dbg("updateRealCurrentYear")
    -- Years change in March

    -- Years in FS25 start at 1, so add the baseYear and subtract 1
    ThroughTheYears.realCurrentYear = ThroughTheYears.baseYear + ThroughTheYears.gameCurrentYear

    log_dbg(string.format("updateRealCurrentYear: %d (%d %d %d)", ThroughTheYears.realCurrentYear,
        ThroughTheYears.baseYear, g_currentMission.environment.currentYear, ThroughTheYears.gameCurrentYear))
    updateShowInStore(notify)
end

local function periodChanged()
    log_dbg("periodChanged: " .. g_currentMission.environment.currentPeriod)

    updateGameCurrentYear()

    -- Period 11 is January so trigger a year change
    if g_currentMission.environment.currentPeriod == 11 then
        --  if currentPeriod = 11 (jan) or 12 (feb) increment year by 1
        updateRealCurrentYear(true)
    end
end

function ThroughTheYears.saveToXMLFile(missionInfo)
    log_dbg("saveToXMLFile")

    if missionInfo.isValid then
        local xmlFileName = missionInfo.savegameDirectory .. "/" .. ThroughTheYears.optionSavegameFileName
        local xmlFile = XMLFile.create("ThroughTheYears", xmlFileName, "ThroughTheYears")

        if xmlFile ~= nil then
            xmlFile:setInt("ThroughTheYears.SETTINGS_VERSION", ThroughTheYears.SETTINGS_VERSION)
            xmlFile:setInt("ThroughTheYears.baseYear", ThroughTheYears.baseYear)
            xmlFile:setInt("ThroughTheYears.baseYearState", ThroughTheYears.baseYearState)
            xmlFile:setInt("ThroughTheYears.filterNoYearState", ThroughTheYears.filterNoYearState)
            xmlFile:setInt("ThroughTheYears.vehicleShopExpirationState", ThroughTheYears.vehicleShopExpirationState)

            for category, value in pairs(ThroughTheYears.typeControlOptions) do
                xmlFile:setInt("ThroughTheYears." .. category .. ".state", value.state)
            end

            xmlFile:save()
            xmlFile:delete()
        end
    end
end

function ThroughTheYears.loadedMission(mission, node)
    log_dbg("loadedMission")

    if mission:getIsServer() then
        local savegameDirectory = mission.missionInfo.savegameDirectory
        local xmlFileName

        if savegameDirectory == nil then
            return
        end

        xmlFileName = mission.missionInfo.savegameDirectory .. "/" .. ThroughTheYears.optionSavegameFileName

        if mission.missionInfo.savegameDirectory ~= nil and fileExists(xmlFileName) then
            local xmlFile = XMLFile.load("ThroughTheYears", xmlFileName)

            if xmlFile ~= nil then
                local settings_version = xmlFile:getInt("ThroughTheYears.SETTINGS_VERSION", 0)

                if settings_version == ThroughTheYears.SETTINGS_VERSION then
                    ThroughTheYears.baseYear = xmlFile:getInt("ThroughTheYears.baseYear",
                        ThroughTheYears.baseYear)
                    ThroughTheYears.baseYearState = xmlFile:getInt("ThroughTheYears.baseYearState",
                        ThroughTheYears.baseYearState)
                    ThroughTheYears.filterNoYearState = xmlFile:getInt("ThroughTheYears.filterNoYearState",
                        ThroughTheYears.filterNoYearState)
                    ThroughTheYears.vehicleShopExpirationState = xmlFile:getInt(
                        "ThroughTheYears.vehicleShopExpirationState",
                        ThroughTheYears.vehicleShopExpirationState)

                    for category, _ in pairs(ThroughTheYears.typeControlOptions) do
                        ThroughTheYears.typeControlOptions[category].state =
                            xmlFile:getInt("ThroughTheYears." .. category .. ".state",
                                ThroughTheYears.typeControlOptions[category].state)
                    end
                else
                    ttyprint(string.format("Loaded settings with version %d but mod uses %d",
                        settings_version, ThroughTheYears.SETTINGS_VERSION))
                end

                xmlFile:delete()
            end
        end
    end

    if mission.cancelLoading then
        return
    end
end

function ThroughTheYears:onBaseYearChanged(state, info, element)
    log_dbg("onBaseYearChanged")

    if ThroughTheYears.baseYearState == state then
        return
    end
    ThroughTheYears.baseYearState = state
    ThroughTheYears.baseYear = tonumber(info.textElement.text)

    log_dbg(string.format("Base Year set to %d", ThroughTheYears.baseYear))

    sendSettingsChangedEvent()
    updateRealCurrentYear(true)
end

local function installBaseYearOption(parentGui)
    log_dbg("installBaseYearOption")

    -- Clone one of the game's default MultiTextOptionElement options ("economicDifficulty") to make life easier
    ThroughTheYears.baseYearOption = parentGui.gameSettingsLayout.elements[5]:clone()

    -- Initialize array used for the options - Use 1950 to the release year of the game
    ThroughTheYears.baseYearOption.yearStringArray = {}
    local loop_cnt = 1
    for year = ThroughTheYears.earliestBaseYear, ThroughTheYears.latestBaseYear do
        ThroughTheYears.baseYearOption.yearStringArray[loop_cnt] = tostring(year)
        loop_cnt = loop_cnt + 1
    end

    -- Text on right side of option
    ThroughTheYears.baseYearOption.elements[1].elements[1]:setText(
        "The base year of the game. Increment to move time forwards or backwards in time manually")
    ThroughTheYears.baseYearOption.elements[1].id = "baseYearOption"
    ThroughTheYears.baseYearOption.elements[1].target = ThroughTheYears
    ThroughTheYears.baseYearOption.elements[1]:setCallback("onClickCallback", "onBaseYearChanged")
    ThroughTheYears.baseYearOption.elements[1]:setTexts(ThroughTheYears.baseYearOption.yearStringArray)
    ThroughTheYears.baseYearOption.elements[1]:setState(ThroughTheYears.baseYearState)

    -- Text on the left side
    ThroughTheYears.baseYearOption.elements[2]:setText("Base Year")

    -- Add the MultiTextOptionElement to the list of options
    parentGui.gameSettingsLayout:addElement(ThroughTheYears.baseYearOption)

    log_dbg("installBaseYearOption completed")
end

function ThroughTheYears:filterNoYearChanged(state, info, element)
    log_dbg("filterNoYearChanged")

    if ThroughTheYears.filterNoYearState == state then
        return
    end
    ThroughTheYears.filterNoYearState = state
    ThroughTheYears.filterNoYear = state == 1

    log_dbg(string.format("Filter no year set to %s", state))

    sendSettingsChangedEvent()
    updateShowInStore(true)
end

local function installNoYearOption(parentGui)
    log_dbg("installNoYearOption")

    ThroughTheYears.filterNoYearOptionStringArray = { "On", "Off" }

    -- Clone one of the game's default MultiTextOptionElement options ("economicDifficulty") to make life easier
    ThroughTheYears.filterNoYearOption = parentGui.gameSettingsLayout.elements[5]:clone()

    -- Text on right side of option
    ThroughTheYears.filterNoYearOption.elements[1].elements[1]:setText(
        "Hide vehicles without known years from the game")
    ThroughTheYears.filterNoYearOption.elements[1].id = "filterNoYearOption"
    ThroughTheYears.filterNoYearOption.elements[1].target = ThroughTheYears
    ThroughTheYears.filterNoYearOption.elements[1]:setCallback("onClickCallback", "filterNoYearChanged")
    ThroughTheYears.filterNoYearOption.elements[1]:setTexts(ThroughTheYears.filterNoYearOptionStringArray)
    ThroughTheYears.filterNoYearOption.elements[1]:setState(ThroughTheYears.filterNoYearState)

    -- Text on the left side
    ThroughTheYears.filterNoYearOption.elements[2]:setText("Hide vehicles with unknown year")

    -- Add the MultiTextOptionElement to the list of options
    parentGui.gameSettingsLayout:addElement(ThroughTheYears.filterNoYearOption)

    log_dbg("installNoYearOption completed")
end
function ThroughTheYears:onVehicleShopExpirationChanged(state, info, element)
    log_dbg("onVehicleShopExpirationChanged")

    if ThroughTheYears.vehicleShopExpirationState == state then
        return
    end

    ThroughTheYears.vehicleShopExpirationState = state
    ThroughTheYears.vehicleShopExpiration = state - 1 -- May become 0 to disable since state == 1 == "Off"

    log_dbg(string.format("Expiration age set to %d", ThroughTheYears.vehicleShopExpiration))

    sendSettingsChangedEvent()
    updateShowInStore(true)
end

local function installVehicleShopExpirationOption(parentGui)
    log_dbg("installVehicleShopExpirationOption")

    -- Clone one of the game's default MultiTextOptionElement options ("economicDifficulty") to make life easier
    ThroughTheYears.vehicleShopExpirationOption = parentGui.gameSettingsLayout.elements[5]:clone()

    ThroughTheYears.vehicleShopExpirationOption.vehicleShopExpirationStringArray = {}
    ThroughTheYears.vehicleShopExpirationOption.vehicleShopExpirationStringArray[1] = "Off"
    for expiration = 1, 50 do
        ThroughTheYears.vehicleShopExpirationOption.vehicleShopExpirationStringArray[expiration + 1] =
            tostring(expiration) .. " years"
    end

    -- Text on right side of option
    ThroughTheYears.vehicleShopExpirationOption.elements[1].elements[1]:setText("Maximum age of vehicles in the shop")
    ThroughTheYears.vehicleShopExpirationOption.elements[1].id = "vehicleShopExpirationOption"
    ThroughTheYears.vehicleShopExpirationOption.elements[1].target = ThroughTheYears
    ThroughTheYears.vehicleShopExpirationOption.elements[1]:setCallback(
        "onClickCallback", "onVehicleShopExpirationChanged")
    ThroughTheYears.vehicleShopExpirationOption.elements[1]:setTexts(
        ThroughTheYears.vehicleShopExpirationOption.vehicleShopExpirationStringArray)
    ThroughTheYears.vehicleShopExpirationOption.elements[1]:setState(ThroughTheYears.vehicleShopExpirationState)

    -- Text on the left side
    ThroughTheYears.vehicleShopExpirationOption.elements[2]:setText("Maximum shop age")

    -- Add the MultiTextOptionElement to the list of options
    parentGui.gameSettingsLayout:addElement(ThroughTheYears.vehicleShopExpirationOption)
    log_dbg("installVehicleShopExpirationOption completed")
end

function ThroughTheYears:onTypeControlChanged(state, info, element)
    log_dbg(string.format("onTypeControlChanged: %s %d", info.id, state))

    if ThroughTheYears.typeControlOptions[info.id].state == state then
        return
    end
    ThroughTheYears.typeControlOptions[info.id].state = state

    sendSettingsChangedEvent()
    updateShowInStore(true)
end

local function installTypeControlOptions(parentGui)
    log_dbg("installTypeControlOptions")

    ThroughTheYears.typeControlStringArray = { "On", "On (No max age)", "Minimum One", "Off" }

    for category, value in TypeControlOptions.pairsByKeys(ThroughTheYears.typeControlOptions) do
        local categoryOption = ThroughTheYears.typeControlOptions[category]

        -- Clone one of the game's default MultiTextOptionElement options ("economicDifficulty") to make life easier
        categoryOption.option = parentGui.gameSettingsLayout.elements[5]:clone()

        -- Set the option element up
        categoryOption.option.elements[1].elements[1]:setText(string.format("Whether %s are affected by game year",
            value.name))
        categoryOption.option.elements[1].id = category
        categoryOption.option.elements[1].target = ThroughTheYears
        categoryOption.option.elements[1]:setCallback("onClickCallback", "onTypeControlChanged")
        categoryOption.option.elements[1]:setTexts(ThroughTheYears.typeControlStringArray)
        categoryOption.option.elements[1]:setState(categoryOption.state)

        -- Text on the left side
        categoryOption.option.elements[2]:setText(value.name)

        -- Add the option element to the list of options
        parentGui.gameSettingsLayout:addElement(categoryOption.option)
    end

    log_dbg("installTypeControlOptions completed")
end

local function installOptions(self)
    log_dbg("installOptions")

    if ThroughTheYears.initOptionGuiDone == false then
        -- self is a GuiElement

        -- Section Header
        local title = self.gameSettingsLayout.elements[7]:clone()
        title:applyProfile("fs25_settingsSectionHeader", true)
        title:setText("Through the Years")
        self.gameSettingsLayout:addElement(title)

        installBaseYearOption(self)
        installNoYearOption(self)
        installVehicleShopExpirationOption(self)
        installTypeControlOptions(self)
        self.gameSettingsLayout:invalidateLayout()

        log_dbg("installOptions completed")

        ThroughTheYears.initOptionGuiDone = true
    end
end

local function storeScaledValues(self)
    log_dbg("storeScaledValues")

    self.calendarTextOffsetY = self.calendarTextOffsetY + self:scalePixelToScreenHeight(7)

    local _, gameInfoDisplayPosY = self:getPosition()
    local posY = gameInfoDisplayPosY - self.calendarTextOffsetY - self:scalePixelToScreenHeight(10)

    ThroughTheYears.yearTextPosY = posY
    ThroughTheYears.yearTextSize = self:scalePixelToScreenHeight(14) -- Slightly smaller than the default text size of 17
end

local function drawYearText(self)
    -- The values used to calculate posX is not properly available until this callback
    local posX = self.calendarIcon.x + self.calendarIcon.width --  + self:scalePixelToScreenWidth(2)

    ThroughTheYears.yearTextPosX = posX

    renderText(ThroughTheYears.yearTextPosX, ThroughTheYears.yearTextPosY, ThroughTheYears.yearTextSize,
        tostring(ThroughTheYears.realCurrentYear))
end

local function addSale(self, itemAdded)
    log_dbg("addSale")
    if ThroughTheYears.verboseDebugEnabled then
        dump(itemAdded)
    end
    -- self is VehicleSaleSystem

    local item_removed = false

    if itemAdded ~= nil and not saleItemMatchesYear(itemAdded) then
        table.removeElement(g_currentMission.vehicleSaleSystem.items, itemAdded)
        item_removed = true

        -- Try up to 100 times to generate a new sale item
        for i = 1, 100 do
            local random_vehicle = self:generateRandomVehicle()
            if random_vehicle ~= nil and saleItemMatchesYear(random_vehicle) then
                table.addElement(g_currentMission.vehicleSaleSystem.items, random_vehicle)
                item_removed = false
                if ThroughTheYears.verboseDebugEnabled then
                    log_dbg("Sale replaced with")
                    dump(random_vehicle)
                end
                break
            end
        end

        if item_removed then
            log_dbg("Could not generate new sale item")
        end
    end

    if item_removed then
        g_currentMission.vehicleSaleSystem.numGeneratedItems =
            g_currentMission.vehicleSaleSystem.numGeneratedItems - 1
    end
end

function ThroughTheYears:getIsItemUnlocked(superFunc, storeItem)
    log_dbg("getIsItemUnlocked")

    -- For some reason the original getIsItemUnlocked function does not take showInStore into
    -- account, so we add that check here
    return superFunc(self, storeItem) and storeItem.showInStore
end

function ThroughTheYears:onFinishedLoading()
    log_dbg("onFinishedLoading")

    addShowInStoreCopy()
    -- Call the updateShowInStore to load all the data and set initial data
    -- We call it here during the last stages of loading so that when the player presses "Start"
    -- most of the work has already been done.
    updateShowInStore(false)
    ThroughTheYears.initian_load_done = true
end

function ThroughTheYears:onStartMission()
    log_dbg("onStartMission")

    -- Call the updateRealCurrentYear to set the current year when mission is started, since this is
    -- the first time where the g_currentMission.environment.currentYear is properly set
    updateGameCurrentYear()
    updateRealCurrentYear(false)
end
function ThroughTheYears:init()
    if ThroughTheYears.modActivated == nil then
        ThroughTheYears.debugEnabled = false        -- Set to true to enable debug printing
        ThroughTheYears.verboseDebugEnabled = false -- Set to true to enable verbose debug printing

        log_dbg("Initializing")

        g_messageCenter:subscribe(MessageType.PERIOD_CHANGED, periodChanged, self)

        ThroughTheYears.modActivated = true
        ThroughTheYears.initOptionGuiDone = false

        -- Options
        ThroughTheYears.earliestBaseYear = 1950
        ThroughTheYears.latestBaseYear = 2025                              -- Release year of the game
        ThroughTheYears.realCurrentYear = ThroughTheYears.earliestBaseYear -- Updated on load
        ThroughTheYears.baseYear = ThroughTheYears.earliestBaseYear
        ThroughTheYears.baseYearState = 1
        ThroughTheYears.filterNoYearState = 1 -- "On"
        ThroughTheYears.filterNoYear = true
        ThroughTheYears.vehicleShopExpirationState = 21
        ThroughTheYears.vehicleShopExpiration = 20
        ThroughTheYears.typeControlOptions = TypeControlOptions.getDefaultOptions()
        ThroughTheYears.SETTINGS_VERSION = 1

        -- Install our options when the settings are opened
        InGameMenuSettingsFrame.onFrameOpen = Utils.appendedFunction(
            InGameMenuSettingsFrame.onFrameOpen, installOptions)

        ThroughTheYears.optionSavegameFileName = "ThroughTheYears.xml"

        VehicleSaleSystem.addSale = Utils.appendedFunction(VehicleSaleSystem.addSale, addSale)

        FSCareerMissionInfo.saveToXMLFile = Utils.appendedFunction(
            FSCareerMissionInfo.saveToXMLFile, ThroughTheYears.saveToXMLFile)

        -- This needs to be overwritten rather than appended to handle the return value
        StoreManager.getIsItemUnlocked = Utils.overwrittenFunction(
            StoreManager.getIsItemUnlocked, ThroughTheYears.getIsItemUnlocked)

        -- We have to wait until the map has finished loading to avoid modifying the store before
        -- the StoreManager has finished loading
        FSBaseMission.onStartMission = Utils.appendedFunction(
            FSBaseMission.onStartMission, ThroughTheYears.onStartMission)

        FSBaseMission.onFinishedLoading = Utils.appendedFunction(
            FSBaseMission.onFinishedLoading, ThroughTheYears.onFinishedLoading)

        Mission00.loadMission00Finished = Utils.appendedFunction(
            Mission00.loadMission00Finished, ThroughTheYears.loadedMission)

        MissionManager.addMission = Utils.appendedFunction(MissionManager.addMission, addMission)
        FSBaseMission.onConnectionFinishedLoading = Utils.appendedFunction(
            FSBaseMission.onConnectionFinishedLoading, ThroughTheYears.loadSettingsForClient)

        -- Render the current year in the top HUD display
        GameInfoDisplay.storeScaledValues = Utils.appendedFunction(GameInfoDisplay.storeScaledValues,
            storeScaledValues)
        GameInfoDisplay.draw = Utils.appendedFunction(GameInfoDisplay.draw, drawYearText)

        log_dbg("Initialized")
    end
end

ThroughTheYears:init()
