-- Copyright ©2024-2025 by Todd Hundersmarck (ThundR) 
-- All Rights Reserved

--[[

Unauthorized use and/or distribution of this work entitles myself, the author, to unlimited free and unrestricted use, access, and distribution of any works related to the unauthorized user and/or distributor.

--]]

_G.THDMFuncType = {
    GET = 1,
    SET = 2,
    ADD = 3
}
THDensityMapController = {}
local THDensityMapController_mt = Class(THDensityMapController)
THDensityMapController.CLASS_NAME = "THDensityMapController"
THDensityMapController.MESSAGE = {
    FUNCTION_QUEUE_PAUSED = "Active function queue is currently paused"
}
local debugFlagId = THUtils.createDebugFlagId(THDensityMapController.CLASS_NAME)
THDensityMapController.debugFlagId = debugFlagId
local function initScript()
    local self = THDensityMapController.new()
    if self ~= nil then
        _G.g_thDensityMapController = self
        _G.DensityMapModifier = g_thGlobalEnv.DensityMapModifier
        _G.DensityMapFilter = g_thGlobalEnv.DensityMapFilter
        _G.DensityMapMultiModifier = g_thGlobalEnv.DensityMapMultiModifier
    end
end
function THDensityMapController.new(customMt)
    customMt = customMt or THDensityMapController_mt
    if THUtils.argIsValid(type(customMt) == THValueType.TABLE, "customMt", customMt) then
        local self = setmetatable({}, customMt)
        self.isPostInitFinished = false
        self.activeFunctionQueue = {}
        self.isActiveFunctionQueuePaused = false
        self.eventListeners = {}
        self.dmCallingParams = {}
        self.dmReturnParams = {}
        self.dmLayerIds = {}
        g_thEventManager:addEventListener(self)
        return self
    end
end
function THDensityMapController.onPostInit(self)
    if not self.isPostInitFinished then
        if g_thEventManager == nil or #self.eventListeners > 0 then
            THUtils.overwriteFunction("DensityMapMultiModifier", "addExecuteSet", false, false, self, THDensityMapController.overwrite_dmMultiModifierAddExecuteSet)
            THUtils.overwriteFunction("DensityMapMultiModifier", "addExecuteSetWithStats", false, false, self, THDensityMapController.overwrite_dmMultiModifierAddExecuteSetWithStats)
            THUtils.overwriteFunction("DensityMapMultiModifier", "execute", false, false, self, THDensityMapController.overwrite_dmMultiModifierExecute)
            THUtils.overwriteFunction("DensityMapModifier", "new", false, false, self, THDensityMapController.overwrite_dmModifierNew)
            THUtils.overwriteFunction("DensityMapModifier", "resetDensityMapAndChannels", false, false, self, THDensityMapController.overwrite_dmModifierResetDensityMapAndChannels)
            THUtils.overwriteFunction("DensityMapFilter", "new", false, false, self, THDensityMapController.overwrite_dmFilterNew)
            THUtils.overwriteFunction("DensityMapFilter", "resetDensityMapAndChannels", false, false, self, THDensityMapController.overwrite_dmFilterResetDensityMapAndChannels)
            THUtils.overwriteFunction("DensityMapModifier", "executeGet", false, false, self, THDensityMapController.overwrite_dmModifierExecuteGet)
            THUtils.overwriteFunction("DensityMapModifier", "executeAdd", false, false, self, THDensityMapController.overwrite_dmModifierExecuteAdd)
            THUtils.overwriteFunction("DensityMapModifier", "executeAddWithStats", false, false, self, THDensityMapController.overwrite_dmModifierExecuteAddWithStats)
            THUtils.overwriteFunction("DensityMapModifier", "executeSet", false, false, self, THDensityMapController.overwrite_dmModifierExecuteSet)
            THUtils.overwriteFunction("DensityMapModifier", "executeSetWithStats", false, false, self, THDensityMapController.overwrite_dmModifierExecuteSetWithStats)
        end
        self.isPostInitFinished = true
    end
