using System;
using System.Collections.Generic;
using System.Globalization;
using System.Threading.Tasks;
using CitizenFX.Core;
using static CitizenFX.Core.Native.API;

namespace RvWorldCleanup.Client
{
    public class WorldCleanupClient : BaseScript
    {
        private static readonly string[] BlockedScenarioTypes =
        {
            "WORLD_HUMAN_AA_COFFEE",
            "WORLD_HUMAN_AA_SMOKE",
            "WORLD_HUMAN_BINOCULARS",
            "WORLD_HUMAN_CLIPBOARD",
            "WORLD_HUMAN_DRINKING",
            "WORLD_HUMAN_GUARD_STAND",
            "WORLD_HUMAN_HANG_OUT_STREET",
            "WORLD_HUMAN_SMOKING",
            "WORLD_HUMAN_STAND_IMPATIENT",
            "PROP_HUMAN_SEAT_BENCH",
            "PROP_HUMAN_SEAT_CHAIR",
            "WORLD_VEHICLE_ATTRACTOR",
            "WORLD_VEHICLE_BICYCLE_BMX",
            "WORLD_VEHICLE_EMPTY",
            "WORLD_VEHICLE_POLICE_CAR",
            "WORLD_VEHICLE_POLICE_NEXT_TO_CAR"
        };

        private CleanupSettings _settings;
        private int _nextConfigRefreshAt;
        private int _nextCleanupAt;

        public WorldCleanupClient()
        {
            _settings = CleanupSettings.Load();
            ApplyPersistentToggles();

            EventHandlers["onClientResourceStart"] += new Action<string>(OnClientResourceStart);
            EventHandlers["onClientResourceStop"] += new Action<string>(OnClientResourceStop);

            Tick += OnTick;
        }

        private void OnClientResourceStart(string resourceName)
        {
            if (resourceName != GetCurrentResourceName())
            {
                return;
            }

            _settings = CleanupSettings.Load();
            ApplyPersistentToggles();

            Debug.WriteLine(string.Format(
                CultureInfo.InvariantCulture,
                "[{0}] Ambient cleanup aktiv. Radius={1:0.##}, Intervall={2}ms",
                GetCurrentResourceName(),
                _settings.ClearRadius,
                _settings.ClearIntervalMs));
        }

        private void OnClientResourceStop(string resourceName)
        {
            if (resourceName != GetCurrentResourceName())
            {
                return;
            }

            RestorePersistentToggles();
        }

        private async Task OnTick()
        {
            var now = GetGameTimer();

            if (now >= _nextConfigRefreshAt)
            {
                _settings = CleanupSettings.Load();
                ApplyPersistentToggles();
                _nextConfigRefreshAt = now + _settings.ConfigRefreshMs;
            }

            if (!_settings.Enabled)
            {
                await Delay(1000);
                return;
            }

            ApplyFrameSuppression();

            if (_settings.DisableScenarioActions)
            {
                ApplyScenarioSuppression();
            }

            if (now >= _nextCleanupAt)
            {
                CleanupNearbyWorld();
                _nextCleanupAt = now + _settings.ClearIntervalMs;
            }

            await Delay(0);
        }

        private void ApplyPersistentToggles()
        {
            SetRandomBoats(!_settings.DisableRandomBoats);
            SetGarbageTrucks(!_settings.DisableGarbageTrucks);

            var allowRandomCops = !_settings.DisableRandomCops;
            SetCreateRandomCops(allowRandomCops);
            SetCreateRandomCopsOnScenarios(allowRandomCops);
            SetCreateRandomCopsNotOnScenarios(allowRandomCops);

            var allowDispatch = !_settings.DisableDispatchServices;

            for (var service = 1; service <= 15; service++)
            {
                EnableDispatchService(service, allowDispatch);
            }

            SetMaxWantedLevel(_settings.DisableDispatchServices ? 0 : 5);
        }

        private void RestorePersistentToggles()
        {
            SetRandomBoats(true);
            SetGarbageTrucks(true);
            SetCreateRandomCops(true);
            SetCreateRandomCopsOnScenarios(true);
            SetCreateRandomCopsNotOnScenarios(true);

            for (var service = 1; service <= 15; service++)
            {
                EnableDispatchService(service, true);
            }

            SetMaxWantedLevel(5);
        }

