--
-- PrecisionFarming
--
-- @author Stefan Maurus
-- @date 26/06/2020
--
-- Copyright (C) GIANTS Software GmbH, Confidential, All Rights Reserved.


PrecisionFarming = {}
PrecisionFarming.MOD_NAME = g_currentModName
PrecisionFarming.BASE_DIRECTORY = g_currentModDirectory

source(PrecisionFarming.BASE_DIRECTORY .. "scripts/UsageBuffer.lua")
source(PrecisionFarming.BASE_DIRECTORY .. "scripts/AdditionalPlaceableTypes.lua")
source(PrecisionFarming.BASE_DIRECTORY .. "scripts/AdditionalSpecializations.lua")

source(PrecisionFarming.BASE_DIRECTORY .. "scripts/events/AdditionalFieldBuyInfoEvent.lua")
source(PrecisionFarming.BASE_DIRECTORY .. "scripts/events/ExtendedSprayerAmountEvent.lua")
source(PrecisionFarming.BASE_DIRECTORY .. "scripts/events/FarmlandStatisticsEvent.lua")
source(PrecisionFarming.BASE_DIRECTORY .. "scripts/events/FarmlandStatisticsResetEvent.lua")
source(PrecisionFarming.BASE_DIRECTORY .. "scripts/events/RequestFarmlandStatisticsEvent.lua")
source(PrecisionFarming.BASE_DIRECTORY .. "scripts/events/RequestFieldBuyInfoEvent.lua")
source(PrecisionFarming.BASE_DIRECTORY .. "scripts/events/SoilSamplerStartEvent.lua")
source(PrecisionFarming.BASE_DIRECTORY .. "scripts/events/SoilSamplerSendEvent.lua")

source(PrecisionFarming.BASE_DIRECTORY .. "scripts/maps/ValueMap.lua")
source(PrecisionFarming.BASE_DIRECTORY .. "scripts/maps/SharedSoilStateMap.lua")
source(PrecisionFarming.BASE_DIRECTORY .. "scripts/maps/SoilMap.lua")
source(PrecisionFarming.BASE_DIRECTORY .. "scripts/maps/YieldMap.lua")
source(PrecisionFarming.BASE_DIRECTORY .. "scripts/maps/PHMap.lua")
source(PrecisionFarming.BASE_DIRECTORY .. "scripts/maps/NitrogenMap.lua")

source(PrecisionFarming.BASE_DIRECTORY .. "scripts/IngameMapExtension.lua")
source(PrecisionFarming.BASE_DIRECTORY .. "scripts/AIExtension.lua")
source(PrecisionFarming.BASE_DIRECTORY .. "scripts/FieldInfoDisplayExtension.lua")
source(PrecisionFarming.BASE_DIRECTORY .. "scripts/HarvestExtension.lua")
source(PrecisionFarming.BASE_DIRECTORY .. "scripts/HelplineExtension.lua")
source(PrecisionFarming.BASE_DIRECTORY .. "scripts/AdditionalFieldBuyInfo.lua")

source(PrecisionFarming.BASE_DIRECTORY .. "scripts/statistics/FarmlandStatistics.lua")
source(PrecisionFarming.BASE_DIRECTORY .. "scripts/statistics/FarmlandStatistic.lua")
source(PrecisionFarming.BASE_DIRECTORY .. "scripts/statistics/FarmlandStatisticCounter.lua")

local PrecisionFarming_mt = Class(PrecisionFarming)

function PrecisionFarming.new(customMt)
    local self = setmetatable({}, customMt or PrecisionFarming_mt)

    self.overwrittenGameFunctions = {}
    self.valueMaps = {}

    self:registerValueMap(SoilMap.new(self))
    self:registerValueMap(PHMap.new(self))
    self:registerValueMap(NitrogenMap.new(self))
    self:registerValueMap(YieldMap.new(self))

    self:registerValueMap(SharedSoilStateMap.new(self))

    self.ingameMapExtension = IngameMapExtension.new()
    self.aiExtension = AIExtension.new()
    self.fieldInfoDisplayExtension = FieldInfoDisplayExtension.new()
    self.harvestExtension = HarvestExtension.new(self)
    self.helplineExtension = HelplineExtension.new(self)
    self.farmlandStatistics = FarmlandStatistics.new(self)
    self.additionalFieldBuyInfo = AdditionalFieldBuyInfo.new(self)

    self.firstTimeRun = false
    self.firstTimeRunDelay = 2000

    return self
end

function PrecisionFarming:initialize()
    for i=1, #self.valueMaps do
        self.valueMaps[i]:initialize(self)
        self.valueMaps[i]:overwriteGameFunctions(self)
    end

    self.ingameMapExtension:overwriteGameFunctions(self)
    self.aiExtension:overwriteGameFunctions(self)
    self.fieldInfoDisplayExtension:overwriteGameFunctions(self)
    self.harvestExtension:overwriteGameFunctions(self)
    self.helplineExtension:overwriteGameFunctions(self)
    self.farmlandStatistics:overwriteGameFunctions(self)
    self.additionalFieldBuyInfo:overwriteGameFunctions(self)

    self.registeredI18NTexts = {}
    self:registerI18N()
