using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

namespace JamUtils.FirstPersonCharacterController.RigidbodyVelocity
{

    // һ˳ƽɫ
    public class FPSCharacterController : MonoBehaviour
    {
        [Flags]
        public enum CharacterModule
        {
            None,
            LookAround = 1 << 0,  // 
            MoveAround = 1 << 1,  // ˮƽƶ
            MoveInAir = 1 << 2,  // ƶ
            Dodge = 1 << 3,  // 
            Rush = 1 << 4,  // 
            WalkOnSlope = 1 << 5 | 1 << 1,  // бƶ
            WalkOnStairs = 1 << 6 | 1 << 1,  // ¥ƶ
            WallRun = 1 << 7,  // ߱
            Jump = 1 << 8,  // Ծ
            WallJump = 1 << 9,  // 
            Slide = 1 << 10, // 
            Shot = 1 << 11, // 
            Step = 1 << 12, // Ų
            PullTrick = 1 << 13, // 
            ExtraGravity = 1 << 14,  // 
            Crouch = 1 << 15,
        }

        [SerializeField] private CharacterModule m_Modules;

        [SerializeField] private Transform m_Eye;

        [SerializeField] private PlayerBody m_Body;
        [SerializeField] private GroundChecker m_GroundChecker;
        [SerializeField] private WallChecker m_WallChecker;

        [SerializeField] private MainCameraFollow m_Camera;

        #region Modules

        [Header("Look Around")]
        [SerializeField] private float m_LookSensitive = 1000f;
        [Range(0.01f, 1)]
        [SerializeField] private float m_LookSmooth = 0.2f;
        private float m_CameraRotation;
        private float m_BodyRotation;

        [Header("Move Around")]
        [SerializeField] private float m_MoveSpeed = 100f;
        [Range(0.01f, 1)]
        [SerializeField] private float m_MoveSmooth = 0.2f;
        private Vector3 m_MoveDirection;
        private bool m_ReadyMoveAround = false;
        [SerializeField] private bool m_HeadBobbing = false;

        [Header("Move In Air")]
        [SerializeField] private float m_MoveSpeedInAir = 50f;
        [SerializeField] private float m_MoveInAirSmooth = 0.2f;
        private Vector3 m_MoveInAirDirection;

        [Header("Jump")]
        [SerializeField] private float m_JumpPower = 300f;
        private bool m_ReadyToJump = false;

        [Header("Shot")]
        [SerializeField] private Transform m_Muzzle;
        [SerializeField] private LayerMask m_HittableLayers;
        [SerializeField] private float m_ShotInfiniteDistance = 100f;

        [Header("WallJump")]
        [SerializeField] private float m_WallJumpForce = 1;
		[SerializeField] private float m_WallJumpPower = 1000;
        private bool m_ReadyWallJump = false;

        [Header("WallRun")]
        [SerializeField] private float m_WallRunSpeed = 1;

		[Header("ExtraGravity")]
        [SerializeField] private Vector3 m_ExtraGravity;

		[Header("WalkOnStairs")]
		[SerializeField] private Transform m_Lower;
		[SerializeField] private Transform m_Upper;
		[SerializeField] private float m_StepSmooth;

		#endregion

		public Func<RaycastHit, bool> checkHit;
        public Action<Vector3, Transform> shootTarget;

        private Rigidbody m_Rigidbody;

        private bool m_LockCursor = false;
        public bool lockCursor
        {
            get
            {
                return m_LockCursor;
            }
            set
            {
                m_LockCursor = value;
                if (value)
                {
                    Cursor.lockState = CursorLockMode.Locked;
                }
                else
                {
                    Cursor.lockState = CursorLockMode.None;
                }
            }
        }

        bool IsModuleActive(CharacterModule module)
        {
            return (module & m_Modules) != 0;
        }

        void ActivateModule(CharacterModule module)
        {
            m_Modules |= module;
        }

