--luacheck: ignore
local Public = {}

local GetNoise = require 'utils.get_noise'
local Constants = require 'maps.cave_miner_v2.constants'
local BiterRaffle = require 'functions.biter_raffle'
local LootRaffle = require 'functions.loot_raffle'
local Esq = require 'modules.entity_spawn_queue'
local Pets = require 'modules.biter_pets'

local math_sqrt = math.sqrt
local math_random = math.random
local math_floor = math.floor

local spawn_amount_rolls = {}
for a = 48, 1, -1 do
    table.insert(spawn_amount_rolls, math_floor(a ^ 5))
end

function Public.get_difficulty_modifier(position)
    local difficulty = math_sqrt(position.x ^ 2 + position.y ^ 2) * 0.0001
    return difficulty
end

function Public.get_colored_name(player_index)
    local player = game.players[player_index]
    local colored_name = table.concat({'[color=', player.chat_color.r, ',', player.chat_color.g, ',', player.chat_color.b, ']', player.name, '[/color]'})
    return colored_name
end

function Public.unstuck_player(player_index)
    local player = game.players[player_index]
    local surface = player.surface
    local position = surface.find_non_colliding_position('character', player.position, 32, 0.5)
    if not position then
        return
    end
    player.teleport(position, surface)
end

function Public.roll_biter_amount()
    local max_chance = 0
    for k, v in pairs(spawn_amount_rolls) do
        max_chance = max_chance + v
    end
    local r = math_random(0, max_chance)
    local current_chance = 0
    for k, v in pairs(spawn_amount_rolls) do
        current_chance = current_chance + v
        if r <= current_chance then
            return k
        end
    end
end

--lab-dark-1 > position has been copied
--lab-dark-2 > position has been visited
function Public.reveal(cave_miner, surface, source_surface, position, brushsize)
    local tile = source_surface.get_tile(position)
    if tile.name == 'lab-dark-2' then
        return
    end
    local tiles = {}
    local copied_tiles = {}
    local i = 0
    local brushsize_square = brushsize ^ 2
    for _, tile in pairs(source_surface.find_tiles_filtered({area = {{position.x - brushsize, position.y - brushsize}, {position.x + brushsize, position.y + brushsize}}})) do
        local tile_position = tile.position
        if tile.name ~= 'lab-dark-2' and tile.name ~= 'lab-dark-1' and (position.x - tile_position.x) ^ 2 + (position.y - tile_position.y) ^ 2 < brushsize_square then
            i = i + 1
            copied_tiles[i] = {name = 'lab-dark-1', position = tile.position}
            tiles[i] = {name = tile.name, position = tile.position}
        end
    end
    surface.set_tiles(tiles, true, false, false, false)
    source_surface.set_tiles(copied_tiles, false, false, false, false)

    for _, entity in pairs(source_surface.find_entities_filtered({area = {{position.x - brushsize, position.y - brushsize}, {position.x + brushsize, position.y + brushsize}}})) do
        local entity_position = entity.position
        if (position.x - entity_position.x) ^ 2 + (position.y - entity_position.y) ^ 2 < brushsize_square then
            local e = entity.clone({position = entity_position, surface = surface})
            if entity.force.index == 2 then
                e.active = true
                table.insert(cave_miner.reveal_queue, {entity.type, entity.position.x, entity.position.y})
            end
            entity.destroy()
        end
    end

    source_surface.set_tiles({{name = 'lab-dark-2', position = position}}, false)
    source_surface.request_to_generate_chunks(position, 3)
end

function Public.get_base_ground_tile(position, seed)
    local noise = GetNoise('large_caves', position, seed)
    local name
    if noise < 0.1 then
        name = 'dirt-7'
    else
        name = 'nuclear-ground'
    end
    return name
end

function Public.spawn_player(player)
    if not player.character then
        player.create_character()
    end

    local surface = player.surface
    local position
    position = surface.find_non_colliding_position('character', player.force.get_spawn_position(surface), 48, 1)
    if not position then
        position = player.force.get_spawn_position(surface)
    end
    player.teleport(position, surface)

    for name, count in pairs(Constants.starting_items) do
        player.insert({name = name, count = count})
    end
end

function Public.set_mining_speed(cave_miner, force)
    force.manual_mining_speed_modifier = -0.60 + cave_miner.pickaxe_tier * 0.40
    return force.manual_mining_speed_modifier
end

function Public.place_worm(surface, position, multiplier)
    local e = surface.create_entity({name = BiterRaffle.roll('worm', Public.get_difficulty_modifier(position) * multiplier), position = position, force = 'enemy'})
    return e
end