        private void ApplyFrameSuppression()
        {
            SetPedPopulationBudget(_settings.DisableAmbientPeds ? 0 : 3);
            SetVehiclePopulationBudget(_settings.DisableAmbientVehicles ? 0 : 3);

            SetPedDensityMultiplierThisFrame(_settings.DisableAmbientPeds ? 0.0f : 1.0f);
            SetScenarioPedDensityMultiplierThisFrame(
                _settings.DisableScenarioPeds ? 0.0f : 1.0f,
                _settings.DisableScenarioPeds ? 0.0f : 1.0f);
            SetVehicleDensityMultiplierThisFrame(_settings.DisableAmbientVehicles ? 0.0f : 1.0f);
            SetRandomVehicleDensityMultiplierThisFrame(_settings.DisableAmbientVehicles ? 0.0f : 1.0f);
            SetParkedVehicleDensityMultiplierThisFrame(_settings.DisableParkedVehicles ? 0.0f : 1.0f);
        }

        private void ApplyScenarioSuppression()
        {
            SuppressShockingEventsNextFrame();

            foreach (var scenarioType in BlockedScenarioTypes)
            {
                SetScenarioTypeEnabled(scenarioType, false);
            }
        }

        private void CleanupNearbyWorld()
        {
            var playerPed = PlayerPedId();

            if (playerPed == 0 || !DoesEntityExist(playerPed))
            {
                return;
            }

            var playerCoords = GetEntityCoords(playerPed, true);
            var radius = _settings.ClearRadius;

            if (_settings.DisableAmbientVehicles || _settings.DisableParkedVehicles)
            {
                RemoveVehiclesFromGeneratorsInArea(
                    playerCoords.X - radius,
                    playerCoords.Y - radius,
                    playerCoords.Z - 10.0f,
                    playerCoords.X + radius,
                    playerCoords.Y + radius,
                    playerCoords.Z + 10.0f);
            }

            if (_settings.DeleteExistingPeds)
            {
                CleanupPeds(playerCoords, radius, playerPed);
            }

            if (_settings.DeleteExistingVehicles)
            {
                CleanupVehicles(playerCoords, radius, playerPed);
            }
        }

        private void CleanupPeds(Vector3 origin, float radius, int playerPed)
        {
            var iterator = FindFirstPed(ref playerPed);

            try
            {
                if (iterator == -1)
                {
                    return;
                }

                var ped = playerPed;

                do
                {
                    if (!CanDeletePed(ped, playerPed, origin, radius))
                    {
                        continue;
                    }

                    SetEntityAsMissionEntity(ped, true, true);
                    DeletePed(ref ped);
                }
                while (FindNextPed(iterator, ref ped));
            }
            finally
            {
                EndFindPed(iterator);
            }
        }

        private void CleanupVehicles(Vector3 origin, float radius, int playerPed)
        {
            var vehicle = 0;
            var iterator = FindFirstVehicle(ref vehicle);

            try
            {
                if (iterator == -1)
                {
                    return;
                }

                do
                {
                    if (!CanDeleteVehicle(vehicle, playerPed, origin, radius))
                    {
                        continue;
                    }

                    SetEntityAsMissionEntity(vehicle, true, true);
                    DeleteVehicle(ref vehicle);
                }
                while (FindNextVehicle(iterator, ref vehicle));
            }
            finally
            {
                EndFindVehicle(iterator);
            }
        }

        private bool CanDeletePed(int ped, int playerPed, Vector3 origin, float radius)
        {
            if (ped == 0 || ped == playerPed || !DoesEntityExist(ped))
            {
                return false;
            }

            if (IsPedAPlayer(ped))
            {
                return false;
            }

            if (_settings.PreserveMissionEntities && IsEntityAMissionEntity(ped))
            {
                return false;
            }

            if (_settings.PreserveNetworkedEntities && NetworkGetEntityIsNetworked(ped))
            {
                return false;
            }

            if (IsPedInAnyVehicle(ped, false))
            {
                return false;
            }

            return IsInsideRadius(origin, GetEntityCoords(ped, true), radius);
        }

        private bool CanDeleteVehicle(int vehicle, int playerPed, Vector3 origin, float radius)
        {
            if (vehicle == 0 || !DoesEntityExist(vehicle))
            {
                return false;
            }

            var currentPlayerVehicle = GetVehiclePedIsIn(playerPed, false);

            if (vehicle == currentPlayerVehicle)
            {
                return false;
            }

            if (_settings.PreserveMissionEntities && IsEntityAMissionEntity(vehicle))
            {
                return false;
            }

            if (_settings.PreserveNetworkedEntities && NetworkGetEntityIsNetworked(vehicle))
            {
                return false;
            }

            if (_settings.PreserveOccupiedVehicles && !IsVehicleSeatFree(vehicle, -1))
            {
                return false;
            }

            return IsInsideRadius(origin, GetEntityCoords(vehicle, true), radius);
        }

