require("mw_utils")

--Events we can use:
-- CL_EVENT_SOUND [sound, vector for spatial] (5004) -> sound (eg: "event 5004 mw19.sound 0,10,10")
-- CL_EJECT_BRASS1 [attachment name] (6001) -> ejection (eg: event 6001 tag_ejection)
-- CL_EVENT_DISPATCHEFFECT0 [particle name, attachment name] (9001) -> particles (eg: event 9001 mw_muzzleflash tag_flash)
-- CL_EVENT_DISPATCHEFFECT1 [0/1] (9011) -> left hand grip pose toggle (eg: event 9011 0 -> event 9011 1)
-- CL_EVENT_DISPATCHEFFECT2 [0/1] (9021) -> right hand grip pose toggle (eg: event 9021 0 -> event 9021 1)
-- CL_EVENT_DISPATCHEFFECT3 [name] (9031) -> run attachment function (eg: event 9031 FillBullets) 

--Animation event codes
local CL_EVENT_MUZZLEFLASH0 = 5001 -- Muzzleflash on attachment 0
local CL_EVENT_MUZZLEFLASH1 = 5011 -- Muzzleflash on attachment 1
local CL_EVENT_MUZZLEFLASH2 = 5021 -- Muzzleflash on attachment 2
local CL_EVENT_MUZZLEFLASH3 = 5031 -- Muzzleflash on attachment 3
local CL_EVENT_SPARK0 = 5002 -- Spark on attachment 0
local CL_EVENT_NPC_MUZZLEFLASH0	= 5003 -- Muzzleflash on attachment 0 for third person views
local CL_EVENT_NPC_MUZZLEFLASH1	= 5013 -- Muzzleflash on attachment 1 for third person views
local CL_EVENT_NPC_MUZZLEFLASH2	= 5023 -- Muzzleflash on attachment 2 for third person views
local CL_EVENT_NPC_MUZZLEFLASH3	= 5033 -- Muzzleflash on attachment 3 for third person views
local CL_EVENT_SOUND = 5004 -- Emit a sound // NOTE THIS MUST MATCH THE DEFINE AT CBaseEntity::PrecacheModel on the server!!!!!
local CL_EVENT_EJECTBRASS1 = 6001 -- Eject a brass shell from attachment 1
local CL_EVENT_DISPATCHEFFECT0	= 9001 -- Hook into a DispatchEffect on attachment 0
local CL_EVENT_DISPATCHEFFECT1	= 9011 -- Hook into a DispatchEffect on attachment 1
local CL_EVENT_DISPATCHEFFECT2	= 9021 -- Hook into a DispatchEffect on attachment 2
local CL_EVENT_DISPATCHEFFECT3	= 9031 -- Hook into a DispatchEffect on attachment 3
local CL_EVENT_DISPATCHEFFECT4	= 9041 -- Hook into a DispatchEffect on attachment 4
local CL_EVENT_DISPATCHEFFECT5	= 9051 -- Hook into a DispatchEffect on attachment 5
local CL_EVENT_DISPATCHEFFECT6	= 9061 -- Hook into a DispatchEffect on attachment 6
local CL_EVENT_DISPATCHEFFECT7	= 9071 -- Hook into a DispatchEffect on attachment 7
local CL_EVENT_DISPATCHEFFECT8	= 9081 -- Hook into a DispatchEffect on attachment 8
local CL_EVENT_DISPATCHEFFECT9	= 9091 -- Hook into a DispatchEffect on attachment 9
local utilef = util.Effect
local pef = ParticleEffectAttach

local function invalidateBoneCacheForParticles(ent)
    while (IsValid(ent)) do
        if (ent:IsEffectActive(EF_BONEMERGE) || !IsValid(ent:GetParent())) then
            ent:InvalidateBoneCache()
        end
        ent = ent:GetParent()
    end
end

local function findAttachmentInChildren(ent, attName)
    local attId = mw_utils.LookupAttachmentCached(ent, attName)

    for _, c in pairs(ent:GetChildren()) do
        if (c:GetClass() != "class C_BaseFlex") then
            continue
        end
        
        local ce, ca = findAttachmentInChildren(c, attName)

        if (ca != nil) then
            attId = ca
            ent = ce
        end
    end

    return ent, attId
end

function ENT:FindAttachment(attName)
    return findAttachmentInChildren(self, attName)
end

local function createEffectDataForShell(owner, attName)
    local data = EffectData()
    data:SetEntity(owner)
    
    local attEnt, attId = findAttachmentInChildren(owner, attName)

    if (attId == nil) then
        mw_utils.ErrorPrint("createEffectDataForShell: "..attName.." does not exist on model!")
        return data
    end

    local att = attEnt:GetAttachment(attId)
    data:SetOrigin(att.Pos)
    data:SetAngles(att.Ang)
    
    owner = owner:GetOwner()

    while (IsValid(owner) && !owner:IsPlayer()) do
        owner = owner:GetOwner()
    end

    if (IsValid(owner)) then
        data:SetNormal(owner:GetVelocity():GetNormalized())
        data:SetMagnitude(owner:GetVelocity():Length())
    end

    return data
end

function ENT:IsFirstPerson()
    local w = self:GetOwner()
    return IsValid(w:GetOwner()) && w:GetOwner():IsPlayer() && !w:GetOwner():ShouldDrawLocalPlayer() && w:IsCarriedByLocalPlayer()
end

