--[[
Copyright (C) GtX (Andy), 2022

Author: GtX | Andy
Date: 23.10.2022
Revision: FS22-01

Contact:
https://forum.giants-software.com
https://github.com/GtX-Andy

Important:
Free for use in mods (FS22 Only) - no permission needed.
No modifications may be made to this script, including conversion to other game versions without written permission from GtX | Andy
Copying or removing any part of this code for external use without written permission from GtX | Andy is prohibited.

Frei verwendbar (Nur LS22) - keine erlaubnis nötig
Ohne schriftliche Genehmigung von GtX | Andy dürfen keine Änderungen an diesem Skript vorgenommen werden, einschließlich der Konvertierung in andere Spielversionen
Das Kopieren oder Entfernen irgendeines Teils dieses Codes zur externen Verwendung ohne schriftliche Genehmigung von GtX | Andy ist verboten.
]]

LivestockTrailerSpecial = {}

LivestockTrailerSpecial.MOD_NAME = g_currentModName
LivestockTrailerSpecial.SPEC_NAME = string.format("spec_%s.livestockTrailerSpecial", g_currentModName)

function LivestockTrailerSpecial.prerequisitesPresent(specializations)
    return SpecializationUtil.hasSpecialization(FillUnit, specializations) and SpecializationUtil.hasSpecialization(LivestockTrailer, specializations)
end

function LivestockTrailerSpecial.initSpecialization()
    local schema = Vehicle.xmlSchema

    schema:setXMLSpecializationType("LivestockTrailerSpecial")

    schema:register(XMLValueType.NODE_INDEX, "vehicle.livestockTrailer.baleTrigger#node", "Bale trigger node")
    schema:register(XMLValueType.FLOAT, "vehicle.livestockTrailer.baleTrigger#inputLitresPerSecond", "Bale delete litres per second", 100)
    schema:register(XMLValueType.INT, "vehicle.livestockTrailer.baleTrigger#fillUnitIndex", "Fill Unit Index", 1)

    schema:register(XMLValueType.INT, "vehicle.livestockTrailer.bedding#minimumAnimals", "Minimum number of animals to start conversion timer", 1)
    schema:register(XMLValueType.INT, "vehicle.livestockTrailer.bedding#conversionSeconds", "Conversion rate in seconds when carrying minimum number of animals", 60)
    schema:register(XMLValueType.STRING, "vehicle.livestockTrailer.bedding#fillType", "Bedding fill type", "STRAW")
    schema:register(XMLValueType.STRING, "vehicle.livestockTrailer.bedding#convertedFillType", "Converted fill type", "MANURE")

    schema:setXMLSpecializationType()

    local schemaSavegame = Vehicle.xmlSchemaSavegame

    schemaSavegame:register(XMLValueType.FLOAT, string.format("vehicles.vehicle(?).%s.livestockTrailerSpecial#conversionTime", LivestockTrailerSpecial.MOD_NAME), "Current conversion time")
end

function LivestockTrailerSpecial.registerFunctions(vehicleType)
    SpecializationUtil.registerFunction(vehicleType, "livestockTrailerBaleTriggerCallback", LivestockTrailerSpecial.livestockTrailerBaleTriggerCallback)
end

function LivestockTrailerSpecial.registerEventListeners(vehicleType)
    SpecializationUtil.registerEventListener(vehicleType, "onLoad", LivestockTrailerSpecial)
    SpecializationUtil.registerEventListener(vehicleType, "onLoadFinished", LivestockTrailerSpecial)
    SpecializationUtil.registerEventListener(vehicleType, "onDelete", LivestockTrailerSpecial)
    SpecializationUtil.registerEventListener(vehicleType, "onUpdate", LivestockTrailerSpecial)
    SpecializationUtil.registerEventListener(vehicleType, "onFillUnitFillLevelChanged", LivestockTrailerSpecial)
end

