// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.

using System;
using System.Collections.Generic;
using System.Xml;
using FlaxEditor.GUI.ContextMenu;
using FlaxEditor.GUI.Input;
using FlaxEditor.Options;
using FlaxEngine;
using FlaxEngine.GUI;
using FlaxEngine.Json;

namespace FlaxEditor.Windows
{
    /// <summary>
    /// Provides in-editor play mode simulation.
    /// </summary>
    /// <seealso cref="FlaxEditor.Windows.EditorWindow" />
    public class GameWindow : EditorWindow
    {
        private readonly RenderOutputControl _viewport;
        private readonly GameRoot _guiRoot;
        private bool _showGUI = true;
        private bool _showDebugDraw = false;
        private bool _isMaximized = false, _isUnlockingMouse = false;
        private bool _cursorVisible = true;
        private float _gameStartTime;
        private GUI.Docking.DockState _maximizeRestoreDockState;
        private GUI.Docking.DockPanel _maximizeRestoreDockTo;
        private CursorLockMode _cursorLockMode = CursorLockMode.None;

        // Viewport scaling variables
        private List<ViewportScaleOptions> _defaultViewportScaling = new List<ViewportScaleOptions>();
        private List<ViewportScaleOptions> _customViewportScaling = new List<ViewportScaleOptions>();
        private float _viewportAspectRatio = 1;
        private float _windowAspectRatio = 1;
        private bool _useAspect = false;
        private bool _freeAspect = true;

        /// <summary>
        /// Gets the viewport.
        /// </summary>
        public RenderOutputControl Viewport => _viewport;

        /// <summary>
        /// Gets or sets a value indicating whether show game GUI in the view or keep it hidden.
        /// </summary>
        public bool ShowGUI
        {
            get => _showGUI;
            set
            {
                if (value != _showGUI)
                {
                    _showGUI = value;
                    _guiRoot.Visible = value;
                }
            }
        }

        /// <summary>
        /// Gets or sets a value indicating whether show Debug Draw shapes in the view or keep it hidden.
        /// </summary>
        public bool ShowDebugDraw
        {
            get => _showDebugDraw;
            set => _showDebugDraw = value;
        }

        /// <summary>
        /// Gets or sets a value indicating whether game window is maximized (only in play mode).
        /// </summary>
        private bool IsMaximized
        {
            get => _isMaximized;
            set
            {
                if (_isMaximized == value)
                    return;
                _isMaximized = value;
                var rootWindow = RootWindow;
                if (value)
                {
                    // Maximize
                    _maximizeRestoreDockTo = _dockedTo;
                    _maximizeRestoreDockState = _dockedTo.TryGetDockState(out _);
                    if (_maximizeRestoreDockState != GUI.Docking.DockState.Float)
                    {
                        var monitorBounds = Platform.GetMonitorBounds(PointToScreen(Size * 0.5f));
                        ShowFloating(monitorBounds.Location + new Float2(200, 200), Float2.Zero, WindowStartPosition.Manual);
                        rootWindow = RootWindow;
                    }
                    if (rootWindow != null && !rootWindow.IsMaximized)
                        rootWindow.Maximize();
                }
                else
                {
                    // Restore
                    if (rootWindow != null)
                        rootWindow.Restore();
                    if (_maximizeRestoreDockTo != null && _maximizeRestoreDockTo.IsDisposing)
                        _maximizeRestoreDockTo = null;
                    Show(_maximizeRestoreDockState, _maximizeRestoreDockTo);
                }
            }
        }

        /// <summary>
        /// Gets or sets a value indicating whether center mouse position on window focus in play mode. Helps when working with games that lock mouse cursor.
        /// </summary>
        public bool CenterMouseOnFocus { get; set; }

        /// <summary>
        /// Gets or sets a value indicating whether auto-focus game window on play mode start.
        /// </summary>
        public bool FocusOnPlay { get; set; }

        private enum ViewportScaleType
        {
            Resolution = 0,
            Aspect = 1,
        }

        private class ViewportScaleOptions
        {
            /// <summary>
            /// The name.
            /// </summary>
            public string Label;

