local Chrono_table = require 'maps.chronosphere.table'
local Balance = require 'maps.chronosphere.balance'
local Difficulty = require 'modules.difficulty_vote'
local Raffle = require 'utils.math.raffle'

local Public = {}
local List = require 'maps.chronosphere.production_list'
local FT = require 'utils.functions.flying_texts'

local function roll_assembler()
    local objective = Chrono_table.get_table()
    local choices = {types = {}, weights = {}}
    for _, item in pairs(List) do
        if objective.chronojumps >= item.jump_min then
            table.insert(choices.types, item.id)
            table.insert(choices.weights, item.weight)
        end
    end
    return Raffle.raffle(choices.types, choices.weights)
end

function Public.calculate_factory_level(xp, whole_level)
  local base = Balance.factory_level(Difficulty.get().difficulty_vote_value) -- 750 -> 1000 -> 2333
  local level = (xp / base)^(1 / 2)
  if whole_level then
    return math.floor(level)
  end
  return level
end

local function total_avg_level()
    local production = Chrono_table.get_production_table()
    local levels = 0
    local count = 0
    for _, assembler in pairs(production.train_assemblers) do
        levels = levels + assembler.tier
        count = count + 1
    end
    return math.floor(levels / count)
end

local function produce(factory, train)
    if factory.active then
        if not factory.entity.valid then
            factory.active = false
            return
        end
        local id = factory.id
        factory.progress = factory.progress + 60 * (1 + (factory.tier - 1) / 4)
        local progress_excess = factory.progress - List[id].base_time * 60
        local pollution_coef = 0.1 + factory.tier / 40
        if progress_excess >= 0 then
            local multi = math.floor(progress_excess / (List[id].base_time * 60))
            factory.progress = factory.progress - (1 + multi) * List[id].base_time * 60
            local inserted = factory.entity.get_output_inventory().insert {name = List[id].name, count = 1 + multi}
            factory.produced = factory.produced + inserted
            local surface = factory.entity.surface
            surface.pollute(factory.entity.position, inserted * pollution_coef)
            if train then
                game.get_pollution_statistics(surface).on_flow('cargo-wagon', inserted * pollution_coef)
                game.forces.player.get_item_production_statistics(surface).on_flow(List[id].name, inserted)
                factory.entity.products_finished = factory.entity.products_finished + inserted
            else
                game.get_pollution_statistics(surface).on_flow('item-on-ground', inserted * pollution_coef)
            end
        end
    end
end

function Public.register_train_assembler(entity, id)
    local production = Chrono_table.get_production_table()
    production.train_assemblers[id] = {
        entity = entity,
        id = id,
        progress = 0,
        produced = 0,
        tier = 0,
        active = true
    }
end

local function levelup_train_factory(id)
  local production = Chrono_table.get_production_table()
  local xp = production.experience[id]
  local level = Public.calculate_factory_level(xp, true)
  production.train_assemblers[id].tier = level
end

function Public.produce_assemblers()
    local production = Chrono_table.get_production_table()
    for _, factory in pairs(production.assemblers) do
        produce(factory, false)
    end
    for key, factory in pairs(production.train_assemblers) do
        if factory.tier > 0 then
            produce(factory, true)
        end
    end
end

function Public.roll_random_assembler()
    local entity_to_spawn = 'electric-furnace'
    local id = roll_assembler()
    if List[id].kind == 'assembler' then
        entity_to_spawn = 'assembling-machine-1'
    end
    if List[id].kind == 'fluid-assembler' then
        entity_to_spawn = 'assembling-machine-2'
    end

    local tier = 1 + math.min(4, total_avg_level())
    tier = math.random(1, tier)
    if tier > 2 and entity_to_spawn == 'assembling-machine-1' then
        entity_to_spawn = 'assembling-machine-2'
    end
    if tier > 4 and entity_to_spawn == 'assembling-machine-2' then
        entity_to_spawn = 'assembling-machine-3'
    end
    return {entity = entity_to_spawn, id = id, tier = tier}
end

function Public.register_random_assembler(entity, id, tier)
    local production = Chrono_table.get_production_table()
    if not entity or not entity.valid then
        return
    end
    if List[id].kind == 'assembler' or List[id].kind == 'fluid-assembler' then
        entity.set_recipe(List[id].recipe_override or List[id].name)
        entity.recipe_locked = true
    end
    production.assemblers[#production.assemblers + 1] = {
        entity = entity,
        id = id,
        progress = 0,
        produced = 0,
        tier = tier,
        active = false
    }
end

function Public.check_activity()
    local production = Chrono_table.get_production_table()
    for key, factory in pairs(production.assemblers) do
        local entity = factory.entity
        if not entity.valid then
            factory.active = false
            goto continue
        end
        local surface = entity.surface
        local count = surface.count_entities_filtered {position = entity.position, radius = 10, force = 'player'}
        if count > 10 then
            factory.active = true
            FT.flying_text(nil, surface, entity.position, 'Active', {r = 0, g = 0.98, b = 0})
        else
            factory.active = false
            FT.flying_text(nil, surface, entity.position, 'Not Active', {r = 0.98, g = 0, b = 0})
        end
        ::continue::
    end
end

function Public.jump_procedure()
    local production = Chrono_table.get_production_table()
    for _, factory in pairs(production.assemblers) do
        if not production.experience[factory.id] then
            production.experience[factory.id] = 0
        end
        production.experience[factory.id] = production.experience[factory.id] + factory.produced
        levelup_train_factory(factory.id)
    end
    production.assemblers = {}
    for _, factory in pairs(production.train_assemblers) do
        if not production.experience[factory.id] then
            production.experience[factory.id] = 0
        end
        production.experience[factory.id] = production.experience[factory.id] + (factory.produced / 2)
        production.train_assemblers[factory.id].produced = 0
        levelup_train_factory(factory.id)
    end
end

return Public