end
function THDensityMapController.getDensityMapLayerId(self, dmUserDataId)
    local idVarType = type(dmUserDataId)
    if idVarType == THValueType.STRING then
        return self.dmLayerIds[dmUserDataId]
    elseif idVarType == THValueType.USERDATA then
        return self.dmLayerIds[tostring(dmUserDataId)]
    elseif dmUserDataId ~= nil then
        THUtils.errorMsg(true, THMessage.ARGUMENT_INVALID, "dmUserDataId", dmUserDataId)
    end
end
function THDensityMapController.setDensityMapLayerId(self, dmUserData, layerId, force)
    if THUtils.argIsValid(type(dmUserData) == THValueType.USERDATA, "dmUserData", dmUserData) then
        force = THUtils.validateArg(not force or force == true, "force", force, false)
        if force or self:getActiveFunctionCache() ~= nil then
            local dmUserDataId = tostring(dmUserData)
            if type(layerId) == THValueType.USERDATA then
                layerId = self:getDensityMapLayerId(layerId)
            end
            if THUtils.getIsType(layerId, THValueType.INTEGER) and layerId > 0 then
                self.dmLayerIds[dmUserDataId] = layerId
                return dmUserDataId
            end
        end
    end
end
function THDensityMapController.getActiveFunctionCache(self)
    return self.activeFunctionQueue[1]
end
function THDensityMapController.setActiveFunctionCache(self, thFunctionCache)
    if THUtils.argIsValid(type(thFunctionCache) == THValueType.TABLE and thFunctionCache.id ~= nil, "thFunctionCache", thFunctionCache) then
        local thLastFunctionCache = self:getActiveFunctionCache()
        if thLastFunctionCache == nil
            or thLastFunctionCache.id ~= thFunctionCache.id
        then
            table.insert(self.activeFunctionQueue, 1, thFunctionCache)
            return true
        end
    end
    return false
end
function THDensityMapController.restoreLastActiveFunctionCache(self)
    local numQueuedFunctions = #self.activeFunctionQueue
    if numQueuedFunctions > 0 then
        table.remove(self.activeFunctionQueue, 1)
        return self:getActiveFunctionCache()
    end
end
function THDensityMapController.resetActiveFunctionQueue(self)
    THUtils.clearTable(self.activeFunctionQueue)
end
function THDensityMapController.pauseActiveFunctionQueue(self)
    self.isActiveFunctionQueuePaused = true
end
function THDensityMapController.resumeActiveFunctionQueue(self)
    self.isActiveFunctionQueuePaused = false
end
function THDensityMapController.addEventListener(self, target)
    if THUtils.argIsValid(type(target) == THValueType.TABLE, "target", target)
        and THUtils.assert(target ~= self, true, "THDensityMapController cannot listen to itself")
    then
        local isTargetFound = false
        for _, otherTarget in pairs(self.eventListeners) do
            if target == otherTarget then
                isTargetFound = true
            end
        end
        if not isTargetFound then
            local listenerData = {
                target = target
            }
            table.insert(self.eventListeners, listenerData)
        end
    end
end
function THDensityMapController.removeEventListener(self, target)
    if THUtils.argIsValid(type(target) == THValueType.TABLE, "target", target) then
        local numListeners = #self.eventListeners
        if numListeners > 0 then
            for listenerIndex = numListeners, 1, -1 do
                local listenerData = self.eventListeners[listenerIndex]
                if target == listenerData.target then
                    table.remove(self.eventListeners, listenerIndex)
                end
            end
        end
    end
end
function THDensityMapController.raiseEvent(self, eventName, ...)
    local numEventListeners = #self.eventListeners
    if numEventListeners > 0 then
        self:pauseActiveFunctionQueue()
        for listenerIndex = 1, numEventListeners do
            local listenerData = self.eventListeners[listenerIndex]
            local target = listenerData.target
            if target[eventName] ~= nil then
                THUtils.pcall(target[eventName], target, ...)
            end
        end
        self:resumeActiveFunctionQueue()
    end
