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

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

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

Note:
Based on 'HighPressureWasherLance.lua' by Giants Software, not using the class in case another mod modifies it and I have some extra animations.
https://gdn.giants-software.com/documentation_scripting_fs19.php?version=script&category=63&class=10272

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

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


PressureWasherVehicleLance = {}

PressureWasherVehicleLance.MOD_NAME = g_currentModName
PressureWasherVehicleLance.COLLISION_MASK = 32 + 64 + 128 + 256 + 4096 + 8194

local PressureWasherVehicleLance_mt = Class(PressureWasherVehicleLance, HandTool)
InitObjectClass(PressureWasherVehicleLance, "PressureWasherVehicleLance")

g_xmlManager:addInitSchemaFunction(function ()
    local schema = HandTool.xmlSchema

    schema:setXMLSpecializationType("PressureWasherVehicleLance")

    schema:register(XMLValueType.BOOL, "handTool.pressureWasherVehicleLance#loadPressureInputBindings", "Allow pressure adjustment", false)

    schema:register(XMLValueType.NODE_INDEX, "handTool.pressureWasherVehicleLance.lance#node", "Lance node")
    schema:register(XMLValueType.NODE_INDEX, "handTool.pressureWasherVehicleLance.lance#raycastNode", "Raycast node")
    schema:register(XMLValueType.FLOAT, "handTool.pressureWasherVehicleLance.lance#washDistance", "Max. wash distance", 15)
    schema:register(XMLValueType.FLOAT, "handTool.pressureWasherVehicleLance.lance#washMultiplier", "Wash multiplier", 1)

    schema:register(XMLValueType.NODE_INDEX, "handTool.pressureWasherVehicleLance.lance.handle#node", "Lance handle node")
    schema:register(XMLValueType.VECTOR_ROT, "handTool.pressureWasherVehicleLance.lance.handle#rotOff", "Handle off rotation")
    schema:register(XMLValueType.VECTOR_ROT, "handTool.pressureWasherVehicleLance.lance.handle#rotOn", "Handle on rotation")

    schema:register(XMLValueType.NODE_INDEX, "handTool.pressureWasherVehicleLance.lance.pressureAdjustment#rotateNode", "Pressure adjustment node")
    schema:register(XMLValueType.VECTOR_ROT, "handTool.pressureWasherVehicleLance.lance.pressureAdjustment#rotateIncrements", "Increments used when rotating", "0 0 -10")
    schema:register(XMLValueType.NODE_INDEX, "handTool.pressureWasherVehicleLance.lance.pressureAdjustment#effectNode", "Effect node to scale, must be scaled correctly at 1 1 1")

    SoundManager.registerSampleXMLPaths(schema, "handTool.pressureWasherVehicleLance.sounds", "washing")
    EffectManager.registerEffectXMLPaths(schema, "handTool.pressureWasherVehicleLance.effects")

    schema:setXMLSpecializationType()
end)

function PressureWasherVehicleLance.new(isServer, isClient, customMt)
    local self = PressureWasherVehicleLance:superClass().new(isServer, isClient, customMt or PressureWasherVehicleLance_mt)

    self.isPWVLance = true

    self.connectedVehicle = nil
    self.distanceToVehicle = 0

    self.foundVehicle = nil
    self.doWashing = false

    self.washDistance = 10
    self.washMultiplier = 1
    self.lastWashMultiplier = 1
    self.pumpWorkMultiplier = -1

    self.allowPressureAdjustment = false

    return self
end