        private static bool IsInsideRadius(Vector3 origin, Vector3 target, float radius)
        {
            var distanceX = origin.X - target.X;
            var distanceY = origin.Y - target.Y;
            var distanceZ = origin.Z - target.Z;

            return (distanceX * distanceX) + (distanceY * distanceY) + (distanceZ * distanceZ) <= radius * radius;
        }

        private sealed class CleanupSettings
        {
            public bool Enabled { get; private set; }
            public bool DisableAmbientPeds { get; private set; }
            public bool DisableScenarioPeds { get; private set; }
            public bool DisableAmbientVehicles { get; private set; }
            public bool DisableParkedVehicles { get; private set; }
            public bool DisableDispatchServices { get; private set; }
            public bool DisableRandomCops { get; private set; }
            public bool DisableGarbageTrucks { get; private set; }
            public bool DisableRandomBoats { get; private set; }
            public bool DisableScenarioActions { get; private set; }
            public bool DeleteExistingPeds { get; private set; }
            public bool DeleteExistingVehicles { get; private set; }
            public bool PreserveMissionEntities { get; private set; }
            public bool PreserveNetworkedEntities { get; private set; }
            public bool PreserveOccupiedVehicles { get; private set; }
            public float ClearRadius { get; private set; }
            public int ClearIntervalMs { get; private set; }
            public int ConfigRefreshMs { get; private set; }

            public static CleanupSettings Load()
            {
                return new CleanupSettings
                {
                    Enabled = ReadBool("rv_cleanup_enable", true),
                    DisableAmbientPeds = ReadBool("rv_cleanup_disable_ambient_peds", true),
                    DisableScenarioPeds = ReadBool("rv_cleanup_disable_scenario_peds", true),
                    DisableAmbientVehicles = ReadBool("rv_cleanup_disable_ambient_vehicles", true),
                    DisableParkedVehicles = ReadBool("rv_cleanup_disable_parked_vehicles", true),
                    DisableDispatchServices = ReadBool("rv_cleanup_disable_dispatch_services", true),
                    DisableRandomCops = ReadBool("rv_cleanup_disable_random_cops", true),
                    DisableGarbageTrucks = ReadBool("rv_cleanup_disable_garbage_trucks", true),
                    DisableRandomBoats = ReadBool("rv_cleanup_disable_random_boats", true),
                    DisableScenarioActions = ReadBool("rv_cleanup_disable_scenario_actions", true),
                    DeleteExistingPeds = ReadBool("rv_cleanup_delete_existing_peds", true),
                    DeleteExistingVehicles = ReadBool("rv_cleanup_delete_existing_vehicles", true),
                    PreserveMissionEntities = ReadBool("rv_cleanup_preserve_mission_entities", true),
                    PreserveNetworkedEntities = ReadBool("rv_cleanup_preserve_networked_entities", true),
                    PreserveOccupiedVehicles = ReadBool("rv_cleanup_preserve_occupied_vehicles", true),
                    ClearRadius = ReadFloat("rv_cleanup_clear_radius", 175.0f),
                    ClearIntervalMs = ReadInt("rv_cleanup_clear_interval_ms", 10000),
                    ConfigRefreshMs = ReadInt("rv_cleanup_config_refresh_ms", 5000)
                };
            }

            private static bool ReadBool(string convarName, bool defaultValue)
            {
                var defaultText = defaultValue ? "true" : "false";
                var value = GetConvar(convarName, defaultText);

                if (string.IsNullOrWhiteSpace(value))
                {
                    return defaultValue;
                }

                value = value.Trim().ToLowerInvariant();

                return value == "1" || value == "true" || value == "yes" || value == "on";
            }

            private static int ReadInt(string convarName, int defaultValue)
            {
                var value = GetConvarInt(convarName, defaultValue);
                return value > 0 ? value : defaultValue;
            }

            private static float ReadFloat(string convarName, float defaultValue)
            {
                var rawValue = GetConvar(convarName, defaultValue.ToString(CultureInfo.InvariantCulture));
                float parsedValue;

                if (float.TryParse(rawValue, NumberStyles.Float, CultureInfo.InvariantCulture, out parsedValue) && parsedValue > 0.0f)
                {
                    return parsedValue;
                }

                return defaultValue;
            }
        }
    }
}
