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

--]]

THMapTypeManager = {}
local THMapTypeManager_mt = Class(THMapTypeManager)
THMapTypeManager.CLASS_NAME = "THMapTypeManager"
local debugFlagId = THUtils.createDebugFlagId(THMapTypeManager.CLASS_NAME)
THMapTypeManager.debugFlagId = debugFlagId
local function initScript()
    local self = THMapTypeManager.new()
    if self ~= nil then
        _G.g_thMapTypeManager = self
    end
end
function THMapTypeManager.new(customMt)
    customMt = customMt or THMapTypeManager_mt
    if THUtils.argIsValid(type(customMt) == THValueType.TABLE, "customMt", customMt) then
        local self = setmetatable({}, customMt)
        self.maxFruitState = 0
        self.maxFruitHaulmState = 0
        self.fruitTypes = {
            array = {},
            byName = {},
            byIndex = {}
        }
        self.validFruitStateNames = {}
        self.dataPlaneIdToFruitType = {}
        self.fillTypes = {
            array = {},
            byName = {},
            byIndex = {}
        }
        if g_thEventManager ~= nil then
            g_thEventManager:addEventListener(self)
        end
        return self
    end
end
function THMapTypeManager.onInitTerrain(self, mission, terrainNode, mapI3DFilename)
    local pixelsToSqm = mission:getFruitPixelsToSqm()
    if pixelsToSqm ~= nil and pixelsToSqm > 0 then
        local fruitMetersPerPixel = math.sqrt(pixelsToSqm)
        fruitMetersPerPixel = THUtils.round(fruitMetersPerPixel, 6)
        fruitMetersPerPixel = math.abs(fruitMetersPerPixel)
        self.fruitMetersPerPixel = fruitMetersPerPixel
    end
    self:initFruitTypes()
end
function THMapTypeManager.onLoadMapFinished(self, mission, ...)
    self:initFillTypes()
