// Copyright (c) Wojciech Figat. All rights reserved.

using System.Collections.Generic;
using FlaxEditor.Surface.Elements;
using FlaxEngine;

namespace FlaxEditor.Surface
{
    public partial class VisjectSurface
    {
        /// <inheritdoc />
        public override void Update(float deltaTime)
        {
            // Update scale
            var currentScale = _rootControl.Scale.X;
            if (Mathf.Abs(_targetScale - currentScale) > 0.001f)
            {
                var scale = new Float2(Mathf.Lerp(currentScale, _targetScale, deltaTime * 10.0f));
                _rootControl.Scale = scale;
            }

            // Navigate when mouse is near the edge and is doing sth
            bool isMovingWithMouse = false;
            if (IsMovingSelection || (IsConnecting && !IsPrimaryMenuOpened))
            {
                var moveVector = Float2.Zero;
                float edgeDetectDistance = 22.0f;
                if (_mousePos.X < edgeDetectDistance)
                {
                    moveVector.X -= 1;
                }
                if (_mousePos.Y < edgeDetectDistance)
                {
                    moveVector.Y -= 1;
                }
                if (_mousePos.X > Width - edgeDetectDistance)
                {
                    moveVector.X += 1;
                }
                if (_mousePos.Y > Height - edgeDetectDistance)
                {
                    moveVector.Y += 1;
                }
                moveVector.Normalize();
                isMovingWithMouse = moveVector.LengthSquared > Mathf.Epsilon;
                if (isMovingWithMouse)
                {
                    _rootControl.Location -= moveVector * _moveViewWithMouseDragSpeed;
                }
            }
            _moveViewWithMouseDragSpeed = isMovingWithMouse ? Mathf.Clamp(_moveViewWithMouseDragSpeed + deltaTime * 20.0f, 1.0f, 8.0f) : 1.0f;

            base.Update(deltaTime);

            // Batch undo actions
            if (_batchedUndoActions != null && _batchedUndoActions.Count != 0)
            {
                Undo.AddAction(_batchedUndoActions.Count == 1 ? _batchedUndoActions[0] : new MultiUndoAction(_batchedUndoActions));
                _batchedUndoActions.Clear();
            }
        }

        /// <summary>
        /// Draws the surface background.
        /// </summary>
        protected virtual void DrawBackground()
        {
            DrawBackgroundDefault(Style.Background, Size, _rootControl.Location);
            //DrawBackgroundSolidColor(Style.BackgroundColor, Width, Height);
            DrawGridBackground();
        }

        internal static void DrawBackgroundSolidColor(Color color, float width, float height)
        {
            Rectangle backgroundRect = new Rectangle(0f, 0f, width, height);
            Render2D.FillRectangle(backgroundRect, color);
        }

        internal void DrawGridBackground()
        {
            var viewRect = GetClientArea();
            var upperLeft = _rootControl.PointFromParent(viewRect.Location);
            var bottomRight = _rootControl.PointFromParent(viewRect.Size);
            var min = Float2.Min(upperLeft, bottomRight);
            var max = Float2.Max(upperLeft, bottomRight);
            var pixelRange = (max - min) * ViewScale * 2.75f;
            Render2D.PushClip(ref viewRect);
            DrawAxis(Float2.UnitX, viewRect, min.X, max.X, pixelRange.X);
            DrawAxis(Float2.UnitY, viewRect, min.Y, max.Y, pixelRange.Y);
            Render2D.PopClip();
        }

        private void DrawAxis(Float2 axis, Rectangle viewRect, float min, float max, float pixelRange)
        {
            var linesColor = Style.BackgroundColor.RGBMultiplied(1.2f);
            float[] gridTickStrengths = null;
            Utilities.Utils.DrawCurveTicks((decimal tick, double step, float strength) =>
            {
                var p = _rootControl.PointToParent(axis * (float)tick); ;

                // Draw line
                var lineRect = new Rectangle
                (
                 viewRect.Location + (p - 0.5f) * axis,
                 Float2.Lerp(viewRect.Size, Float2.One, axis)
                );
                Render2D.FillRectangle(lineRect, linesColor.AlphaMultiplied(strength));

            }, Utilities.Utils.CurveTickSteps, ref gridTickStrengths, min, max, pixelRange);
        }

        internal static void DrawBackgroundDefault(Texture background, Float2 size, Float2 offset)
        {
            if (background && background.ResidentMipLevels > 0)
            {
                var bSize = background.Size;
                var pos = Float2.Mod(offset / bSize) * bSize;
                var max = Float2.Ceil(size / bSize + 1.0f);

                if (pos.X > 0)
                    pos.X -= bSize.X;
                if (pos.Y > 0)
                    pos.Y -= bSize.Y;

                for (int i = 0; i < max.X; i++)
                {
                    for (int j = 0; j < max.Y; j++)
                    {
                        Render2D.DrawTexture(background, new Rectangle(pos.X + i * bSize.X, pos.Y + j * bSize.Y, bSize), Color.White);
                    }
                }
            }
        }

