using System.Text;
using UnityEngine;

namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
{
	public class OneGrabRotateConstraint : IOneHandContraintMovement
	{
		#region Log

		const string LOG_TAG = "VIVE.OpenXR.Toolkits.RealisticHandInteraction.OneGrabRotateConstraint";
		private StringBuilder m_sb = null;
		internal StringBuilder sb
		{
			get
			{
				if (m_sb == null) { m_sb = new StringBuilder(); }
				return m_sb;
			}
		}
		private void DEBUG(string msg) { Debug.Log($"{LOG_TAG}, {msg}"); }
		private void WARNING(string msg) { Debug.LogWarning($"{LOG_TAG}, {msg}"); }
		private void ERROR(string msg) { Debug.LogError($"{LOG_TAG}, {msg}"); }
		int logFrame = 0;
		bool printIntervalLog => logFrame == 0;

		#endregion

		private enum RotationAxis
		{
			XAxis = 0,
			YAxis = 1,
			ZAxis = 2,
		}

		[SerializeField]
		private Transform m_Constraint;
		[SerializeField]
		private Transform m_Pivot;
		[SerializeField]
		private RotationAxis m_RotationAxis = RotationAxis.XAxis;
		[SerializeField]
		private ConstraintInfo m_ClockwiseAngle = ConstraintInfo.Identity;
		public float clockwiseAngle => m_ClockwiseAngle.value;
		[SerializeField]
		private ConstraintInfo m_CounterclockwiseAngle = ConstraintInfo.Identity;
		public float counterclockwiseAngle => m_CounterclockwiseAngle.value;
		private float m_TotalDegrees = 0.0f;
		public float totalDegrees => m_TotalDegrees;
		private Pose previousHandPose = Pose.identity;

		public override void Initialize(IGrabbable grabbable)
		{
			if (grabbable is HandGrabInteractable handGrabbable)
			{
				if (m_Constraint == null)
				{
					m_Constraint = handGrabbable.transform;
					WARNING("Since no constraint object is set, self will be used as the constraint object.");
				}
				if (m_Pivot == null)
				{
					m_Pivot = handGrabbable.transform;
					WARNING("Since no pivot is set, self will be used as the pivot.");
				}
			}
		}

		public override void OnBeginGrabbed(IGrabbable grabbable)
		{
			if (grabbable.grabber is HandGrabInteractor handGrabber)
			{
				HandPose handPose = HandPoseProvider.GetHandPose(handGrabber.isLeft ? HandPoseType.HAND_LEFT : HandPoseType.HAND_RIGHT);
				handPose.GetPosition(JointType.Wrist, out Vector3 wristPos);
				handPose.GetRotation(JointType.Wrist, out Quaternion wristRot);
				previousHandPose = new Pose(wristPos, wristRot);
			}
		}

		public override void UpdatePose(Pose handPose)
		{
			if (previousHandPose == Pose.identity)
			{
				previousHandPose = handPose;
				return;
			}

			Vector3 axis = Vector3.zero;
			switch (m_RotationAxis)
			{
				case RotationAxis.XAxis: axis = Vector3.right; break;
				case RotationAxis.YAxis: axis = Vector3.up; break;
				case RotationAxis.ZAxis: axis = Vector3.forward; break;
			}
			Vector3 worldAxis = m_Pivot.TransformDirection(axis);

			Vector3 previousOffset = previousHandPose.position - m_Pivot.position;
			Vector3 previousVector = Vector3.ProjectOnPlane(previousOffset, worldAxis);

			Vector3 targetOffset = handPose.position - m_Pivot.position;
			Vector3 targetVector = Vector3.ProjectOnPlane(targetOffset, worldAxis);

			float angleDelta = Vector3.Angle(previousVector, targetVector);
			angleDelta *= Vector3.Dot(Vector3.Cross(previousVector, targetVector), worldAxis) > 0.0f ? 1.0f : -1.0f;

			float previousAngle = m_TotalDegrees;
			m_TotalDegrees += angleDelta;
			if (m_CounterclockwiseAngle.enableConstraint)
			{
				m_TotalDegrees = Mathf.Max(m_TotalDegrees, -m_CounterclockwiseAngle.value);
			}
			if (m_ClockwiseAngle.enableConstraint)
			{
				m_TotalDegrees = Mathf.Min(m_TotalDegrees, m_ClockwiseAngle.value);
			}
			angleDelta = m_TotalDegrees - previousAngle;
			m_Constraint.RotateAround(m_Pivot.position, worldAxis, angleDelta);

			previousHandPose = handPose;
		}

		public override void OnEndGrabbed(IGrabbable grabbable)
		{
			previousHandPose = Pose.identity;
		}
	}
}
