local Event = require 'utils.event'
local Market = require 'maps.mountain_fortress_v3.basic_markets'
local LocomotiveMarket = require 'maps.mountain_fortress_v3.locomotive.market'
local ICW = require 'maps.mountain_fortress_v3.icw.main'
local WPT = require 'maps.mountain_fortress_v3.table'
local ICFunctions = require 'maps.mountain_fortress_v3.ic.functions'
local Session = require 'utils.datastore.session_data'
local Difficulty = require 'modules.difficulty_vote_by_amount'
local RPG = require 'modules.rpg.main'
local Gui = require 'utils.gui'
local Alert = require 'utils.alert'
local Math2D = require 'math2d'
local PermissionGroups = require 'maps.mountain_fortress_v3.locomotive.permission_groups'

local Public = {}

local rpg_main_frame = RPG.main_frame_name
local random = math.random
local floor = math.floor
local round = math.round

local clear_items_upon_surface_entry = {
    ['entity-ghost'] = true,
    ['small-electric-pole'] = true,
    ['medium-electric-pole'] = true,
    ['big-electric-pole'] = true,
    ['substation'] = true
}

local function add_random_loot_to_main_market(rarity)
    local main_market_items = WPT.get('main_market_items')
    local items = Market.get_random_item(rarity, true, false)
    if not items then
        return false
    end

    local types = game.item_prototypes

    for k, v in pairs(main_market_items) do
        if not v.static then
            main_market_items[k] = nil
        end
    end

    for k, v in pairs(items) do
        local price = v.price[1][2] + random(1, 15) * rarity
        local value = v.price[1][1]
        local stack = 1
        if v.offer.item == 'coin' then
            price = v.price[1][2]
            stack = v.offer.count
            if not stack then
                stack = v.price[1][2]
            end
        end

        if not main_market_items[v.offer.item] then
            main_market_items[v.offer.item] = {
                stack = stack,
                value = value,
                price = price,
                tooltip = types[v.offer.item].localised_name,
                upgrade = false
            }
        end
    end
end

local function validate_player(player)
    if not player then
        return false
    end
    if not player.valid then
        return false
    end
    if not player.character then
        return false
    end
    if not player.connected then
        return false
    end
    if not game.get_player(player.name) then
        return false
    end
    return true
end

local function property_boost(data)
    local xp_floating_text_color = {r = 188, g = 201, b = 63}
    local visuals_delay = 1800
    local loco_surface = WPT.get('loco_surface')
    if not (loco_surface and loco_surface.valid) then
        return
    end
    local locomotive_xp_aura = WPT.get('locomotive_xp_aura')
    local locomotive = WPT.get('locomotive')
    local xp_points = WPT.get('xp_points')
    local aura = locomotive_xp_aura
    local rpg = data.rpg
    local loco = locomotive.position
    local area = {
        left_top = {x = loco.x - aura, y = loco.y - aura},
        right_bottom = {x = loco.x + aura, y = loco.y + aura}
    }

    for _, player in pairs(game.connected_players) do
        if not validate_player(player) then
            return
        end
        if player.afk_time < 200 then
            if Math2D.bounding_box.contains_point(area, player.position) or player.surface.index == loco_surface.index then
                if player.surface.index == loco_surface.index then
                    PermissionGroups.add_player_to_permission_group(player, 'limited')
                elseif ICFunctions.get_player_surface(player) then
                    return PermissionGroups.add_player_to_permission_group(player, 'limited')
                else
                    PermissionGroups.add_player_to_permission_group(player, 'near_locomotive')
                end

                local pos = player.position
                RPG.gain_xp(player, 0.5 * (rpg[player.index].bonus + xp_points))

                player.create_local_flying_text {
                    text = '+' .. '',
                    position = {x = pos.x, y = pos.y - 2},
                    color = xp_floating_text_color,
                    time_to_live = 60,
                    speed = 3
                }
                rpg[player.index].xp_since_last_floaty_text = 0
                rpg[player.index].last_floaty_text = game.tick + visuals_delay
                if player.gui.screen[rpg_main_frame] then
                    local f = player.gui.screen[rpg_main_frame]
                    local d = Gui.get_data(f)
                    if d.exp_gui and d.exp_gui.valid then
                        d.exp_gui.caption = floor(rpg[player.index].xp)
                    end
                end
            else
                local active_surface_index = WPT.get('active_surface_index')
                local surface = game.surfaces[active_surface_index]
                if surface and surface.valid then
                    if player.surface.index == surface.index then
                        PermissionGroups.add_player_to_permission_group(player, 'main_surface')
                    end
                end
            end
        end
    end
end

local function is_around_train(data)
    local entity = data.entity
    local aura = data.aura + 20
    local loco = data.locomotive.position
    local area = {
        left_top = {x = loco.x - aura, y = loco.y - aura},
        right_bottom = {x = loco.x + aura, y = loco.y + aura}
    }
    local pos = entity.position

    if Math2D.bounding_box.contains_point(area, pos) then
        return true
    end
    return false
end