end
function THDensityMapController.getEventParams(self, dmUserData, ...)
    if THUtils.argIsValid(type(dmUserData) == THValueType.USERDATA, "dmUserData", dmUserData) then
        local callingParams = self.dmCallingParams[dmUserData]
        local returnParams = self.dmReturnParams[dmUserData]
        if callingParams == nil then
            callingParams = { dmFilters = { n = 0 } }
            self.dmCallingParams[dmUserData] = callingParams
        else
            for key in pairs(callingParams) do
                if key ~= "dmFilters" then
                    callingParams[key] = nil
                end
            end
        end
        THUtils.safePack(callingParams.dmFilters, ...)
        if returnParams == nil then
            returnParams = {}
            self.dmReturnParams[dmUserData] = returnParams
        else
            THUtils.clearTable(returnParams)
        end
        return callingParams, returnParams
    end
end
function THDensityMapController.overwrite_dmModifierNew(self, superFunc, dataPlaneId, startChannel, numChannels, terrainNode, ...)
    local function appendFunc(rParent, ...)
        if rParent ~= nil and dataPlaneId ~= nil then
            THUtils.pcall(function()
                self:setDensityMapLayerId(rParent, dataPlaneId)
            end)
        end
        return rParent, ...
    end
    return appendFunc(superFunc(dataPlaneId, startChannel, numChannels, terrainNode, ...))
end
function THDensityMapController.overwrite_dmFilterNew(self, superFunc, dataPlaneId, startChannel, numChannels, ...)
    local function appendFunc(rParent, ...)
        if rParent ~= nil and dataPlaneId ~= nil then
            THUtils.pcall(function()
                self:setDensityMapLayerId(rParent, dataPlaneId)
            end)
        end
        return rParent, ...
    end
    return appendFunc(superFunc(dataPlaneId, startChannel, numChannels, ...))
end
function THDensityMapController.overwrite_dmModifierResetDensityMapAndChannels(self, superFunc, parent, dataPlaneId, ...)
    local function appendFunc(...)
        if dataPlaneId ~= nil then
            THUtils.pcall(function()
                self:setDensityMapLayerId(parent, dataPlaneId)
            end)
        end
        return ...
    end
    return appendFunc(superFunc(parent, dataPlaneId, ...))
end
function THDensityMapController.overwrite_dmFilterResetDensityMapAndChannels(self, superFunc, parent, dataPlaneId, ...)
    local function appendFunc(...)
        if dataPlaneId ~= nil then
            THUtils.pcall(function()
                self:setDensityMapLayerId(parent, dataPlaneId)
            end)
        end
        return ...
    end
    return appendFunc(superFunc(parent, dataPlaneId, ...))
