local Event = require 'utils.event'
local Public = require 'maps.mountain_fortress_v3.table'
local Collapse = require 'modules.collapse'
local RPG = require 'modules.rpg.main'
local WD = require 'modules.wave_defense.table'
local Alert = require 'utils.alert'
local Task = require 'utils.task_token'
local Color = require 'utils.color_presets'
local Session = require 'utils.datastore.session_data'
local Modifiers = require 'utils.player_modifiers'

local floor = math.floor
local abs = math.abs
local random = math.random
local sub = string.sub
local sqrt = math.sqrt
local zone_settings = Public.zone_settings

local clear_breach_text_and_render = function ()
    local beam1 = Public.get('zone1_beam1')
    if beam1 and beam1.valid then
        beam1.destroy()
    end
    local beam2 = Public.get('zone1_beam2')
    if beam2 and beam2.valid then
        beam2.destroy()
    end
    local beam3 = Public.get('zone1_beam3')
    if beam3 and beam3.valid then
        beam3.destroy()
    end
    local zone1_text1 = Public.get('zone1_text1')
    if zone1_text1 and zone1_text1.valid then
        Public.get('zone1_text1').text = 'Collapse has begun!'
    end
    local zone1_text2 = Public.get('zone1_text2')
    if zone1_text2 and zone1_text2.valid then
        zone1_text2.text = 'Collapse has begun!'
    end
    local zone1_text3 = Public.get('zone1_text3')
    if zone1_text3 and zone1_text3.valid then
        zone1_text3.text = 'Collapse has begun!'
    end
end

local damage_indicator_token =
    Task.register(
        function (event)
            local player_index = event.player_index
            local player = game.get_player(player_index)
            if not player or not player.valid then
                return
            end
            if player.character then
                player.character.surface.create_entity({ name = 'water-splash', position = player.character.position })
            end
            return true
        end, true
    )

local increase_movement_speed_token =
    Task.register(
        function (event)
            local player_index = event.player_index
            local player = game.get_player(player_index)
            if not player or not player.valid then
                return
            end
            Modifiers.remove_single_modifier(player, 'character_running_speed_modifier', 'breached_wall')
        end, true
    )

local collapse_message =
    Task.register(
        function (data)
            local pos = data.position
            local message = ({ 'breached_wall.collapse_start' })
            local collapse_position =
            {
                position = pos
            }
            Alert.alert_all_players_location(collapse_position, message)
        end
    )

local driving_state_changed_token =
    Task.register(
        function (event)
            local player_index = event.player_index
            local player = game.get_player(player_index)
            if not player or not player.valid then
                return
            end

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

            local s = Public.get('validate_spider')
            if entity.name == 'spidertron' then
                if player.driving then
                    if not s[player.index] then
                        s[player.index] = entity
                    end
                else
                    if s[player.index] then
                        s[player.index] = nil
                    end
                end
            end
        end
    )

local spidertron_unlocked =
    Task.register(
        function (event)
            if event then
                local message = ({ 'breached_wall.spidertron_unlocked' })
                if event.bw then
                    message = ({ 'breached_wall.spidertron_unlocked_bw' })
                end
                Alert.alert_all_players(30, message, nil, 'achievement/tech-maniac', 0.1)
            end
        end
    )

local first_player_to_zone =
    Task.register(
        function (data)
            local player = data.player
            if not player or not player.valid then
                return
            end
            local breached_wall = data.breached_wall
            local message = ({ 'breached_wall.first_to_reach', player.name, breached_wall })
            Alert.alert_all_players(10, message)
            Public.shuffle_prices()
        end
    )

local artillery_warning =
    Task.register(
        function ()
            local message = ({ 'breached_wall.artillery_warning' })
            Alert.alert_all_players(10, message)
        end
    )

local breach_wall_warning_teleport = function (player, check_trusted)
    if not player or not player.valid then
        return
    end

    local wave_number = WD.get('wave_number')
    if wave_number >= 200 then
        return false
    end

    if not check_trusted then
        local message = ({ 'breached_wall.warning_teleport', player.name })
        Alert.alert_all_players(40, message)
    else
        local message = ({ 'breached_wall.warning_not_trusted_teleport', player.name })
        Alert.alert_all_players(40, message)
    end
    local pos = player.physical_surface.find_non_colliding_position('character', player.force.get_spawn_position(player.physical_surface), 3, 0)
    if pos then
        player.teleport(pos, player.physical_surface)
    else
        pos = game.forces.player.get_spawn_position(player.physical_surface)
        player.teleport(pos, player.physical_surface)
    end
    return true
