--[[
#include Automatic.lua
]]

SHRAPNEL_TIMESCALE = 1
SHRAPNEL_TABLE = {}

SHRAPNEL_STATS = {}
SHRAPNEL_STATS.SubSteps = 4 -- Default is 4

SHRAPNEL_STATS.GlobalDamageScale = 0.25 -- Default is 1
SHRAPNEL_STATS.BodyImpulseScale = 1/3 -- Default is 1

-- STATS.HookShoot = false
-- STATS.HookExplosions = false

SHRAPNEL_STATS.BaseSpeed = 125 -- Default is 125
SHRAPNEL_STATS.BaseShrapnel = 4 -- Default is 4

SHRAPNEL_STATS.SpeedRandomness = 1 / 8 -- Default is 1 / 8

SHRAPNEL_STATS.SoftMaxSplits = 4.5 -- Default is 3.5
SHRAPNEL_STATS.SpeedDiminish = 2 / 3 -- Default is 2 / 3

SHRAPNEL_STATS.SplitDiminish = (0.5 / SHRAPNEL_STATS.BaseShrapnel) ^ (1 / SHRAPNEL_STATS.SoftMaxSplits)

SHRAPNEL_STATS.DeletionSpeedThreshhold = 1 / 50 -- Default is 1 / 50
-- Time in seconds that the projectile can stay out for without hitting something before it is deleted
SHRAPNEL_STATS.DeletionAfterDriftSec = 1 -- Default is 1

SHRAPNEL_STATS.DustParticles = true -- Default is true
SHRAPNEL_STATS.VoxelChunkParticles = true -- Default is true

SHRAPNEL_STATS.Lookup = {
    none        = { damage = true, reflect = false, rndangle = 1.75, splitscale = 1.0, spark = false },

    glass       = { damage = true, reflect = false, rndangle = 0.875, splitscale = 1.0, spark = false },
    foliage     = { damage = true, reflect = false, rndangle = 1.75, splitscale = 1.0, spark = false },
    plastic     = { damage = true, reflect = false, rndangle = 4.375, splitscale = 1.0, spark = false },
    plaster     = { damage = true, reflect = false, rndangle = 6.5625, splitscale = 1.0, spark = false },
    dirt        = { damage = true, reflect = false, rndangle = 8.75, splitscale = 1.0, spark = false },
    ice         = { damage = true, reflect = false, rndangle = 8.75, splitscale = 1.0, spark = false },
    wood        = { damage = true, reflect = false, rndangle = 10.9375, splitscale = 0.8, spark = false },
    metal       = { damage = true, reflect = false, rndangle = 13.125, splitscale = 0.65, spark = true },
    masonry     = { damage = true, reflect = false, rndangle = 17.5, splitscale = 0.7, spark = false },

    heavymetal  = { damage = true, reflect = true, rndangle = 8.75, splitscale = 0.5, spark = true },
    rock        = { damage = false, reflect = true, rndangle = 8.75, splitscale = 0.5, spark = false },
    hardmetal   = { damage = false, reflect = true, rndangle = 8.75, splitscale = 0.5, spark = true },
    hardmasonry = { damage = false, reflect = true, rndangle = 8.75, splitscale = 0.5, spark = false },
    unphysical  = { damage = false, reflect = true, rndangle = 8.75, splitscale = 1.0, spark = false },
}

local ShrapnelWhizzSound = LoadSound('bullet-pass0.ogg')
local ShrapnelRicochetSound = LoadSound('bullet-hit0.ogg')

