DiseaseMapFrame = {}
DiseaseMapFrame.MOD_NAME = g_currentModName or "FS25_CropDiseases_BMP"
DiseaseMapFrame.OVERLAY_RESOLUTION = 1024

local DiseaseMapFrame_mt = Class(DiseaseMapFrame)
local ThreatValueMap = {}
local ThreatValueMap_mt = Class(ThreatValueMap)

local function getL10nText(key, fallback)
    if g_i18n ~= nil and g_i18n.hasText ~= nil and g_i18n:hasText(key) then
        return g_i18n:getText(key)
    end

    return fallback or key
end

local function getPrecisionFarmingMapFrameExtension()
    local precisionFarming = rawget(_G, "g_precisionFarming")
    if precisionFarming == nil then
        return nil
    end

    return precisionFarming.inGameMenuMapFrameExtension
end

local function getThreatLayoutOffsets()
    local _, selectorOffset = getNormalizedScreenValues(0, 80)
    local _, dotOffset = getNormalizedScreenValues(0, 75)
    local _, filterOffset = getNormalizedScreenValues(0, 60)

    return selectorOffset, dotOffset, filterOffset
end

local function resetOverlay(overlayId)
    if overlayId == nil or overlayId == 0 then
        return
    end

    resetDensityMapVisualizationOverlay(overlayId)
    setOverlayColor(overlayId, 1, 1, 1, 1)
end

local function getIsThreatOverlayReady(overlayId)
    if overlayId == nil or overlayId == 0 then
        return false
    end

    if getIsDensityMapVisualizationOverlayReady ~= nil and not getIsDensityMapVisualizationOverlayReady(overlayId) then
        return false
    end

    if getIsOverlayReady ~= nil and not getIsOverlayReady(overlayId) then
        return false
    end

    return true
end

local function clearMap(mapId, width, height, numChannels)
    if mapId == nil or width == nil or height == nil then
        return
    end

    setBitVectorMapParallelogram(mapId, 0, 0, width, 0, 0, height, 0, numChannels, 0)
end

local function setMapPoint(mapId, x, z, numChannels, value)
    setBitVectorMapParallelogram(mapId, x, z, 1, 0, 0, 1, 0, numChannels, value)
end

local function getOrCreateBitVectorMap(cache, name, width, height, numChannels)
    local entry = cache[name]
    if entry ~= nil and entry.mapId ~= nil and entry.width == width and entry.height == height and entry.numChannels == numChannels then
        return entry.mapId
    end

    if entry ~= nil and entry.mapId ~= nil then
        delete(entry.mapId)
    end

    local mapId = createBitVectorMap(name)
    loadBitVectorMapNew(mapId, width, height, numChannels, false)

    cache[name] = {
        mapId = mapId,
        width = width,
        height = height,
        numChannels = numChannels
    }

    return mapId
end

local function getOrCreateBitVectorMapEntry(cache, name, width, height, numChannels)
    local entry = cache[name]
    if entry ~= nil and entry.mapId ~= nil and entry.width == width and entry.height == height and entry.numChannels == numChannels then
        return entry
    end

    getOrCreateBitVectorMap(cache, name, width, height, numChannels)
    entry = cache[name]
    if entry ~= nil then
        entry.initialized = false
    end
    return entry
end

local function applyHealthyFieldBase(overlayId, color)
    if g_currentMission == nil or g_currentMission.fieldGroundSystem == nil or overlayId == nil or overlayId == 0 or color == nil then
        return
    end

    local groundTypeMapId, firstChannel, numChannels = g_currentMission.fieldGroundSystem:getDensityMapData(FieldDensityMap.GROUND_TYPE)
    if groundTypeMapId == nil or firstChannel == nil or numChannels == nil then
        return
    end

    local maxValue = bit32.lshift(1, numChannels) - 1
    for value = 1, maxValue do
        setDensityMapVisualizationOverlayStateColor(
            overlayId,
            groundTypeMapId,
            0,
            0,
            firstChannel,
            numChannels,
            value,
            color[1],
            color[2],
            color[3]
        )
    end