        /// <summary>
        /// Draws the selection background.
        /// </summary>
        /// <remarks>Called only when user is selecting nodes using rectangle tool.</remarks>
        protected virtual void DrawSelection()
        {
            var style = FlaxEngine.GUI.Style.Current;
            var selectionRect = Rectangle.FromPoints(_leftMouseDownPos, _mousePos);
            Render2D.FillRectangle(selectionRect, style.Selection);
            Render2D.DrawRectangle(selectionRect, style.SelectionBorder);
        }

        /// <summary>
        /// Draws all the connections between surface nodes.
        /// </summary>
        /// <param name="mousePosition">The current mouse position (in surface-space).</param>
        protected virtual void DrawConnections(ref Float2 mousePosition)
        {
            // Draw all connections at once to boost batching process
            for (int i = 0; i < Nodes.Count; i++)
            {
                Nodes[i].DrawConnections(ref mousePosition);
                Nodes[i].DrawSelectedConnections(_selectedConnectionIndex);
            }
        }

        /// <summary>
        /// Draws the connecting line.
        /// </summary>
        /// <remarks>Called only when user is connecting nodes.</remarks>
        protected virtual void DrawConnectingLine()
        {
            var cmVisible = _activeVisjectCM != null && _activeVisjectCM.Visible;
            var endPos = cmVisible ? _rootControl.PointFromParent(ref _cmStartPos) : _rootControl.PointFromParent(ref _mousePos);
            Color lineColor = Style.Colors.Connecting;

            List<IConnectionInstigator> instigators = new List<IConnectionInstigator>(_connectionInstigators);
            for (int i = 0; i < instigators.Count; i++)
            {
                IConnectionInstigator currentInstigator = instigators[i];
                Float2 currentStartPosition = currentInstigator.ConnectionOrigin;

                // Check if mouse is over any box
                if (_lastInstigatorUnderMouse != null && !cmVisible)
                {
                    // Check if can connect objects
                    bool canConnect = currentInstigator.CanConnectWith(_lastInstigatorUnderMouse);
                    lineColor = canConnect ? Style.Colors.ConnectingValid : Style.Colors.ConnectingInvalid;
                    endPos = _lastInstigatorUnderMouse.ConnectionOrigin;
                }

                Float2 actualStartPos = currentStartPosition;
                Float2 actualEndPos = endPos;

                if (currentInstigator is Archetypes.Tools.RerouteNode)
                {
                    if (endPos.X < currentStartPosition.X && _lastInstigatorUnderMouse is null or Box { IsOutput: true })
                    {
                        actualStartPos = endPos;
                        actualEndPos = currentStartPosition;
                    }
                }
                else if (currentInstigator is Box { IsOutput: false })
                {
                    actualStartPos = endPos;
                    actualEndPos = currentStartPosition;
                }

                // Draw connection
                currentInstigator.DrawConnectingLine(ref actualStartPos, ref actualEndPos, ref lineColor);
            }
        }

        /// <summary>
        /// Draws the brackets and connections
        /// </summary>
        protected virtual void DrawInputBrackets()
        {
            var style = FlaxEngine.GUI.Style.Current;
            var fadedColor = style.ForegroundGrey;
            foreach (var inputBracket in _inputBrackets)
            {
                // Draw brackets
                var upperLeft = _rootControl.PointToParent(inputBracket.Area.UpperLeft);
                var bottomRight = _rootControl.PointToParent(inputBracket.Area.BottomRight);

                var upperRight = new Float2(bottomRight.X, upperLeft.Y);
                var bottomLeft = new Float2(upperLeft.X, bottomRight.Y);

                // Calculate control points
                float height = bottomLeft.Y - upperLeft.Y;
                float offsetX = height / 10f;
                var leftControl1 = new Float2(bottomLeft.X - offsetX, bottomLeft.Y);
                var leftControl2 = new Float2(upperLeft.X - offsetX, upperLeft.Y);

                // Draw left bracket
                Render2D.DrawBezier(bottomLeft, leftControl1, leftControl2, upperLeft, style.Foreground, 2.2f);

                // Calculate control points
                var rightControl1 = new Float2(bottomRight.X + offsetX, bottomRight.Y);
                var rightControl2 = new Float2(upperRight.X + offsetX, upperRight.Y);

                // Draw right bracket
                Render2D.DrawBezier(bottomRight, rightControl1, rightControl2, upperRight, fadedColor, 2.2f);

                // Draw connection bezier
                // X-offset at 75%
                var bezierStartPoint = new Float2(upperRight.X + offsetX * 0.75f, (upperRight.Y + bottomRight.Y) * 0.5f);
                var bezierEndPoint = inputBracket.Box.ParentNode.PointToParent(_rootControl.Parent, inputBracket.Box.Center);

                Elements.OutputBox.DrawConnection(Style, ref bezierStartPoint, ref bezierEndPoint, ref fadedColor);

                // Debug Area
                //Rectangle drawRect = Rectangle.FromPoints(upperLeft, bottomRight);
                //Render2D.FillRectangle(drawRect, Color.Green * 0.5f);
            }
        }

