using System;
using System.Runtime.CompilerServices;

using RobocraftX.Character;
using RobocraftX.Character.Movement;
using RobocraftX.Common.Players;
using RobocraftX.Common.Input;
using RobocraftX.CR.MachineEditing.BoxSelect;
using RobocraftX.Physics;
using RobocraftX.Blocks.Ghost;
using RobocraftX.Character.Camera;
using RobocraftX.Character.Factories;
using Gamecraft.CharacterVulnerability;
using Gamecraft.CharacterVulnerability.Entities;
using Svelto.ECS;
using Unity.Mathematics;
using Unity.Physics;
using UnityEngine;

using GamecraftModdingAPI.Engines;

namespace GamecraftModdingAPI.Players
{
	internal class PlayerEngine : IApiEngine, IFactoryEngine
	{
		public string Name { get; } = "GamecraftModdingAPIPlayerGameEngine";

		public EntitiesDB entitiesDB { set; private get; }

		public bool isRemovable => false;

		public IEntityFactory Factory { set; private get; }

		private bool isReady = false;

		public void Dispose()
		{
			isReady = false;
		}

		public void Ready()
		{
			isReady = true;
		}

        public uint GetLocalPlayer()
		{
			if (!isReady) return uint.MaxValue;
			PlayerIDStruct[] localPlayers = entitiesDB.QueryEntities<PlayerIDStruct>(PlayersExclusiveGroups.LocalPlayers).ToFastAccess(out uint count);
			if (count > 0)
			{
				return localPlayers[0].ID.entityID;
			}
			return uint.MaxValue;
		}

        public uint GetRemotePlayer()
		{
			if (!isReady) return uint.MaxValue;
			PlayerIDStruct[] localPlayers = entitiesDB.QueryEntities<PlayerIDStruct>(PlayersExclusiveGroups.RemotePlayers).ToFastAccess(out uint count);
            if (count > 0)
            {
                return localPlayers[0].ID.entityID;
            }
			return uint.MaxValue;
		}

        public bool ExistsById(uint playerId)
		{
			return entitiesDB.Exists<PlayerIDStruct>(playerId, PlayersExclusiveGroups.LocalPlayers)
			       || entitiesDB.Exists<PlayerIDStruct>(playerId, PlayersExclusiveGroups.RemotePlayers);
		}

        public float3 GetLocation(uint playerId)
		{
			ref var rbes = ref GetCharacterStruct<RigidBodyEntityStruct>(playerId, out bool exists);
			if (exists)
			{
				return rbes.position;
			}
			return float3.zero;
		}

		public bool SetLocation(uint playerId, float3 location, bool exitSeat = true)
		{
			ExclusiveGroup[] characterGroups = CharacterExclusiveGroups.AllCharacters;
			for (int i = 0; i < characterGroups.Length; i++)
            {
                EGID egid = new EGID(playerId, characterGroups[i]);
				if (entitiesDB.Exists<RigidBodyEntityStruct>(egid))
                {
                    ref RigidBodyEntityStruct rbes = ref entitiesDB.QueryEntity<RigidBodyEntityStruct>(egid);
                    if (characterGroups[i] == CharacterExclusiveGroups.InPilotSeatGroup && exitSeat)
                    {
                        entitiesDB.QueryEntity<CharacterPilotSeatEntityStruct>(egid).instantExit = true;
                        entitiesDB.PublishEntityChange<CharacterPilotSeatEntityStruct>(egid);
                    }
					rbes.position = location;
					return true;
                }
            }
			return false;
		}

		public float3 GetRotation(uint playerId)
		{
			ref var rbes = ref GetCharacterStruct<RigidBodyEntityStruct>(playerId, out bool exists);
			if (exists)
			{
				return ((Quaternion) rbes.rotation).eulerAngles;
			}
			return default;
		}

		public bool SetRotation(uint playerId, float3 value)
        {
	        ref var rbes = ref GetCharacterStruct<RigidBodyEntityStruct>(playerId, out bool exists);
            if (exists)
            {
	            Quaternion q = rbes.rotation;
	            q.eulerAngles = value;
	            rbes.rotation = q;
				return true;
            }
            return false;
        }

        public float3 GetLinearVelocity(uint playerId)
		{
			ref var rbes = ref GetCharacterStruct<RigidBodyEntityStruct>(playerId, out bool exists);
			if (exists)
            {
				return rbes.velocity;
            }
            return float3.zero;
		}

		public bool SetLinearVelocity(uint playerId, float3 value)
        {
	        ref var rbes = ref GetCharacterStruct<RigidBodyEntityStruct>(playerId, out bool exists);
			if (exists)
            {
                rbes.velocity = value;
				return true;
            }
			return false;
        }

