local customizedelta = 0
local sightdelta = 0
local sprintdelta = 0
local peekdelta = 0
local bipoddelta = 0
local blindfiredelta, blindfiredeltaleft, blindfiredeltaright, blindfiredeltakys = 0, 0, 0, 0
local freeaim_p, freeaim_y = 0, 0
local nearwalldelta = 0

local angle_zero = Angle(0, 0, 0)
local vector_origin = Vector(0, 0, 0)

local peekvector = Vector(0, 0, -2)

local m_appor = math.Approach
local f_lerp = function(dlt, from, to) return from + (to - from) * dlt end
local function ApproachMod(usrobj, to, dlt)
    usrobj[1] = m_appor(usrobj[1], to[1], dlt)
    usrobj[2] = m_appor(usrobj[2], to[2], dlt)
    usrobj[3] = m_appor(usrobj[3], to[3], dlt)
end

local function LerpMod(usrobj, to, dlt, clamp_ang)
    usrobj[1] = f_lerp(dlt, usrobj[1], to[1])
    usrobj[2] = f_lerp(dlt, usrobj[2], to[2])
    usrobj[3] = f_lerp(dlt, usrobj[3], to[3])
    if clamp_ang then
        for i = 1, 3 do usrobj[i] = math.NormalizeAngle(usrobj[i]) end
    end
end

SWEP.BenchPos = SWEP.BenchPos or Vector(0, 0, 0)
SWEP.BenchAng = SWEP.BenchAng or Angle(0, 0, 0)

SWEP.ViewModelPos = Vector(0, 0, 0)
SWEP.ViewModelAng = Angle(0, 0, 0)

