--
-- Author: E.T.A La Marchoise
-- Date: 15-09-22
-- Version: 1.0.0.0
-- Copyright (C) E.T.A La Marchoise, All Rights Reserved
-- MultiVineBaseDetector.lua
--

MultiVineBaseDetector = {};
MultiVineBaseDetector.MOD_NAME = g_currentModName;

function MultiVineBaseDetector.prerequisitesPresent(specializations)
    return true;
end

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

    schema:setXMLSpecializationType("MultiVineBaseDetector")
    schema:setXMLSpecializationType()
end

function MultiVineBaseDetector.registerFunctions(vehicleType)
    SpecializationUtil.registerFunction(vehicleType, "vineRaycastCallback", MultiVineBaseDetector.vineRaycastCallback)
    SpecializationUtil.registerFunction(vehicleType, "getCanStartVineDetection", MultiVineBaseDetector.getCanStartVineDetection)
    SpecializationUtil.registerFunction(vehicleType, "getFirstVineHitPosition", MultiVineBaseDetector.getFirstVineHitPosition)
    SpecializationUtil.registerFunction(vehicleType, "getCurrentVineHitPosition", MultiVineBaseDetector.getCurrentVineHitPosition)
    SpecializationUtil.registerFunction(vehicleType, "getCurrentVineHitDistance", MultiVineBaseDetector.getCurrentVineHitDistance)
    SpecializationUtil.registerFunction(vehicleType, "clearCurrentVinePlaceable", MultiVineBaseDetector.clearCurrentVinePlaceable)
    SpecializationUtil.registerFunction(vehicleType, "cancelVineDetection", MultiVineBaseDetector.cancelVineDetection)
    SpecializationUtil.registerFunction(vehicleType, "finishedVineDetection", MultiVineBaseDetector.finishedVineDetection)
    SpecializationUtil.registerFunction(vehicleType, "getIsValidVinePlaceable", MultiVineBaseDetector.getIsValidVinePlaceable)
    SpecializationUtil.registerFunction(vehicleType, "handleVinePlaceable", MultiVineBaseDetector.handleVinePlaceable)
end

function MultiVineBaseDetector.registerOverwrittenFunctions(vehicleType)
    --SpecializationUtil.registerOverwrittenFunction(vehicleType, "loadSpeedRotatingPartFromXML", MultiVineBaseDetector.loadSpeedRotatingPartFromXML)
end

function MultiVineBaseDetector.registerEventListeners(vehicleType)
    SpecializationUtil.registerEventListener(vehicleType, "onPreLoad", MultiVineBaseDetector);
    SpecializationUtil.registerEventListener(vehicleType, "onLoad", MultiVineBaseDetector);
    SpecializationUtil.registerEventListener(vehicleType, "onUpdate", MultiVineBaseDetector);
end

function MultiVineBaseDetector:onPreLoad(savegame)
    self.spec_multiVineBaseDetector = self[("spec_%s.multiVineBaseDetector"):format(MultiVineBaseDetector.MOD_NAME)];
end

function MultiVineBaseDetector:onLoad(savegame)
    local spec = self.spec_multiVineBaseDetector;

    spec.detectors = {};

    self.xmlFile:iterate("vehicle.multiVineBaseDetector.detector", function(iDetector, key)
        local node = I3DUtil.indexToObject(self.components, self.xmlFile:getString(key .. "#node"), self.i3dMappings);
        local maxDistance = self.xmlFile:getFloat(key .. "#maxDistance") or 1.0;

        table.insert(spec.detectors, {
            node = node,
            maxDistance = maxDistance,
            vineNode = nil,
            isRaycasting = false,
            firstHitPosition = {0, 0, 0},
            currentHitPosition = {0, 0, 0},
            currentHitDistance = 0,
            currentNode = nil,
            animation = {
                name = self.xmlFile:getString(key .. ".animation#name"),
                speedScale = self.xmlFile:getFloat(key .. ".animation#speedScale") or 1,
                poleThreshold = self.xmlFile:getFloat(key .. ".animation#poleThreshold") or 0.1,
            }
        });
    end);

    spec.isVineDetectionActive = false;
end

function MultiVineBaseDetector:onUpdate(dt, isActiveForInput, isActiveForInputIgnoreSelection, isSelected)
    local spec = self.spec_multiVineBaseDetector;

    if self.isServer and spec.detectors ~= nil then
        if self:getCanStartVineDetection() then
            spec.isVineDetectionActive = true

            for iDetector, detector in pairs(spec.detectors) do
                if not detector.isRaycasting then
                    detector.isRaycasting = true;
                    local wx, wy, wz = getWorldTranslation(detector.node);
                    local dx, dy, dz = localDirectionToWorld(detector.node, 0, -1, 0);

                    local vineRaycastResult = {
                        raycastCallback = function(self, hitActorId, x, y, z, distance, nx, ny, nz, subShapeIndex, hitShapeId, isLast)
                            MultiVineBaseDetector.vineRaycastCallback(self.self, hitActorId, x, y, z, distance, nx, ny, nz, subShapeIndex, hitShapeId, isLast, detector, iDetector)
                        end,
                        self = self
                    }

                    raycastAll(wx, wy, wz, dx, dy, dz, "raycastCallback", detector.maxDistance, vineRaycastResult, nil, false, true);
                end
            end
        elseif spec.isVineDetectionActive then
            for iDetector, detector in pairs(spec.detectors) do
                self:clearCurrentVinePlaceable(detector);
                self:finishedVineDetection(detector);
            end

            spec.isVineDetectionActive = false
        end
    end
