DiseaseMiniMap = {}
DiseaseMiniMap.MOD_NAME = g_currentModName or "FS25_CropDiseases_BMP"
DiseaseMiniMap.OVERLAY_RESOLUTION = 1024
DiseaseMiniMap.MODE = {
    OFF = 0,
    OVERVIEW = 1,
    DISEASES = 2,
    PESTS = 3,
    SEVERITY = 4
}

addModEventListener(DiseaseMiniMap)

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 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

function DiseaseMiniMap:loadMap()
    self.mode = DiseaseMiniMap.MODE.OFF
    self.baseOverlay = nil
    self.valueOverlay = nil
    self.actionEvents = {}
    self.actionsRegistered = false
    self.hooksInstalled = false
    self.hudInstanceHooked = false
    self.courseplayHooked = false
    self.lastInputContext = nil
    self.lastOverlayRevision = -1
    self.lastOverlayRenderTime = -1
    self.isColorBlindMode = g_gameSettings ~= nil and g_gameSettings:getValue(GameSettings.SETTING.USE_COLORBLIND_MODE) or false
    self.overlayReady = {
        base = false,
        value = false
    }
end

function DiseaseMiniMap:getCurrentValueMap()
    if DiseaseMapFrame == nil or DiseaseMapFrame.valueMaps == nil then
        return nil
    end

    if self.mode == DiseaseMiniMap.MODE.OVERVIEW then
        return DiseaseMapFrame.valueMaps[1]
    elseif self.mode == DiseaseMiniMap.MODE.DISEASES then
        return DiseaseMapFrame.valueMaps[2]
    elseif self.mode == DiseaseMiniMap.MODE.PESTS then
        return DiseaseMapFrame.valueMaps[3]
    elseif self.mode == DiseaseMiniMap.MODE.SEVERITY then
        return DiseaseMapFrame.valueMaps[4]
    end

    return nil
end

function DiseaseMiniMap:deleteMap()
    self:unregisterActionEvents()

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

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

function DiseaseMiniMap:update(dt)
    if not self.hooksInstalled then
        self:installHooks()
    end

    self:tryHookHudInstance()
    self:tryHookCourseplay()

    local currentContext = g_inputBinding ~= nil and g_inputBinding.getContextName ~= nil and g_inputBinding:getContextName() or nil
    if currentContext ~= nil and currentContext ~= self.lastInputContext then
        self.lastInputContext = currentContext
        self.actionsRegistered = false
    end

    if not self.actionsRegistered then
        self:registerActionEvents()
    end

    if self.mode == DiseaseMiniMap.MODE.OFF then
        return
    end

    local colorBlind = g_gameSettings ~= nil and g_gameSettings:getValue(GameSettings.SETTING.USE_COLORBLIND_MODE) or false
    local revision = DiseaseGrid ~= nil and (DiseaseGrid.changeRevision or 0) or 0
    if colorBlind ~= self.isColorBlindMode then
        self.isColorBlindMode = colorBlind
        self:rebuildOverlays()
        return
    end

    if revision ~= (self.lastOverlayRevision or -1) then
        self:refreshOverlayVisualization()
    end
end

function DiseaseMiniMap:getHudIngameMap()
    local mission = g_currentMission
    if mission == nil or mission.hud == nil then
        return nil
    end

    return mission.hud.ingameMap or mission.hud.inGameMap or mission.hud.minimap or mission.hud.miniMap
end

function DiseaseMiniMap:getIsHudMinimap(mapSelf)
    local hudMap = self:getHudIngameMap()
    if hudMap == nil or mapSelf ~= hudMap then
        return false
    end

    if mapSelf.isFullscreen == true then
        return false
    end

    if mapSelf.isVisible ~= nil and mapSelf.isVisible ~= true then
        return false
    end

    if g_gui ~= nil and g_gui.getIsGuiVisible ~= nil and g_gui:getIsGuiVisible() and not g_gui:getIsOverlayGuiVisible() then
        return false
    end

    return true
end

function DiseaseMiniMap:createOverlays()
    if self.baseOverlay == nil then
        self.baseOverlay = createDensityMapVisualizationOverlay("bmpMiniMapThreatBase", DiseaseMiniMap.OVERLAY_RESOLUTION, DiseaseMiniMap.OVERLAY_RESOLUTION)
    end

    if self.valueOverlay == nil then
        self.valueOverlay = createDensityMapVisualizationOverlay("bmpMiniMapThreatValue", DiseaseMiniMap.OVERLAY_RESOLUTION, DiseaseMiniMap.OVERLAY_RESOLUTION)
    end
end