function PressureWasherVehicleLance:postLoad(xmlFile)
    if not PressureWasherVehicleLance:superClass().postLoad(self, xmlFile) then
        return false
    end

    self.loadPressureInputBindings = xmlFile:getValue("handTool.pressureWasherVehicleLance#loadPressureInputBindings", false)

    self.lanceNode = xmlFile:getValue("handTool.pressureWasherVehicleLance.lance#node", nil, self.components, self.i3dMappings)
    self.lanceRaycastNode = xmlFile:getValue("handTool.pressureWasherVehicleLance.lance#raycastNode", nil, self.components, self.i3dMappings)

    self.washDistance = xmlFile:getValue("handTool.pressureWasherVehicleLance.lance#washDistance", 10)
    self.washMultiplier = xmlFile:getValue("handTool.pressureWasherVehicleLance.lance#washMultiplier", 1)

    local handleNode = xmlFile:getValue("handTool.pressureWasherVehicleLance.lance.handle#node", nil, self.components, self.i3dMappings)

    if handleNode ~= nil then
        local rotOff = xmlFile:getValue("handTool.pressureWasherVehicleLance.lance.handle#rotOff", {getRotation(handleNode)}, true)
        local rotOn = xmlFile:getValue("handTool.pressureWasherVehicleLance.lance.handle#rotOn", rotOff, true)

        self.handle = {
            node = handleNode,
            rotOff = rotOff,
            rotOn = rotOn
        }

        setRotation(handleNode, rotOff[1], rotOff[2], rotOff[3])
    end

    if self.loadPressureInputBindings then
        local rotateNode = xmlFile:getValue("handTool.pressureWasherVehicleLance.lance.pressureAdjustment#rotateNode", nil, self.components, self.i3dMappings)

        if rotateNode ~= nil then
            local increments = xmlFile:getValue("handTool.pressureWasherVehicleLance.lance.pressureAdjustment#rotateIncrements", "0 0 -10", true)
            local effectNode = xmlFile:getValue("handTool.pressureWasherVehicleLance.lance.pressureAdjustment#effectNode", nil, self.components, self.i3dMappings)

            self.pressureAdjustment = {
                node = rotateNode,
                increments = increments,
                effectNode = effectNode,
                startRotation = {getRotation(rotateNode)}
            }
        end
    end

    self.effects = g_effectManager:loadEffect(xmlFile, "handTool.pressureWasherVehicleLance.effects", self.components, self, self.i3dMappings)
    g_effectManager:setFillType(self.effects, FillType.WATER)

    self.washingSample = g_soundManager:loadSampleFromXML(xmlFile, "handTool.pressureWasherVehicleLance.sounds", "washing", self.baseDirectory, self.components, 0, AudioGroup.VEHICLE, self.i3dMappings, self)

    return true
end

function PressureWasherVehicleLance:delete()
    g_effectManager:deleteEffects(self.effects)
    g_soundManager:deleteSample(self.washingSample)

    if self:hasConnectedVehicle() then
        self.connectedVehicle:setPressureWasherState(PressureWasherVehicle.STATE_DEACTIVATE)
        self.connectedVehicle = nil
    end

    PressureWasherVehicleLance:superClass().delete(self)
end

function PressureWasherVehicleLance:onDeactivate()
    self:setIsWashing(false, true, true)

    if self:hasConnectedVehicle() then
        self.connectedVehicle:setPressureWasherState(PressureWasherVehicle.STATE_DEACTIVATE)
        self.connectedVehicle = nil
    end

    PressureWasherVehicleLance:superClass().onDeactivate(self)
end

function PressureWasherVehicleLance:draw()
    PressureWasherVehicleLance:superClass().draw(self)

    if self:getIsActiveForInput() and self:hasConnectedVehicle() then
        local spec = self.connectedVehicle.spec_pressureWasherVehicle

        -- for _, consumer in ipairs (spec.consumers) do
            -- local fillUnit = self.connectedVehicle.spec_fillUnit.fillUnits[consumer.fillUnitIndex]

            -- if fillUnit ~= nil then
                -- g_currentMission:addExtraPrintText(string.format("%s: %d l (%d%%)", consumer.title, MathUtil.round(fillUnit.fillLevel), 100 * (fillUnit.fillLevel / fillUnit.capacity)))
            -- end
        -- end

        if self.allowPressureAdjustment then
            local setWorkingPressure = MathUtil.round(spec.pump.maxPressure * spec.pump.workMultiplier)

            g_currentMission:addExtraPrintText(string.format("PSI: %d (%d%%)", setWorkingPressure, 100 * (setWorkingPressure / spec.pump.maxPressure)))
        end

        g_currentMission:addExtraPrintText(string.format("%s: %.0f%%", spec.texts.hoseLength, 100 * math.min(spec.hoseLengthUsed / spec.hoseLength, 1)))
    end
end

