-- DrivableHandtool (FS22) – kézi eszköz mozgás
-- FIX: kliensen nincs automatikus hátra-jobbra drift (deadzone + lokális axis, szerver-axis ignorálás)

DrivableHandtool = {}

DrivableHandtool.MOD_NAME = g_currentModName
DrivableHandtool.MAX_FORWARD_SPEED   = 3.9
DrivableHandtool.MAX_BACKWARD_SPEED  = 2.9
DrivableHandtool.AXIS_DEADZONE       = 0.002   -- ~0.2% holttér a kvantálási zaj ellen
DrivableHandtool.LOOK_DEADZONE       = 0.0005  -- egér nézés holttér

local function clampDeadzone(x, dz)
    if x > -dz and x < dz then return 0 end
    -- opcionális: kicsit húzzuk be a skálát, hogy a deadzone után se legyen ugrás
    return x
end

function DrivableHandtool.prerequisitesPresent(specializations)
    return SpecializationUtil.hasSpecialization(CCTDrivable, specializations)
end

function DrivableHandtool.registerFunctions(vehicleType)
    SpecializationUtil.registerFunction(vehicleType, "resetInputs", DrivableHandtool.resetInputs)
    SpecializationUtil.registerFunction(vehicleType, "updateKinematic", DrivableHandtool.updateKinematic)
    SpecializationUtil.registerFunction(vehicleType, "getAccelerationAxis", DrivableHandtool.getAccelerationAxis)
    SpecializationUtil.registerFunction(vehicleType, "getCruiseControlDisplayInfo", DrivableHandtool.getCruiseControlDisplayInfo)
    SpecializationUtil.registerFunction(vehicleType, "getPosition", DrivableHandtool.getPosition)
    SpecializationUtil.registerFunction(vehicleType, "getRotation", DrivableHandtool.getRotation)
end

function DrivableHandtool.registerEventListeners(vehicleType)
    SpecializationUtil.registerEventListener(vehicleType, "onLoad", DrivableHandtool)
    SpecializationUtil.registerEventListener(vehicleType, "onLoadFinished", DrivableHandtool)
    SpecializationUtil.registerEventListener(vehicleType, "onReadUpdateStream", DrivableHandtool)
    SpecializationUtil.registerEventListener(vehicleType, "onWriteUpdateStream", DrivableHandtool)
    SpecializationUtil.registerEventListener(vehicleType, "onUpdate", DrivableHandtool)
    SpecializationUtil.registerEventListener(vehicleType, "onUpdateInterpolation", DrivableHandtool)
    SpecializationUtil.registerEventListener(vehicleType, "onLeaveVehicle", DrivableHandtool)
    SpecializationUtil.registerEventListener(vehicleType, "onRegisterActionEvents", DrivableHandtool)
    SpecializationUtil.registerEventListener(vehicleType, "onEnterVehicle", DrivableHandtool)
end

function DrivableHandtool.registerOverwrittenFunctions(vehicleType)
    SpecializationUtil.registerOverwrittenFunction(vehicleType, "setWorldPosition", DrivableHandtool.setWorldPosition)
    SpecializationUtil.registerOverwrittenFunction(vehicleType, "setWorldPositionQuaternion", DrivableHandtool.setWorldPositionQuaternion)
end

function DrivableHandtool:onLoad(savegame)
    self.spec_drivableHandtool = {}
    local spec = self.spec_drivableHandtool

    spec.currentTurnAngle = 0
    spec.gravity          = -9.81
    spec.inputValues      = { moveForward = 0, moveRight = 0, lookRight = 0 }

    spec.axisForward, spec.axisSide, spec.axisLookRight = 0, 0, 0
    spec.axisForwardSend, spec.axisSideSend, spec.axisLookRightSend = 0, 0, 0
    spec.verticalVelocity = 0
    spec.actionEvents     = {}
    spec.syncedOnce       = false
    spec.lastServerPos    = {0, 0, 0}
    spec.lastServerRot    = 0

    self:resetInputs()

    if self.isServer then
        spec.interpolatorTurnAngle = InterpolatorAngle.new(0)
        self.networkTimeInterpolator.maxInterpolationAlpha = 1.2
    end

    spec.dirtyFlag = self:getNextDirtyFlag()

    local spec_cctdrivable = self.spec_cctdrivable
    if spec_cctdrivable ~= nil then
        spec_cctdrivable.movementCollisionMask = 18
    end
end

