local Color = require 'utils.color_presets'
local Event = require 'utils.event'
local WPT = require 'maps.mountain_fortress_v3.table'
local WD = require 'modules.wave_defense.table'
local RPG = require 'modules.rpg.main'
local Alert = require 'utils.alert'
local Task = require 'utils.task'
local Token = require 'utils.token'
local Public = {}

local shuffle = table.shuffle_table
local random = math.random
local floor = math.floor
local insert = table.insert
local abs = math.abs

local item_worths = {
    ['wooden-chest'] = 4,
    ['iron-chest'] = 8,
    ['steel-chest'] = 64,
    ['storage-tank'] = 64,
    ['transport-belt'] = 4,
    ['fast-transport-belt'] = 16,
    ['express-transport-belt'] = 64,
    ['underground-belt'] = 16,
    ['fast-underground-belt'] = 64,
    ['express-underground-belt'] = 256,
    ['splitter'] = 16,
    ['fast-splitter'] = 64,
    ['express-splitter'] = 256,
    ['burner-inserter'] = 2,
    ['inserter'] = 8,
    ['long-handed-inserter'] = 16,
    ['fast-inserter'] = 32,
    ['filter-inserter'] = 40,
    ['stack-inserter'] = 128,
    ['stack-filter-inserter'] = 160,
    ['small-electric-pole'] = 4,
    ['medium-electric-pole'] = 32,
    ['big-electric-pole'] = 64,
    ['substation'] = 256,
    ['pipe'] = 1,
    ['pipe-to-ground'] = 15,
    ['pump'] = 32,
    ['rail'] = 8,
    ['train-stop'] = 64,
    ['rail-signal'] = 16,
    ['rail-chain-signal'] = 16,
    ['logistic-robot'] = 256,
    ['construction-robot'] = 256,
    ['logistic-chest-active-provider'] = 256,
    ['logistic-chest-passive-provider'] = 256,
    ['logistic-chest-storage'] = 256,
    ['logistic-chest-buffer'] = 512,
    ['logistic-chest-requester'] = 512,
    ['roboport'] = 2048,
    ['small-lamp'] = 16,
    ['red-wire'] = 4,
    ['green-wire'] = 4,
    ['arithmetic-combinator'] = 16,
    ['decider-combinator'] = 16,
    ['constant-combinator'] = 16,
    ['power-switch'] = 16,
    ['programmable-speaker'] = 32,
    ['stone-brick'] = 2,
    ['concrete'] = 4,
    ['hazard-concrete'] = 4,
    ['refined-concrete'] = 16,
    ['refined-hazard-concrete'] = 16,
    ['cliff-explosives'] = 256,
    ['repair-pack'] = 8,
    ['boiler'] = 8,
    ['steam-engine'] = 32,
    ['solar-panel'] = 64,
    ['accumulator'] = 64,
    ['nuclear-reactor'] = 8192,
    ['heat-pipe'] = 128,
    ['heat-exchanger'] = 256,
    ['steam-turbine'] = 256,
    ['burner-mining-drill'] = 8,
    ['electric-mining-drill'] = 32,
    ['offshore-pump'] = 16,
    ['pumpjack'] = 64,
    ['stone-furnace'] = 4,
    ['steel-furnace'] = 64,
    ['electric-furnace'] = 256,
    ['assembling-machine-1'] = 32,
    ['assembling-machine-2'] = 128,
    ['assembling-machine-3'] = 512,
    ['oil-refinery'] = 256,
    ['chemical-plant'] = 128,
    ['centrifuge'] = 2048,
    ['lab'] = 64,
    ['beacon'] = 512,
    ['speed-module'] = 128,
    ['speed-module-2'] = 512,
    ['speed-module-3'] = 2048,
    ['effectivity-module'] = 128,
    ['effectivity-module-2'] = 512,
    ['effectivity-module-3'] = 2048,
    ['productivity-module'] = 128,
    ['productivity-module-2'] = 512,
    ['productivity-module-3'] = 2048,
    ['iron-plate'] = 1,
    ['copper-plate'] = 1,
    ['solid-fuel'] = 16,
    ['steel-plate'] = 8,
    ['plastic-bar'] = 8,
    ['sulfur'] = 4,
    ['battery'] = 16,
    ['explosives'] = 4,
    ['crude-oil-barrel'] = 8,
    ['heavy-oil-barrel'] = 16,
    ['light-oil-barrel'] = 16,
    ['lubricant-barrel'] = 16,
    ['petroleum-gas-barrel'] = 16,
    ['sulfuric-acid-barrel'] = 16,
    ['water-barrel'] = 4,
    ['copper-cable'] = 1,
    ['iron-stick'] = 1,
    ['iron-gear-wheel'] = 2,
    ['empty-barrel'] = 4,
    ['electronic-circuit'] = 4,
    ['advanced-circuit'] = 16,
    ['processing-unit'] = 128,
    ['engine-unit'] = 8,
    ['electric-engine-unit'] = 64,
    ['flying-robot-frame'] = 128,
    ['satellite'] = 32768,
    ['rocket-control-unit'] = 256,
    ['low-density-structure'] = 64,
    ['rocket-fuel'] = 256,
    ['nuclear-fuel'] = 1024,
    ['uranium-235'] = 1024,
    ['uranium-238'] = 32,
    ['uranium-fuel-cell'] = 128,
    ['automation-science-pack'] = 4,
    ['logistic-science-pack'] = 16,
    ['military-science-pack'] = 64,
    ['chemical-science-pack'] = 128,
    ['production-science-pack'] = 256,
    ['utility-science-pack'] = 256,
    ['space-science-pack'] = 512,
    ['submachine-gun'] = 32,
    ['shotgun'] = 16,
    ['combat-shotgun'] = 256,
    ['rocket-launcher'] = 128,
    ['flamethrower'] = 512,
    ['firearm-magazine'] = 4,
    ['piercing-rounds-magazine'] = 8,
    ['uranium-rounds-magazine'] = 64,
    ['shotgun-shell'] = 4,
    ['piercing-shotgun-shell'] = 16,
    ['cannon-shell'] = 8,
    ['explosive-cannon-shell'] = 16,
    ['uranium-cannon-shell'] = 64,
    ['explosive-uranium-cannon-shell'] = 64,
    ['rocket'] = 8,
    ['explosive-rocket'] = 8,
    ['atomic-bomb'] = 16384,
    ['flamethrower-ammo'] = 32,
    ['grenade'] = 16,
    ['cluster-grenade'] = 64,
    ['poison-capsule'] = 64,
    ['slowdown-capsule'] = 16,
    ['defender-capsule'] = 16,
    ['distractor-capsule'] = 128,
    ['destroyer-capsule'] = 256,
    ['light-armor'] = 32,
    ['heavy-armor'] = 256,
    ['modular-armor'] = 1024,
    ['power-armor'] = 4096,
    ['power-armor-mk2'] = 32768,
    ['solar-panel-equipment'] = 256,
    ['fusion-reactor-equipment'] = 8192,
    ['energy-shield-equipment'] = 512,
    ['energy-shield-mk2-equipment'] = 4096,
    ['battery-equipment'] = 128,
    ['battery-mk2-equipment'] = 2048,
    ['personal-laser-defense-equipment'] = 2048,
    ['belt-immunity-equipment'] = 256,
    ['exoskeleton-equipment'] = 1024,
    ['personal-roboport-equipment'] = 512,
    ['personal-roboport-mk2-equipment'] = 4096,
    ['night-vision-equipment'] = 256,
    ['stone-wall'] = 8,
    ['gate'] = 16,
    ['gun-turret'] = 64,
    ['laser-turret'] = 1024,
    ['flamethrower-turret'] = 2048,
    ['radar'] = 32,
    ['rocket-silo'] = 65536
}