end

function ThreatValueMap.new(name, overviewLabelKey, overviewLabelFallback, entries, baseColor, mode)
    local self = setmetatable({}, ThreatValueMap_mt)
    self.name = name
    self.overviewLabelKey = overviewLabelKey
    self.overviewLabelFallback = overviewLabelFallback
    self.entries = entries or {}
    self.baseColor = baseColor or {0.18, 0.72, 0.18, 1}
    self.mode = mode or "threat"
    self.filter = {}
    self.displayValues = {}
    self.threatKeyToIndex = {}

    for index, entry in ipairs(self.entries) do
        self.filter[index] = true
        if entry.threatKey ~= nil then
            self.threatKeyToIndex[entry.threatKey] = index
        elseif entry.category ~= nil then
            self.threatKeyToIndex[entry.category] = index
        end
    end

    return self
end

function ThreatValueMap:getShowInMenu()
    return true
end

function ThreatValueMap:getAllowCoverage()
    return false
end

function ThreatValueMap:getOverviewLabel()
    return getL10nText(self.overviewLabelKey, self.overviewLabelFallback)
end

function ThreatValueMap:getDisplayValues()
    if #self.displayValues == #self.entries then
        return self.displayValues
    end

    self.displayValues = {}
    for _, entry in ipairs(self.entries) do
        table.insert(self.displayValues, {
            colors = {
                [false] = {entry.color},
                [true] = {entry.colorBlind or entry.color}
            },
            description = getL10nText(entry.labelKey, entry.labelFallback)
        })
    end

    return self.displayValues
end

function ThreatValueMap:getValueFilter()
    return self.filter, nil
end

function ThreatValueMap:getDisplayIndex(defs, severity, threatId)
    if self.mode == "severity" then
        local sev = math.max(0, math.min(3, tonumber(severity or 0) or 0))
        return sev > 0 and sev or nil
    end

    local threatKey = defs ~= nil and defs.getThreatKeyById ~= nil and defs:getThreatKeyById(threatId) or nil
    if threatKey == nil then
        return nil
    end

    local directIndex = self.threatKeyToIndex[threatKey]
    if directIndex ~= nil then
        return directIndex
    end

    local category = defs.getThreatCategory ~= nil and defs:getThreatCategory(threatKey) or nil
    return category ~= nil and self.threatKeyToIndex[category] or nil
end

function ThreatValueMap:getCacheEntry(mapCache)
    if DiseaseGrid == nil or DiseaseGrid.width == nil or DiseaseGrid.height == nil then
        return nil
    end

    return getOrCreateBitVectorMapEntry(mapCache, self.name .. "OverlayMap", DiseaseGrid.width, DiseaseGrid.height, 4)
end

function ThreatValueMap:rebuildCacheMap(mapCache)
    if DiseaseGrid == nil or DiseaseGrid.mapId == nil or DiseaseGrid.width == nil or DiseaseGrid.height == nil then
        return nil
    end

    local defs = _G.DiseaseDefs
    if defs == nil then
        return nil
    end

    local entry = self:getCacheEntry(mapCache)
    if entry == nil or entry.mapId == nil then
        return nil
    end

    clearMap(entry.mapId, DiseaseGrid.width, DiseaseGrid.height, entry.numChannels)

    for z = 0, DiseaseGrid.height - 1 do
        for x = 0, DiseaseGrid.width - 1 do
            local severity = getBitVectorMapPoint(DiseaseGrid.mapId, x, z, 0, 2) or 0
            local displayIndex = 0
            if severity > 0 and (DiseaseGrid.getIsFieldGroundAtMapCell == nil or DiseaseGrid:getIsFieldGroundAtMapCell(x, z)) then
                local threatId = getBitVectorMapPoint(DiseaseGrid.mapId, x, z, 2, 4) or 0
                displayIndex = self:getDisplayIndex(defs, severity, threatId) or 0
            end
            setMapPoint(entry.mapId, x, z, entry.numChannels, displayIndex)
        end
    end

    entry.initialized = true
    return entry
