namespace TurnTheGameOn.IKDriver
{
	using UnityEngine;
	using UnityEngine.UI;
	using System.Collections;
	using System.Collections.Generic;

	[System.Serializable, RequireComponent(typeof(Animator))]
	public class IKD_IKDriver : MonoBehaviour
	{
		public Animator animator;																						// animator for calling IK functions
		public Transform lhswt_W, lhswt_NW, lhswt_N, lhswt_NE, lhswt_E, lhswt_SE, lhswt_S, lhswt_SW;					// left hand steering wheel IK targets
		public Transform rhswt_W, rhswt_NW, rhswt_N, rhswt_NE, rhswt_E, rhswt_SE, rhswt_S, rhswt_SW;					// right hand steering wheel IK targets
		public Transform leftFootIdle, rightFootIdle, footClutch, footBrake, footGas, handShift;						//
		public Transform leftHandObj, rightHandObj, leftFootObj, rightFootObj;											// current IK targets
		public Transform headLookIKCP, targetLeftHandIK, targetRightHandIK, targetRightFootIK, targetLeftFootIK;		// Avatar IK control points
		public bool controlSteeringWheel = true;
		public Transform readOnlySteeringWheel;
		public Transform steeringWheel;
		public SpeedType speedType;
		public Rigidbody vehicleRigidbody;																				// for calculating steering wheel shake value
		public Text gearText;																							//assign the gear UI text component, when this components text changes a shift will be triggered
		public enum TargetSide { Left, Right, None }
		public TargetSide shiftHand = TargetSide.Right, clutchFoot = TargetSide.Left, brakeFoot = TargetSide.Right, gasFoot = TargetSide.Right;		// avatar options
		public enum SteeringTargets { Two, All }
		public SteeringTargets steeringTargets;
		public Vector3 avatarPosition, lookPosition, defaultSteering;
		public IKD_RCC_CarControllerReference rccReference;
		public bool useRCCInput;
		[Range (0,1)] public float wheelShake = 1;
		[Range (0,360)] public float steeringWheelRotation;																//maximum rotation of steering wheel transform on x axis
		[Range (0,90)] public float steeringWheelRotationTwoTargets = 85f;
		public float lookTargetPosX;																					//the look target transform position.x value
		public float rotationSpeed;
		public float steeringRotationSpeed = 0.07f;
		public float defaultLookXPos, maxLookRight, maxLookLeft;														//the maximum distance the look target can move left or right of the default position
		public float minLookSpeed, maxLookSpeed;																		//use min speed when steering, max to snap back speed the look object will use when not steering
		public bool ikActive = false;																					//enable/disable IK control of the avatar
		public bool shift;																								//set this bool to true to trigger a shift
		public bool enableShifting = true;
		public bool mobile;
		public string steeringAxis = "Horizontal", throttleAxis = "Vertical";
		public string editorView = "avatar";
		private float lookObjMoveSpeed;																					//the speed at which the look target object will move
		private float horizontalInput, verticalInput;
		private float tempShake;
		private float targetY;
		private float zAngle;
		private float yVelocity;
		private float rotationLimit;
		private string gearString;
		private bool shifting;																		//used to determine when the right hand should target a steering wheel target or shift target

		void Start () 
		{
			if(TurnTheGameOn.IKDriver.IKD_StaticUtility.m_IKD_UtilitySettings.useMobileController) mobile = true;
			if (useRCCInput && !rccReference) rccReference = GetComponent <IKD_RCC_CarControllerReference> ();
			transform.localPosition = avatarPosition;
			lookTargetPosX = defaultLookXPos;
			TargetShifter ();
			rotationLimit = steeringTargets == SteeringTargets.Two ? steeringWheelRotationTwoTargets : steeringWheelRotation;
			wheelCP = shiftHand == TargetSide.Left ? targetLeftHandIK : shiftHand == TargetSide.Right ? targetRightHandIK : null; 
		}

		void OnAnimatorIK ()
		{			
			if (animator && ikActive && rightHandObj != null)
			{
				SetShiftState ();
				SetSteeringWheelRotation ();
				SetIKValues (AvatarIKGoal.LeftHand, 1, 1, targetLeftHandIK.position, targetLeftHandIK.rotation);
				SetIKValues (AvatarIKGoal.RightHand, 1, 1, targetRightHandIK.position, targetRightHandIK.rotation);
				SetIKValues (AvatarIKGoal.LeftFoot, 1, 1, targetLeftFootIK.position, targetLeftFootIK.rotation);
				SetIKValues (AvatarIKGoal.RightFoot, 1, 1, targetRightFootIK.position, targetRightFootIK.rotation);
				GetInput ();
				UpdateHandTransforms ();
				UpdateFootTransforms ();
				UpdateIKTargetTransforms ();
				UpdateIKLook ();
			}
			else
			{
				ZeroIKWeight (AvatarIKGoal.LeftHand);
				ZeroIKWeight (AvatarIKGoal.RightHand);
				ZeroIKWeight (AvatarIKGoal.LeftFoot);
				ZeroIKWeight (AvatarIKGoal.RightFoot);
				animator.SetLookAtWeight (0);
			}
		}

