using System;
using UnityEngine;
using UnityEditor.Experimental.GraphView;
using UnityEngine.UIElements;
using UnityEngine.UIElements.StyleSheets;

namespace UnityEditor.ShaderGraph.Drawing
{
    class WindowDraggable : MouseManipulator
    {
        bool m_Active;

        WindowDockingLayout m_WindowDockingLayout;

        Vector2 m_LocalMosueOffset;

        VisualElement m_Handle;
        GraphView m_GraphView;

        public Action OnDragFinished;

        public WindowDraggable(VisualElement handle = null, VisualElement container = null)
        {
            m_Handle = handle;
            m_Active = false;
            m_WindowDockingLayout = new WindowDockingLayout();

            if (container != null)
                container.RegisterCallback<GeometryChangedEvent>(OnParentGeometryChanged);
        }

        protected override void RegisterCallbacksOnTarget()
        {
            if (m_Handle == null)
                m_Handle = target;
            m_Handle.RegisterCallback(new EventCallback<MouseDownEvent>(OnMouseDown), TrickleDownEnum.NoTrickleDown);
            m_Handle.RegisterCallback(new EventCallback<MouseMoveEvent>(OnMouseMove), TrickleDownEnum.NoTrickleDown);
            m_Handle.RegisterCallback(new EventCallback<MouseUpEvent>(OnMouseUp), TrickleDownEnum.NoTrickleDown);
            target.RegisterCallback<GeometryChangedEvent>(OnGeometryChanged);
        }

        protected override void UnregisterCallbacksFromTarget()
        {
            m_Handle.UnregisterCallback(new EventCallback<MouseDownEvent>(OnMouseDown), TrickleDownEnum.NoTrickleDown);
            m_Handle.UnregisterCallback(new EventCallback<MouseMoveEvent>(OnMouseMove), TrickleDownEnum.NoTrickleDown);
            m_Handle.UnregisterCallback(new EventCallback<MouseUpEvent>(OnMouseUp), TrickleDownEnum.NoTrickleDown);
            target.UnregisterCallback<GeometryChangedEvent>(OnGeometryChanged);
            if (m_GraphView != null)
                m_GraphView.UnregisterCallback<GeometryChangedEvent>(OnGeometryChanged);
        }

        void OnMouseDown(MouseDownEvent evt)
        {
            m_Active = true;

            VisualElement parent = target.parent;
            while (parent != null && !(parent is GraphView))
                parent = parent.parent;
            m_GraphView = parent as GraphView;

            if (m_GraphView != null)
                m_GraphView.RegisterCallback<GeometryChangedEvent>(OnGeometryChanged);

            // m_LocalMouseOffset is offset from the target element's (0, 0) to the
            // to the mouse position.
            m_LocalMosueOffset = m_Handle.WorldToLocal(evt.mousePosition);

            m_Handle.CaptureMouse();
            evt.StopImmediatePropagation();
        }

        void OnMouseMove(MouseMoveEvent evt)
        {
            if (m_Active)
            {
                // The mouse position of is corrected according to the offset within the target
                // element (m_LocalWorldOffset) to set the position relative to the mouse position
                // when the dragging started.
                Vector2 position = target.parent.WorldToLocal(evt.mousePosition) - m_LocalMosueOffset;

                // Make sure that the object remains in the parent window
                position.x = Mathf.Clamp(position.x, 0f, target.parent.layout.width - target.layout.width);
                position.y = Mathf.Clamp(position.y, 0f, target.parent.layout.height - target.layout.height);

                // While moving, use only the left and top position properties,
                // while keeping the others NaN to not affect layout.
                target.style.left = position.x;
                target.style.top = position.y;
                target.style.right = float.NaN;
                target.style.bottom = float.NaN;
            }
        }

        void OnMouseUp(MouseUpEvent evt)
        {
            bool emitDragFinishedEvent = m_Active;

            m_Active = false;

            if (m_Handle.HasMouseCapture())
            {
                m_Handle.ReleaseMouse();
            }

            evt.StopImmediatePropagation();

            // Recalculate which corner to dock to
            m_WindowDockingLayout.CalculateDockingCornerAndOffset(target.layout, target.parent.layout);
            m_WindowDockingLayout.ClampToParentWindow();

            // Use the docking results to figure which of left/right and top/bottom needs to be set.
            m_WindowDockingLayout.ApplyPosition(target);

            // Signal that the dragging has finished.
            if (emitDragFinishedEvent && OnDragFinished != null)
                OnDragFinished();
        }

        void OnGeometryChanged(GeometryChangedEvent geometryChangedEvent)
        {
            // Make the target clamp to the border of the window if the
            // parent window becomes too small to contain it.
            if (target.parent.layout.width < target.layout.width)
            {
                if (m_WindowDockingLayout.dockingLeft)
                {
                    target.style.left = 0f;
                    target.style.right = float.NaN;
                }
                else
                {
                    target.style.left = float.NaN;
                    target.style.right = 0f;
                }
            }

            if (target.parent.layout.height < target.layout.height)
            {
                if (m_WindowDockingLayout.dockingTop)
                {
                    target.style.top = 0f;
                    target.style.bottom = float.NaN;
                }
                else
                {
                    target.style.top = float.NaN;
                    target.style.bottom = 0f;
                }
            }
        }

        void OnParentGeometryChanged(GeometryChangedEvent geometryChangedEvent)
        {
            // Check if the parent window can no longer contain the target window.
            // If the window is out of bounds, make one edge clamp to the border of the
            // parent window.
            if (target.layout.xMin < 0f)
            {
                target.style.left = 0f;
                target.style.right = float.NaN;
            }

            if (target.layout.xMax > geometryChangedEvent.newRect.width)
            {
                target.style.left = float.NaN;
                target.style.right = 0f;
            }

            if (target.layout.yMax > geometryChangedEvent.newRect.height)
            {
                target.style.top = float.NaN;
                target.style.bottom = 0f;
            }

            if (target.layout.yMin < 0f)
            {
                target.style.top = 0f;
                target.style.bottom = float.NaN;
            }
        }
    }
}