end

function ThreatValueMap:ensureCacheMap(mapCache)
    local entry = self:getCacheEntry(mapCache)
    if entry == nil then
        return nil
    end

    if entry.initialized ~= true then
        entry = self:rebuildCacheMap(mapCache)
    end

    return entry
end

function ThreatValueMap:updateCacheCell(mapCache, x, z, severity, threatId)
    local entry = self:getCacheEntry(mapCache)
    if entry == nil or entry.mapId == nil or entry.initialized ~= true then
        return
    end

    local defs = _G.DiseaseDefs
    if defs == nil then
        return
    end

    local displayIndex = 0
    if (severity or 0) > 0 and (DiseaseGrid == nil or DiseaseGrid.getIsFieldGroundAtMapCell == nil or DiseaseGrid:getIsFieldGroundAtMapCell(x, z)) then
        displayIndex = self:getDisplayIndex(defs, severity or 0, threatId or 0) or 0
    end

    setMapPoint(entry.mapId, x, z, entry.numChannels, displayIndex)
end

function ThreatValueMap:buildOverlay(baseOverlayId, valueOverlayId, isColorBlindMode, mapCache)
    resetOverlay(baseOverlayId)
    resetOverlay(valueOverlayId)

    if DiseaseGrid == nil or DiseaseGrid.mapId == nil or DiseaseGrid.width == nil or DiseaseGrid.height == nil then
        return
    end

    local defs = _G.DiseaseDefs
    if defs == nil then
        return
    end

    local anySelected = false
    for index = 1, #self.entries do
        if self.filter[index] then
            anySelected = true
            break
        end
    end

    if not anySelected then
        return
    end

    applyHealthyFieldBase(baseOverlayId, self.baseColor)

    local colorBlind = isColorBlindMode == true
    for index, entry in ipairs(self.entries) do
        if self.filter[index] then
            local color = colorBlind and (entry.colorBlind or entry.color) or entry.color

            if self.mode == "severity" then
                local severityValue = entry.severity or index
                setDensityMapVisualizationOverlayStateColor(
                    valueOverlayId,
                    DiseaseGrid.mapId,
                    0,
                    0,
                    0,
                    2,
                    severityValue,
                    color[1],
                    color[2],
                    color[3]
                )
            elseif entry.threatKey ~= nil then
                local threatId = defs:getThreatId(entry.threatKey)
                if threatId ~= nil and threatId > 0 then
                    setDensityMapVisualizationOverlayStateColor(
                        valueOverlayId,
                        DiseaseGrid.mapId,
                        0,
                        0,
                        2,
                        4,
                        threatId,
                        color[1],
                        color[2],
                        color[3]
                    )
                end
            elseif entry.category ~= nil then
                for _, threatKey in ipairs(defs:getThreatKeys() or {}) do
                    if defs:getThreatCategory(threatKey) == entry.category then
                        local threatId = defs:getThreatId(threatKey)
                        if threatId ~= nil and threatId > 0 then
                            setDensityMapVisualizationOverlayStateColor(
                                valueOverlayId,
                                DiseaseGrid.mapId,
                                0,
                                0,
                                2,
                                4,
                                threatId,
                                color[1],
                                color[2],
                                color[3]
                            )
                        end
                    end
                end
            end
        end
    end
end