		void SetShiftState ()
		{
			if (gearText.text != gearString)
			{
				gearString = gearText.text;
				if (enableShifting)	TargetShifter ();
			}
			if (shift)
			{
				shift = false;
				if (enableShifting) TargetShifter ();
			}
		}

		void SetSteeringWheelRotation ()
		{
			if (controlSteeringWheel)
			{
				if (steeringWheel != null)
				{				
					tempShake = Random.Range (1.0f, 2.0f);
					float speedometerMultiplier = speedType == SpeedType.MPH ? 2.23693629f : speedType == SpeedType.KPH ? 3.6f : 0f;
					tempShake = tempShake * wheelShake * 50 * LinearDistance (0, 150, vehicleRigidbody.velocity.magnitude * speedometerMultiplier);
					tempShake = Random.Range (-tempShake, tempShake);
					targetY = Mathf.SmoothDamp (targetY, (-(horizontalInput * rotationLimit) - tempShake), ref yVelocity, rotationSpeed);
					zAngle = Mathf.SmoothDampAngle (steeringWheel.localEulerAngles.z, targetY, ref yVelocity, steeringRotationSpeed);
					steeringWheel.localEulerAngles = new Vector3 (defaultSteering.x, defaultSteering.y, zAngle);
				}
			}
			else
			{
				if (readOnlySteeringWheel != null)
				{
					targetY = readOnlySteeringWheel.localEulerAngles.z;
				}	
			}
		}

		void SetIKValues (AvatarIKGoal goal, float positionWeight, float rotationWeight, Vector3 position, Quaternion rotation)
		{
			animator.SetIKPositionWeight (goal, positionWeight);
			animator.SetIKRotationWeight (goal, rotationWeight);
			animator.SetIKPosition (goal, position);
			animator.SetIKRotation (goal, rotation);
		}

		void ZeroIKWeight (AvatarIKGoal goal)
		{
			animator.SetIKPositionWeight (goal, 0);
			animator.SetIKRotationWeight (goal, 0);
		}

		void UpdateIKLook ()
		{
			if (headLookIKCP != null)
			{
				animator.SetLookAtWeight (1);
				animator.SetLookAtPosition (headLookIKCP.position);
			}
		}

		void GetInput ()
		{
			if (useRCCInput)
			{				
				horizontalInput = rccReference.rcc_Horizontal;
				verticalInput = rccReference.rcc_Vertical;
			}
			else
			{
				horizontalInput = mobile ? TurnTheGameOn.IKDriver.IKD_CrossPlatformInputManager.GetAxis (steeringAxis) : Input.GetAxis (steeringAxis);
				verticalInput = mobile ? TurnTheGameOn.IKDriver.IKD_CrossPlatformInputManager.GetAxis (throttleAxis) : Input.GetAxis (throttleAxis);
			}
		}

		void UpdateHandTransforms ()
		{
			if (steeringTargets == SteeringTargets.Two) 
			{
				rightHandObj = rhswt_E;
				leftHandObj = lhswt_W;
			}
			else if (steeringTargets == SteeringTargets.All) 
			{
				if (targetY >= 0.0f && targetY <= 90.0f) 
				{
					leftHandObj = lhswt_W;
					rightHandObj = rhswt_E;
				}
				else if (targetY >= 90.0f && targetY <= 180.0f) 
				{
					leftHandObj = lhswt_N;
					rightHandObj = rhswt_SW;
				}
				else if (targetY >= 180.0f && targetY <= 270.0f) 
				{
					leftHandObj = lhswt_E;
					rightHandObj = rhswt_NW;
				}
				else if (targetY >=270f && targetY <=370f) 
				{
					leftHandObj = lhswt_S;
					rightHandObj = rhswt_N;
				}					
				else if (targetY >= -90.0f && targetY <= 0.0f)
				{
					leftHandObj = lhswt_W;
					rightHandObj = rhswt_E;
				}
				else if (targetY >= -180.0f && targetY <= -90.0f)
				{
					leftHandObj = lhswt_S;
					rightHandObj = rhswt_N;
				}
				else if (targetY >= -270.0f && targetY <= -180.0f)
				{
					leftHandObj = lhswt_E;
					rightHandObj = rhswt_W;
				}
				else if (targetY >= -370.0f && targetY <= -270.0f)
				{
					leftHandObj = lhswt_N;
					rightHandObj = rhswt_S;
				}
			}
			lookTargetPosX = horizontalInput > 0 ? defaultLookXPos + maxLookRight : 
				horizontalInput < 0 ? defaultLookXPos + maxLookLeft : 
				defaultLookXPos;
			lookObjMoveSpeed = horizontalInput > 0 || horizontalInput < 0 ? minLookSpeed : 
				Mathf.Approximately (lookPosition.x, lookTargetPosX) ? minLookSpeed :
				Mathf.Lerp (lookObjMoveSpeed, maxLookSpeed, 1 * Time.deltaTime);
		}