local function fish_tag()
    local locomotive_cargo = WPT.get('locomotive_cargo')
    if not (locomotive_cargo and locomotive_cargo.valid) then
        return
    end
    if not (locomotive_cargo.surface and locomotive_cargo.surface.valid) then
        return
    end

    local locomotive_tag = WPT.get('locomotive_tag')

    if locomotive_tag then
        if locomotive_tag.valid then
            if locomotive_tag.position.x == locomotive_cargo.position.x and locomotive_tag.position.y == locomotive_cargo.position.y then
                return
            end
            locomotive_tag.destroy()
        end
    end
    WPT.set(
        'locomotive_tag',
        locomotive_cargo.force.add_chart_tag(
            locomotive_cargo.surface,
            {
                icon = {type = 'item', name = 'raw-fish'},
                position = locomotive_cargo.position,
                text = ' '
            }
        )
    )
end

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

    local position = locomotive.surface.find_non_colliding_position('stone-furnace', locomotive.position, 16, 2)
    if not position then
        return
    end
    game.forces.player.set_spawn_position({x = position.x, y = position.y}, locomotive.surface)
end

local function refill_fish()
    local locomotive_cargo = WPT.get('locomotive_cargo')
    if not locomotive_cargo then
        return
    end
    if not locomotive_cargo.valid then
        return
    end
    locomotive_cargo.get_inventory(defines.inventory.cargo_wagon).insert({name = 'raw-fish', count = random(2, 5)})
end

local function set_carriages()
    local locomotive = WPT.get('locomotive')
    if not locomotive or not locomotive.valid then
        return
    end

    if not locomotive.train then
        return
    end

    local carriages = locomotive.train.carriages
    local t = {}
    for i = 1, #carriages do
        local e = carriages[i]
        if (e and e.valid) then
            t[e.unit_number] = true
        end
    end

    WPT.set('carriages_numbers', t)
    WPT.set('carriages', locomotive.train.carriages)
end

local function set_locomotive_health()
    local locomotive_health = WPT.get('locomotive_health')
    local locomotive_max_health = WPT.get('locomotive_max_health')
    local locomotive = WPT.get('locomotive')

    local function check_health()
        local m = locomotive_health / locomotive_max_health
        if locomotive_health > locomotive_max_health then
            WPT.set('locomotive_health', locomotive_max_health)
        end
        rendering.set_text(WPT.get('health_text'), 'HP: ' .. round(locomotive_health) .. ' / ' .. round(locomotive_max_health))
        local carriages = WPT.get('carriages')
        if carriages then
            for i = 1, #carriages do
                local entity = carriages[i]
                if not (entity and entity.valid) then
                    return
                end
                local cargo_health = 600
                if entity.type == 'locomotive' then
                    entity.health = 1000 * m
                else
                    entity.health = cargo_health * m
                end
            end
        end
    end

    if not (locomotive and locomotive.valid) then
        return
    end

    check_health()
end

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

    local icw_table = ICW.get_table()
    local icw_locomotive = WPT.get('icw_locomotive')
    local loco_surface = icw_locomotive.surface
    local unit_surface = locomotive.unit_number
    local locomotive_surface = game.surfaces[icw_table.wagons[unit_surface].surface.index]
    if loco_surface.valid then
        WPT.set('loco_surface', locomotive_surface)
    end
end

local function on_research_finished(event)
    local research = event.research
    if not research then
        return
    end

    local name = research.name

    if name == 'discharge-defense-equipment' then
        local message = ({'locomotive.discharge_unlocked'})
        Alert.alert_all_players(15, message, nil, 'achievement/tech-maniac', 0.1)
    end
    if name == 'artillery' then
        local message = ({'locomotive.artillery_unlocked'})
        Alert.alert_all_players(15, message, nil, 'achievement/tech-maniac', 0.1)
    end

    local locomotive = WPT.get('locomotive')
    if not locomotive or not locomotive.valid then
        return
    end

    local market_announce = WPT.get('market_announce')
    if market_announce > game.tick then
        return
    end

    local breached_wall = WPT.get('breached_wall')
    add_random_loot_to_main_market(breached_wall)
    local message = ({'locomotive.new_items_at_market'})
    Alert.alert_all_players(5, message, nil, 'achievement/tech-maniac', 0.1)
    LocomotiveMarket.refresh_gui()
end

local function on_player_changed_surface(event)
    local player = game.players[event.player_index]
    if not validate_player(player) then
        return
    end

    local active_surface = WPT.get('active_surface_index')
    local surface = game.surfaces[active_surface]
    if not surface or not surface.valid then
        return
    end

    local itemGhost = player.cursor_ghost
    if itemGhost then
        player.cursor_ghost = nil
    end

    local item = player.cursor_stack
    if item and item.valid_for_read then
        local name = item.name
        if clear_items_upon_surface_entry[name] then
            player.cursor_stack.clear()
        end
    end

    if player.surface.name == 'nauvis' then
        local pos = surface.find_non_colliding_position('character', game.forces.player.get_spawn_position(surface), 3, 0, 5)
        if pos then
            player.teleport(pos, surface)
        else
            pos = game.forces.player.get_spawn_position(surface)
            player.teleport(pos, surface)
        end
    end

    local locomotive_surface = WPT.get('loco_surface')

    if locomotive_surface and locomotive_surface.valid and player.surface.index == locomotive_surface.index then
        return PermissionGroups.add_player_to_permission_group(player, 'limited')
    elseif ICFunctions.get_player_surface(player) then
        return PermissionGroups.add_player_to_permission_group(player, 'limited')
    elseif player.surface.index == surface.index then
        return PermissionGroups.add_player_to_permission_group(player, 'main_surface')
    end