function DiseaseMiniMap:rebuildOverlays()
    self:createOverlays()

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

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

function DiseaseMiniMap:refreshOverlayVisualization()
    if self.valueOverlay == nil then
        self:rebuildOverlays()
        return
    end

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

function DiseaseMiniMap:updateOverlayReadyState()
    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.value and getIsThreatOverlayReady(self.valueOverlay) then
        self.overlayReady.value = true
    end
end

function DiseaseMiniMap:renderMapOverlay(mapSelf, overlayId)
    if overlayId == nil or overlayId == 0 or mapSelf == nil or mapSelf.layout == nil then
        return
    end

    local width, height = mapSelf.layout:getMapSize()
    local x, y = mapSelf.layout:getMapPosition()
    local pivotX, pivotY = mapSelf.layout:getMapPivot()

    local mx = x + width * mapSelf.mapExtensionOffsetX
    local my = y + height * mapSelf.mapExtensionOffsetZ

    local sizeX = width * mapSelf.mapExtensionScaleFactor
    local sizeY = height * mapSelf.mapExtensionScaleFactor
    local rotationPivotX = (pivotX + x) - mx
    local rotationPivotY = (pivotY + y) - my

    if mapSelf.clipX1 ~= nil then
        local u1, v1, u2, v2, u3, v3, u4, v4
        mx, my, sizeX, sizeY, u1, v1, u2, v2, u3, v3, u4, v4 = Overlay.getClippingUVs(
            Overlay.DEFAULT_UVS,
            mx,
            my,
            sizeX,
            sizeY,
            mapSelf.clipX1,
            mapSelf.clipY1,
            mapSelf.clipX2,
            mapSelf.clipY2
        )

        if u1 == nil then
            return
        end

        setOverlayUVs(overlayId, u1, v1, u2, v2, u3, v3, u4, v4)
    end

    setOverlayRotation(overlayId, mapSelf.layout:getMapRotation(), rotationPivotX, rotationPivotY)
    setOverlayColor(overlayId, 1, 1, 1, math.sqrt(mapSelf.layout:getMapAlpha() or 1))
    renderOverlay(overlayId, mx, my, sizeX, sizeY)

    if mapSelf.clipX1 ~= nil then
        setOverlayUVs(overlayId, unpack(Overlay.DEFAULT_UVS))
    end
end

function DiseaseMiniMap:drawOverlays(mapSelf)
    if self.mode == DiseaseMiniMap.MODE.OFF or not self:getIsHudMinimap(mapSelf) then
        return
    end

    local now = g_time or 0
    if self.lastOverlayRenderTime == now then
        return
    end

    if self.lastOverlayRevision == -1 then
        self:rebuildOverlays()
    end

    self:updateOverlayReadyState()
    if not self.overlayReady.base and not self.overlayReady.value then
        return
    end

    if self.overlayReady.base then
        self:renderMapOverlay(mapSelf, self.baseOverlay)
    end

    if self.overlayReady.value then
        self:renderMapOverlay(mapSelf, self.valueOverlay)
    end

    self.lastOverlayRenderTime = now
end

function DiseaseMiniMap:drawModeLabel(mapSelf)
    if self.mode == DiseaseMiniMap.MODE.OFF or not self:getIsHudMinimap(mapSelf) then
        return
    end

    local layout = mapSelf ~= nil and mapSelf.layout or nil
    local valueMap = self:getCurrentValueMap()
    if layout == nil or valueMap == nil or layout.drawCoordinates == nil then
        return
    end

    local label = valueMap.getOverviewLabel ~= nil and valueMap:getOverviewLabel() or getL10nText("bmp_minimapThreatsLabel", "THREATS")
    local oldOffset = layout.coordOffsetY or 0
    local lineStep = (layout.coordinateFontSize or 0.012) * 1.25

    layout.coordOffsetY = oldOffset - lineStep * 2.0
    layout:drawCoordinates(label)
    layout.coordOffsetY = oldOffset
end

function DiseaseMiniMap:actionToggle()
    self.mode = self.mode + 1
    if self.mode > DiseaseMiniMap.MODE.SEVERITY then
        self.mode = DiseaseMiniMap.MODE.OFF
    end

    self.lastOverlayRevision = -1
    self.overlayReady.base = false
    self.overlayReady.value = false

    if self.mode ~= DiseaseMiniMap.MODE.OFF then
        self:rebuildOverlays()
    end

    self.lastOverlayRenderTime = -1

    self:updateActionEventText()
end