MaterialSoundBank = {
    none = false,
    glass = { small = LoadSound('glass/break-s0'), medium =  LoadSound('glass/break-m0'), large = LoadSound('glass/break-l0') },
    foliage = { small = LoadSound('foliage/break-s0'), medium =  LoadSound('foliage/break-m0'), large = LoadSound('foliage/break-l0') },
    plastic = { small = LoadSound('plastic/break-s0'), medium =  LoadSound('plastic/break-m0'), large = LoadSound('plastic/break-l0') },
    plaster = { small = LoadSound('plastic/break-s0'), medium =  LoadSound('plastic/break-m0'), large = LoadSound('plastic/break-l0') },
    dirt = { small = LoadSound('dirt/break-s0'), medium =  LoadSound('dirt/break-m0'), large = LoadSound('dirt/break-l0') },
    ice = { small = LoadSound('ice/break-s0'), medium =  LoadSound('ice/break-m0'), large = LoadSound('ice/break-l0') },
    wood = { small = LoadSound('wood/break-s0'), medium =  LoadSound('wood/break-m0'), large = LoadSound('wood/break-l0') },
    metal = { small = LoadSound('metal/break-s0'), medium =  LoadSound('metal/break-m0'), large = LoadSound('metal/break-l0') },
    masonry = { small = LoadSound('masonry/break-s0'), medium =  LoadSound('masonry/break-m0'), large = LoadSound('masonry/break-l0') },
    rock = { small = LoadSound('masonry/break-s0'), medium =  LoadSound('masonry/break-m0'), large = LoadSound('masonry/break-l0') },
    heavymetal = { small = LoadSound('metal/break-s0'), medium =  LoadSound('metal/break-m0'), large = LoadSound('metal/break-l0') },
    hardmetal = { small = LoadSound('metal/break-s0'), medium =  LoadSound('metal/break-m0'), large = LoadSound('metal/break-l0') },
    hardmasonry = { small = LoadSound('masonry/break-s0'), medium =  LoadSound('masonry/break-m0'), large = LoadSound('masonry/break-l0') },
    -- unphysical = { small = LoadSound('unphysical/break-s0'), medium =  LoadSound('unphysical/break-m0'), large = LoadSound('unphysical/break-l0') },
}

local function RandomRotateVector(fwd, spread)
    local rot = QuatEuler(
        math.random() * spread * 2 - spread,
        math.random() * spread * 2 - spread,
        math.random() * spread * 2 - spread
    )
    return QuatRotateVec(rot, fwd)
end

-- local function Dust(origin, fwd, scale, color)
--     scale = scale or 1
--     ParticleReset()
--     ParticleType('smoke')
--     ParticleCollide(0, 1, 'constant', 0.1)
--     ParticleRadius(0.125 * scale, 0.25 * scale)
--     ParticleDrag(0.25)
--     ParticleAlpha(0.55, 0.25)
--     ParticleGravity(-0.1)

--     ParticleTile(0)
--     ParticleColor(unpack(color or { 0.55, 0.55, 0.55 }))
--     ParticleStretch(20, 0, 'cosine')

--     local vel = VecScale(fwd or AutoVecRnd(1), math.random() * 3)
--     local life = math.random() * 0.9 + 0.125
--     SpawnParticle(origin, vel, life)
-- end

local function Dust(origin, dir, strength, color)
    Sparks(origin, dir, 12, 9, math.random(1,2), AutoColors.amber_light)
    PlaySound(Sounds.Tink, origin, 0.5)
end

local function VoxelChunk(origin, vel, life, color)
    ParticleReset()
    ParticleType('plain')
    ParticleCollide(0, 1)
    ParticleRadius(0.04, 0.04)
    ParticleDrag(0.025)
    ParticleAlpha(1, 0, 'smooth', 0, 0.8)
    ParticleGravity(-10)
    ParticleSticky(0.1, 0.35)

    ParticleTile(6)
    ParticleColor(unpack(color or { 0.55, 0.55, 0.55 }))
    -- ParticleStretch(20, 0, 'cosine')

    SpawnParticle(origin, vel or Vec(), life or 0.25)
end

---@return { origin:table, fwd:table, speed:number, dist:number, split:number, explosive:boolean }
function Proj(origin, fwd, speed, split, explosive)
    return {
        pos = VecCopy(origin),
        fwd = VecCopy(fwd),
        speed = (speed or 100) - (math.random() * speed * SHRAPNEL_STATS.SpeedRandomness),
        split = split or 0,
        explosive = explosive,
        
        time = 0,
        hurt_player = false,
        whizz = false,
        origin = VecCopy(origin),
        random = math.random()
    }
