--[[-- info
    Provides the ability to spawn aliens.
]]

-- dependencies
require 'utils.table'
local Event = require 'utils.event'
local Global = require 'utils.global'
local Token = require 'utils.token'
local Task = require 'utils.task'
local AlienEvolutionProgress = require 'utils.alien_evolution_progress'
local Debug = require 'map_gen.maps.diggy.debug'
local Template = require 'map_gen.maps.diggy.template'
local CreateParticles = require 'features.create_particles'
local format_number = require 'util'.format_number
local random = math.random
local floor = math.floor
local ceil = math.ceil
local size = table.size
local pairs = pairs
local raise_event = script.raise_event
local get_aliens = AlienEvolutionProgress.get_aliens
local create_spawner_request = AlienEvolutionProgress.create_spawner_request
local set_timeout_in_ticks = Task.set_timeout_in_ticks
local destroy_rock = CreateParticles.destroy_rock

-- this
local AlienSpawner = {}

local config

local memory = {
    alien_collision_boxes = {},
}
local locations_to_scan = {
    {x = 0, y = -1.5}, -- up
    {x = 1.5, y = 0}, -- right
    {x = 0, y = 1.5}, -- bottom
    {x = -1.5, y = 0}, -- left
}

Global.register_init({
    memory = memory,
}, function(tbl)
    for name, prototype in pairs(game.entity_prototypes) do
        if prototype.type == 'unit' and prototype.subgroup.name == 'enemies' then
            tbl.memory.alien_collision_boxes[name] = prototype.collision_box
        end
    end
end, function(tbl)
    memory = tbl.memory
end)

local rocks_to_find = Template.diggy_rocks

---Triggers mining at the collision_box of the alien, to free it
local do_alien_mining = Token.register(function(params)
    local surface = params.surface
    local create_entity = surface.create_entity
    local create_particle = surface.create_particle
    local find_non_colliding_position = surface.find_non_colliding_position

    local rocks = surface.find_entities_filtered({area = params.clear_area, name = rocks_to_find})

    local rock_count = #rocks
    if rock_count > 0 then
        -- with multiple rocks opening at once, it will spawn less particles in total per rock
        local particle_count
        if rock_count == 1 then
            particle_count = 15
        elseif rock_count == 2 then
            particle_count = 10
        else
            particle_count = 5
        end

        for rock_index = rock_count, 1, -1 do
            local rock = rocks[rock_index]
            raise_event(defines.events.script_raised_destroy, {entity = rock, cause = "alien_emerges"})
            destroy_rock(create_particle, particle_count, rock.position)
            rock.destroy()
        end
    end

    local spawn_location = params.spawn_location
    -- amount is not used for aliens prototypes, it just carries along in the params
    local amount = spawn_location.amount
    while amount > 0 do
        spawn_location.position = find_non_colliding_position(spawn_location.name, spawn_location.position, 2, 0.4) or spawn_location.position
        create_entity(spawn_location)
        amount = amount - 1
    end
end)

---Spawns aliens given the parameters.
---@param aliens table index is the name, value is the amount of biters to spawn
---@param force LuaForce of the biters
---@param surface LuaSurface to spawn on
---@param x number
---@param y number
local function spawn_aliens(aliens, force, surface, x, y)
    local position = {x = x, y = y}
    local count_tiles_filtered = surface.count_tiles_filtered

    local spawn_count = 0
    local alien_collision_boxes = memory.alien_collision_boxes

    for name, amount in pairs(aliens) do
        local collision_box = alien_collision_boxes[name]
        if not collision_box then
            Debug.print_position(position, 'Unable to find prototype data for ' .. name)
            break
        end

        local left_top = collision_box.left_top
        local right_bottom = collision_box.right_bottom
        local left_top_x = left_top.x * 1.6
        local left_top_y = left_top.y * 1.6
        local right_bottom_x = right_bottom.x * 1.6
        local right_bottom_y = right_bottom.y * 1.6

        for i = #locations_to_scan, 1, -1 do
            local direction = locations_to_scan[i]
            local x_center = direction.x + x
            local y_center = direction.y + y

            -- *_center indicates the offset center relative to the location where it should spawn
            -- the area is composed of the bounding_box of the alien with a bigger size so it has space to move
            local offset_area = {
                left_top = {
                    x = floor(x_center + left_top_x),
                    y = floor(y_center + left_top_y),
                },
                right_bottom = {
                    x = ceil(x_center + right_bottom_x),
                    y = ceil(y_center + right_bottom_y),
                },
            }

            -- can't spawn properly if void is present
            if count_tiles_filtered({area = offset_area, name = 'out-of-map'}) == 0 then
                spawn_count = spawn_count + 1
                set_timeout_in_ticks(spawn_count, do_alien_mining, {
                    surface = surface,
                    clear_area = offset_area,
                    spawn_location = {
                        name = name,
                        position = {x = x_center, y = y_center},
                        force = force,
                        amount = amount
                    },
                })
                break
            end
        end
    end
