require("util")
local compound_entities = require("prototypes.compound_entities.main_list")

return function(mod_name)
    local common = {}

    ------------------------------------------------------------------------------------
    -- Get mod name and path to mod
    common.modName = script and script.mod_name or mod_name
    common.modRoot = "__" .. common.modName .. "__"


    ------------------------------------------------------------------------------------
    -- Greatly improved version check for mods (thanks to eradicator!)
    common.Version = {}
    do
        local V = common.Version

        local function parse_version(vstr) -- string "Major.Minor.Patch"
            local err = function()
                error('Invalid Version String: <' .. tostring(vstr) .. '>')
            end
            local r = { vstr:match('^(%d+)%.(%d+)%.(%d+)$') }

            if #r ~= 3 then
                err()
            end

            for i = 1, 3 do
                r[i] = tonumber(r[i])
            end

            return r
        end

        V.gtr = function(verA, verB)
            local a, b, c = unpack(parse_version(verA))
            local x, y, z = unpack(parse_version(verB))
            return (a > x) or (a == x and b > y) or (a == x and b == y and c > z)
        end
        local map = {
            ['='] = function(A, B) return not (V.gtr(A, B) or V.gtr(B, A)) end,
            ['>'] = V.gtr,
            ['!='] = function(A, B) return (V.gtr(A, B) or V.gtr(B, A)) end,
            ['<='] = function(A, B) return V.gtr(B, A) or (not V.gtr(A, B)) end,
            ['>='] = function(A, B) return V.gtr(A, B) or (not V.gtr(B, A)) end,
            ['~='] = function(A, B) return (V.gtr(A, B) or V.gtr(B, A)) end,
            ['<'] = function(A, B) return V.gtr(B, A) end,
        }

        common.check_version = function(mod_name, operator, need_version)
            local mod_version = (mods and mods[mod_name]) or (script and script.active_mods[mod_name])
            return map[operator](mod_version, need_version)
        end
    end

    ------------------------------------------------------------------------------------
    -- Same values for collision masks
    -- Default: {"item-layer", "object-layer", "rail-layer", "floor-layer", "water-tile"}
    common.RAIL_BRIDGE_MASK = { "object-layer", "consider-tile-transitions" }

    -- "Transport Drones" removes "object-layer" from rails, so if bridges have only
    -- {"object-layer"}, there collision mask will be empty, and they can be built even
    -- over cliffs. So we need to add another layer to bridges ("floor-layer").
    -- As of Factorio 1.1.0, rails need to have "rail-layer" in their mask. This will work
    -- alright, but isn't available in earlier versions of Factorio, so we will use
    -- "floor-layer" there instead.
    local need = common.check_version("base", ">=", "1.1.0") and "rail-layer" or "floor-layer"
    table.insert(common.RAIL_BRIDGE_MASK, need)

    -- Rails use basically the same mask as rail bridges, ...
    common.RAIL_MASK = util.table.deepcopy(common.RAIL_BRIDGE_MASK)
    -- ... we just need to add some layers so our rails have the same mask as vanilla rails.
    table.insert(common.RAIL_MASK, "item-layer")
    table.insert(common.RAIL_MASK, "water-tile")



    ------------------------------------------------------------------------------------
    -- Set maximum_wire_distance of Power-to-rail connectors
    common.POWER_TO_RAIL_WIRE_DISTANCE = 4



    ------------------------------------------------------------------------------------
    -- List of compound entities
    -- Key:       name of the base entity
    -- tab:       name of the global table where data of these entity are stored
    -- hidden:    table containing the hidden entities needed by this entity
    --            (Key:   name under which the hidden entity will be stored in the table;
    --             Value: name of the entity that should be placed)
    common.compound_entities = compound_entities.get_HE_list()

    -- Map the short handles of hidden entities (e.g. "pole") to real prototype types
    -- (e.g. "electric-pole")
    common.HE_map = compound_entities.HE_map
    -- Reverse lookup
    common.HE_map_reverse = compound_entities.HE_map_reverse

    ------------------------------------------------------------------------------------
    -- There may be trees for which we don't want to create variations. These patterns
    -- are used to build a list of trees we want to ignore.
    common.ignore_name_patterns = {
        -- Ignore our own trees
        "bio%-tree%-.+%-%d",
        -- Tree prototypes created by "Robot Tree Farm" or "Tral's Robot Tree Farm"
        "rtf%-.+%-%d+",
        -- Tree prototypes created by "Industrial Revolution 2"
        ".*%-*ir2%-.+",
    }


    -- Get list of tree prototypes that we want to ignore
    common.get_tree_ignore_list = function()
        local ignore = {}
        local trees = game and
            prototypes.get_entity_filtered({ { filter = "type", type = "tree" } }) or
            data.raw.tree
        for tree_name, tree in pairs(trees) do
            for p, pattern in ipairs(common.ignore_name_patterns) do
                if tree_name:match(pattern) then
                    ignore[tree_name] = true
                    break
                end
            end
        end
        return ignore
    end


    -- 0.17.42/0.18.09 fixed a bug where musk floor was created for the force "enemy".
    -- Because it didn't belong to any player, in map view the electric grid overlay wasn't
    -- shown for musk floor. Somebody complained about seeing it now, so starting with version
    -- 0.17.45/0.18.13, there is a setting to hide the overlay again. If it is set to "true",
    -- a new force will be created that the hidden electric poles of musk floor belong to.
    -- (UPDATE: 0.18.29 reversed the setting -- if active, tiles will now be visible in map
    -- view, not hidden. The definition of UseMuskForce has been changed accordingly.)
    common.MuskForceName = "BI-Musk_floor_general_owner"
    common.UseMuskForce = not settings.startup["BI_Show_musk_floor_in_mapview"].value


    ------------------------------------------------------------------------------------
    -- Enable writing to log file until startup options are set, so debugging output
    -- from the start of a game session can be logged. This depends on a locally
    -- installed dummy mod to allow debugging output during development without
    -- spamming real users.
    -- If the "_debug" dummy mod is active, debugging will always be on. If you don't
    -- have this dummy mod but want to turn on logging anyway, set the default value
    -- to "true"!
    local default = false

    common.is_debug = ((mods and mods["_debug"]) or
            (script and script.active_mods["_debug"])) and
        true or default


    ------------------------------------------------------------------------------------
    --                               DEBUGGING FUNCTIONS                              --
    ------------------------------------------------------------------------------------


    ------------------------------------------------------------------------------------
    -- Output debugging text
    common.writeDebug = function(msg, tab, print_line)
        local args = {}
        -- Use serpent.line instead of serpent.block if this is true!
        local line = print_line and
            (string.lower(print_line) == "line" or string.lower(print_line) == "l") and
            true or false

        if common.is_debug then
            if type(tab) ~= "table" then
                tab = { tab }
            end
            local v
            for k = 1, #tab do
                v = tab[k]
                -- NIL
                if v == nil then
                    args[#args + 1] = "NIL"
                    -- TABLE
                elseif type(v) == "table" then
                    args[#args + 1] = line and serpent.line(table.deepcopy(v)) or
                        serpent.block(table.deepcopy(v))
                    -- OTHER VALUE
                else
                    args[#args + 1] = v
                end
            end
            if #args == 0 then
                args[1] = "nil"
            end
            args.n = #args

            -- Print the message text to log and game
            log(string.format(tostring(msg), table.unpack(args)))

            if game then
                game.print(string.format(tostring(msg), table.unpack(args)))
            end
        end
    end

    ------------------------------------------------------------------------------------
    -- Simple helper to show a single value with descriptive text
    common.show = function(desc, term)
        if common.is_debug then
            common.writeDebug(tostring(desc) .. ": %s", type(term) == "table" and { term } or term)
        end
    end

    ------------------------------------------------------------------------------------
    -- Print "entityname (id)"
    common.print_name_id = function(entity)
        local id
        local name = "unknown entity"

        if entity and entity.valid then
            -- Stickers don't have an index or unit_number!
            id = (entity.type == "sticker" and entity.type) or
                entity.unit_number or entity.type

            name = entity.name
        end

        return string.format("%s (%s)", name, id or "nil")
    end

    ------------------------------------------------------------------------------------
    -- Print "entityname"
    common.print_name = function(entity)
        return entity and entity.valid and entity.name or ""
    end


    ------------------------------------------------------------------------------------
    -- Throw an error if a wrong argument has been passed to a function
    common.arg_err = function(arg, arg_type)
        error(string.format(
            "Wrong argument! %s is not %s!",
            (arg or "nil"), (arg_type and "a valid " .. arg_type or "valid")
        )
        )
    end

    ------------------------------------------------------------------------------------
    -- Rudimentary check of the arguments passed to a function
    common.check_args = function(arg, arg_type, desc)
        if not (arg and type(arg) == arg_type) then
            game.print(type(arg) .. " = " .. arg_type)
            common.arg_err(arg or "nil", desc or arg_type or "nil")
        end
    end



    ------------------------------------------------------------------------------------
    --                                  MOD SPECIFIC                                  --
    ------------------------------------------------------------------------------------

    ------------------------------------------------------------------------------------
    -- Are tiles from Alien Biomes available? (Returns true or false)
    common.AB_tiles = function()
        local ret = false

        if game then
            local AB = prototypes.item["fertilizer"].place_as_tile_result.result.name
            -- In data stage, place_as_tile is only changed to Alien Biomes tiles if
            -- both "vegetation-green-grass-1" and "vegetation-green-grass-3" exist. Therefore,
            -- we only need to check for one tile in the control stage.
            ret = (AB == "vegetation-green-grass-3") and true or false
        else
            ret = data.raw.tile["vegetation-green-grass-1"] and
                data.raw.tile["vegetation-green-grass-3"] and true or false
        end

        return ret
    end

    ------------------------------------------------------------------------------------
    -- Function for removing individual entities
    common.remove_entity = function(entity)
        if entity and entity.valid then
            entity.destroy { raise_destroy = true }
        end
    end

    ------------------------------------------------------------------------------------
    -- Function for removing invalid prototypes from list of compound entities
    common.rebuild_compound_entity_list = function()
        local f_name = "rebuild_compound_entity_list"
        common.writeDebug("Entered function %s()", { f_name })

        local ret = {}

        for c_name, c_data in pairs(common.compound_entities) do
            common.show("base_name", c_name)
            common.show("data", c_data)
            -- Is the base entity in the game?
            if c_data.base and c_data.base.name and prototypes.entity[c_data.base.name] then
                -- Make a copy of the compound-entity data
                common.writeDebug("%s exists -- copying data", { c_name })
                ret[c_name] = util.table.deepcopy(c_data)

                -- Check hidden entities
                for h_key, h_data in pairs(ret[c_name].hidden) do
                    common.writeDebug("h_key: %s\th_data: %s", { h_key, h_data })
                    -- Remove hidden entity if it doesn't exist
                    if not prototypes.entity[h_data.name] then
                        common.writeDebug("Removing %s (%s) from list of hidden entities!", { h_data.name, h_key })
                        ret[c_name].hidden[h_key] = nil
                    end
                end

                -- Clean table
            else
                local tab = c_data.tab
                if tab then
                    -- Remove main table from global
                    common.writeDebug("Removing %s (%s obsolete entries)", { tab, #tab })
                    storage[tab] = nil
                end

                -- If this compound entity requires additional tables in global, initialize
                -- them now!
                local related_tables = c_data.add_global_tables
                if related_tables then
                    for t, tab in ipairs(related_tables or {}) do
                        common.writeDebug("Removing storage[%s] (%s values)", { tab, table_size(storage[tab]) })
                        storage[tab] = nil
                    end
                end

                -- If this compound entity requires additional values in global, remove them!
                local related_vars = c_data.add_global_values
                if related_vars then
                    for var_name, value in pairs(related_vars or {}) do
                        common.writeDebug("Removing storage[%s] (was: %s)",
                            { var_name, var_name and storage[var_name] or "nil" })
                        storage[var_name] = nil
                    end
                end
            end
        end
        common.show("ret", ret)
        return ret
    end
    ------------------------------------------------------------------------------------
    -- Function to add all optional values for a compound entity to the table entry.
    common.add_optional_data = function(base)
        local f_name = "add_optional_data"
        common.writeDebug("Entered function %s(%s)", { f_name, common.print_name_id(base) })
        if not (base and base.valid and storage.compound_entities[base.name]) then
            common.arg_err(base, "base of a compound entity")
        end

        -- Add optional values to global table
        local data = storage.compound_entities[base.name]
        common.show("data", data)
        local tab = data.tab
        common.show("tab", tab)
        common.show("storage[tab]", storage[tab] or "nil")

        local entry = storage[tab][base.unit_number]

        for k, v in pairs(data.optional or {}) do
            if entry[k] then
                common.writeDebug("%s already exists: %s", { k, entry[k] })
            else
                entry[k] = v
                common.writeDebug("Added data to %s: %s = %s", { common.print_name_id(base), k, v })
            end
        end
    end


    ------------------------------------------------------------------------------------
    -- Function for removing all parts of invalid compound entities
    common.clean_global_compounds_table = function(entity_name)
        local f_name = "clean_table"
        common.writeDebug("Entered function %s(%s)", { f_name, entity_name or "nil" })
        common.writeDebug("Entries in common.compound_entities[%s]: %s",
            { entity_name, table_size(storage.compound_entities[entity_name]) })

        local entity_table = storage.compound_entities[entity_name]
        common.show("entity_table", entity_table and entity_table.tab)
        entity_table = entity_table and entity_table.tab and storage[entity_table.tab]
        common.writeDebug("entity_table: %s", { entity_table }, "line")
        local hidden_entities = storage.compound_entities[entity_name].hidden
        common.show("hidden_entities", hidden_entities)
        local removed = 0
        -- Scan the whole table
        for c, compound in pairs(entity_table) do
            common.writeDebug("c: %s\tcompound: %s", { c, compound })
            -- No or invalid base entity!
            if not (compound.base and compound.base.valid) then
                common.writeDebug("%s (%s) has no valid base entity -- removing entry!", { entity_name, c })

                for h_name, h_entity in pairs(hidden_entities) do
                    common.writeDebug("Removing %s (%s)", { h_name, h_entity.name })
                    common.remove_entity(compound[h_name])
                end
                entity_table[c] = nil
                removed = removed + 1
                common.writeDebug("Removed %s %s", { entity_name, c })
            end
        end
        common.show("Removed entities", removed)
        common.show("Pruned list size", table_size(entity_table))
        return removed
    end


    ------------------------------------------------------------------------------------
    -- Function to resore missing parts of compound entities
    common.restore_missing_entities = function(entity_name)
        local f_name = "restore_missing_entities"
        common.writeDebug("Entered function %s(%s)", { f_name, entity_name or "nil" })
        common.writeDebug("storage.compound_entities[%s]: %s entries",
            { entity_name, table_size(storage.compound_entities[entity_name]) })

        local check = storage.compound_entities[entity_name]
        local entity_table = check and storage[check.tab] or {}
        local hidden_entities = check and check.hidden or {}

        local checked = 0
        local restored = 0
        -- Scan the whole table
        for c, compound in pairs(entity_table) do
            -- Base entity is valid!
            if (compound.base and compound.base.valid) then
                common.writeDebug("%s is valid -- checking hidden entities!", { common.print_name_id(compound.base) })
                for h_name, h_entity in pairs(hidden_entities) do
                    -- Hidden entity is missing
                    if compound[h_name] and compound[h_name].valid then
                        common.writeDebug("%s: OK", { h_name })
                    else
                        common.writeDebug("%s: MISSING!", { h_name })
                        common.create_entities(entity_table, compound.base, { [h_name] = h_entity.name })
                        restored = restored + 1
                        common.writeDebug("Created %s (%s) for %s",
                            { h_name, h_entity.name, common.print_name_id(compound.base) })
                    end
                end
                checked = checked + 1
            end
        end
        common.writeDebug("Checked %s compound entities", { checked })
        common.writeDebug("Restored %s entities", { restored })
        return { checked = checked, restored = restored }
    end


    ------------------------------------------------------------------------------------
    -- Function to find all unregistered compound entities of a particular type
    common.register_in_compound_entity_tab = function(compound_name)
        local f_name = "register_in_compound_entity_tab"
        common.writeDebug("Entered function %s(%s)", { f_name, compound_name })

        local cnt = 0
        local h_cnt = 0
        local data = storage.compound_entities[compound_name]
        if not data then
            common.arg_err(compound_name, "name of a compound entity")
        end

        local g_tab = storage[data.tab]
        local found, h_found, created

        -- Scan all surfaces
        for s, surface in pairs(game.surfaces) do
            -- Check the bases of all compound entities on the surface
            found = surface.find_entities_filtered({ name = compound_name })
            for b, base in ipairs(found) do
                -- Base entity isn't registered yet!
                if not g_tab[base.unit_number] then
                    common.writeDebug("Found unregistered entity: %s!", { common.print_name_id(base) })
                    -- Create an entry in the global table
                    g_tab[base.unit_number] = { base = base }
                    -- Add optional data to the table, if there are any
                    common.add_optional_data(base)


                    -- Check if it already has any hidden entities
                    for h_name, h_data in pairs(data.hidden) do
                        h_found = surface.find_entities_filtered({
                            name = h_data.name,
                            type = h_data.type,
                            position = common.offset_position(base.position, h_data.base_offset),
                        })

                        -- Check for multiple hidden entities of the same type in the same position!
                        if #h_found > 1 then
                            local cnt = 0
                            for duplicate = 2, #h_found do
                                h_found[duplicate].destroy({ raise_destroy = true })
                                cnt = cnt + 1
                            end
                            common.writeDebug("Removed %s duplicate entities (%s)!", { cnt, h_data.name })
                        end

                        -- There still is one hidden entity left. Add it to the table!
                        if next(h_found) then
                            common.writeDebug("Found %s -- adding it to the table.", { common.print_name_id(base) })
                            g_tab[base.unit_number][h_name] = h_found[1]

                            -- Create hidden entity! This will automatically add it to the table.
                        else
                            created = common.create_entities(g_tab, base, { [h_name] = h_data })
                            common.writeDebug("Created hidden %s: %s",
                                { h_name, created and common.print_name_id(created) or "nil" })
                            h_cnt = h_cnt + 1
                        end
                    end
                    cnt = cnt + 1
                end
            end
        end
        common.writeDebug("Registered %s compound entities and created %s hidden entities", { cnt, h_cnt })
        return cnt
    end

    ------------------------------------------------------------------------------------
    -- Function to find all unregistered compound entities
    common.find_unregistered_entities = function()
        local f_name = "find_unregistered_entities"
        common.writeDebug("Entered function %s()", { f_name })

        local cnt = 0
        for compound_entity, c in pairs(storage.compound_entities) do
            cnt = cnt + common.register_in_compound_entity_tab(compound_entity)
        end
        common.writeDebug("Registered %s compound entities.", { cnt })
        return cnt
    end

    ------------------------------------------------------------------------------------
    -- Function to normalize positions
    common.normalize_position = function(pos)
        if pos and type(pos) == "table" and table_size(pos) == 2 then
            local x = pos.x or pos[1]
            local y = pos.y or pos[2]
            if x and y and type(x) == "number" and type(y) == "number" then
                return { x = x, y = y }
            end
        end
    end


    ------------------------------------------------------------------------------------
    -- Calculate the offset position of a hidden entity
    common.offset_position = function(base_pos, offset)
        common.check_args(base_pos, "table", "position")
        offset = offset or { x = 0, y = 0 }
        common.check_args(offset, "table", "position")

        base_pos = common.normalize_position(base_pos)
        offset = common.normalize_position(offset)

        common.show("base_pos", base_pos)
        common.show("offset", offset)
        common.show("new", { x = base_pos.x + offset.x, y = base_pos.y + offset.y })
        return { x = base_pos.x + offset.x, y = base_pos.y + offset.y }
    end

    ------------------------------------------------------------------------------------
    -- Check if argument is a valid surface
    common.is_surface = function(surface)
        local t = type(surface)
        local result = false
        if (t == "number" or t == "string") and game.surfaces[surface] and game.surfaces[surface].valid then
            result = game.surfaces[surface]
        elseif surface and surface.object_name == "LuaSurface" and surface.valid then
            result = surface
        end
        return result
    end


    ------------------------------------------------------------------------------------
    -- Make hidden entities unminable and indestructible
    local function make_unminable(entities)
        for e, entity in ipairs(entities or {}) do
            if entity.valid then
                entity.minable = false
                entity.destructible = false
            end
        end
    end

    --------------------------------------------------------------------
    -- Create and register hidden entities
    common.create_entities = function(g_table, base_entity, hidden_entities)
        local f_name = "create_entities"
        common.writeDebug("Entered function %s(%s, %s, %s)",
            { f_name, "g_table", base_entity, hidden_entities })
        common.show("#g_table", g_table and table_size(g_table))

        common.check_args(g_table, "table")
        common.check_args(base_entity, "userdata")

        if not base_entity.valid then
            common.arg_err(base_entity, "base entity")
            -- A table is required, but it may be empty! (This is needed for the
            -- bio gardens, which only have a hidden pole if the "Easy Gardens"
            -- setting is enabled.)
        elseif not (hidden_entities and type(hidden_entities) == "table") then
            common.arg_err(hidden_entities, "array of hidden-entity names")
        end
        local base_pos = common.normalize_position(base_entity.position) or
            common.arg_err(position or "nil", "position")

        local entity, offset, pos

        -- Initialize entry in global table
        g_table[base_entity.unit_number] = g_table[base_entity.unit_number] or {}
        g_table[base_entity.unit_number].base = base_entity

        -- Create hidden entities
        local data
        for key, tab in pairs(hidden_entities) do
            common.writeDebug("key: %s\tname: %s", { key, tab })
            data = storage.compound_entities[base_entity.name].hidden[key]
            common.show("data", data)
            entity = base_entity.surface.create_entity({
                name = data.name,
                type = data.type,
                position = common.offset_position(base_pos, data.base_offset),
                force = base_entity.force,
            })
            -- Raise the event manually, so we can pass on extra data!
            script.raise_event(defines.events.script_raised_built, {
                entity = entity,
                base_entity = base_entity
            })

            -- Make hidden entity unminable/undestructible
            make_unminable({ entity })

            -- Add hidden entity to global table
            g_table[base_entity.unit_number][key] = entity
        end

        -- Add optional values to global table
        common.add_optional_data(base_entity)
        common.writeDebug("g_table[%s]: %s", { base_entity.unit_number, g_table[base_entity.unit_number] })
    end


    --------------------------------------------------------------------
    -- Make a list of the pole types that Bio gardens may connect to
    common.get_garden_pole_connectors = function()
        local ret
        if common.get_startup_setting("BI_Easy_Bio_Gardens") then
            common.writeDebug("\"Easy gardens\": Compiling list of poles they can connect to!")
            ret = {}
            local poles = prototypes.get_entity_filtered({
                { filter = "type", type = "electric-pole" },
                {
                    filter = "name",
                    name = {
                        -- Poles named here will be ignored!
                        "bi-rail-power-hidden-pole",
                        "bi-musk-mat-hidden-pole",
                        "bi-bio-garden-hidden-pole"
                    },
                    invert = "true",
                    mode = "and"
                }
            })
            for p, pole in pairs(poles) do
                ret[#ret + 1] = pole.name
            end
        else
            common.writeDebug("\"Easy gardens\": Not active -- nothing to do!")
        end
        return ret
    end

    --------------------------------------------------------------------
    -- Connect hidden poles of Bio gardens!
    -- (This function may be called for hidden poles that have not been
    -- added to the table yet if the pole has just been built. In this
    -- case, we pass on the new pole explicitly!)
    common.connect_garden_pole = function(base, new_pole)
        local compound_entity = storage.compound_entities["bi-bio-garden"]
        local pole = storage[compound_entity.tab][base.unit_number] and
            storage[compound_entity.tab][base.unit_number].pole or
            new_pole

        if pole and pole.valid and compound_entity.hidden and
            compound_entity.hidden.pole and
            compound_entity.hidden.pole.name then
            local wire_reach = prototypes.entity[compound_entity.hidden.pole.name] and
                prototypes.entity[compound_entity.hidden.pole.name].max_wire_distance
            if not wire_reach then
                error("Prototype for hidden pole of Bio gardens doesn't exist!")
            end

            pole.disconnect_neighbour()

            -- Each pole can only have 5 connections. Let's connect to other hidden
            -- poles first!
            local connected
            local neighbours = pole.surface.find_entities_filtered({
                position = pole.position,
                radius = wire_reach,
                type = "electric-pole",
                name = compound_entity.hidden.pole.name
            })
            common.writeDebug("Pole %g has %s neighbours", { pole.unit_number, #neighbours - 1 })

            for n, neighbour in pairs(neighbours or {}) do
                if pole ~= neighbour then
                    connected = pole.connect_neighbour(neighbour)
                    common.writeDebug("Connected pole %g to %s %g: %s",
                        { pole.unit_number, neighbour.name, neighbour.unit_number, connected })
                end
            end


            -- Look for other poles around this one
            neighbours = pole.surface.find_entities_filtered({
                position = pole.position,
                radius = wire_reach,
                type = "electric-pole",
                name = storage.mod_settings.garden_pole_connectors,
            })
            common.writeDebug("Pole %g has %s neighbours", { pole.unit_number, #neighbours })
            for n, neighbour in pairs(neighbours or {}) do
                connected = pole.connect_neighbour(neighbour)
                common.writeDebug("Connected pole %g to neighbour %s (%g): %s",
                    { pole.unit_number, neighbour.name, neighbour.unit_number, connected })
            end
        end
    end

    --------------------------------------------------------------------
    -- Connect hidden poles of powered rails -- this is also used in
    -- migration scripts, so make it a function in common.lua!
    -- (This function may be called for hidden poles that have not been
    -- added to the table yet if the pole has just been built. In this
    -- case, we pass on the new pole explicitly!)
    common.connect_power_rail = function(base, new_pole)
        local pole = storage.bi_power_rail_table[base.unit_number].pole or new_pole
        if pole and pole.valid then
            -- Remove all copper wires from new pole
            pole.disconnect_neighbour()
            common.writeDebug("Removed all wires from %s %g", { pole.name, pole.unit_number })

            -- Look for connecting rails at front and back of the new rail
            for s, side in ipairs({ "front", "back" }) do
                common.writeDebug("Looking for rails at %s", { side })
                local neighbour
                -- Look in all three directions
                for d, direction in ipairs({ "left", "straight", "right" }) do
                    common.writeDebug("Looking for rails in %s direction", { direction })
                    neighbour = base.get_connected_rail {
                        rail_direction = defines.rail_direction[side],
                        rail_connection_direction = defines.rail_connection_direction[direction]
                    }
                    common.writeDebug("Rail %s of %s (%s): %s (%s)",
                        { direction, base.name, base.unit_number, (neighbour and neighbour.name or "nil"), (neighbour and neighbour.unit_number or "nil") })

                    -- Only make a connection if found rail is a powered rail
                    -- (We'll know it's the right type if we find it in our table!)
                    neighbour = neighbour and neighbour.valid and storage.bi_power_rail_table[neighbour.unit_number]
                    if neighbour and neighbour.pole and neighbour.pole.valid then
                        pole.connect_neighbour(neighbour.pole)
                        common.writeDebug("Connected poles!")
                    end
                end
            end

            -- Look for Power-rail connectors
            local connector = base.surface.find_entities_filtered {
                position = base.position,
                radius = common.POWER_TO_RAIL_WIRE_DISTANCE, -- maximum_wire_distance of Power-to-rail-connectors
                name = "bi-power-to-rail-pole"
            }
            -- Connect to first Power-rail connector we've found
            if connector and next(connector) then
                pole.connect_neighbour(connector[1])
                common.writeDebug("Connected " .. pole.name .. " (" .. pole.unit_number ..
                    ") to " .. connector[1].name .. " (" .. connector[1].unit_number .. ")")
                common.writeDebug("Connected %s (%g) to %s (%g)",
                    { pole.name, pole.unit_number, connector[1].name, connector[1].unit_number })
            end
            common.writeDebug("Stored %s (%g) in global table", { base.name, base.unit_number })
        end
    end


    ------------------------------------------------------------------------------------
    -- Get the value of a startup setting
    common.get_startup_setting = function(setting_name)
        return settings.startup[setting_name] and settings.startup[setting_name].value
    end


    ------------------------------------------------------------------------------------
    -- Add the "icons" property based on the value of "icon"
    ------------------------------------------------------------------------------------
    common.BI_add_icons = function()
        for tab_name, tab in pairs(data.raw) do
            for proto_type_name, proto_type in pairs(data.raw[tab_name] or {}) do
                if proto_type.BI_add_icon then
                    proto_type.icons = {
                        {
                            icon = proto_type.icon,
                            icon_size = proto_type.icon_size,
                            icon_mipmaps = proto_type.icon_mipmaps
                        }
                    }
                    proto_type.BI_add_icon = nil
                    common.writeDebug("Added \"icons\" property to data.raw[\"%s\"][\"%s\"]: %s",
                        { tab_name, proto_type_name, proto_type.icons }, "line")
                end
            end
        end
    end

    ------------------------------------------------------------------------------------
    --                                    END OF FILE
    ------------------------------------------------------------------------------------
    return common
end
