using GTANetworkAPI;
using Newtonsoft.Json.Linq;

namespace ReallifeGamemode.Server.Util
{
    //Enums for ease of use
    public enum WindowID
    {
        WindowFrontRight,
        WindowFrontLeft,
        WindowRearRight,
        WindowRearLeft
    }

    public enum WindowState
    {
        WindowFixed,
        WindowDown,
        WindowBroken
    }

    public enum DoorID
    {
        DoorFrontLeft,
        DoorFrontRight,
        DoorRearLeft,
        DoorRearRight,
        DoorHood,
        DoorTrunk
    }

    public enum DoorState
    {
        DoorClosed,
        DoorOpen,
        DoorBroken,
    }

    public enum WheelID
    {
        Wheel0,
        Wheel1,
        Wheel2,
        Wheel3,
        Wheel4,
        Wheel5,
        Wheel6,
        Wheel7,
        Wheel8,
        Wheel9
    }

    public enum WheelState
    {
        WheelFixed,
        WheelBurst,
        WheelOnRim,
    }

    public class VehicleStreaming : Script
    {
        //This is the data object which will be synced to vehicles
        public class VehicleSyncData
        {
            //Used to bypass some streaming bugs
            public Vector3 Position { get; set; } = new Vector3();
            public Vector3 Rotation { get; set; } = new Vector3();

            //Basics
            public float Dirt { get; set; } = 0.0f;
            public bool Locked { get; set; } = true;
            public bool Engine { get; set; } = false;

            //(Not synced)
            //public float BodyHealth { get; set; } = 1000.0f;
            //public float EngineHealth { get; set; } = 1000.0f;

            //Doors 0-7 (0 = closed, 1 = open, 2 = broken) (This uses enums so don't worry about it)
            public int[] Door { get; set; } = new int[8] { 0, 0, 0, 0, 0, 0, 0, 0 };

            //Windows (0 = up, 1 = down, 2 = smashed) (This uses enums so don't worry about it)
            public int[] Window { get; set; } = new int[4] { 0, 0, 0, 0 };

            //Wheels 0-7, 45/47 (0 = fixed, 1 = flat, 2 = missing) (This uses enums so don't worry about it)
            public int[] Wheel { get; set; } = new int[10] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
        }

        //API functions for people to use
        public static void SetVehicleWindowState(Vehicle veh, WindowID window, WindowState state)
        {
            VehicleSyncData data = GetVehicleSyncData(veh);
            if (data == default(VehicleSyncData)) //If data doesn't exist create a new one. This is the process for all API functions
                data = new VehicleSyncData();

            data.Window[(int)window] = (int)state;
            UpdateVehicleSyncData(veh, data);
            NAPI.ClientEvent.TriggerClientEventInDimension(veh.Dimension, "VehStream_SetVehicleWindowStatus_Single", veh.Handle, (int)window, (int)state);
        }

        public static WindowState GetVehicleWindowState(Vehicle veh, WindowID window)
        {
            VehicleSyncData data = GetVehicleSyncData(veh);
            if (data == default(VehicleSyncData))
            {
                data = new VehicleSyncData();
                UpdateVehicleSyncData(veh, data);
            }
            return (WindowState)data.Window[(int)window];
        }

        public static void SetVehicleWheelState(Vehicle veh, WheelID wheel, WheelState state)
        {
            VehicleSyncData data = GetVehicleSyncData(veh);
            if (data == default(VehicleSyncData))
                data = new VehicleSyncData();

            data.Wheel[(int)wheel] = (int)state;
            UpdateVehicleSyncData(veh, data);
            NAPI.ClientEvent.TriggerClientEventInDimension(veh.Dimension, "VehStream_SetVehicleWheelStatus_Single", veh.Handle, (int)wheel, (int)state);
        }

        public static WheelState GetVehicleWheelState(Vehicle veh, WheelID wheel)
        {
            VehicleSyncData data = GetVehicleSyncData(veh);
            if (data == default(VehicleSyncData))
            {
                data = new VehicleSyncData();
                UpdateVehicleSyncData(veh, data);
            }
            return (WheelState)data.Wheel[(int)wheel];
        }

        public static void SetVehicleDirt(Vehicle veh, float dirt)
        {
            VehicleSyncData data = GetVehicleSyncData(veh);
            if (data == default(VehicleSyncData))
                data = new VehicleSyncData();

            data.Dirt = dirt;
            UpdateVehicleSyncData(veh, data);
            NAPI.ClientEvent.TriggerClientEventInDimension(veh.Dimension, "VehStream_SetVehicleDirtLevel", veh.Handle, dirt);
        }

