local Price_raffle = require 'maps.expanse.price_raffle'
local Public = {}

local ores = {'copper-ore', 'iron-ore', 'stone', 'coal'}
local price_modifiers = {
    ['unit-spawner'] = -256,
    ['unit'] = -16,
    ['turret'] = -128,
    ['tree'] = -8,
    ['simple-entity'] = 2,
    ['cliff'] = -128,
    ['water'] = -5,
    ['water-green'] = -5,
    ['deepwater'] = -5,
    ['deepwater-green'] = -5,
    ['water-mud'] = -6,
    ['water-shallow'] = -6
}

local function reward_tokens(expanse, entity)
    local chance = expanse.token_chance % 1
    local count = math.floor(expanse.token_chance)

    if chance > 0 then
        chance = math.floor(chance * 1000)
        if math.random(1, 1000) <= chance then
            entity.surface.spill_item_stack(entity.position, {name = 'small-plane', count = 1}, true, nil, false)
        end
    end
    if count > 0 then
        for _ = 1, count, 1 do
            entity.surface.spill_item_stack(entity.position, {name = 'small-plane', count = 1}, true, nil, false)
        end
    end
end

local function get_cell_value(expanse, left_top)
    local square_size = expanse.square_size
    local value = square_size ^ 2
    value = value * 8

    local source_surface = game.surfaces[expanse.source_surface]
    local area = {{left_top.x, left_top.y}, {left_top.x + square_size, left_top.y + square_size}}
    local entities = source_surface.find_entities(area)
    local tiles = source_surface.find_tiles_filtered({area = area})

    for _, tile in pairs(tiles) do
        if price_modifiers[tile.name] then
            value = value + price_modifiers[tile.name]
        end
    end
    for _, entity in pairs(entities) do
        if price_modifiers[entity.type] then
            value = value + price_modifiers[entity.type]
        end
    end

    local distance = math.sqrt(left_top.x ^ 2 + left_top.y ^ 2)
    value = value * (distance * expanse.price_distance_modifier)
    local ore_modifier = distance * (expanse.price_distance_modifier / 20)
    if ore_modifier > expanse.max_ore_price_modifier then
        ore_modifier = expanse.max_ore_price_modifier
    end

    for _, entity in pairs(entities) do
        if entity.type == 'resource' then
            if entity.prototype.resource_category == 'basic-fluid' then
                value = value + (entity.amount * ore_modifier * 0.01)
            else
                value = value + (entity.amount * ore_modifier)
            end
        end
    end

    value = math.floor(value)
    if value < 16 then
        value = 16
    end

    return value
end

local function get_left_top(expanse, position)
    local vectors = {{-1, 0}, {1, 0}, {0, 1}, {0, -1}}
    table.shuffle_table(vectors)

    local surface = game.surfaces.expanse

    for _, v in pairs(vectors) do
        local tile = surface.get_tile({position.x + v[1], position.y + v[2]})
        if tile.name == 'out-of-map' then
            local left_top = tile.position
            left_top.x = left_top.x - left_top.x % expanse.square_size
            left_top.y = left_top.y - left_top.y % expanse.square_size
            if not expanse.grid[tostring(left_top.x .. '_' .. left_top.y)] then
                return left_top
            end
        end
    end

    return false
end

local function is_container_position_valid(expanse, position)
    if game.tick == 0 then
        return true
    end

    local left_top = get_left_top(expanse, position)
    if not left_top then
        return false
    end

    if
        game.surfaces.expanse.count_entities_filtered(
            {
                name = 'logistic-chest-requester',
                force = 'neutral',
                area = {{left_top.x - 1, left_top.y - 1}, {left_top.x + expanse.square_size + 1, left_top.y + expanse.square_size + 1}}
            }
        ) > 0
     then
        return false
    end

    return true
end

