using System;
using System.Collections.Generic;
using UnityEngine;
using Drawing;
using Reset.Movement;
using Sirenix.OdinInspector;

public enum PlayerFacingDirection{
    TowardsTarget = 0,
    MatchForward,
    MatchCamera,
    Static
}

namespace Reset.Units{
    // public interface IBuffSource{ - Unused good idea?
    //     public Object sourceObject();
    //     public 
    // }
    public class UnitMovementData{
        // Movement Direction
        public Vector3 targetMoveDirection;
        public PlayerFacingDirection moveFacing;
        
        public float accelerationSmoothing = 5f;
        public float deaccelerationSmoothing = 5f;
        public AnimationCurve deaccelerationCurve;

        // Move Speed
        public float moveSpeedTarget = 15f;
        public float moveSpeedSoothing = 10f;
        
        // Jumping
        [ShowInInspector, ReadOnly] public float jumpPower;
        public float jumpPowerDecay;
    
        // Gravity
        [ShowInInspector, ReadOnly] public float gravityPower;
        public float gravityMax;
        public float gravityAcceleration;
        
        // Rotation
        public PlayerFacingDirection rotateFacing;
        public float rotationSpeedTarget = 5f;
        public float rotationSmoothing;
        
        // Smoothing
        public Quaternion targetRotation;
        public float currentRotSpeed;
    }
    
    public class UnitMovementHandler : MonoBehaviour{
        // class MovementFloatModifier{
        //     // IBuffSource source 
        //     public float value;
        // }
        
        // Debug viewing
        // Smoothed Values
        [ShowInInspector, ReadOnly] private float outputSpeed;
        [ShowInInspector, ReadOnly] private Vector3 outputMoveDirection;
        [ShowInInspector, ReadOnly] private Quaternion outputRotation;
        [ShowInInspector, ReadOnly] private float outputRotationSpeed;
        
        private CharacterController controller;
        public PlayerControls controls;

        [SerializeReference, ShowInInspector]
        public UnitMovementData data = new UnitMovementData();
    
        void Awake(){
            controller = GetComponent<CharacterController>();
        }

        void Update(){
            UpdateCurrentDirection();
            UpdateCurrentSpeed();
            UpdateCurrentGravity();
            UpdateCurrentRotation();
            //
            DoMovement();
        }
        
        private void UpdateCurrentDirection(){
            // Construct move direction
            Vector3 targetDirection = data.targetMoveDirection;
	        
            Debug.Log(controls);
            // Get input value
            Vector3 inputMovement = 
                new Vector3(
                    controls.
                        rawMoveInput.x, 
                    0f, 
                    controls.
                        rawMoveInput.y);

            if (inputMovement.magnitude < .1f) {
                inputMovement = Vector3.zero;
            }

            // Change how movement is managed based on facing state
            switch (data.moveFacing) {
                case PlayerFacingDirection.TowardsTarget:
                    break;
                case PlayerFacingDirection.MatchForward: // TODO: Recomment
                    // targetDirection = agent.transform.forward * inputMovement.magnitude;
                    targetDirection = inputMovement;
                    break;
                case PlayerFacingDirection.MatchCamera:
                    targetDirection = Quaternion.Euler(Camera.main.transform.forward.DirectionTo(transform.forward)) * inputMovement;	// NOTE: This used to be t: currentRotSpeed * Time.deltaTime.
                    // See how it feels with a hard set value and go fro there.
                    break;
                case PlayerFacingDirection.Static:
                    break;
                default:
                    throw new ArgumentOutOfRangeException();
            }

            // Remove Y from variables
            Vector3 targetNoY = new Vector3(targetDirection.x, 0f, targetDirection.z);
            Vector3 currentNoY = new Vector3(outputMoveDirection.x, 0f, outputMoveDirection.z);
			
            // Smooth movement 
            if (targetNoY.magnitude > currentNoY.magnitude) {
                currentNoY = Vector3.Lerp(currentNoY, targetNoY,data.accelerationSmoothing * Time.deltaTime);	

            } else {
                float deaccelValue = data.deaccelerationCurve.Evaluate(inputMovement.magnitude); 
				
                currentNoY = Vector3.Lerp(currentNoY, targetNoY, data.deaccelerationSmoothing * Time.deltaTime);	
            }
            
            // Commit move direction
            outputMoveDirection = new Vector3(currentNoY.x, outputMoveDirection.y, currentNoY.z);
        }

        private void UpdateCurrentSpeed(){
            outputSpeed = Mathf.Lerp(outputSpeed, data.moveSpeedTarget, data.moveSpeedSoothing * Time.deltaTime);
        }

        private void UpdateCurrentGravity(){
            // Accelerate gravity
            data.gravityPower += data.gravityAcceleration * Time.deltaTime;
            data.gravityPower = Mathf.Min(data.gravityPower, data.gravityMax);
            
            // Apply a constant gravity if the player is grounded
            if (controller.isGrounded) {
                data.gravityPower = .1f;
            }
            
            // Create the final gravity value
            float gravityMoveDirection = data.jumpPower + Physics.gravity.y * data.gravityPower;
            
            // Commit gravity to move direction, ignoring XZ since those are done in UpdateMovementDirection
            outputMoveDirection = new Vector3(outputMoveDirection.x, gravityMoveDirection, outputMoveDirection.z);
        }

        private void UpdateCurrentRotation(){
            // Calculate rotation speed
            outputRotationSpeed = Mathf.Lerp(outputRotationSpeed, data.rotationSpeedTarget, data.rotationSmoothing * Time.deltaTime);
			
            // Get input value
            Vector3 inputMovement = new Vector3(controls.rawMoveInput.x, 0f, controls.rawMoveInput.y);
			
            switch (data.rotateFacing) {
                case PlayerFacingDirection.TowardsTarget:
                    // Set rotation to just the direction of the target

                    Debug.LogError("Not implemented...");
					
                    break;
                case PlayerFacingDirection.MatchForward:
                    if (controls.rawMoveInput.magnitude < 0.01f) { break; }

                    outputRotation = Quaternion.LookRotation(inputMovement) *
                                     Quaternion.Euler(Camera.main.transform.rotation.eulerAngles.Flatten(0f, null, 0f));

                    break;
                case PlayerFacingDirection.MatchCamera:
                    // Craft a new rotation that flattens the camera's rotation to the ground
                    // Needed to keep the character from inheriting the camera's height-rotation
                    outputRotation  = Quaternion.Euler(Camera.main.transform.rotation.eulerAngles.Flatten(0, null, 0));
                    break;
                case PlayerFacingDirection.Static:
                    outputRotation  = transform.rotation;
                    break;
            }
			
            // Set final rotation
            transform.rotation = Quaternion.Lerp(transform.rotation, outputRotation, 10f * Time.deltaTime).Flatten(0, null, 0);
        }

        public void DoMovement(){
            DoMovement(outputMoveDirection, outputSpeed);
        }
        
        public void DoMovement(Vector3 moveDir, float speed){
            Debug.Log( outputMoveDirection);;
            controller.Move((Camera.main.transform.rotation.Flatten(0, null, 0) * (moveDir * speed) * Time.deltaTime));
        }

        public void LateUpdate(){
            UpdateGravityLate();
        }

        private void UpdateGravityLate(){
            // Decay jump power
        }
    }
}

