---
-- SweeperBrush
--
-- Author: Wopster
-- Purpose: Clears area from leftovers and clears tracks.
--
-- Copyright (c) Creative Mesh UG, 2019

---@class SweeperBrush
SweeperBrush = {}

SweeperBrush.MOD_NAME = g_currentModName
SweeperBrush.PRODUCT_LOSS_MULTIPLIER = 0.25 -- We lose 75% of the actual product.

---Called on specialization initializing.
function SweeperBrush.initSpecialization()
    g_workAreaTypeManager:addWorkAreaType("sweeperBrush", true)
end

function SweeperBrush.prerequisitesPresent(specializations)
    return SpecializationUtil.hasSpecialization(WorkArea, specializations)
        and SpecializationUtil.hasSpecialization(Dischargeable, specializations)
        and SpecializationUtil.hasSpecialization(TurnOnVehicle, specializations)
end

function SweeperBrush.registerFunctions(vehicleType)
    SpecializationUtil.registerFunction(vehicleType, "processSweeperBrushArea", SweeperBrush.processSweeperBrushArea)
    SpecializationUtil.registerFunction(vehicleType, "processSweeperBrushSideArea", SweeperBrush.processSweeperBrushSideArea)
    SpecializationUtil.registerFunction(vehicleType, "canUseSweeperBrush", SweeperBrush.canUseSweeperBrush)
end

function SweeperBrush.registerOverwrittenFunctions(vehicleType)
    SpecializationUtil.registerOverwrittenFunction(vehicleType, "loadWorkAreaFromXML", SweeperBrush.loadWorkAreaFromXML)
    SpecializationUtil.registerOverwrittenFunction(vehicleType, "getIsWorkAreaActive", SweeperBrush.getIsWorkAreaActive)
end

function SweeperBrush.registerEventListeners(vehicleType)
    SpecializationUtil.registerEventListener(vehicleType, "onLoad", SweeperBrush)
    SpecializationUtil.registerEventListener(vehicleType, "onDelete", SweeperBrush)
    SpecializationUtil.registerEventListener(vehicleType, "onDischargeStateChanged", SweeperBrush)
    SpecializationUtil.registerEventListener(vehicleType, "onStartWorkAreaProcessing", SweeperBrush)
    SpecializationUtil.registerEventListener(vehicleType, "onEndWorkAreaProcessing", SweeperBrush)
end

---Called on load.
function SweeperBrush:onLoad(savegame)
    self.spec_sweeperBrush = self[("spec_%s.sweeperBrush"):format(SweeperBrush.MOD_NAME)]
    local spec = self.spec_sweeperBrush

    spec.isSweeping = false

    if self.isClient then
        spec.samples = {}
        spec.samples.work = g_soundManager:loadSampleFromXML(self.xmlFile, "vehicle.sweeperBrush.sounds", "sweeping", self.baseDirectory, self.components, 0, AudioGroup.VEHICLE, self.i3dMappings, self)
        spec.isSweepingSamplePlaying = false
    end

    spec.fillUnitIndex = Utils.getNoNil(getXMLInt(self.xmlFile, "vehicle.sweeperBrush#fillUnitIndex"), 1)
    spec.fillLitersPerSecond = Utils.getNoNil(getXMLFloat(self.xmlFile, "vehicle.sweeperBrush#fillLitersPerSecond"), 5) / 1000
    spec.sideDropOffset = Utils.getNoNil(getXMLFloat(self.xmlFile, "vehicle.sweeperBrush#sideDropOffset"), 1)
    spec.sidePickupLiters = Utils.getNoNil(getXMLFloat(self.xmlFile, "vehicle.sweeperBrush#sidePickupLiters"), 10)
    spec.tipAnimationName = getXMLString(self.xmlFile, "vehicle.sweeperBrush#tipAnimationName")

    spec.workAreaParameters = {}
    spec.workAreaParameters.forcedFillType = FillType.MANURE
end

---Called on delete.
function SweeperBrush:onDelete()
    if self.isClient then
        local spec = self.spec_sweeperBrush
        g_soundManager:deleteSamples(spec.samples)
    end
end

---Called on update frame to process the sweeper side brush area.
function SweeperBrush:processSweeperBrushSideArea(workArea, dt)
    local spec = self.spec_sweeperBrush

    if self:canUseSweeperBrush() then
        local xs, _, zs = getWorldTranslation(workArea.start)
        local xw, _, zw = getWorldTranslation(workArea.width)
        local xh, _, zh = getWorldTranslation(workArea.height)

        local xds, _, zds = localToWorld(workArea.start, spec.sideDropOffset, 0, 0)
        local xdw, _, zdw = localToWorld(workArea.width, spec.sideDropOffset, 0, 0)
        local xdh, _, zdh = localToWorld(workArea.height, spec.sideDropOffset, 0, 0)

        local x, z, widthX, widthZ, heightX, heightZ = MathUtil.getXZWidthAndHeight(xds, zds, xdw, zdw, xdh, zdh)

        local x1 = x + heightX
        local z1 = z + heightZ
        local y1 = getTerrainHeightAtWorldPos(g_currentMission.terrainRootNode, x1, 0, z1)

        local x2 = x + widthX + heightX
        local z2 = z + widthZ + heightZ
        local y2 = getTerrainHeightAtWorldPos(g_currentMission.terrainRootNode, x2, 0, z2)

        FSDensityMapUtil.eraseTireTrack(xs, zs, xw, zw, xh, zh)

        local lsx, lsy, lsz, lex, ley, lez, lineRadius = DensityMapHeightUtil.getLineByArea(workArea.start, workArea.width, workArea.height)

        local supportedFillTypes = self:getFillUnitSupportedFillTypes(spec.fillUnitIndex)
        if supportedFillTypes ~= nil then
            for fillType, state in pairs(supportedFillTypes) do
                if state then
                    local liters = -DensityMapHeightUtil.tipToGroundAroundLine(self, -spec.sidePickupLiters, fillType, lsx, lsy, lsz, lex, ley, lez, lineRadius, nil, nil, false, nil)
                    if liters > 0 then
                        DensityMapHeightUtil.tipToGroundAroundLine(self, liters, fillType, x1, y1, z1, x2, y2, z2, 0, nil, nil, false, nil)
                    end
                end
            end
        end
    end

    spec.isSweeping = true

    return 0, 0
