-- BunkerSiloExtension
-- Version: 1.0.0.0
-- Author: Tidde

if g_bunkerSiloExtension ~= nil then
    bunkerSiloExtension = g_bunkerSiloExtension
else
    bunkerSiloExtension = {}
    g_bunkerSiloExtension = bunkerSiloExtension
end
if bunkerSiloExtension._initialized then
    return
end
bunkerSiloExtension._initialized = true

bunkerSiloExtension.debug = false

bunkerSiloExtension.switchPolicy = {
    liters = 1000  
}

bunkerSiloExtension.luaMappings = {
    { baseFillType = "CHAFF", input = "CHAFF",        output = "SILAGE",            extraFillTypes = { "CHAFF" } },
    { baseFillType = "CHAFF", input = "SILO_GRASS",   output = "GRASS_FERMENTED",   extraFillTypes = { "GRASS_WINDROW", "SILO_GRASS" } },
    { baseFillType = "CHAFF", input = "SILO_ALFALFA", output = "ALFALFA_FERMENTED", extraFillTypes = { "SILO_ALFALFA", "ALFALFA_WINDROW" } },
    { baseFillType = "CHAFF", input = "SILO_CLOVER",  output = "CLOVER_FERMENTED",  extraFillTypes = { "SILO_CLOVER", "CLOVER_WINDROW" } },
}

bunkerSiloExtension.beforeSwitchLookup = {} 
bunkerSiloExtension.baseToExtras       = {} 

function bunkerSiloExtension:getSwitchThreshold(silo)
    local threshold = self.switchPolicy and self.switchPolicy.liters or 1000
    if threshold == nil or threshold < 1 then
        threshold = 1000
    end
    return threshold
end

local function getFillTypeIndex(name)
    if name == nil or name == "" then
        return nil
    end
    return g_fillTypeManager:getFillTypeIndexByName(string.upper(name))
end

local function acceptFillType(silo, index)
    if index ~= nil then
        silo.acceptedFillTypes[index] = true
    end
end

local function getSiloTotalFillLevel(silo)
    if silo.fillLevel ~= nil then
        return silo.fillLevel
    end

    local area = silo.bunkerSiloArea
    if area == nil then
        return 0
    end

    local sum = 0
    for fillTypeIndex, isAccepted in pairs(silo.acceptedFillTypes or {}) do
        if isAccepted then
            local areaLevel = DensityMapHeightUtil.getFillLevelAtArea(fillTypeIndex, area.sx, area.sz, area.wx, area.wz, area.hx, area.hz) or 0
            sum = sum + areaLevel
        end
    end
    return sum
end

local function probeVehicleFillType(vehicle)
    local fillUnitSpec = vehicle and vehicle.spec_fillUnit
    if not fillUnitSpec or not fillUnitSpec.fillUnits then
        return nil
    end

    local bestType, bestLevel = nil, 0
    for _, fillUnit in ipairs(fillUnitSpec.fillUnits) do
        local level = fillUnit.fillLevel or 0
        local fillType = fillUnit.fillType
        if level > bestLevel and fillType and fillType ~= FillType.UNKNOWN then
            bestLevel, bestType = level, fillType
        end
    end
    return bestType
end

function bunkerSiloExtension:loadMap()
    self.beforeSwitchLookup = {}
    self.baseToExtras       = {}

    for _, mapping in ipairs(self.luaMappings) do
        local baseIndex = getFillTypeIndex(mapping.baseFillType)
        local inputIndex   = getFillTypeIndex(mapping.input)
        local outputIndex  = getFillTypeIndex(mapping.output)
        
        if baseIndex and inputIndex and outputIndex then
            local extras = self.baseToExtras[baseIndex]
            if extras == nil then
                extras = {}
                self.baseToExtras[baseIndex] = extras
            end
            
            for _, extraName in ipairs(mapping.extraFillTypes or {}) do
                local extraIndex = getFillTypeIndex(extraName)
                if extraIndex then
                    self.beforeSwitchLookup[extraIndex] = { inIdx = inputIndex, outIdx = outputIndex }
                    extras[extraIndex] = true
                end
            end
        end
    end
end
addModEventListener(bunkerSiloExtension)

local function onBunkerSiloLoad(silo, superFunc, components, xmlFile, key, i3dMappings)
    local success = superFunc(silo, components, xmlFile, key, i3dMappings)

    for baseIndex, extras in pairs(bunkerSiloExtension.baseToExtras) do
        if silo.acceptedFillTypes[baseIndex] then
            for extraIndex, _ in pairs(extras) do
                acceptFillType(silo, extraIndex)
            end
        end
    end

    if silo.bunkerSiloArea ~= nil then
        g_densityMapHeightManager:setConvertingFillTypeAreas(silo.bunkerSiloArea, silo.acceptedFillTypes, silo.inputFillType)
    end

    silo._bunkerSiloExtension_defaultInput  = silo.inputFillType
    silo._bunkerSiloExtension_defaultOutput = silo.outputFillType
    silo._bunkerSiloExtension_locked        = false

    return success
