local table_insert = table.insert

local Table = require 'modules.scrap_towny_ffa.table'
local Town_center = require 'modules.scrap_towny_ffa.town_center'

local upgrade_functions = {
    -- Upgrade Town Center Health
    [1] = function(town_center, player)
        local market = town_center.market
        local surface = market.surface
        if town_center.max_health > 500000 then
            return
        end
        town_center.health = town_center.health + town_center.max_health
        town_center.max_health = town_center.max_health * 2
        Town_center.set_market_health(market, 0)
        surface.play_sound({path = 'utility/achievement_unlocked', position = player.position, volume_modifier = 1})
    end,
    -- Upgrade Backpack
    [2] = function(town_center, player)
        local market = town_center.market
        local force = market.force
        local surface = market.surface
        if force.character_inventory_slots_bonus > 100 then
            return
        end
        force.character_inventory_slots_bonus = force.character_inventory_slots_bonus + 5
        surface.play_sound({path = 'utility/achievement_unlocked', position = player.position, volume_modifier = 1})
    end,
    -- Upgrade Mining Productivity
    [3] = function(town_center, player)
        local market = town_center.market
        local force = market.force
        local surface = market.surface
        if town_center.upgrades.mining_prod >= 10 then
            return
        end
        town_center.upgrades.mining_prod = town_center.upgrades.mining_prod + 1
        force.mining_drill_productivity_bonus = force.mining_drill_productivity_bonus + 0.1
        surface.play_sound({path = 'utility/achievement_unlocked', position = player.position, volume_modifier = 1})
    end,
    -- Laser Turret Slot
    [4] = function(town_center, player)
        local market = town_center.market
        local surface = market.surface
        town_center.upgrades.laser_turret.slots = town_center.upgrades.laser_turret.slots + 1
        surface.play_sound({path = 'utility/new_objective', position = player.position, volume_modifier = 1})
    end,
    -- Set Spawn Point
    [5] = function(town_center, player)
        local ffatable = Table.get_table()
        local market = town_center.market
        local force = market.force
        local surface = market.surface
        local spawn_point = force.get_spawn_position(surface)
        ffatable.spawn_point[player.name] = spawn_point
        surface.play_sound({path = 'utility/scenario_message', position = player.position, volume_modifier = 1})
    end
}

local function clear_offers(market)
    for _ = 1, 256, 1 do
        local a = market.remove_market_item(1)
        if a == false then
            return
        end
    end
end

