---
-- StrawHarvestBioHeadPlant
--
-- Author: Stijn Wopereis (Wopster)
-- Purpose: Base placeable class for the bio head plant.
--
-- Copyright (c) Creative Mesh UG, 2019

---@class StrawHarvestBioHeadPlant
StrawHarvestBioHeadPlant = {}
StrawHarvestBioHeadPlant.MOD_DIR = g_currentModDirectory

local StrawHarvestBioHeadPlant_mt = Class(StrawHarvestBioHeadPlant, Placeable)

InitObjectClass(StrawHarvestBioHeadPlant, "StrawHarvestBioHeadPlant")

function StrawHarvestBioHeadPlant:new(isServer, isClient, mt)
    local self = Placeable:new(isServer, isClient, mt or StrawHarvestBioHeadPlant_mt)

    registerObjectClassName(self, "StrawHarvestBioHeadPlant")

    self.bioHeadPlantDirtyFlag = self:getNextDirtyFlag()

    return self
end

function StrawHarvestBioHeadPlant:delete()
    if self.unloadingStation ~= nil then
        g_currentMission.storageSystem:removeUnloadingStation(self.unloadingStation)
        self.unloadingStation:delete()
    end

    if self.exhaustEffect ~= nil then
        g_i3DManager:releaseSharedI3DFile(self.exhaustEffect.filename, self.baseDirectory, true)
    end

    g_animationManager:deleteAnimations(self.animationNodes)

    if self.fillPlane ~= nil then
        self.fillPlane:delete()
        self.fillPlane = nil
    end

    g_currentMission.environment:removeMinuteChangeListener(self)
    g_currentMission.environment:removeHourChangeListener(self)

    if self.playerTriggerNode ~= nil then
        removeTrigger(self.playerTriggerNode)
    end

    unregisterObjectClassName(self)
    StrawHarvestBioHeadPlant:superClass().delete(self)
end

function StrawHarvestBioHeadPlant:load(xmlFilename, x, y, z, rx, ry, rz, initRandom)
    if not StrawHarvestBioHeadPlant:superClass().load(self, xmlFilename, x, y, z, rx, ry, rz, initRandom) then
        return false
    end

    local xmlFile = loadXMLFile("TempXML", xmlFilename)

    self.fillLevel = 0
    self.fillType = FillType.UNKNOWN
    self.capacity = Utils.getNoNil(getXMLFloat(xmlFile, "placeable.bioHeadPlant#capacity"), 25000)
    self.usagePerMinute = Utils.getNoNil(getXMLFloat(xmlFile, "placeable.bioHeadPlant#usagePerMinute"), 150)
    self.priceScale = Utils.getNoNil(getXMLFloat(xmlFile, "placeable.bioHeadPlant#priceScale"), 1)
    self.income = 0

    local unloadingStation = UnloadingStation:new(self.isServer, self.isClient)
    if unloadingStation:load(self.nodeId, xmlFile, "placeable.bioHeatPlant.unloadingStation", self.customEnvironment) then
        unloadingStation:addTargetStorage(self)
        self.unloadingStation = unloadingStation
        self.unloadingStation.owningPlaceable = self
    else
        unloadingStation:delete()
    end

    if self.isClient then
        self.isPlayingAnimation = false
        self.animationNodes = g_animationManager:loadAnimations(xmlFile, "placeable.bioHeatPlant.animationNodes", self.nodeId, self, nil)

        local filename = getXMLString(xmlFile, "placeable.bioHeatPlant.exhaust#filename");
        local node = I3DUtil.indexToObject(self.nodeId, getXMLString(xmlFile, "placeable.bioHeatPlant.exhaust#node"));
        if filename ~= nil then
            local i3dNode = g_i3DManager:loadSharedI3DFile(filename, self.baseDirectory, false, false, false);
            if i3dNode ~= 0 then
                self.exhaustEffect = {}
                self.exhaustEffect.node = getChildAt(i3dNode, 0)
                self.exhaustEffect.filename = filename
                link(Utils.getNoNil(node, self.nodeId), self.exhaustEffect.node)
                setVisibility(self.exhaustEffect.node, false)
                setShaderParameter(self.exhaustEffect.node, "param", 0, 0, 0, 0.4, false)
                delete(i3dNode)
            end
        end
    end

    self.fillPlane = FillPlane:new()
    self.fillPlane:load(self.nodeId, xmlFile, "placeable.bioHeatPlant.fillPlane")

    self.supportedFillTypes = {}

    local fillTypeCategories = getXMLString(xmlFile, "placeable.bioHeatPlant#fillTypeCategories")
    local fillTypeNames = getXMLString(xmlFile, "placeable.bioHeatPlant#fillTypes")

    local fillTypes
    if fillTypeCategories ~= nil and fillTypeNames == nil then
        fillTypes = g_fillTypeManager:getFillTypesByCategoryNames(fillTypeCategories, "Warning: StrawHarvestBioHeadPlant has invalid fillTypeCategory '%s'.")
    elseif fillTypeCategories == nil and fillTypeNames ~= nil then
        fillTypes = g_fillTypeManager:getFillTypesByNames(fillTypeNames, "Warning: StrawHarvestBioHeadPlant has invalid fillType '%s'.")
    else
        print("Warning: StrawHarvestBioHeadPlant a 'Storage' entry needs either the 'fillTypeCategories' or 'fillTypes' attribute.")
        return false
    end

    for _, fillType in pairs(fillTypes) do
        self.supportedFillTypes[fillType] = true
    end

    local triggerNode = I3DUtil.indexToObject(self.nodeId, getXMLString(xmlFile, "placeable.bioHeatPlant.playerTrigger#node"))
    if triggerNode == nil then
        g_logManager:error("Error: StrawHarvestBioHeadPlant could not load player trigger.")
        return false
    end

    self.playerTriggerNode = triggerNode
    self.playerInRange = false
    addTrigger(triggerNode, "playerTriggerCallback", self)

    delete(xmlFile)

    return true