            /// <summary>
            /// The Type of scaling to do.
            /// </summary>
            public ViewportScaleType ScaleType;

            /// <summary>
            /// The width and height to scale by.
            /// </summary>
            public Int2 Size;

            /// <summary>
            /// If the scaling is active.
            /// </summary>
            public bool Active;
        }

        private class GameRoot : ContainerControl
        {
            public bool EnableEvents => !Time.GamePaused;

            public override bool OnCharInput(char c)
            {
                if (!EnableEvents)
                    return false;

                return base.OnCharInput(c);
            }

            public override DragDropEffect OnDragDrop(ref Float2 location, DragData data)
            {
                if (!EnableEvents)
                    return DragDropEffect.None;

                return base.OnDragDrop(ref location, data);
            }

            public override DragDropEffect OnDragEnter(ref Float2 location, DragData data)
            {
                if (!EnableEvents)
                    return DragDropEffect.None;

                return base.OnDragEnter(ref location, data);
            }

            public override void OnDragLeave()
            {
                if (!EnableEvents)
                    return;

                base.OnDragLeave();
            }

            public override DragDropEffect OnDragMove(ref Float2 location, DragData data)
            {
                if (!EnableEvents)
                    return DragDropEffect.None;

                return base.OnDragMove(ref location, data);
            }

            public override bool OnKeyDown(KeyboardKeys key)
            {
                if (!EnableEvents)
                    return false;

                return base.OnKeyDown(key);
            }

            public override void OnKeyUp(KeyboardKeys key)
            {
                if (!EnableEvents)
                    return;

                base.OnKeyUp(key);
            }

            public override bool OnMouseDoubleClick(Float2 location, MouseButton button)
            {
                if (!EnableEvents)
                    return false;

                return base.OnMouseDoubleClick(location, button);
            }

            public override bool OnMouseDown(Float2 location, MouseButton button)
            {
                if (!EnableEvents)
                    return false;

                return base.OnMouseDown(location, button);
            }

            public override void OnMouseEnter(Float2 location)
            {
                if (!EnableEvents)
                    return;

                base.OnMouseEnter(location);
            }

            public override void OnMouseLeave()
            {
                if (!EnableEvents)
                    return;

                base.OnMouseLeave();
            }

            public override void OnMouseMove(Float2 location)
            {
                if (!EnableEvents)
                    return;

                base.OnMouseMove(location);
            }

            public override bool OnMouseUp(Float2 location, MouseButton button)
            {
                if (!EnableEvents)
                    return false;

                return base.OnMouseUp(location, button);
            }

            public override bool OnMouseWheel(Float2 location, float delta)
            {
                if (!EnableEvents)
                    return false;

                return base.OnMouseWheel(location, delta);
            }
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="GameWindow"/> class.
        /// </summary>
        /// <param name="editor">The editor.</param>
        public GameWindow(Editor editor)
        : base(editor, true, ScrollBars.None)
        {
            Title = "Game";
            AutoFocus = true;

            FlaxEditor.Utilities.Utils.SetupCommonInputActions(this);

            var task = MainRenderTask.Instance;

            // Setup viewport
            _viewport = new RenderOutputControl(task)
            {
                AnchorPreset = AnchorPresets.StretchAll,
                Offsets = Margin.Zero,
                AutoFocus = false,
                Parent = this
            };
            task.PostRender += OnPostRender;

            // Override the game GUI root
            _guiRoot = new GameRoot
            {
                AnchorPreset = AnchorPresets.StretchAll,
                Offsets = Margin.Zero,
                //Visible = false,
                AutoFocus = false,
                Parent = _viewport
            };
            RootControl.GameRoot = _guiRoot;

            SizeChanged += control => { ResizeViewport(); };

            Editor.StateMachine.PlayingState.SceneDuplicating += PlayingStateOnSceneDuplicating;
            Editor.StateMachine.PlayingState.SceneRestored += PlayingStateOnSceneRestored;

            // Link editor options
            Editor.Options.OptionsChanged += OnOptionsChanged;
            OnOptionsChanged(Editor.Options.Options);
        }