local function set_offers(town_center)
    local market = town_center.market
    local force = market.force

    local special_offers = {}

    if town_center.max_health < 500000 then
        special_offers[1] = {{{'coin', town_center.max_health * 0.1}}, 'Upgrade Town Center Health'}
    else
        special_offers[1] = {{{'coin', 1}}, 'Maximum Health upgrades reached!'}
    end
    if force.character_inventory_slots_bonus <= 100 then
        special_offers[2] = {{{'coin', (force.character_inventory_slots_bonus / 5 + 1) * 50}}, 'Upgrade Backpack +5 Slot'}
    else
        special_offers[2] = {{{'coin', 1}}, 'Maximum Backpack upgrades reached!'}
    end
    if town_center.upgrades.mining_prod < 10 then
        special_offers[3] = {{{'coin', (town_center.upgrades.mining_prod + 1) * 400}}, 'Upgrade Mining Productivity +10%'}
    else
        special_offers[3] = {{{'coin', 1}}, 'Maximum Mining upgrades reached!'}
    end
    local laser_turret = 'Laser Turret Slot [#' .. tostring(town_center.upgrades.laser_turret.slots + 1) .. ']'
    special_offers[4] = {{{'coin', 1000 + (town_center.upgrades.laser_turret.slots * 50)}}, laser_turret}
    local spawn_point = 'Set Spawn Point'
    special_offers[5] = {{{'coin', 1}}, spawn_point}

    local market_items = {}

    for _, v in pairs(special_offers) do
        table_insert(market_items, {price = v[1], offer = {type = 'nothing', effect_description = v[2]}})
    end

    -- coin purchases
    table_insert(market_items, {price = {{'coin', 1}}, offer = {type = 'give-item', item = 'raw-fish', count = 1}})
    table_insert(market_items, {price = {{'coin', 1}}, offer = {type = 'give-item', item = 'wood', count = 6}})
    table_insert(market_items, {price = {{'coin', 1}}, offer = {type = 'give-item', item = 'iron-ore', count = 6}})
    table_insert(market_items, {price = {{'coin', 1}}, offer = {type = 'give-item', item = 'copper-ore', count = 6}})
    table_insert(market_items, {price = {{'coin', 1}}, offer = {type = 'give-item', item = 'stone', count = 6}})
    table_insert(market_items, {price = {{'coin', 1}}, offer = {type = 'give-item', item = 'coal', count = 6}})
    table_insert(market_items, {price = {{'coin', 1}}, offer = {type = 'give-item', item = 'uranium-ore', count = 4}})
    table_insert(market_items, {price = {{'coin', 300}}, offer = {type = 'give-item', item = 'loader', count = 1}})
    table_insert(market_items, {price = {{'coin', 600}}, offer = {type = 'give-item', item = 'fast-loader', count = 1}})
    table_insert(market_items, {price = {{'coin', 900}}, offer = {type = 'give-item', item = 'express-loader', count = 1}})
    -- scrap selling
    table_insert(market_items, {price = {{'wood', 7}}, offer = {type = 'give-item', item = 'coin', count = 1}})
    table_insert(market_items, {price = {{'iron-ore', 7}}, offer = {type = 'give-item', item = 'coin', count = 1}})
    table_insert(market_items, {price = {{'copper-ore', 7}}, offer = {type = 'give-item', item = 'coin', count = 1}})
    table_insert(market_items, {price = {{'stone', 7}}, offer = {type = 'give-item', item = 'coin', count = 1}})
    table_insert(market_items, {price = {{'coal', 7}}, offer = {type = 'give-item', item = 'coin', count = 1}})
    table_insert(market_items, {price = {{'uranium-ore', 5}}, offer = {type = 'give-item', item = 'coin', count = 1}})
    table_insert(market_items, {price = {{'copper-cable', 12}}, offer = {type = 'give-item', item = 'coin', count = 1}})
    table_insert(market_items, {price = {{'iron-gear-wheel', 3}}, offer = {type = 'give-item', item = 'coin', count = 1}})
    table_insert(market_items, {price = {{'iron-stick', 12}}, offer = {type = 'give-item', item = 'coin', count = 1}})
    table_insert(market_items, {price = {{'empty-barrel', 1}}, offer = {type = 'give-item', item = 'coin', count = 1}})

    for _, item in pairs(market_items) do
        market.add_market_item(item)
    end
end

local function refresh_offers(event)
    local ffatable = Table.get_table()
    local market = event.entity or event.market
    if not market then
        return
    end
    if not market.valid then
        return
    end
    if market.name ~= 'market' then
        return
    end
    local town_center = ffatable.town_centers[market.force.name]
    if not town_center then
        return
    end
    clear_offers(market)
    set_offers(town_center)
end

local function offer_purchased(event)
    local ffatable = Table.get_table()
    local player = game.players[event.player_index]
    local market = event.market
    local offer_index = event.offer_index
    local count = event.count
    if not upgrade_functions[offer_index] then
        return
    end

    local town_center = ffatable.town_centers[market.force.name]
    if not town_center then
        return
    end

    upgrade_functions[offer_index](town_center, player)

    if count > 1 then
        local offers = market.get_market_items()
        local price = offers[offer_index].price[1].amount
        player.insert({name = 'coin', count = price * (count - 1)})
    end
end

local function on_gui_opened(event)
    local gui_type = event.gui_type
    if gui_type ~= defines.gui_type.entity then
        return
    end
    local entity = event.entity
    if entity == nil or not entity.valid then
        return
    end
    if entity.type == 'market' then
        refresh_offers(event)
    end
end

local function on_market_item_purchased(event)
    offer_purchased(event)
    refresh_offers(event)
end

local function inside(pos, area)
    return pos.x >= area.left_top.x and pos.x <= area.right_bottom.x and pos.y >= area.left_top.y and pos.y <= area.right_bottom.y
end