        public static float GetVehicleDirt(Vehicle veh)
        {
            VehicleSyncData data = GetVehicleSyncData(veh);
            if (data == default(VehicleSyncData))
            {
                data = new VehicleSyncData();
                UpdateVehicleSyncData(veh, data);
            }
            return data.Dirt;
        }

        public static void SetDoorState(Vehicle veh, DoorID door, DoorState state)
        {
            VehicleSyncData data = GetVehicleSyncData(veh);
            if (data == default(VehicleSyncData))
                data = new VehicleSyncData();

            data.Door[(int)door] = (int)state;
            UpdateVehicleSyncData(veh, data);
            NAPI.ClientEvent.TriggerClientEventInDimension(veh.Dimension, "VehStream_SetVehicleDoorStatus_Single", veh, (int)door, (int)state);
        }

        public static DoorState GetDoorState(Vehicle veh, DoorID door)
        {
            VehicleSyncData data = GetVehicleSyncData(veh);
            if (data == default(VehicleSyncData))
            {
                data = new VehicleSyncData();
                UpdateVehicleSyncData(veh, data);
            }
            return (DoorState)data.Door[(int)door];
        }

        public static void SetEngineState(Vehicle veh, bool status)
        {
            VehicleSyncData data = GetVehicleSyncData(veh);
            if (data == default(VehicleSyncData))
                data = new VehicleSyncData();

            data.Engine = status;
            UpdateVehicleSyncData(veh, data);
            NAPI.ClientEvent.TriggerClientEventInDimension(veh.Dimension, "VehStream_SetEngineStatus", veh, status);
        }

        public static bool GetEngineState(Vehicle veh)
        {
            VehicleSyncData data = GetVehicleSyncData(veh);
            if (data == default(VehicleSyncData))
            {
                data = new VehicleSyncData();
                UpdateVehicleSyncData(veh, data);
            }
            return data.Engine;
        }

        public static void SetLockStatus(Vehicle veh, bool status)
        {
            VehicleSyncData data = GetVehicleSyncData(veh);
            if (data == default(VehicleSyncData))
                data = new VehicleSyncData();

            data.Locked = status;
            UpdateVehicleSyncData(veh, data);
            NAPI.ClientEvent.TriggerClientEventInDimension(veh.Dimension, "VehStream_SetLockStatus", veh, status);
            NAPI.Pools.GetAllPlayers().ForEach(p =>
            {
                if (p.IsInVehicle && p.Vehicle.Handle == veh.Handle)
                {
                    p.TriggerEvent("Vehicle_setLockStatus", status);
                }
            });
        }

        public static bool GetLockState(Vehicle veh)
        {
            VehicleSyncData data = GetVehicleSyncData(veh);
            if (data == default(VehicleSyncData))
            {
                data = new VehicleSyncData();
                UpdateVehicleSyncData(veh, data);
            }
            return data.Locked;
        }

        //Used internally only but publicly available in case any of you need it
        public static VehicleSyncData GetVehicleSyncData(Vehicle veh)
        {
            if (veh != null)
            {
                if (NAPI.Entity.DoesEntityExist(veh))
                {
                    if (NAPI.Data.HasEntitySharedData(veh.Handle, "VehicleSyncData"))
                    {
                        //API converts class objects to JObject so we have to change it back
                        JObject obj = NAPI.Data.GetEntitySharedData(veh.Handle, "VehicleSyncData");
                        return obj.ToObject<VehicleSyncData>();
                    }
                }
            }

            return default(VehicleSyncData); //null
        }

        //Used internally only but publicly available in case any of you need it
        public static bool UpdateVehicleSyncData(Vehicle veh, VehicleSyncData data)
        {
            if (veh != null)
            {
                if (NAPI.Entity.DoesEntityExist(veh))
                {
                    if (data != null)
                    {
                        data.Position = veh.Position;
                        data.Rotation = veh.Rotation;
                        NAPI.Data.SetEntitySharedData(veh, "VehicleSyncData", data);
                        return true;
                    }
                }
            }
            return false;
        }

        //Called from the client to sync dirt level
        [RemoteEvent("VehStream_SetDirtLevel")]
        public void VehStreamSetDirtLevel(Client player, Vehicle veh, float dirt)
        {
            VehicleSyncData data = GetVehicleSyncData(veh);
            if (data == default(VehicleSyncData))
                data = new VehicleSyncData();

            data.Dirt = dirt;

            UpdateVehicleSyncData(veh, data);

            //Re-distribute the goods
            NAPI.ClientEvent.TriggerClientEventInDimension(veh.Dimension, "VehStream_SetVehicleDirtLevel", veh.Handle, dirt);
        }