        /// <summary>
        /// Draw connection hints for lazy connect feature.
        /// </summary>
        protected virtual void DrawLazyConnect()
        {
            var style = FlaxEngine.GUI.Style.Current;

            if (_lazyConnectStartNode != null)
            {
                Float2 upperLeft = _rootControl.PointToParent(_lazyConnectStartNode.UpperLeft);
                Rectangle startNodeOutline = new Rectangle(upperLeft + 1f, _lazyConnectStartNode.Size - 1f);
                startNodeOutline.Size *= ViewScale;
                Render2D.DrawRectangle(startNodeOutline.MakeExpanded(4f), style.BackgroundSelected, 4f);
            }

            if (_lazyConnectEndNode != null)
            {
                Float2 upperLeft = _rootControl.PointToParent(_lazyConnectEndNode.UpperLeft);
                Rectangle startNodeOutline = new Rectangle(upperLeft + 1f, _lazyConnectEndNode.Size - 1f);
                startNodeOutline.Size *= ViewScale;
                Render2D.DrawRectangle(startNodeOutline.MakeExpanded(4f), style.BackgroundSelected, 4f);
            }

            Rectangle startRect = new Rectangle(_rightMouseDownPos - 6f, new Float2(12f));
            Rectangle endRect = new Rectangle(_mousePos - 6f, new Float2(12f));

            // Start and end shadows/ outlines
            Render2D.FillRectangle(startRect.MakeExpanded(2.5f), Color.Black);
            Render2D.FillRectangle(endRect.MakeExpanded(2.5f), Color.Black);

            Render2D.DrawLine(_rightMouseDownPos, _mousePos, Color.Black, 7.5f);
            Render2D.DrawLine(_rightMouseDownPos, _mousePos, style.ForegroundGrey, 5f);

            // Draw start and end boxes over the lines to hide ugly artifacts at the ends
            Render2D.FillRectangle(startRect, style.ForegroundGrey);
            Render2D.FillRectangle(endRect, style.ForegroundGrey);
        }

        /// <summary>
        /// Draws the contents of the surface (nodes, connections, comments, etc.).
        /// </summary>
        protected virtual void DrawContents()
        {
            base.Draw();
        }

        /// <inheritdoc />
        public override void Draw()
        {
            var style = FlaxEngine.GUI.Style.Current;
            var rect = new Rectangle(Float2.Zero, Size);

            DrawBackground();

            _rootControl.DrawComments();

            // Reset input flags here because this is the closest to Update we have
            WasSelecting = IsSelecting;
            WasMovingSelection = IsMovingSelection;

            if (IsSelecting)
            {
                DrawSelection();
            }

            // Push surface view transform (scale and offset)
            Render2D.PushTransform(ref _rootControl._cachedTransform);
            var features = Render2D.Features;
            Render2D.Features = Render2D.RenderingFeatures.None;

            var mousePos = _rootControl.PointFromParent(ref _mousePos);
            DrawConnections(ref mousePos);

            if (IsConnecting)
            {
                DrawConnectingLine();
            }

            Render2D.Features = features;
            Render2D.PopTransform();

            DrawInputBrackets();

            DrawContents();

            if (_isLazyConnecting)
                DrawLazyConnect();

            //Render2D.DrawText(style.FontTitle, string.Format("Scale: {0}", _rootControl.Scale), rect, Enabled ? Color.Red : Color.Black);

            // Draw border
            if (ContainsFocus)
            {
                Render2D.DrawRectangle(new Rectangle(1, 1, rect.Width - 2, rect.Height - 2), Editor.IsPlayMode ? Color.OrangeRed : style.BackgroundSelected);
            }

            // Draw disabled overlay
            //if (!Enabled)
            //    Render2D.FillRectangle(rect, new Color(0.2f, 0.2f, 0.2f, 0.5f), true);
        }
    }
}