function DrivableHandtool:onLoadFinished()
    self:raiseActive()
end

-- belépéskor minden tengely és gyorsulás kinullázása
function DrivableHandtool:onEnterVehicle()
    local spec = self.spec_drivableHandtool
    spec.axisForward, spec.axisSide, spec.axisLookRight = 0, 0, 0
    spec.axisForwardSend, spec.axisSideSend, spec.axisLookRightSend = 0, 0, 0
    spec.verticalVelocity = 0
    self:resetInputs()
    self:raiseActive()
end

-- Net sync (read): POZÍCIÓ/ROT jöhet mindig; AXIS csak akkor íródjon felül, ha NEM mi irányítjuk lokálisan
function DrivableHandtool:onReadUpdateStream(streamId, timestamp, connection)
    local spec = self.spec_drivableHandtool
    if streamReadBool(streamId) then
        -- bejövő axisok (kvantáltak)
        local axF = streamReadUIntN(streamId, 10) / 1023 * 2 - 1
        local axS = streamReadUIntN(streamId, 10) / 1023 * 2 - 1
        local axL = streamReadUIntN(streamId, 10) / 1023 * 2 - 1

        local x  = streamReadFloat32(streamId)
        local y  = streamReadFloat32(streamId)
        local z  = streamReadFloat32(streamId)
        local rot= streamReadFloat32(streamId)

        -- mindig frissítjük a szerver-pozit a vizuális synchez
        spec.lastServerPos = {x, y, z}
        spec.lastServerRot = rot
        spec.syncedOnce    = true

        -- HA nem mi ülünk benne lokálisan (azaz ez egy másik játékos eszköze), akkor átvehetjük a szerver axisát vizuális animációhoz
        -- HA mi ülünk benne (self:getIsEntered() == true), AKKOR NEM írjuk felül a lokális axisokat a hálózatiakkal!
        if not self:getIsEntered() then
            spec.axisForward   = clampDeadzone(axF, DrivableHandtool.AXIS_DEADZONE)
            spec.axisSide      = clampDeadzone(axS, DrivableHandtool.AXIS_DEADZONE)
            spec.axisLookRight = clampDeadzone(axL, DrivableHandtool.LOOK_DEADZONE)
        end

        self:raiseActive()
    end
end

-- Net sync (write): a SZERVER küldi – a szerver az ott tárolt axisokat küldi (ezek nálunk clampelve vannak)
function DrivableHandtool:onWriteUpdateStream(streamId, connection, dirtyMask)
    local spec = self.spec_drivableHandtool
    if streamWriteBool(streamId, bitAND(dirtyMask, spec.dirtyFlag) ~= 0) then
        streamWriteUIntN(streamId, (spec.axisForward + 1) / 2 * 1023, 10)
        streamWriteUIntN(streamId, (spec.axisSide + 1) / 2 * 1023, 10)
        streamWriteUIntN(streamId, (spec.axisLookRight + 1) / 2 * 1023, 10)
        local x, y, z   = getWorldTranslation(self.rootNode)
        local _, rotY,_ = getWorldRotation(self.rootNode)
        streamWriteFloat32(streamId, x)
        streamWriteFloat32(streamId, y)
        streamWriteFloat32(streamId, z)
        streamWriteFloat32(streamId, rotY)
    end
end

-- Client/Server Update
function DrivableHandtool:onUpdate(dt, isActiveForInput, isActiveForInputIgnoreSelection, isSelected)
    local spec = self.spec_drivableHandtool

    -- ha mi ülünk benne: a helyi bemenetből képezzük az axisokat, majd CLAMP/DEADZONE és küldés
    if self:getIsEntered() and self.isClient then
        local fwd = clampDeadzone(spec.inputValues.moveForward, DrivableHandtool.AXIS_DEADZONE)
        local sid = clampDeadzone(spec.inputValues.moveRight,   DrivableHandtool.AXIS_DEADZONE)
        local lkp = clampDeadzone(spec.inputValues.lookRight,   DrivableHandtool.LOOK_DEADZONE)

        spec.axisForward   = fwd
        spec.axisSide      = sid
        spec.axisLookRight = lkp
        self:resetInputs()

        if spec.axisForward ~= spec.axisForwardSend or spec.axisSide ~= spec.axisSideSend or spec.axisLookRight ~= spec.axisLookRightSend then
            spec.axisForwardSend, spec.axisSideSend, spec.axisLookRightSend = spec.axisForward, spec.axisSide, spec.axisLookRight
            self:raiseDirtyFlags(spec.dirtyFlag)
        end
    end

    -- ha kliens és NEM mi ülünk benne: a szerver-pozit követjük vizuálisan
    if self.isClient and not self.isServer and spec.syncedOnce and not self:getIsEntered() then
        local lp  = spec.lastServerPos
        local rot = spec.lastServerRot
        setTranslation(self.rootNode, lp[1], lp[2], lp[3])
        setRotation(self.rootNode, 0, rot, 0)
    end

    -- Fizika futtatása:
    --  - szerveren mindig (ő az authoritative)
    --  - kliensen csak akkor, ha MI irányítjuk (helyi előrejelzés/érzet, axis már clampelve)
    if self.isServer or (self.isClient and self:getIsEntered()) then
        self:updateKinematic(dt)
    end