end

---Called on update frame to process the sweeper brush area.
function SweeperBrush:processSweeperBrushArea(workArea, dt)
    local spec = self.spec_sweeperBrush

    if self:canUseSweeperBrush() then
        local xs, _, zs = getWorldTranslation(workArea.start)
        local xw, _, zw = getWorldTranslation(workArea.width)
        local xh, _, zh = getWorldTranslation(workArea.height)

        FSDensityMapUtil.eraseTireTrack(xs, zs, xw, zw, xh, zh)

        local lsx, lsy, lsz, lex, ley, lez, lineRadius = DensityMapHeightUtil.getLineByArea(workArea.start, workArea.width, workArea.height)

        local deltaFill = spec.fillLitersPerSecond * dt * 0.1 -- fill it slowly

        local pickupLiters = deltaFill
        local supportedFillTypes = self:getFillUnitSupportedFillTypes(spec.fillUnitIndex)
        if supportedFillTypes ~= nil then
            for fillType, state in pairs(supportedFillTypes) do
                if state then
                    local liters = -DensityMapHeightUtil.tipToGroundAroundLine(self, -math.huge, fillType, lsx, lsy, lsz, lex, ley, lez, lineRadius, nil, nil, false, nil)
                    if liters > 0 then
                        if fillType ~= spec.workAreaParameters.forcedFillType then
                            liters = liters * SweeperBrush.PRODUCT_LOSS_MULTIPLIER
                        end

                        pickupLiters = pickupLiters + liters
                    end
                end
            end
        end

        spec.workAreaParameters.lastPickupLiters = spec.workAreaParameters.lastPickupLiters + pickupLiters
    end

    spec.isSweeping = true

    return 0, 0
end

---Returns true when the sweeper brush can be used, false otherwise.
function SweeperBrush:canUseSweeperBrush()
    return self:getLastSpeed() > 0.2
end

---Called on start of processing area in order to set initial values.
function SweeperBrush:onStartWorkAreaProcessing(dt)
    local spec = self.spec_sweeperBrush
    spec.isSweeping = false
    spec.workAreaParameters.lastPickupLiters = 0
end

---Called on end of processing area in order to set act on changed values.
function SweeperBrush:onEndWorkAreaProcessing(dt, hasProcessed)
    local spec = self.spec_sweeperBrush

    if self.isServer then
        if spec.workAreaParameters.lastPickupLiters > 0 then
            self:addFillUnitFillLevel(self:getOwnerFarmId(), spec.fillUnitIndex, spec.workAreaParameters.lastPickupLiters, spec.workAreaParameters.forcedFillType, ToolType.UNDEFINED, nil)
        end
    end

    if self.isClient then
        if spec.isSweeping then
            if not spec.isSweepingSamplePlaying then
                g_soundManager:playSample(spec.samples.work)
                spec.isSweepingSamplePlaying = true
            end
        else
            if spec.isSweepingSamplePlaying then
                g_soundManager:stopSample(spec.samples.work)
                spec.isSweepingSamplePlaying = false
            end
        end
    end
end

---Called on discharge state change.
function SweeperBrush:onDischargeStateChanged(dischargState)
    local spec = self.spec_sweeperBrush

    if spec.tipAnimationName ~= nil then
        local playAnimation = dischargState == Dischargeable.DISCHARGE_STATE_OFF or dischargState == Dischargeable.DISCHARGE_STATE_GROUND or dischargState == Dischargeable.DISCHARGE_STATE_OBJECT

        if playAnimation then
            local animTime = self:getAnimationTime(spec.tipAnimationName)
            local dir = dischargState == Dischargeable.DISCHARGE_STATE_OFF and -1 or 1
            self:playAnimation(spec.tipAnimationName, dir, animTime, true)
        end
    end
end

----------------
-- Overwrites --
----------------

---Loads work areas from xml for the sweeper brush.
function SweeperBrush:loadWorkAreaFromXML(superFunc, workArea, xmlFile, key)
    local value = superFunc(self, workArea, xmlFile, key)

    if workArea.type == WorkAreaType.DEFAULT then
        workArea.type = WorkAreaType.SWEEPERBRUSH
    end

    return value
end

---Returns true when the work area is active, false otherwise.
function SweeperBrush:getIsWorkAreaActive(superFunc, workArea)
    local spec = self.spec_sweeperBrush
    local area = self.spec_workArea.workAreas[spec.workAreaIndex]
    if area ~= nil and workArea == area then
        if not self:getIsTurnedOn() then
            return false
        end
    end

    if self:getFillUnitFreeCapacity(spec.fillUnitIndex) == 0 then
        return false
    end

    return superFunc(self, workArea)
end
