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

[RequireComponent(typeof(CharacterController))]
[RequireComponent(typeof(CameraBobber))]
public class PlayerController : MonoBehaviour {
    public float MovementSpeed;
    public float JumpVelocity;
    public float SlideVelocity;
    [Tooltip("How long after falling off a ledge can the player still jump?")]
    public float JumpGracePeriod;
    [Tooltip("Lower values make the movement feel floatier.")]
    public float Antislipperiness = 1;
    [Tooltip("Lower values make the movement feel floatier in air.")]
    public float AntislipperinessInAir = 1;

    [Header("Runtime values")]
    public bool Grounded;
    public Vector3 GroundNormal;

    private CharacterController Character;
    private CameraBobber Bobber;

    private Vector3 GroundVelocity = new Vector3();
    private float FallingVelocity;
    private float LastGroundedTime;
    private float SlideStartTime;

    private void Awake() {
        Character = GetComponent<CharacterController>();
        Bobber = GetComponent<CameraBobber>();
    }

    private void Start() {
        LastGroundedTime = -JumpGracePeriod;
        Grounded = false;
    }

    private void Update() {
        Vector3 CurrentVelocity = new Vector3();
        GroundNormal = RaycastGroundNormal();

        // Sliding
        bool Sliding = Grounded && Vector3.Angle(GroundNormal, Vector3.up) > Character.slopeLimit;

        // Groundedness stuff and gravity
        if (Character.isGrounded && !Sliding) {
            FallingVelocity = Mathf.Max(0, FallingVelocity);
            LastGroundedTime = Time.time;
        } else {
            FallingVelocity += Physics.gravity.y * Time.deltaTime;
        }
        Grounded = Time.time - LastGroundedTime <= JumpGracePeriod && FallingVelocity <= 0;

        // Jumping
        if (Input.GetButton("Jump") && Grounded) {
            FallingVelocity = JumpVelocity;
            Grounded = false;
        }
        CurrentVelocity += FallingVelocity * Vector3.up;

        float SlippyFactor = Grounded ? Antislipperiness : AntislipperinessInAir;

        // Subtract friction
        float FrictionFactor = Mathf.Max(GroundVelocity.magnitude, 1);
        float Friction = Mathf.Min(GroundVelocity.magnitude, FrictionFactor * SlippyFactor * Time.deltaTime);
        GroundVelocity -= Friction * GroundVelocity.normalized;

        // Add player input momentum
        float TargetSpeed = MovementSpeed; // Tweak this to enable running or stuff
        Vector3 Move = transform.forward * Input.GetAxis("Vertical") + transform.right * Input.GetAxis("Horizontal");
        // If Move magnitude is < 0, it's probably an analog stick, so scale target speed accordingly.
        TargetSpeed *= Mathf.Min(1, Move.magnitude);

        if (Move.magnitude > 0) {
            Move.Normalize();
            if (Grounded) {
                // Slow down when moving on slopes
                TargetSpeed *= Vector3.Dot(Vector3.up, GroundNormal);
            }
            float CurrentSpeed = Vector3.Dot(Move, GroundVelocity);
            float SpeedDiff = TargetSpeed - CurrentSpeed;
            float Acceleration = Mathf.Min(SpeedDiff, TargetSpeed * SlippyFactor * Time.deltaTime);
            GroundVelocity += Move * Acceleration;
        }
        CurrentVelocity += GroundVelocity;

        // Sliiide
        if (Sliding) {
            CurrentVelocity = Vector3.ProjectOnPlane(CurrentVelocity, GroundNormal);
            CurrentVelocity += Vector3.ProjectOnPlane(-Vector3.up, GroundNormal) * SlideVelocity;
        }

        Bobber.BobMagnitude = Mathf.Lerp(Bobber.BobMagnitude, TargetSpeed > 0 ? GroundVelocity.magnitude / TargetSpeed : 1, 5f * Time.deltaTime);
        Bobber.Moving = GroundVelocity.magnitude > 0.1f;
        Bobber.InAir = !Grounded;

        Character.Move(CurrentVelocity * Time.deltaTime);
    }

    private Vector3 RaycastGroundNormal() {
        RaycastHit Hit;
        if (Physics.CapsuleCast(
                transform.position + Vector3.up * Character.radius,
                transform.position + Vector3.up * (Character.height - Character.radius),
                Character.radius, -Vector3.up, out Hit, Character.skinWidth * 2)) {
            return Hit.normal;
        } else {
            return Vector3.up;
        }
    }
}