        private void ChangeViewportRatio(ViewportScaleOptions v)
        {
            if (v == null)
                return;

            if (v.Size.Y <= 0 || v.Size.X <= 0)
            {
                return;
            }

            if (string.Equals(v.Label, "Free Aspect") && v.Size == new Int2(1, 1))
            {
                _freeAspect = true;
                _useAspect = true;
            }
            else
            {
                switch (v.ScaleType)
                {
                case ViewportScaleType.Aspect:
                    _useAspect = true;
                    _freeAspect = false;
                    break;
                case ViewportScaleType.Resolution:
                    _useAspect = false;
                    _freeAspect = false;
                    break;
                }
            }

            _viewportAspectRatio = (float)v.Size.X / v.Size.Y;

            if (!_freeAspect)
            {
                if (!_useAspect)
                {
                    _viewport.KeepAspectRatio = true;
                    _viewport.CustomResolution = new Int2(v.Size.X, v.Size.Y);
                }
                else
                {
                    _viewport.CustomResolution = new Int2?();
                    _viewport.KeepAspectRatio = true;
                }
            }
            else
            {
                _viewport.CustomResolution = new Int2?();
                _viewport.KeepAspectRatio = false;
            }

            ResizeViewport();
        }

        private void ResizeViewport()
        {
            if (!_freeAspect)
            {
                _windowAspectRatio = Width / Height;
            }
            else
            {
                _windowAspectRatio = 1;
            }

            var scaleWidth = _viewportAspectRatio / _windowAspectRatio;
            var scaleHeight = _windowAspectRatio / _viewportAspectRatio;

            if (scaleHeight < 1)
            {
                _viewport.Bounds = new Rectangle(0, Height * (1 - scaleHeight) / 2, Width, Height * scaleHeight);
            }
            else
            {
                _viewport.Bounds = new Rectangle(Width * (1 - scaleWidth) / 2, 0, Width * scaleWidth, Height);
            }
            PerformLayout();
        }

        private void OnPostRender(GPUContext context, ref RenderContext renderContext)
        {
            // Debug Draw shapes
            if (_showDebugDraw)
            {
                var task = _viewport.Task;

                // Draw actors debug shapes manually if editor viewport is hidden (game viewport task is always rendered before editor viewports)
                var editWindowViewport = Editor.Windows.EditWin.Viewport;
                if (editWindowViewport.Task.LastUsedFrame != Engine.FrameCount)
                {
                    var drawDebugData = editWindowViewport.DebugDrawData;
                    drawDebugData.Clear();
                    var selectedParents = editWindowViewport.TransformGizmo.SelectedParents;
                    if (selectedParents.Count > 0)
                    {
                        for (int i = 0; i < selectedParents.Count; i++)
                        {
                            if (selectedParents[i].IsActiveInHierarchy)
                                selectedParents[i].OnDebugDraw(drawDebugData);
                        }
                    }
                    unsafe
                    {
                        fixed (IntPtr* actors = drawDebugData.ActorsPtrs)
                        {
                            DebugDraw.DrawActors(new IntPtr(actors), drawDebugData.ActorsCount, true);
                        }
                    }
                }

                DebugDraw.Draw(ref renderContext, task.OutputView);
            }
        }

        private void OnOptionsChanged(EditorOptions options)
        {
            CenterMouseOnFocus = options.Interface.CenterMouseOnGameWinFocus;
            FocusOnPlay = options.Interface.FocusGameWinOnPlay;
        }

        private void PlayingStateOnSceneDuplicating()
        {
            // Remove remaining GUI controls so loaded scene can add own GUI
            //_guiRoot.DisposeChildren();

            // Show GUI
            //_guiRoot.Visible = _showGUI;
        }

        private void PlayingStateOnSceneRestored()
        {
            // Remove remaining GUI controls so loaded scene can add own GUI
            //_guiRoot.DisposeChildren();

            // Hide GUI
            //_guiRoot.Visible = false;
        }

        /// <inheritdoc />
        protected override bool CanOpenContentFinder => !Editor.IsPlayMode;