local item_names = {}
for k, _ in pairs(item_worths) do
    insert(item_names, k)
end
local size_of_item_names = #item_names

local function get_raffle_keys()
    local raffle_keys = {}
    for i = 1, size_of_item_names, 1 do
        raffle_keys[i] = i
    end
    shuffle(raffle_keys)
    return raffle_keys
end

local function get_random_weighted(weighted_table, weight_index)
    local total_weight = 0
    weight_index = weight_index or 1

    for _, w in pairs(weighted_table) do
        total_weight = total_weight + w[weight_index]
    end

    local index = random() * total_weight
    local weight_sum = 0
    for k, w in pairs(weighted_table) do
        weight_sum = weight_sum + w[weight_index]
        if weight_sum >= index then
            return w, k
        end
    end
end

local function init_price_check(locomotive, mystical_chest)
    local roll = 48 + abs(locomotive.position.y) * 1.75
    roll = roll * random(25, 1337) * 0.01

    local item_stacks = {}
    local roll_count = 2
    for _ = 1, roll_count, 1 do
        for _, stack in pairs(Public.roll(floor(roll / roll_count), 2)) do
            if not item_stacks[stack.name] then
                item_stacks[stack.name] = stack.count
            else
                item_stacks[stack.name] = item_stacks[stack.name] + stack.count
            end
        end
    end

    local price = {}
    for k, v in pairs(item_stacks) do
        insert(price, {name = k, count = v})
    end

    mystical_chest.price = price