function Public.spawn_random_biter(surface, position, multiplier)
    local name = BiterRaffle.roll('mixed', Public.get_difficulty_modifier(position) * multiplier)
    local non_colliding_position = surface.find_non_colliding_position(name, position, 16, 1)
    local unit
    if non_colliding_position then
        unit = surface.create_entity({name = name, position = non_colliding_position, force = 'enemy'})
    else
        unit = surface.create_entity({name = name, position = position, force = 'enemy'})
    end
    unit.ai_settings.allow_try_return_to_spawner = false
    unit.ai_settings.allow_destroy_when_commands_fail = false
    return unit
end

function Public.rock_spawns_biters(cave_miner, position)
    local amount = Public.roll_biter_amount()
    local surface = game.surfaces.nauvis
    local difficulty_modifier = Public.get_difficulty_modifier(position)
    local tick = game.tick
    for c = 1, amount, 1 do
        Esq.add_to_queue(tick + c * 25, surface, {name = BiterRaffle.roll('mixed', difficulty_modifier), position = position, force = 'enemy'}, 8)
    end
end

function Public.loot_crate(surface, position, container_name, player_index)
    local amount_multiplier = Constants.treasures[container_name].amount_multiplier
    local base_amount = 16 * amount_multiplier
    local difficulty_modifier = Public.get_difficulty_modifier(position)
    local slots = game.entity_prototypes[container_name].get_inventory_size(defines.inventory.chest)
    local tech_bonus = Constants.treasures[container_name].tech_bonus
    local description = Constants.treasures[container_name].description

    local blacklist = LootRaffle.get_tech_blacklist(difficulty_modifier + tech_bonus)

    local item_stacks = LootRaffle.roll(base_amount + difficulty_modifier * amount_multiplier * 5000, slots, blacklist)
    local container = surface.create_entity({name = container_name, position = position, force = 'neutral'})
    for _, item_stack in pairs(item_stacks) do
        container.insert(item_stack)
    end
    container.minable = false

    if not description then
        return
    end
    if not player_index then
        return
    end
    local player = game.players[player_index]
    local text
    if math_random(1, 2) == 1 then
        text = player.name .. ' found ' .. description
    else
        text = player.name .. ' uncovered ' .. description
    end

    for _, player in pairs(game.forces.player.connected_players) do
        player.add_custom_alert(container, {type = 'item', name = 'wooden-chest'}, text, true)
    end
end

function Public.place_crude_oil(surface, position, multiplier)
    if not surface.can_place_entity({name = 'crude-oil', position = position, amount = 1}) then
        return
    end
    local d = math_sqrt(position.x ^ 2 + position.y ^ 2)
    local amount = math_random(50000, 100000) + d * 100 * multiplier
    surface.create_entity({name = 'crude-oil', position = position, amount = amount})
end

function Public.create_top_gui(player)
    local frame = player.gui.top.cave_miner
    if frame then
        return
    end
    frame = player.gui.top.add({type = 'frame', name = 'cave_miner', direction = 'horizontal'})
    frame.style.maximal_height = 38

    local label = frame.add({type = 'label', caption = 'Loading...'})
    label.style.font = 'heading-2'
    label.style.font_color = {225, 225, 225}
    label.style.margin = 0
    label.style.padding = 0

    local label = frame.add({type = 'label', caption = 'Loading...'})
    label.style.font = 'heading-2'
    label.style.font_color = {225, 225, 225}
    label.style.margin = 0
    label.style.padding = 0
end

function Public.update_top_gui(cave_miner)
    local pickaxe_tiers = Constants.pickaxe_tiers
    for _, player in pairs(game.connected_players) do
        local element = player.gui.top.cave_miner
        if element and element.valid then
            element.children[1].caption = pickaxe_tiers[cave_miner.pickaxe_tier] .. ' Pickaxe  | '
            element.children[1].tooltip = 'Mining speed ' .. (1 + game.forces.player.manual_mining_speed_modifier) * 100 .. '%'
            element.children[2].caption = 'Rocks broken: ' .. cave_miner.rocks_broken
        end
    end
end

local function is_entity_in_darkness(entity)
    if not entity then
        return
    end
    if not entity.valid then
        return
    end
    local position = entity.position

    local d = position.x ^ 2 + position.y ^ 2
    if d < 512 then
        return false
    end

    for _, lamp in pairs(entity.surface.find_entities_filtered({area = {{position.x - 16, position.y - 16}, {position.x + 16, position.y + 16}}, name = 'small-lamp'})) do
        local circuit = lamp.get_or_create_control_behavior()
        if circuit then
            if lamp.energy > 25 and circuit.disabled == false then
                return
            end
        else
            if lamp.energy > 25 then
                return
            end
        end
    end

    return true
end

