local mod_name = minetest.get_current_modname()
local mod_path = minetest.get_modpath(mod_name)
local S = minetest.get_translator(mod_name)

local sfx = pmb_statusfx.registered_status_effects
local pl = pmb_statusfx.player_effects

local function is_valid_player(name)
    if minetest.player_exists(name) then
        local player = minetest.get_player_by_name(name)
    end
end

function pmb_statusfx.register_status(name, def)
    if def.fx_use_player_functions_for_objects then
        def.on_apply_object = def.on_apply_player
        def.on_timeout_object = def.on_timeout_player
        def.on_remove_object = def.on_remove_player
    end
    sfx[name] = def
end


local messages = {
    "            hey you, you're finally awake...",
    "            you were trying to cross the border, weren't you",
    "            just like us, and that thief over there",
}
pmb_statusfx.register_status("awakening", {
    ----
    fx_allow_player = true,
    fx_allow_object = false,
    -- if true, counts down one stack then the next, rather than all of them, allows stacking time up infinitely
    fx_only_tick_once = false,
    fx_only_apply_once = true,
    fx_timeout_individual = true,
    ----
    on_apply_player = function(object, count, meta)
        meta.next_trigger = meta.time - 1
        meta.next_message = (meta.next_message or 1)
        return true -- false to prevent apply
    end,
    on_remove_player = function(object, count, meta)
        return false -- true to reset timer
    end,
    on_step_player = function(object, count, meta, dtime)
        if meta.next_trigger and meta.time
        and meta.time < meta.next_trigger then
            minetest.chat_send_player(object:get_player_name(), messages[meta.next_message or 1] or "")
            meta.next_trigger = meta.next_trigger - 1
            meta.next_message = (meta.next_message or 1) + 1
        end
        return false -- true to remove
    end,
    ----
})

local function check_player_status_effect(name, status_name)
    if not pl[name] then pl[name] = {} end
    if not pl[name][status_name] then
        pl[name][status_name] = {
            meta = {time=0},
            stack = {}
        }
    end
end