end

-- Szerver interpoláció (más kliensek felé)
function DrivableHandtool:onUpdateInterpolation(dt, isActiveForInput, isActiveForInputIgnoreSelection, isSelected)
    if not self.isServer then return end

    self:updateKinematic(dt)

    local component = self.components[1]
    local x, y, z = self:getCCTWorldTranslation()
    component.networkInterpolators.position:setTargetPosition(x, y, z)
    self.spec_drivableHandtool.interpolatorTurnAngle:setTargetAngle(self.spec_drivableHandtool.currentTurnAngle)

    self.networkTimeInterpolator:startNewPhase(dt + 30)
    self.networkTimeInterpolator:update(dt)

    x, y, z = component.networkInterpolators.position:getInterpolatedValues(self.networkTimeInterpolator.interpolationAlpha)
    setTranslation(self.rootNode, x, y, z)

    local turnAngle = self.spec_drivableHandtool.interpolatorTurnAngle:getInterpolatedValue(self.networkTimeInterpolator.interpolationAlpha)
    local dirX, dirZ = math.sin(turnAngle), math.cos(turnAngle)
    setDirection(self.rootNode, dirX, 0, dirZ, 0, 1, 0)
end

function DrivableHandtool:onLeaveVehicle()
    local spec = self.spec_drivableHandtool
    spec.axisForward, spec.axisSide, spec.axisLookRight = 0, 0, 0
    spec.axisForwardSend, spec.axisSideSend, spec.axisLookRightSend = 0, 0, 0
    self:resetInputs()
    self:raiseActive()
end

-- Kinematika
function DrivableHandtool:updateKinematic(dt)
    local spec = self.spec_drivableHandtool

    -- nézés (Y elfordulás) csak ha tényleg van értelmezhető look input
    if spec.axisLookRight ~= 0 then
        spec.currentTurnAngle = spec.currentTurnAngle - spec.axisLookRight
    end

    -- mozgás tengelyek deadzone után
    local inputX = clampDeadzone(spec.axisSide,    DrivableHandtool.AXIS_DEADZONE)
    local inputZ = clampDeadzone(spec.axisForward, DrivableHandtool.AXIS_DEADZONE)

    -- ha semmi input és nincs felfelé sebesség, nincs mozgás
    if inputX == 0 and inputZ == 0 and (spec.verticalVelocity <= 0) then return end

    local dtInSec = dt * 0.001
    local normX, normZ = MathUtil.vector2Normalize(inputX, inputZ)

    -- ha normálás 0,0-ra adna NaN-t, védjük le
    if normX ~= normX or normZ ~= normZ then
        return
    end

    local dirX, _, dirZ = localDirectionToWorld(self.rootNode, normX, 0, normZ)
    dirX, dirZ = MathUtil.vector2Normalize(dirX, dirZ)

    local speedLimit = DrivableHandtool.MAX_FORWARD_SPEED
    if inputZ < 0 then
        speedLimit = DrivableHandtool.MAX_BACKWARD_SPEED
    end

    local movement = speedLimit * dtInSec
    local moveX, moveZ = movement * dirX, movement * dirZ

    spec.verticalVelocity = spec.verticalVelocity + spec.gravity * dtInSec * 0.02
    local moveY = spec.verticalVelocity

    self:moveCCT(moveX, moveY, moveZ)
end

-- HUD / segédfüggvények
function DrivableHandtool:getAccelerationAxis()
    return self.spec_drivableHandtool.axisForward or 0
end
function DrivableHandtool:getCruiseControlDisplayInfo()
    return 0, false
end
function DrivableHandtool:getPosition()
    return getWorldTranslation(self.rootNode)