end
function THMapTypeManager.initFruitTypes(self)
    local fruitTypesArray = g_fruitTypeManager:getFruitTypes()
    local fruitMetersPerPixel = self:getFruitMetersPerPixel()
    self.maxFruitState = 0
    self.maxFruitHaulmState = 0
    THUtils.clearTable(self.fruitTypes.array)
    THUtils.clearTable(self.fruitTypes.byIndex)
    THUtils.clearTable(self.fruitTypes.byName)
    for fruitTypeIndex = 1, #fruitTypesArray do
        local fruitTypeDesc = fruitTypesArray[fruitTypeIndex]
        local thFruitTypeData = {
            desc = fruitTypeDesc,
            name = string.upper(fruitTypeDesc.name),
            index = fruitTypeDesc.index,
            title = fruitTypeDesc.name,
            maxNumStates = 0,
            minGrowthState = 0,
            maxGrowthState = 0,
            minForageState = 0,
            maxForageState = 0,
            minPreparingState = 0,
            maxPreparingState = 0,
            minPreparedState = 0,
            maxPreparedState = 0,
            minHarvestState = 0,
            maxHarvestState = 0,
            minWitheredState = 0,
            maxWitheredState = 0,
            minCutState = 0,
            maxCutState = 0,
            numCutStates = 0,
            cutStates = {},
            minExtraState = 0,
            maxExtraState = 0,
            numBlocksPerUnit = 0,
            plantSeparation = { 0, 0 }
        }
        local fruitTypeYield = THUtils.toNumber(fruitTypeDesc.literPerSqm) or 0
        fruitTypeYield = math.max(0, fruitTypeYield)
        thFruitTypeData.yield = fruitTypeYield
        local fillTypeDesc = fruitTypeDesc.fillType
        if fillTypeDesc == nil then
            THUtils.errorMsg(false, "Cannot find fill type associated with fruit type %q [%s], trying alterative method...", thFruitTypeData.name, thFruitTypeData.index)
            fillTypeDesc = g_fillTypeManager:getFillTypeByName(thFruitTypeData.name)
            if fillTypeDesc == nil then
                THUtils.errorMsg(nil, "No fill type associated with fruit type %q [%s], which could lead to unwanted behavior", thFruitTypeData.name, thFruitTypeData.index)
            end
        end
        if fillTypeDesc ~= nil then
            thFruitTypeData.title = fillTypeDesc.title or fillTypeDesc.name
            thFruitTypeData.fillType = fillTypeDesc
        end
        local windrowDesc = fruitTypeDesc.windrowFillType
        if windrowDesc == nil then
            if fruitTypeDesc.hasWindrow and fruitTypeDesc.windrowName ~= nil then
                windrowDesc = g_fillTypeManager:getFillTypeByName(fruitTypeDesc.windrowName)
            end
        end
        if windrowDesc ~= nil then
            thFruitTypeData.windrowFillType = windrowDesc
            local windrowYield = THUtils.toNumber(fruitTypeDesc.windrowLiterPerSqm)
            if windrowYield ~= nil and windrowYield >= 0 then
                if fruitTypeYield <= 0 then
                    thFruitTypeData.windrowFactor = 0
                else
                    thFruitTypeData.windrowFactor = windrowYield / fruitTypeYield
                end
                thFruitTypeData.windrowYield = windrowYield
            else
                thFruitTypeData.windrowYield = fruitTypeYield
                thFruitTypeData.windrowFactor = 1
            end
        end
        local windrowCutDesc = fruitTypeDesc.windrowCutFillType
        if windrowCutDesc ~= nil then
            thFruitTypeData.windrowCutFillType = windrowCutDesc
            local windrowCutFactor = THUtils.toNumber(fruitTypeDesc.windrowCutFactor)
            if windrowCutFactor ~= nil and windrowCutFactor >= 0 then
                if fruitTypeYield <= 0 then
                    thFruitTypeData.windrowCutYield = 0
                else
                    thFruitTypeData.windrowCutYield = fruitTypeYield * windrowCutFactor
                end
                thFruitTypeData.windrowCutFactor = windrowCutFactor
            else
                thFruitTypeData.windrowCutYield = fruitTypeYield
                thFruitTypeData.windrowCutFactor = 1
            end
        end
        local maxNumStates = 0
        local numStateChannels = THUtils.toNumber(fruitTypeDesc.numStateChannels, true)
        if numStateChannels ~= nil and numStateChannels > 0 then
            maxNumStates = (2 ^ numStateChannels) - 1
        end
        thFruitTypeData.maxNumStates = maxNumStates
        if maxNumStates > 0 then
            thFruitTypeData.maxGrowthState = THUtils.toNumber(fruitTypeDesc.numGrowthStates, true) or 0
            thFruitTypeData.minGrowthState = math.min(1, thFruitTypeData.maxGrowthState)
            thFruitTypeData.maxHarvestState = THUtils.toNumber(fruitTypeDesc.maxHarvestingGrowthState, true) or 0
            thFruitTypeData.minHarvestState = THUtils.toNumber(fruitTypeDesc.minHarvestingGrowthState, true) or thFruitTypeData.maxHarvestState
            thFruitTypeData.minForageState = THUtils.toNumber(fruitTypeDesc.minForageGrowthState, true) or 0
            if thFruitTypeData.minHarvestState > 1 and thFruitTypeData.minForageState > 0 then
                thFruitTypeData.maxForageState = math.max(thFruitTypeData.minForageState, thFruitTypeData.minHarvestState - 1)
            else
                thFruitTypeData.maxForageState = thFruitTypeData.minForageState
            end
            thFruitTypeData.minPreparingState = THUtils.toNumber(fruitTypeDesc.minPreparingGrowthState, true) or 0
            thFruitTypeData.maxPreparingState = THUtils.toNumber(fruitTypeDesc.maxPreparingGrowthState, true) or thFruitTypeData.minPreparingState
            thFruitTypeData.minPreparedState = THUtils.toNumber(fruitTypeDesc.preparedGrowthState, true) or 0
            thFruitTypeData.maxPreparedState = thFruitTypeData.minPreparedState
            thFruitTypeData.minWitheredState = THUtils.toNumber(fruitTypeDesc.witheredState, true) or 0
            thFruitTypeData.maxWitheredState = thFruitTypeData.minWitheredState
        end
        local defaultCutState = THUtils.toNumber(fruitTypeDesc.cutState, true) or 0
        if defaultCutState > 0 then
            table.insert(thFruitTypeData.cutStates, defaultCutState)
        end
        if fruitTypeDesc.cutStates ~= nil then
            for fruitState in pairs(fruitTypeDesc.cutStates) do
                if fruitState > 0 and fruitState ~= defaultCutState then
                    table.insert(thFruitTypeData.cutStates, fruitState)
                end
            end
        end
        local numCutStates = #thFruitTypeData.cutStates
        thFruitTypeData.numCutStates = numCutStates
        if numCutStates > 0 then
            THUtils.sortTable(thFruitTypeData.cutStates, function(pa, pb)
                return pa < pb
            end)
            thFruitTypeData.minCutState = thFruitTypeData.cutStates[1]
            thFruitTypeData.maxCutState = thFruitTypeData.cutStates[numCutStates]
        end
        if maxNumStates > 0 then
            local finalState = thFruitTypeData.maxCutState
            if finalState <= 0 then
                finalState = thFruitTypeData.maxWitheredState
            end
            if finalState > 0 and finalState < maxNumStates then
                thFruitTypeData.minExtraState = math.min(maxNumStates, finalState + 1)
                thFruitTypeData.maxExtraState = maxNumStates
            end
        end
        local numHaulmStates = 0
        local numStateChannelsHaulm = THUtils.toNumber(fruitTypeDesc.numStateChannelsHaulm, true)
        if numStateChannelsHaulm ~= nil and numStateChannelsHaulm > 0 then
            numHaulmStates = (2 ^ numStateChannelsHaulm) - 1
        end
        thFruitTypeData.numHaulmStates = numHaulmStates
        local numBlocksPerUnit = THUtils.toNumber(fruitTypeDesc.numBlocksPerUnit, true) or 0
        local plantSeparation = fruitTypeDesc.plantSeparation
        if plantSeparation ~= nil and #plantSeparation >= 2 then
            local sepWidth = THUtils.toNumber(plantSeparation[1]) or 0
            local sepLength = THUtils.toNumber(plantSeparation[2]) or 0
            thFruitTypeData.plantSeparation = { sepWidth, sepLength }
        end
        if numBlocksPerUnit > 0
            and (thFruitTypeData.plantSeparation[1] <= 0 or thFruitTypeData.plantSeparation[2] <= 0)
        then
            local separation = 1 / numBlocksPerUnit
            thFruitTypeData.numBlocksPerUnit = numBlocksPerUnit
            thFruitTypeData.plantSeparation = { separation, separation }
        elseif thFruitTypeData.plantSeparation[1] > 0 then
            thFruitTypeData.numBlocksPerUnit = 1 / thFruitTypeData.plantSeparation[1]
            if thFruitTypeData.plantSeparation[2] <= 0 then
                thFruitTypeData.plantSeparation[2] = thFruitTypeData.plantSeparation[1]
            end
        elseif thFruitTypeData.plantSeparation[2] > 0 then
            thFruitTypeData.numBlocksPerUnit = 1 / thFruitTypeData.plantSeparation[2]
            thFruitTypeData.plantSeparation[1] = thFruitTypeData.plantSeparation[2]
        elseif fruitMetersPerPixel > 0 then
            thFruitTypeData.numBlocksPerUnit = 1 / fruitMetersPerPixel
            thFruitTypeData.plantSeparation = { fruitMetersPerPixel, fruitMetersPerPixel }
        end
        if self.fruitTypes.byName[thFruitTypeData.name] == nil then
            table.insert(self.fruitTypes.array, thFruitTypeData)
            self.fruitTypes.byName[thFruitTypeData.name] = thFruitTypeData
            self.fruitTypes.byIndex[thFruitTypeData.index] = thFruitTypeData
            if fruitTypeDesc.terrainDataPlaneId ~= nil then
                self.dataPlaneIdToFruitType[fruitTypeDesc.terrainDataPlaneId] = thFruitTypeData
            end
            if fruitTypeDesc.nameToGrowthState ~= nil then
                for fruitStateName in pairs(fruitTypeDesc.nameToGrowthState) do
                    if type(fruitStateName) == THValueType.STRING then
                        fruitStateName = string.upper(fruitStateName)
                    end
                    self.validFruitStateNames[fruitStateName] = true
                end
            end
            self.maxFruitState = math.max(self.maxFruitState, thFruitTypeData.maxNumStates)
            self.maxFruitHaulmState = math.max(self.maxFruitHaulmState, thFruitTypeData.numHaulmStates)
        end
    end
    return true