function pmb_statusfx.apply_status(object, status_name, duration, params)
    local sdef = sfx[status_name]
    if not sdef then
        minetest.log("warning", "No status with name "..dump(status_name))
        return false end

    local husk = {
        time = duration or 1
    }

    if minetest.is_player(object) then
        local name = object:get_player_name()
        -- make sure the player is in the system and has this status_name as a key so you don't `nil[status_name]` later
        check_player_status_effect(name, status_name)

        -- the stack contains all effects and their timeouts {time=3}. Could contain more info in future like source
        local stack = pl[name][status_name].stack
        stack[#stack+1] = husk

        local no_apply
        -- contains persistent information about the effect, such as the total time / longest duration
        local meta = pl[name][status_name].meta
        if #stack == 1 or not sdef.fx_only_apply_once then
            no_apply = sdef.on_apply_player(object, #stack, meta, params or {})
            if no_apply then
                stack[#stack] = nil
            end
        end

        if not no_apply then
            if meta.time < duration then
                meta.time = duration
            end
            -- show the effect on the hud if it has a hud defined
            pmb_statusfx.hud.add_hud(object, status_name)
        end
    else
        local ent = object:get_luaentity()
        -- only apply to mobs that allow it explicitly
        if ent and not ent._pmb_statusfx_enable then return false end

        local can_save_static = (ent._pmb_staticdata_load_list ~= nil)
        if can_save_static then
            ent._pmb_staticdata_load_list[#ent._pmb_staticdata_load_list+1] = "_pmb_status_effects"
        end

        if not ent._pmb_status_effects then ent._pmb_status_effects = {} end
        if not ent._pmb_status_effects[status_name] then
            ent._pmb_status_effects[status_name] = {stack={}, meta={time=duration}}
        end

        -- the stack contains all effects and their timeouts {time=3}. Could contain more info in future like source
        local stack = ent._pmb_status_effects[status_name].stack
        stack[#stack+1] = husk

        local no_apply
        -- contains persistent information about the effect, such as the total time / longest duration
        local meta = ent._pmb_status_effects[status_name].meta
        local old_meta_time = meta.time

        if #stack == 1 or not sdef.fx_only_apply_once then
            -- ask the effect whether it should affect this mob, and do its init stuff
            no_apply = sdef.on_apply_object(object, #stack, meta, params or {})
            -- if the mob has a function deciding if it should allow statuses, then check that
            if (not no_apply) and ent._pmb_statusfx_on_apply then
                meta.time = duration
                no_apply = ent._pmb_statusfx_on_apply(ent, status_name, meta, params or {})
            end
            if no_apply then
                stack[#stack] = nil
                meta.time = old_meta_time
            end
        end
    end
end

function pmb_statusfx.player_has_status(player, status_name)
    local pi = pl[player:get_player_name()]
    if pi and pi[status_name] and #pi[status_name].stack > 0 then
        return true
    else
        return false
    end
end

-- forcibly remove status, but notify the status of this
function pmb_statusfx.remove_status(player, status_name, no_notify)
    if not pmb_statusfx.player_has_status(player, status_name) then return end
    local name = player:get_player_name()
    local sdef = sfx[status_name]
    local stack = pl[name][status_name].stack

    local meta = pl[name][status_name].meta
    local count = #stack

    for i=#stack, 1, -1 do
        if sdef.fx_only_tick_once == true and i < #stack then break end
        if sdef.fx_timeout_individual then
            if not no_notify then sdef.on_remove_player(player, count, meta, true) end
        end
        table.remove(stack, i)
    end

    if (not sdef.fx_timeout_individual) then
        if not no_notify then sdef.on_remove_player(player, #stack, meta, true) end
        pmb_statusfx.hud.remove_hud(player, status_name)
    end
    pl[name][status_name] = nil
end


local _timer = 2
function pmb_statusfx.on_step(dtime)
    -- tick all effects for players
    for player_name, nss in pairs(pl) do
        local player = minetest.get_player_by_name(player_name)
        if not player then goto continue end
        for status_name, status_data in pairs(pl[player_name]) do
            local meta = pl[player_name][status_name].meta
            meta.time = meta.time - dtime

            local stack = status_data.stack
            local count = #stack
            local sdef = sfx[status_name]
            if not sdef then
                pl[player_name][status_name] = nil
                minetest.log("warning", "Removing status effect save data for "..status_name.." because there is no definition for it")
                goto continue2
            end

            local to_remove
            if sdef.on_step_player and count > 0 then
                to_remove = sdef.on_step_player(player, count, meta, dtime)
            end

            -- loop backward to make sure you don't upset the indexes
            for i=#stack, 1, -1 do
                if sdef.fx_only_tick_once == true and i < #stack then break end

                stack[i].time = stack[i].time - dtime
                if stack[i].time <= 0 then
                    if sdef.fx_timeout_individual then
                        sdef.on_remove_player(player, count, meta)
                    end
                    table.remove(stack, i)
                end
            end

            if (not sdef.fx_timeout_individual) and (count ~= #stack) and (#stack == 0) then
                sdef.on_remove_player(player, #stack, meta)
                pmb_statusfx.hud.remove_hud(player, status_name)
            end

            if #stack == 0 then
                pl[player_name][status_name] = nil
            end
            ::continue2::
        end
        -- update the hud
        pmb_statusfx.hud.update_hud(player)

        ::continue::
    end

    if _timer < 0 then
        world_storage.set("pmb_statusfx", "playerdata", pl)
        world_storage.set_timeout("pmb_statusfx", 1)
        _timer = 5
    else
        _timer = _timer - dtime
    end
end

local function tick_entities(dtime)
    for j, object in ipairs(minetest.get_objects_in_area(vector.new(-30912,-30912,-30912), vector.new(30927,30927,30927))) do
        local ent = object:get_luaentity()
        if ent and ent._pmb_status_effects then
            for status_name, status_data in pairs(ent._pmb_status_effects) do
                local stack = status_data.stack
                local count = #stack
                local sdef = sfx[status_name]
                if not sdef then
                    ent._pmb_status_effects[status_name] = nil
                    minetest.log("warning", "Removing status effect save data for "..status_name.." because there is no definition for it")
                    goto continue
                end

                local meta = ((#stack > 0) and stack[1]) or {}

                local to_remove
                if sdef.on_step_object and count > 0 then
                    to_remove = sdef.on_step_object(object, count, meta, dtime)
                end

                local rem = {}
                for i=1, #stack do
                    if sdef.fx_only_tick_once == true and i > 1 then break end

                    stack[i].time = stack[i].time - dtime
                    if stack[i].time <= 0 then
                        if i == 1 and stack[2] ~= nil then
                            local t = stack[2].time
                            stack[2] = table.copy(meta)
                            stack[2].time = t
                        end
                        rem[#rem+1] = i
                        if sdef.fx_timeout_individual then
                            sdef.on_remove_object(object, count, meta)
                        end
                    end
                end

                for k,i in ipairs(rem) do
                    table.remove(stack, i)
                    if #stack == 0 then
                    end
                end

                if (not sdef.fx_timeout_individual) and count ~= #stack and #stack == 0 then
                    sdef.on_remove_object(object, count, meta)
                    pmb_statusfx.hud.remove_hud(object, status_name)
                end

                ent._pmb_status_effects[status_name] = status_data
                ::continue::
            end
        end
    end
end

minetest.register_globalstep(function(dtime)
    pmb_statusfx.on_step(dtime)
    tick_entities(dtime)
    -- error(dump(pl))
end)

-- minetest.register_on_joinplayer(function(player, last_login)
--     pmb_statusfx.apply_status(player, "awakening", 6, {})
-- end)