function DiseaseMapFrame.new(customMt)
    local self = setmetatable({}, customMt or DiseaseMapFrame_mt)
    self.mapCache = {}
    self.overlayReady = {
        base = false,
        values = false
    }
    self.inGameMenuMapFrame = nil
    self.activeValueMapIndex = 1
    self.valueMaps = {
        ThreatValueMap.new(
            "bmpThreatOverview",
            "bmp_mapSubOverview",
            "Overview",
            {
                {
                    category = "disease",
                    labelKey = "bmp_mapLayerDiseases",
                    labelFallback = "Diseases",
                    color = {0.86, 0.16, 0.12, 1},
                    colorBlind = {0.86, 0.16, 0.12, 1}
                },
                {
                    category = "pest",
                    labelKey = "bmp_mapLayerPests",
                    labelFallback = "Pests",
                    color = {0.52, 0.14, 0.68, 1},
                    colorBlind = {0.52, 0.14, 0.68, 1}
                }
            },
            {0.52, 0.52, 0.52, 1}
        ),
        ThreatValueMap.new(
            "bmpThreatDiseases",
            "bmp_mapLayerDiseases",
            "Diseases",
            {
                {threatKey = "bmp_dis_powdery", labelKey = "bmp_dis_powdery", labelFallback = "Powdery mildew", color = {0.94, 0.82, 0.18, 1}},
                {threatKey = "bmp_dis_rust", labelKey = "bmp_dis_rust", labelFallback = "Rust", color = {0.90, 0.44, 0.10, 1}},
                {threatKey = "bmp_dis_septoria", labelKey = "bmp_dis_septoria", labelFallback = "Septoria", color = {0.78, 0.60, 0.14, 1}},
                {threatKey = "bmp_dis_rootrot", labelKey = "bmp_dis_rootrot", labelFallback = "Root rot", color = {0.55, 0.34, 0.16, 1}},
                {threatKey = "bmp_dis_snowmold", labelKey = "bmp_dis_snowmold", labelFallback = "Snow mold", color = {0.48, 0.72, 0.86, 1}},
                {threatKey = "bmp_dis_lateblight", labelKey = "bmp_dis_lateblight", labelFallback = "Late blight", color = {0.86, 0.14, 0.10, 1}}
            },
            {0.52, 0.52, 0.52, 1}
        ),
        ThreatValueMap.new(
            "bmpThreatPests",
            "bmp_mapLayerPests",
            "Pests",
            {
                {threatKey = "bmp_pest_aphids", labelKey = "bmp_pest_aphids", labelFallback = "Aphids", color = {0.18, 0.68, 0.22, 1}},
                {threatKey = "bmp_pest_caterpillars", labelKey = "bmp_pest_caterpillars", labelFallback = "Caterpillars", color = {0.95, 0.60, 0.14, 1}},
                {threatKey = "bmp_pest_wireworm", labelKey = "bmp_pest_wireworm", labelFallback = "Wireworm", color = {0.47, 0.05, 0.14, 1}}
            },
            {0.52, 0.52, 0.52, 1}
        ),
        ThreatValueMap.new(
            "bmpThreatSeverity",
            "bmp_mapLayerSeverity",
            "Severity",
            {
                {severity = 1, labelKey = "bmp_mapSeverityLow", labelFallback = "Low", color = {0.56, 0.88, 0.50, 1}},
                {severity = 2, labelKey = "bmp_mapSeverityMedium", labelFallback = "Medium", color = {0.95, 0.82, 0.18, 1}},
                {severity = 3, labelKey = "bmp_mapSeverityHigh", labelFallback = "High", color = {0.90, 0.18, 0.12, 1}}
            },
            {0.52, 0.52, 0.52, 1},
            "severity"
        )
    }
    self.overlayUpdateTimer = 0
    self.overlayUpdateInterval = 1000
    self.lastOverlayRevision = -1
    return self
end

function DiseaseMapFrame:delete()
    if self.baseOverlay ~= nil then
        delete(self.baseOverlay)
        self.baseOverlay = nil
    end

    if self.valueOverlay ~= nil then
        delete(self.valueOverlay)
        self.valueOverlay = nil
    end

    for _, entry in pairs(self.mapCache) do
        if entry.mapId ~= nil then
            delete(entry.mapId)
        end
    end

    self.mapCache = {}
end

