--luacheck: ignore
local Public = {}

local Table = require 'modules.scrap_towny_ffa.table'

local town_radius = 27
local connection_radius = 7

local neutral_whitelist = {
    ['wooden-chest'] = true,
    ['iron-chest'] = true,
    ['steel-chest'] = true,
    ['raw-fish'] = true
}

local entity_type_whitelist = {
    ['accumulator'] = true,
    ['ammo-turret'] = true,
    ['arithmetic-combinator'] = true,
    ['artillery-turret'] = true,
    ['assembling-machine'] = true,
    ['boiler'] = true,
    ['constant-combinator'] = true,
    ['container'] = true,
    ['curved-rail'] = true,
    ['decider-combinator'] = true,
    ['electric-pole'] = true,
    ['electric-turret'] = true,
    ['fluid-turret'] = true,
    ['furnace'] = true,
    ['gate'] = true,
    ['generator'] = true,
    ['heat-interface'] = true,
    ['heat-pipe'] = true,
    ['infinity-container'] = true,
    ['infinity-pipe'] = true,
    ['inserter'] = true,
    ['lab'] = true,
    ['lamp'] = true,
    ['land-mine'] = true,
    ['loader'] = true,
    ['logistic-container'] = true,
    ['market'] = true,
    ['mining-drill'] = true,
    ['offshore-pump'] = true,
    ['pipe'] = true,
    ['pipe-to-ground'] = true,
    ['programmable-speaker'] = true,
    ['pump'] = true,
    ['radar'] = true,
    ['rail-chain-signal'] = true,
    ['rail-signal'] = true,
    ['reactor'] = true,
    ['roboport'] = true,
    ['rocket-silo'] = true,
    ['solar-panel'] = true,
    ['splitter'] = true,
    ['storage-tank'] = true,
    ['straight-rail'] = true,
    ['train-stop'] = true,
    ['transport-belt'] = true,
    ['underground-belt'] = true,
    ['wall'] = true
}

local function isolated(surface, force, position)
    local position_x = position.x
    local position_y = position.y
    local area = {{position_x - connection_radius, position_y - connection_radius}, {position_x + connection_radius, position_y + connection_radius}}
    local count = 0

    for _, e in pairs(surface.find_entities_filtered({area = area, force = force.name})) do
        if entity_type_whitelist[e.type] then
            count = count + 1
            if count > 1 then
                return false
            end -- are there more than one team entities in the area?
        end
    end

    return true
end

local function refund_item(event, item_name)
    if item_name == 'blueprint' then
        return
    end
    if event.player_index then
        game.players[event.player_index].insert({name = item_name, count = 1})
        return
    end

    if event.robot then
        local inventory = event.robot.get_inventory(defines.inventory.robot_cargo)
        inventory.insert({name = item_name, count = 1})
        return
    end
end

local function error_floaty(surface, position, msg)
    surface.create_entity(
        {
            name = 'flying-text',
            position = position,
            text = msg,
            color = {r = 0.77, g = 0.0, b = 0.0}
        }
    )
end

local function in_range(pos1, pos2, radius)
    if pos1 == nil then
        return false
    end
    if pos2 == nil then
        return false
    end
    if radius < 1 then
        return true
    end
    local dx = pos1.x - pos2.x
    local dy = pos1.y - pos2.y
    if dx ^ 2 + dy ^ 2 < radius ^ 2 then
        return true
    end
    return false
end

-- is the position near a town?
function Public.near_town(position, surface, radius)
    local ffatable = Table.get_table()
    for _, town_center in pairs(ffatable.town_centers) do
        if town_center ~= nil then
            local market = town_center.market
            if in_range(position, market.position, radius) and market.surface == surface then
                return true
            end
        end
    end
    return false
end

local function in_town(force, position)
    local ffatable = Table.get_table()
    local town_center = ffatable.town_centers[force.name]
    if town_center ~= nil then
        local center = town_center.market.position
        if position.x >= center.x - town_radius and position.x <= center.x + town_radius then
            if position.y >= center.y - town_radius and position.y <= center.y + town_radius then
                return true
            end
        end
    end
    return false
