namespace Fusion.Addons.KCC
{
	using UnityEngine;
	using System.Runtime.CompilerServices;
	// This file contains utilities for debugging.
	public partial class KCC
	{
		// PRIVATE MEMBERS
		private static readonly Vector3[] _spherePoints          = MakeUnitSphere(24);
		private static readonly Vector3[] _lowerHemiSpherePoints = MakeLowerUnitHemiSphere(24);
		private static readonly Vector3[] _upperHemiSpherePoints = MakeUpperUnitHemiSphere(24);
		// PUBLIC METHODS
		/// 
		/// Logs current KCC and KCCData state.
		/// 
		[HideInCallstack]
		public void Dump()
		{
			_debug.Dump(this);
		}
		/// 
		/// Enable logs for a given duration. This outputs same logs as Dump().
		/// Duration of logs. Use negative value for infinite logging.
		/// 
		public void EnableLogs(float duration = -1.0f)
		{
			_debug.EnableLogs(this, duration);
		}
		/// 
		/// Logs info message into console with frame/tick metadata.
		/// 
		/// Custom message objects.
		[HideInCallstack]
		public void Log(params object[] messages)
		{
			KCCUtility.Log(this, default, EKCCLogType.Info, messages);
		}
		/// 
		/// Logs warning message into console with frame/tick metadata.
		/// 
		/// Custom message objects.
		[HideInCallstack]
		public void LogWarning(params object[] messages)
		{
			KCCUtility.Log(this, default, EKCCLogType.Warning, messages);
		}
		/// 
		/// Logs error message into console with frame/tick metadata.
		/// 
		/// Custom message objects.
		[HideInCallstack]
		public void LogError(params object[] messages)
		{
			KCCUtility.Log(this, default, EKCCLogType.Error, messages);
		}
		/// 
		/// Draws debug line in editor scene view.
		/// The color is yellow (forward tick), red (resimulation tick) or green (render).
		/// 
		/// Duration of the drawing.
		public void DrawLine(float duration = 0.0f)
		{
			Color   color;
			Vector3 position;
			NetworkRunner runner = Runner;
			if (runner != null)
			{
				bool isInFixedUpdate = runner.Stage != default;
				bool isInForwardTick = runner.IsForward == true;
				if (isInFixedUpdate == true)
				{
					position = _fixedData.TargetPosition;
					if (isInForwardTick == true)
					{
						color = new Color(1.0f, 1.0f, 0.0f, 1.0f);
					}
					else
					{
						color = new Color(1.0f, 0.0f, 0.0f, 1.0f);
					}
				}
				else
				{
					position = _renderData.TargetPosition;
					color = new Color(0.0f, 1.0f, 0.0f, 1.0f);
				}
			}
			else
			{
				position = _transform.position;
				color = new Color(0.0f, 0.0f, 1.0f, 1.0f);
			}
			UnityEngine.Debug.DrawLine(position, position + Vector3.up, color, duration);
		}
		/// 
		/// Draws debug line in editor scene view.
		/// 
		/// Color of the line.
		/// Duration of the drawing.
		public void DrawLine(Color color, float duration = 0.0f)
		{
			Vector3 position = Data.TargetPosition;
			UnityEngine.Debug.DrawLine(position, position + Vector3.up, color, duration);
		}
		/// 
		/// Draws debug sphere in editor scene view, using radius defined in KCC Settings.
		/// 
		/// Base position of the sphere.
		/// Color of the sphere.
		/// Duration of the drawing.
		public void DrawSphere(Vector3 position, Color color, float duration = 0.0f)
		{
			DrawSphere(position, _settings.Radius, color, duration);
		}
		/// 
		/// Draws debug sphere in editor scene view.
		/// 
		/// Base position of the sphere.
		/// Radius of the sphere.
		/// Color of the sphere.
		/// Duration of the drawing.
		public void DrawSphere(Vector3 position, float radius, Color color, float duration = 0.0f)
		{
			if (radius <= 0.0f)
				return;
			DrawWireSphere(position, radius, color, duration);
		}
		/// 
		/// Draws debug capsule in editor scene view, using radius and height defined in KCC Settings.
		/// 
		/// Base position of the capsule.
		/// Color of the capsule.
		/// Duration of the drawing.
		public void DrawCapsule(Vector3 position, Color color, float duration = 0.0f)
		{
			DrawCapsule(position, _settings.Radius, _settings.Height, color, duration);
		}
		/// 
		/// Draws debug capsule in editor scene view.
		/// 
		/// Base position of the capsule.
		/// Radius of the capsule.
		/// Height of the capsule.
		/// Color of the capsule.
		/// Duration of the drawing.
		public void DrawCapsule(Vector3 position, float radius, float height, Color color, float duration = 0.0f)
		{
			if (radius <= 0.0f)
				return;
			height = Mathf.Max(radius * 2.0f, height);
			Vector3 baseLow     = position + Vector3.up * radius;
			Vector3 baseHigh    = position + Vector3.up * (height - radius);
			Vector3 offsetFront = Vector3.forward * radius;
			Vector3 offsetBack  = Vector3.back    * radius;
			Vector3 offsetLeft  = Vector3.left    * radius;
			Vector3 offsetRight = Vector3.right   * radius;
			DrawWireHemiSphere(_lowerHemiSpherePoints, baseLow, radius, color, duration);
			DrawWireHemiSphere(_upperHemiSpherePoints, baseHigh, radius, color, duration);
			UnityEngine.Debug.DrawLine(baseLow + offsetFront, baseHigh + offsetFront, color, duration);
			UnityEngine.Debug.DrawLine(baseLow + offsetBack,  baseHigh + offsetBack, color, duration);
			UnityEngine.Debug.DrawLine(baseLow + offsetLeft,  baseHigh + offsetLeft, color, duration);
			UnityEngine.Debug.DrawLine(baseLow + offsetRight, baseHigh + offsetRight, color, duration);
		}
		// PRIVATE METHODS
		[HideInCallstack]
		[System.Diagnostics.Conditional(TRACING_SCRIPT_DEFINE)]
		private void Trace(params object[] messages)
		{
			KCCUtility.Trace(this, messages);
		}
		[MethodImpl(MethodImplOptions.AggressiveInlining)]
		private bool CheckSpawned()
		{
			if (_isSpawned == false)
			{
				LogError($"{nameof(KCC)}.{nameof(Spawned)}() has not been called yet! Use {nameof(KCC)}.{nameof(InvokeOnSpawn)}() to register a callback.", this);
				return false;
			}
			return true;
		}
		private static void DrawWireSphere(Vector3 position, float radius, Color color, float duration = 0.0f)
		{
			Vector3[] points = _spherePoints;
			int length = points.Length / 3;
			for (int i = 0; i < length; i++)
			{
				int j = (i + 1) % length;
				Vector3 startX = position + radius * points[length * 0 + i];
				Vector3 endX   = position + radius * points[length * 0 + j];
				Vector3 startY = position + radius * points[length * 1 + i];
				Vector3 endY   = position + radius * points[length * 1 + j];
				Vector3 startZ = position + radius * points[length * 2 + i];
				Vector3 endZ   = position + radius * points[length * 2 + j];
				UnityEngine.Debug.DrawLine(startX, endX, color, duration);
				UnityEngine.Debug.DrawLine(startY, endY, color, duration);
				UnityEngine.Debug.DrawLine(startZ, endZ, color, duration);
			}
		}
		private static void DrawWireHemiSphere(Vector3[] points, Vector3 position, float radius, Color color, float duration = 0.0f)
		{
			int length = points.Length / 3;
			for (int i = 0; i < length - 1; i++)
			{
				int j = (i + 1) % length;
				Vector3 startX = position + radius * points[length * 0 + i];
				Vector3 endX   = position + radius * points[length * 0 + j];
				Vector3 startY = position + radius * points[length * 1 + i];
				Vector3 endY   = position + radius * points[length * 1 + j];
				Vector3 startZ = position + radius * points[length * 2 + i];
				Vector3 endZ   = position + radius * points[length * 2 + j];
				UnityEngine.Debug.DrawLine(startX, endX, color, duration);
				UnityEngine.Debug.DrawLine(startY, endY, color, duration);
				UnityEngine.Debug.DrawLine(startZ, endZ, color, duration);
			}
		}
		private static Vector3[] MakeUnitSphere(int length)
		{
			Vector3[] points = new Vector3[length * 3];
			for (int i = 0; i < length; i++)
			{
				float f = i / (float)length;
				float c = Mathf.Cos(f * (float)(System.Math.PI * 2.0f));
				float s = Mathf.Sin(f * (float)(System.Math.PI * 2.0f));
				points[length * 0 + i] = new Vector3(c, s, 0);
				points[length * 1 + i] = new Vector3(0, c, s);
				points[length * 2 + i] = new Vector3(s, 0, c);
			}
			return points;
		}
		private static Vector3[] MakeLowerUnitHemiSphere(int length)
		{
			int count = length + 1;
			Vector3[] points = new Vector3[count * 3];
			for (int i = 0; i < count; i++)
			{
				float f  = i / (float)length;
				float c1 = Mathf.Cos(Mathf.PI * f * 2.0f);
				float s1 = Mathf.Sin(Mathf.PI * f * 2.0f);
				float c2 = Mathf.Cos(Mathf.PI * (f + 0.5f));
				float s2 = Mathf.Sin(Mathf.PI * (f + 0.5f));
				float c3 = Mathf.Cos(Mathf.PI * (f + 1.0f));
				float s3 = Mathf.Sin(Mathf.PI * (f + 1.0f));
				points[count * 0 + i] = new Vector3(c3, s3, 0);
				points[count * 1 + i] = new Vector3(0, c2, s2);
				points[count * 2 + i] = new Vector3(s1, 0, c1);
			}
			return points;
		}
		private static Vector3[] MakeUpperUnitHemiSphere(int length)
		{
			int count = length + 1;
			Vector3[] points = new Vector3[count * 3];
			for (int i = 0; i < count; i++)
			{
				float f  = i / (float)length;
				float c1 = Mathf.Cos(Mathf.PI * f * 2.0f);
				float s1 = Mathf.Sin(Mathf.PI * f * 2.0f);
				float c2 = Mathf.Cos(Mathf.PI * (f - 0.5f));
				float s2 = Mathf.Sin(Mathf.PI * (f - 0.5f));
				float c3 = Mathf.Cos(Mathf.PI * f);
				float s3 = Mathf.Sin(Mathf.PI * f);
				points[count * 0 + i] = new Vector3(c3, s3, 0);
				points[count * 1 + i] = new Vector3(0, c2, s2);
				points[count * 2 + i] = new Vector3(s1, 0, c1);
			}
			return points;
		}
	}
}