end

local function on_player_driving_changed_state(event)
    local player = game.players[event.player_index]
    if not player or not player.valid then
        return
    end
    local entity = event.entity
    if not entity or not entity.valid then
        return
    end

    local trusted = Session.get_trusted_table()
    if #trusted == 0 then
        return
    end

    local locomotive = WPT.get('locomotive')
    if not locomotive or not locomotive.valid then
        return
    end

    if entity.unit_number == locomotive.unit_number then
        if not trusted[player.name] then
            if player.character and player.character.valid and player.character.driving then
                player.character.driving = false
            end
        end
    end
end

function Public.boost_players_around_train()
    local rpg = RPG.get('rpg_t')
    local active_surface_index = WPT.get('active_surface_index')
    if not active_surface_index then
        return
    end
    local locomotive = WPT.get('locomotive')
    if not (locomotive and locomotive.valid) then
        return
    end
    local surface = game.surfaces[active_surface_index]
    local icw_table = ICW.get_table()
    local unit_surface = locomotive.unit_number
    local locomotive_surface = game.surfaces[icw_table.wagons[unit_surface].surface.index]

    local data = {
        surface = surface,
        locomotive_surface = locomotive_surface,
        rpg = rpg
    }
    property_boost(data)
end

function Public.is_around_train(entity)
    local locomotive = WPT.get('locomotive')
    local active_surface_index = WPT.get('active_surface_index')

    if not active_surface_index then
        return false
    end
    if not locomotive then
        return false
    end
    if not locomotive.valid then
        return false
    end

    if not entity or not entity.valid then
        return false
    end

    local surface = game.surfaces[active_surface_index]
    local aura = WPT.get('locomotive_xp_aura')

    local data = {
        locomotive = locomotive,
        surface = surface,
        entity = entity,
        aura = aura
    }

    local success = is_around_train(data)
    return success
end

function Public.render_train_hp()
    local active_surface_index = WPT.get('active_surface_index')
    local surface = game.surfaces[active_surface_index]

    local locomotive_health = WPT.get('locomotive_health')
    local locomotive_max_health = WPT.get('locomotive_max_health')
    local locomotive = WPT.get('locomotive')
    local locomotive_xp_aura = WPT.get('locomotive_xp_aura')

    WPT.set().health_text =
        rendering.draw_text {
        text = 'HP: ' .. locomotive_health .. ' / ' .. locomotive_max_health,
        surface = surface,
        target = locomotive,
        target_offset = {0, -4.5},
        color = locomotive.color,
        scale = 1.40,
        font = 'default-game',
        alignment = 'center',
        scale_with_zoom = false
    }

    WPT.set().caption =
        rendering.draw_text {
        text = 'Comfy Choo Choo',
        surface = surface,
        target = locomotive,
        target_offset = {0, -6.25},
        color = locomotive.color,
        scale = 1.80,
        font = 'default-game',
        alignment = 'center',
        scale_with_zoom = false
    }

    WPT.set().circle =
        rendering.draw_circle {
        surface = surface,
        target = locomotive,
        color = locomotive.color,
        filled = false,
        radius = locomotive_xp_aura,
        only_in_alt_mode = true
    }
end

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

    local active_surface_index = WPT.get('active_surface_index')
    local active_surface = game.surfaces[active_surface_index]
    if not active_surface or not active_surface.valid then
        return
    end

    local icw_locomotive = WPT.get('icw_locomotive')
    local surface = icw_locomotive.surface
    if not surface or not surface.valid then
        return
    end

    local total_interior_pollution = surface.get_total_pollution()

    local pollution = surface.get_total_pollution() * (3 / (4 / 3 + 1)) * Difficulty.get().difficulty_vote_value
    active_surface.pollute(locomotive.position, pollution)
    game.pollution_statistics.on_flow('locomotive', pollution - total_interior_pollution)
    surface.clear_pollution()
end

local boost_players = Public.boost_players_around_train
local pollute_area = Public.transfer_pollution

local function tick()
    local ticker = game.tick

    if ticker % 30 == 0 then
        set_locomotive_health()
        validate_index()
        fish_tag()
    end

    if ticker % 120 == 0 then
        -- tp_player()
        boost_players()
    end

    if ticker % 1200 == 0 then
        set_player_spawn()
        refill_fish()
    end

    if ticker % 2500 == 0 then
        pollute_area()
    end
end

Event.on_nth_tick(5, tick)

Event.add(defines.events.on_research_finished, on_research_finished)
Event.add(defines.events.on_player_changed_surface, on_player_changed_surface)
Event.add(defines.events.on_player_driving_changed_state, on_player_driving_changed_state)
Event.add(defines.events.on_train_created, set_carriages)

return Public