function SWEP:GetViewModelPosition(pos, ang)
    if !IsValid(self:GetOwner()) then
        return Vector(0, 0, 0), Angle(0, 0, 0)
    end

    if TacRP.ConVars["dev_benchgun"]:GetBool() then
        return self.BenchPos, self.BenchAng
    end
    self.BenchPos = pos
    self.BenchAng = ang

    local vm = self:GetOwner():GetViewModel()
    local FT = self:DeltaSysTime() -- FrameTime()
    local RFT = RealFrameTime()

    ang = ang - (self:GetOwner():GetViewPunchAngles() * 0.5)

    local oldang = Angle(0, 0, 0)

    oldang:Set(ang)

    local offsetpos = Vector(self.PassivePos)
    local offsetang = Angle(self.PassiveAng)

    local extra_offsetpos = Vector(0, 0, 0)
    local extra_offsetang = Angle(0, 0, 0)

    -- local cor_val = (self.ViewModelFOV / self:GetShouldFOV())
    local cor_val = 0.75

    ---------------------------------------------
    -- Blindfire
    ---------------------------------------------
    local bfmode = self:GetBlindFireMode()
    local bfl = bfmode == TacRP.BLINDFIRE_LEFT
    local bfr = bfmode == TacRP.BLINDFIRE_RIGHT
    local bfs = bfmode == TacRP.BLINDFIRE_KYS
    blindfiredelta = math.Approach(blindfiredelta, self:GetBlindFire() and 1 or 0, FT / 0.3)
    blindfiredeltaleft = math.Approach(blindfiredeltaleft, bfl and 1 or 0, FT / (bfr and 0.45 or 0.3))
    blindfiredeltaright = math.Approach(blindfiredeltaright, bfr and 1 or 0, FT / (bfl and 0.45 or 0.3))
    blindfiredeltakys = math.Approach(blindfiredeltakys, bfs and 1 or 0, FT / (bfs and 0.75 or 0.3))
    if blindfiredelta > 0 then
        local curvedblindfiredelta = self:Curve(blindfiredelta)
        local curvedblindfiredeltaleft = self:Curve(blindfiredeltaleft)
        local curvedblindfiredeltaright = self:Curve(blindfiredeltaright)
        local curvedblindfiredeltakys = self:Curve(blindfiredeltakys)

        offsetpos = LerpVector(curvedblindfiredelta, offsetpos, self:GetValue("BlindFirePos"))
        offsetang = LerpAngle(curvedblindfiredelta, offsetang, self:GetValue("BlindFireAng"))

        if curvedblindfiredeltaleft > 0 then
            offsetpos = LerpVector(curvedblindfiredeltaleft, offsetpos, self:GetValue("BlindFireLeftPos"))
            offsetang = LerpAngle(curvedblindfiredeltaleft, offsetang,  self:GetValue("BlindFireLeftAng"))
        end

        if curvedblindfiredeltaright > 0 then
            offsetpos = LerpVector(curvedblindfiredeltaright, offsetpos, self:GetValue("BlindFireRightPos"))
            offsetang = LerpAngle(curvedblindfiredeltaright, offsetang,  self:GetValue("BlindFireRightAng"))
        end

        if curvedblindfiredeltakys > 0 then
            offsetpos = LerpVector(curvedblindfiredeltakys, offsetpos, self:GetValue("BlindFireSuicidePos"))
            offsetang = LerpAngle(curvedblindfiredeltakys, offsetang,  self:GetValue("BlindFireSuicideAng"))
        end
    end

    ---------------------------------------------
    -- Aiming & Peeking
    ---------------------------------------------
    local ads = self:GetAimDownSightsTime()
    if self:GetScopeLevel() > 0 then
        if self:GetSprintLockTime() > CurTime() then
            ads = ads + self:GetSprintToFireTime()
        end
        sightdelta = m_appor(sightdelta, 1, FT / ads)
    else
        sightdelta = m_appor(sightdelta, 0, FT / ads)
    end

    -- if IsFirstTimePredicted() or game.SinglePlayer() then
        if self:GetPeeking() then
            peekdelta = m_appor(peekdelta, 1, FT / 0.2)
        else
            peekdelta = m_appor(peekdelta, 0, FT / 0.2)
        end
    -- end

    local curvedsightdelta = self:Curve(sightdelta)
    local curvedpeekdelta = self:Curve(peekdelta)

    -- cor_val = Lerp(sightdelta, cor_val, 1)

    local ppos = Vector(self:GetValue("PeekPos")) * curvedpeekdelta
    local pang = Angle(self:GetValue("PeekAng")) * curvedpeekdelta

    if sightdelta > 0 then
        local sightpos, sightang = self:GetSightPositions()

        if self:DoLowerIrons() then
            sightpos = sightpos + LerpVector(curvedpeekdelta, peekvector, vector_origin)
        end

        LerpMod(offsetpos, sightpos + ppos, curvedsightdelta)
        LerpMod(offsetang, sightang + pang, curvedsightdelta, true)

        local eepos, eeang = self:GetExtraSightPosition()
        local im = self:GetValue("SightMidPoint")
        local midpoint = curvedsightdelta * math.cos(curvedsightdelta * (math.pi / 2)) * (1 - curvedpeekdelta)
        local joffset = (im and im.Pos or Vector(0, 0, 0) + ppos) * midpoint
        local jaffset = (im and im.Ang or Angle(0, 0, 0) + pang) * midpoint

        LerpMod(extra_offsetpos, -eepos + joffset, curvedsightdelta)
        LerpMod(extra_offsetang, -eeang + jaffset, curvedsightdelta)
    end

    ---------------------------------------------
    -- Bipod
    ---------------------------------------------
    local amt = math.Clamp(self:GetBipodPos():Distance(self:GetOwner():EyePos()) / 60, 0.15, 0.3)
    bipoddelta = math.Approach(bipoddelta, self:GetInBipod() and 1 or 0, FT / (self:GetInBipod() and 0.4 or amt))

    if bipoddelta > 0 then
        local curvedbipoddelta = self:Curve(bipoddelta)
        pos = LerpVector(math.Clamp(curvedbipoddelta - curvedsightdelta, 0, 1), pos, self:GetBipodPos())
    end

    ---------------------------------------------
    -- Procedural Firing
    ---------------------------------------------
    local procdata = self:GetValue("ProceduralIronFire")
    if IsValid(vm) and procdata then
        local dt = math.max(0, UnPredictedCurTime() - self:GetLastProceduralFireTime() + self:GetPingOffsetScale())

        if dt <= procdata.tmax then
            self.ProceduralIronCleanup = false
            if !(self:GetValue("LastShot") and self:Clip1() == 0) then
                for k, v in pairs(procdata.bones or {}) do
                    local bone = vm:LookupBone(v.bone or "")
                    if !bone then continue end

                    local f = 1
                    if v.t0 == 0 then
                        f = v.t1 and math.Clamp(1 - dt / v.t1, 0, 1) or 0
                    else
                        f = v.t1 and (dt > v.t0 and math.Clamp(1 - (dt - v.t0) / (v.t1 - v.t0), 0, 1) or (dt / v.t0)) or (dt > v.t0 and 1 or (dt / v.t0))
                    end
                    if v.pos then
                        local offset = LerpVector(f, vector_origin, v.pos)
                        vm:ManipulateBonePosition(bone, offset, false)
                    end
                    if v.ang then
                        local offset = LerpAngle(f, angle_zero, v.ang)
                        vm:ManipulateBoneAngles(bone, offset, false)
                    end
                end
            end

            local dtc = math.ease.InQuad(math.Clamp(1 - dt / procdata.t, 0, 1))

            if dtc > 0 and procdata.vm_pos then
                LerpMod(offsetpos, offsetpos + procdata.vm_pos, dtc)
            end
            if dtc > 0 and procdata.vm_ang then
                LerpMod(offsetang, offsetang + procdata.vm_ang, dtc, true)
            end
        elseif !self.ProceduralIronCleanup then
            self.ProceduralIronCleanup = true
            for k, v in pairs(procdata.bones or {}) do
                local bone = vm:LookupBone(v.bone or "")
                if !bone then continue end
                if v.pos then
                    vm:ManipulateBonePosition(bone, vector_origin, false)
                end
                if v.ang then
                    vm:ManipulateBoneAngles(bone, angle_zero, false)
                end
            end
        end
    end

    ---------------------------------------------
    -- Free Aim & Sway
    ---------------------------------------------
    local swayang = self:GetSwayAngles()
    extra_offsetang.y = extra_offsetang.y - (swayang.p * cor_val)
    extra_offsetang.p = extra_offsetang.p + (swayang.y * cor_val)

    local idlesway = Lerp(self:GetSightDelta(), 1 / 3, 0)
    extra_offsetpos.x = extra_offsetpos.x + (swayang.y * cor_val * idlesway)
    extra_offsetpos.z = extra_offsetpos.z + (swayang.p * cor_val * idlesway)

    local freeaimang = self:GetFreeAimOffset()

    freeaim_p = f_lerp(0.5, freeaim_p, freeaimang.p)
    freeaim_y = f_lerp(0.5, freeaim_y, freeaimang.y)
    freeaim_p = m_appor(freeaim_p, freeaimang.p, FT)
    freeaim_y = m_appor(freeaim_y, freeaimang.y, FT)

    extra_offsetang.y = extra_offsetang.y - (freeaim_p * cor_val)
    extra_offsetang.p = extra_offsetang.p + (freeaim_y * cor_val)

    ---------------------------------------------
    -- Customization
    ---------------------------------------------
    -- if IsFirstTimePredicted() or game.SinglePlayer() then
        if self:GetCustomize() then
            customizedelta = m_appor(customizedelta, 1, RealFrameTime() * 1 / 0.15)
        else
            customizedelta = m_appor(customizedelta, 0, RealFrameTime() * 1 / 0.15)
        end
    -- end

    if customizedelta > 0 then
        local curvedcustomizedelta = self:Curve(customizedelta)
        LerpMod(offsetpos, self:GetValue("CustomizePos"), curvedcustomizedelta)
        LerpMod(offsetang, self:GetValue("CustomizeAng"), curvedcustomizedelta)

        LerpMod(extra_offsetang, angle_zero, curvedcustomizedelta, true)
    end

    ---------------------------------------------
    -- Sprinting
    ---------------------------------------------
    local stf = self:GetSprintToFireTime()
    if self:GetCustomize() or self:GetInBipod() or (!self:GetSafe() and !self.LastWasSprinting and !self:ShouldLowerWeapon()) then
        -- not accurate to how sprint progress works but looks much smoother
        if self:GetScopeLevel() > 0 and self:GetSprintLockTime() > UnPredictedCurTime() then
            stf = stf + self:GetAimDownSightsTime() * 0.5
        end
        sprintdelta = m_appor(sprintdelta, 0, FT / stf)
        self.LastReloadEnd = nil
    elseif self:GetReloading() then
        if (self.LastWasSprinting or self:ShouldLowerWeapon()) and self:GetEndReload() then
            self.LastReloadEnd = self.LastReloadEnd or (self:GetReloadFinishTime() - UnPredictedCurTime())
            sprintdelta = 1 - self:Curve((self:GetReloadFinishTime() - UnPredictedCurTime()) / self.LastReloadEnd)
        else
            sprintdelta = m_appor(sprintdelta, 0, FT / 0.5)
        end
    else
        if self:GetLastMeleeTime() + 0.5 > CurTime() or self:GetStartPrimedGrenadeTime() + 0.8 > CurTime() then
            sprintdelta = m_appor(sprintdelta, 0, FT / 0.2)
        else
            sprintdelta = m_appor(sprintdelta, 1, FT / stf)
        end
        self.LastReloadEnd = nil
    end
    local curvedsprintdelta = self:Curve(sprintdelta)
    if curvedsprintdelta > 0 then
        LerpMod(offsetpos, self:GetValue("SprintPos"), curvedsprintdelta)
        LerpMod(offsetang, self:GetValue("SprintAng"), curvedsprintdelta)
        LerpMod(extra_offsetang, angle_zero, curvedsprintdelta, true)

        local sim = self:GetValue("SprintMidPoint")
        local spr_midpoint = curvedsprintdelta * math.cos(curvedsprintdelta * (math.pi / 2))
        local spr_joffset = (sim and sim.Pos or Vector(0, 0, 0)) * spr_midpoint
        local spr_jaffset = (sim and sim.Ang or Angle(0, 0, 0)) * spr_midpoint
        extra_offsetpos:Add(spr_joffset)
        extra_offsetang:Add(spr_jaffset)
    end

    ---------------------------------------------
    -- Near Walling
    ---------------------------------------------
    nearwalldelta = m_appor(nearwalldelta, self:GetNearWallAmount(), FT / 0.3)
    local curvednearwalldelta = self:Curve(nearwalldelta) - customizedelta - sightdelta
    if curvednearwalldelta > 0 then
        local sprpos = LerpVector(curvednearwalldelta, vector_origin, self:GetValue("NearWallPos"))
        local sprang = LerpAngle(curvednearwalldelta, angle_zero, self:GetValue("NearWallAng"))

        local pointdir = self:GetOwner():WorldToLocalAngles(self:GetShootDir())

        extra_offsetpos:Add(pointdir:Right() * sprpos[2])
        extra_offsetpos:Add(pointdir:Forward() * sprpos[1])
        extra_offsetpos:Add(pointdir:Up() * sprpos[3])

        extra_offsetang:Add(sprang)
    end

    self.SwayScale = f_lerp(sightdelta, 1, 0.1)
    self.BobScale = 0

    local speed = 15 * FT * (game.SinglePlayer() and 1 or 2)

    LerpMod(self.ViewModelPos, offsetpos, speed)
    LerpMod(self.ViewModelAng, offsetang, speed, true)
    ApproachMod(self.ViewModelPos, offsetpos, speed * 0.1)
    ApproachMod(self.ViewModelAng, offsetang, speed * 0.1)

    self.ViewModelAng:Normalize()

    pos = pos + (ang:Right() * offsetpos[1])
    pos = pos + (ang:Forward() * offsetpos[2])
    pos = pos + (ang:Up() * offsetpos[3])

    ang:RotateAroundAxis(ang:Up(), offsetang[1])
    ang:RotateAroundAxis(ang:Right(), offsetang[2])
    ang:RotateAroundAxis(ang:Forward(), offsetang[3])

    pos = pos + (oldang:Right() * extra_offsetpos[1])
    pos = pos + (oldang:Forward() * extra_offsetpos[2])
    pos = pos + (oldang:Up() * extra_offsetpos[3])

    ang:RotateAroundAxis(oldang:Up(), extra_offsetang[1])
    ang:RotateAroundAxis(oldang:Right(), extra_offsetang[2])
    ang:RotateAroundAxis(oldang:Forward(), extra_offsetang[3])

    pos, ang = self:GetViewModelBob(pos, ang)
    pos, ang = self:GetViewModelSway(pos, ang)

    self.ViewModelPos = pos
    self.ViewModelAng = ang

    self.LastSysTime = SysTime()
    return pos, ang
end

function SWEP:TranslateFOV(fov)
    return fov
end
