-- FoldableTrigger.lua
-- Robuste LS25-Version für Fahrzeuge mit Foldable-Spezi
-- Absicherung gegen Preview-/Konfigurationsmenü-Neuaufbau und gelöschte Nodes

FoldableTrigger = {
    MOD_NAME = g_currentModName,
    SPEC_NAME = g_currentModName .. ".foldableTrigger"
}

function FoldableTrigger.prerequisitesPresent(specializations)
    return SpecializationUtil.hasSpecialization(Foldable, specializations)
end

function FoldableTrigger.initSpecialization()
    local schema = Vehicle.xmlSchema
    schema:setXMLSpecializationType("FoldableTrigger")
    schema:register(XMLValueType.NODE_INDEX, "vehicle.foldableTrigger#triggerNode", "Player trigger node")
    schema:setXMLSpecializationType()
end

function FoldableTrigger.registerFunctions(vehicleType)
    SpecializationUtil.registerFunction(vehicleType, "onFoldableTriggerCallback", FoldableTrigger.onFoldableTriggerCallback)
end

function FoldableTrigger.registerEventListeners(vehicleType)
    SpecializationUtil.registerEventListener(vehicleType, "onLoad", FoldableTrigger)
    SpecializationUtil.registerEventListener(vehicleType, "onLoadFinished", FoldableTrigger)
    SpecializationUtil.registerEventListener(vehicleType, "onDelete", FoldableTrigger)
    SpecializationUtil.registerEventListener(vehicleType, "onFoldStateChanged", FoldableTrigger)
end

function FoldableTrigger:onLoad(savegame)
    local spec = self["spec_" .. FoldableTrigger.SPEC_NAME]
    self.spec_foldableTrigger = spec

    if spec == nil then
        return
    end

    spec.triggerNode = nil
    spec.triggerRegistered = false
    spec.isPlayerInRange = false
    spec.activatable = nil
    spec.isDeleted = false

    local triggerNode = self.xmlFile:getValue("vehicle.foldableTrigger#triggerNode", nil, self.components, self.i3dMappings)

    if triggerNode ~= nil and triggerNode ~= 0 and entityExists(triggerNode) then
        spec.triggerNode = triggerNode
        addTrigger(spec.triggerNode, "onFoldableTriggerCallback", self)
        spec.triggerRegistered = true
    end
end

function FoldableTrigger:onLoadFinished(savegame)
    local spec = self.spec_foldableTrigger
    if spec == nil or spec.isDeleted then
        return
    end

    if spec.triggerNode ~= nil and spec.triggerNode ~= 0 and entityExists(spec.triggerNode) then
        spec.activatable = FoldableTriggerActivatable.new(self)
    end
end

function FoldableTrigger:onDelete()
    local spec = self.spec_foldableTrigger
    if spec == nil then
        return
    end

    spec.isDeleted = true
    spec.isPlayerInRange = false

    if spec.activatable ~= nil and g_currentMission ~= nil and g_currentMission.activatableObjectsSystem ~= nil then
        g_currentMission.activatableObjectsSystem:removeActivatable(spec.activatable)
        spec.activatable = nil
    end

    if spec.triggerRegistered and spec.triggerNode ~= nil and spec.triggerNode ~= 0 and entityExists(spec.triggerNode) then
        removeTrigger(spec.triggerNode)
    end

    spec.triggerRegistered = false
    spec.triggerNode = nil
end

function FoldableTrigger:onFoldStateChanged(direction, moveToMiddle)
    local spec = self.spec_foldableTrigger
    if spec == nil or spec.isDeleted then
        return
    end

    if spec.activatable ~= nil then
        spec.activatable:updateText()
    end
end