end
function THMapTypeManager.getFruitType(self, fruitTypeId, verbose)
    verbose = THUtils.validateArg(not verbose or verbose == true, "verbose", verbose, true)
    local idVarType = type(fruitTypeId)
    local thFruitTypeData = nil
    if idVarType == THValueType.STRING then
        thFruitTypeData = self.fruitTypes.byName[fruitTypeId:upper()]
    elseif idVarType == THValueType.NUMBER then
        thFruitTypeData = self.fruitTypes.byIndex[fruitTypeId]
    elseif idVarType == THValueType.TABLE and fruitTypeId.name ~= nil then
        thFruitTypeData = self.fruitTypes.byName[fruitTypeId.name:upper()]
        if thFruitTypeData ~= fruitTypeId then
            thFruitTypeData = nil
        end
    elseif fruitTypeId ~= nil then
        verbose = true
    end
    if thFruitTypeData == nil and verbose then
        THUtils.errorMsg(true, THMessage.ARGUMENT_INVALID, "fruitTypeId", fruitTypeId)
    end
    return thFruitTypeData
end
function THMapTypeManager.getFruitTypes(self)
    return self.fruitTypes.array, #self.fruitTypes.array
end
function THMapTypeManager.getFruitTypeByDataPlaneId(self, dataPlaneId)
    if dataPlaneId ~= nil then
        return self.dataPlaneIdToFruitType[dataPlaneId]
    end
