using UnityEngine;

namespace NBF
{
    [RequireComponent(typeof(CharacterController))]
    [RequireComponent(typeof(AudioSource))]
    public class FirstPersonController : MonoBehaviour
    {
        [SerializeField] private bool m_IsWalking;

        [SerializeField] public float m_WalkSpeed;

        [SerializeField] public float m_RunSpeed;

        [SerializeField] [Range(0f, 1f)] private float m_RunstepLenghten;

        [SerializeField] private float m_JumpSpeed;

        [SerializeField] private float m_StickToGroundForce;

        [SerializeField] private float m_GravityMultiplier;

        [SerializeField] public MouseLook m_MouseLook;

        [SerializeField] private bool m_UseFovKick;

        [SerializeField] private FOVKick m_FovKick = new FOVKick();

        [SerializeField] private bool m_UseHeadBob;

        [SerializeField] private CurveControlledBob m_HeadBob = new CurveControlledBob();

        [SerializeField] private LerpControlledBob m_JumpBob = new LerpControlledBob();

        [SerializeField] private float m_StepInterval;

        [SerializeField] private AudioClip[] m_FootstepSounds;

        [SerializeField] private AudioClip[] m_FootstepSoundsBridge;

        [SerializeField] private AudioClip[] m_FootstepSoundsStone;

        [SerializeField] private AudioClip m_JumpSound;

        [SerializeField] private AudioClip m_LandSound;

        private Camera m_Camera;

        private bool m_Jump;

        public bool frezzeRotation;

        public bool frezzePosition;

        public bool rotateMouseInFixedUpdate = true;

        public Vector2 m_Input;

        public Vector3 m_MoveDir = Vector3.zero;

        public CharacterController m_CharacterController;

        private CollisionFlags m_CollisionFlags;

        private bool m_PreviouslyGrounded;

        private Vector3 m_OriginalCameraPosition;

        private float m_StepCycle;

        private float m_NextStep;

        private bool m_Jumping;

        private AudioSource m_AudioSource;

        private string groundTypeTag = "";

        public float horizontal;

        public float vertical;

        public bool isJumping;

        public bool isRuning;

        public bool notMove;

        public bool isWater;

        
        private void Start()
        {
            m_CharacterController = GetComponent<CharacterController>();
            m_Camera = Camera.main;
            m_OriginalCameraPosition = m_Camera.transform.localPosition;
            m_FovKick.Setup(m_Camera);
            m_HeadBob.Setup(m_Camera, m_StepInterval);
            m_StepCycle = 0f;
            m_NextStep = m_StepCycle / 2f;
            m_Jumping = false;
            m_AudioSource = GetComponent<AudioSource>();
            m_MouseLook.Init(transform, m_Camera.transform);
        }

        private void Update()
        {
            if (!frezzeRotation && !rotateMouseInFixedUpdate)
            {
                RotateView();
            }

            if (!m_Jump)
            {
                m_Jump = isJumping;
            }

            if (!m_PreviouslyGrounded && m_CharacterController.isGrounded)
            {
                StartCoroutine(m_JumpBob.DoBobCycle());
                PlayLandingSound();
                m_MoveDir.y = 0f;
                m_Jumping = false;
            }

            if (!m_CharacterController.isGrounded && !m_Jumping && m_PreviouslyGrounded)
            {
                m_MoveDir.y = 0f;
            }

            m_PreviouslyGrounded = m_CharacterController.isGrounded;
        }

        private void PlayLandingSound()
        {
            m_AudioSource.clip = m_LandSound;
            m_AudioSource.Play();
            m_NextStep = m_StepCycle + 0.5f;
        }

        private void FixedUpdate()
        {
            if (!frezzeRotation && rotateMouseInFixedUpdate)
            {
                RotateView();
            }

            if (frezzePosition)
            {
                return;
            }

            GetInput(out var speed);
            
            Vector3 vector = transform.forward * m_Input.y + transform.right * m_Input.x;
            Physics.SphereCast(transform.position, m_CharacterController.radius, Vector3.down, out var hitInfo,
                m_CharacterController.height / 2f, -1, QueryTriggerInteraction.Ignore);
            vector = Vector3.ProjectOnPlane(vector, hitInfo.normal).normalized;
            m_MoveDir.x = vector.x * speed;
            m_MoveDir.z = vector.z * speed;
            if (m_CharacterController.isGrounded)
            {
                m_MoveDir.y = 0f - m_StickToGroundForce;
                if (m_Jump && !isWater)
                {
                    if ((bool)transform.parent && transform.parent.gameObject.layer == LayerMask.NameToLayer("Boat"))
                    {
                        return;
                    }

                    m_MoveDir.y = m_JumpSpeed;
                    PlayJumpSound();
                    m_Jump = false;
                    m_Jumping = true;
                }
            }
            else
            {
                m_MoveDir += Physics.gravity * m_GravityMultiplier * Time.fixedDeltaTime;
            }

            if (!notMove)
            {
                m_CollisionFlags = m_CharacterController.Move(m_MoveDir * Time.fixedDeltaTime);
            }

            ProgressStepCycle(speed);
            UpdateCameraPosition(speed);
            m_MouseLook.UpdateCursorLock();
        }