function DiseaseMapFrame:initialize()
    self.isColorBlindMode = g_gameSettings:getValue(GameSettings.SETTING.USE_COLORBLIND_MODE)
    g_messageCenter:subscribe(MessageType.SETTING_CHANGED[GameSettings.SETTING.USE_COLORBLIND_MODE], self.setColorBlindMode, self)
end

function DiseaseMapFrame:setColorBlindMode()
    self.isColorBlindMode = g_gameSettings:getValue(GameSettings.SETTING.USE_COLORBLIND_MODE)
end

function DiseaseMapFrame:unloadMapData()
    g_messageCenter:unsubscribeAll(self)
end

function DiseaseMapFrame:createOverlays()
    if self.baseOverlay == nil then
        self.baseOverlay = createDensityMapVisualizationOverlay("bmpThreatBase", DiseaseMapFrame.OVERLAY_RESOLUTION, DiseaseMapFrame.OVERLAY_RESOLUTION)
        self.overlayReady.base = false
    end

    if self.valueOverlay == nil then
        self.valueOverlay = createDensityMapVisualizationOverlay("bmpThreatValue", DiseaseMapFrame.OVERLAY_RESOLUTION, DiseaseMapFrame.OVERLAY_RESOLUTION)
        self.overlayReady.values = false
    end
end

function DiseaseMapFrame:getCurrentValueMap()
    return self.valueMaps[self.activeValueMapIndex]
end

function DiseaseMapFrame:updatePageData()
    local frame = self.inGameMenuMapFrame
    if frame == nil or frame.bmpThreatPageIndex == nil then
        return
    end

    local valueMap = self:getCurrentValueMap()
    if valueMap == nil then
        return
    end

    local displayValues = valueMap:getDisplayValues()
    local valueFilter = valueMap:getValueFilter()

    frame.dataTables[frame.bmpThreatPageIndex] = displayValues
    frame.filterStates[frame.bmpThreatPageIndex] = valueFilter

    local numSelected = 0
    for index = 1, #valueFilter do
        if valueFilter[index] then
            numSelected = numSelected + 1
        end
    end

    frame.numSelectedFilters[frame.bmpThreatPageIndex] = numSelected
    if numSelected == 0 then
        frame.buttonDeselectAllText:setText(g_i18n:getText(InGameMenuMapFrame.L10N_SYMBOL.SELECT_ALL))
    else
        frame.buttonDeselectAllText:setText(g_i18n:getText(InGameMenuMapFrame.L10N_SYMBOL.DESELECT_ALL))
    end
end

function DiseaseMapFrame:onThreatSelectorChanged(state)
    self.activeValueMapIndex = state
    self:updatePageData()

    if self.inGameMenuMapFrame ~= nil then
        self.inGameMenuMapFrame.filterList:reloadData()
    end

    self:updateThreatOverlay()
end

function DiseaseMapFrame:applyThreatPageLayout(frame)
    frame = frame or self.inGameMenuMapFrame
    if frame == nil or self.subSelector == nil or self.subDotBox == nil then
        return
    end

    local selectorOffset, dotOffset, filterOffset = getThreatLayoutOffsets()

    if self.subSelectorBaseY ~= nil then
        self.subSelector:setPosition(nil, self.subSelectorBaseY - selectorOffset)
    end

    if self.subDotBoxBaseY ~= nil then
        self.subDotBox:setPosition(nil, self.subDotBoxBaseY - dotOffset)
    end

    frame.filterListContainer:setPosition(nil, self.filterListContainerPositionY - filterOffset)
    frame.filterListContainer:setSize(nil, self.filterListContainerSizeY - filterOffset, true)
    frame.filterList:setSize(nil, self.filterListSizeY - filterOffset, true)
    frame.filterListSlider:setSize(nil, self.filterListSliderSizeY - filterOffset, true)
    frame.filterListSlider.elements[1]:setSize(nil, self.filterListSliderElementSizeY - filterOffset, true)
    frame.buttonDeselectAllContainer:setPosition(nil, self.buttonPositionDeselectAll)