function DiseaseMiniMap:tryHookHudInstance()
    if self.hudInstanceHooked then
        return
    end

    if Utils == nil or Utils.appendedFunction == nil then
        return
    end

    local map = self:getHudIngameMap()
    if map == nil or map.drawFields == nil then
        return
    end

    map.drawFields = Utils.appendedFunction(map.drawFields, function(mapSelf)
        if DiseaseMiniMap ~= nil then
            DiseaseMiniMap:drawOverlays(mapSelf)
        end
    end)

    self.hudInstanceHooked = true
    print("[FS25_CropDiseases_BMP] Late hook: hud.ingameMap.drawFields hooked")
end

function DiseaseMiniMap:tryHookCourseplay()
    if self.courseplayHooked then
        return
    end

    if Courseplay == nil or Courseplay.drawHudMap == nil then
        return
    end

    if Utils == nil or Utils.overwrittenFunction == nil then
        return
    end

    Courseplay.drawHudMap = Utils.overwrittenFunction(Courseplay.drawHudMap, function(mapSelf, superFunc, ...)
        if DiseaseMiniMap ~= nil then
            DiseaseMiniMap:drawOverlays(mapSelf)
        end

        if superFunc ~= nil then
            return superFunc(mapSelf, ...)
        end

        return nil
    end)

    self.courseplayHooked = true
    print("[FS25_CropDiseases_BMP] Late hook: Courseplay.drawHudMap hooked")
end

function DiseaseMiniMap:updateActionEventText()
    if g_inputBinding == nil then
        return
    end

    local text = getL10nText("input_BMP_DISEASE_MINIMAP_TOGGLE", "Cycle minimap threat layers")
    for _, eventId in ipairs(self.actionEvents or {}) do
        if eventId ~= nil then
            g_inputBinding:setActionEventTextVisibility(eventId, true)
            g_inputBinding:setActionEventTextPriority(eventId, GS_PRIO_VERY_LOW)
            g_inputBinding:setActionEventText(eventId, text)
        end
    end
end

function DiseaseMiniMap:unregisterActionEvents()
    if g_inputBinding == nil then
        return
    end

    for _, eventId in ipairs(self.actionEvents or {}) do
        if eventId ~= nil then
            g_inputBinding:removeActionEvent(eventId)
        end
    end

    self.actionEvents = {}
    self.actionsRegistered = false
end

function DiseaseMiniMap:registerActionEventsForContext(contextName)
    if g_inputBinding == nil or contextName == nil then
        return
    end

    g_inputBinding:beginActionEventsModification(contextName)
    local _, eventId = g_inputBinding:registerActionEvent(
        "BMP_DISEASE_MINIMAP_TOGGLE",
        self,
        self.actionToggle,
        false,
        true,
        false,
        true
    )
    g_inputBinding:endActionEventsModification(true)

    if eventId ~= nil then
        table.insert(self.actionEvents, eventId)
    end
end

function DiseaseMiniMap:registerActionEvents()
    self:unregisterActionEvents()

    self:registerActionEventsForContext((PlayerInputComponent ~= nil and PlayerInputComponent.INPUT_CONTEXT_NAME) or "PLAYER")
    self:registerActionEventsForContext((Vehicle ~= nil and Vehicle.INPUT_CONTEXT_NAME) or "VEHICLE")
    self:updateActionEventText()

    self.actionsRegistered = #self.actionEvents > 0
end

function DiseaseMiniMap:installHooks()
    if self.hooksInstalled or IngameMap == nil then
        return
    end

    if IngameMap.drawFields ~= nil then
        IngameMap.drawFields = Utils.overwrittenFunction(IngameMap.drawFields, function(mapSelf, superFunc, ...)
            superFunc(mapSelf, ...)
            self:drawOverlays(mapSelf)
        end)
    end

    if IngameMap.drawPlayersCoordinates ~= nil then
        IngameMap.drawPlayersCoordinates = Utils.overwrittenFunction(IngameMap.drawPlayersCoordinates, function(mapSelf, superFunc, ...)
            superFunc(mapSelf, ...)
            self:drawModeLabel(mapSelf)
        end)
    end

    if PlayerInputComponent ~= nil and PlayerInputComponent.registerActionEvents ~= nil then
        PlayerInputComponent.registerActionEvents = Utils.overwrittenFunction(PlayerInputComponent.registerActionEvents, function(target, superFunc, ...)
            superFunc(target, ...)
            self.actionsRegistered = false
        end)
    end

    if Vehicle ~= nil and Vehicle.registerActionEvents ~= nil then
        Vehicle.registerActionEvents = Utils.overwrittenFunction(Vehicle.registerActionEvents, function(target, superFunc, ...)
            superFunc(target, ...)
            self.actionsRegistered = false
        end)
    end

    self.hooksInstalled = true
end