function PressureWasherVehicleLance:update(dt, allowInput)
    PressureWasherVehicleLance:superClass().update(self, dt, allowInput)

    if self:hasConnectedVehicle() then
        if allowInput then
            self:setIsWashing(self.connectedVehicle:getCanPressureWasherOperate(self.activatePressed, true), false)
        end

        if self.isServer then
            local washMultiplier = self.connectedVehicle:updatePressureWasherConsumers(dt, self.doWashing, self.washMultiplier)

            self.lastWashMultiplier = washMultiplier

            if self.doWashing then
                self.foundVehicle = nil
                self:cleanVehicle(self.player.cameraNode, dt, washMultiplier)

                if self.lanceRaycastNode ~= nil then
                    self:cleanVehicle(self.lanceRaycastNode, dt, washMultiplier)
                end
            end
        end

        if self.pressureAdjustment ~= nil and self:hasConnectedVehicle() then
            local pumpWorkMultiplier = self.connectedVehicle:getPumpWorkMultiplier()

            if pumpWorkMultiplier ~= self.pumpWorkMultiplier then
                self.pumpWorkMultiplier = pumpWorkMultiplier

                local factor = 10 * (pumpWorkMultiplier - 0.1)
                local iX, iY, iZ = unpack(self.pressureAdjustment.increments)
                local rX, rY, rZ = unpack(self.pressureAdjustment.startRotation)

                setRotation(self.pressureAdjustment.node, rX + (iX * factor), rY + (iY * factor), rZ + (iZ * factor))

                if self.pressureAdjustment.effectNode ~= nil then
                    if pumpWorkMultiplier < 1 then
                        pumpWorkMultiplier = pumpWorkMultiplier + 0.08
                    end

                    setScale(self.pressureAdjustment.effectNode, pumpWorkMultiplier, pumpWorkMultiplier, pumpWorkMultiplier)
                end
            end
        end
    end

    self.activatePressed = false
end

function PressureWasherVehicleLance:setIsWashing(doWashing, force, noEventSend)
    HPWLanceStateEvent.sendEvent(self.player, doWashing, noEventSend)

    if self.doWashing ~= doWashing then
        if doWashing then
            g_effectManager:setFillType(self.effects, FillType.WATER)
            g_effectManager:startEffects(self.effects)

            g_soundManager:playSample(self.washingSample)

            if self.handle ~= nil then
                setRotation(self.handle.node, self.handle.rotOn[1], self.handle.rotOn[2], self.handle.rotOn[3])
            end
        else
            if force then
                g_effectManager:resetEffects(self.effects)
            else
                g_effectManager:stopEffects(self.effects)
            end

            g_soundManager:stopSample(self.washingSample)

            if self.handle ~= nil then
                setRotation(self.handle.node, self.handle.rotOff[1], self.handle.rotOff[2], self.handle.rotOff[3])
            end
        end

        self.doWashing = doWashing

        if self:hasConnectedVehicle() then
            self.connectedVehicle:setPressureWasherIsWashing(doWashing)
        end
    end
end

function PressureWasherVehicleLance:cleanVehicle(node, dt, washMultiplier)
    local x, y, z = getWorldTranslation(node)
    local dx, dy, dz = localDirectionToWorld(node, 0, 0, -1)
    local lastFoundVehicle = self.foundVehicle

    raycastAll(x, y, z, dx, dy, dz, "washRaycastCallback", math.max(self.washDistance * self.pumpWorkMultiplier, 5), self, PressureWasherVehicleLance.COLLISION_MASK)

    if self.foundVehicle ~= nil and lastFoundVehicle ~= self.foundVehicle then
        self.foundVehicle:addDirtAmount(-washMultiplier * dt / self.foundVehicle:getWashDuration())
    end
end

function PressureWasherVehicleLance:washRaycastCallback(hitActorId, x, y, z, distance, nx, ny, nz, subShapeIndex, hitShapeId)
    local vehicle = g_currentMission.nodeToObject[hitActorId]

    if hitActorId ~= hitShapeId then
        -- object is a compoundChild. Try to find the compound
        local parentId = hitShapeId

        while parentId ~= 0 do
            if g_currentMission.nodeToObject[parentId] ~= nil then
                -- found valid compound
                vehicle = g_currentMission.nodeToObject[parentId]
                break
            end

            parentId = getParent(parentId)
        end
    end

    self.distanceToVehicle = distance

    if vehicle ~= nil and vehicle.getAllowsWashingByType ~= nil and vehicle:getAllowsWashingByType(Washable.WASHTYPE_HIGH_PRESSURE_WASHER) then
        self.foundVehicle = vehicle

        return false
    end

    return true
end

function PressureWasherVehicleLance:getIsActiveForInput()
    if self.player == g_currentMission.player and not g_gui:getIsGuiVisible() then
        return true
    end

    return false
end