        //Called from the client to sync door data
        [RemoteEvent("VehStream_SetDoorData")]
        public void VehStreamSetDoorData(Client player, Vehicle veh, int door1state, int door2state, int door3state, int door4state, int door5state, int door6state, int door7state, int door8state)
        {
            VehicleSyncData data = GetVehicleSyncData(veh);
            if (data == default(VehicleSyncData))
                data = new VehicleSyncData();

            data.Door[0] = door1state;
            data.Door[1] = door2state;
            data.Door[2] = door3state;
            data.Door[3] = door4state;
            data.Door[4] = door5state;
            data.Door[5] = door6state;
            data.Door[6] = door7state;
            data.Door[7] = door8state;

            UpdateVehicleSyncData(veh, data);

            //Re-distribute the goods
            NAPI.ClientEvent.TriggerClientEventInDimension(veh.Dimension, "VehStream_SetVehicleDoorStatus", veh.Handle, door1state, door2state, door3state, door4state, door5state, door6state, door7state, door8state);
        }

        //Called from the client to sync window data
        [RemoteEvent("VehStream_SetWindowData")]
        public void VehStreamSetWindowData(Client player, Vehicle veh, int window1state, int window2state, int window3state, int window4state)
        {
            VehicleSyncData data = GetVehicleSyncData(veh);
            if (data == default(VehicleSyncData))
                data = new VehicleSyncData();

            data.Window[0] = window1state;
            data.Window[1] = window2state;
            data.Window[2] = window3state;
            data.Window[3] = window4state;

            UpdateVehicleSyncData(veh, data);

            //Re-distribute the goods
            NAPI.ClientEvent.TriggerClientEventInDimension(veh.Dimension, "VehStream_SetVehicleWindowStatus", veh.Handle, window1state, window2state, window3state, window4state);
        }

        //Called from the client to sync wheel data
        [RemoteEvent("VehStream_SetWheelData")]
        public void VehStreamSetWheelData(Client player, Vehicle veh, int wheel1state, int wheel2state, int wheel3state, int wheel4state, int wheel5state, int wheel6state, int wheel7state, int wheel8state, int wheel9state, int wheel10state)
        {
            VehicleSyncData data = GetVehicleSyncData(veh);
            if (data == default(VehicleSyncData))
                data = new VehicleSyncData();

            data.Wheel[0] = wheel1state;
            data.Wheel[1] = wheel2state;
            data.Wheel[2] = wheel3state;
            data.Wheel[3] = wheel4state;
            data.Wheel[4] = wheel5state;
            data.Wheel[5] = wheel6state;
            data.Wheel[6] = wheel7state;
            data.Wheel[7] = wheel8state;
            data.Wheel[8] = wheel9state;
            data.Wheel[9] = wheel10state;
            UpdateVehicleSyncData(veh, data);

            //Re-distribute the goods
            NAPI.ClientEvent.TriggerClientEventInDimension(veh.Dimension, "VehStream_SetVehicleWheelStatus", veh.Handle, wheel1state, wheel2state, wheel3state, wheel4state, wheel5state, wheel6state, wheel7state, wheel8state, wheel9state, wheel10state);
        }

        //Other events
        [ServerEvent(Event.PlayerEnterVehicleAttempt)]
        public void VehStreamEnterAttempt(Client player, Vehicle veh, sbyte seat)
        {
            VehicleSyncData data = GetVehicleSyncData(veh);
            if (data == default(VehicleSyncData))
                data = new VehicleSyncData();

            UpdateVehicleSyncData(veh, data);
            NAPI.ClientEvent.TriggerClientEvent(player, "VehStream_PlayerEnterVehicleAttempt", veh.Handle.Value, seat);
        }

        [ServerEvent(Event.PlayerExitVehicleAttempt)]
        public void VehStreamExitAttempt(Client player, Vehicle veh)
        {
            VehicleSyncData data = GetVehicleSyncData(veh);
            if (data == default(VehicleSyncData))
                data = new VehicleSyncData();

            data.Position = veh.Position;
            data.Rotation = veh.Rotation;

            UpdateVehicleSyncData(veh, data);
            NAPI.ClientEvent.TriggerClientEvent(player, "VehStream_PlayerExitVehicleAttempt", veh);
        }

