local Color = require 'utils.color_presets'
local Event = require 'utils.event'
local Global = require 'utils.global'

local this =
{
    refill_turrets = {},
    refill_chests = { index = 1, placed = 0 },
    full_turrets = {},
    valid_chest =
    {
        ['iron-chest'] = { valid = true, limit = 4 }
    },
    valid_turrets =
    {
        ['gun-turret'] = true,
        ['artillery-turret'] = true
    },
    valid_ammo =
    {
        ['firearm-magazine'] = { valid = true, priority = 1 },
        ['piercing-rounds-magazine'] = { valid = true, priority = 2 },
        ['uranium-rounds-magazine'] = { valid = true, priority = 3 }
    },
    message_limit = {},
    player_settings = {},
    globally_enabled = false
}

Global.register(
    this,
    function (t)
        this = t
    end
)

local Public = {}
local insert = table.insert
local autofill_amount = 10
local valid_chest = this.valid_chest
local valid_turrets = this.valid_turrets
local valid_ammo = this.valid_ammo

local function validate_entity(entity)
    if not entity then
        return false
    end
    if not entity.valid then
        return false
    end
    return true
end

local function fast_remove(tbl, index)
    local count = #tbl
    if index > count then
        return
    elseif index < count then
        tbl[index] = tbl[count]
    end

    tbl[count] = nil
end

local function get_player_data(player, remove_user_data)
    if this.globally_enabled then
        return
    end
    if remove_user_data then
        if this.player_settings[player.index] then
            this.player_settings[player.index] = nil
        end
    end
    if not this.player_settings[player.index] then
        this.player_settings[player.index] =
        {
            placed = 0,
            chests = {},
            turrets = {},
            enabled = true
        }
    end
    return this.player_settings[player.index]
end

local function contains(table, entity)
    if not table then
        return
    end
    for k, turret in pairs(table) do
        if not validate_entity(turret) then
            return false
        end
        if not validate_entity(entity) then
            return false
        end
        if turret.unit_number == entity.unit_number then
            return true
        end
    end
end

local function get_highest(table)
    local highest = -math.huge
    local item
    local count

    for k, v in pairs(table) do
        if (k and valid_ammo[k] and valid_ammo[k].priority > highest) then
            item = k
            count = v
        end
    end

    if not item or not count then
        return false
    end

    return item, count
end

