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


IngameMapExtension = {}

IngameMapExtension.MOD_NAME = g_currentModName
IngameMapExtension.MOD_DIR = g_currentModDirectory
IngameMapExtension.GUI_ELEMENTS = PrecisionFarming.BASE_DIRECTORY .. "gui/ui_elements.png"
IngameMapExtension.GUI_ELEMENTS_SIZE = {1024, 1024}
IngameMapExtension.GUI_EXT_XML = PrecisionFarming.BASE_DIRECTORY .. "gui/IngameMapExtension.xml"

IngameMapExtension.NUM_BASE_OVERLAY_TYPES = 4 -- fruit, growth, soil, farmland
IngameMapExtension.NUM_BASE_MAPS = 3 -- fruit, growth, soil

local IngameMapExtension_mt = Class(IngameMapExtension)

function IngameMapExtension.new(customMt)
    local self = setmetatable({}, customMt or IngameMapExtension_mt)

    self.inGameMenuMapFrame = nil

    g_gui:loadProfiles(PrecisionFarming.BASE_DIRECTORY .. "gui/guiProfiles.xml")

    for _, profile in pairs(g_gui.profiles) do
        for name, value in pairs(profile.values) do
            if (name == "imageFilename" or name == "iconFilename") and value == "g_paUIElements" then
                profile.values[name] = IngameMapExtension.GUI_ELEMENTS
                profile.values["imageSize"] = IngameMapExtension.GUI_ELEMENTS_SIZE[1] .. " " .. IngameMapExtension.GUI_ELEMENTS_SIZE[2]
            end
        end
    end

    local uiScale = g_gameSettings:getValue("uiScale")

    local _
    self.mapLabelOffsetX, self.mapLabelOffsetY = getNormalizedScreenValues(5 * uiScale, 4 * uiScale)
    _, self.mapLabelTextSize = getNormalizedScreenValues(0, 15 * uiScale)

    local width, height = getNormalizedScreenValues(120 * uiScale, 6 * uiScale)
    local gradientOverlay = Overlay:new(IngameMapExtension.GUI_ELEMENTS, 0, 0, width, height)
    gradientOverlay:setUVs(g_colorBgUVs)
    gradientOverlay:setColor(1, 1, 1, 1)
    self.gradientElement = HUDElement:new(gradientOverlay)

    width, height = getNormalizedScreenValues(122 * uiScale, 8 * uiScale)
    local gradientOverlayBackground = Overlay:new(g_baseUIFilename, 0, 0, width, height)
    gradientOverlayBackground:setUVs(g_colorBgUVs)
    gradientOverlayBackground:setColor(0, 0, 0, 0.6)
    self.gradientBackgroundElement = HUDElement:new(gradientOverlayBackground)

    self.gradientOffsetX, self.gradientOffsetY = getNormalizedScreenValues(10 * uiScale, 10 * uiScale)

    return self
end

function IngameMapExtension:getGlobalI18N(list)
    table.insert(list, "ui_precisionFarming_header")
    table.insert(list, "ui_precisionFarming_help")
end

function IngameMapExtension:loadFromXML(xmlFile, key, baseDirectory, configFileName, mapFilename)
end

function IngameMapExtension:unloadMapData()
    if self.inGameMenuMapFrame ~= nil then
        local mapFrame = self.inGameMenuMapFrame
        if mapFrame ~= nil then
            -- remove additional ui elements
            if mapFrame.precisionFarmingContainer ~= nil then
                mapFrame.precisionFarmingContainer:delete()
                mapFrame.precisionFarmingContainer = nil
            end

            if mapFrame.precisionFarmingAdditionalContainer ~= nil then
                mapFrame.precisionFarmingAdditionalContainer:delete()
                mapFrame.precisionFarmingAdditionalContainer = nil
            end

            mapFrame.buttonSwitchMapMode:setVisible(true)
            mapFrame.isPrecisionFarmingPage = nil
            mapFrame:setupMapOverview()
        end

        -- remove PA page from tabbed menu
        local ingameMenu = g_gui.screenControllers[InGameMenu]
        if ingameMenu.pAgController ~= nil then
            local pageTab = ingameMenu.pageTabs[ingameMenu.pAgController]
            ingameMenu.pagingTabList:removeElement(pageTab)
            pageTab:delete()

            ingameMenu.currentPageId = next(ingameMenu.pagingElement.pages)
            ingameMenu.pagingElement.currentPageIndex = ingameMenu.currentPageId
            local pageRoot = ingameMenu.pageRoots[ingameMenu.pAgController]
            ingameMenu.pagingElement:removeElement(pageRoot)

            ingameMenu:unregisterPage(ingameMenu.pAgController:class())
            ingameMenu.pAgController = nil
        end
    end
end

function IngameMapExtension:delete()
end

function IngameMapExtension:updatePrecisionFarmingOverlays()
    if self.inGameMenuMapFrame ~= nil then
        self.inGameMenuMapFrame:generateOverviewOverlay()
    end
end

function IngameMapExtension:update(dt)
    local inputHelpMode = g_inputBinding:getInputHelpMode()
    if inputHelpMode ~= self.lastInputHelpMode then
        self.lastInputHelpMode = inputHelpMode

        if self.inGameMenuMapFrame ~= nil then
            IngameMapExtension.updateButtonOnInputHelpChange(self.inGameMenuMapFrame.helpButton, "ingameMenuPrecisionFarmingHelpButtonConsole", "ingameMenuPrecisionFarmingHelpButton")
        end
    end
end

