local Public = {}

local Functions = require 'maps.biter_battles_v2.functions'
local bb_config = require 'maps.biter_battles_v2.config'
local table_remove = table.remove
local table_insert = table.insert

local direction_translation = {
    [0] = 4,
    [1] = 5,
    [2] = 6,
    [3] = 7,
    [4] = 0,
    [5] = 1,
    [6] = 2,
    [7] = 3
}

local cliff_orientation_translation = {
    ['east-to-none'] = 'west-to-none',
    ['east-to-north'] = 'west-to-south',
    ['east-to-south'] = 'west-to-north',
    ['east-to-west'] = 'west-to-east',
    ['north-to-east'] = 'south-to-west',
    ['north-to-none'] = 'south-to-none',
    ['north-to-south'] = 'south-to-north',
    ['north-to-west'] = 'south-to-east',
    ['south-to-east'] = 'north-to-west',
    ['south-to-none'] = 'north-to-none',
    ['south-to-north'] = 'north-to-south',
    ['south-to-west'] = 'north-to-east',
    ['west-to-east'] = 'east-to-west',
    ['west-to-none'] = 'east-to-none',
    ['west-to-north'] = 'east-to-south',
    ['west-to-south'] = 'east-to-north',
    ['none-to-east'] = 'none-to-west',
    ['none-to-north'] = 'none-to-south',
    ['none-to-south'] = 'none-to-north',
    ['none-to-west'] = 'none-to-east'
}

local entity_copy_functions = {
    ['tree'] = function(surface, entity, target_position)
        if not surface.can_place_entity({name = entity.name, position = target_position}) then
            return
        end
        entity.clone({position = target_position, surface = surface, force = 'neutral'})
    end,
    ['simple-entity'] = function(surface, entity, target_position)
        local mirror_entity = {name = entity.name, position = target_position, direction = direction_translation[entity.direction]}
        if not surface.can_place_entity(mirror_entity) then
            return
        end
        local mirror_entity2 = surface.create_entity(mirror_entity)
        mirror_entity2.graphics_variation = entity.graphics_variation
    end,
    ['cliff'] = function(surface, entity, target_position)
        local mirror_entity = {name = entity.name, position = target_position, cliff_orientation = cliff_orientation_translation[entity.cliff_orientation]}
        if not surface.can_place_entity(mirror_entity) then
            return
        end
        surface.create_entity(mirror_entity)
        return
    end,
    ['resource'] = function(surface, entity, target_position)
        surface.create_entity({name = entity.name, position = target_position, amount = entity.amount})
    end,
    ['corpse'] = function(surface, entity, target_position)
        surface.create_entity({name = entity.name, position = target_position})
    end,
    ['unit-spawner'] = function(surface, entity, target_position, force_name)
        local mirror_entity = {name = entity.name, position = target_position, direction = direction_translation[entity.direction], force = force_name .. '_biters'}
        if not surface.can_place_entity(mirror_entity) then
            return
        end
        table_insert(global.unit_spawners[force_name .. '_biters'], surface.create_entity(mirror_entity))
    end,
    ['turret'] = function(surface, entity, target_position, force_name)
        local mirror_entity = {name = entity.name, position = target_position, direction = direction_translation[entity.direction], force = force_name .. '_biters'}
        if not surface.can_place_entity(mirror_entity) then
            return
        end
        surface.create_entity(mirror_entity)
    end,
    ['rocket-silo'] = function(surface, entity, target_position, force_name)
        if surface.count_entities_filtered({name = 'rocket-silo', area = {{target_position.x - 8, target_position.y - 8}, {target_position.x + 8, target_position.y + 8}}}) > 0 then
            return
        end
        global.rocket_silo[force_name] =
            surface.create_entity({name = entity.name, position = target_position, direction = direction_translation[entity.direction], force = force_name})
        global.rocket_silo[force_name].minable = false
        Functions.add_target_entity(global.rocket_silo[force_name])
    end,
    ['ammo-turret'] = function(surface, entity, target_position, force_name)
        local direction = 0
        if force_name == 'south' then
            direction = 4
        end
        local mirror_entity = {name = entity.name, position = target_position, force = force_name, direction = direction}
        if not surface.can_place_entity(mirror_entity) then
            return
        end
        local e = surface.create_entity(mirror_entity)
        Functions.add_target_entity(e)
        local inventory = entity.get_inventory(defines.inventory.turret_ammo)
        if inventory.is_empty() then
            return
        end
        for name, count in pairs(inventory.get_contents()) do
            e.insert({name = name, count = count})
        end
    end,
    ['wall'] = function(surface, entity, target_position, force_name)
        local e = entity.clone({position = target_position, surface = surface, force = force_name})
        e.active = true
    end,
    ['container'] = function(surface, entity, target_position, force_name)
        local e = entity.clone({position = target_position, surface = surface, force = force_name})
        e.active = true
    end,
    ['fish'] = function(surface, entity, target_position)
        local mirror_entity = {name = entity.name, position = target_position}
        if not surface.can_place_entity(mirror_entity) then
            return
        end
        surface.create_entity(mirror_entity)
    end
}