end
BunkerSilo.load = Utils.overwrittenFunction(BunkerSilo.load, onBunkerSiloLoad)

local function trySwitchSiloFillType(silo, vehicle)
    if silo._bunkerSiloExtension_locked then
        return
    end

    local threshold = bunkerSiloExtension:getSwitchThreshold(silo)  
    local currentFillLevel = silo.fillLevel or 0
    if currentFillLevel > threshold then
        return
    end

    local triggerFillType = probeVehicleFillType(vehicle)
    if not triggerFillType then
        return
    end

    local mappedIO = bunkerSiloExtension.beforeSwitchLookup[triggerFillType]
    if not mappedIO or not mappedIO.inIdx or not mappedIO.outIdx then
        return
    end
    
    if mappedIO.inIdx == silo.inputFillType then
        return
    end

    if silo.bunkerSiloArea ~= nil then
        g_densityMapHeightManager:removeConvertingFillTypeAreas(silo.bunkerSiloArea)
        g_densityMapHeightManager:setConvertingFillTypeAreas(silo.bunkerSiloArea, silo.acceptedFillTypes, mappedIO.inIdx)
    end
    
    silo.inputFillType  = mappedIO.inIdx
    silo.outputFillType = mappedIO.outIdx
end

local originalInteractionTriggerCallback = BunkerSilo.interactionTriggerCallback
BunkerSilo.interactionTriggerCallback = function(silo, triggerId, otherId, onEnter, onLeave, onStay, otherShapeId)
    local vehicle = g_currentMission.nodeToObject[otherId] or g_currentMission.nodeToObject[otherShapeId]
    if vehicle ~= nil then
        if onEnter or onStay then
            trySwitchSiloFillType(silo, vehicle)
        end
    end

    if originalInteractionTriggerCallback ~= nil then
        return originalInteractionTriggerCallback(silo, triggerId, otherId, onEnter, onLeave, onStay, otherShapeId)
    end
end

local function onBunkerSiloUpdate(silo, superFunc, dt)
    local threshold = bunkerSiloExtension:getSwitchThreshold(silo)

    if silo._bunkerSiloExtension_locked and getSiloTotalFillLevel(silo) <= 0 then
        if silo.bunkerSiloArea ~= nil then
            g_densityMapHeightManager:removeConvertingFillTypeAreas(silo.bunkerSiloArea)
        end
        silo.inputFillType  = silo._bunkerSiloExtension_defaultInput  or silo.inputFillType
        silo.outputFillType = silo._bunkerSiloExtension_defaultOutput or silo.outputFillType
        if silo.bunkerSiloArea ~= nil then
            g_densityMapHeightManager:setConvertingFillTypeAreas(silo.bunkerSiloArea, silo.acceptedFillTypes, silo.inputFillType)
        end
        silo._bunkerSiloExtension_locked = false
    end

    if not silo._bunkerSiloExtension_locked then
        if getSiloTotalFillLevel(silo) > threshold then
            silo._bunkerSiloExtension_locked = true
        end
    end

    return superFunc(silo, dt)
end
BunkerSilo.update = Utils.overwrittenFunction(BunkerSilo.update, onBunkerSiloUpdate)

if BunkerSilo.setIsFermenting ~= nil then
    local originalSetIsFermenting = BunkerSilo.setIsFermenting
    function BunkerSilo:setIsFermenting(isFermenting)
        if isFermenting and self._bunkerSiloExtension_locked then
            if self.bunkerSiloArea ~= nil then
                g_densityMapHeightManager:removeConvertingFillTypeAreas(self.bunkerSiloArea)
            end
            self.inputFillType  = self._bunkerSiloExtension_defaultInput or self.inputFillType
            self.outputFillType = self._bunkerSiloExtension_defaultOutput or self.outputFillType
            if self.bunkerSiloArea ~= nil then
                g_densityMapHeightManager:setConvertingFillTypeAreas(self.bunkerSiloArea, self.acceptedFillTypes, self.inputFillType)
            end
            self._bunkerSiloExtension_locked = false
        end
        return originalSetIsFermenting(self, isFermenting)
    end
end