end

local function roll_item_stacks(remaining_budget, max_slots, blacklist)
    local item_stack_set = {}
    local item_stack_set_worth = 0

    for i = 1, max_slots, 1 do
        if remaining_budget <= 0 then
            break
        end
        local item_stack = Public.roll_item_stack(remaining_budget, blacklist)
        item_stack_set[i] = item_stack
        remaining_budget = remaining_budget - item_stack.count * item_worths[item_stack.name]
        item_stack_set_worth = item_stack_set_worth + item_stack.count * item_worths[item_stack.name]
    end

    return item_stack_set, item_stack_set_worth
end

local pause_wd_token =
    Token.register(
    function()
        WD.pause(false)
        local mc_rewards = WPT.get('mc_rewards')
        mc_rewards.temp_boosts.wave_defense = nil
        local message = ({'locomotive.wd_resumed'})
        Alert.alert_all_players(15, message, nil, 'achievement/tech-maniac')
    end
)

local restore_mining_speed_token =
    Token.register(
    function()
        local mc_rewards = WPT.get('mc_rewards')
        local force = game.forces.player
        if mc_rewards.temp_boosts.mining then
            force.manual_mining_speed_modifier = force.manual_mining_speed_modifier - 0.5
            mc_rewards.temp_boosts.mining = nil
        end
    end
)

local restore_movement_speed_token =
    Token.register(
    function()
        local mc_rewards = WPT.get('mc_rewards')
        local force = game.forces.player
        if mc_rewards.temp_boosts.movement then
            force.character_running_speed_modifier = force.character_running_speed_modifier - 0.2
            mc_rewards.temp_boosts.movement = nil
        end
    end
)