end

function Sim(dt)
    local player_pos = VecAdd(GetPlayerTransform().pos, Vec(0, 1.8 / 2))
    
    for pi, proj in pairs(SHRAPNEL_TABLE) do
        QueryRejectBody(GetToolBody())
        QueryRequire('physical')
        local raycast = AutoRaycast(proj.pos, proj.fwd, proj.speed * dt)

        proj.time = proj.time + dt
        proj.pos = raycast.intersection

        local distance_to_player = AutoVecDist(player_pos, proj.pos)
        if not proj.whizz and distance_to_player < 1 then
            PlaySound(ShrapnelWhizzSound)
            proj.whizz = true
        end

        local voxels_removed = 0
        if raycast.hit then
            local mat, mat_r, mat_g, mat_b = GetShapeMaterialAtPosition(raycast.shape, raycast.intersection)
            mat = ((not mat) or mat == '') and 'none' or mat
            local mat_color = { mat_r, mat_g, mat_b }

            -- AutoInspect(mat)
            -- MATS = MATS or {}
            -- MATS[mat] = (MATS[mat] or 1) + 1
            -- AutoInspectWatch(MATS)
            

            local lookup = SHRAPNEL_STATS.Lookup[mat]
            if not lookup then
                -- error('invalid lookup [' .. mat .. ']')
                SHRAPNEL_TABLE[pi] = nil
                return
            end

            if HasTag(GetShapeBody(raycast.shape), Tool.id .. 'Thrown') then
                Explosion(raycast.intersection, 0.8)
                Delete(GetShapeBody(raycast.shape))
            end

            ApplyBodyImpulse(GetShapeBody(raycast.shape), proj.pos,
                VecScale(VecScale(proj.fwd, proj.speed * SHRAPNEL_STATS.BodyImpulseScale), math.abs(raycast.dot)))

            if lookup.reflect or raycast.dot > 0.5 then
                proj.fwd = RandomRotateVector(raycast.reflection, lookup.rndangle)
                -- PlaySound(ShrapnelRicochetSound, proj.pos, 0.3 + math.random() * 0.3)
            else
                if lookup.damage then
                    local damage = Vec((proj.speed / SHRAPNEL_STATS.BaseSpeed) * 0.625, (proj.speed / SHRAPNEL_STATS.BaseSpeed) * 0.525, (proj.speed / SHRAPNEL_STATS.BaseSpeed) * 0.1)
                    damage = VecScale(damage, SHRAPNEL_STATS.GlobalDamageScale)
                    voxels_removed = MakeHole(proj.pos, damage[1], damage[2], damage[3], true)
                end

                -- local dist = AutoVecDist(proj.origin, proj.pos)
                -- local step = 0.25
                -- for t=0, dist, step do
                --     Dust(VecLerp(proj.origin, proj.pos, (t + math.random() * step) / dist), nil, 0.25)
                -- end

                
                local breaking_multi = math.min(voxels_removed * 1 / 3 + 0.1, 1)
                if SHRAPNEL_STATS.DustParticles then
                    Dust(proj.pos, AutoVecRescale(VecSub(proj.origin, proj.pos), -5 * breaking_multi), 0.6 * breaking_multi, mat_color)
                end

                if SHRAPNEL_STATS.VoxelChunkParticles then
                    for i = 1, breaking_multi do
                        local vel = VecNormalize(VecSub(proj.pos, proj.origin))
                        -- DebugLine(proj.pos, VecAdd(proj.pos, vel))
                        vel = RandomRotateVector(vel, lookup.rndangle / 2)
                        vel = VecScale(vel, math.random() * 10 + (proj.speed / SHRAPNEL_STATS.BaseSpeed * 10))
                        VoxelChunk(proj.pos, vel, math.random() * 2 + 4, mat_color)
                    end
                end
                
                if math.random() < 0.5 and MaterialSoundBank[mat] then
                    PlaySound(MaterialSoundBank[mat].small, proj.pos, math.random() * 0.2 + 0.2)
                end
                
                if lookup.spark then
                    local color = { AutoHSVToRGB(0.02 + proj.random * 0.15, 0.6, 1) }
                    PointLight(proj.pos, color[1], color[2], color[3], proj.speed / SHRAPNEL_STATS.BaseSpeed * 0.25)
                end

                if proj.explosive and mat == 'wood' and math.random() < 0.125 then
                    SpawnFire(proj.pos)
                    -- Explosion(proj.pos, 0)
                end

                proj.fwd = RandomRotateVector(proj.fwd, lookup.rndangle)
            end

            for i = 1, math.floor(proj.split) + (math.random() < proj.split % 1 and 1 or 0) do
                SHRAPNEL_TABLE[#SHRAPNEL_TABLE + 1] = Proj(
                    proj.pos,
                    RandomRotateVector(proj.fwd, lookup.rndangle),
                    proj.speed * (math.random() < 0.5 and SHRAPNEL_STATS.SpeedDiminish or 1),
                    proj.split * SHRAPNEL_STATS.SplitDiminish * lookup.splitscale,
                    proj.explosive and math.random() < 0.4
                )
            end
        else
            proj.pos = raycast.intersection
        end

        if raycast.hit or proj.time > SHRAPNEL_STATS.DeletionAfterDriftSec or proj.speed < SHRAPNEL_STATS.DeletionSpeedThreshhold then
            SHRAPNEL_TABLE[pi] = nil
        end
        -- proj.pos = VecAdd(proj.pos, VecScale(proj.fwd, 5 * dt))
    end