function IngameMapExtension:overwriteGameFunctions(pfModule)
    pfModule:overwriteGameFunction(MapOverlayGenerator, "new", function(superFunc, self, l10n, fruitTypeManager, fillTypeManager, farmlandManager, farmManager)
        self = superFunc(self, l10n, fruitTypeManager, fillTypeManager, farmlandManager, farmManager)

        local valueMaps = pfModule:getValueMaps()
        for i=1, #valueMaps do
            local valueMap = valueMaps[i]
            if valueMap:getShowInMenu() then
                local func = function(mapOverlayGenerator, filter, ignoreFarmlandSelection, overlayToUse)
                    valueMap:buildOverlay(overlayToUse or mapOverlayGenerator.farmlandStateOverlay, filter, mapOverlayGenerator.isColorBlindMode)
                    if overlayToUse == mapOverlayGenerator.pa_minimapOverlay then
                        mapOverlayGenerator.lastFarmlandStateOverlayValueMap = valueMap
                        mapOverlayGenerator.lastFarmlandStateOverlayValueMapFilter = filter
                    end

                    if not ignoreFarmlandSelection then
                        if pfModule.farmlandStatistics ~= nil then
                            pfModule.farmlandStatistics:buildOverlay(overlayToUse or mapOverlayGenerator.farmlandStateOverlay)
                        end
                    end
                end

                self.typeBuilderFunctionMap[IngameMapExtension.NUM_BASE_OVERLAY_TYPES + i] = func
                self.overlayHandles[IngameMapExtension.NUM_BASE_OVERLAY_TYPES + i] = self.farmlandStateOverlay

                MapOverlayGenerator.OVERLAY_TYPE[valueMap:getId()] = IngameMapExtension.NUM_BASE_OVERLAY_TYPES + i
                self.overlayTypeCheckHash[IngameMapExtension.NUM_BASE_OVERLAY_TYPES + i] = valueMap:getId()
            end
        end

        return self
    end)

    pfModule:overwriteGameFunction(MapOverlayGenerator, "buildSoilStateMapOverlay", function(superFunc, self, soilStateFilter)
        soilStateFilter[MapOverlayGenerator.SOIL_STATE_INDEX.NEEDS_LIME] = false
        soilStateFilter[MapOverlayGenerator.SOIL_STATE_INDEX.FERTILIZED] = false

        superFunc(self, soilStateFilter)
    end)

    pfModule:overwriteGameFunction(MapOverlayGenerator, "getDisplaySoilStates", function(superFunc, self)
        local displayValues = superFunc(self)

        displayValues[MapOverlayGenerator.SOIL_STATE_INDEX.FERTILIZED].description = g_i18n:getText("soilStates_replaced", IngameMapExtension.MOD_NAME)
        displayValues[MapOverlayGenerator.SOIL_STATE_INDEX.NEEDS_LIME].description = g_i18n:getText("soilStates_replaced", IngameMapExtension.MOD_NAME)

        return displayValues
    end)

    pfModule:overwriteGameFunction(InGameMenuMapFrame, "setupMapOverview", function(superFunc, self)
        -- remove all additional fruit type boxes here since the basegame executes this only one time while in the mission, so there is no need to clear
        -- so we have to clear this every time we recreate the overview for the PF page
        for i=#self.mapOverviewFruitTypeBox, 2, -1 do
            self.mapOverviewFruitTypeBox[i]:delete()
            table.remove(self.mapOverviewFruitTypeBox, i)
        end

        pfModule.ingameMapExtension.inGameMenuMapFrame = self
        superFunc(self)
        self.pf_numBaseMaps = #self.mapSelectorMapping

        if self.isPrecisionFarmingPage then
            self.mapSelectorMapping = {}
            self.mapSelectorTexts = {}

            local valueMaps = g_precisionFarming:getValueMaps()
            for i=1, #valueMaps do
                local valueMap = valueMaps[i]
                if valueMap:getShowInMenu() then
                    table.insert(self.mapSelectorMapping, self.pf_numBaseMaps + i)
                    table.insert(self.mapSelectorTexts, valueMap:getOverviewLabel())
                end
            end

            self.mapOverviewSelector:setTexts(self.mapSelectorTexts)
        end
    end)

    pfModule:overwriteGameFunction(InGameMenuMapFrame, "generateOverviewOverlay", function(superFunc, self)
        local currentPageMappingIndex = self.filterPaging.currentPageMappingIndex
        if self.isPrecisionFarmingPage and not self.isChangingState then
            self.isChangingState = true
            self:onClickMapOverviewSelector(currentPageMappingIndex + self.pf_numBaseMaps)
            self.filterPaging.currentPageMappingIndex = currentPageMappingIndex
            self.isChangingState = false

            self.lastPrecisionFarmingMapSelectorState = self.mapOverviewSelector:getState()

            local valueMaps = pfModule:getValueMaps()
            pfModule:onValueMapSelectionChanged(valueMaps[self.lastPrecisionFarmingMapSelectorState])
        elseif not self.isPrecisionFarmingPage then
            self.lastDefaultMapSelectorState = currentPageMappingIndex
        end

        pfModule.ingameMapExtension.inGameMenuMapFrame = self
        superFunc(self)

        if self.isMapOverviewInitialized then
            local state = self.mapOverviewSelector:getState()
            local currentMap = self.mapSelectorMapping[state]

            local valueMaps = g_precisionFarming:getValueMaps()
            local index = currentMap - self.pf_numBaseMaps
            if index > 0 then
                local valueMap = valueMaps[index]
                if valueMap ~= nil and valueMap:getShowInMenu() then
                    local filter = valueMap:getValueFilter()
                    self.mapOverlayGenerator:generateOverlay(IngameMapExtension.NUM_BASE_OVERLAY_TYPES + index, self.overviewOverlayFinishedCallback, filter)
                end
            end
        end

        self:assignFilterData()
    end)

    -- update filter page state and toggle the additional PA ui elements
    pfModule:overwriteGameFunction(InGameMenuMapFrame, "onFrameOpen", function(superFunc, self)
        InGameMenuMapFrame:superClass().onFrameOpen(self)

        -- update callback function, so we can overwrite the callback
        self.ingameMap["onClickMapCallback"] = self.ingameMap.target["onClickMap"]

        self:toggleMapInput(true) -- disabled by onFrameClose()
        self.ingameMap:onOpen()
        self.ingameMap:registerActionEvents() -- call only after toggleMapInput to have the right input context set
        self:disableAlternateBindings() -- clear alternate UI navigation bindings so we can tab through hotspots with D-Pad

        self.mapOverviewZoom = 1
        self.mapOverviewCenterX = 0.5
        self.mapOverviewCenterY = 0.5

        self.mode = InGameMenuMapFrame.MODE_OVERVIEW -- force overview mode on open
        self.filterBox:setVisible(true)
        self.farmlandValueBox:setVisible(false)

        if self.visible and not self.isMapOverviewInitialized then -- only initialize when the page is opened for display
            self:setupMapOverview()
            self:assignFilterData()
            -- self:selectFirstHotspot()
            self.mapOverviewSelector:setState(1)
        else
            self:setMapSelectionItem(self.currentHotspot) -- update selection and its context input state
        end

        self:initializeFilterButtonState()

        -- restore last selector state
        local state = 1
        if self.isPrecisionFarmingPage then
            state = self.lastPrecisionFarmingMapSelectorState or state
        else
            state = self.lastDefaultMapSelectorState or state
        end

        self.mapOverviewSelector:setState(state)
        self:assignFilterData()
        self:onClickMapOverviewSelector(state)

        self:setMapSelectionItem(nil)

        FocusManager:setFocus(self.mapOverviewSelector)
        self:updateInputGlyphs()

        if self.precisionFarmingContainer ~= nil then
            self.precisionFarmingContainer:setVisible(self.isPrecisionFarmingPage)
        end
    end)

    pfModule:overwriteGameFunction(InGameMenuMapFrame, "onLoadMapFinished", function(superFunc, self)
        pfModule.ingameMapExtension.inGameMenuMapFrame = self
        superFunc(self)

        -- load additional ui file into uiInGameMenuMapFrame
        local xmlFile = loadXMLFile("Temp", IngameMapExtension.GUI_EXT_XML)
        if xmlFile ~= nil and xmlFile ~= 0 then
            if #self.elements > 0 then
                local mapFrame = self.elements[1].elements[4]
                if mapFrame ~= nil and mapFrame.profile == "uiInGameMenuMapFrame" then
                    self.onClickButtonResetStats = function(...)
                        if pfModule.farmlandStatistics ~= nil then
                            pfModule.farmlandStatistics:onClickButtonResetStats()
                        end
                    end

                    self.onClickButtonSwitchValues = function(...)
                        if pfModule.farmlandStatistics ~= nil then
                            pfModule.farmlandStatistics:onClickButtonSwitchValues()
                        end
                    end

                    self.onClickButtonHelp = function(...)
                        if pfModule.helplineExtension ~= nil then
                            local valueMaps = pfModule:getValueMaps()
                            local valueMap = valueMaps[self.lastPrecisionFarmingMapSelectorState]
                            if valueMap ~= nil then
                                pfModule.helplineExtension:openHelpMenu(valueMap:getHelpLinePage())
                            else
                                pfModule.helplineExtension:openHelpMenu(0)
                            end

                            return true
                        end

                        return false
                    end

                    self.onClickButtonResetYield = function(...)
                        if pfModule.yieldMap ~= nil then
                            return pfModule.yieldMap:onClickButtonResetYield()
                        end

                        return false
                    end

                    local controls = {LABORATORY_INFO_TEXT = "laboratoryInfoText",
                                      LABORATORY_WINDOW = "laboratoryWindow",
                                      ECONOMIC_ANALYSIS_WINDOW = "economicAnalysisWindow",
                                      ECONOMIC_ANALYSIS_HEADER_FIELD = "economicAnalysisHeaderField",
                                      ECONOMIC_ANALYSIS_HEADER_VALUES = "economicAnalysisHeaderValues",
                                      STATS_SWITCH_BUTTON = "buttonSwitchValues",
                                      STATS_RESET_BUTTON = "buttonResetStats",
                                      STATS_AMOUNT_TEXT = "statAmountText",
                                      STATS_COST_TEXT = "statCostText",
                                      STATS_PERCENTAGE_TEXT = "statPercentageText",
                                      STATS_TOTAL_COST_TEXT = "statTotalCostText",
                                      STATS_TOTAL_COST_PCT_TEXT = "statTotalCostPercentageText",
                                      STATS_TOTAL_EARNINGS_TEXT = "statTotalEarningsText",
                                      STATS_TOTAL_EARNINGS_PCT_TEXT = "statTotalEarningsPercentageText",
                                      STATS_TOTAL_TEXT = "statTotalText",
                                      STATS_TOTAL_PCT_TEXT = "statTotalPercentageText",
                                      FIELD_BUY_WINDOW = "fieldBuyInfoWindow",
                                      FIELD_BUY_HEADER = "fieldBuyHeader",
                                      FIELD_BUY_SOIL_NAME_TXT = "soilNameText",
                                      FIELD_BUY_SOIL_PCT_TXT = "soilPercentageText",
                                      FIELD_BUY_SOIL_PCT_BAR = "soilPercentageBar",
                                      FIELD_BUY_YIELD_PCT_TXT = "yieldPercentageText",
                                      FIELD_BUY_YIELD_PCT_BAR_BASE = "yieldPercentageBarBase",
                                      FIELD_BUY_YIELD_PCT_BAR_POS = "yieldPercentageBarPos",
                                      FIELD_BUY_YIELD_PCT_BAR_NEG = "yieldPercentageBarNeg",
                                      RESET_YIELD_BUTTON_BACKGROUND = "resetYieldButtonBackground",
                                      RESET_YIELD_BUTTON = "resetYieldButton",
                                      HELP_BUTTON_BACKGROUND = "helpButtonBackground",
                                      HELP_BUTTON = "helpButton",
                    }

                    -- remove all to avoid warnings
                    for k, v in pairs(controls) do
                        self.controlIDs[v] = nil
                    end

                    self:registerControls(controls)

                    g_gui:loadGuiRec(xmlFile, "ingameMapExtension", mapFrame, self)

                    self:exposeControlsAsFields("MapFrame")
                    self:onGuiSetupFinished()

                    if pfModule.soilMap ~= nil then
                        pfModule.soilMap:setMapFrame(self)
                    end

                    if pfModule.farmlandStatistics ~= nil then
                        pfModule.farmlandStatistics:setMapFrame(self)
                    end

                    if pfModule.additionalFieldBuyInfo ~= nil then
                        pfModule.additionalFieldBuyInfo:setMapFrame(self)
                    end

                    if pfModule.yieldMap ~= nil then
                        pfModule.yieldMap:setMapFrame(self)
                    end

                    self.precisionFarmingContainer = mapFrame.elements[#mapFrame.elements - 1]
                    self.precisionFarmingAdditionalContainer = mapFrame.elements[#mapFrame.elements]

                    self.precisionFarmingContainer:applyScreenAlignment()
                    self.precisionFarmingContainer:updateAbsolutePosition()
                    self.precisionFarmingAdditionalContainer:applyScreenAlignment()
                    self.precisionFarmingAdditionalContainer:updateAbsolutePosition()
                end
            end
            delete(xmlFile)
        end
    end)

    pfModule:overwriteGameFunction(InGameMenuMapFrame, "assignFilterData", function(superFunc, self)
        if self.isPrecisionFarmingPage then
            self.filterPaging:updatePageMapping()

            if self.mapSelectorMapping ~= nil then
                local state = self.mapOverviewSelector:getState()
                local currentMap = self.mapSelectorMapping[state]

                self.mapOverviewSoilBox:invalidateLayout()

                local valueMaps = g_precisionFarming:getValueMaps()
                local index = currentMap - self.pf_numBaseMaps
                if index > 0 then
                    local valueMap = valueMaps[index]
                    if valueMap ~= nil and valueMap:getShowInMenu() then
                        local valuesToDisplay = valueMap:getDisplayValues()

                        self:assignGroundStateFilterData(false, valuesToDisplay, self.soilStateFilterButton,
                            self.soilStateFilterColor, self.soilStateFilterText)
                    end
                end
            end
        else
            superFunc(self)
        end
    end)

    pfModule:overwriteGameFunction(InGameMenuMapFrame, "assignGroundStateColors", function(superFunc, self, colorElement, stateColors)
        -- store the default size if we have only one element
        if colorElement.defaultSizeX == nil then
            colorElement.defaultSizeX = colorElement.size[1]
        else
            colorElement:setSize(colorElement.defaultSizeX, nil)
        end

        -- always delete all children in case the number of colors changes
        for i = #colorElement.elements, 1, -1 do
            colorElement.elements[i]:delete()
            colorElement.elements[i] = nil
        end

        superFunc(self, colorElement, stateColors)

        for i = #colorElement.elements, 1, -1 do
            colorElement.elements[i]:updateAbsolutePosition()
        end
    end)

    pfModule:overwriteGameFunction(InGameMenuMapFrame, "onClickSoilFilter", function(superFunc, self, element, soilStateIndex)
        if self.mapSelectorMapping ~= nil then
            local state = self.mapOverviewSelector:getState()
            local currentMap = self.mapSelectorMapping[state]

            local valueMaps = g_precisionFarming:getValueMaps()
            local index = currentMap - self.pf_numBaseMaps
            if index > 0 then
                local valueMap = valueMaps[index]
                if valueMap ~= nil and valueMap:getShowInMenu() then
                    local filter, filterEnabled = valueMap:getValueFilter()
                    if filterEnabled ~= nil then
                        if filterEnabled[soilStateIndex] ~= true then
                            return
                        end
                    end

                    self:toggleFilter(element, filter, soilStateIndex)
                    return
                end
            end
        end

        superFunc(self, element, soilStateIndex)
    end)

    pfModule:overwriteGameFunction(InGameMenuMapFrame, "assignGroundStateFilterData", function(superFunc, self, isGrowth, displayStates, filterButtons, filterColors, filterTexts)
        if self.isPrecisionFarmingPage then
            if self.mapSelectorMapping ~= nil then
                local state = self.mapOverviewSelector:getState()
                local currentMap = self.mapSelectorMapping[state]

                local valueMaps = g_precisionFarming:getValueMaps()
                local index = currentMap - self.pf_numBaseMaps
                if index > 0 then
                    local valueMap = valueMaps[index]
                    if valueMap ~= nil and valueMap:getShowInMenu() then
                        local filter = valueMap:getValueFilter()

                        local oldSoilStateFilter = self.soilStateFilter
                        self.soilStateFilter = filter
                        superFunc(self, isGrowth, displayStates, filterButtons, filterColors, filterTexts)
                        self.soilStateFilter = oldSoilStateFilter

                        return
                    end
                end
            end
        end

        superFunc(self, isGrowth, displayStates, filterButtons, filterColors, filterTexts)
    end)

    -- if the page changed and the map overview page has the PA active flag set
    -- > we disable the normal map page button and enable the PA page button
    pfModule:overwriteGameFunction(TabbedMenu, "onPageChange", function(superFunc, self, pageIndex, pageMappingIndex, element, skipTabVisualUpdate)
        local mapOverviewController = self.mapOverviewController
        local page = self.pagingElement:getPageElementByIndex(pageIndex)
        if page.name == "PRECISION_FARMING" then
            local mapPageId = self.pagingElement:getPageIdByElement(mapOverviewController)
            local mapPageMappingIndex = self.pagingElement:getPageMappingIndex(mapPageId)

            mapOverviewController.precisionFarmingPageLoading = true
            if not mapOverviewController.isPrecisionFarmingPage then
                mapOverviewController.isPrecisionFarmingPage = true
                mapOverviewController.isMapOverviewInitialized = false -- force recreation of filter boxes
            end

            self.pageSelector:setState(mapPageMappingIndex, true) -- set state with force event
            self.pageSelector.state = pageMappingIndex -- set page back to PA map index to be in correct state for left/right switch

            for _, element in pairs(self.pagingTabList.listItems) do
                element:applyProfile(TabbedMenu.PROFILE.PAGE_TAB)
            end

            local activeTab = self.pageTabs[self.pAgController]
            if activeTab ~= nil then
                activeTab:applyProfile(TabbedMenu.PROFILE.PAGE_TAB_ACTIVE)
            end

            self:updatePageTabDisplay()
            mapOverviewController:setMapSelectionItem(nil) -- unselect hotspot if selected

            return
        elseif page.name == "ingameMenuMapOverview" then
            -- if we are changing from PA map to normal map we close the map and reopen it to have the correct state
            if not mapOverviewController.precisionFarmingPageLoading then
                if mapOverviewController.isPrecisionFarmingPage then
                    mapOverviewController.isPrecisionFarmingPage = false
                    mapOverviewController.isMapOverviewInitialized = false -- force recreation of filter boxes
                end
                page:onFrameClose()
                page:setVisible(false)
            end

            mapOverviewController.precisionFarmingPageLoading = false
        end

        if pfModule.additionalFieldBuyInfo ~= nil then
            pfModule.additionalFieldBuyInfo:onFarmlandSelectionChanged()
            if self.resetUIDeadzones ~= nil then
                self:resetUIDeadzones()
            end
        end

        superFunc(self, pageIndex, pageMappingIndex, element, skipTabVisualUpdate)
    end)

    -- block switching to farmlands mode when in PA page (-> switchMapMode input is invisible)
    pfModule:overwriteGameFunction(InGameMenuMapFrame, "onClickSwitchMapMode", function(superFunc, self)
        if pfModule.additionalFieldBuyInfo ~= nil then
            pfModule.additionalFieldBuyInfo:onFarmlandSelectionChanged()
            self:resetUIDeadzones()
        end

        if self.isPrecisionFarmingPage then
            if pfModule.farmlandStatistics ~= nil then
                pfModule.farmlandStatistics:onClickSwitchMapMode()
            end

            return
        end

        superFunc(self)
    end)

    -- hide switch map button for PA screen
    pfModule:overwriteGameFunction(InGameMenuMapFrame, "updateInputGlyphs", function(superFunc, self)
        superFunc(self)

        if self.isPrecisionFarmingPage then
            self.buttonSwitchMapMode:setVisible(false)
        else
            self.buttonSwitchMapMode:setVisible(true)
        end
    end)

    -- new on click map event to catch clicking on soil map
    pfModule:overwriteGameFunction(InGameMenuMapFrame, "onClickMap", function(superFunc, self, element, worldX, worldZ)
        if self.isPrecisionFarmingPage then
            if self.mode == InGameMenuMapFrame.MODE_OVERVIEW then
                local farmlandId = self.farmlandManager:getFarmlandIdAtWorldPosition(worldX, worldZ)
                if farmlandId ~= nil then
                    if pfModule.farmlandStatistics ~= nil then
                        pfModule.farmlandStatistics:onClickMapFarmland(farmlandId)
                    end
                end

                self:resetUIDeadzones()
            end
        else
            local oldFarmland = self.selectedFarmland

            superFunc(self, element, worldX, worldZ)

            if self.selectedFarmland ~= oldFarmland then
                if pfModule.additionalFieldBuyInfo ~= nil then
                    pfModule.additionalFieldBuyInfo:onFarmlandSelectionChanged(self.selectedFarmland)
                    self:resetUIDeadzones()
                end
            end
        end
    end)

    local deadzoneElements = {"laboratoryWindow", "economicAnalysisWindow", "resetYieldButtonBackground", "helpButtonBackground"}

    pfModule:overwriteGameFunction(InGameMenuMapFrame, "resetUIDeadzones", function(superFunc, self)
        superFunc(self)

        if self.isPrecisionFarmingPage then
            for i=1, #deadzoneElements do
                local element = self[deadzoneElements[i]]
                if element:getIsVisible() then
                    self.ingameMap:addCursorDeadzone(
                        element.absPosition[1],
                        element.absPosition[2],
                        element.size[1],
                        element.size[2])
                end
            end
        else
            if self.fieldBuyInfoWindow:getIsVisible() then
                self.ingameMap:addCursorDeadzone(
                    self.fieldBuyInfoWindow.absPosition[1],
                    self.fieldBuyInfoWindow.absPosition[2],
                    self.fieldBuyInfoWindow.size[1],
                    self.fieldBuyInfoWindow.size[2])
            end
        end
    end)

    -- block clicking on hotspots while in PA page
    pfModule:overwriteGameFunction(InGameMenuMapFrame, "setMapSelectionItem", function(superFunc, self, hotspot)
        if self.isPrecisionFarmingPage then
            return superFunc(self, nil)
        end

        return superFunc(self, hotspot)
    end)

    pfModule:overwriteGameFunction(InGameMenuMapFrame, "onMenuActivate", function(superFunc, self)
        local inputUsed = false
        if self.isPrecisionFarmingPage then
            if g_inputBinding:getInputHelpMode() == GS_INPUT_HELP_MODE_GAMEPAD then
                if pfModule.farmlandStatistics ~= nil then
                    inputUsed = pfModule.farmlandStatistics:onMenuActivate()
                end
            end
        end

        if not inputUsed then
            superFunc(self)
        end
    end)

    pfModule:overwriteGameFunction(InGameMenuMapFrame, "onMenuCancel", function(superFunc, self)
        local inputUsed = false
        if self.isPrecisionFarmingPage then
            if g_inputBinding:getInputHelpMode() == GS_INPUT_HELP_MODE_GAMEPAD then
                if pfModule.farmlandStatistics ~= nil then
                    inputUsed = pfModule.farmlandStatistics:onMenuCancel()
                end
            end
        end

        if not inputUsed then
            superFunc(self)
        end
    end)

    pfModule:overwriteGameFunction(InGameMenuMapFrame, "onSwitchVehicle", function(superFunc, self, _, _, direction)
        local inputUsed = false
        if self.isPrecisionFarmingPage then
            if g_inputBinding:getInputHelpMode() == GS_INPUT_HELP_MODE_GAMEPAD then
                if direction == 1 then
                    inputUsed = inputUsed or self:onClickButtonResetYield()
                elseif direction == -1 then
                    inputUsed = inputUsed or self:onClickButtonHelp()
                end
            end
        end

        if not inputUsed then
            superFunc(self, _, _, direction)
        end
    end)

    pfModule:overwriteGameFunction(IngameMap, "drawMapLabel", function(superFunc, self)
        superFunc(self)

        if self.renderPALabelThisFrame then
            local inGameMenuMapFrame = pfModule.ingameMapExtension.inGameMenuMapFrame
            if inGameMenuMapFrame ~= nil then
                local mapOverlayGenerator = inGameMenuMapFrame.mapOverlayGenerator
                if mapOverlayGenerator ~= nil then
                    local requiredValueMap = mapOverlayGenerator.lastFarmlandStateOverlayValueMap
                    if requiredValueMap ~= nil then
                        local element = self.overlay
                        local label = requiredValueMap:getMinimapLabel()
                        if label ~= nil then
                            setTextAlignment(RenderText.ALIGN_LEFT)
                            local offsetX, offsetY, textSize = pfModule.ingameMapExtension.mapLabelOffsetX, pfModule.ingameMapExtension.mapLabelOffsetY, pfModule.ingameMapExtension.mapLabelTextSize

                            local tx, ty = element.x + offsetX, element.y + element.height - offsetY - textSize
                            setTextColor(0, 0, 0, 1)
                            renderText(tx, ty-0.0015, textSize, label)
                            setTextColor(1, 1, 1, 1)
                            renderText(tx, ty, textSize, label)
                        end

                        local gradientUVs = requiredValueMap:getMinimapGradientUVs(mapOverlayGenerator.isColorBlindMode)
                        local gradientLabel = requiredValueMap:getMinimapGradientLabel()
                        if gradientUVs ~= nil and gradientLabel ~= nil then
                            local gradientBackgroundElement = pfModule.ingameMapExtension.gradientBackgroundElement
                            gradientBackgroundElement.overlay.x = element.x + pfModule.ingameMapExtension.gradientOffsetX
                            gradientBackgroundElement.overlay.y = element.y + pfModule.ingameMapExtension.gradientOffsetY
                            gradientBackgroundElement:draw()

                            local gradientElement = pfModule.ingameMapExtension.gradientElement
                            gradientElement.overlay.x = gradientBackgroundElement.overlay.x + (gradientBackgroundElement.overlay.width - gradientElement.overlay.width) * 0.5
                            gradientElement.overlay.y = gradientBackgroundElement.overlay.y + (gradientBackgroundElement.overlay.height - gradientElement.overlay.height) * 0.5
                            gradientElement:draw()

                            gradientElement:setUVs(unpack(gradientUVs))

                            setTextAlignment(RenderText.ALIGN_CENTER)
                            local tx, ty = gradientBackgroundElement.overlay.x + gradientBackgroundElement.overlay.width * 0.5, gradientBackgroundElement.overlay.y + gradientBackgroundElement.overlay.height + pfModule.ingameMapExtension.gradientOffsetY * 0.5
                            setTextColor(0, 0, 0, 1)
                            renderText(tx, ty-0.0015, pfModule.ingameMapExtension.mapLabelTextSize * 0.85, gradientLabel)
                            setTextColor(1, 1, 1, 1)
                            renderText(tx, ty, pfModule.ingameMapExtension.mapLabelTextSize * 0.85, gradientLabel)
                        end
                    end
                end
            end

            self.renderPALabelThisFrame = false
        end
    end)

    pfModule:overwriteGameFunction(IngameMap, "drawMap", function(superFunc, self, alpha, isStandalone)
        self.mapOverlay:setColor(nil, nil, nil, alpha or self.mapAlpha)
        self.mapElement:draw()

        if isStandalone then
            local lastMinVisWidth = self.minMapVisWidth
            self.minMapVisWidth = 0.3
            if self.smoothedMapZoomLevel == nil or self.smoothedMapZoomTargetLevel == nil then
                self.smoothedMapZoomLevel = self.minMapVisWidth
                self.smoothedMapZoomTargetLevel = self.minMapVisWidth
            end

            local inGameMenuMapFrame = pfModule.ingameMapExtension.inGameMenuMapFrame
            if inGameMenuMapFrame ~= nil then
                local mapOverlayGenerator = inGameMenuMapFrame.mapOverlayGenerator
                if mapOverlayGenerator ~= nil then
                    local requiredValueMap
                    for i=1, #pfModule.valueMaps do
                        local valueMap = pfModule.valueMaps[i]
                        if valueMap:getRequireMinimapDisplay() then
                            requiredValueMap = valueMap
                        end
                    end

                    if requiredValueMap ~= nil and self.state ~= IngameMap.STATE_OFF then
                        if self.smoothedMapZoomLevel == requiredValueMap:getMinimapZoomFactor() and requiredValueMap == mapOverlayGenerator.lastFarmlandStateOverlayValueMap then
                            if mapOverlayGenerator.pa_minimapOverlayReadyForDisplay then
                                local overlay = mapOverlayGenerator.pa_minimapOverlay
                                if overlay ~= nil and overlay ~= 0 then
                                    local mapUVs = self.mapUVs
                                    local element = self.overlay
                                    setOverlayUVs(overlay, mapUVs[1], mapUVs[2], mapUVs[3], mapUVs[4], mapUVs[5], mapUVs[6], mapUVs[7], mapUVs[8])
                                    renderOverlay(overlay, element.x, element.y, element.width, element.height)

                                    self.renderPALabelThisFrame = true
                                end
                            end

                            local additionElement = requiredValueMap:getMinimapAdditionalElement()
                            if additionElement ~= nil then
                                local x, y = requiredValueMap:getMinimapAdditionalElementRealSize()
                                if x > 0 and y > 0 then
                                    local zoomLevel = requiredValueMap:getMinimapZoomFactor()
                                    local yFactor = IngameMap.MIN_MAP_HEIGHT / IngameMap.MIN_MAP_WIDTH
                                    if self.state == IngameMap.STATE_MAP then
                                        zoomLevel = 1
                                        yFactor = 1
                                    end

                                    local metersWidth = self.worldSizeX * zoomLevel
                                    local metersHeight = self.worldSizeZ * zoomLevel * yFactor
                                    additionElement.overlay.width = self.overlay.width * (x / metersWidth)
                                    additionElement.overlay.height = self.overlay.height * (y / metersHeight)
                                end

                                local additionalElementLinkNode = requiredValueMap:getMinimapAdditionalElementLinkNode()
                                if additionalElementLinkNode ~= nil then
                                    local linkX, _, linkZ = getWorldTranslation(additionalElementLinkNode)
                                    local objectX = (linkX + self.worldCenterOffsetX) / self.worldSizeX
                                    local objectZ = (linkZ + self.worldCenterOffsetZ) / self.worldSizeZ

                                    local oldCenterXPos, oldCenterZPos = self.centerXPos, self.centerZPos
                                    if self.state == IngameMap.STATE_MAP then
                                        self.centerXPos, self.centerZPos = 0.5, 0.5
                                    end
                                    self:setMapObjectOverlayPosition(additionElement.overlay, objectX, objectZ, additionElement.overlay.width, additionElement.overlay.height, true, true, false, false, false, false)
                                    self.centerXPos, self.centerZPos = oldCenterXPos, oldCenterZPos
                                else
                                    local element = self.overlay
                                    additionElement.overlay.x = element.x + element.width * 0.5 - additionElement.overlay.width * 0.5
                                    additionElement.overlay.y = element.y + element.height * 0.5 - additionElement.overlay.height * 0.5
                                end

                                additionElement:draw()
                            end
                        end

                        if mapOverlayGenerator.lastFarmlandStateOverlayValueMapGenerated == nil or mapOverlayGenerator.lastFarmlandStateOverlayValueMapGenerated == true then
                            local requiredFilter = requiredValueMap:getMinimapValueFilter()
                            local lastValueMap = mapOverlayGenerator.lastFarmlandStateOverlayValueMap
                            local lastValueMapFilter = mapOverlayGenerator.lastFarmlandStateOverlayValueMapFilter

                            local requiresUpdate = requiredValueMap:getMinimapRequiresUpdate() or g_server == nil

                            -- avoid display of overlay if it's mixed with last value map
                            if lastValueMap ~= requiredValueMap then
                                mapOverlayGenerator.pa_minimapOverlayReadyForDisplay = false
                            end

                            if (lastValueMap ~= requiredValueMap or lastValueMapFilter ~= requiredFilter or requiresUpdate) then
                                if mapOverlayGenerator.pa_minimapOverlay == nil then
                                    mapOverlayGenerator.pa_minimapOverlay = createDensityMapVisualizationOverlay("pa_minimapOverlay", 1024, 1024)
                                end

                                local updateTimeLimit = requiredValueMap:getMinimapUpdateTimeLimit()
                                -- for clients we permanently update the map with slower interval since we don't know when we recieved the latest data from the server
                                if g_server == nil then
                                    updateTimeLimit = 0.15
                                end
                                setDensityMapVisualizationOverlayUpdateTimeLimit(mapOverlayGenerator.pa_minimapOverlay, updateTimeLimit)

                                local buildFunc = mapOverlayGenerator.typeBuilderFunctionMap[IngameMapExtension.NUM_BASE_OVERLAY_TYPES + requiredValueMap.valueMapIndex]
                                buildFunc(mapOverlayGenerator, requiredFilter, true, mapOverlayGenerator.pa_minimapOverlay)

                                generateDensityMapVisualizationOverlay(mapOverlayGenerator.pa_minimapOverlay)
                                mapOverlayGenerator.lastFarmlandStateOverlayValueMapGenerated = false
                                requiredValueMap:setMinimapRequiresUpdate(false)
                            end
                        end

                        if mapOverlayGenerator.lastFarmlandStateOverlayValueMapGenerated == false then
                            if getIsDensityMapVisualizationOverlayReady(mapOverlayGenerator.pa_minimapOverlay) then
                                mapOverlayGenerator.lastFarmlandStateOverlayValueMapGenerated = true
                                mapOverlayGenerator.pa_minimapOverlayReadyForDisplay = true
                            end
                        end

                        self.minMapVisWidth = requiredValueMap:getMinimapZoomFactor()
                    else
                        mapOverlayGenerator.lastFarmlandStateOverlayValueMap = nil
                        mapOverlayGenerator.lastFarmlandStateOverlayValueMapGenerated = nil
                    end
                end
            end

            if self.state ~= IngameMap.STATE_MAP then
                if lastMinVisWidth ~= self.minMapVisWidth then
                    if self.smoothedMapZoomLevel == nil then
                        self.smoothedMapZoomLevel = lastMinVisWidth
                    end

                    self.smoothedMapZoomTargetLevel = self.minMapVisWidth
                end

                if self.smoothedMapZoomLevel ~= self.smoothedMapZoomTargetLevel then
                    local dir = MathUtil.sign(self.smoothedMapZoomTargetLevel-self.smoothedMapZoomLevel)
                    local limit = dir == 1 and math.min or math.max
                    self.smoothedMapZoomLevel = limit(self.smoothedMapZoomLevel + (g_currentDt * 0.001 * dir * 0.3), self.smoothedMapZoomTargetLevel)

                    self.mapVisWidthMin = self.smoothedMapZoomLevel
                end
            else
                self.smoothedMapZoomLevel = nil
                self.smoothedMapZoomTargetLevel = nil
                self.minMapVisWidth = 0.3
            end
        else
            self.smoothedMapZoomLevel = self.minMapVisWidth
        end

        if isStandalone then -- draw frame and other components
            IngameMap:superClass().draw(self)
        end
    end)

    -- do not floor the coordinates since it looks bad while zoomed in
    pfModule:overwriteGameFunction(IngameMap, "updatePlayerPosition", function(superFunc, self)
        local playerPosX, playerPosY, playerPosZ = 0, 0, 0

        if self.topDownCamera ~= nil then
            playerPosX, playerPosY, playerPosZ, self.playerRotation = self.topDownCamera:determineMapPosition()
        elseif g_currentMission.controlPlayer then
            if g_currentMission.player ~= nil then
                playerPosX, playerPosY, playerPosZ, self.playerRotation = self:determinePlayerPosition(g_currentMission.player)
            end
        elseif g_currentMission.controlledVehicle ~= nil then
            playerPosX, playerPosY, playerPosZ, self.playerRotation = self:determineVehiclePosition(g_currentMission.controlledVehicle)
        end
        -- convert coordinates, coordinates (0..1) within the map
        self.normalizedPlayerPosX = MathUtil.clamp((playerPosX + self.worldCenterOffsetX) / self.worldSizeX, 0, 1)
        self.normalizedPlayerPosZ = MathUtil.clamp((playerPosZ + self.worldCenterOffsetZ) / self.worldSizeZ, 0, 1)
    end)

    -- since we cannot hook up to the page generation functions since the mod is loaded to late
    -- we add the page now afterwards to the page list right behind the ingameMenuMapOverview page
    local ingameMenu = g_gui.screenControllers[InGameMenu]
    for frameController, tab in pairs(ingameMenu.pageTabs) do
        if frameController.name == "ingameMenuMapOverview" then
            ingameMenu.mapOverviewController = frameController

            local fakeClass = {}
            ingameMenu.pAgController = {}
            setmetatable(ingameMenu.pAgController, {__index = frameController})
            ingameMenu.pAgController.class = function()
                return fakeClass
            end

            local pageRoot = {}
            setmetatable(pageRoot, {__index = frameController})
            pageRoot.name = "PRECISION_FARMING"
            pageRoot.title = "PRECISION_FARMING"
            pageRoot.delete = function() end
            pageRoot.onFrameOpen = function(_, ...)
                frameController:setVisible(true)
                frameController:setSoundSuppressed(true)
                FocusManager:setGui(frameController.name)
                frameController:setSoundSuppressed(false)
                frameController:onFrameOpen()
            end

            ingameMenu.pAgController.getFirstDescendant = function()
                return pageRoot
            end

            for i, page in ipairs(ingameMenu.pagingElement.pages) do
                if not page.disabled then
                    ingameMenu.pagingElement.currentPageIndex = i
                    break
                end
            end

            ingameMenu:addPage(ingameMenu.pAgController, 3, IngameMapExtension.GUI_ELEMENTS, {129, 1, 62, 62}, ingameMenu.pageEnablingPredicates[frameController])

            local tabButton = ingameMenu.pageTabs[ingameMenu.pAgController]:getDescendantByName(TabbedMenu.PAGE_TAB_TEMPLATE_BUTTON_NAME)
            tabButton.onClickCallback = function()
                ingameMenu:onPageClicked(ingameMenu.activeDetailPage)

                local pageId = ingameMenu.pagingElement:getPageIdByElement(pageRoot)
                local pageMappingIndex = ingameMenu.pagingElement:getPageMappingIndex(pageId)
                ingameMenu.pageSelector:setState(pageMappingIndex, true) -- set state with force event
            end

            break
        end
    end
end

function IngameMapExtension.updateButtonOnInputHelpChange(element, profileGamepad, profile)
    if element ~= nil then
        local useGamepadButtons = g_inputBinding:getInputHelpMode() == GS_INPUT_HELP_MODE_GAMEPAD

        GuiOverlay.deleteOverlay(element.icon)

        element.hasLoadedInputGlyph = false
        element.inputActionName = nil
        element.keyDisplayText = nil

        element:applyProfile(useGamepadButtons and profileGamepad or profile)

        if element.inputActionName ~= nil then
            element:loadInputGlyph(true)
        end
    end
end