function ENT:HandleEjection(attName)
    local w = self:GetOwner()

    if (w.Shell == "mwb_shelleject" || w.Shell == "mwb_shelleject_comp") then
        mw_utils.ErrorPrint("DoEjection: do not use mwb_shelleject! Use an existing caliber or make your own.")
        return true
    elseif (istable(w.Shell)) then
        mw_utils.DevPrint("DoEjection: still using legacy way! Consider switching to new method.")
    end

    local eff = isstring(w.Shell) && w.Shell || "mwb_shelleject_comp"

    if (self:IsFirstPerson()) then
        local data = createEffectDataForShell(self, attName)
        data:SetFlags(1)
        utilef(eff, data)
    else
        local data = createEffectDataForShell(w, attName)
        data:SetFlags(0)
        utilef(eff, data)
    end

    return true
end

function ENT:HandleParticle(partName, attName)
    local w = self:GetOwner()

    if (w.ParticleEffects != nil && w.ParticleEffects[partName] != nil) then
        partName = w.ParticleEffects[partName]
    end

    if (self:IsFirstPerson()) then
        local ent, attId = findAttachmentInChildren(self, attName)

        if (attId == nil) then
            mw_utils.ErrorPrint("HandleParticle: "..attName.." does not exist on viewmodel!")
            return true
        end

        if (self.m_Particles[partName] != nil) then
            self.m_Particles[partName]:StopEmissionAndDestroyImmediately()
        end

        local particleSystem = CreateParticleSystem(ent, partName, PATTACH_POINT_FOLLOW, attId)
        particleSystem:SetIsViewModelEffect(true)
        particleSystem:SetShouldDraw(false)
        self.m_Particles[partName] = particleSystem
    else
        local ent, attId = findAttachmentInChildren(w, attName)

        if (attId == nil) then
            mw_utils.ErrorPrint("HandleParticle: "..attName.." does not exist on worldmodel!")
            return true
        end

        ent:StopParticlesNamed(partName)
        pef(partName, PATTACH_POINT_FOLLOW, ent, attId)
    end

    return true
end

function ENT:HandleSound(soundName, spatialVector)
    local w = self:GetOwner()

    if (IsValid(w) && w.SoundOverrides != nil) then
        soundName = w.SoundOverrides[soundName] || soundName
    end

    if (spatialVector != nil && !spatialVector:IsZero()) then
        if (IsValid(w:GetOwner()) && !w:GetOwner():IsOnGround()) then
            return true
        end

        local ang = self:GetAngles()
        local pos = self:GetPos()
        pos:Add(ang:Forward() * spatialVector.y)
        pos:Add(ang:Right() * spatialVector.x)
        pos:Add(ang:Up() * spatialVector.z)

        sound.Play(soundName, pos)
        return true
    end
    
    --if (self:GetPlaybackRate() != 1) then
    --    self:EmitSound(soundName, 100, math.Clamp(self:GetPlaybackRate(), 0.95, 1.15) * 100, 1, CHAN_AUTO, SND_CHANGE_PITCH)
    --else
        self:EmitSound(soundName, 0, math.Clamp(self:GetPlaybackRate(), 1, 1.1) * 100, 0, 0, SND_SHOULDPAUSE + SND_CHANGE_PITCH)
    --end

    return true
end

function ENT:HandleLeftHandGrip(val)
    self.m_LeftHandGripTarget = tonumber(val)
    return true
end

function ENT:HandleRightHandGrip(val)
    self.m_RightHandGripTarget = tonumber(val)
    return true
end

function ENT:HandleAttFunction(name)
    self:GetOwner():AttachmentFunction(name)
    return true
end

local function eventError(event, msg)
    mw_utils.ErrorPrint("FireAnimationEvent ("..event.."): "..msg)
end

function ENT:FireAnimationEvent(pos, ang, event, name)
    if (event == CL_EVENT_DISPATCHEFFECT3) then
        return self:HandleAttFunction(name)
    end

    if (event == CL_EVENT_DISPATCHEFFECT1) then
        return self:HandleLeftHandGrip(name || 0)
    end

    if (event == CL_EVENT_DISPATCHEFFECT2) then
        return self:HandleRightHandGrip(name || 0)
    end

    if (event == CL_EVENT_SOUND) then
        if (name == nil) then
            eventError(event, "Missing sound name!")
            return true
        end

        local args = string.Explode(" ", name)

        if (#args <= 0) then
            eventError(event, "Missing arguments!")
            return true
        end

        local soundName = args[1]
        local spatialVector = Vector()

        if (args[2] != nil) then
            local components = string.Explode(",", args[2])

            if (#components <= 1) then
                --jake used spaces like a dumbass
                spatialVector.x = args[2]
                spatialVector.y = args[3]
                spatialVector.z = args[4]
            else
                spatialVector.x = tonumber(components[1]) || 0
                spatialVector.y = tonumber(components[2]) || 0
                spatialVector.z = tonumber(components[3]) || 0
            end
        end

        return self:HandleSound(soundName, spatialVector)
    end

    if (event == CL_EVENT_EJECTBRASS1) then
        if (name == nil) then
            eventError(event, "Missing attachment name!")
            return true
        end

        if (self:GetOwner().HandleEjection != nil) then
            return self:GetOwner():HandleEjection(name)
        end

        return self:HandleEjection(name)
    end

    if (event == CL_EVENT_DISPATCHEFFECT0) then
        if (name == nil) then
            eventError(event, "Missing arguments!")
            return true
        end

        local args = string.Explode(" ", name)

        if (#args <= 0) then
            eventError(event, "Missing arguments!")
            return true
        end

        local partName = args[1]
        local attName = args[2]

        if (attName == nil) then
            eventError(event, "Missing attachment name!")
            return true
        end
        
        return self:HandleParticle(partName, attName)
    end
end