end
function THDensityMapController.overwrite_dmModifierExecuteShared(self, parent, funcType, hasStats, superFunc, value, ...)
    local thFunctionCache, callingParams, returnParams = nil, nil, nil
    local funcName = nil
    local prependSuccess = false
    if not self.isActiveFunctionQueuePaused then
        THUtils.pcall(function(...)
            thFunctionCache = self:getActiveFunctionCache()
            if thFunctionCache ~= nil then
                if funcType == THDMFuncType.ADD then
                    if hasStats then
                        funcName = "executeAddWithStats"
                    else
                        funcName = "executeAdd"
                    end
                elseif funcType == THDMFuncType.SET then
                    if hasStats then
                        funcName = "executeSetWithStats"
                    else
                        funcName = "executeSet"
                    end
                elseif funcType == THDMFuncType.GET then
                    funcName = "executeGet"
                    hasStats = false
                else
                    THUtils.errorMsg(true, THMessage.ARGUMENT_INVALID, "funcType", funcType)
                end
                if funcName ~= nil then
                    callingParams, returnParams = self:getEventParams(parent, ...)
                    if callingParams ~= nil and returnParams ~= nil then
                        callingParams.dmParent = parent
                        callingParams.dmFuncName = funcName
                        callingParams.dmFuncType = funcType
                        callingParams.hasStats = hasStats
                        if funcType ~= THDMFuncType.GET then
                            callingParams.value = value
                        end
                        self:raiseEvent("dmOnModifierExecute", parent, thFunctionCache, superFunc, callingParams, returnParams)
                        if funcType ~= THDMFuncType.GET then
                            if callingParams.value ~= nil then
                                if value ~= nil and callingParams.value ~= value then
                                    value = callingParams.value
                                end
                            end
                        end
                        prependSuccess = true
                    end
                end
            end
        end, ...)
    end
    if not prependSuccess then
        if funcType == THDMFuncType.GET then
            return superFunc(parent, ...)
        else
            return superFunc(parent, value, ...)
        end
    end
    local function appendFunc(rSumPixels, rChangedArea, rTotalArea, ...)
        if rTotalArea ~= nil then
            THUtils.pcall(function(...)
                local function processReturnValues()
                    if returnParams.addedTotalArea ~= nil then
                        if returnParams.resetTotals then
                            rTotalArea = returnParams.addedTotalArea
                        else
                            rTotalArea = rTotalArea + returnParams.addedTotalArea
                        end
                        returnParams.addedTotalArea = nil
                    end
                    if returnParams.addedChangedArea ~= nil then
                        if returnParams.resetTotals or rChangedArea == nil then
                            rChangedArea = math.min(rTotalArea, returnParams.addedChangedArea)
                        else
                            rChangedArea = math.min(rTotalArea, rChangedArea + returnParams.addedChangedArea)
                        end
                        returnParams.addedChangedArea = nil
                    end
                    if returnParams.addedSumPixels ~= nil then
                        if returnParams.resetTotals then
                            rSumPixels = math.min(rTotalArea, returnParams.addedSumPixels)
                        else
                            rSumPixels = math.min(rTotalArea, rSumPixels + returnParams.addedSumPixels)
                        end
                        returnParams.addedSumPixels = nil
                    end
                    returnParams.resetTotals = nil
                    returnParams.noAllowRun = nil
                end
                processReturnValues()
                self:raiseEvent("dmOnModifierExecuteFinished", parent, thFunctionCache, superFunc, returnParams)
                processReturnValues()
            end, ...)
        end
        return rSumPixels, rChangedArea, rTotalArea, ...
    end
    if returnParams ~= nil and returnParams.noAllowRun then
        return appendFunc(0, 0, 0, 0)
    end
    if callingParams ~= nil and callingParams.dmFilters ~= nil then
        if funcType == THDMFuncType.GET then
            return appendFunc(superFunc(parent, THUtils.unpack(callingParams.dmFilters)))
        end
        return appendFunc(superFunc(parent, value, THUtils.unpack(callingParams.dmFilters)))
    end
    if funcType == THDMFuncType.GET then
        return appendFunc(superFunc(parent, ...))
    end
    return appendFunc(superFunc(parent, value, ...))
end
function THDensityMapController.overwrite_dmModifierExecuteGet(self, superFunc, parent, ...)
    return THDensityMapController.overwrite_dmModifierExecuteShared(self, parent, THDMFuncType.GET, false, superFunc, nil, ...)
end
function THDensityMapController.overwrite_dmModifierExecuteAdd(self, superFunc, parent, value, ...)
    return THDensityMapController.overwrite_dmModifierExecuteShared(self, parent, THDMFuncType.ADD, false, superFunc, value, ...)
end
function THDensityMapController.overwrite_dmModifierExecuteAddWithStats(self, superFunc, parent, value, ...)
    return THDensityMapController.overwrite_dmModifierExecuteShared(self, parent, THDMFuncType.ADD, true, superFunc, value, ...)
end
function THDensityMapController.overwrite_dmModifierExecuteSet(self, superFunc, parent, value, ...)
    return THDensityMapController.overwrite_dmModifierExecuteShared(self, parent, THDMFuncType.SET, false, superFunc, value, ...)
end
function THDensityMapController.overwrite_dmModifierExecuteSetWithStats(self, superFunc, parent, value, ...)
    return THDensityMapController.overwrite_dmModifierExecuteShared(self, parent, THDMFuncType.SET, true, superFunc, value, ...)