end
function DrivableHandtool:getRotation()
    return getWorldRotation(self.rootNode)
end

-- Input reset
function DrivableHandtool:resetInputs()
    local spec = self.spec_drivableHandtool
    spec.inputValues.moveForward = 0
    spec.inputValues.moveRight   = 0
    spec.inputValues.lookRight   = 0
end

-- Action events (WASD + JUMP)
function DrivableHandtool:onRegisterActionEvents()
    if self.isClient then
        local spec = self.spec_drivableHandtool
        self:clearActionEventsTable(spec.actionEvents)

        local _, id = self:addActionEvent(spec.actionEvents, InputAction.AXIS_MOVE_FORWARD_PLAYER,  self, DrivableHandtool.actionEventMoveForwardBackward, false, false, true, true, nil)
        g_inputBinding:setActionEventTextVisibility(id, false)
        _, id = self:addActionEvent(spec.actionEvents, InputAction.AXIS_MOVE_SIDE_PLAYER,          self, DrivableHandtool.actionEventMoveLeftRight,       false, false, true, true, nil)
        g_inputBinding:setActionEventTextVisibility(id, false)
        _, id = self:addActionEvent(spec.actionEvents, InputAction.AXIS_LOOK_LEFTRIGHT_PLAYER,     self, DrivableHandtool.actionEventLookLeftRight,       false, false, true, true, nil)
        g_inputBinding:setActionEventTextVisibility(id, false)
        _, id = self:addActionEvent(spec.actionEvents, InputAction.JUMP,                           self, DrivableHandtool.actionEventJump,                false, true,  false, true, nil)
        g_inputBinding:setActionEventText(id, g_i18n:getText("input_JUMP"))
    end
end

function DrivableHandtool:actionEventMoveForwardBackward(_, inputValue)
    self.spec_drivableHandtool.inputValues.moveForward = self.spec_drivableHandtool.inputValues.moveForward - inputValue
end
function DrivableHandtool:actionEventMoveLeftRight(_, inputValue)
    self.spec_drivableHandtool.inputValues.moveRight = self.spec_drivableHandtool.inputValues.moveRight - inputValue
end
function DrivableHandtool:actionEventLookLeftRight(_, inputValue)
    inputValue = inputValue * 0.003 * g_currentDt * g_gameSettings:getValue(GameSettings.SETTING.CAMERA_SENSITIVITY)
    self.spec_drivableHandtool.inputValues.lookRight = self.spec_drivableHandtool.inputValues.lookRight + inputValue
end
function DrivableHandtool:actionEventJump()
    if self.isServer then
        local spec = self.spec_drivableHandtool
        if spec.verticalVelocity <= 0 then
            spec.verticalVelocity = 0.1
        end
    end
end

-- Overwrites (csak a turnAngle helyretételére)
function DrivableHandtool:setWorldPosition(superFunc, x,y,z, xRot,yRot,zRot,i, changeInterp)
    superFunc(self, x,y,z, xRot,yRot,zRot,i, changeInterp)
    if self.isServer and i == 1 then
        local dx, _, dz = localDirectionToWorld(self.rootNode, 0, 0, 1)
        self.spec_drivableHandtool.currentTurnAngle = MathUtil.getYRotationFromDirection(dx, dz)
        if changeInterp then
            self.spec_drivableHandtool.interpolatorTurnAngle:setAngle(self.spec_drivableHandtool.currentTurnAngle)
        end
    end
end

-- kamera fix
local function updateRotateNodeRotation(self, superFunc)
    if self.vehicle.spec_drivableHandtool ~= nil then
        self.rotY = self.origRotY
    end
    superFunc(self)
end
VehicleCamera.updateRotateNodeRotation = Utils.overwrittenFunction(VehicleCamera.updateRotateNodeRotation, updateRotateNodeRotation)

function DrivableHandtool:setWorldPositionQuaternion(superFunc, x,y,z, qx,qy,qz,qw,i, changeInterp)
    superFunc(self, x,y,z, qx,qy,qz,qw,i, changeInterp)
    if self.isServer and i == 1 then
        local dx, _, dz = localDirectionToWorld(self.rootNode, 0, 0, 1)
        self.spec_drivableHandtool.currentTurnAngle = MathUtil.getYRotationFromDirection(dx, dz)
        if changeInterp then
            self.spec_drivableHandtool.interpolatorTurnAngle:setAngle(self.spec_drivableHandtool.currentTurnAngle)
        end
    end
end