        /// <inheritdoc />
        protected override bool CanUseNavigation => false;

        /// <inheritdoc />
        public override void OnPlayBegin()
        {
            _gameStartTime = Time.UnscaledGameTime;
        }

        /// <inheritdoc />
        public override void OnPlayEnd()
        {
            IsMaximized = false;
            Cursor = CursorType.Default;
        }

        /// <inheritdoc />
        public override void OnShowContextMenu(ContextMenu menu)
        {
            base.OnShowContextMenu(menu);

            // Viewport Brightness
            {
                var brightness = menu.AddButton("Viewport Brightness");
                brightness.CloseMenuOnClick = false;
                var brightnessValue = new FloatValueBox(_viewport.Brightness, 140, 2, 50.0f, 0.001f, 10.0f, 0.001f)
                {
                    Parent = brightness
                };
                brightnessValue.ValueChanged += () => _viewport.Brightness = brightnessValue.Value;
            }

            // Viewport Resolution
            {
                var resolution = menu.AddButton("Viewport Resolution");
                resolution.CloseMenuOnClick = false;
                var resolutionValue = new FloatValueBox(_viewport.ResolutionScale, 140, 2, 50.0f, 0.1f, 4.0f, 0.001f)
                {
                    Parent = resolution
                };
                resolutionValue.ValueChanged += () => _viewport.ResolutionScale = resolutionValue.Value;
            }

            // Viewport aspect ratio
            {
                // Create default scaling options if they dont exist from deserialization.
                if (_defaultViewportScaling.Count == 0)
                {
                    _defaultViewportScaling.Add(new ViewportScaleOptions
                    {
                        Label = "Free Aspect",
                        ScaleType = ViewportScaleType.Aspect,
                        Size = new Int2(1, 1),
                        Active = true,
                    });
                    _defaultViewportScaling.Add(new ViewportScaleOptions
                    {
                        Label = "16:9 Aspect",
                        ScaleType = ViewportScaleType.Aspect,
                        Size = new Int2(16, 9),
                        Active = false,
                    });
                    _defaultViewportScaling.Add(new ViewportScaleOptions
                    {
                        Label = "16:10 Aspect",
                        ScaleType = ViewportScaleType.Aspect,
                        Size = new Int2(16, 10),
                        Active = false,
                    });
                    _defaultViewportScaling.Add(new ViewportScaleOptions
                    {
                        Label = "1920x1080 Resolution",
                        ScaleType = ViewportScaleType.Resolution,
                        Size = new Int2(1920, 1080),
                        Active = false,
                    });
                    _defaultViewportScaling.Add(new ViewportScaleOptions
                    {
                        Label = "2560x1440 Resolution",
                        ScaleType = ViewportScaleType.Resolution,
                        Size = new Int2(2560, 1440),
                        Active = false,
                    });
                }

                var vsMenu = menu.AddChildMenu("Viewport Size").ContextMenu;

                CreateViewportSizingContextMenu(vsMenu);
            }

            // Take Screenshot
            {
                var takeScreenshot = menu.AddButton("Take Screenshot");
                takeScreenshot.Clicked += TakeScreenshot;
            }

            menu.AddSeparator();

            // Show GUI
            {
                var button = menu.AddButton("Show GUI");
                button.CloseMenuOnClick = false;
                var checkbox = new CheckBox(140, 2, ShowGUI) { Parent = button };
                checkbox.StateChanged += x => ShowGUI = x.Checked;
            }

            // Show Debug Draw
            {
                var button = menu.AddButton("Show Debug Draw");
                button.CloseMenuOnClick = false;
                var checkbox = new CheckBox(140, 2, ShowDebugDraw) { Parent = button };
                checkbox.StateChanged += x => ShowDebugDraw = x.Checked;
            }

            menu.MinimumWidth = 200;
            menu.AddSeparator();
        }