end

---Spawns a worm given the parameters.
---@param evo evolution value to pick the right worm
---@param force LuaForce of the biters
---@param surface LuaSurface to spawn on
---@param x number
---@param y number
local function spawn_worm(evolution, force, surface, x, y)
    local position = {x = x, y = y}
    local count_tiles_filtered = surface.count_tiles_filtered

    local spawn_count = 0
    local worm
    if (evolution < 0.2) then 
        worm = "small-worm-turret"
    else if (evolution < 0.4) then 
        worm = "medium-worm-turret"
    else if (evolution < 0.6) then 
        worm = "big-worm-turret"
    else if (evolution < 0.65) then 
        worm = "bob-big-worm-turret"
    else if (evolution < 0.7) then 
		if ((y+x)%4) == 0 then worm = "bob-big-poison-worm-turret" end
		if ((y+x)%4) == 1 then worm = "bob-big-fire-worm-turret" end
		if ((y+x)%4) == 2 then worm = "bob-big-electric-worm-turret" end
		if ((y+x)%4) == 3 then worm = "bob-big-explosive-worm-turret" end
    else if (evolution < 0.8) then 
		if ((y+x)%6) == 0 then worm = "bob-big-poison-worm-turret" end
		if ((y+x)%6) == 1 then worm = "bob-big-fire-worm-turret" end
		if ((y+x)%6) == 2 then worm = "bob-big-electric-worm-turret" end
		if ((y+x)%6) == 3 then worm = "bob-big-explosive-worm-turret" end
		if ((y+x)%6) == 4 then worm = "bob-big-piercing-worm-turret" end
		if ((y+x)%6) == 5 then worm = "bob-big-acid-worm-turret" end
    else if (evolution < 0.9) then 
		if ((y+x)%9) == 0 then worm = "bob-big-poison-worm-turret" end
		if ((y+x)%9) == 1 then worm = "bob-big-fire-worm-turret" end
		if ((y+x)%9) == 2 then worm = "bob-big-electric-worm-turret" end
		if ((y+x)%9) == 3 then worm = "bob-big-explosive-worm-turret" end
		if ((y+x)%9) == 4 then worm = "bob-big-piercing-worm-turret" end
		if ((y+x)%9) == 5 then worm = "bob-big-acid-worm-turret" end
		if ((y+x)%9) > 5 then worm = "bob-big-worm-turret" end
    else if (evolution < 0.92) then 
		if ((y+x)%9) == 0 then worm = "bob-big-poison-worm-turret" end
		if ((y+x)%9) == 1 then worm = "bob-big-fire-worm-turret" end
		if ((y+x)%9) == 2 then worm = "bob-big-electric-worm-turret" end
		if ((y+x)%9) == 3 then worm = "bob-big-explosive-worm-turret" end
		if ((y+x)%9) == 4 then worm = "bob-big-piercing-worm-turret" end
		if ((y+x)%9) == 5 then worm = "bob-big-acid-worm-turret" end
		if ((y+x)%9) == 6 then worm = "bob-big-worm-turret" end
		if ((y+x)%9) > 5 then worm = "bob-giant-worm-turret" end
    else if (evolution < 0.94) then 
		if ((y+x)%9) == 0 then worm = "bob-big-poison-worm-turret" end
		if ((y+x)%9) == 1 then worm = "bob-big-fire-worm-turret" end
		if ((y+x)%9) == 2 then worm = "bob-big-electric-worm-turret" end
		if ((y+x)%9) == 3 then worm = "bob-big-explosive-worm-turret" end
		if ((y+x)%9) == 4 then worm = "bob-big-piercing-worm-turret" end
		if ((y+x)%9) == 5 then worm = "bob-big-acid-worm-turret" end
		if ((y+x)%9) == 6 then worm = "bob-big-worm-turret" end
		if ((y+x)%9) == 7 then worm = "bob-giant-worm-turret" end
		if ((y+x)%9) > 5 then worm = "behemoth-worm-turret" end
    else if (evolution < 0.94) then 
		if ((y+x)%9) == 0 then worm = "bob-big-electric-worm-turret" end
		if ((y+x)%9) == 1 then worm = "bob-big-explosive-worm-turret" end
		if ((y+x)%9) == 2 then worm = "bob-big-piercing-worm-turret" end
		if ((y+x)%9) == 3 then worm = "bob-big-acid-worm-turret" end
		if ((y+x)%9) == 4 then worm = "bob-big-worm-turret" end
		if ((y+x)%9) == 5 then worm = "bob-giant-worm-turret" end
		if ((y+x)%9) == 6 then worm = "behemoth-worm-turret" end
		if ((y+x)%9) > 6 then worm = "maf-behemoth-worm-turret" end
    else if (evolution < 0.96) then 
		if ((y+x)%9) == 0 then worm = "bob-big-electric-worm-turret" end
		if ((y+x)%9) == 1 then worm = "bob-big-acid-worm-turret" end
		if ((y+x)%9) == 2 then worm = "bob-big-worm-turret" end
		if ((y+x)%9) == 3 then worm = "bob-giant-worm-turret" end
		if ((y+x)%9) == 4 then worm = "behemoth-worm-turret" end
		if ((y+x)%9) == 5 then worm = "maf-behemoth-worm-turret" end
		if ((y+x)%9) > 5 then worm = "maf-colossal-worm-turret" end
    else if (evolution < 0.98) then 
		if ((y+x)%9) == 0 then worm = "bob-big-electric-worm-turret" end
		if ((y+x)%9) == 1 then worm = "bob-big-acid-worm-turret" end
		if ((y+x)%9) == 2 then worm = "bob-big-worm-turret" end
		if ((y+x)%9) == 3 then worm = "bob-giant-worm-turret" end
		if ((y+x)%9) == 4 then worm = "behemoth-worm-turret" end
		if ((y+x)%9) == 5 then worm = "maf-behemoth-worm-turret" end
		if ((y+x)%9) == 6 then worm = "maf-colossal-worm-turret" end
		if ((y+x)%9) > 6 then worm = "bm-worm-boss-acid-shooter" end
    else 
		if ((y+x)%10) <= 1 then worm = "behemoth-worm-turret" end
		if ((y+x)%10) > 5 then worm = "spawner" end
		if ((y+x)%10) == 2 then worm = "maf-behemoth-worm-turret" end
		if ((y+x)%10) == 3 then worm = "maf-colossal-worm-turret" end
		if ((y+x)%10) == 4 then worm = "bm-worm-boss-acid-shooter" end
		if ((y+x)%10) == 5 then worm = "bm-worm-boss-fire-shooter" end
    end end end end end end end end end end end end 
    if (worm == "spawner") then
		if (game.tick % 7 == 0) then worm = "bob-biter-spawner" end
		if (game.tick % 7 == 1) then worm = "bob-spitter-spawner" end
		if (game.tick % 7 == 2) and (y > 1000) then worm = "bob-super-spawner" end
		if (game.tick % 7 == 3) and (y > 300)then worm = "ne-spawner-blue" end
		if (game.tick % 7 == 4) and (y > 300)then worm = "ne-spawner-red" end
		if (game.tick % 7 == 5) and (y > 300)then worm = "ne-spawner-green" end
		if (game.tick % 7 == 6) and (y > 300)then worm = "ne-spawner-yellow" end
	end 
    surface.create_entity({name = worm,position = {x = x, y = y},force = force})