local function darkness_event(cave_miner, entity)
    local index = tostring(entity.unit_number)
    local darkness = cave_miner.darkness

    if darkness[index] then
        darkness[index] = darkness[index] + 1
    else
        darkness[index] = -3
    end

    if darkness[index] <= 0 then
        return
    end

    local position = entity.position
    local difficulty_modifier = Public.get_difficulty_modifier(position)

    local count = math_floor(darkness[index] * 0.33) + 1
    if count > 16 then
        count = 16
    end
    for c = 1, count, 1 do
        Esq.add_to_queue(game.tick + math_random(5, 45) * c, entity.surface, {name = BiterRaffle.roll('mixed', difficulty_modifier), position = position, force = 'enemy'}, 8)
    end

    entity.damage(darkness[index] * 2, 'neutral', 'poison')
end

function Public.darkness(cave_miner)
    for _, player in pairs(game.connected_players) do
        local character = player.character
        if character and character.valid and not character.driving then
            character.disable_flashlight()
            if is_entity_in_darkness(character) then
                darkness_event(cave_miner, character)
            else
                cave_miner.darkness[tostring(character.unit_number)] = nil
            end
        end
    end
end

Public.mining_events = {
    {
        function(cave_miner, entity, player_index)
            if math.random(1, 8) == 1 then
                entity.surface.spill_item_stack(entity.position, {name = 'raw-fish', count = 1}, true)
            end
        end,
        350000,
        'Nothing'
    },
    {
        function(cave_miner, entity, player_index)
            local amount = Public.roll_biter_amount()
            local position = entity.position
            local surface = entity.surface
            local difficulty_modifier = Public.get_difficulty_modifier(position)
            local tick = game.tick
            for c = 1, amount, 1 do
                Esq.add_to_queue(tick + c * 25, surface, {name = BiterRaffle.roll('mixed', difficulty_modifier), position = position, force = 'enemy'}, 8)
            end
        end,
        16384,
        'Mixed_Biters'
    },
    {
        function(cave_miner, entity, player_index)
            local amount = Public.roll_biter_amount()
            local position = entity.position
            local surface = entity.surface
            local difficulty_modifier = Public.get_difficulty_modifier(position)
            local tick = game.tick
            for c = 1, amount, 1 do
                Esq.add_to_queue(tick + c * 25, surface, {name = BiterRaffle.roll('biter', difficulty_modifier), position = position, force = 'enemy'}, 8)
            end
        end,
        2048,
        'Biters'
    },
    {
        function(cave_miner, entity, player_index)
            local amount = Public.roll_biter_amount()
            local position = entity.position
            local surface = entity.surface
            local difficulty_modifier = Public.get_difficulty_modifier(position)
            local tick = game.tick
            for c = 1, amount, 1 do
                Esq.add_to_queue(tick + c * 25, surface, {name = BiterRaffle.roll('spitter', difficulty_modifier), position = position, force = 'enemy'}, 8)
            end
        end,
        2048,
        'Spitters'
    },
    {
        function(cave_miner, entity, player_index)
            local position = entity.position
            local surface = entity.surface
            Public.loot_crate(surface, position, 'wooden-chest', player_index)
        end,
        1024,
        'Treasure_Tier_1'
    },
    {
        function(cave_miner, entity, player_index)
            local position = entity.position
            local surface = entity.surface
            Public.loot_crate(surface, position, 'iron-chest', player_index)
        end,
        512,
        'Treasure_Tier_2'
    },
    {
        function(cave_miner, entity, player_index)
            local position = entity.position
            local surface = entity.surface
            Public.loot_crate(surface, position, 'steel-chest', player_index)
        end,
        256,
        'Treasure_Tier_3'
    },
    {
        function(cave_miner, entity, player_index)
            local position = entity.position
            local surface = entity.surface
            Public.loot_crate(surface, position, 'crash-site-spaceship-wreck-medium-' .. math_random(1, 3), player_index)
        end,
        128,
        'Treasure_Tier_4'
    },
    {
        function(cave_miner, entity, player_index)
            local position = entity.position
            local surface = entity.surface
            Public.loot_crate(surface, position, 'crash-site-spaceship-wreck-big-' .. math_random(1, 2), player_index)
        end,
        64,
        'Treasure_Tier_5'
    },
    {
        function(cave_miner, entity, player_index)
            local position = entity.position
            local surface = entity.surface
            Public.loot_crate(surface, position, 'big-ship-wreck-' .. math_random(1, 3), player_index)
        end,
        32,
        'Treasure_Tier_6'
    },
    {
        function(cave_miner, entity, player_index)
            local position = entity.position
            local surface = entity.surface
            Public.loot_crate(surface, position, 'crash-site-chest-' .. math_random(1, 2), player_index)
        end,
        16,
        'Treasure_Tier_7'
    },
    {
        function(cave_miner, entity, player_index)
            local position = entity.position
            local surface = entity.surface
            Public.loot_crate(surface, position, 'crash-site-spaceship', player_index)
            Public.unstuck_player(player_index)
        end,
        8,
        'Treasure_Tier_8'
    },
    {
        function(cave_miner, entity, player_index)
            local position = entity.position
            local surface = entity.surface
            local unit = Public.spawn_random_biter(surface, position, 2)
            Pets.biter_pets_tame_unit(game.players[player_index], unit, true)
        end,
        256,
        'Pet'
    },
    {
        function(cave_miner, entity, player_index)
            local position = entity.position
            local surface = entity.surface
            surface.create_entity({name = 'biter-spawner', position = position, force = 'enemy'})
            Public.unstuck_player(player_index)
        end,
        1024,
        'Nest'
    },
    {
        function(cave_miner, entity, player_index)
            local position = entity.position
            local surface = entity.surface
            Public.place_worm(surface, position, 1)
            Public.unstuck_player(player_index)
        end,
        1024,
        'Worm'
    },
    {
        function(cave_miner, entity, player_index)
            local position = entity.position
            if position.x ^ 2 + position.y ^ 2 < 8000 then
                return
            end
            local surface = entity.surface
            Public.place_worm(surface, position, 1)
            Public.unstuck_player(player_index)
            local difficulty_modifier = Public.get_difficulty_modifier(position)
            local tick = game.tick
            for c = 1, math_random(1, 7), 1 do
                Esq.add_to_queue(
                    tick + c * 90 + math_random(0, 90),
                    surface,
                    {
                        name = BiterRaffle.roll('worm', difficulty_modifier),
                        position = {position.x + (-4 + math_random(0, 8)), position.y + (-4 + math_random(0, 8))},
                        force = 'enemy'
                    },
                    16
                )
            end
        end,
        128,
        'Worms'
    },
    {
        function(cave_miner, entity, player_index)
            local position = entity.position
            local surface = entity.surface
            surface.create_entity({name = 'compilatron', position = position, force = 'player'})
        end,
        64,
        'Friendly Compilatron'
    },
    {
        function(cave_miner, entity, player_index)
            local position = entity.position
            local surface = entity.surface
            surface.create_entity({name = 'compilatron', position = position, force = 'enemy'})
        end,
        128,
        'Enemy Compilatron'
    },
    {
        function(cave_miner, entity, player_index)
            local position = entity.position
            local surface = entity.surface
            local entity = surface.create_entity({name = cave_miner.buildings_raffle[math_random(1, #cave_miner.buildings_raffle)], position = position, force = 'player'})
            entity.health = math_random(1, entity.prototype.max_health)
            local player = game.players[player_index]
            game.print(Public.get_colored_name(player_index) .. ' discovered an abandoned building', Constants.chat_color)
        end,
        128,
        'Abandoned Building'
    },
    {
        function(cave_miner, entity, player_index)
            local position = entity.position
            local surface = entity.surface
            surface.create_entity({name = 'car', position = position, force = 'player'})
            Public.unstuck_player(player_index)
            local player = game.players[player_index]
            game.print(player.name .. ' has finally found their car!!', Constants.chat_color)
        end,
        32,
        'Car'
    },
    {
        function(cave_miner, entity, player_index)
            local position = entity.position
            local surface = entity.surface
            local tick = game.tick

            local trees = {}
            for k, prototype in pairs(game.entity_prototypes) do
                if prototype.type == 'tree' then
                    table.insert(trees, k)
                end
            end
            table.shuffle_table(trees)
            local tree = game.entity_prototypes[trees[1]].name

            for c = 1, math_random(4, 96), 1 do
                Esq.add_to_queue(tick + c * 5, surface, {name = tree, position = position, force = 'neutral'}, 64)
            end
            local player = game.players[player_index]
            game.print(player.name .. ' found a whole forest!', Constants.chat_color)
        end,
        64,
        'Forest'
    }
}

Public.on_entity_died = {
    ['unit'] = function(cave_miner, entity)
        local position = entity.position
        local surface = entity.surface
        if math.random(1, 8) == 1 then
            surface.spill_item_stack(position, {name = 'raw-fish', count = 1}, true)
        end
    end,
    ['unit-spawner'] = function(cave_miner, entity)
        local position = entity.position
        local surface = entity.surface
        local a = 64 * 0.0001
        local b = math.sqrt(position.x ^ 2 + position.y ^ 2)
        local c = math_floor(a * b) + 1
        for _ = 1, c, 1 do
            Public.spawn_random_biter(surface, position, 1)
        end
    end,
    ['simple-entity'] = function(cave_miner, entity)
        local position = entity.position
        cave_miner.rocks_broken = cave_miner.rocks_broken + 1
        if math.random(1, 6) == 1 then
            Public.rock_spawns_biters(cave_miner, position)
        end
    end,
    ['container'] = function(cave_miner, entity)
        local position = entity.position
        Public.reveal(cave_miner, game.surfaces.nauvis, game.surfaces.cave_miner_source, position, 20)
    end
}

return Public
