--[[-- info
    Provides the ability to spawn random ores all over the place.
]]

-- dependencies
local Event = require 'utils.event'
local Debug = require 'map_gen.maps.diggy.debug'
local Template = require 'map_gen.maps.diggy.template'
local Perlin = require 'map_gen.shared.perlin_noise'
local Simplex = require 'map_gen.shared.simplex_noise'
local Utils = require 'utils.core'
local random = math.random
local sqrt = math.sqrt
local ceil = math.ceil
local min = math.min
local pairs = pairs
local template_resources = Template.resources
local Global = require 'utils.global'


local ores_to_find = {"iron-ore", 
"copper-ore",
"coal",
"tin-ore",
"lead-ore",
"quartz",
"bauxite-ore",
"sulfur",
"zinc-ore",
"nickel-ore",
"cobalt-ore",
"silver-ore",
"gold-ore",
"rutile-ore",
"tungsten-ore",
"gem-ore",
"uranium-ore",
"lithia-water",
"crude-oil"
}
Global.register({
    ores_to_find = ores_to_find,
}, function (tbl)
    ores_to_find = tbl.ores_to_find
end)

-- this
local ScatteredResources = {}

local function get_name_by_weight(collection, sum)
    local pre_calculated = random()
    local current = 0
    local target = pre_calculated * sum

    for name, weight in pairs(collection) do
        current = current + weight
        if (current >= target) then
            return name
        end
    end

    Debug.print('Current \'' .. current .. '\' should be higher or equal to random \'' .. target .. '\'')
end

--overcrowded_resource returns true if one or more entity <resource_name> is found within <min_gap> tiles from <position>
local function overcrowded_resource(surface, position, resource_name, min_gap)
    local bounding_box = {{x = position.x - min_gap + 1, y = position.y - min_gap + 1}, {x = position.x + min_gap, y = position.y + min_gap}}
    local found_resources = surface.count_entities_filtered{area = bounding_box, name = resource_name, limit = 1}
    return found_resources ~= 0
end