function Public.expand(expanse, left_top)
    expanse.grid[tostring(left_top.x .. '_' .. left_top.y)] = true

    local source_surface = game.surfaces[expanse.source_surface]
    if not source_surface then
        return
    end
    source_surface.request_to_generate_chunks(left_top, 3)
    source_surface.force_generate_chunk_requests()

    local square_size = expanse.square_size
    local area = {{left_top.x, left_top.y}, {left_top.x + square_size, left_top.y + square_size}}
    local surface = game.surfaces.expanse

    source_surface.clone_area(
        {
            source_area = area,
            destination_area = area,
            destination_surface = surface,
            clone_tiles = true,
            clone_entities = true,
            clone_decoratives = true,
            clear_destination_entities = false,
            clear_destination_decoratives = true,
            expand_map = true
        }
    )

    for _, e in pairs(source_surface.find_entities(area)) do
        e.destroy()
    end

    local positions = {
        {x = left_top.x + math.random(1, square_size - 2), y = left_top.y},
        {x = left_top.x, y = left_top.y + math.random(1, square_size - 2)},
        {x = left_top.x + math.random(1, square_size - 2), y = left_top.y + (square_size - 1)},
        {x = left_top.x + (square_size - 1), y = left_top.y + math.random(1, square_size - 2)}
    }

    for _, position in pairs(positions) do
        if is_container_position_valid(expanse, position) then
            local e = surface.create_entity({name = 'logistic-chest-requester', position = position, force = 'neutral'})
            e.destructible = false
            e.minable = false
        end
    end

    if game.tick == 0 then
        local a = math.floor(expanse.square_size * 0.5)
        for x = 1, 3, 1 do
            for y = 1, 3, 1 do
                surface.set_tiles({{name = 'water', position = {a + x, a + y - 2}}}, true)
            end
        end
        surface.create_entity({name = 'crude-oil', position = {a - 3, a}, amount = 1500000})
        surface.create_entity({name = 'rock-big', position = {a, a}})
        surface.create_entity({name = 'tree-0' .. math.random(1, 9), position = {a, a - 1}})
        surface.spill_item_stack({a, a + 2}, {name = 'small-plane', count = 1}, false, nil, false)
        surface.spill_item_stack({a + 0.5, a + 2.5}, {name = 'small-plane', count = 1}, false, nil, false)
        surface.spill_item_stack({a - 0.5, a + 2.5}, {name = 'small-plane', count = 1}, false, nil, false)

        for x = 0, square_size, 1 do
            for y = 0, square_size, 1 do
                if surface.can_place_entity({name = 'wooden-chest', position = {x, y}}) and surface.can_place_entity({name = 'coal', position = {x, y}, amount = 1}) then
                    surface.create_entity({name = ores[(x + y) % 4 + 1], position = {x, y}, amount = 1500})
                end
            end
        end
    end
end

local function init_container(expanse, entity)
    local left_top = get_left_top(expanse, entity.position)
    if not left_top then
        return
    end

    local cell_value = get_cell_value(expanse, left_top)

    local item_stacks = {}
    local roll_count = 2
    for _ = 1, roll_count, 1 do
        for _, stack in pairs(Price_raffle.roll(math.floor(cell_value / roll_count), 2)) do
            if not item_stacks[stack.name] then
                item_stacks[stack.name] = stack.count
            else
                item_stacks[stack.name] = item_stacks[stack.name] + stack.count
            end
        end
    end

    local price = {}
    for k, v in pairs(item_stacks) do
        table.insert(price, {name = k, count = v})
    end

    local containers = expanse.containers
    containers[entity.unit_number] = {entity = entity, left_top = left_top, price = price}
end

function Public.set_container(expanse, entity)
    if entity.name ~= 'logistic-chest-requester' then
        return
    end
    if not expanse.containers[entity.unit_number] then
        init_container(expanse, entity)
    end

    local container = expanse.containers[entity.unit_number]

    local inventory = container.entity.get_inventory(defines.inventory.chest)

    if not inventory.is_empty() then
        local contents = inventory.get_contents()
        if contents['small-plane'] then
            local count_removed = inventory.remove({name = 'small-plane', count = 1})
            if count_removed > 0 then
                init_container(expanse, entity)
                container = expanse.containers[entity.unit_number]
                game.print("The hungry chest has renewed it's offer! [gps=" .. math.floor(entity.position.x) .. ',' .. math.floor(entity.position.y) .. ',expanse]')
            end
        end
    end

    for key, item_stack in pairs(container.price) do
        local count_removed = inventory.remove(item_stack)
        container.price[key].count = container.price[key].count - count_removed
        if container.price[key].count <= 0 then
            table.remove(container.price, key)
        end
    end

    if #container.price == 0 then
        Public.expand(expanse, container.left_top)
        local a = math.floor(expanse.square_size * 0.5)
        local expansion_position = {x = expanse.containers[entity.unit_number].left_top.x + a, y = expanse.containers[entity.unit_number].left_top.y + a}
        expanse.containers[entity.unit_number] = nil
        if not inventory.is_empty() then
            for name, count in pairs(inventory.get_contents()) do
                entity.surface.spill_item_stack(entity.position, {name = name, count = count}, true, nil, false)
            end
        end
        reward_tokens(expanse, entity)
        entity.destructible = true
        entity.die()
        return expansion_position
    end

    for slot = 1, 30, 1 do
        entity.clear_request_slot(slot)
    end

    for slot, item_stack in pairs(container.price) do
        container.entity.set_request_slot(item_stack, slot)
    end
end

return Public
