using System.Collections.Generic;
using UnityEditor.Experimental.GraphView;
using UnityEditor.ShaderGraph.Drawing.Interfaces;
using UnityEngine;
using UnityEngine.UIElements;

namespace UnityEditor.ShaderGraph.Drawing.Views
{
    interface ISelectionProvider
    {
        List<ISelectable> GetSelection { get; }
    }

    class GraphSubWindow : GraphElement, ISGResizable
    {
        ISGViewModel m_ViewModel;

        ISGViewModel ViewModel
        {
            get => m_ViewModel;
            set => m_ViewModel = value;
        }

        Dragger m_Dragger;

        // This needs to be something that each subclass defines for itself at creation time
        // if they all use the same they'll be stacked on top of each other at SG window creation
        protected WindowDockingLayout windowDockingLayout { get; private set; } = new WindowDockingLayout
        {
            dockingTop = true,
            dockingLeft = false,
            verticalOffset = 8,
            horizontalOffset = 8,
        };

        // Used to cache the window docking layout between resizing operations as it interferes with window resizing operations
        private IStyle cachedWindowDockingStyle;

        protected VisualElement m_MainContainer;
        protected VisualElement m_Root;
        protected Label m_TitleLabel;
        protected Label m_SubTitleLabel;
        protected ScrollView m_ScrollView;
        protected VisualElement m_ContentContainer;
        protected VisualElement m_HeaderItem;
        protected VisualElement m_ParentView;

        // These are used as default values for styling and layout purposes
        // They can be overriden if a child class wants to roll its own style and layout behavior
        public virtual string layoutKey => "UnityEditor.ShaderGraph.SubWindow";
        public virtual string styleName => "GraphSubWindow";
        public virtual string UxmlName => "GraphSubWindow";

        // Each sub-window will override these if they need to
        public virtual string elementName => "";
        public virtual string windowTitle => "";

        public VisualElement ParentView
        {
            get
            {
                if (!isWindowed && m_ParentView == null)
                    m_ParentView = GetFirstAncestorOfType<GraphView>();
                return m_ParentView;
            }

            set
            {
                if (!isWindowed)
                    return;
                m_ParentView = value;
            }
        }

        public List<ISelectable> selection
        {
            get
            {
                if (ParentView is ISelectionProvider selectionProvider)
                    return selectionProvider.GetSelection;

                AssertHelpers.Fail("GraphSubWindow was unable to find a selection provider. Please check if parent view of: " + name + " implements ISelectionProvider::GetSelection");
                return new List<ISelectable>();
            }
        }

        public override string title
        {
            get { return m_TitleLabel.text; }
            set { m_TitleLabel.text = value; }
        }

        public string subTitle
        {
            get { return m_SubTitleLabel.text; }
            set { m_SubTitleLabel.text = value; }
        }

        // Intended for future handling of docking to sides of the shader graph window
        bool m_IsWindowed;
        public bool isWindowed
        {
            get { return m_IsWindowed; }
            set
            {
                if (m_IsWindowed == value) return;

                if (value)
                {
                    capabilities &= ~Capabilities.Movable;
                    AddToClassList("windowed");
                    this.RemoveManipulator(m_Dragger);
                }
                else
                {
                    capabilities |= Capabilities.Movable;
                    RemoveFromClassList("windowed");
                    this.AddManipulator(m_Dragger);
                }
                m_IsWindowed = value;
            }
        }

        public override VisualElement contentContainer => m_ContentContainer;

        private bool m_IsResizable = false;

        // Can be set by child classes as needed
        protected bool isWindowResizable
        {
            get => m_IsResizable;
            set
            {
                if (m_IsResizable != value)
                {
                    m_IsResizable = value;
                    HandleResizingBehavior(m_IsResizable);
                }
            }
        }

        void HandleResizingBehavior(bool isResizable)
        {
            if (isResizable)
            {
                var resizeElement = this.Q<ResizableElement>();
                resizeElement.BindOnResizeCallback(OnWindowResize);
                hierarchy.Add(resizeElement);
            }
            else
            {
                var resizeElement = this.Q<ResizableElement>();
                resizeElement.SetResizeRules(ResizableElement.Resizer.None);
                hierarchy.Remove(resizeElement);
            }
        }

        protected void SetResizingRules(ResizableElement.Resizer resizeDirections)
        {
            var resizeElement = this.Q<ResizableElement>();
            resizeElement.SetResizeRules(resizeDirections);
        }

        private bool m_IsScrollable = false;

        // Can be set by child classes as needed
        protected bool isWindowScrollable
        {
            get => m_IsScrollable;
            set
            {
                if (m_IsScrollable != value)
                {
                    m_IsScrollable = value;
                    HandleScrollingBehavior(m_IsScrollable);
                }
            }
        }

        protected float scrollableWidth
        {
            get { return m_ScrollView.contentContainer.layout.width - m_ScrollView.contentViewport.layout.width; }
        }

        protected float scrollableHeight
        {
            get { return contentContainer.layout.height - m_ScrollView.contentViewport.layout.height; }
        }

        void HandleScrollingBehavior(bool scrollable)
        {
            if (scrollable)
            {
                // Remove the categories container from the content item and add it to the scrollview
                m_ContentContainer.RemoveFromHierarchy();
                m_ScrollView.Add(m_ContentContainer);
                AddToClassList("scrollable");
            }
            else
            {
                // Remove the categories container from the scrollview and add it to the content item
                m_ContentContainer.RemoveFromHierarchy();
                m_Root.Add(m_ContentContainer);

                RemoveFromClassList("scrollable");
            }
        }