function LivestockTrailerSpecial:onLoad(savegame)
    self.spec_livestockTrailerSpecial = self[LivestockTrailerSpecial.SPEC_NAME]

    if self.spec_livestockTrailerSpecial == nil then
        Logging.error("[%s] Specialization with name 'livestockTrailerSpecial' was not found in modDesc!", LivestockTrailerSpecial.MOD_NAME)
    end

    local spec = self.spec_livestockTrailerSpecial

    if spec ~= nil then
        spec.conversionTime = 0
        spec.strawBeddingConversionMs = -1

        if self.isServer then
            spec.balesInTrigger = {}

            spec.baleTriggerNode = self.xmlFile:getValue("vehicle.livestockTrailer.baleTrigger#node", nil, self.components, self.i3dMappings)
            spec.inputLitresPerMs = self.xmlFile:getValue("vehicle.livestockTrailer.baleTrigger#inputLitresPerSecond", 100) / 1000
            spec.fillUnitIndex = self.xmlFile:getValue("vehicle.livestockTrailer.baleTrigger#fillUnitIndex", 1)

            if spec.baleTriggerNode ~= nil then
                addTrigger(spec.baleTriggerNode, "livestockTrailerBaleTriggerCallback", self)
            end

            spec.minimumAnimals = math.max(self.xmlFile:getValue("vehicle.livestockTrailer.bedding#minimumAnimals", 1), 0)
            spec.strawBeddingConversionMs = self.xmlFile:getValue("vehicle.livestockTrailer.bedding#conversionSeconds", 10) * 1000

            local fillTypeName = self.xmlFile:getValue("vehicle.livestockTrailer.bedding#fillType", "STRAW")
            spec.beddingFillTypeIndex = g_fillTypeManager:getFillTypeIndexByName(fillTypeName)

            if spec.beddingFillTypeIndex == nil then
                spec.beddingFillTypeIndex = FillType.STRAW

                Logging.error("[%s] Invalid fillType '%s' given at 'vehicle.livestockTrailer.bedding', using STRAW instead!", LivestockTrailerSpecial.MOD_NAME, convertedFillTypeName)
            end

            local convertedFillTypeName = self.xmlFile:getValue("vehicle.livestockTrailer.bedding#convertedFillType", "MANURE")
            spec.convertedFillTypeIndex = g_fillTypeManager:getFillTypeIndexByName(convertedFillTypeName)

            if spec.convertedFillTypeIndex == nil then
                spec.convertedFillTypeIndex = FillType.MANURE

                Logging.error("[%s] Invalid convertedFillType '%s' given at 'vehicle.livestockTrailer.bedding', using MANURE instead!", LivestockTrailerSpecial.MOD_NAME, convertedFillTypeName)
            end
        end

        if g_maxModDescVersion >= 69 then
            -- 'removeEventListener' was incompatible with mods until 1.7.0.0 update so only when correct game version
            if spec.baleTriggerNode == nil and spec.strawBeddingConversionMs <= 0 then
                SpecializationUtil.removeEventListener(self, "onUpdate", LivestockTrailerSpecial)
                SpecializationUtil.removeEventListener(self, "onFillUnitFillLevelChanged", LivestockTrailerSpecial)
            elseif spec.strawBeddingConversionMs <= 0 then
                SpecializationUtil.removeEventListener(self, "onFillUnitFillLevelChanged", LivestockTrailerSpecial)
            end
        end
    end
end

function LivestockTrailerSpecial:onLoadFinished(savegame)
    if self.isServer and savegame ~= nil and savegame.xmlFile ~= nil then
        local spec = self.spec_livestockTrailerSpecial

        if spec ~= nil then
            spec.conversionTime = savegame.xmlFile:getValue(string.format("%s.%s.livestockTrailerSpecial#conversionTime", savegame.key, LivestockTrailerSpecial.MOD_NAME), 0)
        end
    end
end

function LivestockTrailerSpecial:onDelete()
    local spec = self.spec_livestockTrailerSpecial

    if spec ~= nil and spec.baleTriggerNode ~= nil then
        removeTrigger(spec.baleTriggerNode)
        spec.baleTriggerNode = nil
    end
end