function PressureWasherVehicleLance:setConnectedVehicle(vehicle, washMultiplier, allowPressureAdjustment)
    self.connectedVehicle = nil

    if vehicle ~= nil and vehicle.spec_pressureWasherVehicle ~= nil then
        if washMultiplier ~= nil and washMultiplier > 0 then
            self.washMultiplier = washMultiplier
        end

        self.allowPressureAdjustment = Utils.getNoNil(allowPressureAdjustment, false)

        if not self.allowPressureAdjustment and self.loadPressureInputBindings then
            if self.actionEventIdIncreaseWorkingPressure ~= nil then
                g_inputBinding:setActionEventTextVisibility(self.actionEventIdIncreaseWorkingPressure, false)
                g_inputBinding:setActionEventActive(self.actionEventIdIncreaseWorkingPressure, false)
            end

            if self.actionEventIdDecreaseWorkingPressure ~= nil then
                g_inputBinding:setActionEventTextVisibility(self.actionEventIdDecreaseWorkingPressure, false)
                g_inputBinding:setActionEventActive(self.actionEventIdDecreaseWorkingPressure, false)
            end
        end

        self.connectedVehicle = vehicle
        self.isActive = true

        self:raiseActive()
    elseif VehicleDebug.state == VehicleDebug.DEBUG then
        print(string.format("  Debug: [%s]  Failed to set vehicle or vehicle removed!", PressureWasherVehicleLance.MOD_NAME))
    end
end

function PressureWasherVehicleLance:getConnectedVehicle()
    if self.connectedVehicle ~= nil and not self.connectedVehicle.isDeleted then
        return self.connectedVehicle
    end

    return nil
end

function PressureWasherVehicleLance:hasConnectedVehicle()
    if self.connectedVehicle ~= nil then
        return not self.connectedVehicle.isDeleted
    end

    return false
end

function PressureWasherVehicleLance:registerActionEvents()
    if self.loadPressureInputBindings then
        g_inputBinding:beginActionEventsModification(Player.INPUT_CONTEXT_NAME)

        local _, actionEventId = g_inputBinding:registerActionEvent(InputAction.TOGGLE_TURNLIGHT_RIGHT, self, self.onChangeWorkingPressure, false, true, false, true, 1)
        g_inputBinding:setActionEventText(actionEventId, "PSI (+)")
        self.actionEventIdIncreaseWorkingPressure = actionEventId

        _, actionEventId = g_inputBinding:registerActionEvent(InputAction.TOGGLE_TURNLIGHT_LEFT, self, self.onChangeWorkingPressure, false, true, false, true, -1)
        g_inputBinding:setActionEventText(actionEventId, "PSI (-)")
        self.actionEventIdDecreaseWorkingPressure = actionEventId

        g_inputBinding:endActionEventsModification()
    end
end

function PressureWasherVehicleLance:onChangeWorkingPressure(_, _, callbackState)
    if self:hasConnectedVehicle() then
        self.connectedVehicle:adjustPumpWorkMultiplier(callbackState, true)
    end
end

function PressureWasherVehicleLance:isBeingUsed()
    return self.doWashing
end

function PressureWasherVehicleLance:updateDebugValues(values)
    if self:hasConnectedVehicle() and self:getIsActiveForInput() then
        local spec = self.connectedVehicle.spec_pressureWasherVehicle

        local vehicleName = "N/A"
        local distanceToVehicle = "N/A"
        local washableSpec = nil

        if self.doWashing and self.foundVehicle ~= nil then
            vehicleName = self.foundVehicle:getName()
            distanceToVehicle = self.distanceToVehicle
            washableSpec = self.foundVehicle.spec_washable
        end

        local formatedOperatingTime = string.format("%.2f (%d seconds)", self.connectedVehicle:getFormattedPressureWasherOperatingTime(), (self.connectedVehicle.operatingTime or 0) / 1000)

        local debugValues = {
            {name = "Pressure Washer Name", value = self.connectedVehicle:getName()},
            {name = "Pump Operating Time", value = formatedOperatingTime},
            {name = "Wash Multiplier", value = self.washMultiplier},
            {name = "Extra Wash Multiplier", value = self.lastWashMultiplier - self.washMultiplier},
            {name = "Total Wash Multiplier", value = self.lastWashMultiplier},
            {name = "Wash Distance", value = math.max(self.washDistance * self.pumpWorkMultiplier, 5)},
            {name = "Hose Length", value = spec.hoseLength},
            {name = "Hose Length Used", value = MathUtil.round(spec.hoseLengthUsed)},
            {name = "Vehicle Name", value = vehicleName},
            {name = "Distance To Vehicle", value = distanceToVehicle}
        }

        if washableSpec ~= nil and Washable.updateDebugValues ~= nil then
            Washable.updateDebugValues(self.foundVehicle, debugValues)
        end

        DebugUtil.renderTable(0.81, 0.65, getCorrectTextSize(0.015), debugValues, 0)
    end
end

registerHandTool(string.format("%s.pressureWasherVehicleLance", PressureWasherVehicleLance.MOD_NAME), PressureWasherVehicleLance)
