using System.Collections;
using UnityEngine;

public class PlayerMovement : MonoBehaviour
{
    //移动相关的参数
    [Header("Movement")]
    public float moveSpeed;
    public float xtraSpeedCap;
    public float xtraSpeedCapSlope;
    public float xtraSpeedCapReal;
    public float xtraSpeedCapSlopeReal;
    public float slideSpeed;

    private float desiredMoveSpeed;
    private float lastDesiredMoveSpeed;
    public float lastFallSpeed;

    //强度参数
    public float speedIncreaseMultiplier;
    public float slopeIncreaseMultiplier;

    public float groundDrag;

    private float desiredDurationPlus = 0.8f;
    private float elapsedPlus;
    float pcntCpltPlus;

    //跳跃相关的参数
    public float jumpForce;
    public float jumpCooldown;
    public float airMarkiplier;
    bool readyToJump;

    //键位相关的参数
    [Header("Keybinds")]
    public KeyCode jumpKey = KeyCode.Space;
    public KeyCode slowKey = KeyCode.LeftShift;

    //检测是否站在地面的参数
    [Header("Ground Check")]
    public float playerHeight;
    public LayerMask whatIsGroundnt;
    public bool grounded;

    //倾斜感知相关的参数
    [Header("Slope Handling")]
    public float maxSlopeAngle;
    private RaycastHit slopeHit;
    private bool exitingSlope;

    public Transform orientation;

    float horizontalInput;
    float verticalInput;

    Vector3 moveDirection;

    Rigidbody rb;

    public Camera camme;

    public FallingAcceleration fa;

    public MovementState state;

    public enum MovementState
    {
        moving,
        sliding,
        air
    }

    public bool sliding;

    private void Start()
    {
        rb = GetComponent<Rigidbody>();
        rb.freezeRotation = true;
        readyToJump = true;
        xtraSpeedCap = xtraSpeedCapReal;
        xtraSpeedCapSlope = xtraSpeedCapSlopeReal;
    }

    private void Update()
    {
        //检测是否站在地上
        grounded = Physics.Raycast(transform.position, Vector3.down, playerHeight * 0.5f + 0.3F, ~whatIsGroundnt);

        MyInput();
        SpeedControl();
        StateHandler();

        //调整阻尼
        if (grounded)
            rb.linearDamping = groundDrag;
        else
            rb.angularDamping = 0;

        if (!grounded)
        {
            lastFallSpeed = fa.fallTimer * fa.fallAcceleration;
        }
        else if (grounded && !sliding)
        {
            lastFallSpeed = 0;
        }

        if (!sliding)
        {
            xtraSpeedCap = xtraSpeedCapReal;
            xtraSpeedCapSlope = xtraSpeedCapSlopeReal;
        }
        else if (sliding && grounded)
        {
            xtraSpeedCap = xtraSpeedCapReal + (lastFallSpeed / 26f);
            xtraSpeedCapSlope = xtraSpeedCapSlopeReal + (lastFallSpeed / 20f);
        }

        //FOV缩放
        if (sliding && grounded && rb.mass == 1 || !grounded)
        {
            elapsedPlus += Time.deltaTime;
            pcntCpltPlus = elapsedPlus / desiredDurationPlus;
            camme.fieldOfView = Mathf.Lerp(PlayerPrefs.GetFloat("FOVLevel", 100f), PlayerPrefs.GetFloat("FOVLevel", 100f) + 10, pcntCpltPlus);
            if (pcntCpltPlus >= 1)
            {
                elapsedPlus = 0;
            }
        }
        else if (grounded && !sliding && rb.mass == 1)
        {
            camme.fieldOfView = PlayerPrefs.GetFloat("FOVLevel", 100f);
        }
    }

    private void FixedUpdate()
    {
        MovePlayer();
    }

    private void MyInput()
    {
        horizontalInput = Input.GetAxisRaw("Horizontal");
        verticalInput = Input.GetAxisRaw("Vertical");

        //检查是否可以跳跃
        if (grounded && sliding && Input.GetKey(jumpKey) && readyToJump)
        {
            readyToJump = false;

            Jump();

            rb.AddForce(moveDirection * (jumpForce), ForceMode.Impulse);

            Invoke(nameof(ResetJump), jumpCooldown);
        }
        else if (Input.GetKey(jumpKey) && readyToJump && grounded)
        {
            readyToJump = false;

            Jump();

            Invoke(nameof(ResetJump), jumpCooldown);
        }
    }