end
function THMapTypeManager.getFruitGrowthState(self, fruitTypeId, fruitStateId, verbose)
    local thFruitTypeData = self:getFruitType(fruitTypeId, verbose)
    local fruitState = nil
    if thFruitTypeData ~= nil then
        local stateIdVarType = type(fruitStateId)
        if stateIdVarType == THValueType.STRING then
            fruitState = thFruitTypeData.desc:getGrowthStateByName(fruitStateId)
        elseif stateIdVarType == THValueType.NUMBER then
            if math.floor(fruitStateId) == fruitStateId
                and fruitStateId >= 0 and fruitStateId <= thFruitTypeData.maxNumStates
            then
                fruitState = fruitStateId
            end
        elseif fruitStateId ~= nil then
            verbose = true
        end
    end
    if fruitState == nil and verbose then
        THUtils.errorMsg(true, THMessage.ARGUMENT_INVALID, "fruitStateId", fruitStateId)
    end
    return fruitState
end
function THMapTypeManager.getFruitMetersPerPixel(self)
    return self.fruitMetersPerPixel or 0
end
function THMapTypeManager.getFruitPixelSpacing(self, value, noZero)
    noZero = THUtils.validateArg(not noZero or noZero == true, "noZero", noZero, true)
    if THUtils.argIsValid(type(value) == THValueType.NUMBER, "value", value) then
        local fruitMetersPerPixel = self:getFruitMetersPerPixel()
        if fruitMetersPerPixel > 0 then
            local newValue = THUtils.round(value / fruitMetersPerPixel) * fruitMetersPerPixel
            if newValue == 0 then
                if noZero then
                    newValue = fruitMetersPerPixel
                end
            else
                newValue = math.abs(newValue)
            end
            return newValue
        end
    end
    return value