		void UpdateFootTransforms ()
		{
			if (verticalInput > 0) {
				if (gasFoot == TargetSide.Right) {
					rightFootObj = footGas;
				} else if (gasFoot == TargetSide.Left) {
					rightFootObj = rightFootIdle;
					leftFootObj = footGas;
				}
			} 
			else if (verticalInput < 0) {
				if (gearString == "R") {	//Reversing	//Set Foot Gas					
					if (gasFoot == TargetSide.Right) {
						rightFootObj = footGas;
					} else if (gasFoot == TargetSide.Left) {
						rightFootObj = rightFootIdle;
						leftFootObj = footGas;
					}
				} 
				else {	//Braking	//Set Foot Brake					
					if (brakeFoot == TargetSide.Right) {
						rightFootObj = footBrake;
					} else if (brakeFoot == TargetSide.Left) {
						leftFootObj = footBrake;
					}
				}
			} 
			else {
				rightFootObj = rightFootIdle;
			}
		}

		float shiftLerp;
		Vector3 shitStartPosition;
		Quaternion shiftStartRotation;
		float holdShiftTimer;
		float shiftBackTimer;
		public float holdShiftTime = 0.25f;
		public float shiftBackTime = 0.75f;
		public float shiftAnimSpeed = 30.0f;
		public float IKSpeed = 8.0f;
		Vector3 pastFollowerPosition, pastTargetPosition;
		public AnimationCurve shiftCurve;
		public Transform wheelCP;
		public Transform returnShiftTarget;
		bool targetingWheel;