if BunkerSilo.setCovered ~= nil then
    local originalSetCovered = BunkerSilo.setCovered
    function BunkerSilo:setCovered(covered, noEventSend)
        if covered then
            if self._bunkerSiloExtension_locked then
                if self.bunkerSiloArea ~= nil then
                    g_densityMapHeightManager:removeConvertingFillTypeAreas(self.bunkerSiloArea)
                end
                self.inputFillType  = self._bunkerSiloExtension_defaultInput or self.inputFillType
                self.outputFillType = self._bunkerSiloExtension_defaultOutput or self.outputFillType
                if self.bunkerSiloArea ~= nil then
                    g_densityMapHeightManager:setConvertingFillTypeAreas(self.bunkerSiloArea, self.acceptedFillTypes, self.inputFillType)
                end
                self._bunkerSiloExtension_locked = false
            end
        else
            if self._bunkerSiloExtension_locked and getSiloTotalFillLevel(self) <= 0 then
                if self.bunkerSiloArea ~= nil then
                    g_densityMapHeightManager:removeConvertingFillTypeAreas(self.bunkerSiloArea)
                end
                self.inputFillType  = self._bunkerSiloExtension_defaultInput or self.inputFillType
                self.outputFillType = self._bunkerSiloExtension_defaultOutput or self.outputFillType
                if self.bunkerSiloArea ~= nil then
                    g_densityMapHeightManager:setConvertingFillTypeAreas(self.bunkerSiloArea, self.acceptedFillTypes, self.inputFillType)
                end
                self._bunkerSiloExtension_locked = false
            end
        end
        return originalSetCovered(self, covered, noEventSend)
    end
end

function bunkerSiloExtension.registerSavegameXMLPaths(schema, basePath)
    schema:register(XMLValueType.STRING, basePath .. "#bunkerSiloExtensionInputFillType",  "bunkerSiloExtension: current input fillType name")
    schema:register(XMLValueType.STRING, basePath .. "#bunkerSiloExtensionOutputFillType", "bunkerSiloExtension: current output fillType name")
end
BunkerSilo.registerSavegameXMLPaths = Utils.appendedFunction(BunkerSilo.registerSavegameXMLPaths, bunkerSiloExtension.registerSavegameXMLPaths)

function bunkerSiloExtension.saveToXMLFile(silo, xmlFile, key, usedModNames)
    if silo.inputFillType ~= nil and silo.outputFillType ~= nil then
        local inName  = g_fillTypeManager:getFillTypeByIndex(silo.inputFillType).name
        local outName = g_fillTypeManager:getFillTypeByIndex(silo.outputFillType).name
        xmlFile:setValue(key .. "#bunkerSiloExtensionInputFillType",  inName)
        xmlFile:setValue(key .. "#bunkerSiloExtensionOutputFillType", outName)
    end
end
BunkerSilo.saveToXMLFile = Utils.appendedFunction(BunkerSilo.saveToXMLFile, bunkerSiloExtension.saveToXMLFile)

local function onBunkerSiloLoadFromXMLFile(silo, superFunc, xmlFile, key)
    local success = superFunc(silo, xmlFile, key)
    local inName  = xmlFile:getValue(key .. "#bunkerSiloExtensionInputFillType")
    local outName = xmlFile:getValue(key .. "#bunkerSiloExtensionOutputFillType")
    
    if inName and outName then
        local inputIndex  = getFillTypeIndex(inName)
        local outputIndex = getFillTypeIndex(outName)
        if inputIndex and outputIndex then
            if silo.bunkerSiloArea ~= nil then
                g_densityMapHeightManager:removeConvertingFillTypeAreas(silo.bunkerSiloArea)
            end
            
            silo.inputFillType  = inputIndex
            silo.outputFillType = outputIndex
            
            if silo.bunkerSiloArea ~= nil then
                g_densityMapHeightManager:setConvertingFillTypeAreas(silo.bunkerSiloArea, silo.acceptedFillTypes, silo.inputFillType)
            end
        end
    end
    return success
end
BunkerSilo.loadFromXMLFile = Utils.overwrittenFunction(BunkerSilo.loadFromXMLFile, onBunkerSiloLoadFromXMLFile)

function bunkerSiloExtension.writeStream(silo, streamId, connection)
    if not connection:getIsServer() then
        streamWriteUIntN(streamId, silo.inputFillType  or FillType.UNKNOWN, FillTypeManager.SEND_NUM_BITS)
        streamWriteUIntN(streamId, silo.outputFillType or FillType.UNKNOWN, FillTypeManager.SEND_NUM_BITS)
    end
end
BunkerSilo.writeStream = Utils.appendedFunction(BunkerSilo.writeStream, bunkerSiloExtension.writeStream)

function bunkerSiloExtension.readStream(silo, streamId, connection, objectId)
    if connection:getIsServer() then
        local inputIndex  = streamReadUIntN(streamId, FillTypeManager.SEND_NUM_BITS)
        local outputIndex = streamReadUIntN(streamId, FillTypeManager.SEND_NUM_BITS)
        
        if inputIndex and outputIndex then
            if silo.bunkerSiloArea ~= nil then
                g_densityMapHeightManager:removeConvertingFillTypeAreas(silo.bunkerSiloArea)
            end
            silo.inputFillType  = inputIndex
            silo.outputFillType = outputIndex
            if silo.bunkerSiloArea ~= nil then
                g_densityMapHeightManager:setConvertingFillTypeAreas(silo.bunkerSiloArea, silo.acceptedFillTypes, silo.inputFillType)
            end
        end
    end
end
BunkerSilo.readStream = Utils.appendedFunction(BunkerSilo.readStream, bunkerSiloExtension.readStream)