        private void CreateViewportSizingContextMenu(ContextMenu vsMenu)
        {
            // Add default viewport sizing options
            for (int i = 0; i < _defaultViewportScaling.Count; i++)
            {
                var viewportScale = _defaultViewportScaling[i];
                var button = vsMenu.AddButton(viewportScale.Label);
                button.CloseMenuOnClick = false;
                button.Icon = viewportScale.Active ? Style.Current.CheckBoxTick : SpriteHandle.Invalid;
                button.Tag = viewportScale;
                if (viewportScale.Active)
                    ChangeViewportRatio(viewportScale);

                button.Clicked += () =>
                {
                    if (button.Tag == null)
                        return;

                    // Reset selected icon on all buttons
                    foreach (var child in vsMenu.Items)
                    {
                        if (child is ContextMenuButton cmb && cmb.Tag is ViewportScaleOptions v)
                        {
                            if (cmb == button)
                            {
                                v.Active = true;
                                button.Icon = Style.Current.CheckBoxTick;
                                ChangeViewportRatio(v);
                            }
                            else if (v.Active)
                            {
                                cmb.Icon = SpriteHandle.Invalid;
                                v.Active = false;
                            }
                        }
                    }
                };
            }
            if (_defaultViewportScaling.Count != 0)
                vsMenu.AddSeparator();

            // Add custom viewport options
            for (int i = 0; i < _customViewportScaling.Count; i++)
            {
                var viewportScale = _customViewportScaling[i];
                var childCM = vsMenu.AddChildMenu(viewportScale.Label);
                childCM.CloseMenuOnClick = false;
                childCM.Icon = viewportScale.Active ? Style.Current.CheckBoxTick : SpriteHandle.Invalid;
                childCM.Tag = viewportScale;
                if (viewportScale.Active)
                    ChangeViewportRatio(viewportScale);

                var applyButton = childCM.ContextMenu.AddButton("Apply");
                applyButton.Tag = childCM.Tag = viewportScale;
                applyButton.CloseMenuOnClick = false;
                applyButton.Clicked += () =>
                {
                    if (childCM.Tag == null)
                        return;

                    // Reset selected icon on all buttons
                    foreach (var child in vsMenu.Items)
                    {
                        if (child is ContextMenuButton cmb && cmb.Tag is ViewportScaleOptions v)
                        {
                            if (child == childCM)
                            {
                                v.Active = true;
                                childCM.Icon = Style.Current.CheckBoxTick;
                                ChangeViewportRatio(v);
                            }
                            else if (v.Active)
                            {
                                cmb.Icon = SpriteHandle.Invalid;
                                v.Active = false;
                            }
                        }
                    }
                };

                var deleteButton = childCM.ContextMenu.AddButton("Delete");
                deleteButton.CloseMenuOnClick = false;
                deleteButton.Clicked += () =>
                {
                    if (childCM.Tag == null)
                        return;

                    var v = (ViewportScaleOptions)childCM.Tag;
                    if (v.Active)
                    {
                        v.Active = false;
                        _defaultViewportScaling[0].Active = true;
                        ChangeViewportRatio(_defaultViewportScaling[0]);
                    }
                    _customViewportScaling.Remove(v);
                    vsMenu.DisposeAllItems();
                    CreateViewportSizingContextMenu(vsMenu);
                    vsMenu.PerformLayout();
                };
            }
            if (_customViewportScaling.Count != 0)
                vsMenu.AddSeparator();

            // Add button
            var add = vsMenu.AddButton("Add...");
            add.CloseMenuOnClick = false;
            add.Clicked += () =>
            {
                var popup = new ContextMenuBase
                {
                    Size = new Float2(230, 125),
                    ClipChildren = false,
                    CullChildren = false,
                };
                popup.Show(add, new Float2(add.Width, 0));

                var nameLabel = new Label
                {
                    Parent = popup,
                    AnchorPreset = AnchorPresets.TopLeft,
                    Text = "Name",
                    HorizontalAlignment = TextAlignment.Near,
                };
                nameLabel.LocalX += 10;
                nameLabel.LocalY += 10;

                var nameTextBox = new TextBox
                {
                    Parent = popup,
                    AnchorPreset = AnchorPresets.TopLeft,
                    IsMultiline = false,
                };
                nameTextBox.LocalX += 100;
                nameTextBox.LocalY += 10;

                var typeLabel = new Label
                {
                    Parent = popup,
                    AnchorPreset = AnchorPresets.TopLeft,
                    Text = "Type",
                    HorizontalAlignment = TextAlignment.Near,
                };
                typeLabel.LocalX += 10;
                typeLabel.LocalY += 35;

                var typeDropdown = new Dropdown
                {
                    Parent = popup,
                    AnchorPreset = AnchorPresets.TopLeft,
                    Items = { "Aspect", "Resolution" },
                    SelectedItem = "Aspect",
                    Width = nameTextBox.Width
                };
                typeDropdown.LocalY += 35;
                typeDropdown.LocalX += 100;

                var whLabel = new Label
                {
                    Parent = popup,
                    AnchorPreset = AnchorPresets.TopLeft,
                    Text = "Width & Height",
                    HorizontalAlignment = TextAlignment.Near,
                };
                whLabel.LocalX += 10;
                whLabel.LocalY += 60;

                var wValue = new IntValueBox(16)
                {
                    Parent = popup,
                    AnchorPreset = AnchorPresets.TopLeft,
                    MinValue = 1,
                    Width = 55,
                };
                wValue.LocalY += 60;
                wValue.LocalX += 100;

                var hValue = new IntValueBox(9)
                {
                    Parent = popup,
                    AnchorPreset = AnchorPresets.TopLeft,
                    MinValue = 1,
                    Width = 55,
                };
                hValue.LocalY += 60;
                hValue.LocalX += 165;

                var submitButton = new Button
                {
                    Parent = popup,
                    AnchorPreset = AnchorPresets.TopLeft,
                    Text = "Submit",
                    Width = 70,
                };
                submitButton.LocalX += 40;
                submitButton.LocalY += 90;

                submitButton.Clicked += () =>
                {
                    Enum.TryParse(typeDropdown.SelectedItem, out ViewportScaleType type);

                    var combineString = type == ViewportScaleType.Aspect ? ":" : "x";
                    var name = nameTextBox.Text + " (" + wValue.Value + combineString + hValue.Value + ") " + typeDropdown.SelectedItem;

                    var newViewportOption = new ViewportScaleOptions
                    {
                        ScaleType = type,
                        Label = name,
                        Size = new Int2(wValue.Value, hValue.Value),
                    };

                    _customViewportScaling.Add(newViewportOption);
                    vsMenu.DisposeAllItems();
                    CreateViewportSizingContextMenu(vsMenu);
                    vsMenu.PerformLayout();
                };

                var cancelButton = new Button
                {
                    Parent = popup,
                    AnchorPreset = AnchorPresets.TopLeft,
                    Text = "Cancel",
                    Width = 70,
                };
                cancelButton.LocalX += 120;
                cancelButton.LocalY += 90;

                cancelButton.Clicked += () =>
                {
                    nameTextBox.Clear();
                    typeDropdown.SelectedItem = "Aspect";
                    hValue.Value = 9;
                    wValue.Value = 16;
                    popup.Hide();
                };
            };
        }