end

-- function handleCommand(cmd)
--     if STATS.HookExplosions then
--         split = AutoSplit(cmd, " ")
--         if #split == 5 then
--             if split[1] == "explosion" then
--                 local strength = tonumber(split[2])
--                 local x = tonumber(split[3])
--                 local y = tonumber(split[4])
--                 local z = tonumber(split[5])
                
--                 local origin = Vec(x, y, z)
--                 local pos = VecCopy(origin)
--                 local ray = AutoQueryClosest(origin, 0.1)
--                 if ray.hit then pos = VecAdd(pos, VecScale(ray.normal, 0.05)) end
                
--                 for i = 1, strength * 20 do
--                     local fwd = VecNormalize(AutoVecRnd(1))
                    
--                     Shrapnel[#Shrapnel + 1] = Proj(pos, fwd, STATS.BaseSpeed, STATS.BaseShrapnel, true)
--                 end
--             end
--         end
--     end

--     if STATS.HookShoot then
--         if #split == 8 then
--             if split[1] == "shot" then
--                 local strength = tonumber(split[2]) -- This is absoulutly useless and always is 1.0
--                 local x = tonumber(split[3])
--                 local y = tonumber(split[4])
--                 local z = tonumber(split[5])
--                 local dx = tonumber(split[6])
--                 local dy = tonumber(split[7])
--                 local dz = tonumber(split[8])

--                 local origin = Vec(x, y, z)
--                 local diff = Vec(dx, dy, dz)
--                 local dir = VecNormalize(diff)

--                 -- local pct = GetPlayerCameraTransform()
--                 -- AutoInspect(VecDot(AutoTransformFwd(pct), dir))

--                 local query = AutoQueryClosest(origin, 2)
--                 local mat = GetShapeMaterialAtPosition(query.shape, query.point) or 'none'
--                 mat = mat == '' and 'none' or mat

--                 -- AutoInspect(mat)
--                 local lookup = STATS.Lookup[mat]
--                 if not lookup then
--                     -- error('invalid lookup [' .. mat .. ']')
--                     return
--                 end

--                 for i = 1, STATS.ShootShrapnel do
--                     Shrapnel[#Shrapnel + 1] = Proj(origin, RandomRotateVector(dir, lookup.rndangle), STATS.ShootSpeed, STATS.BaseShrapnel, false)
--                 end
--             end
--         end
--     end

--     -- AutoInspectWatch({ time = GetTime(), cmd })
-- end