local function get_valid_chest()
    local chests = {}
    if this.globally_enabled then
        local refill_chests = this.refill_chests

        if not next(refill_chests) then
            return
        end

        local chest

        for i = 1, #refill_chests do
            chest = refill_chests[i]
            if chest then
                chests[#chests + 1] = chest
            end

            if not chest.valid then
                fast_remove(refill_chests, i)
                refill_chests.placed = refill_chests.placed - 1
                if refill_chests.placed <= 0 then
                    refill_chests.placed = 0
                end
                return false
            end
        end
        return chests
    end

    local player_data = this.player_settings

    if not next(player_data) then
        return
    end

    local chest

    for i = 1, #player_data do
        local player = game.get_player(i)
        if player and player.valid then
            local p_data = get_player_data(player)
            local p_chests = p_data.chests
            if not next(p_chests) then
                return
            end
            for t = 1, #p_chests do
                chest = p_chests[t]
                if chest then
                    chests[#chests + 1] = chest
                end
                if not chest.valid then
                    fast_remove(p_data.chests, i)
                    p_data.placed = p_data.placed - 1
                    if p_data.placed <= 0 then
                        p_data.placed = 0
                    end
                    return
                end
            end
        end
    end
    return chests
end

local function get_ammo(entity_turret)
    local turret = entity_turret.get_inventory(defines.inventory.turret_ammo)

    local contents = turret.get_contents()

    local c = 0

    for item, count in pairs(contents) do
        if valid_ammo[item] and valid_ammo[item].valid and count >= 1 then
            c = count
            return item, c
        end
    end

    return false, c
end

local function get_items(chest)
    local contents = chest.get_contents()
    local item, count = get_highest(contents)

    if valid_ammo[item] and valid_ammo[item].valid and count >= 1 then
        return item, count
    end
end

local function remove_ammo(chest, entity_turret)
    local turret = entity_turret.get_inventory(defines.inventory.turret_ammo)
    local current_ammo

    if not chest or not chest.valid then
        return
    end

    local contents = turret.get_contents()

    for item, count in pairs(contents) do
        if count >= 1 then
            local t = { name = item, count = count }
            if chest.can_insert(t) then
                local c = chest.insert(t)
                current_ammo = item
                turret.remove({ name = item, count = c })
                return current_ammo
            end
        end
    end
end

local function refill(entity_turret, entity_chest)
    local turret = entity_turret

    for _, chests in next, entity_chest do
        local chest = chests.get_inventory(defines.inventory.chest)

        local item, count = get_items(chest)

        local turret_inv = turret.get_inventory(defines.inventory.turret_ammo)
        if valid_ammo[item] and valid_ammo[item].valid and count >= 1 then
            local ammo_name, ammo_count = get_ammo(turret)

            if ammo_name and valid_ammo[ammo_name].priority < valid_ammo[item].priority then
                remove_ammo(chest, turret)
            end
            if ammo_count and ammo_count >= 10 then
                goto continue
            end
            local t = { name = item, count = 1 }
            local c = turret_inv.insert(t)
            if (c > 0) then
                chest.remove({ name = item, count = c })
            end

            ::continue::
        end
    end
end

local function do_refill_turrets()
    local chest = get_valid_chest()

    if not chest then
        return
    end

    if this.globally_enabled then
        local turrets = this.refill_turrets

        if not next(turrets) then
            return
        end

        for i = 1, #turrets do
            local turret = turrets[i]
            if not turret then
                return
            end

            if not turret.valid then
                fast_remove(turrets, i)
            else
                refill(turret, chest)
            end
        end
        return
    end

    local player_data = this.player_settings

    if not next(player_data) then
        return
    end

    for p, _ in next, player_data do
        local player = game.get_player(p)
        if player and player.valid then
            local p_data = get_player_data(player)
            if not p_data then
                return
            end
            local turrets = p_data.turrets
            if not next(turrets) then
                return
            end
            for key, turret in next, turrets do
                if not turret then
                    return
                end

                if not turret.valid then
                    fast_remove(turrets, key)
                else
                    refill(turret, chest)
                end
            end
        end
    end
end

local function show_text(msg, pos, color, surface)
    if color == nil then
        surface.create_entity({ name = 'flying-text', position = pos, text = msg })
    else
        surface.create_entity({ name = 'flying-text', position = pos, text = msg, color = color })
    end
end

local function move_items(source, destination, stack)
    if (source.get_item_count(stack.name) == 0) then
        return -1
    end

    if (not destination.can_insert(stack)) then
        return -2
    end

    local itemsRemoved = source.remove(stack)
    stack.count = itemsRemoved
    return destination.insert(stack)
end

local function move_multiple(source, destination, stack, amount)
    local ret = 0
    for _, itemName in pairs(stack) do
        ret = move_items(source, destination, { name = itemName, count = amount })
        if (ret > 0) then
            return ret
        end
    end
    return ret
end

local function auto_insert_into_turret(player, turret)
    local inventory = player.get_main_inventory()
    if (inventory == nil) then
        return
    end

    local ret = move_multiple(inventory, turret, { 'artillery-shell', 'uranium-rounds-magazine', 'piercing-rounds-magazine', 'firearm-magazine' }, autofill_amount)

    if (ret > 1) then
        show_text('[Autofill] Inserted ' .. ret .. '!', turret.position, Color.info, player.surface)
    elseif (ret == -1) then
        show_text('[Autofill] Out of ammo!', turret.position, Color.red, player.surface)
    elseif (ret == -2) then
        show_text('[Autofill] Autofill ERROR! - Report this bug!', turret.position, Color.red, player.surface)
    end
end

local function auto_insert_into_vehicle(player, vehicle)
    local inventory = player.get_main_inventory()
    if (inventory == nil) then
        return
    end

    if ((vehicle.name == 'car') or (vehicle.name == 'tank') or (vehicle.name == 'locomotive')) then
        move_multiple(inventory, vehicle, { 'nuclear-fuel', 'rocket-fuel', 'solid-fuel', 'coal', 'wood' }, 50)
    end

    if ((vehicle.name == 'car') or (vehicle.name == 'tank')) then
        move_multiple(inventory, vehicle, { 'uranium-rounds-magazine', 'piercing-rounds-magazine', 'firearm-magazine' }, autofill_amount)
    end

    if (vehicle.name == 'tank') then
        move_multiple(inventory, vehicle, { 'explosive-uranium-cannon-shell', 'uranium-cannon-shell', 'explosive-cannon-shell', 'cannon-shell' }, autofill_amount)
    end
end

local function on_entity_built(event)
    local player = game.players[event.player_index]
    if not (player and player.valid) then
        return
    end

    local ce = event.entity
    if not (ce and ce.valid) then
        return
    end

    if (valid_chest[ce.name] and valid_chest[ce.name].valid) then
        if this.globally_enabled then
            if (this.refill_chests.placed < valid_chest[ce.name].limit) then
                if this.message_limit[player.index] then
                    this.message_limit[player.index] = nil
                end
                Public.add_chest_to_refill_callback(nil, ce)
            else
                if not this.message_limit[player.index] then
                    this.message_limit[player.index] = true
                    player.print('[Autofill] Chest limit reached.', { color = Color.warning })
                end
            end
        else
            local player_data = get_player_data(player)
            if (player_data.placed < valid_chest[ce.name].limit) then
                if this.message_limit[player.index] then
                    this.message_limit[player.index] = nil
                end
                Public.add_chest_to_refill_callback(player, ce)
            else
                if not this.message_limit[player.index] then
                    this.message_limit[player.index] = true
                    player.print('[Autofill] Chest limit reached.', { color = Color.warning })
                end
            end
        end
    end

    if (valid_turrets[ce.name]) then
        if this.globally_enabled then
            Public.refill_turret_callback(nil, ce)
        else
            Public.refill_turret_callback(player, ce)
        end
        auto_insert_into_turret(player, ce)
    end

    if ((ce.name == 'car') or (ce.name == 'tank') or (ce.name == 'locomotive')) then
        auto_insert_into_vehicle(player, ce)
    end
end

local function on_pre_player_mined_item(event)
    local player = game.get_player(event.player_index)

    if not validate_entity(player) then
        return
    end

    local entity = event.entity
    if not validate_entity(entity) then
        return
    end

    if not valid_turrets[entity.name] then
        return
    end

    if this.globally_enabled then
        local chest = get_valid_chest()

        if not chest then
            return
        end

        local refill_turrets = this.refill_turrets
        local full_turrets = this.full_turrets

        if contains(refill_turrets, entity) or contains(full_turrets, entity) then
            remove_ammo(chest, entity)
        end
        return
    end

    local chest = get_valid_chest(player)

    if not chest then
        return
    end

    local refill_turrets = this.refill_turrets
    local full_turrets = this.full_turrets

    if contains(refill_turrets, entity) or contains(full_turrets, entity) then
        remove_ammo(chest, entity)
    end
end

local function on_tick()
    do_refill_turrets()
end

Public.refill_turret_callback = function (player, turret)
    if this.globally_enabled then
        local refill_turrets = this.refill_turrets
        if turret and turret.valid then
            refill_turrets[#refill_turrets + 1] = turret
        end
        return
    end
    if turret and turret.valid then
        local player_data = get_player_data(player)
        local turrets = player_data.turrets
        insert(turrets, turret)
    end
end

Public.add_chest_to_refill_callback = function (player, entity)
    if entity and entity.valid then
        if this.globally_enabled then
            local refill_chests = this.refill_chests
            refill_chests[#refill_chests + 1] = entity
            refill_chests.placed = refill_chests.placed + 1
        else
            local player_data = get_player_data(player)
            local chest_placed = player_data.placed
            local chests = player_data.chests

            insert(chests, entity)

            player_data.placed = chest_placed + 1
        end

        rendering.draw_text
        {
            text = '⚙',
            surface = entity.surface,
            target = entity,
            target_offset = { 0, -0.5 },
            scale = 1.5,
            color = { r = 0, g = 0.6, b = 1 },
            alignment = 'center'
        }
    end
end

Public.globally_enabled = function (value)
    if value then
        this.globally_enabled = value
    else
        this.globally_enabled = false
    end
    return this.globally_enabled
end

Event.add(defines.events.on_built_entity, on_entity_built)
Event.add(defines.events.on_pre_player_mined_item, on_pre_player_mined_item)
Event.on_nth_tick(50, on_tick)

return Public