local mc_random_rewards = {
    {
        name = 'XP',
        color = {r = 0.00, g = 0.45, b = 0.00},
        tooltip = 'Selecting this will insert random XP onto the global xp pool!',
        func = (function(player)
            local rng = random(1024, 10240)
            RPG.add_to_global_pool(rng)
            local message = ({'locomotive.xp_bonus', player.name})
            Alert.alert_all_players(15, message, nil, 'achievement/tech-maniac')
            return true
        end),
        512
    },
    {
        name = 'Coins',
        color = {r = 0.00, g = 0.35, b = 0.00},
        tooltip = 'Selecting this will grant each player some coins!',
        func = (function(p)
            local rng = random(256, 512)
            local players = game.connected_players
            for i = 1, #players do
                local player = players[i]
                if player and player.valid then
                    if player.can_insert({name = 'coin', count = rng}) then
                        player.insert({name = 'coin', count = rng})
                    end
                end
            end
            local message = ({'locomotive.coin_bonus', p.name})
            Alert.alert_all_players(15, message, nil, 'achievement/tech-maniac')
            return true
        end),
        512
    },
    {
        name = 'Movement bonus',
        str = 'movement',
        color = {r = 0.00, g = 0.25, b = 0.00},
        tooltip = 'Selecting this will grant the team a bonus movement speed for 15 minutes!',
        func = (function(player)
            local mc_rewards = WPT.get('mc_rewards')
            local force = game.forces.player
            if mc_rewards.temp_boosts.movement then
                return false, '[Rewards] Movement bonus is already applied. Please choose another reward.'
            end

            mc_rewards.temp_boosts.movement = true

            Task.set_timeout_in_ticks(54000, restore_movement_speed_token)
            force.character_running_speed_modifier = force.character_running_speed_modifier + 0.2
            local message = ({'locomotive.movement_bonus', player.name})
            Alert.alert_all_players(15, message, nil, 'achievement/tech-maniac')
            return true
        end),
        512
    },
    {
        name = 'Mining bonus',
        str = 'mining',
        color = {r = 0.00, g = 0.00, b = 0.25},
        tooltip = 'Selecting this will grant the team a bonus mining speed for 15 minutes!',
        func = (function(player)
            local mc_rewards = WPT.get('mc_rewards')
            local force = game.forces.player
            if mc_rewards.temp_boosts.mining then
                return false, '[Rewards] Mining bonus is already applied. Please choose another reward.'
            end

            mc_rewards.temp_boosts.mining = true

            Task.set_timeout_in_ticks(54000, restore_mining_speed_token)
            force.manual_mining_speed_modifier = force.manual_mining_speed_modifier + 0.5
            local message = ({'locomotive.mining_bonus', player.name})
            Alert.alert_all_players(15, message, nil, 'achievement/tech-maniac')
            return true
        end),
        512
    },
    {
        name = 'Inventory Bonus',
        color = {r = 0.00, g = 0.00, b = 0.25},
        tooltip = 'Selecting this will grant the team permanent inventory bonus!',
        func = (function(player)
            local force = game.forces.player
            force.character_inventory_slots_bonus = force.character_inventory_slots_bonus + 1
            local message = ({'locomotive.inventory_bonus', player.name})
            Alert.alert_all_players(15, message, nil, 'achievement/tech-maniac')
            return true
        end),
        512
    },
    {
        name = 'Heal Locomotive',
        color = {r = 0.00, g = 0.00, b = 0.25},
        tooltip = 'Selecting this will heal the main locomotive to full health!',
        func = (function(player)
            local locomotive_max_health = WPT.get('locomotive_max_health')
            WPT.set('locomotive_health', locomotive_max_health)
            local message = ({'locomotive.locomotive_health', player.name})
            Alert.alert_all_players(15, message, nil, 'achievement/tech-maniac')
            return true
        end),
        256
    },
    {
        name = 'Wave Defense',
        str = 'wave_defense',
        color = {r = 0.35, g = 0.00, b = 0.00},
        tooltip = 'Selecting this will pause the wave defense for 5 minutes. Ideal if you want to take a break!',
        func = (function(player)
            local mc_rewards = WPT.get('mc_rewards')
            if mc_rewards.temp_boosts.wave_defense then
                return false, '[Rewards] Wave Defense break is already applied. Please choose another reward.'
            end
            mc_rewards.temp_boosts.wave_defense = true

            WD.pause(true)
            Task.set_timeout_in_ticks(18000, pause_wd_token)
            local message = ({'locomotive.wd_paused', player.name})
            Alert.alert_all_players(15, message, nil, 'achievement/tech-maniac')
            return true
        end),
        64
    }
}

local function mystical_chest_reward(player)
    if player.gui.screen['reward_system'] then
        player.gui.screen['reward_system'].destroy()
        return
    end

    local frame = player.gui.screen.add {type = 'frame', caption = 'Mystical Reward:', name = 'reward_system', direction = 'vertical'}
    frame.auto_center = true
    frame = frame.add {type = 'frame', name = 'reward_system_1', direction = 'vertical', style = 'inside_shallow_frame'}
    frame.style.padding = 4

    local mc_rewards = WPT.get('mc_rewards')
    mc_rewards.current = {}

    for i = 1, 3 do
        local d, key = get_random_weighted(mc_random_rewards)
        if not mc_rewards.current[key] and not mc_rewards.temp_boosts[d.str] then
            mc_rewards.current[key] = {
                id = i,
                name = d.name
            }
            local b = frame.add({type = 'button', name = tostring(i), caption = d.name})
            b.style.font_color = d.color
            b.style.font = 'heading-2'
            b.style.minimal_width = 180
            b.style.horizontal_align = 'center'
            b.tooltip = d.tooltip
        end
    end
    if not next(mc_rewards.current) then
        if player.gui.screen['reward_system'] then
            player.gui.screen['reward_system'].destroy()
        end
        return player.print('[Rewards] No rewards are available.', Color.fail)
    end

    -- something fancy to reward players
end