local function process_entity(surface, entity, force_name)
    if not entity.valid then
        return
    end
    if not entity_copy_functions[entity.type] then
        return
    end

    local match_mirror = bb_config.match_mirror

    local target_position
    if force_name == 'north' then
        target_position = entity.position
    else
        if match_mirror then
            target_position = {x = entity.position.x, y = entity.position.y * -1}
        else
            target_position = {x = entity.position.x * -1, y = entity.position.y * -1}
        end
    end

    entity_copy_functions[entity.type](surface, entity, target_position, force_name)
end

local function copy_chunk(chunk)
    local target_surface = game.surfaces.biter_battles

    local source_surface = game.surfaces.bb_source
    local source_chunk_position = {chunk[1][1], chunk[1][2]}
    local source_left_top = {x = source_chunk_position[1] * 32, y = source_chunk_position[2] * 32}
    local source_area = {{source_left_top.x, source_left_top.y}, {source_left_top.x + 32, source_left_top.y + 32}}

    local target_chunk_position = chunk[1]
    local target_left_top = {x = target_chunk_position[1] * 32, y = target_chunk_position[2] * 32}
    local target_area = {{target_left_top.x, target_left_top.y}, {target_left_top.x + 32, target_left_top.y + 32}}

    if not source_surface.is_chunk_generated(source_chunk_position) then
        source_surface.request_to_generate_chunks({x = source_left_top.x + 16, y = source_left_top.y + 16}, 0)
        return
    end

    local match_mirror = bb_config.match_mirror

    if chunk[2] == 1 then
        source_surface.clone_area(
            {
                source_area = source_area,
                destination_area = target_area,
                destination_surface = target_surface,
                --destination_force = …,
                clone_tiles = true,
                clone_entities = false,
                clone_decoratives = false,
                clear_destination_entities = false,
                clear_destination_decoratives = false,
                expand_map = false
            }
        )
        chunk[2] = chunk[2] + 1
        return
    end

    if chunk[2] == 2 then
        for _, entity in pairs(source_surface.find_entities_filtered({area = source_area})) do
            process_entity(target_surface, entity, 'north')
        end
        chunk[2] = chunk[2] + 1
        return
    end

    local decoratives = {}
    if match_mirror then
        for k, decorative in pairs(source_surface.find_decoratives_filtered {area = source_area}) do
            decoratives[k] = {name = decorative.decorative.name, position = {decorative.position.x, decorative.position.y}, amount = decorative.amount}
        end
    else
        for k, decorative in pairs(source_surface.find_decoratives_filtered {area = source_area}) do
            decoratives[k] = {name = decorative.decorative.name, position = {(decorative.position.x * -1) - 1, (decorative.position.y * -1) - 1}, amount = decorative.amount}
        end
    end
    target_surface.create_decoratives({check_collision = false, decoratives = decoratives})

    return true
end