end

function PrecisionFarming:loadMap(filename)
    if g_modIsLoaded[PrecisionFarming.MOD_NAME] then
        if not Utils.getNoNil(getXMLBool(g_savegameXML, "gameSettings.precisionFarming#initialized"), false) then
            self.firstTimeRun = true
            setXMLBool(g_savegameXML, "gameSettings.precisionFarming#initialized", true)
            g_gameSettings:saveToXMLFile(g_savegameXML)
        end

        self.mapFilename = filename
        self.configFileName = Utils.getFilename("PrecisionFarming.xml", PrecisionFarming.BASE_DIRECTORY)
        local xmlFile = loadXMLFile("ConfigXML", self.configFileName)

        for i=1, #self.valueMaps do
            self.valueMaps[i]:loadFromXML(xmlFile, "precisionFarming", PrecisionFarming.BASE_DIRECTORY, self.configFileName, filename)
        end

        for i=1, #self.valueMaps do
            self.valueMaps[i]:postLoad(xmlFile, "precisionFarming", PrecisionFarming.BASE_DIRECTORY, self.configFileName, filename)
        end

        self.ingameMapExtension:loadFromXML(xmlFile, "precisionFarming", PrecisionFarming.BASE_DIRECTORY, self.configFileName, filename)
        self.aiExtension:loadFromXML(xmlFile, "precisionFarming.aiExtension", PrecisionFarming.BASE_DIRECTORY, self.configFileName, filename)
        self.fieldInfoDisplayExtension:loadFromXML(xmlFile, "precisionFarming.fieldInfoDisplayExtension", PrecisionFarming.BASE_DIRECTORY, self.configFileName, filename)
        self.harvestExtension:loadFromXML(xmlFile, "precisionFarming.harvestExtension", PrecisionFarming.BASE_DIRECTORY, self.configFileName, filename)
        self.helplineExtension:loadFromXML(xmlFile, "precisionFarming.helplineExtension", PrecisionFarming.BASE_DIRECTORY, self.configFileName, filename)
        self.farmlandStatistics:loadFromXML(xmlFile, "precisionFarming.farmlandStatistics", PrecisionFarming.BASE_DIRECTORY, self.configFileName, filename)
        self.additionalFieldBuyInfo:loadFromXML(xmlFile, "precisionFarming.additionalFieldBuyInfo", PrecisionFarming.BASE_DIRECTORY, self.configFileName, filename)

        delete(xmlFile)
    end
end

function PrecisionFarming:unloadMapData()
    self.ingameMapExtension:unloadMapData()
end

function PrecisionFarming:initTerrain(mission, terrainId, filename)
    for i=1, #self.valueMaps do
        self.valueMaps[i]:initTerrain(mission, terrainId, filename)
    end
end

function PrecisionFarming:deleteMap()
    if g_modIsLoaded[PrecisionFarming.MOD_NAME] then
        for i=1, #self.valueMaps do
            self.valueMaps[i]:delete()
        end

        self.ingameMapExtension:delete()
        self.aiExtension:delete()
        self.fieldInfoDisplayExtension:delete()
        self.harvestExtension:delete()
        self.helplineExtension:delete()
        self.farmlandStatistics:delete()
        self.additionalFieldBuyInfo:delete()

        for i=#self.overwrittenGameFunctions, 1, -1 do
            local reference = self.overwrittenGameFunctions[i]
            reference.object[reference.funcName] = reference.oldFunc

            self.overwrittenGameFunctions[i] = nil
        end

        -- remove texts again
        local realI18N = getfenv(0)["g_i18n"]
        for name, _ in pairs(self.registeredI18NTexts) do
            realI18N:setText(name, nil)
        end
    end
end

function PrecisionFarming:loadFromItemsXML(xmlFile, key)
    for i=#self.valueMaps, 1, -1 do
        self.valueMaps[i]:loadFromItemsXML(xmlFile, key)
    end

    self.farmlandStatistics:loadFromItemsXML(xmlFile, key)
    self.additionalFieldBuyInfo:loadFromItemsXML(xmlFile, key)
end

function PrecisionFarming:saveToXMLFile(xmlFile, key, usedModNames)
    for i=1, #self.valueMaps do
        self.valueMaps[i]:saveToXMLFile(xmlFile, key, usedModNames)
    end

    self.farmlandStatistics:saveToXMLFile(xmlFile, key, usedModNames)
    self.additionalFieldBuyInfo:saveToXMLFile(xmlFile, key, usedModNames)
end