end

function StrawHarvestBioHeadPlant:readStream(streamId, connection)
    StrawHarvestBioHeadPlant:superClass().readStream(self, streamId, connection)

    if connection:getIsServer() then
        local fillType = streamReadUIntN(streamId, FillTypeManager.SEND_NUM_BITS)
        local fillLevel = streamReadUInt8(streamId) / 255 * self.capacity
        self:setFillLevel(fillLevel, fillType)

        local unloadingStationId = NetworkUtil.readNodeObjectId(streamId)
        self.unloadingStation:readStream(streamId, connection)
        g_client:finishRegisterObject(self.unloadingStation, unloadingStationId)
    end
end

function StrawHarvestBioHeadPlant:writeStream(streamId, connection)
    StrawHarvestBioHeadPlant:superClass().writeStream(self, streamId, connection)

    if not connection:getIsServer() then
        streamWriteUIntN(streamId, self.fillType, FillTypeManager.SEND_NUM_BITS)
        streamWriteUInt8(streamId, math.floor(self.fillLevel / self.capacity * 255))

        NetworkUtil.writeNodeObjectId(streamId, NetworkUtil.getObjectId(self.unloadingStation))
        self.unloadingStation:writeStream(streamId, connection)
        g_server:registerObjectInStream(connection, self.unloadingStation)
    end
end

function StrawHarvestBioHeadPlant:readUpdateStream(streamId, timestamp, connection)
    StrawHarvestBioHeadPlant:superClass().readUpdateStream(self, streamId, timestamp, connection)

    if connection:getIsServer() then
        if streamReadBool(streamId) then
            local fillType = streamReadUIntN(streamId, FillTypeManager.SEND_NUM_BITS)
            local fillLevel = streamReadUInt8(streamId) / 255 * self.capacity
            self:setFillLevel(fillLevel, fillType)
        end
    end
end

function StrawHarvestBioHeadPlant:writeUpdateStream(streamId, connection, dirtyMask)
    StrawHarvestBioHeadPlant:superClass().writeUpdateStream(self, streamId, connection, dirtyMask)

    if not connection:getIsServer() then
        if streamWriteBool(streamId, bitAND(dirtyMask, self.bioHeadPlantDirtyFlag) ~= 0) then
            streamWriteUIntN(streamId, self.fillType, FillTypeManager.SEND_NUM_BITS)
            streamWriteUInt8(streamId, math.floor(self.fillLevel / self.capacity * 255))
        end
    end
end