    private void StateHandler()
    {
        //检测运动状态
        if (sliding)
        {
            state = MovementState.sliding;

            if (OnSlope() && rb.linearVelocity.y < 0.1f)
                desiredMoveSpeed = slideSpeed;

            else
                desiredMoveSpeed = moveSpeed;
        }

        //运动状态
        else if (grounded)
        {
            state = MovementState.moving;
            desiredMoveSpeed = moveSpeed;
        }

        //空中状态
        else
        {
            state = MovementState.air;
        }

        //检查是否有速度改变
        if (Mathf.Abs(desiredMoveSpeed - lastDesiredMoveSpeed) > 4f && moveSpeed != 0 && !sliding && (xtraSpeedCap != xtraSpeedCapReal))
        {
            StopAllCoroutines();
            StartCoroutine(SmoothlyLerpMoveSpeed());
        }
    }

    private IEnumerator SmoothlyLerpMoveSpeed()
    {
        //平滑改变速度
        float time = 0;
        float difference = Mathf.Abs(desiredMoveSpeed - moveSpeed);
        float startValue = moveSpeed;

        while (time < difference)
        {
            moveSpeed = Mathf.Lerp(startValue, desiredMoveSpeed, time / difference);

            if (OnSlope())
            {
                float slopeAngle = Vector3.Angle(Vector3.up, slopeHit.normal);
                float slopeAngleIncrease = 1 + (slopeAngle / 90f);

                time += Time.deltaTime * speedIncreaseMultiplier * slopeIncreaseMultiplier * slopeAngleIncrease;
            }
            else
                time += Time.deltaTime * speedIncreaseMultiplier;

            yield return null;
        }

        moveSpeed = desiredMoveSpeed;
    }

    private void MovePlayer()
    {
        //设置移动指向
        moveDirection = orientation.forward * verticalInput + orientation.right * horizontalInput;

        //设置重力
        if (Input.GetKey(slowKey) && grounded && !sliding && !OnSlope())
        {
            rb.mass = 1.3f;
        }
        else if (Input.GetKey(slowKey) && grounded && !sliding && OnSlope())
        {
            rb.mass = 5f;
        }
        else
        {
            rb.mass = 1;
        }

        //倾斜处理
        if (OnSlope() && !exitingSlope)
        {
            rb.AddForce(GetSlopeMoveDirection(moveDirection) * moveSpeed * 20f, ForceMode.Force);

            if (rb.linearVelocity.y != 0)
                rb.AddForce(Vector3.down * 200f, ForceMode.Force);
        }
        //在地面上处理
        else if (grounded)
            rb.AddForce(moveDirection.normalized * moveSpeed * 10f, ForceMode.Force);
        //在空中处理
        else if (!grounded)
            rb.AddForce(moveDirection.normalized * moveSpeed * 10f * airMarkiplier, ForceMode.Force);

        //取消重力
        rb.useGravity = !OnSlope();
    }

    private void SpeedControl()
    {
        //限速
        if (OnSlope() && !exitingSlope)
        {
            if (rb.linearVelocity.magnitude > (moveSpeed + xtraSpeedCapSlope))
                rb.linearVelocity = rb.linearVelocity.normalized * moveSpeed;
        }
        else
        {
            Vector3 flatVel = new Vector3(rb.linearVelocity.x, 0f, rb.linearVelocity.z);

            //限速
            if (flatVel.magnitude > (moveSpeed + xtraSpeedCap))
            {
                Vector3 limitedVel = flatVel.normalized * moveSpeed;
                rb.linearVelocity = new Vector3(limitedVel.x, rb.linearVelocity.y, limitedVel.z);
            }
        }
    }

    private void Jump()
    {
        exitingSlope = true;

        //清除y速度
        rb.linearVelocity = new Vector3(rb.linearVelocity.x, 0f, rb.linearVelocity.z);

        rb.AddForce(transform.up * jumpForce, ForceMode.Impulse);
    }

    private void ResetJump()
    {
        readyToJump = true;

        exitingSlope = false;
    }

    public bool OnSlope()
    {
        if (Physics.Raycast(transform.position, Vector3.down, out slopeHit, playerHeight * 0.5f + 0.3f))
        {
            float angle = Vector3.Angle(Vector3.up, slopeHit.normal);
            return angle < maxSlopeAngle && angle != 0;
        }

        return false;
    }

    public Vector3 GetSlopeMoveDirection(Vector3 direction)
    {
        return Vector3.ProjectOnPlane(direction, slopeHit.normal).normalized;
    }
}