end

local breach_wall_enforced = function (player)
    if not player or not player.valid then
        return
    end

    local wave_number = WD.get('wave_number')
    if wave_number >= 200 then
        return false
    end

    local surface = player.physical_surface
    local position = player.physical_position
    local locomotive = Public.get('locomotive')
    if not locomotive or not locomotive.valid then
        return
    end

    local adjusted_zones = Public.get('adjusted_zones')

    local message = ({ 'breached_wall.warning_teleport_enforced', player.name })
    Alert.alert_all_players(40, message)
    if adjusted_zones.reversed then
        player.teleport({ position.x, position.y - 10 }, surface)
    else
        player.teleport({ position.x, position.y + 10 }, surface)
    end
    Modifiers.update_single_modifier(player, 'character_running_speed_modifier', 'breached_wall', -0.85)
    Task.set_duration_task(15, 200, damage_indicator_token, { player_index = player.index })
    if player.driving then
        player.driving = false
    end
    if player.character then
        player.character.health = player.character.health - (player.character.max_health * 0.50)
        player.character.surface.create_entity({ name = 'water-splash', position = position })
        if player.character.health <= 0 then
            player.character.die('enemy')
        end
    end
    Task.set_timeout_in_ticks(200, increase_movement_speed_token, { player_index = player.index })
    return true
end

local spidertron_too_far =
    Task.register(
        function (data)
            local player = data.player
            local message = ({ 'breached_wall.cheating_through', player.name })
            Alert.alert_all_players(30, message)
        end
    )

local check_distance_between_player_and_locomotive = function (player)
    local surface = player.physical_surface
    local position = player.physical_position
    local locomotive = Public.get('locomotive')
    if not locomotive or not locomotive.valid then
        return
    end

    local adjusted_zones = Public.get('adjusted_zones')

    local gap_between_locomotive = Public.get('gap_between_locomotive')
    gap_between_locomotive.highest_pos = locomotive.position
    gap_between_locomotive = Public.get('gap_between_locomotive')

    local p_y = abs(position.y)
    if p_y < 300 then
        return
    end
    local t_y = abs(gap_between_locomotive.highest_pos.y)

    local locomotive_distance_too_far = p_y - t_y > gap_between_locomotive.neg_gap

    if locomotive_distance_too_far then
        if adjusted_zones.reversed then
            player.teleport({ position.x, t_y + gap_between_locomotive.neg_gap - 4 }, surface)
        else
            player.teleport({ position.x, (t_y + gap_between_locomotive.neg_gap - 4) * -1 }, surface)
        end

        player.print(({ 'breached_wall.hinder' }), { color = Color.warning })
        if player.driving then
            player.driving = false
        end
        if player.character then
            player.character.health = player.character.health - (player.character.max_health * 0.15)
            player.character.surface.create_entity({ name = 'water-splash', position = position })
            if player.character.health <= 0 then
                player.character.die('enemy')
            end
        end
    end
end

local check_tiles = function (player)
    local p = player.physical_position
    local tile = player.physical_surface.get_tile(p.x, p.y)
    if tile and tile.name == 'red-refined-concrete' then
        local rpg_t = RPG.get_value_from_player(player.index)
        if rpg_t and rpg_t.volcanic_zone then
            if random(1, 32) == 1 then
                Public.buried_enemies.buried_biter(player.physical_surface, p)
            end
        end
    end
    if tile and tile.name == 'hazard-concrete-right' then
        local rpg_t = RPG.get_value_from_player(player.index)
        if rpg_t and rpg_t.tech_zone then
            if random(1, 32) == 1 then
                Public.buried_enemies.buried_tech(player.physical_surface, p)
            end
        end
    end
end

