local anim_frames_amount = 6
local anim_total_duration = 0.4
local anim_frame_duration = anim_total_duration / anim_frames_amount

local function add_swing_trail(props)
    local _props = props or {}
    local pos = _props.pos
    local player = _props.player

    if not player:is_player() then
        return
    end

    local time_from_last_punch = _props.time_from_last_punch
    local tool_capabilities = _props.tool_capabilities
    local protocol_version = core.get_player_information(player:get_player_name()).protocol_version
    local protocol_versions = core.protocol_versions or {
        ['5.0.0'] = 37,
        ['5.1.0'] = 38,
        ['5.10.0'] = 46,
        ['5.11.0'] = 47,
        ['5.12.0'] = 48,
        ['5.2.0'] = 39,
        ['5.3.0'] = 39,
        ['5.4.0'] = 39,
        ['5.5.0'] = 40,
        ['5.6.0'] = 41,
        ['5.7.0'] = 42,
        ['5.8.0'] = 43,
        ['5.9.0'] = 44,
        ['5.9.1'] = 45
    }
    local is_new_particle = protocol_version >= (protocol_versions['5.9.0'] or 0)
    local wielditem = player:get_wielded_item()
    local _x_obsidianmese_def = wielditem:get_definition()._x_obsidianmese or {}
    local slash_texture = _x_obsidianmese_def.slash_texture
    local slash_glow = _x_obsidianmese_def.slash_glow
    local slash_size = _x_obsidianmese_def.slash_size
    local slash_scale = _x_obsidianmese_def.slash_scale
    local slash_sound_name = _x_obsidianmese_def.slash_sound_name

    if slash_texture then
        local wielditem_name = wielditem:get_name()
        local wieldeditem_meta = wielditem:get_meta()
        local slash_time_start = wieldeditem_meta:get_string('x_obsidianmese:slash_time_start')
        tool_capabilities = tool_capabilities or wielditem:get_tool_capabilities()
        local full_punch_interval = tool_capabilities.full_punch_interval

        -- microseconds to seconds
        local current_time = core.get_us_time() / 1000000

        if slash_time_start == '' then
            -- First puch, assume full punch
            slash_time_start = current_time
            current_time = current_time + full_punch_interval
        else
            slash_time_start = tonumber(slash_time_start)
        end

        wieldeditem_meta:set_string('x_obsidianmese:slash_time_start', tostring(current_time))

        core.after(0, function(v_wielditem_name, v_player, v_wielditem)
            if v_wielditem_name == v_player:get_wielded_item():get_name() then
                v_player:set_wielded_item(v_wielditem)
            end
        end, wielditem_name, player, wielditem)

        local tflp = time_from_last_punch or current_time - slash_time_start

        if tflp >= full_punch_interval then
            local player_pos = player:get_pos()

            -- Raycast
            local player_props = player:get_properties()
            local eye_height = player_props.eye_height or 1.625
            local player_pos_with_eye_height = vector.new(player_pos.x, player_pos.y + eye_height, player_pos.z)
            local look_dir = player:get_look_dir()
            local distance = vector.distance(player_pos_with_eye_height, pos) + 0.5
            local new_pos = vector.add(player_pos_with_eye_height, vector.multiply(look_dir, distance))

            local ray = core.raycast(player_pos_with_eye_height, new_pos, true, false, nil)
            local particle_def = {
                velocity = vector.new(),
                acceleration = vector.new(),
                expirationtime = anim_total_duration - anim_frame_duration,
                collisiondetection = false,
                collision_removal = false,
                object_collision = false,
                vertical = false,
                animation = {
                    type = 'vertical_frames',
                    aspect_w = 32,
                    aspect_h = 32,
                    length = anim_total_duration
                },
                glow = slash_glow or 2
            }

            if is_new_particle then
                -- new particle def > 5.9.0
                particle_def.texture = {
                    name = 'x_obsidianmese_default_slash.png',
                    alpha_tween = { 1, 0.25 },
                    scale = slash_scale or { x = 30, y = 30 },
                    blend = 'alpha'
                }
            else
                -- old particle def
                particle_def.texture = 'x_obsidianmese_default_slash.png'
                particle_def.size = slash_size or 30
            end

            -- custom texture (if bool == true then keep default texture)
            if type(slash_texture) ~= 'boolean' then
                if is_new_particle then
                    -- new particle def > 5.9.0
                    particle_def.texture.name = slash_texture
                else
                    -- old particle def
                    particle_def.texture = slash_texture
                end
            end

            for pt in ray do
                if pt.type == 'node' then
                    --
                    -- Node
                    --
                    particle_def.pos = vector.add(pt.intersection_point, vector.divide(pt.intersection_normal, 3))

                elseif pt.type == 'object'
                    and not pt.ref:is_player()
                    and pt.ref:get_luaentity()
                then
                    --
                    -- Lua Entity
                    --
                    particle_def.pos = pt.intersection_point

                elseif pt.type == 'object'
                    and pt.ref:is_player()
                    and pt.ref:get_player_name() ~= player:get_player_name()
                then
                    --
                    -- Player Object
                    --
                    particle_def.pos = pt.intersection_point
                end

                if particle_def.pos then
                    core.add_particle(particle_def)

                    core.sound_play({
                        name = slash_sound_name or 'x_obsidianmese_sword_swing',
                        gain = 0.4,
                    }, {
                        pos = player_pos,
                        pitch = math.random(10, 15) / 10,
                        object = player,
                        max_hear_distance = 32
                    }, true)
                    break
                end
            end
        end
    end
end

core.register_on_mods_loaded(function()
    for name, def in pairs(core.registered_entities) do
        local old_punch = def.on_punch

        if not old_punch then
            old_punch = function() end
        end

        local on_punch = function(self, puncher, time_from_last_punch, tool_capabilities, dir, damage)
            old_punch(self, puncher, time_from_last_punch, tool_capabilities, dir, damage)

            local pos = self.object:get_pos()

            if not pos then
                return
            end

            add_swing_trail({
                pos = pos,
                player = puncher,
                time_from_last_punch = time_from_last_punch,
                tool_capabilities = tool_capabilities
            })
        end

        def.on_punch = on_punch

        core.register_entity(':' .. name, def)
    end
end)

core.register_on_punchnode(function(pos, node, puncher, pointed_thing)
    add_swing_trail({
        pos = pos,
        player = puncher
    })
end)

core.register_on_punchplayer(function(player, hitter, time_from_last_punch, tool_capabilities, dir, damage)
    add_swing_trail({
        pos = player:get_pos(),
        player = hitter,
        time_from_last_punch = time_from_last_punch,
        tool_capabilities = tool_capabilities
    })
end)