--[[--
    Registers all event handlers.
]]
function ScatteredResources.register(config)

    -- source of noise for resource generation
    -- index determines offset
    -- '-1' is reserved for cluster mode
    -- compound clusters use as many indexes as needed > 1
    local base_seed
    local function seeded_noise(surface, x, y, index, sources)
        base_seed = base_seed or surface.map_gen_settings.seed + surface.index + 4000
        local noise = 0
        for _, settings in pairs(sources) do
            settings.type = settings.type or 'perlin'
            settings.offset = settings.offset or 0
            if settings.type == 'zero' then
                noise = noise + 0
            elseif settings.type == 'one' then
                noise = noise + settings.weight * 1
            elseif settings.type == 'perlin' then
                noise = noise + settings.weight * Perlin.noise(x/settings.variance, y/settings.variance,
                            base_seed + 2000*index + settings.offset)
            elseif settings.type == 'simplex' then
                noise = noise + settings.weight * Simplex.d2(x/settings.variance, y/settings.variance,
                            base_seed + 2000*index + settings.offset)
            else
                Debug.print('noise type \'' .. settings.type .. '\' not recognized')
            end

        end
        return noise
    end

    -- global config values

    local resource_richness_weights = config.resource_richness_weights
    local resource_richness_weights_sum = 0
    for _, weight in pairs(resource_richness_weights) do
        resource_richness_weights_sum = resource_richness_weights_sum + weight
    end
    local resource_richness_values = config.resource_richness_values
    local resource_type_scalar = config.resource_type_scalar

    -- scattered config values
    local s_mode = config.scattered_mode
    local s_dist_mod = config.scattered_distance_probability_modifier
    local s_min_prob = config.scattered_min_probability
    local s_max_prob = config.scattered_max_probability
    local s_dist_richness = config.scattered_distance_richness_modifier
    local s_cluster_prob = config.scattered_cluster_probability_multiplier
    local s_cluster_mult = config.scattered_cluster_yield_multiplier

    local s_resource_weights = config.scattered_resource_weights
    local s_resource_weights_sum = 0
    for _, weight in pairs(s_resource_weights) do
        s_resource_weights_sum = s_resource_weights_sum + weight
    end
    local s_min_dist = config.scattered_minimum_resource_distance

    -- cluster config values
    local cluster_mode = config.cluster_mode

    -- compound cluster spawning
    local c_mode = config.cluster_mode
    local c_clusters = config.ore_pattern
    if 'table' ~= type(c_clusters) then
        error('ore_pattern invalid')
    end
    local c_count = 0
    for _, cluster in pairs(c_clusters) do
        c_count = c_count + 1
        cluster.weights_sum = 0
        -- ensure the cluster colors are valid otherwise it fails silently
        -- and breaks things elsewhere
        if cluster.color then
            local c = cluster.color
            if (not c.r) or (not c.g) or (not c.b) then
                cluster.color = nil
            elseif c.r < 0 or c.r > 1 or c.g < 0 or c.g > 1 or c.b < 0 or c.b > 1 then
                cluster.color = nil
            end
        end
        for _, weight in pairs(cluster.weights) do
            cluster.weights_sum = cluster.weights_sum + weight
        end
    end

    local function spawn_cluster_resource(surface, x, y, cluster)
        local distance = sqrt(x * x + y * y)
        local resource_name = get_name_by_weight(cluster.weights, cluster.weights_sum)
        if resource_name == 'skip' then
            return false
        end

        for i, n in pairs(ores_to_find) do  
            if (n == resource_name) then 
                game.print("We have found "..n.."!")
                table.remove(ores_to_find, i)
            end
        end

        local cluster_distance = cluster.distances[resource_name]
        if cluster_distance and distance < cluster_distance then
            return false
        end

        local position = {x = x, y = y}
        if resource_name == 'crude-oil' then
            -- min_gap default is 2.  Represents minimum tile gap between resources in cluster.
            -- Can be >=0, but keep value small in relation to expected resource patch width.
            local min_gap = 2
            if overcrowded_resource(surface, position, resource_name, min_gap) then
                return false  --when overcrowded_resource is true, skip this resource spawn
            end
        end

        local range = resource_richness_values[get_name_by_weight(resource_richness_weights, resource_richness_weights_sum)]
        local amount = random(range[1], range[2]) * (1 + ((distance / cluster.distance_richness) * 0.01)) * cluster.yield

        if resource_type_scalar[resource_name] then
            amount = amount * resource_type_scalar[resource_name]
        end

        template_resources(surface, {{name = resource_name, position = position, amount = ceil(amount)}})
        return true
    end

    -- event registration
    Event.add(Template.events.on_void_removed, function (event)
        local position = event.position
        local x = position.x
        local y = position.y
        local surface = event.surface

        local distance = config.distance(x, y)

        if c_mode then
            for index,cluster in pairs(c_clusters) do
                if distance >= cluster.min_distance and distance <= cluster.max_distance and cluster.noise_settings.type ~= 'skip' then
                    if cluster.noise_settings.type == "connected_tendril" then
                        local noise = seeded_noise(surface, x, y, index, cluster.noise_settings.sources)
                        if -1 * cluster.noise_settings.threshold < noise and noise < cluster.noise_settings.threshold then
                            if spawn_cluster_resource(surface, x, y, cluster) then
                                return -- resource spawned
                            end
                        end
                    elseif cluster.noise_settings.type == "fragmented_tendril" then
                        local noise1 = seeded_noise(surface, x, y, index, cluster.noise_settings.sources)
                        local noise2 = seeded_noise(surface, x, y, index, cluster.noise_settings.discriminator)
                        if -1 * cluster.noise_settings.threshold < noise1 and noise1 < cluster.noise_settings.threshold
                                and -1 * cluster.noise_settings.discriminator_threshold < noise2
                                and noise2 < cluster.noise_settings.discriminator_threshold then
                            if spawn_cluster_resource(surface, x, y, cluster) then
                                return -- resource spawned
                            end
                        end
                    else
                        local noise = seeded_noise(surface, x, y, index, cluster.noise_settings.sources)
                        if noise >= cluster.noise_settings.threshold then
                            if spawn_cluster_resource(surface, x, y, cluster) then
                                return -- resource spawned
                            end
                        end
                    end
                end
            end
        end

        if s_mode then
            local probability = min(s_max_prob, s_min_prob + 0.01 * (distance / s_dist_mod))

            if (cluster_mode) then
                probability = probability * s_cluster_prob
            end

            if (probability > random()) then
                -- spawn single resource point for scatter mode
                local resource_name = get_name_by_weight(s_resource_weights, s_resource_weights_sum)
                if resource_name == 'skip' or s_min_dist[resource_name] > distance then
                    return
                end

                local range = resource_richness_values[get_name_by_weight(resource_richness_weights, resource_richness_weights_sum)]
                local amount = random(range[1], range[2])
                amount = amount * (1 + ((distance / s_dist_richness) * 0.01))

                if resource_type_scalar[resource_name] then
                    amount = amount * resource_type_scalar[resource_name]
                end

                if (cluster_mode) then
                    amount = amount * s_cluster_mult
                end

                Template.resources(surface, {{name = resource_name, position={x=x,y=y}, amount = ceil(amount)}})
            end
        end
    end)

    if (config.display_ore_clusters) then
        local color = {}
        Event.add(defines.events.on_chunk_generated, function (event)
            local surface = event.surface
            local area = event.area

            for x = area.left_top.x, area.left_top.x + 31 do
                for y = area.left_top.y, area.left_top.y + 31 do
                    for index,cluster in pairs(c_clusters) do
                        if cluster.noise_settings.type == "connected_tendril" then
                            local noise = seeded_noise(surface, x, y, index, cluster.noise_settings.sources)
                            if -1 * cluster.noise_settings.threshold < noise and noise < cluster.noise_settings.threshold then
                                color[index] = color[index] or cluster.color or Utils.random_RGB
                                Debug.print_colored_grid_value('o' .. index, surface, {x = x, y = y}, nil, true, 0, color[index])
                            end
                        elseif cluster.noise_settings.type == "fragmented_tendril" then
                            local noise1 = seeded_noise(surface, x, y, index, cluster.noise_settings.sources)
                            local noise2 = seeded_noise(surface, x, y, index, cluster.noise_settings.discriminator)
                            if -1 * cluster.noise_settings.threshold < noise1 and noise1 < cluster.noise_settings.threshold
                                    and -1 * cluster.noise_settings.discriminator_threshold < noise2
                                    and noise2 < cluster.noise_settings.discriminator_threshold then
                                color[index] = color[index] or cluster.color or Utils.random_RGB
                                Debug.print_colored_grid_value('o' .. index, surface, {x = x, y = y}, nil, true, 0, color[index])
                            end
                        elseif cluster.noise_settings.type ~= 'skip' then
                            local noise = seeded_noise(surface, x, y, index, cluster.noise_settings.sources)
                            if noise >= cluster.noise_settings.threshold then
                                color[index] = color[index] or cluster.color or Utils.random_RGB
                                Debug.print_colored_grid_value('o' .. index, surface, {x = x, y = y}, nil, true, 0, color[index])
                            end
                        end
                    end
                end
            end
        end)
    end
end

function ScatteredResources.get_extra_map_info()
    return [[Scattered Resources, resources are everywhere!
Scans of the mine have shown greater amounts of resources to be deeper in the mine
Ore distribution Vs depth:
             traces  min   max
tin          50      100   600
lead         50      100   600
quartz       100     250   750
bauxite      200     300   800
sulfur       200     400   900
zinc         500     600   1100
nickel       300     600   1100
cobalt       500     650   1150
silver       600     700   1200
gold         400     800   1300
rutile       1000    1200  1700
tungsten     1200    1500  2000
gem          800     1500  2000
uranium      800     1000  1500
lithia-water 400     600   1100
crude-oil    200     400   900
]]
end

return ScatteredResources