        void LookAround()
        {
            if (!IsModuleActive(CharacterModule.LookAround))
                return;

            float mouseX = Input.GetAxis("Mouse X");
            float mouseY = Input.GetAxis("Mouse Y");

            //mouseX = -0.1f; // test jittery

            m_CameraRotation -= mouseY * Time.deltaTime * m_LookSensitive;
            m_CameraRotation = Mathf.Clamp(m_CameraRotation, -90, 90);
            Quaternion rot = Quaternion.Euler(m_CameraRotation, 0, 0);
            m_Eye.localRotation = Quaternion.Slerp(m_Eye.localRotation, rot, m_LookSmooth );

            m_BodyRotation += mouseX * Time.deltaTime * m_LookSensitive;
            rot = Quaternion.Euler(0, m_BodyRotation, 0);
            transform.localRotation = Quaternion.Slerp(transform.localRotation, rot, m_LookSmooth);
        }

        void MoveAroundUpdate()
        {
            if (!IsModuleActive(CharacterModule.MoveAround))
                return;

            if (!m_GroundChecker.isOnGround)
                return;

            float moveX = Input.GetAxisRaw("Horizontal");
            float moveZ = Input.GetAxisRaw("Vertical");

            Vector3 right = transform.right;
            Vector3 forward = transform.forward;

            Vector3 dir = right * moveX + forward * moveZ;

            if (IsModuleActive(CharacterModule.WalkOnSlope))
            {
                if (m_GroundChecker.isOnGround)
                {
                    RaycastHit hitInfo;
                    if (Physics.Raycast(m_GroundChecker.foot.position, Vector3.down, out hitInfo))
                    {
                        Vector3 normal = hitInfo.normal;
                        dir = Vector3.ProjectOnPlane(dir, normal);
                        GizmosHandle.Instance.DoGizmos(() =>
                        {
                            Gizmos.DrawLine(hitInfo.point + new Vector3(0, 0.1f, 0), hitInfo.point + dir);
                        });
                    }
                }
            }

            dir = dir.normalized;

			m_MoveDirection = Vector3.Slerp(m_MoveDirection, dir, 1f);
		}

        void MoveAroundFixedUpdate()
        {
            if (!IsModuleActive(CharacterModule.MoveAround))
                return;

            if (!m_GroundChecker.isOnGround)
                return;

            float vy = m_Rigidbody.velocity.y;
            Vector3 velocity = new Vector3(m_MoveDirection.x * Time.deltaTime * m_MoveSpeed, vy, m_MoveDirection.z * Time.deltaTime * m_MoveSpeed);

			Vector3 rigidVel = m_Rigidbody.velocity;

			// ٶSlerpȽΣ
			//if (Vector3.Angle(rigidVel, velocity) > 90)
			//{
			//	m_Rigidbody.velocity = Vector3.Lerp(rigidVel, velocity, m_MoveSmooth); 
			//}
			//else
			//{
			//	m_Rigidbody.velocity = Vector3.Slerp(rigidVel, velocity, m_MoveSmooth); 
			//}

			m_Rigidbody.velocity = Vector3.Lerp(rigidVel, velocity, m_MoveSmooth);
		}

		void MoveInAirUpdate()
        {
            if (!IsModuleActive(CharacterModule.MoveInAir))
                return;

            if (m_GroundChecker.isOnGround)
                return;

            float moveX = Input.GetAxisRaw("Horizontal");
            float moveZ = Input.GetAxisRaw("Vertical");

            m_MoveInAirDirection = Vector3.ClampMagnitude(transform.right * moveX + transform.forward * moveZ, 1);
        }

        void MoveInAirFixedUpdate()
        {
            if (!IsModuleActive(CharacterModule.MoveInAir))
                return;

            if (m_GroundChecker.isOnGround)
                return;

            if (m_MoveInAirDirection.magnitude == 0f)
                return;

            float vy = m_Rigidbody.velocity.y;
            Vector3 velocity = new Vector3(m_MoveInAirDirection.x * Time.deltaTime * m_MoveSpeedInAir, vy, m_MoveInAirDirection.z * Time.deltaTime * m_MoveSpeedInAir);

            m_Rigidbody.velocity = Vector3.Lerp(m_Rigidbody.velocity, velocity, m_MoveInAirSmooth);
        }

        void Jump()
        {
            if (!IsModuleActive(CharacterModule.Jump))
                return;

            if (!m_GroundChecker.isOnGround)
                return;

            if (Input.GetButtonDown("Jump"))
            {
                m_ReadyToJump = true;
            }
        }