        [ServerEvent(Event.PlayerExitVehicle)]
        public void VehStreamExit(Client player, Vehicle veh)
        {
            VehicleSyncData data = GetVehicleSyncData(veh);
            if (data == default(VehicleSyncData))
                data = new VehicleSyncData();

            data.Position = veh.Position;
            data.Rotation = veh.Rotation;

            UpdateVehicleSyncData(veh, data);
            NAPI.ClientEvent.TriggerClientEvent(player, "VehStream_PlayerExitVehicle", veh.Handle.Value);
        }

        [ServerEvent(Event.PlayerEnterVehicle)]
        public void VehStreamEnter(Client player, Vehicle veh, sbyte seat)
        {
            VehicleSyncData data = GetVehicleSyncData(veh);
            if (data == default(VehicleSyncData))
                data = new VehicleSyncData();

            UpdateVehicleSyncData(veh, data);
            NAPI.ClientEvent.TriggerClientEvent(player, "VehStream_PlayerEnterVehicle", veh, seat);
            player.TriggerEvent("Vehicle_setLockStatus", data.Locked);
        }

        //[ServerEvent(Event.VehicleDamage)]
        //public void VehDamage(Vehicle veh, float bodyHealthLoss, float engineHealthLoss)
        //{
        //    VehicleSyncData data = GetVehicleSyncData(veh);
        //    if (data == default(VehicleSyncData))
        //        data = new VehicleSyncData();

        //    data.BodyHealth -= bodyHealthLoss;
        //    data.EngineHealth -= engineHealthLoss;

        //    UpdateVehicleSyncData(veh, data);

        //    if (NAPI.Vehicle.GetVehicleDriver(veh) != default(Client)) //Doesn't work?
        //        NAPI.ClientEvent.TriggerClientEvent(NAPI.Vehicle.GetVehicleDriver(veh), "VehStream_PlayerExitVehicleAttempt", veh);
        //}

        [ServerEvent(Event.VehicleDoorBreak)]
        public void VehDoorBreak(Vehicle veh, int index)
        {
            VehicleSyncData data = GetVehicleSyncData(veh);
            if (data == default(VehicleSyncData))
                data = new VehicleSyncData();

            data.Door[index] = 2;

            UpdateVehicleSyncData(veh, data);

            NAPI.ClientEvent.TriggerClientEventInDimension(veh.Dimension, "VehStream_SetVehicleDoorStatus", veh.Handle, data.Door[0], data.Door[1], data.Door[2], data.Door[3], data.Door[4], data.Door[5], data.Door[6], data.Door[7]);
        }

        // INDICATORS

        [RemoteEvent("CLIENT:toggleLeftIndicator")]
        public void ToggleLeftIndicator(Client player)
        {
            if (!player.IsInVehicle || player.VehicleSeat != -1) return;
            Vehicle veh = player.Vehicle;

            if (!(veh.GetData("indicatorData") is IndicatorData data)) data = new IndicatorData();

            if (data.Left && data.Right)
            {
                data.Right = false;
            }
            else
            {
                data.Left = !data.Left;
                data.Right = false;
            }

            veh.SetData("indicatorData", data);
            NAPI.ClientEvent.TriggerClientEventForAll("SERVER:setIndicatorStatus", veh.Handle.Value, data.Left, data.Right);
        }

        [RemoteEvent("CLIENT:toggleRightIndicator")]
        public void ToggleRightIndicator(Client player)
        {
            if (!player.IsInVehicle || player.VehicleSeat != -1) return;
            Vehicle veh = player.Vehicle;

            if (!(veh.GetData("indicatorData") is IndicatorData data)) data = new IndicatorData();

            if (data.Left && data.Right)
            {
                data.Left = false;
            }
            else
            {
                data.Right = !data.Right;
                data.Left = false;
            }

            veh.SetData("indicatorData", data);
            NAPI.ClientEvent.TriggerClientEventForAll("SERVER:setIndicatorStatus", veh.Handle.Value, data.Left, data.Right);
        }

        [RemoteEvent("CLIENT:toggleWarningIndicator")]
        public void ToggleWarningIndicator(Client player)
        {
            if (!player.IsInVehicle || player.VehicleSeat != -1) return;
            Vehicle veh = player.Vehicle;

            if (!(veh.GetData("indicatorData") is IndicatorData data)) data = new IndicatorData();

            if (data.Right && data.Left)
            {
                data.Right = false;
                data.Left = false;
            }
            else
            {
                data.Right = true;
                data.Left = true;
            }

            veh.SetData("indicatorData", data);
            NAPI.ClientEvent.TriggerClientEventForAll("SERVER:setIndicatorStatus", veh.Handle.Value, data.Left, data.Right);
        }
    }
}