        private void PlayJumpSound()
        {
            m_AudioSource.clip = m_JumpSound;
            m_AudioSource.Play();
        }

        private void ProgressStepCycle(float speed)
        {
            if (m_CharacterController.velocity.sqrMagnitude > 0f && (m_Input.x != 0f || m_Input.y != 0f))
            {
                m_StepCycle +=
                    (m_CharacterController.velocity.magnitude + speed * (m_IsWalking ? 1f : m_RunstepLenghten)) *
                    Time.fixedDeltaTime;
            }

            if (m_StepCycle > m_NextStep)
            {
                m_NextStep = m_StepCycle + m_StepInterval;
                PlayFootStepAudio();
            }
        }

        private void PlayFootStepAudio()
        {
            if (!m_CharacterController.isGrounded)
            {
                return;
            }

            if (groundTypeTag == "Bridge" || groundTypeTag == "Boat")
            {
                if (m_FootstepSoundsBridge.Length != 0)
                {
                    int num = Random.Range(1, m_FootstepSoundsBridge.Length);
                    m_AudioSource.clip = m_FootstepSoundsBridge[num];
                    m_AudioSource.PlayOneShot(m_AudioSource.clip, 0.3f);
                    m_FootstepSoundsBridge[num] = m_FootstepSoundsBridge[0];
                    m_FootstepSoundsBridge[0] = m_AudioSource.clip;
                }
            }
            else if (groundTypeTag == "Stone")
            {
                if (m_FootstepSoundsStone.Length != 0)
                {
                    int num2 = Random.Range(1, m_FootstepSoundsStone.Length);
                    m_AudioSource.clip = m_FootstepSoundsStone[num2];
                    m_AudioSource.PlayOneShot(m_AudioSource.clip, 0.3f);
                    m_FootstepSoundsStone[num2] = m_FootstepSoundsStone[0];
                    m_FootstepSoundsStone[0] = m_AudioSource.clip;
                }
            }
            else if (m_FootstepSounds.Length != 0)
            {
                int num3 = Random.Range(1, m_FootstepSounds.Length);
                m_AudioSource.clip = m_FootstepSounds[num3];
                m_AudioSource.PlayOneShot(m_AudioSource.clip, 0.3f);
                m_FootstepSounds[num3] = m_FootstepSounds[0];
                m_FootstepSounds[0] = m_AudioSource.clip;
            }
        }

        private void UpdateCameraPosition(float speed)
        {
            if (m_UseHeadBob)
            {
                Vector3 localPosition;
                if (m_CharacterController.velocity.magnitude > 0f && m_CharacterController.isGrounded)
                {
                    m_Camera.transform.localPosition = m_HeadBob.DoHeadBob(m_CharacterController.velocity.magnitude +
                                                                           speed *
                                                                           (m_IsWalking ? 1f : m_RunstepLenghten));
                    localPosition = m_Camera.transform.localPosition;
                    localPosition.y = m_Camera.transform.localPosition.y - m_JumpBob.Offset();
                }
                else
                {
                    localPosition = m_Camera.transform.localPosition;
                    localPosition.y = m_OriginalCameraPosition.y - m_JumpBob.Offset();
                }

                m_Camera.transform.localPosition = localPosition;
            }
        }

        private void GetInput(out float speed)
        {
            bool isWalking = m_IsWalking;
            m_IsWalking = !isRuning;
            speed = (m_IsWalking ? m_WalkSpeed : m_RunSpeed);
            m_Input = new Vector2(horizontal, vertical);
            if (m_Input.sqrMagnitude > 1f)
            {
                m_Input.Normalize();
            }

            if (m_IsWalking != isWalking && m_UseFovKick && m_CharacterController.velocity.sqrMagnitude > 0f)
            {
                StopAllCoroutines();
                StartCoroutine((!m_IsWalking) ? m_FovKick.FOVKickUp() : m_FovKick.FOVKickDown());
            }
        }

        public void RotateView()
        {
            m_MouseLook.LookRotation(transform, m_Camera.transform);
        }
        
        private void OnControllerColliderHit(ControllerColliderHit hit)
        {
            Rigidbody attachedRigidbody = hit.collider.attachedRigidbody;
            groundTypeTag = hit.collider.tag;

            if (m_CollisionFlags != CollisionFlags.Below && !(attachedRigidbody == null))
            {
                _ = attachedRigidbody.isKinematic;
            }
        }

        private void OnTriggerStay(Collider other)
        {
            if (other.CompareTag("Water"))
            {
                isWater = true;
            }
        }

        private void OnTriggerExit(Collider other)
        {
            if (other.CompareTag("Water"))
            {
                isWater = false;
            }
        }
    }
}