end

function DiseaseMapFrame:updateThreatOverlay()
    local frame = self.inGameMenuMapFrame
    local valueMap = self:getCurrentValueMap()
    if frame == nil or valueMap == nil then
        return
    end

    self:createOverlays()
    self:updatePageData()

    valueMap:buildOverlay(self.baseOverlay, self.valueOverlay, self.isColorBlindMode, self.mapCache)
    generateDensityMapVisualizationOverlay(self.baseOverlay)
    generateDensityMapVisualizationOverlay(self.valueOverlay)
    self.overlayReady.base = false
    self.overlayReady.values = false
    self.lastOverlayRevision = DiseaseGrid ~= nil and (DiseaseGrid.changeRevision or 0) or self.lastOverlayRevision
end

function DiseaseMapFrame:refreshThreatOverlayVisualization()
    if self.valueOverlay == nil then
        self:updateThreatOverlay()
        return
    end

    generateDensityMapVisualizationOverlay(self.valueOverlay)
    self.overlayReady.values = false
    self.lastOverlayRevision = DiseaseGrid ~= nil and (DiseaseGrid.changeRevision or 0) or self.lastOverlayRevision
end

function DiseaseMapFrame:onThreatCellChanged(x, z, severity, threatId)
    if x == nil or z == nil then
        return
    end

    for _, valueMap in ipairs(self.valueMaps or {}) do
        valueMap:updateCacheCell(self.mapCache, x, z, severity or 0, threatId or 0)
    end
end

function DiseaseMapFrame:drawThreatOverlays(x, y, width, height)
    if self.baseOverlay ~= nil and not self.overlayReady.base and getIsThreatOverlayReady(self.baseOverlay) then
        self.overlayReady.base = true
    end

    if self.valueOverlay ~= nil and not self.overlayReady.values and getIsThreatOverlayReady(self.valueOverlay) then
        self.overlayReady.values = true
    end

    if self.overlayReady.base and self.baseOverlay ~= nil and self.baseOverlay ~= 0 then
        setOverlayUVs(self.baseOverlay, 0, 0, 0, 1, 1, 0, 1, 1)
        renderOverlay(self.baseOverlay, x, y, width, height)
    end

    if self.overlayReady.values and self.valueOverlay ~= nil and self.valueOverlay ~= 0 then
        setOverlayUVs(self.valueOverlay, 0, 0, 0, 1, 1, 0, 1, 1)
        renderOverlay(self.valueOverlay, x, y, width, height)
    end
end

