using UnityEngine;
using UnityEngine.UI;

public class UIHandle : MonoBehaviour
{
    /// <summary> Object that "owns" the handle </summary>
    public ObjectContainer Owner;

    /// <summary> Object that the handle refers to. Cast in inheriting class </summary>
    public Transform Target;

    /// <summary> Display object on the UI </summary>
    public GameObject Handle;

    /// <summary> Collider for selecting the handle on UI </summary>
    public SphereCollider Collider;

    /// <summary> Object that appears when right-clicking the handle </summary>
    public UIPopupPanel Popup;

    public LineRenderer LineRenderer;

    protected SerializableTransform _defaultTransform;
    protected float _baseScale = 1;
    protected bool _forceDisplayOff = false;
    protected ModelViewerMain Main => ModelViewerMain.GetInstance();

    public static T CreateAsChild<T>(Transform parent) where T : UIHandle
    {
        var handle = new GameObject(parent.name + "_Handle").AddComponent<T>();
        handle.transform.parent = parent.transform;
        handle.transform.localPosition = Vector3.zero;
        handle.transform.localScale = Vector3.one;
        handle.transform.rotation = parent.rotation;
        handle.Target = parent.transform;
        return handle;
    }

    public virtual UIHandle Init(ObjectContainer owner, Transform target)
    {
        Target = target;
        return Init(owner);
    }

    public virtual UIHandle Init(ObjectContainer owner)
    {
        HandleManager.RegisterHandle(this);

        gameObject.layer = LayerMask.NameToLayer("UIHandle");
        Target = Target == null? owner.transform : Target;
        Owner = owner;
        Owner.Handles.Add(this);

        Handle = Instantiate(HandleManager.Instance.Pfb_HandleDisplay, HandleManager.Instance.HandlesPanel.transform);
        Handle.transform.SetAsFirstSibling();

        Popup = Instantiate(HandleManager.Instance.Pfb_Popup, HandleManager.Instance.HandlesPanel.transform).Init(this);
        Popup.gameObject.SetActive(false);

        _defaultTransform = new SerializableTransform(Target, Space.Self);

        var collider = gameObject.AddComponent<SphereCollider>();
        collider.center = Vector3.zero;

        Collider = collider;

        return this;
    }

    private void OnDrawGizmos()
    {
        Gizmos.DrawWireSphere(transform.position + Collider.center, transform.lossyScale.x * Collider.radius);
    }

    public virtual void UpdateManual(Camera camera, bool linesEnabled)
    {
        if (Handle != null && camera != null && transform != null)
        {
            Handle.transform.localScale = Vector3.one * Mathf.Clamp(_baseScale / Vector3.Distance(camera.transform.position, transform.position), 0.1f, 1);

            if (Collider.transform.localScale.x != 0 && transform.lossyScale.x != 0)
            {
                Collider.radius = Mathf.Abs(35 /* magic number */ * (1 / transform.lossyScale.x) * GetRadiusOnScreen(camera, Collider.transform.position, Handle.transform.localScale.x));
                Collider.radius = Mathf.Clamp(Collider.radius, 0.001f, 2);
            }

            Handle.transform.position = camera.WorldToScreenPoint(Collider.transform.TransformPoint(Collider.center));

            if (ShouldBeHidden())
            {
                if (Handle.activeSelf == true)
                {
                    ToggleActive(false);
                }
            }
            else
            {
                bool isOnScreen =
                    !(Handle.transform.position.x < 0 || Handle.transform.position.y < 0 || Handle.transform.position.z < 0
                    || Handle.transform.position.x > Screen.width || Handle.transform.position.y > Screen.height);

                if (Handle.activeSelf != isOnScreen)
                {
                    ToggleActive(!Handle.activeSelf);
                }
            }
        }

        if(Handle.activeSelf && LineRenderer != null)
        {
            if (LineRenderer.enabled != linesEnabled)
            {
                LineRenderer.enabled = linesEnabled;
            }
            if (linesEnabled)
            {
                LineRenderer.SetPositions(new Vector3[] { (Target).position, (Target).parent.position });
            }
        }
    }

    protected virtual bool ShouldBeHidden()
    {
        return _forceDisplayOff;
    }

    /// <summary> For future, `offset` param will allow popups to be spaced out when selecting more than 1 handle at a time. </summary>
    public void TogglePopup(int offset = 0)
    {
        Popup.Offset = offset * (Popup.transform as RectTransform).sizeDelta.x * ModelViewerInterface.GetInstance().MainCanvas.scaleFactor;
        ModelViewerInterface.ToggleVisible(Popup.gameObject);
    }