		public float3 GetAngularVelocity(uint playerId)
        {
	        ref var rbes = ref GetCharacterStruct<RigidBodyEntityStruct>(playerId, out bool exists);
            if (exists)
            {
                return rbes.angularVelocity;
            }
            return float3.zero;
        }

        public bool SetAngularVelocity(uint playerId, float3 value)
        {
	        ref var rbes = ref GetCharacterStruct<RigidBodyEntityStruct>(playerId, out bool exists);
            if (exists)
            {
                rbes.angularVelocity = value;
                return true;
            }
            return false;
        }

		public PhysicsMass GetMass(uint playerId)
		{
			ref var rbes = ref GetCharacterStruct<RigidBodyEntityStruct>(playerId, out bool exists);
			if (exists)
            {
				return rbes.physicsMass;
            }
            return default;
		}

        public bool SetInverseMass(uint playerId, float inverseMass)
		{
			ref var rbes = ref GetCharacterStruct<RigidBodyEntityStruct>(playerId, out bool exists);
			if (exists)
            {
				rbes.physicsMass.InverseInertia = inverseMass;
                return true;
            }
            return false;
		}

		public float? GetLastPingTime(uint playerId, PlayerType type)
		{
			EGID egid = new EGID(playerId, PlayerGroupFromEnum(type));
			if (entitiesDB.Exists<PlayerNetworkStatsEntityStruct>(egid))
			{
				return entitiesDB.QueryEntity<PlayerNetworkStatsEntityStruct>(egid).lastPingTimeSinceLevelLoad;
			}
			return null;
		}

		public float GetInitialHealth(uint playerId)
		{
			ref var c = ref GetCharacterStruct<CharacterHealthEntityStruct>(playerId, out bool exists);
			if (exists)
            {
				return c.initialHealth;
            }
            return -1f;
		}

		public bool SetInitialHealth(uint playerId, float val)
        {
	        ref var c = ref GetCharacterStruct<CharacterHealthEntityStruct>(playerId, out bool exists);
			if (exists)
            {
                c.initialHealth = val;
				return true;
            }
			return false;
        }

		public float GetCurrentHealth(uint playerId)
        {
	        ref var c = ref GetCharacterStruct<CharacterHealthEntityStruct>(playerId, out bool exists);
			if (exists)
            {
                return c.currentHealth;
            }
            return -1f;
        }

		public bool SetCurrentHealth(uint playerId, float val)
        {
	        ref var c = ref GetCharacterStruct<CharacterHealthEntityStruct>(playerId, out bool exists);
			if (exists)
            {
                c.currentHealth = val;
				return true;
            }
            return false;
        }

		public bool DamagePlayer(uint playerId, float amount)
		{
			Factory.BuildEntity<DamageEntityDescriptor>(
				new EGID(CharacterVulnerabilityExclusiveGroups.NextDamageEntityId, CharacterVulnerabilityExclusiveGroups.CharacterDamageExclusiveGroup)
			).Init(new DamageEntityStruct
			{
				damage = amount,
				targetPlayerEntityId = playerId,
			});
			return true;
		}

		public bool GetDamageable(uint playerId)
        {
	        ref var c = ref GetCharacterStruct<CharacterHealthEntityStruct>(playerId, out bool exists);
			if (exists)
            {
                return c.canTakeDamageStat;
            }
            return false;
        }

        public bool SetDamageable(uint playerId, bool val)
        {
	        ref var ches = ref GetCharacterStruct<CharacterHealthEntityStruct>(playerId, out bool exists);
			if (exists)
            {
				ches.canTakeDamage = val;
				ches.canTakeDamage = val;
                return true;
            }
            return false;
        }

		public uint GetInitialLives(uint playerId)
        {
	        ref var c = ref GetCharacterStruct<CharacterLivesEntityComponent>(playerId, out bool exists);
            if (exists)
            {
                return c.initialLives;
            }
            return uint.MaxValue;
        }

        public bool SetInitialLives(uint playerId, uint val)
        {
	        ref var c = ref GetCharacterStruct<CharacterLivesEntityComponent>(playerId, out bool exists);
            if (exists)
            {
                c.initialLives = val;
                return true;
            }
            return false;
        }

		public uint GetCurrentLives(uint playerId)
        {
	        ref var c = ref GetCharacterStruct<CharacterLivesEntityComponent>(playerId, out bool exists);
			if (exists)
            {
				return c.currentLives;
            }
			return uint.MaxValue;
        }

