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


[AddComponentMenu("MouseLook ")]
public class SmoothMouseLook : MonoBehaviour
{
    [Tooltip("Give the user control over this?")]
    [SerializeField] bool enable = true;

    // The speed at which we turn (Mouse Sensitivity)
    // mouseSensitivity.x is for left <-> right 
    // mouseSensitivity.y is for   up <-> down
    [Tooltip("How fast to turn when moving the mouse (bigger=faster, X: left<->right, Y: up<->down).")]
    [SerializeField] Vector2 mouseSensitivity = new Vector2(70f, 60f);

    // How much smoothing goes on for each axis
    [Tooltip("How smooth/mushy the mouse movement becomes (bigger=smoother, X: left<->right, Y: up<->down).")]
    [SerializeField] Vector2 smoothing = new Vector2(2f, 2f);

    [Tooltip("How many frames to wait full control is given to the user.")]
    [SerializeField] float startupTime = 100.0f;

    // How far up the head can tilt, measured in angles from the horizon
    // Must be bigger than headLowerAngleLimit
    [Tooltip("How many degrees the head can look up at max.")]
    [SerializeField] float headUpperAngleLimit = 85f;

    // How far down the head can tilt, measured in angles from the horizon
    // Must be smaller than headUpperAngleLimit
    [Tooltip("How many degrees the head can look down at max.")]
    [SerializeField] float headLowerAngleLimit = -80f;

    // Invert the Y Axis of the Mouse if true
    [Tooltip("Invert Mouse Control for up<->down.")]
    [SerializeField] bool InvertAxisY = true;

    // Our current rotation from start in degrees
    float yaw = 0f;
    float pitch = 0f;

    // Stores the orientations of the head and body when the game started
    // We'll derive new orientations by combining these with the variables yaw 
    // and pitch
    Quaternion bodyStartOrientation;
    Quaternion headStartOrientation;

    // A reference to the head object (the object that will rotate up and down)
    // The body is the current (this) object, so there is no variable needed.
    // We don't want to expose this to the interface. Instead we just look for a
    // Child object with type Camera, when the game starts.
    Transform head;

    // Two 2D-Vectors that store both axis of the mouse
    private Vector2 smoothedMouseDelta;



    // Start is called before the first frame update
    void Start()
    {
        // Find the head – this returns the transform parameter of this objects 
        // first Child of type Camera. If none is found head = null 
        head = GetComponentInChildren<Camera>().transform;

        // Cache the orientation of body and head. This errors if head was not 
        // found
        bodyStartOrientation = transform.localRotation;
        headStartOrientation = head.transform.localRotation;

        // Lock and hide the cursor
        Cursor.lockState = CursorLockMode.Locked;
        Cursor.visible = false;
    }



    // A Easing function that smooths the mouse-movement. This is beeing
    // done by making the head follow the mouse not exactly, but 
    // with some sort of a lag. The further away the heads point of focus
    // is compared to the mouse, the faster it will move, once it comes
    // closer, it slows down – this is called Easing.
    //
    // For an intuitive Explaination look here:
    // https://processing.org/examples/easing.html
    private Vector2 EaseMouseDelta(Vector2 mouseDelta)
    {
        // Scale input against the sensitivity setting and multiply that 
        // with the smoothing value.
        mouseDelta *= mouseSensitivity.x * smoothing.x * Time.deltaTime;
        mouseDelta *= mouseSensitivity.y * smoothing.y * Time.deltaTime;

        // Linear Interpolation ("Lerp") between the smoothed Delta from
        // the last round/Frame and the actual mouse Position.
        smoothedMouseDelta.x = Mathf.Lerp(smoothedMouseDelta.x, mouseDelta.x, 1f / smoothing.x);
        smoothedMouseDelta.y = Mathf.Lerp(smoothedMouseDelta.y, mouseDelta.y, 1f / smoothing.y);

        // Return the smoothed 2D-Vector
        return smoothedMouseDelta;
    }



    // Every time Physics updates, update the movemnent of this object.
    // Do this in FixedUpdate to keep pace with physically simulated Objects
    void FixedUpdate()
    {
        // Read the Position-Change between this Frame and the last
        // Note: GetRawAxis gives more sensitivity
        var mouseDelta = new Vector2(Input.GetAxis("Mouse X"), Input.GetAxis("Mouse Y"));

        // Run the Smoothing-Function we defined above on the Mouse
        // Vector we read before and return a smoothed one
        smoothedMouseDelta = EaseMouseDelta(mouseDelta);

        // Flip the vertical Control, if InvertAxisY is true
        if (InvertAxisY)
        {
            smoothedMouseDelta.y *= -1;
        }

        if (enable) {
            // Add the mouse movements to the current value of yaw and pitch
            yaw += smoothedMouseDelta.x;
            pitch += smoothedMouseDelta.y;

            // Scale the values initially to avoid jumps on scene startup
            yaw = SoftStart(yaw);
            pitch = SoftStart(pitch);

            // Clamp pitch so that we can't look directly down or up
            pitch = Mathf.Clamp(pitch, headLowerAngleLimit, headUpperAngleLimit);
        } else {
            yaw   = 0.0f;
            pitch = 0.0f;
        }

        // Compute rotations by rotating around a fixed axis (rotate yaw-degrees
        // around the up direction for the body, and pitch degrees around the
        // right direction for the head).
        // Note: 90 deg need to be added, to get the initial orientation right
        var bodyRotation = Quaternion.AngleAxis(yaw, Vector3.up);
        var headRotation = Quaternion.AngleAxis(pitch, Vector3.left);
        // Debug.LogFormat("bodyRotation: {0}, headRotation: {1}", bodyRotation, headRotation);

        // Finally combine the rotations for body and head with the start rotations
        transform.localRotation = bodyRotation * bodyStartOrientation;
        head.transform.localRotation = headRotation * headStartOrientation;
    }

    float SoftStart(float invalue) {
        if (Time.frameCount < startupTime) {
            return invalue * Mathf.Lerp(0.0f, 1.0f, Time.frameCount/startupTime);
        } else {
            return invalue;
        }
    }
}