local compare_player_pos = function (player)
    local p = player.physical_position
    local index = player.index
    local adjusted_zones = Public.get('adjusted_zones')
    if not adjusted_zones.size then
        return
    end

    local zone = floor((abs(p.y / zone_settings.zone_depth)) % adjusted_zones.size) + 1
    local rpg_t = RPG.get_value_from_player(index)

    if adjusted_zones.scrap[zone] then
        if rpg_t and not rpg_t.scrap_zone then
            rpg_t.scrap_zone = true
        end
    else
        if rpg_t and rpg_t.scrap_zone then
            rpg_t.scrap_zone = false
        end
    end

    if adjusted_zones.forest[zone] then
        if rpg_t and not rpg_t.forest_zone then
            rpg_t.forest_zone = true
        end
    else
        if rpg_t and rpg_t.forest_zone then
            rpg_t.forest_zone = false
        end
    end

    if adjusted_zones.crystal[zone] then
        if rpg_t and not rpg_t.crystal_zone then
            rpg_t.crystal_zone = true
        end
    else
        if rpg_t and rpg_t.crystal_zone then
            rpg_t.crystal_zone = false
        end
    end

    if adjusted_zones.frostbite[zone] then
        if rpg_t and not rpg_t.frostbite_zone then
            rpg_t.frostbite_zone = true
        end
    else
        if rpg_t and rpg_t.frostbite_zone then
            rpg_t.frostbite_zone = false
        end
    end

    if adjusted_zones.volcanic[zone] then
        if rpg_t and not rpg_t.volcanic_zone then
            rpg_t.volcanic_zone = true
        end
    else
        if rpg_t and rpg_t.volcanic_zone then
            rpg_t.volcanic_zone = false
        end
    end

    if adjusted_zones.tech[zone] then
        if rpg_t and not rpg_t.tech_zone then
            rpg_t.tech_zone = true
        end
    else
        if rpg_t and rpg_t.tech_zone then
            rpg_t.tech_zone = false
        end
    end
end

local compare_player_and_train = function (player, entity)
    if not player.driving then
        return
    end

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

    local position = player.physical_position
    local locomotive = Public.get('locomotive')
    if not locomotive or not locomotive.valid then
        return
    end

    local gap_between_zones = Public.get('gap_between_zones')
    gap_between_zones.highest_pos = locomotive.position
    gap_between_zones = Public.get('gap_between_zones')

    local c_y = abs(position.y)
    local t_y = abs(gap_between_zones.highest_pos.y)

    local spidertron_warning_position = gap_between_zones.neg_gap + 50
    local locomotive_distance_too_far = c_y - t_y > spidertron_warning_position
    local spidertron_warning_position_pre_warning = spidertron_warning_position - 100
    local locomotive_distance_too_far_pre_warning = c_y - t_y > spidertron_warning_position_pre_warning

    local color = Color.yellow
    if locomotive_distance_too_far_pre_warning and not locomotive_distance_too_far then
        local msg = 'Warning! You are getting too far away from the train!'
        player.create_local_flying_text(
            {
                position = position,
                text = msg,
                color = color,
            }
        )
        player.print(msg, { color = color })
    elseif locomotive_distance_too_far then
        local msg = 'Warning! You are too far away from the train! TURN BACK!'
        player.create_local_flying_text(
            {
                position = position,
                text = msg,
                color = color,
            }
        )
        player.print(msg, { color = color })
        if entity.health then
            entity.health = entity.health - 500
            if entity.health <= 0 then
                entity.die('enemy')
                Task.set_timeout_in_ticks(30, spidertron_too_far, { player = player })
                return
            end
        end
    end
end