end

local function prevent_isolation_entity(event, player)
    local p = player or nil
    local entity = event.created_entity
    local position = entity.position
    if not entity.valid then
        return
    end
    local entity_name = entity.name
    local item = event.item
    if item == nil then
        return
    end
    local item_name = item.name
    local force = entity.force
    if force == game.forces.player then
        return
    end
    if force == game.forces['rogue'] then
        return
    end
    local surface = entity.surface
    local error = false
    if not in_town(force, position) and isolated(surface, force, position) then
        error = true
        entity.destroy()
        if entity_name ~= 'entity-ghost' and entity_name ~= 'tile-ghost' then
            refund_item(event, item_name)
        end
    --return true
    end
    if error == true then
        if p ~= nil then
            p.play_sound({path = 'utility/cannot_build', position = p.position, volume_modifier = 0.75})
        end
        error_floaty(surface, position, 'Building is not connected to town!')
    end
end

local function prevent_isolation_tile(event, player)
    local p = player or nil
    local tile = event.tile
    if not tile.valid then
        return
    end
    local tile_name = tile.name
    local surface = game.surfaces[event.surface_index]
    local tiles = event.tiles
    local force
    if event.player_index then
        force = game.players[event.player_index].force
    else
        force = event.robot.force
    end
    local error = false
    local position
    for _, t in pairs(tiles) do
        local old_tile = t.old_tile
        position = t.position
        if not in_town(force, position) and isolated(surface, force, position) then
            error = true
            surface.set_tiles({{name = old_tile.name, position = position}}, true)
            if tile_name ~= 'tile-ghost' then
                if tile_name == 'stone-path' then
                    tile_name = 'stone-brick'
                end
                refund_item(event, tile_name)
            end
        end
    end
    if error == true then
        if p ~= nil then
            p.play_sound({path = 'utility/cannot_build', position = p.position, volume_modifier = 0.75})
        end
        error_floaty(surface, position, 'Tile is not connected to town!')
    end
end

local function restrictions(event, player)
    local p = player or nil
    local entity = event.created_entity
    if not entity.valid then
        return
    end
    local entity_name = entity.name
    local surface = entity.surface
    local position = entity.position
    local error = false
    if entity.force == game.forces['player'] or entity.force == game.forces['rogue'] then
        if Public.near_town(position, surface, 32) then
            error = true
            entity.destroy()
            if entity_name ~= 'entity-ghost' then
                refund_item(event, event.stack.name)
            end
        else
            entity.force = game.forces['neutral']
        end
        return
    end
    if error == true then
        if p ~= nil then
            p.play_sound({path = 'utility/cannot_build', position = p.position, volume_modifier = 0.75})
        end
        error_floaty(surface, position, "Can't build near town!")
    end

    if not neutral_whitelist[entity.type] then
        return
    end
    entity.force = game.forces['neutral']
end

-- called when a player places an item, or a ghost
local function on_built_entity(event)
    local player = game.players[event.player_index]
    if prevent_isolation_entity(event, player) then
        return
    end
    restrictions(event, player)
end

local function on_robot_built_entity(event)
    if prevent_isolation_entity(event) then
        return
    end
    restrictions(event)
end

-- called when a player places landfill
local function on_player_built_tile(event)
    local player = game.players[event.player_index]
    prevent_isolation_tile(event, player)
end

local function on_robot_built_tile(event)
    prevent_isolation_tile(event)
end

local Event = require 'utils.event'
Event.add(defines.events.on_built_entity, on_built_entity)
Event.add(defines.events.on_player_built_tile, on_player_built_tile)
Event.add(defines.events.on_robot_built_entity, on_robot_built_entity)
Event.add(defines.events.on_robot_built_tile, on_robot_built_tile)

return Public