local function container_opened(event)
    local entity = event.entity
    if not entity then
        return
    end
    if not entity.valid then
        return
    end
    if not entity.unit_number then
        return
    end

    local mystical_chest = WPT.get('mystical_chest')
    if not mystical_chest then
        return
    end
    if not (mystical_chest.entity and mystical_chest.entity.valid) then
        return
    end

    if entity.unit_number ~= mystical_chest.entity.unit_number then
        return
    end

    local player = game.get_player(event.player_index)
    if not (player and player.valid) then
        return
    end

    Public.add_mystical_chest(player)
end

local function on_gui_opened(event)
    container_opened(event)
end

local function on_gui_closed(event)
    container_opened(event)
end

local function on_gui_click(event)
    local element = event.element
    if not (element and element.valid) then
        return
    end

    if element.type ~= 'button' then
        return
    end

    if element.parent.name ~= 'reward_system_1' then
        return
    end
    local i = tonumber(element.name)

    local mc_rewards = WPT.get('mc_rewards')
    local current = mc_rewards.current

    local player = game.get_player(element.player_index)
    if not (player and player.valid) then
        return
    end

    for id, data in pairs(current) do
        if data.id == i then
            local success, msg = mc_random_rewards[id].func(player)
            if not success then
                return player.print(msg, Color.fail)
            end
            break
        end
    end

    element.parent.parent.destroy()
end

function Public.roll_item_stack(remaining_budget, blacklist)
    if remaining_budget <= 0 then
        return
    end
    local raffle_keys = get_raffle_keys()
    local item_name = false
    local item_worth = 0
    for _, index in pairs(raffle_keys) do
        item_name = item_names[index]
        item_worth = item_worths[item_name]
        if not blacklist[item_name] and item_worth <= remaining_budget then
            break
        end
    end

    local stack_size = game.item_prototypes[item_name].stack_size * 32

    local item_count = 1

    for c = 1, random(1, stack_size), 1 do
        local price = c * item_worth
        if price <= remaining_budget then
            item_count = c
        else
            break
        end
    end

    return {name = item_name, count = item_count}
end

function Public.roll(budget, max_slots, blacklist)
    if not budget then
        return
    end
    if not max_slots then
        return
    end

    local b
    if not blacklist then
        b = {}
    else
        b = blacklist
    end

    budget = floor(budget)
    if budget == 0 then
        return
    end

    local final_stack_set
    local final_stack_set_worth = 0

    for _ = 1, 5, 1 do
        local item_stack_set, item_stack_set_worth = roll_item_stacks(budget, max_slots, b)
        if item_stack_set_worth > final_stack_set_worth or item_stack_set_worth == budget then
            final_stack_set = item_stack_set
            final_stack_set_worth = item_stack_set_worth
        end
    end
    return final_stack_set
end

function Public.add_mystical_chest(player)
    local locomotive = WPT.get('locomotive')
    if not locomotive then
        return
    end
    if not locomotive.valid then
        return
    end

    local mystical_chest = WPT.get('mystical_chest')
    if not (mystical_chest.entity and mystical_chest.entity.valid) then
        return
    end

    if not mystical_chest.price then
        init_price_check(locomotive, mystical_chest)
    end

    local entity = mystical_chest.entity

    local inventory = mystical_chest.entity.get_inventory(defines.inventory.chest)

    for key, item_stack in pairs(mystical_chest.price) do
        local count_removed = inventory.remove(item_stack)
        mystical_chest.price[key].count = mystical_chest.price[key].count - count_removed
        if mystical_chest.price[key].count <= 0 then
            table.remove(mystical_chest.price, key)
        end
    end

    if #mystical_chest.price == 0 then
        init_price_check(locomotive, mystical_chest)
        if player and player.valid then
            mystical_chest_reward(player)
        end
        return true
    end

    for slot = 1, 30, 1 do
        entity.clear_request_slot(slot)
    end

    for slot, item_stack in pairs(mystical_chest.price) do
        mystical_chest.entity.set_request_slot(item_stack, slot)
    end
end

Public.init_price_check = init_price_check

Event.add(defines.events.on_gui_opened, on_gui_opened)
Event.add(defines.events.on_gui_closed, on_gui_closed)
Event.add(defines.events.on_gui_click, on_gui_click)

return Public