        void JumpFixedUpdate()
        {
            if(m_ReadyToJump)
            {
                m_ReadyToJump = false;
                m_Rigidbody.AddForce(Vector3.up * m_JumpPower, ForceMode.Acceleration);
            }
        }

        void Dodge()
        {
            if (!IsModuleActive(CharacterModule.Dodge))
                return;

            if (Input.GetKeyDown(KeyCode.LeftShift))
            {

            }
        }

        void DodgeFixed()
        {

        }

        void Shot()
        {
            if (!IsModuleActive(CharacterModule.Shot))
                return;

            if (Input.GetButtonDown("Fire1"))
            {
                Vector3 hitPoint = GetHitPoint();
                if (shootTarget != null)
                    shootTarget(hitPoint, m_Muzzle);
            }
        }

        Vector3 GetHitPoint()
        {
            RaycastHit[] hits = Physics.RaycastAll(m_Eye.position, m_Eye.forward, m_HittableLayers, (int)QueryTriggerInteraction.Ignore);
            if (hits.Length < 1)
            {
                return m_Eye.position + m_Eye.forward * m_ShotInfiniteDistance;
            }
            else
            {
                for (int i = 0; i < hits.Length; ++i)
                {
                    if (checkHit != null && checkHit(hits[i]))
                    {
                        return hits[i].point;
                    }
                }
            }
            return hits[0].point;
        }

        void WallJump()
        {
			if (!IsModuleActive(CharacterModule.WallJump))
				return;

			if (!m_WallChecker.IsOnWall)
				return;

			if (m_GroundChecker.isOnGround)
				return;

			if (Input.GetButtonDown("Jump"))
			{
                m_ReadyWallJump = true;
			}
		}

        void WallJumpFixedUpdate()
        {
            if(m_ReadyWallJump)
            {
                m_ReadyWallJump = false;
                Vector3 poc;
                if (m_WallChecker.GetCollisionPoint(out poc))
                {
                    Vector3 dir = Vector3.ClampMagnitude(transform.position - poc, 1f);
                    Vector3 wallJumpDirection = new Vector3(dir.x, 1, dir.z);
                    m_Rigidbody.velocity = Vector3.zero;
                    m_Rigidbody.AddForce(wallJumpDirection * m_WallJumpPower);
                }
            }
        }

        void WallRun()
        {
            if (!IsModuleActive(CharacterModule.WallRun))
                return;

            if (!m_WallChecker.IsOnWall)
                return;

        }

        void PullTrick()
        {

        }

        void ExtraGravity()
        {
            if (!IsModuleActive(CharacterModule.ExtraGravity))
                return;

            m_Rigidbody.AddForce(m_ExtraGravity, ForceMode.Acceleration);
        }

		void WalkOnStairs()
		{
			//int layermask = ~(1 << LayerMask.NameToLayer("Player"));

			//RaycastHit hitLower;
			//if (Physics.Raycast(m_Lower.position, transform.TransformDirection(Vector3.forward), out hitLower, 0.6f, layermask))
			//{
			//	RaycastHit hitUpper;
			//	if (!Physics.Raycast(m_Upper.position, transform.TransformDirection(Vector3.forward), out hitUpper, 0.7f, layermask))
			//	{
			//		m_Rigidbody.position += new Vector3(0f, m_StepSmooth * Time.deltaTime, 0f);
			//	}
			//}
		}


		void SetCamera ()
        {
            m_Camera.SetCameraPositionAndRotation(m_Eye);
        }

        private void Awake()
		{
			m_Rigidbody = GetComponent<Rigidbody>();
		}

		private void Start()
		{
            m_CameraRotation = 0;

            lockCursor = true;
        }

		private void Update()
		{
            LookAround();
            MoveAroundUpdate();
            MoveInAirUpdate();
            Jump();
            Dodge();
            Shot();
            WallJump();
            PullTrick();

            SetCamera();
        }

		private void FixedUpdate()
		{
			WalkOnStairs();
			MoveAroundFixedUpdate();
            MoveInAirFixedUpdate();
            DodgeFixed();
            ExtraGravity();
            JumpFixedUpdate();
            WallJumpFixedUpdate();
        }

    }
}
