require("mw_math")

ENT.m_AimDeltaLerp = 0
ENT.m_LocomotionDeltaLerp = 0
ENT.m_CustomizationRateLerp = 0
ENT.m_bMoveStopped = true
ENT.m_bMoveStarted = false
ENT.m_bOnGround = true
ENT.m_LeftHandGripPoseParameter = nil
ENT.m_LeftHandGripTarget =  0
ENT.m_LeftHandGripLerp = 0
ENT.m_RightHandGripPoseParameter = nil
ENT.m_RightHandGripTarget = 0
ENT.m_RightHandGripLerp = 0
ENT.m_LastSprayRounds = 0

local idleIndices = {
    ["Idle"] = true, 
    ["Jump"] = true, 
    ["Jog_Out"] = true, 
    ["Land"] = true
}
local uncancellableIndices = {
    ["Sprint_In"] = true,
    ["Holster"] = true
}
local allowAim = {
    ["Ads_In"] = true,
    ["Fire_Last"] = true,
    ["Fire"] = true
}

local function playIdleAnimation(vm, seqIndex)
    local ind = vm.m_LastSequenceIndex
    
    if ((idleIndices[ind] || (!uncancellableIndices[ind] && vm:GetCycle() >= 0.98)) && (allowAim[ind] != nil || vm:GetOwner():GetAimDelta() <= 0)) then
        vm:PlayAnimation(seqIndex, true)
    end
end

local function locomotion(vm)
    local w = vm:GetOwner()
    local p = w:GetOwner()

    if (!IsValid(p) || !p:IsPlayer()) then
        return
    end

    local vel = p:GetVelocity()
    vel = Vector(vel.x, vel.y, 0)

    local len = math.max(vel:Length(), 0.01)

    if (!p:IsOnGround() || (p.GetSliding != nil && p:GetSliding())) then
        vm.m_LocomotionDeltaLerp = mw_math.SafeLerp(6 * RealFrameTime(), vm.m_LocomotionDeltaLerp, 0)
    else
        vm.m_LocomotionDeltaLerp = mw_math.SafeLerp(4 * RealFrameTime(), vm.m_LocomotionDeltaLerp, len / p:GetWalkSpeed())
    end

    --jogging and walking
    local slowWalkPoint = p:GetSlowWalkSpeed() / p:GetWalkSpeed()
    local slowWalkDelta = 1 - math.abs(slowWalkPoint - vm.m_LocomotionDeltaLerp) / slowWalkPoint
    local jogDelta = vm.m_LocomotionDeltaLerp - slowWalkDelta

    --when we stop jogging
    if (jogDelta <= 0.5 && !vm.m_bMoveStopped) then
        if (p:IsOnGround()) then
            playIdleAnimation(vm, "Jog_Out")
        end

        vm.m_bMoveStopped = true
    elseif (jogDelta > 0.5) then
        vm.m_bMoveStopped = false
    end

    --when we start moving
    if (vm.m_LocomotionDeltaLerp > 0.1 && !vm.m_bMoveStarted) then
        if (p:IsOnGround()) then
            playIdleAnimation(vm, "Land")
        end

        vm.m_bMoveStarted = true
    elseif (vm.m_LocomotionDeltaLerp <= 0.1) then
        vm.m_bMoveStarted = false
    end

    vm:SetPoseParameter("jog_loop", jogDelta * Lerp(vm.m_AimDeltaLerp, 1, 0.1 * (w.Zoom.MovementMultiplier || 1)))
    vm:SetPoseParameter("walk_loop", slowWalkDelta * Lerp(vm.m_AimDeltaLerp, 1, 0.2 * (w.Zoom.PoseParameterMultiplier || 1)))

    --freefall loop
    local z = math.min(p:GetVelocity().z, 0)
    local delta = math.min(math.min(z + 500, 0) / -1100, 1)
    vm:SetPoseParameter("freefall_loop", delta * Lerp(vm.m_AimDeltaLerp, 1, 0.1))

    --jumping and landing
    if (vm.m_bOnGround != p:IsOnGround()) then
        if (!p:IsOnGround()) then
            playIdleAnimation(vm, "Jump")
        else
            playIdleAnimation(vm, "Land")
        end

        vm.m_bOnGround = p:IsOnGround()
    end

    --sprint
    local sprintPoint = p:GetRunSpeed() / p:GetWalkSpeed()
    local sprintDelta = (vm.m_LocomotionDeltaLerp - 1) / (sprintPoint - 1)
    vm:SetPoseParameter("sprint_loop", math.min(sprintDelta, math.Clamp((CurTime() - w:GetNextReloadTime()) * 10, 0, 1)) * Lerp(vm.m_AimDeltaLerp, 1, 0.1))
    -- not so efficient but very effective sprinting reload bodge

    --the offset when moving in general
    local offsetDelta = mw_math.CosineInterp(vm.m_LocomotionDeltaLerp * math.Clamp(1 - sprintDelta, 0, 1), 0, 1)
    offsetDelta = offsetDelta * (1 - math.Clamp(vm.m_AimDeltaLerp * 2, 0, 1))
    vm:SetPoseParameter("jog_offset", offsetDelta)

    --after fire reshoulder
    if (vm.m_LastSprayRounds != w:GetSprayRounds() && !w:HasFlag("BipodDeployed")) then
        if (w:GetSprayRounds() == 0 && vm.m_LastSprayRounds >= 5) then
            if (!string.find(string.lower(vm.m_LastSequenceIndex), "fire")) then
                local anim = math.random(1, 2) == 1 && "Land" || "Jog_Out"

                if (w.Animations.SprayEnd != nil) then
                    anim = "SprayEnd"
                end

                playIdleAnimation(vm, anim)
                vm.m_LastSprayRounds = w:GetSprayRounds()
            end
        else
            vm.m_LastSprayRounds = w:GetSprayRounds()
        end
    end