local function mirror_chunk(chunk)
    local target_surface = game.surfaces.biter_battles
    local match_mirror = bb_config.match_mirror
    local source_surface = game.surfaces.bb_source
    local source_chunk_position
    if match_mirror then
        source_chunk_position = {chunk[1][1], chunk[1][2] * -1 - 1}
    else
        source_chunk_position = {chunk[1][1] * -1 - 1, chunk[1][2] * -1 - 1}
    end

    local source_left_top = {x = source_chunk_position[1] * 32, y = source_chunk_position[2] * 32}
    local source_area = {{source_left_top.x, source_left_top.y}, {source_left_top.x + 32, source_left_top.y + 32}}

    if not source_surface.is_chunk_generated(source_chunk_position) then
        source_surface.request_to_generate_chunks({x = source_left_top.x + 16, y = source_left_top.y + 16}, 0)
        return
    end

    if chunk[2] == 1 then
        local tiles = {}
        if match_mirror then
            for k, tile in pairs(source_surface.find_tiles_filtered({area = source_area})) do
                tiles[k] = {name = tile.name, position = {tile.position.x, tile.position.y * -1 - 1}}
            end
        else
            for k, tile in pairs(source_surface.find_tiles_filtered({area = source_area})) do
                tiles[k] = {name = tile.name, position = {tile.position.x * -1 - 1, tile.position.y * -1 - 1}}
            end
        end
        target_surface.set_tiles(tiles, true)
        chunk[2] = chunk[2] + 1
        return
    end

    if chunk[2] == 2 then
        for _, entity in pairs(source_surface.find_entities_filtered({area = source_area})) do
            process_entity(target_surface, entity, 'south')
        end
        chunk[2] = chunk[2] + 1
        return
    end

    local decoratives = {}
    for k, decorative in pairs(source_surface.find_decoratives_filtered {area = source_area}) do
        decoratives[k] = {name = decorative.decorative.name, position = {decorative.position.x, decorative.position.y * -1}, amount = decorative.amount}
    end
    target_surface.create_decoratives({check_collision = false, decoratives = decoratives})

    return true
end

local function reveal_chunk(chunk)
    local surface = game.surfaces.biter_battles
    local chunk_position = chunk[1]
    for _, force_name in pairs({'north', 'south'}) do
        local force = game.forces[force_name]
        if force.is_chunk_charted(surface, chunk_position) then
            force.chart(surface, {{chunk_position[1] * 32, chunk_position[2] * 32}, {chunk_position[1] * 32 + 31, chunk_position[2] * 32 + 31}})
        end
    end
end

function Public.add_chunk(event)
    local surface = event.surface
    if surface.name ~= 'biter_battles' then
        return
    end
    local left_top = event.area.left_top
    local terrain_gen = global.terrain_gen

    if left_top.y < 0 then
        terrain_gen.size_of_chunk_copy = terrain_gen.size_of_chunk_copy + 1
        terrain_gen.chunk_copy[terrain_gen.size_of_chunk_copy] = {{left_top.x / 32, left_top.y / 32}, 1}
    else
        terrain_gen.size_of_chunk_mirror = terrain_gen.size_of_chunk_mirror + 1
        terrain_gen.chunk_mirror[terrain_gen.size_of_chunk_mirror] = {{left_top.x / 32, left_top.y / 32}, 1}
    end
end

local function clear_source_surface(terrain_gen)
    if terrain_gen.counter % 1024 == 1023 then
        terrain_gen.counter = terrain_gen.counter + 1
        local surface = game.surfaces.bb_source
        local c = 0
        for chunk in surface.get_chunks() do
            surface.delete_chunk({chunk.x, chunk.y})
            c = c + 1
        end
        print('Deleted ' .. c .. ' source surface chunks.')
    end
end

local function north_work()
    local terrain_gen = global.terrain_gen
    --luacheck: ignore 512
    for k, chunk in pairs(terrain_gen.chunk_copy) do
        if copy_chunk(chunk) then
            reveal_chunk(chunk)
            table_remove(terrain_gen.chunk_copy, k)
            terrain_gen.size_of_chunk_copy = terrain_gen.size_of_chunk_copy - 1
            terrain_gen.counter = terrain_gen.counter + 1
        end
        break
    end
    clear_source_surface(terrain_gen)
end

local function south_work()
    local terrain_gen = global.terrain_gen
    --luacheck: ignore 512
    for k, chunk in pairs(terrain_gen.chunk_mirror) do
        if mirror_chunk(chunk) then
            reveal_chunk(chunk)
            table_remove(terrain_gen.chunk_mirror, k)
            terrain_gen.size_of_chunk_mirror = terrain_gen.size_of_chunk_mirror - 1
            terrain_gen.counter = terrain_gen.counter + 1
        end
        break
    end
    clear_source_surface(terrain_gen)
end

local works = {
    [1] = north_work,
    [2] = south_work
}

function Public.ticking_work()
    local tick = game.ticks_played
    if tick < 4 then
        return
    end
    local work = works[tick % 2 + 1]
    if global.server_restart_timer then
        return
    end
    work()
end

return Public
