using UnityEngine;

namespace Cameras
{
    public class SceneLikeCamera : MonoBehaviour
    {
        [Header("Focus Object")]
        [SerializeField, Tooltip("Enable double-click to focus on objects?")]
        private bool doFocus = false;
        [SerializeField] private float focusLimit = 100f;
        [SerializeField] private float minFocusDistance = 5.0f;
        private float doubleClickTime = .15f;
        private float cooldown = 0;
        [Header("Undo - Only undoes the Focus Object - The keys must be pressed in order.")]
        [SerializeField] private KeyCode firstUndoKey = KeyCode.LeftControl;
        [SerializeField] private KeyCode secondUndoKey = KeyCode.Z;

        [Header("Movement")]
        [SerializeField] private float moveSpeed = 1.0f;
        [SerializeField] private float rotationSpeed = 5f;
        [SerializeField] private float zoomSpeed = 15.0f;

        //Cache last pos and rot be able to undo last focus object action.
        Quaternion prevRot = new Quaternion();
        Vector3 prevPos = new Vector3();

        [Header("Axes Names")]
        [SerializeField, Tooltip("Otherwise known as the vertical axis")] private string mouseY = "Mouse Y";
        [SerializeField, Tooltip("AKA horizontal axis")] private string mouseX = "Mouse X";
        [SerializeField, Tooltip("The axis you want to use for zoom.")] private string zoomAxis = "Mouse ScrollWheel";

        [Header("Move Keys")]
        [SerializeField] private KeyCode forwardKey = KeyCode.W;
        [SerializeField] private KeyCode backKey = KeyCode.S;
        [SerializeField] private KeyCode leftKey = KeyCode.A;
        [SerializeField] private KeyCode rightKey = KeyCode.D;

        [Header("Flat Move"), Tooltip("Instead of going where the camera is pointed, the camera moves only on X and Z axes.")]
        [SerializeField] private KeyCode flatMoveKey = KeyCode.LeftShift;

        [Header("Anchored Movement"), Tooltip("By default in scene-view, this is done by right-clicking for rotation or middle mouse clicking for up and down")]
        [SerializeField] private KeyCode anchoredMoveKey = KeyCode.Mouse2;

        [SerializeField] private KeyCode anchoredRotateKey = KeyCode.Mouse1;

        private void Start()
        {
            SavePosAndRot();
        }

        void Update()
        {
            if (!doFocus)
                return;

            //Double click for focus 
            if (cooldown > 0 && Input.GetKeyDown(KeyCode.Mouse0))
                FocusObject();
            if (Input.GetKeyDown(KeyCode.Mouse0))
                cooldown = doubleClickTime;

            //--------UNDO FOCUS---------
            if (Input.GetKey(firstUndoKey))
            {
                if (Input.GetKeyDown(secondUndoKey))
                    GoBackToLastPosition();
            }

            cooldown -= Time.deltaTime;
        }

        private void FixedUpdate()
        {
            Vector3 move = Vector3.zero;

            //Move and rotate the camera

            if (Input.GetKey(forwardKey))
                move += Vector3.forward * moveSpeed;
            if (Input.GetKey(backKey))
                move += Vector3.back * moveSpeed;
            if (Input.GetKey(leftKey))
                move += Vector3.left * moveSpeed;
            if (Input.GetKey(rightKey))
                move += Vector3.right * moveSpeed;

            //By far the simplest solution I could come up with for moving only on the Horizontal plane - no rotation, just cache y
            if (Input.GetKey(flatMoveKey))
            {
                float origY = transform.position.y;

                transform.Translate(move);
                transform.position = new Vector3(transform.position.x, origY, transform.position.z);

                return;
            }

            float mouseMoveY = Input.GetAxis(mouseY);
            float mouseMoveX = Input.GetAxis(mouseX);

            //Move the camera when anchored
            if (Input.GetKey(anchoredMoveKey))
            {
                move += Vector3.up * mouseMoveY * -moveSpeed;
                move += Vector3.right * mouseMoveX * -moveSpeed;
            }

            //Rotate the camera when anchored
            /*if (Input.GetKey(anchoredRotateKey))
            {
                transform.RotateAround(transform.position, transform.right, mouseMoveY * -rotationSpeed);
                transform.RotateAround(transform.position, Vector3.up, mouseMoveX * rotationSpeed);
            }
            */
            transform.Translate(move);
        }

        private void LateUpdate()
        {
            float mouseMoveY = Input.GetAxis(mouseY);
            float mouseMoveX = Input.GetAxis(mouseX);
            //Rotate the camera when anchored
            if (Input.GetKey(anchoredRotateKey))
            {
                transform.RotateAround(transform.position, transform.right, mouseMoveY * -rotationSpeed);
                transform.RotateAround(transform.position, Vector3.up, mouseMoveX * rotationSpeed);
            }

            //Scroll to zoom
            float mouseScroll = Input.GetAxis(zoomAxis);
            transform.Translate(Vector3.forward * mouseScroll * zoomSpeed);
        }

        private void FocusObject()
        {
            //To be able to undo
            SavePosAndRot();

            //If we double-clicked an object in the scene, go to its position
            Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
            RaycastHit hit;

            if (Physics.Raycast(ray, out hit, focusLimit))
            {
                GameObject target = hit.collider.gameObject;
                Vector3 targetPos = target.transform.position;
                Vector3 targetSize = hit.collider.bounds.size;

                transform.position = targetPos + GetOffset(targetPos, targetSize);

                transform.LookAt(target.transform);
            }
        }

        private void SavePosAndRot()
        {
            prevRot = transform.rotation;
            prevPos = transform.position;
        }

        private void GoBackToLastPosition()
        {
            transform.position = prevPos;
            transform.rotation = prevRot;
        }

        private Vector3 GetOffset(Vector3 targetPos, Vector3 targetSize)
        {
            Vector3 dirToTarget = targetPos - transform.position;

            float focusDistance = Mathf.Max(targetSize.x, targetSize.z);
            focusDistance = Mathf.Clamp(focusDistance, minFocusDistance, focusDistance);

            return -dirToTarget.normalized * focusDistance;
        }
    }
}