function FoldableTrigger:onFoldableTriggerCallback(triggerId, otherId, onEnter, onLeave, onStay, otherShapeId)
    local spec = self.spec_foldableTrigger
    if spec == nil or spec.isDeleted then
        return
    end

    local function isPlayerNode(node)
        if node == nil or node == 0 then
            return false
        end

        if g_currentMission ~= nil and g_currentMission.player ~= nil then
            local player = g_currentMission.player
            if player.rootNode == node or player.node == node or player.characterNode == node then
                return true
            end
        end

        if g_currentMission ~= nil and g_currentMission.players ~= nil then
            for _, player in pairs(g_currentMission.players) do
                if player ~= nil then
                    if player.rootNode == node or player.node == node or player.characterNode == node then
                        return true
                    end
                end
            end
        end

        return false
    end

    if not isPlayerNode(otherId) then
        return
    end

    if onEnter then
        spec.isPlayerInRange = true

        if spec.activatable ~= nil
        and g_currentMission ~= nil
        and g_currentMission.activatableObjectsSystem ~= nil then
            spec.activatable:updateText()
            g_currentMission.activatableObjectsSystem:addActivatable(spec.activatable)
        end
    elseif onLeave then
        spec.isPlayerInRange = false

        if spec.activatable ~= nil
        and g_currentMission ~= nil
        and g_currentMission.activatableObjectsSystem ~= nil then
            g_currentMission.activatableObjectsSystem:removeActivatable(spec.activatable)
        end
    end
end


-- =====================================================================
-- Activatable
-- =====================================================================

FoldableTriggerActivatable = {}
local FoldableTriggerActivatable_mt = Class(FoldableTriggerActivatable)

function FoldableTriggerActivatable.new(vehicle)
    local self = setmetatable({}, FoldableTriggerActivatable_mt)
    self.vehicle = vehicle
    self.activateText = ""
    self:updateText()
    return self
end

function FoldableTriggerActivatable:getIsActivatable()
    local vehicle = self.vehicle
    if vehicle == nil then
        return false
    end

    local spec = vehicle.spec_foldableTrigger
    if spec == nil or spec.isDeleted then
        return false
    end

    if g_currentMission == nil or g_currentMission.accessHandler == nil then
        return false
    end

    if not g_currentMission.accessHandler:canPlayerAccess(vehicle) then
        return false
    end

    if spec.triggerNode == nil or spec.triggerNode == 0 or not entityExists(spec.triggerNode) then
        return false
    end

    return spec.isPlayerInRange == true
end

function FoldableTriggerActivatable:run()
    local vehicle = self.vehicle
    if vehicle == nil then
        return
    end

    local triggerSpec = vehicle.spec_foldableTrigger
    if triggerSpec == nil or triggerSpec.isDeleted then
        return
    end

    local spec = vehicle.spec_foldable
    if spec == nil then
        return
    end

    local toggleDirection = vehicle:getToggledFoldDirection()
    if toggleDirection == nil then
        return
    end

    local allowed, warning = vehicle:getIsFoldAllowed(toggleDirection, false)

    if allowed then
        if toggleDirection == spec.turnOnFoldDirection then
            vehicle:setFoldState(toggleDirection, true)
        else
            vehicle:setFoldState(toggleDirection, false)
        end
    elseif warning ~= nil and g_currentMission ~= nil then
        g_currentMission:showBlinkingWarning(warning, 2000)
    end
end

function FoldableTriggerActivatable:getDistance(x, y, z)
    local vehicle = self.vehicle
    if vehicle == nil then
        return math.huge
    end

    local spec = vehicle.spec_foldableTrigger
    if spec == nil or spec.isDeleted then
        return math.huge
    end

    if spec.triggerNode ~= nil and spec.triggerNode ~= 0 and entityExists(spec.triggerNode) then
        local tx, ty, tz = getWorldTranslation(spec.triggerNode)
        return MathUtil.vector3Length(x - tx, y - ty, z - tz)
    end

    return math.huge
end

function FoldableTriggerActivatable:updateText()
    local vehicle = self.vehicle
    if vehicle == nil then
        self.activateText = ""
        return
    end

    local spec = vehicle.spec_foldable
    if spec == nil then
        self.activateText = ""
        return
    end

    local direction = vehicle:getToggledFoldDirection()
    if direction == nil then
        self.activateText = ""
        return
    end

    if direction == spec.turnOnFoldDirection then
        self.activateText = spec.negDirectionText or g_i18n:getText("action_fold")
    else
        self.activateText = spec.posDirectionText or g_i18n:getText("action_unfold")
    end
end