

local me = pmb_mob_spawn

local function debug_particle(pos, color, time)
    do return end -- for debug purposes
    minetest.add_particle({
        size = 8,
        pos = pos + vector.new(0, math.random(), 0),
        texture = "white.png^[colorize:"..color..":255",
        velocity = vector.new(0, math.random(), 0),
        expirationtime = time,
        glow = 14,
    })
end

function me.mob_spawn_step(dtime)
    -- for each player, find places near them to spawn stuff
end

function me.is_cap_exceded(mob_name)
    for _, groupname in pairs(pmb_mob_spawn.mob[mob_name].groups) do
        if pmb_mob_spawn.cap[groupname].cur > pmb_mob_spawn.cap[groupname].max then
            return true
        end
    end
    return false
end

-- pick a mob based on the location
function me.get_spawn_mob(pos, choosefrom)
    if not me.has_player_in_range(pos, me.spawn_dist.min, me.spawn_dist.max) then return false end
    if me.has_player_in_range(pos, 0, me.spawn_dist.min) then return false end

    local test_in = choosefrom or pmb_mob_spawn.mob_index
    local biome_data = minetest.get_biome_data(pos)
    local spawn_info = {
        biome_name = minetest.get_biome_name(biome_data.biome),
        biome_data = biome_data,
    }

    local i = math.random(1, #test_in)
    for try=1, #test_in do
        local mob_name = test_in[i]
        local mob_reg = pmb_mob_spawn.mob[mob_name]
        if not mob_reg then goto continue end
        if (mob_reg.biomes and #mob_reg.biomes > 0
        and mob_reg.biomes[spawn_info.biome_name] == nil) then
            -- minetest.chat_send_all("could not spawn " .. tostring(mob_name) .. ", biome " .. tostring(spawn_info.biome_name) .. " not allowed.")
            return end

        if me.is_cap_exceded(mob_name) then
            -- minetest.log(minetest.colorize("#f95", dump(mob_name) .. " cap exceeded"))
            goto continue end

        if mob_reg.can_spawn and not mob_reg.can_spawn(pos, spawn_info) then
            -- minetest.log(minetest.colorize("#f0f", dump(mob_name) .. " mob doesn't want to spawn"))
            debug_particle(pos, "#f02", 15)
            goto continue
        end

        if true then
            -- minetest.log(minetest.colorize("#0f5", dump(mob_name)))
            debug_particle(pos, "#0f2", 15)
            return mob_name
        end

        ::continue::
        i = i % #test_in + 1
    end
    return nil
end

function me.add_to_cap(mob_name)
    if not pmb_mob_spawn.mob[mob_name] then
        -- minetest.log(mob_name)
        return false
    end
    for _, groupname in pairs(pmb_mob_spawn.mob[mob_name].groups) do
        pmb_mob_spawn.cap[groupname].cur = pmb_mob_spawn.cap[groupname].cur + 1
    end
    pmb_mob_spawn.cap["all"].cur = pmb_mob_spawn.cap["all"].cur + 1
end
function me.take_from_cap(mob_name)
    if not pmb_mob_spawn.mob[mob_name] then
        return false
    end
    for _, groupname in pairs(pmb_mob_spawn.mob[mob_name].groups) do
        pmb_mob_spawn.cap[groupname].cur = pmb_mob_spawn.cap[groupname].cur - 1
    end
    pmb_mob_spawn.cap["all"].cur = pmb_mob_spawn.cap["all"].cur - 1
end
function me.reset_cap()
    for groupname, group in pairs(pmb_mob_spawn.cap) do
        if not group.cur or group.cur == 0 then
        else
            group.cur = 0
        end
    end
end
function me.recalculate_cap()
    me.reset_cap()
    for _, self in pairs(minetest.luaentities) do
        if self.name and pmb_mob_spawn.mob[self.name] then
            me.add_to_cap(self.name)
        end
    end
end


pmb_mob_spawn.registered_spawnrules = {}
local regspawnrules = pmb_mob_spawn.registered_spawnrules
function pmb_mob_spawn.register_spawnrule(func)
    regspawnrules[#regspawnrules+1] = func
end

-- runs through all spawnrules
-- returns bool
function pmb_mob_spawn.is_spawnrules_allowed(pos)
    for i, func in ipairs(regspawnrules) do
        if not func(pos) then return false end
    end
    return true
end

-- try to spawn a random mob at a location
-- returns mob lua ent or nil
function me.spawn_mob_at(pos)
    if not pmb_mob_spawn.is_spawnrules_allowed(pos) then return end
    local mob_name = me.get_spawn_mob(pos)
    -- minetest.log("spawn try for " .. dump(mob_name))
    if (not mob_name) or not minetest.registered_entities[mob_name] then
        debug_particle(pos, "#ff00ff50", 15)
        return
    else
        debug_particle(pos, "#ff0", 15)
        -- minetest.log(minetest.colorize("#ff0", dump(mob_name) .. " :: SUCCESS"))
    end

    local mob_obj = minetest.add_entity(pos, mob_name)
    -- me.add_to_cap(mob_name)
    local self = mob_obj:get_luaentity()

    -- do the mob's spawn func
    if pmb_mob_spawn.mob[mob_name].on_spawn then
        pmb_mob_spawn.mob[mob_name].on_spawn(self, pos)
    end

    return self
end

function me.find_ground_at(pos, max_search)
    local sdef = minetest.registered_nodes[minetest.get_node(pos).name]

    if sdef and sdef.walkable then
        for i=1, max_search or 10 do
            local p = vector.offset(pos, 0, i, 0)
            local node = minetest.get_node(p)
            local def = minetest.registered_nodes[node.name]
            if def and not def.walkable then
                if minetest.get_item_group(node.name, "full_solid") == 0 then
                    return vector.offset(pos, 0, i, 0)
                end
            end
        end
    else
        for i=0, max_search or 10 do
            local p = vector.offset(pos, 0, -i, 0)
            local node = minetest.get_node(p)
            local def = minetest.registered_nodes[node.name]
            if def and def.walkable then
                if minetest.get_item_group(node.name, "full_solid") > 0 then
                    return vector.offset(pos, 0, -i + 1, 0)
                end
            end
        end
    end
    return false
end

function me.can_despawn(self)
    local name = self.name
    local pos = self.object:get_pos()
    -- force to despawn after a certain dist
    local default_despawn
    if pos and not pmb_mob_spawn.has_player_in_range(pos, 0, me.despawn_dist_force) then
        default_despawn = true
    -- don't despawn if close to player
    elseif pos and (pmb_mob_spawn.has_player_in_range(pos, 0, me.despawn_dist) or (self._no_despawn == true)) then
        default_despawn = false
    end
    -- figure out based on the custom func if it can despawn
    local can_despawn = ((pmb_mob_spawn.mob[name].can_despawn ~= nil) and pmb_mob_spawn.mob[name].can_despawn(self)) or false
    -- do it slowly, by using a random chance
    if (default_despawn == true) or (can_despawn and default_despawn == nil) then
        return true
    end
    -- if all else fails, don't despawn
    return false
end

function me.despawn_all()
    for _, self in pairs(minetest.luaentities) do
        if self.name then
            if not pmb_mob_spawn.mob[self.name] then break end

            if me.can_despawn(self) then
                me.take_from_cap(self.name)
                self.object:remove()
            end
        end
    end
end

minetest.register_abm({
    label = "pmb_mob_spawn:main_spawner",
    nodenames = {"air"},
    neighbours = {"group:full_solid"},
    interval = 10,
    chance = 8000,
    action = function(pos, node, active_object_count, active_object_count_wider)
        minetest.after(math.random() * 10, function()
            pmb_util.did_abm("mob spawn")
            if not me.enabled then return false end
            local ground = me.find_ground_at(pos)
            if not ground then return false end
            local mob = me.spawn_mob_at(ground)
        end)
    end
})


-- despawn mobs and recalculate mob cap manually for now until it can be made better
local despawn_timer = 1
minetest.register_globalstep(function(dtime)
    if not me.enabled then return false end
    despawn_timer = despawn_timer - dtime
    if despawn_timer < 0 then
        despawn_timer = despawn_timer + 5
        local t = os.clock()
        -- me.spawn_by_players()
        me.recalculate_cap()
        -- minetest.log(minetest.colorize("#f91", (os.clock() - t) * 100))
        me.despawn_all()
        -- minetest.log(minetest.colorize("#f0f", me.cap.all.cur))
    end
end)

