using UnityEngine;
using UnityEngine.Playables;
using KinematicCharacterController;

namespace AC.Downloads.KCCIntegration
{

	[RequireComponent (typeof (AC_KCC_CharacterController))]
	public class AC_KCC_Integration : MonoBehaviour
	{

		#region Variables

		[SerializeField] private AC.Char characterAC = null;
		[SerializeField] private AC_KCC_CharacterController characterController = null;
		[SerializeField] private KinematicCharacterMotor motor = null;
		[SerializeField] private string moveSpeedParameter = "MoveSpeed";

		private enum ControlState { Null, UnderDirectControl, UnityPathfinding, ACTurning };
		private ControlState controlState = ControlState.Null;
		private bool isInTimeline;

		#endregion


		#region UnityStandards

		private void OnEnable ()
		{
			characterAC.motionControl = MotionControl.Manual;
			characterAC.animationEngine = AnimationEngine.Custom;

			EventManager.OnCharacterEnterTimeline += OnCharacterEnterTimeline;
			EventManager.OnCharacterExitTimeline += OnCharacterExitTimeline;
		}


		private void OnDisable ()
		{
			EventManager.OnCharacterEnterTimeline -= OnCharacterEnterTimeline;
			EventManager.OnCharacterExitTimeline -= OnCharacterExitTimeline;
		}


		private void Update ()
		{
			// Update what our current control state is
			UpdateControlState ();

			// Move the character according to the current control state
			UpdateMovement ();

			if (!isInTimeline)
			{
				float moveSpeed = motor.BaseVelocity.magnitude * Vector3.Dot (motor.CharacterForward, motor.BaseVelocity.normalized);
				float smoothMoveSpeed = Mathf.Lerp (characterAC.GetAnimator ().GetFloat (moveSpeedParameter), moveSpeed, Time.deltaTime * 4f);
				characterAC.GetAnimator ().SetFloat (moveSpeedParameter, smoothMoveSpeed);
			}
		}

		#endregion


		#region CustomEvents

		private void OnCharacterEnterTimeline (AC.Char character, PlayableDirector director, int trackIndex)
		{
			if (character != characterAC) return;

			isInTimeline = true;
			motor.enabled = false;
			characterAC.moveSpeedParameter = moveSpeedParameter;
		}


		private void OnCharacterExitTimeline (AC.Char character, PlayableDirector director, int trackIndex)
		{
			if (character != characterAC) return;

			isInTimeline = false;
			motor.enabled = true;
			characterAC.moveSpeedParameter = string.Empty;
			characterAC.Teleport (characterAC.transform.position);
		}

		#endregion


		#region PrivateFunctions

		private void UpdateControlState ()
		{
			// Check if we want to determine the character's position through AC, or just through direct input
			if (!KickStarter.stateHandler.IsInGameplay () || !KickStarter.stateHandler.MovementSystemIsEnabled || !characterAC.IsPlayer || characterAC.IsMovingAlongPath () || KickStarter.settingsManager.movementMethod == MovementMethod.PointAndClick)
			{
				// Check if we want to make the character pathfind, or do nothing while AC turns them
				if (characterAC.charState == CharState.Move)
				{
					controlState = ControlState.UnityPathfinding;
				}
				else
				{
					controlState = ControlState.ACTurning;
				}
			}
			else
			{
				controlState = ControlState.UnderDirectControl;
			}
		}


		private void UpdateMovement ()
		{
			switch (controlState)
			{
				case ControlState.UnderDirectControl:
					UpdateDirectInput ();
					break;

				case ControlState.UnityPathfinding:
					UpdatePathfindInput ();
					break;

				case ControlState.ACTurning:
					UpdateJustTurningInput ();
					break;
			}
		}


		private void UpdateDirectInput ()
		{
			// Pass inputs to controller as normal
			PlayerCharacterInputs characterInputs = new ()
			{
				MoveAxisForward = KickStarter.playerInput.InputGetAxis ("Vertical"),
				MoveAxisRight = KickStarter.playerInput.InputGetAxis ("Horizontal"),
				RunHeld = KickStarter.playerInput.InputGetButton ("Run"),
			};

			// Apply inputs to character
			characterController.SetInputs (ref characterInputs);
		}


		private void UpdatePathfindInput ()
		{
			// Read the AC.Char script's GetTargetPosition and GetTargetRotation methods to dictate how the Controller / Motor should be affected
			Vector3 targetPosition = characterAC.GetTargetPosition ();
			Vector3 targetDirection = (targetPosition - motor.TransientPosition).normalized;

			AICharacterInputs inputs = new ()
			{
				MoveVector = targetDirection,
				LookVector = targetDirection
			};

			characterController.SetInputs (ref inputs);
		}


		private void UpdateJustTurningInput ()
		{
			// Stop regular input, halt movement, and use GetFrameRotation to manually enforce the per-frame rotation
			AICharacterInputs inputs = new ();
			characterController.SetInputs (ref inputs);

			motor.BaseVelocity = Vector3.zero;
			motor.SetRotation (characterAC.GetFrameRotation ());
		}


		private void OnTeleport ()
		{
			// This function is called by the Char script whenever the character has been teleported.
			motor.BaseVelocity = Vector3.zero;
			motor.SetPosition (characterAC.GetTargetPosition ());
			motor.SetRotation (characterAC.GetTargetRotation ());
		}

		#endregion

	}

}