        /// <inheritdoc />
        public override void Draw()
        {
            base.Draw();

            if (Camera.MainCamera == null)
            {
                var style = Style.Current;
                Render2D.DrawText(style.FontLarge, "No camera", new Rectangle(Float2.Zero, Size), style.ForegroundDisabled, TextAlignment.Center, TextAlignment.Center);
            }

            // Selected UI controls outline
            bool drawAnySelectedControl = false;
            // TODO: optimize this (eg. cache list of selected UIControl's when selection gets changed)
            var selection = Editor.SceneEditing.Selection;
            for (var i = 0; i < selection.Count; i++)
            {
                if (selection[i].EditableObject is UIControl controlActor && controlActor && controlActor.Control != null)
                {
                    if (!drawAnySelectedControl)
                    {
                        drawAnySelectedControl = true;
                        Render2D.PushTransform(ref _viewport._cachedTransform);
                    }
                    var options = Editor.Options.Options.Visual;
                    var control = controlActor.Control;
                    var bounds = control.EditorBounds;
                    bounds = Rectangle.FromPoints(control.PointToParent(_viewport, bounds.Location), control.PointToParent(_viewport, bounds.Size));
                    Render2D.DrawRectangle(bounds, options.SelectionOutlineColor0, options.UISelectionOutlineSize);
                }
            }
            if (drawAnySelectedControl)
                Render2D.PopTransform();

            // Play mode hints and overlay
            if (Editor.StateMachine.IsPlayMode)
            {
                var style = Style.Current;
                float time = Time.UnscaledGameTime - _gameStartTime;
                float timeout = 3.0f;
                float fadeOutTime = 1.0f;
                float animTime = time - timeout;
                if (animTime < 0)
                {
                    var alpha = Mathf.Saturate(-animTime / fadeOutTime);
                    var rect = new Rectangle(new Float2(6), Size - 12);
                    var text = "Press Shift+F11 to unlock the mouse";
                    Render2D.DrawText(style.FontSmall, text, rect + new Float2(1.0f), style.Background * alpha, TextAlignment.Near, TextAlignment.Far);
                    Render2D.DrawText(style.FontSmall, text, rect, style.Foreground * alpha, TextAlignment.Near, TextAlignment.Far);
                }

                timeout = 1.0f;
                fadeOutTime = 0.6f;
                animTime = time - timeout;
                if (animTime < 0)
                {
                    float alpha = Mathf.Saturate(-animTime / fadeOutTime);
                    Render2D.DrawRectangle(new Rectangle(new Float2(4), Size - 8), Color.Orange * alpha);
                }

                // Add overlay during debugger breakpoint hang
                if (Editor.Simulation.IsDuringBreakpointHang)
                {
                    var bounds = new Rectangle(Float2.Zero, Size);
                    Render2D.FillRectangle(bounds, new Color(0.0f, 0.0f, 0.0f, 0.2f));
                    Render2D.DrawText(Style.Current.FontLarge, "Debugger breakpoint hit...", bounds, Color.White, TextAlignment.Center, TextAlignment.Center);
                }
            }
        }