		void UpdateIKTargetTransforms () 
		{
			targetRightFootIK.localPosition = Vector3.Lerp (targetRightFootIK.localPosition, rightFootObj.localPosition, 8 * Time.deltaTime);
			targetRightFootIK.localRotation = Quaternion.Lerp (targetRightFootIK.localRotation, rightFootObj.localRotation, 8 * Time.deltaTime);
			targetLeftFootIK.localPosition = Vector3.Lerp (targetLeftFootIK.localPosition, leftFootObj.localPosition, 8 * Time.deltaTime);
			targetLeftFootIK.localRotation = Quaternion.Lerp (targetLeftFootIK.localRotation, leftFootObj.localRotation, 8 * Time.deltaTime);
			if (shiftHand == TargetSide.Right) {
				if (shifting){
					shiftLerp += shiftAnimSpeed * Time.deltaTime;
					if (shiftLerp <= 0.4f)
					{
						targetRightHandIK.position = Vector3.Lerp (targetRightHandIK.position, returnShiftTarget.position, LinearDistance(0.0f, 0.4f, shiftLerp));
						targetRightHandIK.rotation = Quaternion.Lerp (shiftStartRotation, returnShiftTarget.rotation, LinearDistance(0.0f, 0.4f, shiftLerp));
					}
					else if (shiftLerp >= 0.85f)
					{
						targetRightHandIK = handShift;
						holdShiftTimer += 1 * Time.deltaTime;
						if (holdShiftTimer >= holdShiftTime) {
							shifting = false;
							shiftBackTimer = 0.0f;
							shiftLerp = 0.0f;
						}
					}
					else
					{						
						targetRightHandIK.position = Vector3.Lerp (returnShiftTarget.position, handShift.position, shiftCurve.Evaluate(shiftLerp));
						targetRightHandIK.rotation = Quaternion.Lerp (shiftStartRotation, handShift.rotation, shiftCurve.Evaluate(shiftLerp));
					}
				} else {
					if (shiftBackTimer <= shiftBackTime) {
						targetRightHandIK = wheelCP;
						shiftLerp += shiftAnimSpeed * Time.deltaTime;
						shiftBackTimer += 1 * Time.deltaTime;
						targetRightHandIK.position = Vector3.Lerp (handShift.position, returnShiftTarget.position, LinearDistance(0.0f, shiftBackTime, shiftBackTimer));
						targetRightHandIK.rotation = Quaternion.Lerp (handShift.rotation, returnShiftTarget.rotation, LinearDistance(0.0f, shiftBackTime, shiftBackTimer));
					} else {
						targetingWheel = true;
						targetRightHandIK.localPosition = Vector3.Lerp (targetRightHandIK.localPosition, rightHandObj.localPosition, IKSpeed);
						targetRightHandIK.localRotation = Quaternion.Lerp (targetRightHandIK.localRotation, rightHandObj.localRotation, 1);
					}
				}
				targetLeftHandIK.localPosition = Vector3.Slerp (targetLeftHandIK.localPosition, leftHandObj.localPosition, IKSpeed * Time.deltaTime);
				targetLeftHandIK.localRotation = Quaternion.Lerp (targetLeftHandIK.localRotation, leftHandObj.localRotation, 1);
			} else if (shiftHand == TargetSide.Left) {
				if (shifting){					
					shiftLerp += shiftAnimSpeed * Time.deltaTime;
					if (shiftLerp <= 0.4f)
					{
						targetLeftHandIK.position = Vector3.Lerp (targetLeftHandIK.position, returnShiftTarget.position, LinearDistance(0.0f, 0.4f, shiftLerp));
						targetLeftHandIK.rotation = Quaternion.Lerp (shiftStartRotation, returnShiftTarget.rotation, LinearDistance(0.0f, 0.4f, shiftLerp));
					}
					else if (shiftLerp >= 0.85f)
					{
						targetLeftHandIK = handShift;
						holdShiftTimer += 1 * Time.deltaTime;
						if (holdShiftTimer >= holdShiftTime) {
							shifting = false;
							shiftBackTimer = 0.0f;
							shiftLerp = 0.0f;
						}
					}
					else
					{						
						targetLeftHandIK.position = Vector3.Lerp (returnShiftTarget.position, handShift.position, shiftCurve.Evaluate(shiftLerp));
						targetLeftHandIK.rotation = Quaternion.Lerp (shiftStartRotation, handShift.rotation, shiftCurve.Evaluate(shiftLerp));
					}
				} else {					
					if (shiftBackTimer <= shiftBackTime) {
						targetLeftHandIK = wheelCP;
						shiftLerp += shiftAnimSpeed * Time.deltaTime;
						shiftBackTimer += 1 * Time.deltaTime;
						targetLeftHandIK.position = Vector3.Lerp (handShift.position, returnShiftTarget.position, LinearDistance(0.0f, shiftBackTime, shiftBackTimer));
						targetLeftHandIK.rotation = Quaternion.Lerp (handShift.rotation, returnShiftTarget.rotation, LinearDistance(0.0f, shiftBackTime, shiftBackTimer));
					} else {
						targetingWheel = true;
						targetLeftHandIK.localPosition = Vector3.Lerp (targetLeftHandIK.localPosition, leftHandObj.localPosition, IKSpeed);
						targetLeftHandIK.localRotation = Quaternion.Lerp (targetLeftHandIK.localRotation, leftHandObj.localRotation, 1);
					}

				}
				targetRightHandIK.localPosition = Vector3.Slerp (targetRightHandIK.localPosition, rightHandObj.localPosition, IKSpeed);
				targetRightHandIK.localRotation = Quaternion.Lerp (targetRightHandIK.localRotation, rightHandObj.localRotation, 1);
			}
			lookPosition = headLookIKCP.localPosition;
			lookPosition.x = Mathf.Lerp (lookPosition.x, lookTargetPosX, lookObjMoveSpeed * Time.deltaTime);
			headLookIKCP.localPosition = lookPosition;
		}

		public void TargetShifter ()
		{
			if (!shifting && targetingWheel) {
				targetingWheel = false;
				shifting = true;
				shiftLerp = 0.0f;
				holdShiftTimer = 0.0f;
				if (shiftHand == TargetSide.Left) {
					shiftStartRotation = targetLeftHandIK.rotation;
				} else if (shiftHand == TargetSide.Right) {
					shiftStartRotation = targetRightHandIK.rotation;
				}
				if (clutchFoot == TargetSide.Left) leftFootObj = footClutch;				
				else if (clutchFoot == TargetSide.Right) rightFootObj = footClutch;
				Invoke ("SetClutchFootIdle", 0.5f);
			}
		}

		public void SetClutchFootIdle ()
		{
			if (clutchFoot == TargetSide.Left) leftFootObj = leftFootIdle;
			else if (clutchFoot == TargetSide.Right) rightFootObj = rightFootIdle;
		}

		float LinearDistance (float _start, float _end, float _position) 
		{
			return Mathf.InverseLerp (_start, _end, _position);
		}

	}
}