        public bool SetCurrentLives(uint playerId, uint val)
        {
	        ref var c = ref GetCharacterStruct<CharacterLivesEntityComponent>(playerId, out bool exists);
			if (exists)
            {
				c.currentLives = val;
                return true;
            }
            return false;
        }

		public bool GetGameOverScreen(uint playerId)
		{
			ref var c = ref GetCharacterStruct<CharacterLivesEntityComponent>(playerId, out bool exists);
			if (exists)
			{
				return c.gameOverScreen;
            }
            return false;
		}

        public bool IsDead(uint playerId)
		{
			return entitiesDB.Exists<RigidBodyEntityStruct>(playerId, CharacterExclusiveGroups.DeadGroup);
		}

        public int GetSelectedBlock(uint playerId)
        {
	        ref var c = ref GetCharacterStruct<EquippedPartStruct>(playerId, out bool exists);
			if (exists)
            {
				return c.SelectedDBPartID;
            }
			return ushort.MaxValue;
		}

		public byte GetSelectedColor(uint playerId)
        {
	        ref var c = ref GetCharacterStruct<EquippedColourStruct>(playerId, out bool exists);
            if (exists)
            {
				return c.indexInPalette;
            }
            return 255;
        }

        // reusable methods

		[MethodImpl(MethodImplOptions.AggressiveInlining)]
		private ExclusiveGroup PlayerGroupFromEnum(PlayerType type)
		{
			return type == PlayerType.Local ? PlayersExclusiveGroups.LocalPlayers : PlayersExclusiveGroups.RemotePlayers; 
		}

		[MethodImpl(MethodImplOptions.AggressiveInlining)]
		public ref T GetCharacterStruct<T>(uint playerId, out bool exists) where T : unmanaged, IEntityComponent
        {
            ExclusiveGroup[] characterGroups = CharacterExclusiveGroups.AllCharacters;
            for (int i = 0; i < characterGroups.Length; i++)
            {
                EGID egid = new EGID(playerId, characterGroups[i]);
                if (entitiesDB.Exists<T>(egid))
                {
	                exists = true;
                    return ref entitiesDB.QueryEntity<T>(egid);
                }
            }

            exists = false;
            T[] arr = new T[1];
            return ref arr[0]; //Return default value
        }

		[MethodImpl(MethodImplOptions.AggressiveInlining)]
        public bool GetPlayerStruct<T>(uint playerId, out T s) where T : unmanaged, IEntityComponent
        {
            ExclusiveGroup[] playerGroups = PlayersExclusiveGroups.AllPlayers;
            for (int i = 0; i < playerGroups.Length; i++)
            {
                EGID egid = new EGID(playerId, playerGroups[i]);
                if (entitiesDB.Exists<T>(egid))
                {
                    s = entitiesDB.QueryEntity<T>(egid);
                    return true;
                }
            }
            s = default;
            return false;
        }
        
        public EGID? GetThingLookedAt(uint playerId, float maxDistance = -1f)
        {
	        if (!entitiesDB.TryQueryMappedEntities<CharacterCameraRayCastEntityStruct>(
		        CameraExclusiveGroups.CameraGroup, out var mapper))
		        return null;
	        mapper.TryGetEntity(playerId, out CharacterCameraRayCastEntityStruct rayCast);
	        float distance = maxDistance < 0
		        ? GhostBlockUtils.GetBuildInteractionDistance(entitiesDB, rayCast)
		        : maxDistance;
	        if (rayCast.hit && rayCast.distance <= distance)
		        return rayCast.hitEgid;

	        return null;
        }

        public unsafe Block[] GetSelectedBlocks(uint playerid)
        {
	        if (!entitiesDB.Exists<BoxSelectStateEntityStruct>(playerid,
		        BoxSelectExclusiveGroups.BoxSelectVolumeExclusiveGroup))
		        return new Block[0];
	        var state = entitiesDB.QueryEntity<BoxSelectStateEntityStruct>(playerid,
		        BoxSelectExclusiveGroups.BoxSelectVolumeExclusiveGroup);
	        var blocks = entitiesDB.QueryEntity<SelectedBlocksStruct>(playerid,
		        BoxSelectExclusiveGroups.BoxSelectVolumeExclusiveGroup);
	        if (!state.active) return new Block[0];
	        var pointer = (EGID*) blocks.selectedBlocks.ToPointer();
	        var ret = new Block[blocks.count];
	        for (int j = 0; j < blocks.count; j++)
	        {
		        var egid = pointer[j];
		        ret[j] = new Block(egid);
	        }

	        return ret;
        }
	}
}