function StrawHarvestBioHeadPlant:loadFromXMLFile(xmlFile, key)
    if not StrawHarvestBioHeadPlant:superClass().loadFromXMLFile(self, xmlFile, key) then
        return false
    end

    local fillTypeStr = getXMLString(xmlFile, key .. "#fillType")
    if fillTypeStr ~= nil then
        local fillLevel = math.max(Utils.getNoNil(getXMLFloat(xmlFile, key .. "#fillLevel"), 0), 0)
        local fillTypeIndex = g_fillTypeManager:getFillTypeIndexByName(fillTypeStr)

        if fillTypeIndex ~= nil then
            if self.supportedFillTypes[fillTypeIndex] ~= nil then
                self:setFillLevel(fillLevel, fillTypeIndex)
            else
                print("Warning: Filltype '" .. fillTypeStr .. "' not supported by StrawHarvestBioHeadPlant " .. getName(self.nodeId))
            end
        else
            print("Error: Invalid filltype '" .. fillTypeStr .. "'")
        end
    end

    self.income = Utils.getNoNil(getXMLFloat(xmlFile, key .. "#income"), self.income)

    return true
end

function StrawHarvestBioHeadPlant:saveToXMLFile(xmlFile, key)
    StrawHarvestBioHeadPlant:superClass().saveToXMLFile(self, xmlFile, key)

    if self.fillLevel > 0 then
        local fillTypeName = g_fillTypeManager:getFillTypeNameByIndex(self.fillType)
        setXMLString(xmlFile, key .. "#fillType", fillTypeName)
        setXMLFloat(xmlFile, key .. "#fillLevel", self.fillLevel)
    end

    setXMLFloat(xmlFile, key .. "#income", self.income)
end

---Called on finalizing the placement.
function StrawHarvestBioHeadPlant:finalizePlacement()
    StrawHarvestBioHeadPlant:superClass().finalizePlacement(self)
    g_currentMission.environment:addMinuteChangeListener(self)
    g_currentMission.environment:addHourChangeListener(self)
    self.unloadingStation:register(true)
end

function StrawHarvestBioHeadPlant:collectPickObjects(node)
    local isFound = false

    if self.unloadingStation ~= nil then
        for _, unloadTrigger in ipairs(self.unloadingStation.unloadTriggers) do
            if node == unloadTrigger.exactFillRootNode then
                isFound = true
                break
            end
        end
    end

    if not isFound then
        StrawHarvestBioHeadPlant:superClass().collectPickObjects(self, node)
    end
end

function StrawHarvestBioHeadPlant:update(dt)
    StrawHarvestBioHeadPlant:superClass().update(self, dt)

    if self.isClient then
        if self.playerInRange then
            self:showStatistics()
            self:raiseActive()
        end
    end
end

function StrawHarvestBioHeadPlant:showStatistics()
    if self.fillType ~= FillType.UNKNOWN then
        local fillLevel = self:getFillLevel(self.fillType)
        local fillType = g_fillTypeManager:getFillTypeByIndex(self.fillType)
        local fillTypeName = fillType ~= nil and fillType.title or ""
        local fillInfoText = string.format(g_i18n:getText("info_fillLevel") .. " %s: %s (%d%%)", fillTypeName, g_i18n:formatFluid(fillLevel), math.floor(100 * fillLevel / self.capacity))
        g_currentMission:addExtraPrintText(fillInfoText)
    end
end

---Called every minute by the environment.
function StrawHarvestBioHeadPlant:minuteChanged(minute)
    if self.isServer then
        if self.fillType ~= FillType.UNKNOWN then
            local price = self.priceScale * g_currentMission.economyManager:getPricePerLiter(self.fillType)
            self.income = self.income + (self.usagePerMinute * price)
            self:setFillLevel(self.fillLevel - self.usagePerMinute, self.fillType)
        end
    end
end

---Called every hour by the environment.
function StrawHarvestBioHeadPlant:hourChanged()
    if self.isServer then
        local farmId = self:getOwnerFarmId()
        g_currentMission:addMoney(self.income, farmId, MoneyType.PROPERTY_INCOME, true)
        g_currentMission:broadcastNotifications(MoneyType.PROPERTY_INCOME, farmId)
        self.income = 0
    end
end