function LivestockTrailerSpecial:onUpdate(dt, isActiveForInput, isActiveForInputIgnoreSelection, isSelected)
    local spec = self.spec_livestockTrailerSpecial

    if self.isServer and spec ~= nil then
        local needsUpdate = false
        local fillUnit = self:getFillUnitByIndex(spec.fillUnitIndex)

        if fillUnit ~= nil then
            if spec.balesInTrigger ~= nil and next(spec.balesInTrigger) ~= nil then
                for bale, _ in pairs(spec.balesInTrigger) do
                    if self:getFillUnitFreeCapacity(spec.fillUnitIndex) <= 0 then
                        break
                    end

                    local fillType = bale:getFillType()

                    if self:getFillUnitAllowsFillType(spec.fillUnitIndex, fillType) then
                        local fillLevel = bale:getFillLevel()
                        local delta = math.min(spec.inputLitresPerMs * dt, fillLevel)

                        delta = self:addFillUnitFillLevel(self:getOwnerFarmId(), spec.fillUnitIndex, delta, fillType, ToolType.BALE, nil)
                        fillLevel = fillLevel - delta
                        needsUpdate = true

                        if fillLevel > 0.01 then
                            bale:setFillLevel(fillLevel)
                        else
                            bale:delete()
                            spec.balesInTrigger[bale] = nil
                        end
                    else
                        spec.balesInTrigger[bale] = nil
                    end
                end
            end

            if spec.strawBeddingConversionMs > 0 and fillUnit.fillLevel > 0.01 and fillUnit.fillType == spec.beddingFillTypeIndex and spec.minimumAnimals <= self:getNumOfAnimals() then
                spec.conversionTime = spec.conversionTime + dt

                if spec.conversionTime >= spec.strawBeddingConversionMs then
                    local fillLevel = fillUnit.fillLevel
                    local farmId = self:getOwnerFarmId()

                    self:addFillUnitFillLevel(farmId, spec.fillUnitIndex, -fillLevel, spec.beddingFillTypeIndex, ToolType.UNDEFINED, nil)
                    self:addFillUnitFillLevel(farmId, spec.fillUnitIndex, fillLevel, spec.convertedFillTypeIndex, ToolType.UNDEFINED, nil)

                    spec.conversionTime = 0
                end

                needsUpdate = true
            end

            if needsUpdate then
                self:raiseActive()
            end
        end
    end
end

function LivestockTrailerSpecial:onFillUnitFillLevelChanged(fillUnitIndex, fillLevelDelta, fillTypeIndex, toolType, fillPositionData, appliedDelta)
    local spec = self.spec_livestockTrailerSpecial

    if spec ~= nil and spec.fillUnitIndex == fillUnitIndex then
        spec.conversionTime = 0 -- Restart timer if fill level changes
    end
end

function LivestockTrailerSpecial:saveToXMLFile(xmlFile, key, usedModNames)
    local spec = self.spec_livestockTrailerSpecial

    if spec ~= nil and spec.strawBeddingConversionMs > 0 then
        xmlFile:setValue(key .. "#conversionTime", spec.conversionTime)
    end
end

function LivestockTrailerSpecial:livestockTrailerBaleTriggerCallback(triggerId, otherActorId, onEnter, onLeave, onStay, otherShapeId)
    local spec = self.spec_livestockTrailerSpecial

    if spec ~= nil and spec.baleTriggerNode == triggerId and otherActorId ~= 0 then
        local object = g_currentMission:getNodeObject(otherActorId)

        if object ~= nil and object:isa(Bale) then
            local fillType = object:getFillType()

            if fillType == spec.beddingFillTypeIndex and self:getFillUnitAllowsFillType(spec.fillUnitIndex, fillType) then
                if onEnter then
                    spec.balesInTrigger[object] = (spec.balesInTrigger[object] or 0) + 1
                elseif onLeave then
                    spec.balesInTrigger[object] = (spec.balesInTrigger[object] or 1) - 1

                    if spec.balesInTrigger[object] <= 0 then
                        spec.balesInTrigger[object] = nil
                    end
                end

                self:raiseActive()
            end
        end
    end
end

function LivestockTrailerSpecial:updateDebugValues(values)
    local spec = self.spec_livestockTrailerSpecial

    if self.isServer and spec ~= nil and spec.strawBeddingConversionMs > 0 then
        local fillUnit = self:getFillUnitByIndex(spec.fillUnitIndex)

        if fillUnit ~= nil then
            if fillUnit.fillType == spec.beddingFillTypeIndex then
                table.insert(values, {
                    name = "Conversion Time",
                    value = string.format("%.0f seconds (%d%%)", spec.conversionTime / 1000, (spec.conversionTime / spec.strawBeddingConversionMs) * 100)
                })
            elseif fillUnit.fillType == spec.convertedFillTypeIndex then
                table.insert(values, {
                    name = "Conversion Time",
                    value = string.format("%.0f seconds (100%%)", spec.strawBeddingConversionMs / 1000)
                })
            end
        end

        table.insert(values, {
            name = "Bedding Fill Type",
            value = g_fillTypeManager:getFillTypeTitleByIndex(spec.beddingFillTypeIndex)
        })

        table.insert(values, {
            name = "Converted Fill Type",
            value = g_fillTypeManager:getFillTypeTitleByIndex(spec.convertedFillTypeIndex)
        })
    end
end