end

ENT.m_InspectSpeed = 1

local function inspection(vm)
    local w = vm:GetOwner()

    local randomness = math.sin(CurTime() * 2) * 0.05 + math.sin(CurTime() * 3) * 0.05
    local inspectDelta = w.FreezeInspectDelta || 0.15

    if (w:Clip1() <= 0 && w.EmptyFreezeInspectDelta) then
        inspectDelta = w.EmptyFreezeInspectDelta
    end

    local bStop = w:HasFlag("StoppedInspectAnimation") || (w:HasFlag("Customizing") && vm:GetCycle() > inspectDelta)
    vm.m_InspectSpeed = mw_math.SafeLerp(5 * RealFrameTime(), vm.m_InspectSpeed, Lerp(mw_math.btn(bStop), 1, randomness))

    if (string.find(string.lower(vm.m_LastSequenceIndex), "inspect")) then
        vm:SetPlaybackRate(vm.m_InspectSpeed)
    end
end

local function grips(vm)
    local w = vm:GetOwner()

    if (w.GripPoseParameters != nil) then
        for i, pp in pairs(w.GripPoseParameters) do
            vm:SetPoseParameter(pp, 0)
        end

        vm.m_LeftHandGripLerp = math.Approach(vm.m_LeftHandGripLerp, vm.m_LeftHandGripTarget, 10 * RealFrameTime())

        if (vm.m_LeftHandGripPoseParameter != nil) then
            vm:SetPoseParameter(vm.m_LeftHandGripPoseParameter, vm.m_LeftHandGripLerp)
        end
    end

    if (w.GripPoseParameters2 != nil) then
        for i, pp in pairs(w.GripPoseParameters2) do
            vm:SetPoseParameter(pp, 0)
        end

        vm.m_RightHandGripLerp = math.Approach(vm.m_RightHandGripLerp, vm.m_RightHandGripTarget, 10 * RealFrameTime())

        if (vm.m_RightHandGripPoseParameter != nil) then
            vm:SetPoseParameter(vm.m_RightHandGripPoseParameter, vm.m_RightHandGripLerp)
        end
    end
end

function ENT:SetPoseParameters()
    local w = self:GetOwner()

    self:SetPoseParameter("aim_offset", self.m_AimDeltaLerp)
    self:SetPoseParameter("hybrid_offset", w:GetAimMode())
    self:SetPoseParameter("firemode_offset", w:GetFiremode() - 1)
    self:SetPoseParameter("empty_offset",  mw_math.btn(w:Clip1() <= 0 || !w:HasFlag("Rechambered")))
    self:SetPoseParameter("bipod", mw_math.btn(w:HasFlag("BipodDeployed")))
end

ENT.m_UpdateDelta = 0

function ENT:Think()
    local w = self:GetOwner()

    if (!IsValid(w)) then
        return
    end
    
    self:ReconcileServerAnims()

    if (!w:IsCarriedByLocalPlayer() || !IsValid(w:GetOwner()) || w != w:GetOwner():GetActiveWeapon()) then
        return
    end

    if (self.m_UpdateDelta <= 0.2) then
        self:UpdateAnimation(self.m_LastSequenceIndex)
        self.m_UpdateDelta = self.m_UpdateDelta + FrameTime()
        --WAKE UP GODDAMN IT
    end
    
    if (self.m_LastSequenceIndex != "INIT") then
        self.m_AimDeltaLerp = mw_math.SafeLerp(30 * RealFrameTime(), self.m_AimDeltaLerp, w:GetAimDelta())
        
        self:SetPoseParameters()
        
        --we play idle a bit earlier if ads in
        local targetCycle = self.m_LastSequenceIndex == "Ads_In" && 0.5 || 0.98

        if (self:GetCycle() >= targetCycle) then
            playIdleAnimation(self, "Idle")
        end 

        --states
        locomotion(self)
        inspection(self)
        grips(self)
    end
end

function ENT:ReconcileServerAnims()
    if (self.m_AnimFromServer == nil) then
        return
    end

    local tick = self.m_AnimFromServer.Tick
    local animId = self.m_AnimFromServer.AnimID
    local rate = self.m_AnimFromServer.Rate
    local timeDifference = self.m_AnimFromServer.TimeDifference
    self.m_AnimFromServer = nil

    local seqIndex = self:GetSequenceIndexByID(animId)

    if (tick < self.m_Tick || (tick == self.m_Tick && self.m_LastSequenceIndex == seqIndex)) then
        return
    end

    local sequences = self:GetOwner().Animations[seqIndex].Sequences
                
    local seqId = self:LookupSequence(sequences[1])
    local length = self:SequenceDuration(seqId)
        
    local cycle = timeDifference / length
    cycle = cycle * rate

    self:PlaySequence(sequences[math.random(1, #sequences)], rate, cycle)

    self.m_LastSequenceIndex = seqIndex
    self.m_Tick = tick
end

function ENT:VManipPostPlayAnim(name)
    playIdleAnimation(self, "Jog_Out")
end

function ENT:VManipHoldQuit()
    playIdleAnimation(self, "Land")
end

function ENT:VManipRemove()
    playIdleAnimation(self, "Jog_Out")
end