--luacheck: ignore
local Public = {}

local table_size = table.size
local table_insert = table.insert
local math_random = math.random
local math_rad = math.rad
local math_sin = math.sin
local math_cos = math.cos
local math_floor = math.floor

local Table = require 'modules.scrap_towny_ffa.table'
local Enemy = require 'modules.scrap_towny_ffa.enemy'
local Building = require 'modules.scrap_towny_ffa.building'

-- don't spawn if town is within this range
local spawn_point_town_buffer = 256
-- clear enemies within this distance from spawn
local spawn_point_safety = 16
-- incremental spawn distance from existing town
-- this is how much each attempt is incremented by for checking a pollution free area
local spawn_point_incremental_distance = 16

local function force_load(position, surface, radius)
    --log("is_chunk_generated = " .. tostring(surface.is_chunk_generated(position)))
    surface.request_to_generate_chunks(position, radius)
    --log("force load position = {" .. position.x .. "," .. position.y .. "}")
    surface.force_generate_chunk_requests()
end

-- gets an area (might not be even amount)
local function get_area(position, w, h)
    local x1 = math_floor(w / 2)
    local x2 = w - x1
    local y1 = math_floor(h / 2)
    local y2 = h - y1
    return {{position.x - x1, position.y - y1}, {position.x + x2, position.y + y2}}
end

local function clear_spawn(position, surface, w, h)
    --log("clear_spawn {" .. position.x .. "," .. position.y .. "}")
    local area = get_area(position, w, h)
    for _, e in pairs(surface.find_entities_filtered({area = area})) do
        if e.type ~= 'character' then
            e.destroy()
        end
    end
end

-- does the position have any pollution
local function has_pollution(position, surface)
    local result = surface.get_pollution(position) > 0.0
    --log("has_pollution = " .. tostring(result))
    return result
end

-- is the position already used
local function in_use(position)
    local ffatable = Table.get_table()
    local result = false
    for _, v in pairs(ffatable.spawn_point) do
        if v == position then
            result = true
        end
    end
    --log("in_use = " .. tostring(result))
    return result
end

-- is the position empty
local function is_empty(position, surface)
    local entity_radius = 1
    local tile_radius = 1
    local entities = surface.count_entities_filtered({position = position, radius = entity_radius, collision_mask = {'object-layer', 'player-layer'}})
    --log("entities = " .. entities)
    if entities > 0 then
        return false
    end
    local tiles = surface.count_tiles_filtered({position = position, radius = tile_radius, collision_mask = 'water-tile'})
    --log("water-tiles = " .. tiles)
    if tiles > 0 then
        return false
    end
    local result = surface.can_place_entity({name = 'character', position = position})
    --log("is_empty = " .. tostring(result))
    return result
end

-- finds a valid spawn point that is not near a town and not in a polluted area
local function find_valid_spawn_point(surface)
    local ffatable = Table.get_table()
    -- check center of map first if valid
    local position = {x = 0, y = 0}
    --log("testing {" .. position.x .. "," .. position.y .. "}")
    force_load(position, surface, 1)
    if in_use(position) == false then
        if Building.near_town(position, surface, spawn_point_town_buffer) == false then
            -- force load the position
            if is_empty(position, surface) == true then
                --log("found valid spawn point at {" .. position.x .. "," .. position.y .. "}")
                return position
            end
        end
    end
    -- otherwise find a nearby town
    local keyset = {}
    for town_name, _ in pairs(ffatable.town_centers) do
        table_insert(keyset, town_name)
    end
    local count = table_size(keyset)
    if count > 0 then
        local town_name = keyset[math_random(1, count)]
        local town_center = ffatable.town_centers[town_name]
        if town_center ~= nil then
            position = town_center.market.position
        end
    --log("town center is {" .. position.x .. "," .. position.y .. "}")
    end
    -- and start checking around it for a suitable spawn position
    local tries = 0
    local radius = spawn_point_town_buffer
    local angle
    while (tries < 100) do
        -- 8 attempts each position
        for _ = 1, 8 do
            -- position on the circle radius
            angle = math_random(0, 360)
            local t = math_rad(angle)
            local x = math_floor(position.x + math_cos(t) * radius)
            local y = math_floor(position.y + math_sin(t) * radius)
            local target = {x = x, y = y}
            --log("testing {" .. target.x .. "," .. target.y .. "}")
            force_load(position, surface, 1)
            if in_use(target) == false then
                if has_pollution(target, surface) == false then
                    if Building.near_town(target, surface, spawn_point_town_buffer) == false then
                        if is_empty(target, surface) == true then
                            --log("found valid spawn point at {" .. target.x .. "," .. target.y .. "}")
                            position = target
                            return position
                        end
                    end
                end
            end
        end
        -- near a town, increment the radius and select another angle
        radius = radius + math_random(1, spawn_point_incremental_distance)
        angle = math_random(0, 360)
        tries = tries + 1
    end
    return {x = 0, y = 0}
end

function Public.get_new_spawn_point(player, surface)
    local ffatable = Table.get_table()
    -- get a new spawn point
    local position = find_valid_spawn_point(surface)
    -- should never be invalid or blocked
    ffatable.spawn_point[player.name] = position
    --log("player " .. player.name .. " assigned new spawn point at {" .. position.x .. "," .. position.y .. "}")
    return position
end

-- gets a new or existing spawn point for the player
function Public.get_spawn_point(player, surface)
    local ffatable = Table.get_table()
    if ffatable.spawn_point == nil then
        ffatable.spawn_point = {}
    end
    -- test the existing spawn point
    local position = ffatable.spawn_point[player.name]
    if position ~= nil then
        -- check that the spawn point is not blocked
        if surface.can_place_entity({name = 'character', position = position}) then
            --log("player " .. player.name .. "using existing spawn point at {" .. position.x .. "," .. position.y .. "}")
            return position
        else
            position = surface.find_non_colliding_position('character', position, 16, 0.25)
            if (position ~= nil) then
                return position
            end
        end
    end
    -- otherwise get a new spawn point
    return Public.get_new_spawn_point(player, surface)
end

function Public.clear_spawn_point(position, surface)
    Enemy.clear_worms(position, surface, spawn_point_safety) -- behemoth worms can attack from a range of 48, clear first time only
    Enemy.clear_enemies(position, surface, spawn_point_safety) -- behemoth worms can attack from a range of 48
    clear_spawn(position, surface, 7, 9)
end

return Public