function DiseaseMapFrame:install()
    if self.isInstalled or InGameMenuMapFrame == nil then
        return
    end

    InGameMenuMapFrame.onLoadMapFinished = Utils.overwrittenFunction(InGameMenuMapFrame.onLoadMapFinished, function(frame, superFunc)
        superFunc(frame)

        self.inGameMenuMapFrame = frame
        function frame.mapOverviewSelector.onClickCallback(_, state)
            frame:onClickMapOverviewSelector(state)
        end

        frame.ingameMap.onDrawPostIngameMapCallback = InGameMenuMapFrame.onDrawPostIngameMap
        frame.ingameMap.onDrawPostIngameMapHotspotsCallback = InGameMenuMapFrame.onDrawPostIngameMapHotspots
        frame.ingameMap.onClickMapCallback = InGameMenuMapFrame.onClickMap

        self:createOverlays()
    end)

    InGameMenuMapFrame.setupMapOverview = Utils.overwrittenFunction(InGameMenuMapFrame.setupMapOverview, function(frame, superFunc)
        superFunc(frame)

        if frame.bmpThreatPageIndex ~= nil then
            return
        end

        table.insert(frame.mapSelectorTexts, getL10nText("bmp_mapOverviewThreats", "Threats"))
        frame.bmpThreatPageIndex = #frame.mapSelectorTexts
        frame.mapOverviewSelector:setTexts(frame.mapSelectorTexts)

        frame.dataTables[frame.bmpThreatPageIndex] = {}
        frame.filterStates[frame.bmpThreatPageIndex] = {}
        frame.numSelectedFilters[frame.bmpThreatPageIndex] = 0

        frame.subCategoryDotBox:addElement(frame.subCategoryDotBox.elements[1]:clone(frame.subCategoryDotBox))
        frame.subCategoryDotBox:invalidateLayout()

        for index, dot in pairs(frame.subCategoryDotBox.elements) do
            local currentIndex = index
            function dot.getIsSelected()
                return frame.mapOverviewSelector:getState() == currentIndex
            end
        end

        self.subSelector = frame.mapOverviewSelector:clone(frame.filterBox)
        local texts = {}
        for _, valueMap in ipairs(self.valueMaps) do
            if valueMap:getShowInMenu() then
                table.insert(texts, valueMap:getOverviewLabel())
            end
        end

        self.subSelector:setTexts(texts)
        self.subSelectorBaseY = self.subSelector.position[2]
        local _, yOffset = getNormalizedScreenValues(0, 80)
        self.subSelector:setPosition(nil, self.subSelector.position[2] - yOffset)
        function self.subSelector.onClickCallback(_, state)
            self:onThreatSelectorChanged(state)
        end
        self.subSelector:addDefaultElements()

        self.subDotBox = frame.subCategoryDotBox:clone(frame.filterBox)
        self.subDotBoxBaseY = self.subDotBox.position[2]
        local _, dotYOffset = getNormalizedScreenValues(0, 75)
        self.subDotBox:setPosition(nil, self.subDotBox.position[2] - dotYOffset)

        local numDots = #self.subDotBox.elements
        if numDots < #texts then
            for _ = 1, #texts - numDots do
                self.subDotBox:addElement(self.subDotBox.elements[1]:clone(self.subDotBox))
            end
        elseif #texts < numDots then
            for _ = 1, numDots - #texts do
                self.subDotBox.elements[#self.subDotBox.elements]:delete()
            end
        end

        for index, dot in pairs(self.subDotBox.elements) do
            local currentIndex = index
            function dot.getIsSelected()
                return self.subSelector:getState() == currentIndex
            end
        end
        self.subDotBox:invalidateLayout()

        self.filterListContainerPositionY = frame.filterListContainer.position[2]
        self.filterListContainerSizeY = frame.filterListContainer.size[2]
        self.filterListSizeY = frame.filterList.size[2]
        self.filterListSliderSizeY = frame.filterListSlider.size[2]
        self.filterListSliderElementSizeY = frame.filterListSlider.elements[1].size[2]
        self.buttonPositionDeselectAllDefault = frame.buttonDeselectAllContainer.position[2]
        local _, btnOffset = getNormalizedScreenValues(0, 16)
        self.buttonPositionDeselectAll = frame.buttonDeselectAllContainer.position[2] + btnOffset

        frame.ingameMap.onDrawPostIngameMapCallback = InGameMenuMapFrame.onDrawPostIngameMap
        frame.ingameMap.onDrawPostIngameMapHotspotsCallback = InGameMenuMapFrame.onDrawPostIngameMapHotspots
        frame.ingameMap.onClickMapCallback = InGameMenuMapFrame.onClickMap
        frame.filterList:reloadData()
    end)

    InGameMenuMapFrame.onClickMapOverviewSelector = Utils.overwrittenFunction(InGameMenuMapFrame.onClickMapOverviewSelector, function(frame, superFunc, state)
        superFunc(frame, state)

        if self.subSelector == nil or self.subDotBox == nil then
            return
        end

        if state == frame.bmpThreatPageIndex then
            self.subSelector:setVisible(true)
            self.subDotBox:setVisible(true)
            frame.filterListContainer:setVisible(true)
            frame.buttonDeselectAllContainer:setVisible(true)
            self:applyThreatPageLayout(frame)
            self:onThreatSelectorChanged(self.subSelector:getState())
        else
            self.subSelector:setVisible(false)
            self.subDotBox:setVisible(false)
            frame.filterListContainer:setPosition(nil, self.filterListContainerPositionY)
            frame.filterListContainer:setSize(nil, self.filterListContainerSizeY, true)
            frame.filterList:setSize(nil, self.filterListSizeY, true)
            frame.filterListSlider:setSize(nil, self.filterListSliderSizeY, true)
            frame.filterListSlider.elements[1]:setSize(nil, self.filterListSliderElementSizeY, true)
            frame.buttonDeselectAllContainer:setPosition(nil, self.buttonPositionDeselectAllDefault)
        end
    end)

    InGameMenuMapFrame.getHasChangeableFilterList = Utils.overwrittenFunction(InGameMenuMapFrame.getHasChangeableFilterList, function(frame, superFunc, ...)
        return superFunc(frame, ...) or frame.mapOverviewSelector:getState() == frame.bmpThreatPageIndex
    end)

    InGameMenuMapFrame.onDrawPostIngameMap = Utils.overwrittenFunction(InGameMenuMapFrame.onDrawPostIngameMap, function(frame, superFunc, element, ingameMap, ...)
        if frame.mapOverviewSelector:getState() == frame.bmpThreatPageIndex then
            local previousHideOverlay = frame.hideContentOverlay
            frame.hideContentOverlay = true
            superFunc(frame, element, ingameMap, ...)
            frame.hideContentOverlay = previousHideOverlay

            local width, height = frame.ingameMapBase.fullScreenLayout:getMapSize()
            local x, y = frame.ingameMapBase.fullScreenLayout:getMapPosition()
            self:drawThreatOverlays(x + width * 0.25, y + height * 0.25, width * 0.5, height * 0.5)
            frame.dynamicMapImageLoadingBg:setVisible(false)
            return
        end

        superFunc(frame, element, ingameMap, ...)
    end)

    InGameMenuMapFrame.generateOverviewOverlay = Utils.overwrittenFunction(InGameMenuMapFrame.generateOverviewOverlay, function(frame, superFunc, ...)
        superFunc(frame, ...)

        if frame.bmpThreatPageIndex ~= nil and frame.mapOverviewSelector:getState() == frame.bmpThreatPageIndex then
            self:updateThreatOverlay()
        end
    end)

    InGameMenuMapFrame.update = Utils.overwrittenFunction(InGameMenuMapFrame.update, function(frame, superFunc, dt)
        superFunc(frame, dt)

        if frame.bmpThreatPageIndex == nil or frame.mapOverviewSelector == nil then
            return
        end

        if frame.mapOverviewSelector:getState() ~= frame.bmpThreatPageIndex then
            self.overlayUpdateTimer = 0
            return
        end

        self:applyThreatPageLayout(frame)

        self.overlayUpdateTimer = (self.overlayUpdateTimer or 0) + (dt or 0)
        if self.overlayUpdateTimer < (self.overlayUpdateInterval or 1000) then
            return
        end
        self.overlayUpdateTimer = 0

        local currentRevision = DiseaseGrid ~= nil and (DiseaseGrid.changeRevision or 0) or 0
        if currentRevision ~= (self.lastOverlayRevision or -1) then
            self:refreshThreatOverlayVisualization()
        end
    end)

    InGameMenuMapFrame.setColorBlindMode = Utils.overwrittenFunction(InGameMenuMapFrame.setColorBlindMode, function(frame, superFunc, isColorBlindMode)
        superFunc(frame, isColorBlindMode)
        self:setColorBlindMode()

        if frame.bmpThreatPageIndex ~= nil and frame.mapOverviewSelector:getState() == frame.bmpThreatPageIndex then
            self:updateThreatOverlay()
        end
    end)

    self.isInstalled = true
end

DiseaseMapFrame = DiseaseMapFrame.new()
DiseaseMapFrame:initialize()
DiseaseMapFrame:install()