end

--[[--
    Registers all event handlers.
]]
function AlienSpawner.register(cfg)
    config = cfg
    local alien_minimum_distance_square = cfg.alien_minimum_distance ^ 2
    local alien_probability = cfg.alien_probability
    local hail_hydra = cfg.hail_hydra
    local evolution_per_void_removed = cfg.evolution_per_void_removed

    if size(hail_hydra) > 0 then
        global.config.hail_hydra.enabled = true
        global.config.hail_hydra.hydras = hail_hydra
    end

    Event.add(Template.events.on_void_removed, function (event)
        local force = game.forces.enemy
        local evolution_factor = force.evolution_factor
        force.evolution_factor = evolution_factor + evolution_per_void_removed

        local position = event.position
        local x = position.x
        local y = position.y
		local rnd = random();

        if (x * x + y * y < alien_minimum_distance_square) then            return        end
		evolution_factor = math.min(1, force.evolution_factor/2.0 + math.sqrt(x * x + y * y)/2000)
		if alien_probability/4 > rnd then 
            -- Spawn harder aliens the deeper you go: average of map's evolution and distance/1500 tiles
            spawn_worm(evolution_factor, force, event.surface, x, y)
		else
        if alien_probability > rnd then 
            -- Spawn harder aliens the deeper you go: average of map's evolution and distance/1500 tiles
            spawn_aliens(get_aliens(create_spawner_request(3), evolution_factor ), force, event.surface, x, y)        
        end end

    end)
end

function AlienSpawner.get_extra_map_info(cfg)
    local multiplier = 10000

    return [[Alien Spawner, aliens might spawn when mining!
Spawn chance: ]] .. (cfg.alien_probability * 100) .. [[%
Minimum spawn distance: ]] .. cfg.alien_minimum_distance .. [[ tiles
Evolution is increased by ]] ..(multiplier * cfg.evolution_per_void_removed * 100)  .. '% per ' .. format_number(multiplier, true) .. [[ mine size
Spawn difficulty is affected by the depth: +one lvl per 100 tiles
]]
end

function AlienSpawner.on_init()
    if config.evolution_over_time_factor then
        game.map_settings.enemy_evolution.time_factor = config.evolution_over_time_factor
    end
    game.forces.enemy.evolution_factor = config.initial_evolution * 0.01
    game.map_settings.pollution.enabled = false
end

return AlienSpawner
