using System;
using UnityEngine;

public class CameraController : MonoBehaviour {
    private static CameraController instance = null;
    private static CameraController Instance { get { return GetInstance(); } }

    private const string path = "Prefabs/CameraController";

    private const float moveSpeed = 0.4f; // camera move - unit per second

    [SerializeField]
    private float minDistance = 3f;
    [SerializeField]
    private float maxDistance = 30f;
    [SerializeField]
    private Rect borders = new Rect(new Vector2(0, 0), new Vector2(50, 50));

    [SerializeField]
    private Vector3 cameraShift = new Vector3(0, 0, -12f); // camera vertical/horisontal shift and distance from pivot
    [SerializeField]
    private Vector3 needCameraShift = new Vector3(0, 0, -12f); // need camera vertical/horisontal shift and distance from pivot
    [SerializeField]
    private Vector3 needPosition = Vector3.zero;

    private Quaternion needRotation = Quaternion.Euler(60, 30, 0);
    [SerializeField]
    private Camera controlCamera = null;

    private Transform selfTransform = null;
    
    private static CameraController GetInstance() {
        if (instance != null) {
            return instance;
        }
        instance = Instantiate(Resources.Load<CameraController>(path));
        return instance;
    }

    public static Camera ControlCamera {
        get { return (Instance != null) ? Instance.controlCamera : null; }
    }

    public static Vector3 Shift {
        get { return (Instance != null) ? Instance.cameraShift : Vector3.zero; }
    }

    public static Vector3 Position {
        get { return (Instance != null) ? Instance.needPosition : Vector3.zero; }
    }

    public static Vector3 Angle {
        get { return (Instance != null) ? Instance.needRotation.eulerAngles : Vector3.zero; }
    }
    public static Rect Borders
    {
        get { return (Instance != null) ? Instance.borders : Rect.zero; }
    }

    public static void SetBorders(Rect newBorders) {
        if (Instance != null) {
            Instance.borders = newBorders;
        }
    }

    public static void SetDistanceBorders(float minDistance, float maxDistance) {
        if (Instance != null) {
            Instance.minDistance = minDistance;
            Instance.maxDistance = maxDistance;
        }
    }

    private void Awake() {
        if (instance != null && instance != this) {
            Destroy(gameObject);
            Debug.Log("Two camera controllers in scene. Removing");
            return;
        }
        instance = this;
        selfTransform = transform;
        if (controlCamera == null) {
            controlCamera = GetComponentInChildren<Camera>();
        }
        // DontDestroyOnLoad(gameObject);
    }

    public static void SetPosition(Vector3 position, bool useAnimation = false) {
        if (Instance != null) {
            Instance.needPosition = position;
            if (!useAnimation) {
                Instance.selfTransform.localPosition = position;
            }
        }
    }

    public static void SetAngles(Vector3 angles, bool useAnimation = false) {
        if (Instance != null) {
            Instance.needRotation = Quaternion.Euler(angles);
            if (!useAnimation) {
                Instance.selfTransform.localEulerAngles = angles;
            }
        }
    }

    public static void SetShift(Vector3 shift, bool useAnimation = false) {
        if (Instance != null) {
            Instance.needCameraShift = shift;
            if (!useAnimation) {
                Instance.cameraShift = shift;
            }
        }
    }

    public static void Move(Vector3 moveVector) {
        float cameraAngle = Instance.transform.eulerAngles.y;
        Instance.needPosition += Quaternion.Euler(0, cameraAngle, 0) * moveVector * moveSpeed;
    }

    public static void MoveRaycast(Vector3 prevScreenPosition, Vector3 currentScreenPosition) {
        Vector3 prevMapPosition = Instance.Raycast(prevScreenPosition);
        Vector3 currentMapPosition = Instance.Raycast(currentScreenPosition);
        Instance.needPosition += prevMapPosition - currentMapPosition;
    }

    public static void Zoom(float scale = 1f) {
        Instance.needCameraShift.z = Instance.needCameraShift.z * scale;
        Instance.needCameraShift.z = -Mathf.Max(-Instance.needCameraShift.z, Instance.minDistance);
        Instance.needCameraShift.z = -Mathf.Min(-Instance.needCameraShift.z, Instance.maxDistance);
    }

    public static void ZoomIn(float speed = 20f) {
        Instance.needCameraShift.z -= Instance.needCameraShift.z * speed * Time.unscaledDeltaTime;
        Instance.needCameraShift.z = -Mathf.Max(-Instance.needCameraShift.z, Instance.minDistance);
    }

    public static void ZoomOut(float speed = 20f) {
        Instance.needCameraShift.z += Instance.needCameraShift.z * speed * Time.unscaledDeltaTime;
        Instance.needCameraShift.z = -Mathf.Min(-Instance.needCameraShift.z, Instance.maxDistance);
    }

    private Vector3 Raycast(Vector2 screenPoint) {
        Vector3 point = Vector3.zero;
        if (ControlCamera != null) {
            var ray = ControlCamera.ScreenPointToRay(screenPoint);
            Plane floorPlane = new Plane(Vector3.up, Vector3.zero);
            float distance = 0;
            if (floorPlane.Raycast(ray, out distance)) {
                return ray.GetPoint(distance);
            }
        }
        return point;
    }

    private void Update() {
        if (controlCamera != null) {
            cameraShift = needCameraShift; // Utils.Lerp(cameraShift, needCameraShift, (0.15f * 30f) * Time.deltaTime);
            controlCamera.transform.localPosition = cameraShift;
        }
        CheckBorders();
        selfTransform.localPosition = needPosition; // Utils.Lerp(selfTransform.localPosition, needPosition, (0.15f * 50f) * Time.deltaTime);
        selfTransform.localRotation = needRotation; // Quaternion.Lerp(selfTransform.localRotation, needRotation, (0.15f * 50f) * Time.deltaTime);
    }

    private void CheckBorders() {
        if (needPosition.x > borders.xMax)
            needPosition.x = borders.xMax;
        if (needPosition.z > borders.yMax)
            needPosition.z = borders.yMax;
        if (needPosition.x < borders.xMin)
            needPosition.x = borders.xMin;
        if (needPosition.z < borders.yMin)
            needPosition.z = borders.yMin;
    }

#if UNITY_EDITOR
    private void OnDrawGizmos() {
        // Camera move borders
        Gizmos.color = Color.green;
        Gizmos.DrawLine(new Vector3(borders.xMax, 0, borders.yMax), new Vector3(borders.xMax, 0, borders.yMin));
        Gizmos.DrawLine(new Vector3(borders.xMax, 0, borders.yMin), new Vector3(borders.xMin, 0, borders.yMin));
        Gizmos.DrawLine(new Vector3(borders.xMin, 0, borders.yMin), new Vector3(borders.xMin, 0, borders.yMax));
        Gizmos.DrawLine(new Vector3(borders.xMin, 0, borders.yMax), new Vector3(borders.xMax, 0, borders.yMax));
    }
#endif
}