        /// <summary>
        /// Unlocks the mouse if game window is focused during play mode.
        /// </summary>
        public void UnlockMouseInPlay()
        {
            if (Editor.StateMachine.IsPlayMode)
            {
                // Cache cursor
                _cursorVisible = Screen.CursorVisible;
                _cursorLockMode = Screen.CursorLock;
                Screen.CursorVisible = true;
                if (Screen.CursorLock == CursorLockMode.Clipped)
                    Screen.CursorLock = CursorLockMode.None;

                // Defocus
                _isUnlockingMouse = true;
                Focus(null);
                _isUnlockingMouse = false;
                Editor.Windows.MainWindow.Focus();
                if (Editor.Windows.PropertiesWin.IsDocked)
                    Editor.Windows.PropertiesWin.Focus();
            }
        }

        /// <summary>
        /// Focuses the game viewport. Shows the window if hidden or unselected.
        /// </summary>
        public void FocusGameViewport()
        {
            if (!IsDocked)
            {
                ShowFloating();
            }
            else if (!IsSelected)
            {
                SelectTab(false);
                RootWindow?.Window?.Focus();
            }
            Focus();
        }

        /// <summary>
        /// Takes the screenshot of the current viewport.
        /// </summary>
        public void TakeScreenshot()
        {
            Screenshot.Capture(_viewport.Task);
        }

        /// <summary>
        /// Takes the screenshot of the current viewport.
        /// </summary>
        /// <param name="path">The output file path.</param>
        public void TakeScreenshot(string path)
        {
            Screenshot.Capture(_viewport.Task, path);
        }

        /// <inheritdoc />
        public override bool OnKeyDown(KeyboardKeys key)
        {
            switch (key)
            {
            case KeyboardKeys.F12:
                Screenshot.Capture(string.Empty);
                return true;
            case KeyboardKeys.F11:
                if (Root.GetKey(KeyboardKeys.Shift))
                {
                    // Unlock mouse in game mode
                    UnlockMouseInPlay();
                    return true;
                }
                else if (Editor.IsPlayMode)
                {
                    // Maximized game window toggle
                    IsMaximized = !IsMaximized;
                    return true;
                }
                break;
            }

            // Prevent closing the game window tab during a play session
            if (Editor.StateMachine.IsPlayMode && Editor.Options.Options.Input.CloseTab.Process(this, key))
            {
                return true;
            }

            return base.OnKeyDown(key);
        }

