namespace Fusion.Addons.KCC
{
	using System;
	using System.Collections.Generic;
	using UnityEngine;
	// This file contains implementation related to physics.
	public partial class KCC
	{
		// PUBLIC METHODS
		/// 
		/// Sphere overlap using same filtering as for KCC physics query.
		/// 
		/// Contains results of the overlap.
		/// Center position of the sphere.
		/// Radius of the sphere.
		/// Use to enable/disable trigger hits.
		public bool SphereOverlap(KCCOverlapInfo overlapInfo, Vector3 position, float radius, QueryTriggerInteraction triggerInteraction)
		{
			return SphereOverlap(overlapInfo, Data, position, radius, default, _settings.CollisionLayerMask, triggerInteraction);
		}
		/// 
		/// Capsule overlap using same filtering as for KCC physics query.
		/// 
		/// Contains results of the overlap.
		/// Bottom position of the capsule.
		/// Radius of the capsule.
		/// Height of the capsule.
		/// Use to enable/disable trigger hits.
		public bool CapsuleOverlap(KCCOverlapInfo overlapInfo, Vector3 position, float radius, float height, QueryTriggerInteraction triggerInteraction)
		{
			return CapsuleOverlap(overlapInfo, Data, position, radius, height, default, _settings.CollisionLayerMask, triggerInteraction);
		}
		/// 
		/// Ray cast using same filtering as for KCC physics query.
		/// 
		/// Contains results of the cast sorted by distance.
		/// Origin position of the cast.
		/// Direction of the cast.
		/// Distance of the cast.
		/// Use to enable/disable trigger hits.
		public bool RayCast(KCCShapeCastInfo shapeCastInfo, Vector3 position, Vector3 direction, float maxDistance, QueryTriggerInteraction triggerInteraction)
		{
			return RayCast(shapeCastInfo, Data, position, direction, maxDistance, _settings.CollisionLayerMask, triggerInteraction);
		}
		/// 
		/// Sphere cast using same filtering as for KCC physics query.
		/// 
		/// Contains results of the cast sorted by distance.
		/// Center position of the sphere.
		/// Radius of the sphere.
		/// Direction of the cast.
		/// Distance of the cast.
		/// Use to enable/disable trigger hits.
		/// Set to true for the result to contain initially overlapping colliders.
		public bool SphereCast(KCCShapeCastInfo shapeCastInfo, Vector3 position, float radius, Vector3 direction, float maxDistance, QueryTriggerInteraction triggerInteraction, bool trackInitialOverlaps = true)
		{
			return SphereCast(shapeCastInfo, Data, position, radius, default, direction, maxDistance, _settings.CollisionLayerMask, triggerInteraction, trackInitialOverlaps);
		}
		/// 
		/// Capsule cast using same filtering as for KCC physics query.
		/// 
		/// Contains results of the cast sorted by distance.
		/// Bottom position of the capsule.
		/// Radius of the capsule.
		/// Height of the capsule.
		/// Direction of the cast.
		/// Distance of the cast.
		/// Use to enable/disable trigger hits.
		/// Set to true for the result to contain initially overlapping colliders.
		public bool CapsuleCast(KCCShapeCastInfo shapeCastInfo, Vector3 position, float radius, float height, Vector3 direction, float maxDistance, QueryTriggerInteraction triggerInteraction, bool trackInitialOverlaps = true)
		{
			return CapsuleCast(shapeCastInfo, Data, position, radius, height, default, direction, maxDistance, _settings.CollisionLayerMask, triggerInteraction, trackInitialOverlaps);
		}
		/// 
		/// Force refresh KCCData.Hits based on current position. If an existing overlap info is provided, the method takes as much information as possible from it.
		/// Metadata (collision type, penetration, ...) for other (new) hits will be missing. Use with caution.
		/// 
		/// Base overlap query results. Only matching colliders metadata is taken from this info if new overlap query is executed.
		/// Controls reuse of base query results / execution of a new overlap query.
	    /// 
	    /// - Default - Hits from base overlap query will be reused only if all colliders are within extent, otherwise new overlap query will be executed.///
- Reuse - Force reuse hits from base overlap query, even if colliders are not within extent.///
- New - Force execute new overlap query.///
/// 
		public void UpdateHits(KCCOverlapInfo baseOverlapInfo, EKCCHitsOverlapQuery overlapQuery)
		{
			UpdateHits(Data, baseOverlapInfo, overlapQuery);
		}
		/// 
		/// Check if the KCC potentially collides with a collider, using same filtering as physics query.
		/// Returning true doesn't mean the collider overlaps. Can be used as a filter after custom overlap/shapecast query.
		/// 
		/// Collider instance.
		public bool IsValidHitCollider(Collider hitCollider)
		{
			if (hitCollider == null)
				return false;
			return IsValidHitCollider(Data, hitCollider);
		}
		// PRIVATE METHODS
		private bool SphereOverlap(KCCOverlapInfo overlapInfo, KCCData data, Vector3 position, float radius, float extent, LayerMask layerMask, QueryTriggerInteraction triggerInteraction)
		{
			overlapInfo.Reset(false);
			overlapInfo.Position           = position;
			overlapInfo.Radius             = radius;
			overlapInfo.Height             = 0.0f;
			overlapInfo.Extent             = extent;
			overlapInfo.LayerMask          = layerMask;
			overlapInfo.TriggerInteraction = triggerInteraction;
			Collider   hitCollider;
			Collider[] hitColliders     = _hitColliders;
			int        hitColliderCount = Runner.GetPhysicsScene().OverlapSphere(position, radius + extent, hitColliders, layerMask, triggerInteraction);
			for (int i = 0; i < hitColliderCount; ++i)
			{
				hitCollider = hitColliders[i];
				if (IsValidHitColliderUnsafe(data, hitCollider) == true)
				{
					overlapInfo.AddHit(hitCollider);
				}
			}
			return overlapInfo.AllHitCount > 0;
		}
		private bool CapsuleOverlap(KCCOverlapInfo overlapInfo, KCCData data, Vector3 position, float radius, float height, float extent, LayerMask layerMask, QueryTriggerInteraction triggerInteraction)
		{
			overlapInfo.Reset(false);
			overlapInfo.Position           = position;
			overlapInfo.Radius             = radius;
			overlapInfo.Height             = height;
			overlapInfo.Extent             = extent;
			overlapInfo.LayerMask          = layerMask;
			overlapInfo.TriggerInteraction = triggerInteraction;
			Vector3 positionUp   = position + new Vector3(0.0f, height - radius, 0.0f);
			Vector3 positionDown = position + new Vector3(0.0f, radius, 0.0f);
			Collider   hitCollider;
			Collider[] hitColliders     = _hitColliders;
			int        hitColliderCount = Runner.GetPhysicsScene().OverlapCapsule(positionDown, positionUp, radius + extent, hitColliders, layerMask, triggerInteraction);
			for (int i = 0; i < hitColliderCount; ++i)
			{
				hitCollider = hitColliders[i];
				if (IsValidHitColliderUnsafe(data, hitCollider) == true)
				{
					overlapInfo.AddHit(hitCollider);
				}
			}
			return overlapInfo.AllHitCount > 0;
		}
		private bool RayCast(KCCShapeCastInfo shapeCastInfo, KCCData data, Vector3 position, Vector3 direction, float maxDistance, LayerMask layerMask, QueryTriggerInteraction triggerInteraction)
		{
			shapeCastInfo.Reset(false);
			shapeCastInfo.Position           = position;
			shapeCastInfo.Direction          = direction;
			shapeCastInfo.MaxDistance        = maxDistance;
			shapeCastInfo.LayerMask          = layerMask;
			shapeCastInfo.TriggerInteraction = triggerInteraction;
			RaycastHit   raycastHit;
			RaycastHit[] raycastHits     = _raycastHits;
			int          raycastHitCount = Runner.GetPhysicsScene().Raycast(position, direction, raycastHits, maxDistance, layerMask, triggerInteraction);
			for (int i = 0; i < raycastHitCount; ++i)
			{
				raycastHit = raycastHits[i];
				if (IsValidHitColliderUnsafe(data, raycastHit.collider) == true)
				{
					shapeCastInfo.AddHit(raycastHit);
				}
			}
			shapeCastInfo.Sort();
			return shapeCastInfo.AllHitCount > 0;
		}
		private bool SphereCast(KCCShapeCastInfo shapeCastInfo, KCCData data, Vector3 position, float radius, float extent, Vector3 direction, float maxDistance, LayerMask layerMask, QueryTriggerInteraction triggerInteraction, bool trackInitialOverlaps)
		{
			shapeCastInfo.Reset(false);
			shapeCastInfo.Position           = position;
			shapeCastInfo.Radius             = radius;
			shapeCastInfo.Extent             = extent;
			shapeCastInfo.Direction          = direction;
			shapeCastInfo.MaxDistance        = maxDistance;
			shapeCastInfo.LayerMask          = layerMask;
			shapeCastInfo.TriggerInteraction = triggerInteraction;
			RaycastHit   raycastHit;
			RaycastHit[] raycastHits     = _raycastHits;
			int          raycastHitCount = Runner.GetPhysicsScene().SphereCast(position, radius + extent, direction, raycastHits, maxDistance, layerMask, triggerInteraction);
			for (int i = 0; i < raycastHitCount; ++i)
			{
				raycastHit = raycastHits[i];
				if (trackInitialOverlaps == false && raycastHit.distance <= 0.0f && raycastHit.point.Equals(default) == true)
					continue;
				if (IsValidHitColliderUnsafe(data, raycastHit.collider) == true)
				{
					shapeCastInfo.AddHit(raycastHit);
				}
			}
			shapeCastInfo.Sort();
			return shapeCastInfo.AllHitCount > 0;
		}
		private bool CapsuleCast(KCCShapeCastInfo shapeCastInfo, KCCData data, Vector3 position, float radius, float height, float extent, Vector3 direction, float maxDistance, LayerMask layerMask, QueryTriggerInteraction triggerInteraction, bool trackInitialOverlaps)
		{
			shapeCastInfo.Reset(false);
			shapeCastInfo.Position           = position;
			shapeCastInfo.Radius             = radius;
			shapeCastInfo.Height             = height;
			shapeCastInfo.Extent             = extent;
			shapeCastInfo.Position           = position;
			shapeCastInfo.Direction          = direction;
			shapeCastInfo.MaxDistance        = maxDistance;
			shapeCastInfo.LayerMask          = layerMask;
			shapeCastInfo.TriggerInteraction = triggerInteraction;
			Vector3 positionUp   = position + new Vector3(0.0f, height - radius, 0.0f);
			Vector3 positionDown = position + new Vector3(0.0f, radius, 0.0f);
			RaycastHit   raycastHit;
			RaycastHit[] raycastHits     = _raycastHits;
			int          raycastHitCount = Runner.GetPhysicsScene().CapsuleCast(positionDown, positionUp, radius + extent, direction, raycastHits, maxDistance, layerMask, triggerInteraction);
			for (int i = 0; i < raycastHitCount; ++i)
			{
				raycastHit = raycastHits[i];
				if (trackInitialOverlaps == false && raycastHit.distance <= 0.0f && raycastHit.point.Equals(default) == true)
					continue;
				if (IsValidHitColliderUnsafe(data, raycastHit.collider) == true)
				{
					shapeCastInfo.AddHit(raycastHit);
				}
			}
			shapeCastInfo.Sort();
			return shapeCastInfo.AllHitCount > 0;
		}
		private void UpdateHits(KCCData data, KCCOverlapInfo baseOverlapInfo, EKCCHitsOverlapQuery overlapQuery)
		{
			bool reuseOverlapInfo;
			switch (overlapQuery)
			{
				case EKCCHitsOverlapQuery.Default: { reuseOverlapInfo = baseOverlapInfo != null && baseOverlapInfo.AllHitsWithinExtent() == true; break; }
				case EKCCHitsOverlapQuery.Reuse:   { reuseOverlapInfo = baseOverlapInfo != null;                                                  break; }
				case EKCCHitsOverlapQuery.New:     { reuseOverlapInfo = false;                                                                    break; }
				default:
					throw new NotImplementedException(nameof(overlapQuery));
			}
			if (reuseOverlapInfo == true)
			{
				_trackOverlapInfo.CopyFromOther(baseOverlapInfo);
			}
			else
			{
				CapsuleOverlap(_trackOverlapInfo, data, data.TargetPosition, _settings.Radius, _settings.Height, _settings.Extent, _settings.CollisionLayerMask, QueryTriggerInteraction.Collide);
				if (baseOverlapInfo != null)
				{
					for (int i = 0; i < _trackOverlapInfo.AllHitCount; ++i)
					{
						KCCOverlapHit trackedHit = _trackOverlapInfo.AllHits[i];
						for (int j = 0; j < baseOverlapInfo.AllHitCount; ++j)
						{
							KCCOverlapHit extendedHit = baseOverlapInfo.AllHits[j];
							if (object.ReferenceEquals(trackedHit.Collider, extendedHit.Collider) == true)
							{
								trackedHit.CopyFromOther(extendedHit);
							}
						}
					}
				}
			}
			data.Hits.Clear();
			for (int i = 0, count = _trackOverlapInfo.AllHitCount; i < count; ++i)
			{
				data.Hits.Add(_trackOverlapInfo.AllHits[i]);
			}
		}
		private void ForceRemoveAllHits(KCCData data)
		{
			_trackOverlapInfo.Reset(false);
			_extendedOverlapInfo.Reset(false);
			data.Hits.Clear();
		}
		private bool IsValidHitCollider(KCCData data, Collider hitCollider)
		{
			if (hitCollider == _collider.Collider)
				return false;
			for (int i = 0, count = _childColliders.Count; i < count; ++i)
			{
				if (hitCollider == _childColliders[i])
					return false;
			}
			int colliderLayerMask = 1 << hitCollider.gameObject.layer;
			if ((_settings.CollisionLayerMask & colliderLayerMask) != colliderLayerMask)
				return false;
			List ignores = data.Ignores.All;
			for (int i = 0, count = ignores.Count; i < count; ++i)
			{
				if (hitCollider == ignores[i].Collider)
					return false;
			}
			if (ResolveCollision != null)
			{
				try
				{
					return ResolveCollision(this, hitCollider);
				}
				catch (Exception exception)
				{
					UnityEngine.Debug.LogException(exception);
				}
			}
			return true;
		}
		private bool IsValidHitColliderUnsafe(KCCData data, Collider overlapCollider)
		{
			if (object.ReferenceEquals(overlapCollider, _collider.Collider) == true)
				return false;
			for (int i = 0, count = _childColliders.Count; i < count; ++i)
			{
				if (object.ReferenceEquals(overlapCollider, _childColliders[i]) == true)
					return false;
			}
			List ignores = data.Ignores.All;
			for (int i = 0, count = ignores.Count; i < count; ++i)
			{
				if (object.ReferenceEquals(overlapCollider, ignores[i].Collider) == true)
					return false;
			}
			if (ResolveCollision != null)
			{
				try
				{
					return ResolveCollision(this, overlapCollider);
				}
				catch (Exception exception)
				{
					UnityEngine.Debug.LogException(exception);
				}
			}
			return true;
		}
	}
}