end
function THMapTypeManager.getFruitPixelAlignedValue(self, value, isCentered, tolerance)
    tolerance = tolerance or 0
    if THUtils.argIsValid(type(value) == THValueType.NUMBER, "value", value)
        and THUtils.argIsValid(type(tolerance) == THValueType.NUMBER, "tolerance", tolerance)
    then
        isCentered = THUtils.validateArg(not isCentered or isCentered == true, "isCentered", isCentered, false)
        local fruitMetersPerPixel = self:getFruitMetersPerPixel()
        if fruitMetersPerPixel > 0 then
            tolerance = math.abs(tolerance)
            tolerance = THUtils.clamp(tolerance, 0, 0.5)
            local newValue = nil
            if tolerance == 0 then
                newValue = THUtils.round(value / fruitMetersPerPixel) * fruitMetersPerPixel
            else
                local pixelFactor = math.abs((value / fruitMetersPerPixel) % 1)
                if pixelFactor < 0.5 - tolerance then
                    newValue = THUtils.floor(value / fruitMetersPerPixel) * fruitMetersPerPixel
                elseif pixelFactor >= 0.5 + tolerance then
                    newValue = THUtils.ceil(value / fruitMetersPerPixel) * fruitMetersPerPixel
                end
            end
            if newValue ~= nil then
                if isCentered then
                    newValue = newValue - (fruitMetersPerPixel * 0.5)
                end
                return newValue
            end
        end
    end
    return value
end
function THMapTypeManager.initFillTypes(self)
    local fillTypesArray = g_fillTypeManager:getFillTypes()
    THUtils.clearTable(self.fillTypes.array)
    THUtils.clearTable(self.fillTypes.byName)
    THUtils.clearTable(self.fillTypes.byIndex)
    for fillTypeIndex = 1, #fillTypesArray do
        local fillTypeDesc = fillTypesArray[fillTypeIndex]
        local thFillTypeData = {
            desc = fillTypeDesc,
            name = fillTypeDesc.name:upper(),
            index = fillTypeIndex,
            title = fillTypeDesc.title or fillTypeDesc.name
        }
        if self.fillTypes.byName[thFillTypeData.name] == nil then
            table.insert(self.fillTypes.array, thFillTypeData)
            self.fillTypes.byName[thFillTypeData.name] = thFillTypeData
            self.fillTypes.byIndex[thFillTypeData.index] = thFillTypeData
        end
    end
    return true
end
function THMapTypeManager.getFillType(self, fillTypeId, verbose)
    verbose = THUtils.validateArg(not verbose or verbose == true, "verbose", verbose, true)
    local thFillTypeData = nil
    if fillTypeId ~= nil then
        local idVarType = type(fillTypeId)
        if idVarType == THValueType.STRING then
            thFillTypeData = self.fillTypes.byName[fillTypeId:upper()]
        elseif idVarType == THValueType.NUMBER then
            thFillTypeData = self.fillTypes.byIndex[fillTypeId]
        elseif idVarType == THValueType.TABLE and type(fillTypeId.name) == THValueType.STRING then
            thFillTypeData = self.fillTypes.byName[fillTypeId.name:upper()]
        else
            verbose = true
        end
    end
    if thFillTypeData == nil and verbose then
        THUtils.errorMsg(true, THMessage.ARGUMENT_INVALID, "fillTypeId", fillTypeId)
    end
    return thFillTypeData
end
function THMapTypeManager.getFillTypes(self)
    return self.fillTypes.array, #self.fillTypes.array
end
THUtils.pcall(initScript)