        protected GraphSubWindow(ISGViewModel viewModel)
        {
            ViewModel = viewModel;
            m_ParentView = ViewModel.parentView;
            ParentView.Add(this);

            var styleSheet = Resources.Load<StyleSheet>($"Styles/{styleName}");
            // Setup VisualElement from Stylesheet and UXML file
            styleSheets.Add(styleSheet);
            var uxml = Resources.Load<VisualTreeAsset>($"UXML/{UxmlName}");
            m_MainContainer = uxml.Instantiate();
            m_MainContainer.AddToClassList("mainContainer");

            m_Root = m_MainContainer.Q("content");
            m_HeaderItem = m_MainContainer.Q("header");
            m_HeaderItem.AddToClassList("subWindowHeader");
            m_ScrollView = m_MainContainer.Q<ScrollView>("scrollView");
            m_TitleLabel = m_MainContainer.Q<Label>(name: "titleLabel");
            m_SubTitleLabel = m_MainContainer.Q<Label>(name: "subTitleLabel");
            m_ContentContainer = m_MainContainer.Q(name: "contentContainer");

            hierarchy.Add(m_MainContainer);

            capabilities |= Capabilities.Movable | Capabilities.Resizable;
            style.overflow = Overflow.Hidden;
            focusable = false;

            name = elementName;
            title = windowTitle;

            ClearClassList();
            AddToClassList(name);

            BuildManipulators();

            /* Event interception to prevent GraphView manipulators from being triggered */
            //RegisterCallback<DragUpdatedEvent>(e =>
            //{
            //    e.StopPropagation();
            //});

            // prevent Zoomer manipulator
            RegisterCallback<WheelEvent>(e =>
            {
                e.StopPropagation();
            });

            //RegisterCallback<MouseDownEvent>(e =>
            //{
            //    // prevent ContentDragger manipulator
            //    e.StopPropagation();
            //});
        }

        public void ShowWindow()
        {
            this.style.visibility = Visibility.Visible;
            this.m_ScrollView.style.display = DisplayStyle.Flex;
            this.MarkDirtyRepaint();
        }

        public void HideWindow()
        {
            this.style.visibility = Visibility.Hidden;
            this.m_ScrollView.style.display = DisplayStyle.None;
            this.MarkDirtyRepaint();
        }

        void BuildManipulators()
        {
            m_Dragger = new Dragger { clampToParentEdges = true };
            RegisterCallback<MouseUpEvent>(OnMoveEnd);
            this.AddManipulator(m_Dragger);
        }

        #region Layout
        public void ClampToParentLayout(Rect parentLayout)
        {
            windowDockingLayout.CalculateDockingCornerAndOffset(layout, parentLayout);
            windowDockingLayout.ClampToParentWindow();

            // If the parent shader graph window is being resized smaller than this window on either axis
            if (parentLayout.width < this.layout.width || parentLayout.height < this.layout.height)
            {
                // Don't adjust the sub window in this case as it causes flickering errors and looks broken
            }
            else
            {
                windowDockingLayout.ApplyPosition(this);
            }

            SerializeLayout();
        }

        public void OnStartResize()
        {
            cachedWindowDockingStyle = this.style;
        }

        public void OnResized()
        {
            if (cachedWindowDockingStyle != null)
            {
                this.style.left = cachedWindowDockingStyle.left;
                this.style.right = cachedWindowDockingStyle.right;
                this.style.bottom = cachedWindowDockingStyle.bottom;
                this.style.top = cachedWindowDockingStyle.top;
            }
            windowDockingLayout.size = layout.size;
            SerializeLayout();
        }

        public void DeserializeLayout()
        {
            var serializedLayout = EditorUserSettings.GetConfigValue(layoutKey);
            if (!string.IsNullOrEmpty(serializedLayout))
                windowDockingLayout = JsonUtility.FromJson<WindowDockingLayout>(serializedLayout);
            else
            {
                // The window size needs to come from the stylesheet or UXML as opposed to being defined in code
                windowDockingLayout.size = layout.size;
            }

            windowDockingLayout.ApplySize(this);
            windowDockingLayout.ApplyPosition(this);
        }

        protected void AddStyleSheetFromPath(string styleSheetPath)
        {
            StyleSheet sheetAsset = Resources.Load<StyleSheet>(styleSheetPath); ;

            if (sheetAsset == null)
            {
                Debug.LogWarning(string.Format("Style sheet not found for path \"{0}\"", styleSheetPath));
                return;
            }
            styleSheets.Add(sheetAsset);
        }

        void SerializeLayout()
        {
            windowDockingLayout.size = layout.size;
            var serializedLayout = JsonUtility.ToJson(windowDockingLayout);
            EditorUserSettings.SetConfigValue(layoutKey, serializedLayout);
        }

        void OnMoveEnd(MouseUpEvent upEvent)
        {
            windowDockingLayout.CalculateDockingCornerAndOffset(layout, ParentView.layout);
            windowDockingLayout.ClampToParentWindow();

            SerializeLayout();
        }

        public bool CanResizePastParentBounds()
        {
            return false;
        }

        void OnWindowResize(MouseUpEvent upEvent)
        {
        }
    }
    #endregion
}