end
function THDensityMapController.overwrite_dmMultiModifierAddExecuteShared(self, parent, funcType, hasStats, superFunc, statName, value, dmModifier, ...)
    local thFunctionCache, callingParams, returnParams = nil, nil, nil
    local funcName = nil
    local prependSuccess = false
    if not self.isActiveFunctionQueuePaused then
        THUtils.pcall(function(...)
            thFunctionCache = self:getActiveFunctionCache()
            if thFunctionCache ~= nil then
                if funcType == THDMFuncType.ADD then
                    if hasStats then
                        funcName = "addExecuteAddWithStats"
                    else
                        funcName = "addExecuteAdd"
                    end
                elseif funcType == THDMFuncType.SET then
                    if hasStats then
                        funcName = "addExecuteSetWithStats"
                    else
                        funcName = "addExecuteSet"
                    end
                else
                    THUtils.errorMsg(true, THMessage.ARGUMENT_INVALID, "funcType", funcType)
                end
                if funcName ~= nil then
                    callingParams, returnParams = self:getEventParams(parent, ...)
                    if callingParams ~= nil and returnParams ~= nil then
                        callingParams.dmParent = parent
                        callingParams.dmModifier = dmModifier
                        callingParams.dmFuncName = funcName
                        callingParams.dmFuncType = funcType
                        callingParams.hasStats = hasStats
                        callingParams.statName = statName
                        callingParams.value = value
                        self:raiseEvent("dmOnMultiModifierAddExecute", parent, thFunctionCache, superFunc, callingParams, returnParams)
                        if callingParams.dmModifier ~= nil then
                            if dmModifier ~= nil and dmModifier ~= callingParams.dmModifier then
                                dmModifier = callingParams.dmModifier
                            end
                        end
                        if callingParams.value ~= nil then
                            if value ~= nil and value ~= callingParams.value then
                                value = callingParams.value
                            end
                        end
                        if hasStats and callingParams.statName ~= nil then
                            if statName ~= nil and statName ~= callingParams.statName then
                                statName = callingParams.statName
                            end
                        end
                        prependSuccess = true
                    end
                end
            end
        end, ...)
    end
    if not prependSuccess then
        if hasStats then
            return superFunc(parent, statName, value, dmModifier, ...)
        end
        return superFunc(parent, value, dmModifier, ...)
    end
    local function appendFunc(...)
        THUtils.pcall(function(...)
            self:raiseEvent("dmOnMultiModifierAddExecuteFinished", parent, thFunctionCache, superFunc, returnParams)
        end)
        return ...
    end
    if returnParams ~= nil and returnParams.noAllowRun then
        return appendFunc()
    end
    if callingParams ~= nil and callingParams.dmFilters ~= nil then
        if hasStats then
            return appendFunc(superFunc(parent, statName, value, dmModifier, THUtils.unpack(callingParams.dmFilters)))
        end
        return appendFunc(superFunc(parent, value, dmModifier, THUtils.unpack(callingParams.dmFilters)))
    end
    if hasStats then
        return appendFunc(superFunc(parent, statName, value, dmModifier, ...))
    end
    return appendFunc(superFunc(parent, value, dmModifier, ...))
end
function THDensityMapController.overwrite_dmMultiModifierAddExecuteSet(self, superFunc, parent, value, dmModifier, ...)
    return THDensityMapController.overwrite_dmMultiModifierAddExecuteShared(self, parent, THDMFuncType.SET, false, superFunc, nil, value, dmModifier, ...)
end
function THDensityMapController.overwrite_dmMultiModifierAddExecuteSetWithStats(self, superFunc, parent, statName, value, dmModifier, ...)
    return THDensityMapController.overwrite_dmMultiModifierAddExecuteShared(self, parent, THDMFuncType.SET, true, superFunc, statName, value, dmModifier, ...)
end
function THDensityMapController.overwrite_dmMultiModifierExecute(self, superFunc, parent, ...)
    local thFunctionCache = nil
    local prependSuccess = false
    if not self.isActiveFunctionQueuePaused then
        THUtils.pcall(function(...)
            thFunctionCache = self:getActiveFunctionCache()
            if thFunctionCache ~= nil then
                self:raiseEvent("dmOnMultiModifierExecute", parent, thFunctionCache, superFunc)
                prependSuccess = true
            end
        end, ...)
    end
    local function appendFunc(...)
        if prependSuccess then
            THUtils.pcall(function(...)
                self:raiseEvent("dmOnMultiModifierExecuteFinished", parent, thFunctionCache, superFunc)
            end)
        end
        return ...
    end
    return appendFunc(superFunc(parent, ...))
end
THUtils.pcall(initScript)