local function distance(player)
    local index = player.index
    local bonus = RPG.get_value_from_player(index, 'bonus') or 1
    local rpg_extra = RPG.get('rpg_extra')
    local breached_wall = Public.get('breached_wall')
    local bonus_xp_on_join = Public.get('bonus_xp_on_join')
    local enable_arties = Public.get('enable_arties')

    local p = player.physical_position

    local validate_spider = Public.get('validate_spider')
    if validate_spider[index] then
        local e = validate_spider[index]
        if not (e and e.valid) then
            validate_spider[index] = nil
        end
        compare_player_and_train(player, validate_spider[index])
    end

    compare_player_pos(player)

    check_tiles(player)

    local distance_to_center = floor(sqrt(p.y ^ 2))
    local adjusted_zones = Public.get('adjusted_zones')
    if adjusted_zones.reversed then
        if distance_to_center < zone_settings.zone_depth * bonus + 32 then
            return
        end
    else
        if distance_to_center < zone_settings.zone_depth * bonus - 20 then
            return
        end
    end

    local breach_wall_warning = Public.get('breach_wall_warning')
    local collapse_started = Public.get('collapse_started')
    local block_non_trusted_trigger_collapse = Public.get('block_non_trusted_trigger_collapse')

    local max = zone_settings.zone_depth * bonus
    local breach_max = zone_settings.zone_depth * breached_wall
    local breach_max_times = distance_to_center >= breach_max
    local max_times = distance_to_center >= max

    if max_times then
        local wave_number = WD.get('wave_number')
        local enforce_wave_200_before_collapse = Public.get('enforce_wave_200_before_collapse')
        if enforce_wave_200_before_collapse then
            if wave_number < 200 then
                breach_wall_enforced(player)
                return
            end
        else
            if block_non_trusted_trigger_collapse and not Session.get_trusted_player(player) and not collapse_started then
                if breach_wall_warning_teleport(player, true) then
                    return
                end
            end
            if not breach_wall_warning then
                Public.set('breach_wall_warning', true)
                breach_wall_warning_teleport(player)
                return
            end
        end
        if breach_max_times then
            local placed_trains_in_zone = Public.get('placed_trains_in_zone')
            local biters = Public.get('biters')
            rpg_extra.breached_walls = rpg_extra.breached_walls + 1
            rpg_extra.reward_new_players = bonus_xp_on_join * rpg_extra.breached_walls
            Public.set('breached_wall', breached_wall + 1)
            biters.amount = 0
            -- local random_seed = Public.get('random_seed')
            -- Public.set('random_seed', random_seed + (breached_wall + 1 * 2))
            placed_trains_in_zone.randomized = false
            Public.enemy_weapon_damage()
            local spidertron_unlocked_enabled = Public.get('spidertron_unlocked_enabled')
            if Public.get('breached_wall') >= Public.get('spidertron_unlocked_at_zone') and not spidertron_unlocked_enabled then
                local get_player_data = Public.get_player_data(player)
                Public.set('spidertron_unlocked_enabled', true)
                local main_market_items = Public.get('main_market_items')
                if not main_market_items['spidertron'] then
                    local bw = Public.get('bw')
                    local spider_tooltip = 'BiterStunner 9000'
                    local rng
                    if bw then
                        rng = random(30000, 80000)
                        spider_tooltip = spider_tooltip .. ' (Exclusive sale!)'
                    else
                        rng = random(70000, 120000)
                    end
                    main_market_items['spidertron'] =
                    {
                        stack = 1,
                        value = 'coin',
                        price = rng * (get_player_data and get_player_data.quality or 1),
                        tooltip = spider_tooltip,
                        upgrade = false,
                        static = true
                    }
                    Task.set_timeout_in_ticks(150, spidertron_unlocked, { bw = bw })
                end
            end

            local data =
            {
                player = player,
                breached_wall = breached_wall
            }
            Task.set_timeout_in_ticks(360, first_player_to_zone, data)
            if breached_wall == 5 then
                if enable_arties == 6 then
                    Task.set_timeout_in_ticks(360, artillery_warning)
                end
            end
        end

        if not Collapse.has_collapse_started() then
            clear_breach_text_and_render()
            Public.set('collapse_started', true)
            Collapse.start_now(true)
            local data =
            {
                position = Collapse.get_position()
            }
            Task.set_timeout_in_ticks(550, collapse_message, data)
        end

        if Collapse.has_collapse_started() then
            clear_breach_text_and_render()
        end

        RPG.set_value_to_player(index, 'bonus', bonus + 1)

        RPG.gain_xp(player, bonus_xp_on_join * bonus)
        return
    end
end

local function on_player_changed_position(event)
    local final_battle = Public.get('final_battle')
    if final_battle then
        return
    end

    local current_task = Public.get('current_task')
    if not current_task.done then
        return
    end

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

    if player.controller_type == defines.controllers.editor then
        return
    end

    if player.controller_type == defines.controllers.remote then
        return
    end

    if player.controller_type == defines.controllers.spectator then
        return
    end
    local surface_name = player.physical_surface.name

    if sub(surface_name, 0, #current_task.starting_planet) ~= current_task.starting_planet then
        return
    end

    local position = player.physical_position

    if not (position.x < Public.zone_settings.zone_width / 2 and position.x >= -Public.zone_settings.zone_width / 2) then
        return
    end

    if position.y > -100 and position.y < -100 then
        return
    end

    if position.y > 100 and position.y < 100 then
        return
    end

    check_distance_between_player_and_locomotive(player)

    distance(player)
end

local function on_player_driving_changed_state(event)
    local final_battle = Public.get('final_battle')
    if final_battle then
        return
    end

    local current_task = Public.get('current_task')
    if not current_task.done then
        return
    end

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

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

    Task.set_timeout_in_ticks(15, driving_state_changed_token, { player_index = player.index, entity = entity })
end

Event.add(defines.events.on_player_changed_position, on_player_changed_position)
Event.add(defines.events.on_player_driving_changed_state, on_player_driving_changed_state)

return Public