function StrawHarvestBioHeadPlant:addFillLevelFromTool(farmId, deltaFillLevel, fillType, fillInfo, toolType)
    assert(deltaFillLevel >= 0)

    local movedFillLevel = 0

    if self:getIsFillTypeAllowed(fillType) and self:getIsToolTypeAllowed(toolType) then
        if self:hasFarmAccessToStorage(farmId, self) then
            if self:getFreeCapacity(fillType) > 0 then
                local oldFillLevel = self:getFillLevel(fillType)
                self:setFillLevel(oldFillLevel + deltaFillLevel, fillType)
                local newFillLevel = self:getFillLevel(fillType)

                movedFillLevel = movedFillLevel + (newFillLevel - oldFillLevel)
            end

            if movedFillLevel >= deltaFillLevel - 0.001 then
                movedFillLevel = deltaFillLevel
            end
        end
    end

    return movedFillLevel
end

function StrawHarvestBioHeadPlant:setFillLevel(fillLevel, fillTypeIndex)
    self.fillLevel = MathUtil.clamp(fillLevel, 0, self.capacity)

    if self.fillPlane ~= nil then
        self.fillPlane:setState(self:getFillProgress())
    end

    if self.fillLevel ~= self.fillLevelSent then
        self:raiseDirtyFlags(self.bioHeadPlantDirtyFlag)
        self.fillLevelSent = self.fillLevel
    end

    if self.fillPlane ~= nil then
        if fillTypeIndex ~= FillType.UNKNOWN and self.fillType ~= fillTypeIndex then
            local material = g_materialManager:getMaterial(fillTypeIndex, "BELT", 1)
            if material ~= nil then
                setMaterial(self.fillPlane.node, material, 0)
            end
        end
    end

    if self.isClient then
        if not self.isPlayingAnimation and self.fillLevel > 0 then
            g_animationManager:startAnimations(self.animationNodes)
            self.isPlayingAnimation = true
        elseif isPlayingAnimation then
            g_animationManager:stopAnimations(self.animationNodes)
            self.isPlayingAnimation = false
        end

        if self.exhaustEffect ~= nil then
            setVisibility(self.exhaustEffect.node, fillLevel > 0)
        end
    end

    self.fillType = fillTypeIndex
end

function StrawHarvestBioHeadPlant:getFillProgress()
    if self.capacity > 0 then
        local progress = self:getFillLevel(self.fillType) / self.capacity
        return MathUtil.clamp(progress, 0, 1)
    end

    return 0
end

function StrawHarvestBioHeadPlant:getFillLevel(fillTypeIndex)
    if fillTypeIndex == self.fillType then
        return self.fillLevel
    end

    return 0
end

function StrawHarvestBioHeadPlant:getFreeCapacity(fillTypeIndex)
    if self:getIsFillTypeAllowed(fillTypeIndex) then
        return math.max(self.capacity - self:getFillLevel(fillTypeIndex), 0)
    end

    return 0
end

function StrawHarvestBioHeadPlant:getIsToolTypeAllowed(toolType)
    return true
end

function StrawHarvestBioHeadPlant:getIsFillTypeAllowed(fillTypeIndex)
    return self.supportedFillTypes[fillTypeIndex]
end

function StrawHarvestBioHeadPlant:getIsFillTypeSupported(fillTypeIndex)
    return self.supportedFillTypes[fillTypeIndex]
end

function StrawHarvestBioHeadPlant:hasFarmAccessToStorage(farmId, storage)
    return g_currentMission.accessHandler:canFarmAccess(farmId, storage)
end

function StrawHarvestBioHeadPlant:playerTriggerCallback(triggerId, otherId, onEnter, onLeave, onStay, otherShapeId)
    if onEnter or onLeave then
        if g_currentMission.player ~= nil and otherId == g_currentMission.player.rootNode then
            if onEnter then
                local farmId = self:getOwnerFarmId()
                if farmId == nil or farmId == AccessHandler.EVERYONE or g_currentMission.accessHandler:canFarmAccessOtherId(g_currentMission:getFarmId(), farmId) then
                    self.playerInRange = true
                end
            else
                self.playerInRange = false
            end

            self:raiseActive()
        end
    end
end

function StrawHarvestBioHeadPlant:addUnloadingStation(station)
    g_messageCenter:publish(MessageType.UNLOADING_STATIONS_CHANGED)
    return true
end

function StrawHarvestBioHeadPlant:removeUnloadingStation(station)
    g_messageCenter:publish(MessageType.UNLOADING_STATIONS_CHANGED)
    return true
end