        /// <inheritdoc />
        public override void OnStartContainsFocus()
        {
            base.OnStartContainsFocus();

            if (Editor.StateMachine.IsPlayMode && !Editor.StateMachine.PlayingState.IsPaused)
            {
                // Center mouse in play mode
                if (CenterMouseOnFocus)
                {
                    var center = PointToWindow(Size * 0.5f);
                    Root.MousePosition = center;
                }

                // Restore cursor
                if (_cursorLockMode != CursorLockMode.None)
                    Screen.CursorLock = _cursorLockMode;
                if (!_cursorVisible)
                    Screen.CursorVisible = false;
            }
        }

        /// <inheritdoc />
        public override void OnEndContainsFocus()
        {
            base.OnEndContainsFocus();

            if (!_isUnlockingMouse)
            {
                // Cache cursor
                _cursorVisible = Screen.CursorVisible;
                _cursorLockMode = Screen.CursorLock;

                // Restore cursor visibility (could be hidden by the game)
                if (!_cursorVisible)
                    Screen.CursorVisible = true;
            }
        }

        /// <inheritdoc />
        public override Control OnNavigate(NavDirection direction, Float2 location, Control caller, List<Control> visited)
        {
            // Block leaking UI navigation focus outside the game window
            if (IsFocused && caller != this)
            {
                // Pick the first UI control if game UI is not focused yet
                foreach (var child in _guiRoot.Children)
                {
                    if (child.Visible)
                        return child.OnNavigate(direction, Float2.Zero, this, visited);
                }
            }
            return null;
        }

        /// <inheritdoc />
        public override bool OnMouseDown(Float2 location, MouseButton button)
        {
            var result = base.OnMouseDown(location, button);

            // Catch user focus
            if (!ContainsFocus)
                Focus();

            return result;
        }

        /// <inheritdoc />
        public override bool UseLayoutData => true;

        /// <inheritdoc />
        public override void OnLayoutSerialize(XmlWriter writer)
        {
            writer.WriteAttributeString("ShowGUI", ShowGUI.ToString());
            writer.WriteAttributeString("ShowDebugDraw", ShowDebugDraw.ToString());
            writer.WriteAttributeString("DefaultViewportScaling", JsonSerializer.Serialize(_defaultViewportScaling));
            writer.WriteAttributeString("CustomViewportScaling", JsonSerializer.Serialize(_customViewportScaling));
        }

        /// <inheritdoc />
        public override void OnLayoutDeserialize(XmlElement node)
        {
            if (bool.TryParse(node.GetAttribute("ShowGUI"), out bool value1))
                ShowGUI = value1;
            if (bool.TryParse(node.GetAttribute("ShowDebugDraw"), out value1))
                ShowDebugDraw = value1;
            if (node.HasAttribute("CustomViewportScaling"))
                _customViewportScaling = JsonSerializer.Deserialize<List<ViewportScaleOptions>>(node.GetAttribute("CustomViewportScaling"));

            for (int i = 0; i < _customViewportScaling.Count; i++)
            {
                if (_customViewportScaling[i].Active)
                    ChangeViewportRatio(_customViewportScaling[i]);
            }

            if (node.HasAttribute("DefaultViewportScaling"))
                _defaultViewportScaling = JsonSerializer.Deserialize<List<ViewportScaleOptions>>(node.GetAttribute("DefaultViewportScaling"));

            for (int i = 0; i < _defaultViewportScaling.Count; i++)
            {
                if (_defaultViewportScaling[i].Active)
                    ChangeViewportRatio(_defaultViewportScaling[i]);
            }
        }

        /// <inheritdoc />
        public override void OnLayoutDeserialize()
        {
            ShowGUI = true;
            ShowDebugDraw = false;
        }
    }
}
