using System;
using System.Collections;
using UnityEngine;
using UnityEngine.EventSystems;

public sealed class OrbitalCameraControl : MonoBehaviour
{
    [SerializeField]
    private CameraController cameraController = null;

    [SerializeField]
    private float minDistance = 1f;
    [SerializeField]
    private float maxDistance = 10f;
    [SerializeField]
    private float distance = 5;
    [SerializeField]
    private float minPitchAngle = -90;
    [SerializeField]
    private float maxPitchAngle = 90;
    [SerializeField]
    private Vector3 cameraRotationSpeed = Vector3.one;
    [SerializeField]
    private Vector3 cameraDragAngleFactor = Vector3.one;
    [SerializeField]
    private Vector3 cameraEulerAngles = Vector3.zero;
    [SerializeField]
    private float keyboardZoomSpeed = 1;
    [SerializeField]
    private float mouseZoomSpeed = 1;

    private bool dragRotation = false;
    private Vector3 dragPosition = Vector3.zero;
    private readonly AnimationCurve rotationDynamics = AnimationCurve.EaseInOut(0, 0, 1, 1);
    public bool IsLocked { get; private set; }

    public float Distance {
        get => distance;
        set => SetDistance(value);
    }

    public Vector3 Euler => cameraEulerAngles;

    public void LockControl(bool needLock = true)
    {
        IsLocked = needLock;
    }

    public void LookAt(Vector3 position, float time = 0, float offsetX = 0, float offsetY = 0, float offsetZ = 0)
    {
        var lookDirection = (position - cameraController.transform.position).normalized;
        var directionRotation = Quaternion.LookRotation(lookDirection, Vector3.up) * Quaternion.Euler(offsetX, -offsetY, offsetZ);
        if (time <= 0)
        {   // instant
            cameraEulerAngles = directionRotation.eulerAngles;
            cameraController.Rotation = directionRotation;
        }
        else
        {   // animated
            StartCoroutine(AnimateLookAtDirection(directionRotation, time));
        }
    }

    public void SetPosition(Vector3 position) => cameraController.SetPosition(position);
    public void SetRotation(Quaternion rotation)
    {
        cameraEulerAngles = rotation.eulerAngles;
        cameraController.Rotation = rotation;
    }
    public void SetRotation(Vector3 eulerAngles)
    {
        cameraEulerAngles = eulerAngles;
        cameraController.Rotation = Quaternion.Euler(eulerAngles);
    }



    public Ray GetRay(Vector2 pointerPosition) => cameraController.GetRay(pointerPosition);

    public void SetFieldOfView(float fowValue) => cameraController.SetFieldOfView(fowValue);

    private void OnEnable()
    {
        cameraController.Rotation = Quaternion.Euler(cameraEulerAngles);
        SetDistance(distance);
    }

    private void Update()
    {
        if (IsLocked)
            return;
        // rotate by mouse
        if (Input.GetMouseButtonDown(1) && EventSystem.current != null && !EventSystem.current.IsPointerOverGameObject())
        {
            dragRotation = true;
            dragPosition = Input.mousePosition;
        }
        if (Input.GetMouseButtonUp(1))
        {
            dragRotation = false;
        }
        if (dragRotation)
        {
            var delta = dragPosition - Input.mousePosition;
            var screenFactor = Mathf.Min(Screen.width, Screen.height);
            delta.x /= screenFactor;
            delta.y /= screenFactor;
            cameraEulerAngles.x += delta.y * cameraDragAngleFactor.x;
            cameraEulerAngles.y += delta.x * cameraDragAngleFactor.y;

            dragPosition = Input.mousePosition;
        }
        // rotate by keyboard
        if (Input.GetKey(KeyCode.W) || Input.GetKey(KeyCode.UpArrow))
        {
            cameraEulerAngles.x += cameraRotationSpeed.x * Time.deltaTime;
        }
        if (Input.GetKey(KeyCode.S) || Input.GetKey(KeyCode.DownArrow))
        {
            cameraEulerAngles.x += -cameraRotationSpeed.x * Time.deltaTime;
        }
        if (Input.GetKey(KeyCode.A) || Input.GetKey(KeyCode.LeftArrow))
        {
            cameraEulerAngles.y += cameraRotationSpeed.y * Time.deltaTime;
        }
        if (Input.GetKey(KeyCode.D) || Input.GetKey(KeyCode.RightArrow))
        {
            cameraEulerAngles.y += -cameraRotationSpeed.y * Time.deltaTime;
        }
        // zoom in/out by keyboard
        if (Input.GetKey(KeyCode.Q))
        {
            distance += keyboardZoomSpeed * Time.deltaTime;
        }
        if (Input.GetKey(KeyCode.E))
        {
            distance -= keyboardZoomSpeed * Time.deltaTime;
        }
        // zoom in/out by scroll
        if (Input.mouseScrollDelta.y > 0)
        {
            distance += mouseZoomSpeed * Input.mouseScrollDelta.y;
        }
        if (Input.mouseScrollDelta.y < 0)
        {
            distance += mouseZoomSpeed * Input.mouseScrollDelta.y;
        }

        SetDistance(distance);
        cameraEulerAngles.x = Mathf.Clamp(cameraEulerAngles.x, minPitchAngle, maxPitchAngle);
        cameraEulerAngles.y = CameraController.RoundAngle(cameraEulerAngles.y);
        cameraController.Rotation = Quaternion.Euler(cameraEulerAngles);
    }

    private void SetDistance(float newDistance)
    {
        distance = Mathf.Clamp(newDistance, minDistance, maxDistance);
        cameraController.Offset = new Vector3(0, 0, -distance);
    }

    private IEnumerator AnimateLookAtDirection(Quaternion direction, float time = 1f)
    {
        var startCameraAngles = cameraController.Rotation;
        var endCameraAngles = direction;
        var rotationTimer = time;
        while (rotationTimer > 0)
        {
            yield return new WaitForEndOfFrame();
            rotationTimer -= Time.deltaTime;
            var animation = rotationDynamics.Evaluate(rotationTimer / time);
            var cameraRotation = Quaternion.Lerp(endCameraAngles, startCameraAngles, animation);
            cameraEulerAngles = cameraRotation.eulerAngles;
            cameraController.Rotation = cameraRotation;
        }
        cameraEulerAngles = endCameraAngles.eulerAngles;
        cameraController.Rotation = endCameraAngles;
    }
}