    public UIHandle SetDefaults(Vector3 localPos, Vector3 localRot, Vector3 localScale)
    {
        _defaultTransform.Position  = localPos;
        _defaultTransform.Rotation  = localRot;
        _defaultTransform.Scale     = localScale;
        return this;
    }

    public UIHandle SetColor(Color color)
    {
        color.a = Handle.GetComponent<Image>().color.a;
        Handle.GetComponent<Image>().color = color;
        return this;
    }

    public UIHandle SetScale(float scale)
    {
        this._baseScale = scale;
        return this;
    }

    public UIHandle SetName(string name)
    {
        Handle.gameObject.name = name;
        Popup.NameLabel.text = name;
        return this;
    }

    public UIHandle SetOffset(Vector3 offset)
    {
        Collider.center = offset;
        return this;
    }

    public UIHandle SetContainer(ObjectContainer container)
    {
        Owner = container;
        return this;
    }

    public float GetRadiusOnScreen(Camera cam, Vector3 position, float screenSize)
    {
        Vector3 a = cam.WorldToScreenPoint(position);
        Vector3 b = new Vector3(a.x, a.y + screenSize, a.z);

        Vector3 aa = cam.ScreenToWorldPoint(a);
        Vector3 bb = cam.ScreenToWorldPoint(b);

        return (aa - bb).magnitude;
    }

    public UIHandle WithLineRenderer()
    {
        LineRenderer = Handle.gameObject.AddComponent<LineRenderer>();
        LineRenderer.positionCount  = 2;
        LineRenderer.startWidth     = LineRenderer.endWidth = 0.005f;
        LineRenderer.material       = HandleManager.Instance.LineRendererMaterial;
        LineRenderer.startColor     = LineRenderer.endColor = Handle.GetComponent<Image>().color;
        return this;
    }

    private void OnDestroy()
    {
        if (Popup)
        {
            Destroy(Popup.gameObject);
        }
        if (Handle)
        {
            Destroy(Handle);
        }
        HandleManager.UnregisterHandle(this);
    }

    private void TransformReset(PoseLoadOptions options = null)
    {
        if(options == null)
        {
            options = PoseLoadOptions.All();
        }

        _defaultTransform.ApplyTo(Target, options);
    }

    public void TransformResetAll()
    {
        var HandleUndoData = new HandleUndoData(this);

        HandleUndoData.OldPosition = new SerializableTransform(Target, Space.World);
        TransformReset();
        HandleUndoData.NewPosition = Target;

        HandleManager.RegisterRuntimeGizmoUndoAction.Invoke(HandleUndoData);
        Owner.SetKeyframe();
    }

    public void TransformResetPosition()
    {
        var HandleUndoData = new HandleUndoData(this);

        HandleUndoData.OldPosition = new SerializableTransform(Target, Space.World);
        TransformReset(new PoseLoadOptions(false) { Position = true });
        HandleUndoData.NewPosition = Target;

        HandleManager.RegisterRuntimeGizmoUndoAction.Invoke(HandleUndoData);
        Owner.SetKeyframe();
    }

    public void TransformResetRotation()
    {
        var HandleUndoData = new HandleUndoData(this);

        HandleUndoData.OldPosition = new SerializableTransform(Target, Space.World);
        TransformReset(new PoseLoadOptions(false) { Rotation = true });
        HandleUndoData.NewPosition = Target;

        HandleManager.RegisterRuntimeGizmoUndoAction.Invoke(HandleUndoData);
        Owner.SetKeyframe();
    }

    public void TransformResetScale()
    {
        var HandleUndoData = new HandleUndoData(this);

        HandleUndoData.OldPosition = new SerializableTransform(Target, Space.World);
        TransformReset(new PoseLoadOptions(false) { Scale = true });
        HandleUndoData.NewPosition = Target;

        HandleManager.RegisterRuntimeGizmoUndoAction.Invoke(HandleUndoData);
        Owner.SetKeyframe();
    }

    public void ToggleActive(bool value)
    {
        Handle.SetActive(value);
        Collider.enabled = value;
        Popup.gameObject.SetActive(false);
    }

    public void ForceDisplayOff(bool value)
    {
        _forceDisplayOff = value;
    }
}