function PrecisionFarming:update(dt)
    if g_modIsLoaded[PrecisionFarming.MOD_NAME] then
        for i=1, #self.valueMaps do
            self.valueMaps[i]:update(dt)
        end

        self.ingameMapExtension:update(dt)
        self.farmlandStatistics:update(dt)
        self.additionalFieldBuyInfo:update(dt)
        self.harvestExtension:update(dt)

        if self.firstTimeRun then
            self.firstTimeRunDelay = math.max(self.firstTimeRunDelay - dt, 0)
            if self.firstTimeRunDelay == 0 then
                if self.helplineExtension:getAllowFirstTimeEvent() then
                    self.helplineExtension:onFirstTimeRun()
                    self.firstTimeRun = false
                end
            end
        end
    end
end

function PrecisionFarming:registerI18N()
    local list = {}
    self.ingameMapExtension:getGlobalI18N(list)
    self.farmlandStatistics:getGlobalI18N(list)
    self.additionalFieldBuyInfo:getGlobalI18N(list)
    self.helplineExtension:getGlobalI18N(list)

    for i=1, #self.valueMaps do
        self.valueMaps[i]:getGlobalI18N(list)
    end

    local realI18N = getfenv(0)["g_i18n"]
    for i=1, #list do
        local text = g_i18n:getText(list[i], PrecisionFarming.MOD_NAME)
        if realI18N.texts[list[i]] == nil then
            realI18N:setText(list[i], text)
            self.registeredI18NTexts[list[i]] = true
        end
    end
end

function PrecisionFarming:registerValueMap(object)
    table.insert(self.valueMaps, object)
    self[object.name] = object
    object.valueMapIndex = #self.valueMaps
end

function PrecisionFarming:getValueMaps()
    return self.valueMaps
end

function PrecisionFarming:getValueMap(index)
    return self.valueMaps[index]
end

function PrecisionFarming:updatePrecisionFarmingOverlays()
    self.ingameMapExtension:updatePrecisionFarmingOverlays()
end

function PrecisionFarming:onValueMapSelectionChanged(valueMap)
    self.yieldMap:onValueMapSelectionChanged(valueMap)
end

function PrecisionFarming:onFarmlandSelectionChanged(farmlandId, fieldNumber, fieldArea)
    self.yieldMap:onFarmlandSelectionChanged(farmlandId, fieldNumber, fieldArea)
end

function PrecisionFarming:collectFieldInfos(fieldInfoDisplayExtension)
    for i=1, #self.valueMaps do
        self.valueMaps[i]:collectFieldInfos(fieldInfoDisplayExtension)
    end
end

function PrecisionFarming:getIsSeasonsActive()
    return g_modIsLoaded["FS19_RM_Seasons"] or g_modIsLoaded["FS19_RM_Seasons_console"]
end

function PrecisionFarming:overwriteGameFunction(object, funcName, newFunc)
    local oldFunc = object[funcName]
    if oldFunc ~= nil then
        object[funcName] = function(...)
            return newFunc(oldFunc, ...)
        end
    end

    local reference = {}
    reference.object = object
    reference.funcName = funcName
    reference.oldFunc = oldFunc

    table.insert(self.overwrittenGameFunctions, reference)
end

g_precisionFarming = PrecisionFarming.new()
addModEventListener(g_precisionFarming)

--
--  Initialize the mod while validating vehicle types so we have everything loaded
--
local function validateVehicleTypes()
    if g_modIsLoaded[PrecisionFarming.MOD_NAME] then
        g_precisionFarming:initialize()
    end
end
VehicleTypeManager.validateVehicleTypes = Utils.prependedFunction(VehicleTypeManager.validateVehicleTypes, validateVehicleTypes)

--
--  Save/load data to/from top of items xml
--
local function saveItems(mission, xmlFile, key, usedModNames)
    if g_modIsLoaded[PrecisionFarming.MOD_NAME] then
        g_precisionFarming:saveToXMLFile(xmlFile, key .. ".precisionFarming", usedModNames)
    end
end
Mission00.saveItems = Utils.prependedFunction(Mission00.saveItems, saveItems)

local function loadItems(mission, xmlFilename)
    if g_modIsLoaded[PrecisionFarming.MOD_NAME] then
        local xmlFile = loadXMLFile("itemsXML", xmlFilename)
        g_precisionFarming:loadFromItemsXML(xmlFile, "items.precisionFarming")
        delete(xmlFile)
    end
end
Mission00.loadItems = Utils.prependedFunction(Mission00.loadItems, loadItems)

--
--  unloadMapData to remove PF UI elements before unloadMapData is called on the UI elements
--
local function unloadMapData(mission, xmlFilename)
    if g_modIsLoaded[PrecisionFarming.MOD_NAME] then
        g_precisionFarming:unloadMapData()
    end
end
Gui.unloadMapData = Utils.prependedFunction(Gui.unloadMapData, unloadMapData)

--
--  Hook onto init terrain to register our bit vectors to the density map syncer
--
local function postInitTerrain(mission, terrainId, filename)
    if g_modIsLoaded[PrecisionFarming.MOD_NAME] then
        g_precisionFarming:initTerrain(mission, terrainId, filename)
    end
end
FSBaseMission.initTerrain = Utils.appendedFunction(FSBaseMission.initTerrain, postInitTerrain)