local function on_tick(_)
    local ffatable = Table.get_table()
    if not ffatable.town_centers then
        return
    end
    local items = {
        'burner-inserter',
        'inserter',
        'long-handed-inserter',
        'fast-inserter',
        'filter-inserter',
        'stack-inserter',
        'stack-filter-inserter',
        'loader',
        'fast-loader',
        'express-loader'
    }
    for _, town_center in pairs(ffatable.town_centers) do
        local market = town_center.market
        local offers = market.get_market_items()
        if offers == nil then
            set_offers(town_center)
        end
        local s = market.surface
        local force = market.force
        -- get the bounding box for the market
        local bb = market.bounding_box
        local area = {left_top = {bb.left_top.x - 2, bb.left_top.y - 2}, right_bottom = {bb.right_bottom.x + 2, bb.right_bottom.y + 2}}
        local entities = s.find_entities_filtered({area = area, name = items})
        for _, e in pairs(entities) do
            if e.name ~= 'loader' and e.name ~= 'fast-loader' and e.name ~= 'express-loader' then
                local ppos = e.pickup_position
                local dpos = e.drop_position
                -- pulling an item from the market
                if inside(ppos, bb) and e.drop_target then
                    local stack = e.held_stack
                    local spos = e.held_stack_position
                    if inside(spos, bb) then
                        local filter
                        local filter_mode = e.inserter_filter_mode
                        if filter_mode ~= nil then
                            for i = 1, e.filter_slot_count do
                                if e.get_filter(i) ~= nil then
                                    filter = e.get_filter(i)
                                    break
                                end
                            end
                        end
                        if (filter_mode == 'whitelist' and filter == 'coin') or (filter_mode == 'blacklist' and filter == nil) or (filter_mode == nil) then
                            if stack.valid and town_center.coin_balance > 0 then
                                -- pull coins
                                stack.set_stack({name = 'coin', count = 1})
                                town_center.coin_balance = town_center.coin_balance - 1
                                Town_center.update_coin_balance(force)
                            end
                        else
                            if filter_mode == 'whitelist' and filter ~= nil and stack.valid then
                                -- purchased and pull items if output buffer is empty
                                -- buffer the output in a item buffer since the stack might be too small
                                -- output items are shared among the output
                                for _, trade in ipairs(offers) do
                                    local type = trade.offer.type
                                    local item = trade.offer.item
                                    local count = trade.offer.count or 1
                                    local cost = trade.price[1].amount
                                    if type == 'give-item' and item == filter then
                                        if town_center.output_buffer[item] == nil then
                                            town_center.output_buffer[item] = 0
                                        end
                                        if town_center.output_buffer[item] == 0 then
                                            -- fill buffer
                                            if town_center.coin_balance >= cost then
                                                town_center.coin_balance = town_center.coin_balance - cost
                                                Town_center.update_coin_balance(force)
                                                town_center.output_buffer[item] = town_center.output_buffer[item] + count
                                            --log("output_buffer[" .. item .. "] = " .. town_center.output_buffer[item])
                                            end
                                        end
                                        if town_center.output_buffer[item] > 0 and not stack.valid_for_read then
                                            -- output the item
                                            local amount = 1
                                            if stack.can_set_stack({name = item, count = amount}) then
                                                town_center.output_buffer[item] = town_center.output_buffer[item] - amount
                                                stack.set_stack({name = item, count = amount})
                                            --log("output_buffer[" .. item .. "] = " .. town_center.output_buffer[item])
                                            end
                                        end
                                        break
                                    end
                                end
                            end
                        end
                    end
                end
                -- pushing an item to the market (coins or scrap)
                if e.pickup_target and inside(dpos, bb) then
                    local stack = e.held_stack
                    local spos = e.held_stack_position
                    if stack.valid_for_read and inside(spos, bb) then
                        local name = stack.name
                        local amount = stack.count
                        if name == 'coin' then
                            -- push coins
                            e.remove_item(stack)
                            town_center.coin_balance = town_center.coin_balance + amount
                            Town_center.update_coin_balance(force)
                        else
                            -- push items to turn into coin
                            for _, trade in ipairs(offers) do
                                local type = trade.offer.type
                                local item = trade.price[1].name
                                local count = trade.price[1].amount
                                local cost = trade.offer.count
                                if type == 'give-item' and name == item and item ~= 'coin' then
                                    e.remove_item(stack)
                                    -- buffer the input in an item buffer that can be sold
                                    if town_center.input_buffer[item] == nil then
                                        town_center.input_buffer[item] = 0
                                    end
                                    town_center.input_buffer[item] = town_center.input_buffer[item] + amount
                                    --log("input_buffer[" .. item .. "] = " .. town_center.input_buffer[item])
                                    if town_center.input_buffer[item] >= count then
                                        town_center.input_buffer[item] = town_center.input_buffer[item] - count
                                        town_center.coin_balance = town_center.coin_balance + cost
                                        Town_center.update_coin_balance(force)
                                    --log("input_buffer[" .. item .. "] = " .. town_center.input_buffer[item])
                                    end
                                end
                            end
                        end
                    end
                end
            end
        end
    end
end

local Event = require 'utils.event'
Event.add(defines.events.on_tick, on_tick)
Event.add(defines.events.on_gui_opened, on_gui_opened)
Event.add(defines.events.on_market_item_purchased, on_market_item_purchased)