end

function MultiVineBaseDetector:finishedVineDetection(detector)
    detector.isRaycasting = false;
end

function MultiVineBaseDetector:getCanStartVineDetection()
    return true;
end

function MultiVineBaseDetector:getFirstVineHitPosition(raycast)
    return raycast.firstHitPosition[1], raycast.firstHitPosition[2], raycast.firstHitPosition[3]
end

function MultiVineBaseDetector:getCurrentVineHitPosition(raycast)
    return raycast.currentHitPosition[1], raycast.currentHitPosition[2], raycast.currentHitPosition[3]
end

function MultiVineBaseDetector:getCurrentVineHitDistance()
    return self.spec_MultiVineBaseDetector.raycast.currentHitDistance
end

function MultiVineBaseDetector:clearCurrentVinePlaceable(detector)
    detector.currentNode = nil;
    detector.placeable = nil;
end

function MultiVineBaseDetector:cancelVineDetection(detector)
    detector.currentNode = nil;
    detector.placeable = nil;

    self:finishedVineDetection(detector);
end

function MultiVineBaseDetector:getIsValidVinePlaceable(vinePlaceable)
    return vinePlaceable ~= nil;
end

function MultiVineBaseDetector:handleVinePlaceable(node, placeable, x, y, z, distance, raycast, iRaycast)
    if raycast.currentNode ~= node then
        raycast.firstHitPosition[3] = z
        raycast.firstHitPosition[2] = y
        raycast.firstHitPosition[1] = x
    end

    raycast.currentNode = node
    raycast.currentHitPosition[3] = z
    raycast.currentHitPosition[2] = y
    raycast.currentHitPosition[1] = x
    raycast.currentHitDistance = distance
    raycast.placeable = placeable

    if self:getIsValidVinePlaceable(placeable) then
        local startPosX, startPosY, startPosZ = self:getFirstVineHitPosition(raycast)
        local currentPosX, currentPosY, currentPosZ = self:getCurrentVineHitPosition(raycast)

        if raycast.animation.name ~= nil then
            local _, _, localStartZ = worldToLocal(node, startPosX, startPosY, startPosZ)
            local _, _, localCurrentZ = worldToLocal(node, currentPosX, currentPosY, currentPosZ)
            local direction = MathUtil.sign(localCurrentZ - localStartZ) >= 0 and 1 or -1
            local posPercentage = localCurrentZ / placeable:getPanelLength()

            if direction < 0 then
                posPercentage = 1 - posPercentage
            end

            if not self:getIsAnimationPlaying(raycast.animation.name) then
                if raycast.animation.poleThreshold < posPercentage then
                    if self:getAnimationTime(raycast.animation.name) < 0.5 then
                        self:playAnimation(raycast.animation.name, raycast.animation.speedScale)
                    end
                elseif self:getAnimationTime(raycast.animation.name) > 0.5 then
                    self:playAnimation(raycast.animation.name, -raycast.animation.speedScale)
                end
            end
        end

        return true
    end

    return false
end

function MultiVineBaseDetector:vineRaycastCallback(hitActorId, x, y, z, distance, nx, ny, nz, subShapeIndex, hitShapeId, isLast, detector, iDetector)
    if hitActorId ~= 0 then
        if VehicleDebug.state == VehicleDebug.DEBUG then
            DebugUtil.drawDebugGizmoAtWorldPos(x, y, z, 0, 0, 1, 0, 1, 0, string.format("hitActorId %s (%s); hitShape %s (%s)", getName(hitActorId), hitActorId, getName(hitShapeId), hitShapeId))
        end

        if not detector.isRaycasting then
            self:cancelVineDetection(detector)

            return false
        end

        local placeable = g_currentMission.vineSystem:getPlaceable(hitActorId)

        if not self:getIsValidVinePlaceable(placeable) or g_currentMission.nodeToObject[hitActorId] == self then
            if isLast then
                self:clearCurrentVinePlaceable(detector)
                self:finishedVineDetection(detector)

                return false
            end

            return true
        end

        if self:handleVinePlaceable(hitActorId, placeable, x, y, z, distance, detector, iDetector) then
            self:finishedVineDetection(detector)

            return false
        end

        if isLast then
            self:finishedVineDetection(detector)

            return false
        end

        return true
    else
        self:clearCurrentVinePlaceable(detector)
        self:finishedVineDetection(detector)
    end
end