// RibbonParentFormExtender.cs
// Copyright (c) 2019 Bentley Systems, Incorporated. All Rights Reserved.

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Forms;
using System.Windows.Input;
using Haestad.Support.Library;
using Haestad.Support.Support;
using Haestad.Support.Win32Api;
using Microsoft.Win32;

// Note: <whar> Adding System.Windows above will cause grief (due to conflict with System.Drawing). It can be done, but you've been warned.

namespace Haestad.Framework.UI.Integration
{
    /// <summary>
    /// This class acts as a decorator for a borderless WinForms parent form that plays host to a Telerik WPF ribbon via
    /// the TelerikWPFRibbonHost. It takes the parent form and embues it with all the necessary smarts so that it behaves
    /// as a regular application window. It was designed to ensure that absolutely all and any logic that is necessary for
    /// the magic trick to work, lives inside this class, requiring the client to do/know very little about the process.
    /// </summary>
    public class RibbonParentFormExtender : ITitleBarColorInformation, IQueryIsMaximized, ISystemMenuOpener
    {
        #region Constructors
        public RibbonParentFormExtender(Form parentForm, TelerikWPFRibbonHost ribbonHost, Icon applicationIcon)
            : this(parentForm, ribbonHost, applicationIcon.ToBitmap())
        {
        }

        public RibbonParentFormExtender(Form parentForm, TelerikWPFRibbonHost ribbonHost, Image applicationIcon)
        {
            CheckConstructorArguments(parentForm, ribbonHost);

            ParentForm = parentForm;
            RibbonHost = ribbonHost;

            RibbonLoaded = false; // Note: <whar> Set before any initialization code executes.
            WindowsProcedureHookSet = false;

            InitializeEvents();
            InitializeBorderEffectWindow();
            InitializeWindows10Colors();
            InitializeApplicationIcon(applicationIcon);
            InitializeTitleBarControlBoxButtons();
            InitializeParentForm();

            SystemMenuAdded = false;
            ActiveBorder = true;

            JustMaximized = false;
            JustRestoredFromMaximize = false;

            IsMaximizing = false;

            IsRestoringDirectlyToMaximized = false;
        }
        #endregion

        #region Event Handlers

        #region RadRibbon
        private void RadRibbon_Loaded(object sender, System.Windows.RoutedEventArgs e)
        {
            RibbonLoaded = true;

            // Note: <whar> Initialization steps that can only be performed after the ribbon has been fully loaded.
            InitializeUIPostRibbonLoad();
        }
        private void Ribbon_LayoutUpdated(object sender, EventArgs e)
        {
            UpdateTitleBarButtonLayout();
            UpdateTitleBarMargin();
        }

        private void RadRibbon_MouseDown(object sender, MouseButtonEventArgs e)
        {
            if (GetIsOverTitleBar(e))
            {
                if (Mouse.LeftButton == MouseButtonState.Pressed)
                    DragMove();
                else if (Mouse.RightButton == MouseButtonState.Pressed)
                {
                    if (((Keyboard.Modifiers & ModifierKeys.Control) > 0) && (((Keyboard.Modifiers & ModifierKeys.Shift) > 0)))
                        DisplayDeveloperInformationMessageBox();
                    else
                        OpenSystemMenu();
                }
            }
        }

        private void RadRibbon_MouseDoubleClick(object sender, MouseButtonEventArgs e)
        {
            if (GetIsOverTitleBar(e) && e.LeftButton == MouseButtonState.Pressed)
                MaximizeRestoreButton_Click(MaximizeRestoreButton, EventArgs.Empty);
        }

        private void RadRibbon_DragEnter(object sender, System.Windows.DragEventArgs e)
        {
            if (ParentForm is IDragDropTarget)
            {
                System.Windows.Forms.DragEventArgs dragEventArgs = DragDropLibrary.ConvertDragEventArgs(e, RibbonHost.Ribbon);
                ((IDragDropTarget)ParentForm).DragDropTarget_OnDragEnter(dragEventArgs);
                e.Effects = DragDropLibrary.ConvertSystemWindowsFormsDragDropEffects(dragEventArgs.Effect);
                e.Handled = true;
            }
        }

        private void RadRibbon_DragOver(object sender, System.Windows.DragEventArgs e)
        {
            RadRibbon_DragEnter(sender, e);
        }

        private void RadRibbon_Drop(object sender, System.Windows.DragEventArgs e)
        {
            if (ParentForm is IDragDropTarget)
            {
                System.Windows.Forms.DragEventArgs dragEventArgs = DragDropLibrary.ConvertDragEventArgs(e, RibbonHost.Ribbon);
                ((IDragDropTarget)ParentForm).DragDropTarget_OnDragDrop(dragEventArgs);
                e.Effects = DragDropLibrary.ConvertSystemWindowsFormsDragDropEffects(dragEventArgs.Effect);
                e.Handled = true;
            }
        }
        #endregion

        #region Backstage
        private void Backstage_Unloaded(object sender, System.Windows.RoutedEventArgs e)
        {
            if (RibbonHost.Ribbon.Backstage.BackstagePosition == Telerik.Windows.Controls.RibbonView.BackstagePosition.Office2013)
                ApplicationIcon.Visible = true;
        }

        private void Backstage_Loaded(object sender, System.Windows.RoutedEventArgs e)
        {
            if (RibbonHost.Ribbon.Backstage.BackstagePosition == Telerik.Windows.Controls.RibbonView.BackstagePosition.Office2013)
                ApplicationIcon.Visible = false;
        }
        #endregion

        #region ParentForm
        private void ParentForm_FormClosed(object sender, FormClosedEventArgs e)
        {
            // Note: <whar> Observed a curious Win32 error on closing the window, just once so far, despite running this scenario
            // hundreds of times. Added a call to InvokeShutdown based on the following link:
            // http://stackoverflow.com/questions/2053538/invalid-window-handle-on-exiting-of-wpf-application-that-uses-system-addin
            RibbonHost.Ribbon.Dispatcher.InvokeShutdown();
        }

        private void ParentForm_Load(object sender, EventArgs e)
        {
            InitializeParentFormPostParentFormLoad();

            RibbonHost.AfterParentFormLoaded();
        }

        private void ParentForm_Resize(object sender, EventArgs e)
        {
            if (MaximizeRestoreButton != null)
                MaximizeRestoreButton.RefreshToolTip();

            // Note: <whar> Needed for title bar buttons.
            ParentForm.Refresh();

            // NET6MIGRATION
            //if (RibbonHost.Ribbon.AssociatedKeyTipService != null)
            //    RibbonHost.Ribbon.AssociatedKeyTipService.ClearSession();

            //RibbonHost.Ribbon.IsMinimizedPopupOpen = false;
        }

        private void ParentForm_Move(object sender, EventArgs e)
        {
            RefreshTitleBarButtons();

            // NET6MIGRATION
            //if (RibbonHost.Ribbon.AssociatedKeyTipService != null)
            //    RibbonHost.Ribbon.AssociatedKeyTipService.ClearSession();

            //RibbonHost.Ribbon.IsMinimizedPopupOpen = false;
        }

        private void ParentForm_Deactivate(object sender, EventArgs e)
        {
            // NET6MIGRATION
            //if (RibbonHost.Ribbon.AssociatedKeyTipService != null)
            //    RibbonHost.Ribbon.AssociatedKeyTipService.ClearSession();

            //RibbonHost.Ribbon.IsMinimizedPopupOpen = false;
        }

        private void ParentForm_BindingContextChanged(object sender, EventArgs e)
        {
            if (!WindowsProcedureHookSet)
            {
                SetParentFormWindowsProcedureHook();
                WindowsProcedureHookSet = true;
            }
        }
        #endregion

        #region TitleBar Buttons
        private void CloseButton_Click(object sender, EventArgs e)
        {
            ParentForm.Close();
        }

        private void MinimizeButton_Click(object sender, EventArgs e)
        {
            ParentForm.WindowState = FormWindowState.Minimized;
        }

        private void MaximizeRestoreButton_Click(object sender, EventArgs e)
        {
            if (ParentForm.WindowState == FormWindowState.Normal)
                ParentForm.WindowState = FormWindowState.Maximized;
            else
                ParentForm.WindowState = FormWindowState.Normal;

            MaximizeRestoreButton.RefreshToolTip();
            MaximizeRestoreButton.Refresh();
        }
        #endregion

        #endregion

        #region ITitleBarColorInformation

        public Color GetTitleBarButtonBackColor()
        {
            if (RibbonExternalConfiguration.Instance.RibbonDebuggingEnabled)
                return Color.Cyan;
            else
                return RibbonHost.GetTitleBarBackColor();
        }

        public Color GetTitleBarButtonForeColor()
        {
            if (RibbonExternalConfiguration.Instance.Windows10StyleEnabled)
            {
                if (ActiveBorder)
                    return Windows10ActiveTitleBarButtonColor;
                else
                    return Windows10InActiveTitleBarButtonColor;
            }
            else
                return RibbonHost.GetTitleBarButtonForeColor(ActiveBorder);
        }

        public Color GetTitleBarButtonHighlightColor()
        {
            return RibbonHost.GetTitleBarButtonHighlightColor();
        }

        #endregion

        #region IQueryIsMaximized
        public bool IsMaximized
        {
            get { return ParentForm.WindowState == FormWindowState.Maximized; }
        }
        #endregion

        #region ISystemMenuOpener
        /// <summary>
        /// 
        /// </summary>
        public void OpenSystemMenu()
        {
            AddSystemMenu();
            WindowLibrary.OpenSystemMenu(ParentForm.Handle);
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="screenPosition"></param>
        public void OpenSystemMenu(System.Drawing.Point screenPosition)
        {
            AddSystemMenu();
            WindowLibrary.OpenSystemMenu(ParentForm.Handle, screenPosition);
        }
        #endregion

        #region Internal Methods
        internal void AddSystemMenu()
        {
            if (SystemMenuAdded)
                return;

            WindowLibrary.AddWindowStyle(ParentForm.Handle, User32Dll.WS_SYSMENU);
            SystemMenuAdded = true;
        }

        internal void RemoveSystemMenu()
        {
            if (!SystemMenuAdded)
                return;

            WindowLibrary.RemoveWindowStyle(ParentForm.Handle, User32Dll.WS_SYSMENU);
            SystemMenuAdded = false;
        }
        #endregion

        #region Private Methods
        #region Initialization
        private void InitializeEvents()
        {
            ParentForm.Resize += ParentForm_Resize;
            ParentForm.Move += ParentForm_Move;
            ParentForm.BindingContextChanged += ParentForm_BindingContextChanged;
            ParentForm.Load += ParentForm_Load;
            ParentForm.FormClosed += ParentForm_FormClosed;
            ParentForm.Deactivate += ParentForm_Deactivate;

            RibbonHost.Ribbon.MouseDoubleClick += RadRibbon_MouseDoubleClick;
            RibbonHost.Ribbon.MouseDown += RadRibbon_MouseDown;
            RibbonHost.Ribbon.Loaded += RadRibbon_Loaded;
            RibbonHost.Ribbon.LayoutUpdated += Ribbon_LayoutUpdated;

            RibbonHost.Ribbon.DragEnter += RadRibbon_DragEnter;
            RibbonHost.Ribbon.Drop += RadRibbon_Drop;
            RibbonHost.Ribbon.DragOver += RadRibbon_DragOver;
        }

        private void InitializeBorderEffectWindow()
        {
            BorderEffectWindow = new BorderEffectWindow(ParentForm);
        }

        private void InitializeParentFormPostParentFormLoad()
        {
            // Note: <whar> Order in this method important.

            // If we are going to use a Windows managed form border, then under windows 7 our life becomes easier if we
            // disable DWM for the parent form. This reverts the form to "Windows Basic" instead of Aero Glass.
            if (EnvironmentLibrary.IsWindows7)
                WindowLibrary.RemoveWindowGlass(ParentForm.Handle);

            // So the OS can handle WinKey+Up/Down and desktop docking.
            WindowLibrary.AddWindowStyle(ParentForm.Handle, User32Dll.WS_MAXIMIZEBOX);
            WindowLibrary.AddWindowStyle(ParentForm.Handle, User32Dll.WS_MINIMIZEBOX);

            // Note: <whar> So the OS will handle aero snap et al.
            WindowLibrary.AddWindowStyle(ParentForm.Handle, User32Dll.WS_THICKFRAME);
        }

        private void InitializeUIPostRibbonLoad()
        {
            if (RibbonHost.Ribbon.RibbonRootPanel == null) return;

            System.Windows.Controls.Grid titleBarGrid = RibbonHost.Ribbon.RibbonRootPanel.Children[2] as System.Windows.Controls.Grid; // Parent of RibbonTitleBarPanel.
            System.Windows.Controls.Border borderAboveAdditionalContent = titleBarGrid.Children[0] as System.Windows.Controls.Border;

            if (RibbonExternalConfiguration.Instance.RibbonDebuggingEnabled)
            {
                //    RibbonHost.Ribbon.RibbonTitleBarPanel.Background = System.Windows.Media.Brushes.Purple; // Note: <whar> CRITICAL; else panel will not get mouse events.
                //    RibbonHost.Ribbon.RibbonRootPanel.Background = System.Windows.Media.Brushes.Green; // Note: <whar> CRITICAL; else "dead" region to right of title bar will not get mouse events.

                //    // Additional content area.
                //    System.Windows.Controls.StackPanel tabStripAdditionalContent = RibbonHost.Ribbon.HelpButton.Parent as System.Windows.Controls.StackPanel;
                //    tabStripAdditionalContent.Background = System.Windows.Media.Brushes.Yellow;

                //    // Behind ribbon tabs.
                //    System.Windows.Shapes.Rectangle tabRectangle = titleBarGrid.Children[1] as System.Windows.Shapes.Rectangle;
                //    tabRectangle.Fill = System.Windows.Media.Brushes.GreenYellow;

                //    // Border above additional content area, below title bar buttons.
                //    borderAboveAdditionalContent.BorderBrush = System.Windows.Media.Brushes.Salmon;

                //    // Bottom of tabs (normally transparent).
                //    System.Windows.Shapes.Rectangle bottomOfTabsRectangle = titleBarGrid.Children[6] as System.Windows.Shapes.Rectangle;
                //    bottomOfTabsRectangle.Fill = System.Windows.Media.Brushes.Blue;
            }
            else
            {
                RibbonHost.Ribbon.RibbonTitleBarPanel.Background = new System.Windows.Media.SolidColorBrush(ColorLibrary.FromColor(RibbonHost.GetTitleBarBackColor())); // Note: <whar> CRITICAL; else panel will not get mouse events.
                RibbonHost.Ribbon.RibbonRootPanel.Background = new System.Windows.Media.SolidColorBrush(ColorLibrary.FromColor(RibbonHost.GetTitleBarBackColor())); // Note: <whar> CRITICAL; else "dead" region to right of title bar will not get mouse events.
                borderAboveAdditionalContent.BorderBrush = new System.Windows.Media.SolidColorBrush(ColorLibrary.FromColor(RibbonHost.GetTitleBarBackColor()));
            }

            SetTitleBarTextColor();
            SetTitleBarFontSize();

            UpdateTitleBarButtonLayout();
            UpdateTitleBarMargin();

            InitializeBackStageEvents();

            // Note: <whar> Part of a solution for getting hosted WPF to react right off the bat, to keyboard events.
            // https://jasonalls.wordpress.com/2012/08/10/capturing-wpf-usercontrol-key-events-when-hosted-in-a-winforms-elementhost-control/
            RibbonHost.Ribbon.Focus();
        }

        private void InitializeBackStageEvents()
        {
            RibbonHost.Ribbon.Backstage.Loaded += Backstage_Loaded;
            RibbonHost.Ribbon.Backstage.Unloaded += Backstage_Unloaded;
        }
        #endregion

        #region Windows Procedure Hook (Sizing, HitTests, Border drawing, System menu)
        private void SetParentFormWindowsProcedureHook()
        {
            PreviousParentFormWindowsProcedure = (IntPtr)User32Dll.GetWindowLong(ParentForm.Handle, User32Dll.GWL_WNDPROC);
            ParentFormWndProcDelegate = new User32Dll.WndProcDelegate(ParentFormWindowsProcedure);
            User32Dll.SetWindowLong(ParentForm.Handle, User32Dll.GWL_WNDPROC, ParentFormWndProcDelegate);
            WindowsProcedureHookSet = true;
        }

        private int ParentFormWindowsProcedure(IntPtr hWnd, uint uMsg, UIntPtr wParam, IntPtr lParam)
        {
            switch (uMsg)
            {
                case WINDOWS_MESSAGES.WM_PAINT:
                    {
                        // Note: <whar> Despite what the M$ documentation implies, seems necessary to ensure our custom border is drawn under all cases.
                        int result = User32Dll.CallWindowProc(PreviousParentFormWindowsProcedure, ParentForm.Handle, uMsg, wParam, lParam);
                        DrawFormBorder(hWnd);
                        return result;
                    }

                // Note: <whar> Gives us complete control over drawing the non-client area.
                case WINDOWS_MESSAGES.WM_NCPAINT:
                    {
                        DrawFormBorder(hWnd);
                        // https://msdn.microsoft.com/en-us/library/windows/desktop/dd145212%28v=vs.85%29.aspx
                        // An application returns zero if it processes this message.
                        return RETURN_VALUES.FALSE;
                    }

                case WINDOWS_MESSAGES.WM_NCACTIVATE:
                    {
                        // https://msdn.microsoft.com/en-us/library/windows/desktop/ms632633%28v=vs.85%29.aspx
                        // If the window is minimized when this message is received, the application should pass the message to the DefWindowProc function.
                        if (ParentForm.WindowState == FormWindowState.Minimized)
                            return User32Dll.CallWindowProc(PreviousParentFormWindowsProcedure, ParentForm.Handle, uMsg, wParam, lParam);

                        bool wParamAsBoolean = (wParam == (UIntPtr)RETURN_VALUES.TRUE);
                        ActiveBorder = wParamAsBoolean;
                        DrawFormBorder(hWnd);

                        RefreshTitleBarButtons();
                        SetTitleBarTextColor();

                        return RETURN_VALUES.TRUE; // Indicates the system should proceed with default processing.
                    }

                // Note: <whar> This message tells windows what we want our non-client (form extent) dimensions to be. 
                // Since we have a form with the WS_THICKFRAME style, but the form is actually borderless, Windows thinks there is an extra
                // region outside the extents of the form that we will be drawing, but we aren't. So we have to handle this message, without adjusting
                // the window rect (which will tell windows we want a system managed border thickness of 0) and everything will then be kosher.
                case WINDOWS_MESSAGES.WM_NCCALCSIZE:
                    {
                        if (wParam != UIntPtr.Zero)
                        {
                            NCCALCSIZE_PARAMS ncCalcSizeParams = (NCCALCSIZE_PARAMS)Marshal.PtrToStructure(lParam, typeof(NCCALCSIZE_PARAMS));
                            Marshal.StructureToPtr(ncCalcSizeParams, lParam, false);
                        }
                        return RETURN_VALUES.TRUE;
                    }

                case User32Dll.WM_NCHITTEST:
                    {
                        RemoveSystemMenu(); // Note: <whar> Must be removed else regular hit test behavior goes awry.

                        int nonClientHitTestConstant = WindowLibrary.GetNonClientHitTestConstant(GetMouseNonClientAreaPosition());
                        if (nonClientHitTestConstant == NCHITTEST_CONSTANTS.LET_SYSTEM_DECIDE)
                            return User32Dll.CallWindowProc(PreviousParentFormWindowsProcedure, ParentForm.Handle, uMsg, wParam, lParam);
                        else
                            return nonClientHitTestConstant;
                    }

                // Note: <whar> Need to tell Windows that we don't want our borderless form to go full screen (the default for borderless forms).
                case WINDOWS_MESSAGES.WM_GETMINMAXINFO:
                    {
                        // Note: <whar> It seems on Windows 10 running on VMware we can get a different message order and as such this property can be null.
                        // Either way, we shouldn't have relied on the message order, so this check is very much desirable.
                        if (CurrentMonitor == null)
                            break;

                        MINMAXINFO mmi = (MINMAXINFO)Marshal.PtrToStructure(lParam, typeof(MINMAXINFO));

                        mmi.ptMaxPosition.x = CurrentMonitor.WorkingArea.X - CurrentMonitor.Bounds.X;
                        mmi.ptMaxPosition.y = CurrentMonitor.WorkingArea.Y - CurrentMonitor.Bounds.Y;

                        mmi.ptMaxSize.x = CurrentMonitor.WorkingArea.Width;
                        mmi.ptMaxSize.y = CurrentMonitor.WorkingArea.Height;

                        if (IsMaximizing || IsRestoringDirectlyToMaximized)
                        {
                            mmi.ptMaxTrackSize.x = mmi.ptMaxSize.x;
                            mmi.ptMaxTrackSize.y = mmi.ptMaxSize.y;
                        }

                        Marshal.StructureToPtr(mmi, lParam, true);
                    }

                    // TODO 1: <whar> Should I be returning (RETURN_VALUES.TRUE) here?
                    break;

                case WINDOWS_MESSAGES.WM_SYSCOMMAND:
                    {
                        if ((int)wParam == (int)SYSTEM_COMMAND_CONSTANTS.SC_KEYMENU)
                            AddSystemMenu(); // Note: <whar> Add back the menu so that ALT+SPACE can open it.
                        else if ((int)wParam == (int)SYSTEM_COMMAND_CONSTANTS.SC_RESTORE)
                        {
                            if (JustMaximized)
                                IsRestoringDirectlyToMaximized = true;
                            else
                                IsRestoringDirectlyToMaximized = false;
                        }
                    }
                    break;

                case WINDOWS_MESSAGES.WM_SIZE:
                    if ((int)wParam == WMSIZE_CONSTANTS.SIZE_MAXIMIZED) // Maximize complete
                    {
                        JustMaximized = true;
                        IsRestoringDirectlyToMaximized = false;
                    }
                    else if ((int)wParam == WMSIZE_CONSTANTS.SIZE_RESTORED)
                    {
                        if (JustMaximized)
                        {
                            JustMaximized = false;
                            JustRestoredFromMaximize = true;
                        }
                    }
                    RibbonHost.CheckControlSizes();
                    break;

                case WINDOWS_MESSAGES.WM_WINDOWPOSCHANGED:
                    {
                        IsMaximizing = false;
                    }
                    break;

                case WINDOWS_MESSAGES.WM_WINDOWPOSCHANGING:
                    {
                        WINDOWPOS windowPos = (WINDOWPOS)Marshal.PtrToStructure(lParam, typeof(WINDOWPOS));
                        SetCurrentMonitor(windowPos);
                        IsMaximizing = IsWindowAboutToMaximize(windowPos);

                        if (IsMaximized && Screen.FromControl(ParentForm).DeviceName != CurrentMonitor.DeviceName) // Note: <whar> Changing screen.
                        {
                            const int ORIGIN_OFFSET = 1; // Note: <whar> Ran into some weird issue where WINKEY+SHIFT+ARROW won't move from a larger secondary 
                            // screen to a smaller primary screen unless we set the origin to be 1 px away from the primary origin. Since the worst that can
                            // happen is the old busted behavior, there is no risk to this change.

                            windowPos.x = CurrentMonitor.WorkingArea.Location.X + ORIGIN_OFFSET;
                            windowPos.y = CurrentMonitor.WorkingArea.Location.Y;
                            windowPos.cx = CurrentMonitor.WorkingArea.Width - ORIGIN_OFFSET;
                            windowPos.cy = CurrentMonitor.WorkingArea.Height;

                            Marshal.StructureToPtr(windowPos, lParam, false);
                        }

                        // Note: <whar> If we use a DragMove() action (title bar drag) of the parent form to the top of the active work area,
                        // the system maximizes the form. When restored, however, the form first restores to the correct location, then it jumps
                        // back to the location where it was just before the aero-maximize kicked in during the DragMove. This does not happen with
                        // regular windows so this code is to catch this specific case that is just trying to MOVE our form immediately after a restore
                        // and prevent that move. It only catches and prevents a single message, so if it's the erroneous system "jump back" it will be 
                        // prevented. If it's a user who just maximized, restored and then decided to move the form, it will still work since, like I 
                        // just said, we only block the first WM_WINDOWPOSCHANGING immediately after a restore. In the unlikely chance the user's direct
                        // move interaction is the very next WM_WINDOWPOSCHANGING message, their move will still work since we only veto one move message.
                        // Of course re-size (and other operations) will work since we are only dealing with SWP_NOSIZE.
                        if (JustRestoredFromMaximize)
                        {
                            JustRestoredFromMaximize = false;

                            uint flagsToCheck = User32Dll.SWP_NOACTIVATE | User32Dll.SWP_NOZORDER | User32Dll.SWP_NOSIZE;
                            if ((windowPos.flags & flagsToCheck) == flagsToCheck)
                            {
                                windowPos.flags |= User32Dll.SWP_NOMOVE;
                                Marshal.StructureToPtr(windowPos, lParam, false);
                            }
                        }

                        break;
                    }
            }

            return User32Dll.CallWindowProc(PreviousParentFormWindowsProcedure, ParentForm.Handle, uMsg, wParam, lParam);
        }
        #endregion

        #region Border Drawing
        private void DrawFormBorder(IntPtr hWnd)
        {
            IntPtr hdc = Win32Library.GetWindowDC(hWnd);

            using (Graphics g = Graphics.FromHdc(hdc))
            {
                Brush borderBrush = ActiveBorder ? ActiveFormBorderBrush : InactiveFormBorderBrush;

                Brush topBrush = null;
                Brush bottomBrush = null;
                Brush rightBrush = null;
                Brush leftBrush = null;

                if (RibbonExternalConfiguration.Instance.RibbonDebuggingEnabled)
                {
                    topBrush = new SolidBrush(Color.Red);
                    bottomBrush = new SolidBrush(Color.Yellow);
                    rightBrush = new SolidBrush(Color.Blue);
                    leftBrush = new SolidBrush(Color.Magenta);
                }
                else
                    topBrush = bottomBrush = rightBrush = leftBrush = borderBrush;

                g.FillRectangle(topBrush,
                    0,
                    0,
                    ParentForm.Width + (2 * EnvironmentLibrary.VisibleBorderWidth),
                    EnvironmentLibrary.VisibleBorderWidth);

                g.FillRectangle(borderBrush,
                    0,
                    ParentForm.Height - EnvironmentLibrary.VisibleBorderWidth,
                    ParentForm.Width + (2 * EnvironmentLibrary.VisibleBorderWidth),
                    EnvironmentLibrary.VisibleBorderWidth);

                g.FillRectangle(rightBrush,
                    ParentForm.Width - EnvironmentLibrary.VisibleBorderWidth,
                    EnvironmentLibrary.VisibleBorderWidth,
                    EnvironmentLibrary.VisibleBorderWidth,
                    ParentForm.Height - (2 * EnvironmentLibrary.VisibleBorderWidth));

                g.FillRectangle(leftBrush,
                    0,
                    EnvironmentLibrary.VisibleBorderWidth,
                    EnvironmentLibrary.VisibleBorderWidth,
                    ParentForm.Height - (2 * EnvironmentLibrary.VisibleBorderWidth));

                Win32Library.ReleaseDC(hWnd, hdc);

                if (RibbonExternalConfiguration.Instance.RibbonDebuggingEnabled) // Note: <whar> Only creating new GDI objects in this case.
                {
                    topBrush.Dispose();
                    bottomBrush.Dispose();
                    rightBrush.Dispose();
                    leftBrush.Dispose();
                }
            }
        }

        private Color GetActiveFormBorderColor()
        {
            if (RibbonExternalConfiguration.Instance.RibbonDebuggingEnabled)
                return Color.Green;
            else
                return RibbonHost.GetActiveFormBorderColor();
        }

        private Color GetInActiveFormBorderColor()
        {
            if (RibbonExternalConfiguration.Instance.RibbonDebuggingEnabled)
                return Color.Red;
            else
                return RibbonHost.GetInActiveFormBorderColor();
        }
        #endregion

        #region Mouse Location / Hittest logic
        private MouseNonClientAreaPosition GetMouseNonClientAreaPosition()
        {
            if (GetIsMouseOverTitleBar())
                return MouseNonClientAreaPosition.TitleBar;

            // Note: <whar> We don't need to worry about the system menu since we implement the behaviors manually.
            // Furthermore, we handle title bar behaviors manually, so we probably don't need to handle the titlebar
            // hit test either (especially since WPF eats the hit test except at the form 1px border).

            return MouseNonClientAreaPosition.None;
        }

        private bool GetIsMouseOverTitleBar()
        {
            if (Mouse.DirectlyOver != null && Mouse.DirectlyOver.Equals(RibbonHost.Ribbon.RibbonTitleBarPanel))
                return true;
            return false;
        }

        private bool GetIsOverTitleBar(System.Windows.Input.MouseEventArgs e)
        {
            if (e.MouseDevice.DirectlyOver.Equals(RibbonHost.Ribbon.RibbonTitleBarPanel))
                return true;
            return false;
        }
        #endregion

        #region Title Bar
        private void InitializeWindows10Colors()
        {
            Windows10ActiveTitleBarButtonColor = Color.FromArgb(255, 0, 0, 0);
            Windows10InActiveTitleBarButtonColor = Color.FromArgb(255, 153, 153, 153);
        }

        private void InitializeApplicationIcon(Image applicationIcon)
        {
            ApplicationIcon = new ApplicationIcon(applicationIcon, this, ParentForm);

            if (RibbonExternalConfiguration.Instance.RibbonDebuggingEnabled)
                ApplicationIcon.BackColor = System.Drawing.Color.Orange;

            RibbonHost.Controls.Add(ApplicationIcon);
        }

        private void InitializeTitleBarControlBoxButtons()
        {
            TitleBarButton closeButton = NewTitleBarButton(TitleBarButtonType.Close);
            RibbonHost.Controls.Add(closeButton);

            TitleBarButton maxRestoreButton = NewTitleBarButton(TitleBarButtonType.MaximizeRestore);
            RibbonHost.Controls.Add(maxRestoreButton);

            TitleBarButton minimizeButton = NewTitleBarButton(TitleBarButtonType.Minimize);
            RibbonHost.Controls.Add(minimizeButton);
        }

        private void InitializeParentForm()
        {
            ParentForm.FormBorderStyle = FormBorderStyle.None;
            ParentForm.IsMdiContainer = false;

            // Note: <whar> This line seems to be essential for our applications. It seems that this is because, unlike the prototype implementations,
            // a new instance of this class is being created BEFORE calling the Parent Form's InitializeComponent() method. It seems that the Ribbon
            // (by way of it's ElementHost container) needs this property set earlier in the start up sequence, else it does something stupid (double scales,
            // when if I understand things correctly, it should probably be doing the opposite. i.e., no scaling). But nothing surprises me with Telerik now.
            ParentForm.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
        }

        private void UpdateTitleBarMargin()
        {
            if (RibbonHost.Ribbon.RibbonTitleBarPanel == null) return;

            // Note: <whar> Doesn't need to consider scaling; the Ribbon takes care of that.

            int buttonWidth = GetTitleBarButtonWidth(false, false);
            const int NUMBER_OF_BUTTONS = 3;
            const int NUMBER_OF_PADDINGS_PER_BUTTON = 2; // Left and right
            RibbonHost.Ribbon.RibbonTitleBarPanel.Margin = new System.Windows.Thickness(ApplicationIcon.Width + EnvironmentLibrary.ApplicationIconButtonPadding, 0,
                (NUMBER_OF_BUTTONS * buttonWidth) + (NUMBER_OF_PADDINGS_PER_BUTTON * NUMBER_OF_BUTTONS * EnvironmentLibrary.TitleBarButtonPadding), 0);
        }

        private void UpdateTitleBarButtonLayout()
        {
            // Note: <whar> Needs to consider scaling since these are WinForms elements.

            int buttonHeight = GetTitleBarButtonHeight(true, true);
            int buttonWidth = GetTitleBarButtonWidth(true, true);

            ApplicationIcon.Size = new System.Drawing.Size(buttonWidth, buttonHeight);
            ApplicationIcon.Location = new System.Drawing.Point(EnvironmentLibrary.ApplicationIconButtonPadding, EnvironmentLibrary.ApplicationIconButtonPadding);

            buttonHeight = GetTitleBarButtonHeight(true, false);
            buttonWidth = GetTitleBarButtonWidth(true, false) + (RibbonExternalConfiguration.Instance.Windows10StyleEnabled ? Convert.ToInt32(0.65 * buttonWidth) : 0);

            CloseButton.Size = new System.Drawing.Size(buttonWidth, buttonHeight);
            CloseButton.Location = new System.Drawing.Point(RibbonHost.Width - CloseButton.Width - EnvironmentLibrary.TitleBarButtonPadding - LEFT_ADJUSTMENT, EnvironmentLibrary.TitleBarButtonPadding);

            MaximizeRestoreButton.Size = new System.Drawing.Size(buttonWidth, buttonHeight);
            MaximizeRestoreButton.Location = new System.Drawing.Point(CloseButton.Left - buttonWidth - (2 * EnvironmentLibrary.TitleBarButtonPadding), EnvironmentLibrary.TitleBarButtonPadding);

            MinimizeButton.Size = new System.Drawing.Size(buttonWidth, buttonHeight);
            MinimizeButton.Location = new System.Drawing.Point(MaximizeRestoreButton.Left - buttonWidth - (2 * EnvironmentLibrary.TitleBarButtonPadding), EnvironmentLibrary.TitleBarButtonPadding);
        }

        private int GetTitleBarButtonHeight(bool considerScalingFactor, bool applicationIcon)
        {
            if (considerScalingFactor)
                return GetTitleBarButtonDimension(EnvironmentLibrary.GetSystemVerticalScalingFactor(), applicationIcon);
            else
                return GetTitleBarButtonDimension(1.0, applicationIcon);
        }

        private int GetTitleBarButtonWidth(bool considerScalingFactor, bool applicationIcon)
        {
            if (considerScalingFactor)
                return GetTitleBarButtonDimension(EnvironmentLibrary.GetSystemHorizontalScalingFactor(), applicationIcon);
            else
                return GetTitleBarButtonDimension(1.0, applicationIcon);
        }

        private int GetTitleBarButtonDimension(double scalingFactor, bool applicationIcon)
        {
            if (!RibbonLoaded || RibbonHost.Ribbon.RibbonTitleBarPanel == null)
                return 0;

            int padding = applicationIcon ? EnvironmentLibrary.ApplicationIconButtonPadding : EnvironmentLibrary.TitleBarButtonPadding;

            return (int)(RibbonHost.Ribbon.RibbonTitleBarPanel.ActualHeight * scalingFactor) - (2 * padding);
        }

        private void SetTitleBarTextColor()
        {
            // Note: <whar> Due to a change made in Telerik UI for WPF (2016.1.217) the title bar color now displays the same
            // as the back color. I assume this is a "fix" made in support of the RibbonWindow, ignoring the case when that window is not used.
            // a.k.a. a regression/bug.
            // ... The plus side is that I use this method to support form titlebar activation.

            System.Windows.Controls.Control windowTitle = GetWindowTitleControl();
            if (windowTitle != null)
                windowTitle.Foreground = new System.Windows.Media.SolidColorBrush(RibbonHost.GetTitleBarTextForeColor(ActiveBorder));
        }

        private void SetTitleBarFontSize()
        {
            System.Windows.Controls.Control windowTitle = GetWindowTitleControl();
            if (windowTitle == null) return;

            System.Windows.Controls.TextBlock textBlock = windowTitle.GetChildOfType<System.Windows.Controls.TextBlock>();
            const double TITLEBAR_FONTSIZE_INCREASE = 2.0; // Note: <whar> The default size seems too small for me. Changes independently of all other font sizes.
            textBlock.FontSize += TITLEBAR_FONTSIZE_INCREASE;
        }

        private System.Windows.Controls.Control GetWindowTitleControl()
        {
            if (RibbonHost.Ribbon.RibbonTitleBarPanel == null) return null;

            IEnumerable<System.Windows.UIElement> children = RibbonHost.Ribbon.RibbonTitleBarPanel.Children.OfType<System.Windows.UIElement>();
            System.Windows.Controls.Control windowTitle = children.FirstOrDefault(c => Telerik.Windows.Controls.RibbonView.Primitives.RibbonTitleBarPanel.GetElement(c) ==
                Telerik.Windows.Controls.RibbonView.Primitives.RibbonTitleBarElement.WindowTitle) as System.Windows.Controls.Control;
            return windowTitle;
        }

        /// <summary>
        /// Mimics .Net 4.0 implemenation of Window.DragMove() for WPF Windows (a really nice feature missing from Windows.Forms).
        /// Ref: https://msdn.microsoft.com/en-us/library/windows/desktop/ms646360%28v=vs.85%29.aspx
        /// </summary>
        private void DragMove()
        {
            // Note: <whar> Check changed to allow maximized form to be dragged (which will restore during the process).
            if (ParentForm.WindowState == FormWindowState.Minimized)
                return;

            if (Mouse.LeftButton != MouseButtonState.Pressed)
                return;

            int SC_DRAG_MOVE = (int)SYSTEM_COMMAND_CONSTANTS.SC_MOVE | NCHITTEST_CONSTANTS.HTCAPTION;
            User32Dll.SendMessage(ParentForm.Handle, WINDOWS_MESSAGES.WM_SYSCOMMAND, (IntPtr)SC_DRAG_MOVE, IntPtr.Zero);
            User32Dll.SendMessage(ParentForm.Handle, User32Dll.WM_LBUTTONUP, IntPtr.Zero, IntPtr.Zero);
        }

        private TitleBarButton NewTitleBarButton(TitleBarButtonType titleBarButtonType)
        {
            // Note: <whar> Reduced glyph size on account of EnvironmentLibrary.TitleBarButtonPadding currently being set to 0.
            const double TITLE_BAR_SIZE_FACTOR = 0.8;

            TitleBarButton button = null;
            switch (titleBarButtonType)
            {
                case TitleBarButtonType.Minimize:
                    MinimizeButton = new TitleBarMinimizeButton(new EventHandler(MinimizeButton_Click), this, RibbonHost.ToolTip, TITLE_BAR_SIZE_FACTOR);
                    button = MinimizeButton;
                    break;
                case TitleBarButtonType.MaximizeRestore:
                    MaximizeRestoreButton = new TitleBarMaximizeRestoreButton(new EventHandler(MaximizeRestoreButton_Click), this, RibbonHost.ToolTip, TITLE_BAR_SIZE_FACTOR, this);
                    button = MaximizeRestoreButton;
                    break;
                case TitleBarButtonType.Close:
                    CloseButton = new TitleBarCloseButton(new EventHandler(CloseButton_Click), this, RibbonHost.ToolTip, TITLE_BAR_SIZE_FACTOR);
                    button = CloseButton;
                    break;
            }

            button.Anchor = AnchorStyles.Top | AnchorStyles.Right;
            return button;
        }

        private void RefreshTitleBarButtons()
        {
            if (MinimizeButton != null)
                MinimizeButton.Refresh();

            if (MaximizeRestoreButton != null)
                MaximizeRestoreButton.Refresh();

            if (CloseButton != null)
                CloseButton.Refresh();
        }
        #endregion

        #region Argument Checks
        private void CheckConstructorArguments(Form parentForm, TelerikWPFRibbonHost ribbonHost)
        {
            CheckParentForm(parentForm);
            CheckRibbonHost(ribbonHost);
        }

        private void CheckParentForm(Form parentForm)
        {
            if (parentForm == null)
                throw new ArgumentNullException("parentForm", "parentForm cannot be null in class RibbonParentFormExtender."); // I18N. OK
        }

        private void CheckRibbonHost(TelerikWPFRibbonHost ribbonHost)
        {
            if (ribbonHost == null)
                throw new ArgumentNullException("ribbonHost", "ribbonHost cannot be null in class RibbonParentFormExtender."); // I18N. OK
        }
        #endregion

        #region General Support

        private bool IsWindowAboutToMaximize(WINDOWPOS windowPos)
        {
            if (IsMaximized) // Can't maximize if already maximized.
                return false;

            const int SWP_STATECHANGED = 0x8000; // Undocumented M$ value; appears to indicate a state change through minimized/maximized/restored
                                                 // Have also observed with WINKEY operations.
                                                 // Can be found mentioned in "Community Additions" here:
                                                 // https://msdn.microsoft.com/en-us/library/windows/desktop/ms632612%28v=vs.85%29.aspx

            uint flagsToCheck = SWP_STATECHANGED;
            if ((windowPos.flags & flagsToCheck) != flagsToCheck)
                return false;

            // Now can only be one of:
            // 1. Restored --> Minimize
            // 2. Restored --> Maximized
            // 3. Minimized --> Restored
            // 4. Minimized --> Maximized

            const int MINIMIZE_POSITION = -32000; // Note: <whar> In reality, minimizing a form pushes it way off screen (x = -32000, y = -32000).
                                                  // One day this might change to something larger due to the fact that screens are becoming larger and Windows supports
                                                  // more and more screens in varied screen arrays. Although unlikely today, it's conceivable that in future it could occur. 
                                                  // To that end adding some code to make sure that when we encounter the combination of flags that indicate a minimize, 
                                                  // that the value for x and y are also as expected (as a sanity check), else we log it.

            if (windowPos.flags == 33136) // SWP_STATECHANGED + SWP_NOCOPYBITS + SWP_SHOWWINDOW + SWP_FRAMECHANGED + SWP_NOACTIVATE(characteristic of a minimize operation)
            {
                if (windowPos.x != MINIMIZE_POSITION)
                    TraceLibrary.WriteLine(HmTraceLevel.Release, "RibbonParentFormExtender::IsWindowAboutToMaximize(). Detected minimize, but x was not " + MINIMIZE_POSITION.ToString() + ".");  // I18N. OK.
                if (windowPos.y != MINIMIZE_POSITION)
                    TraceLibrary.WriteLine(HmTraceLevel.Release, "RibbonParentFormExtender::IsWindowAboutToMaximize(). Detected minimize, but y was not " + MINIMIZE_POSITION.ToString() + ".");  // I18N. OK.

                return false;
            }

            // Now can only be one of:
            // 2. Restored --> Maximized
            // 3. Minimized --> Restored
            // 4. Minimized --> Maximized
            if (windowPos.flags == 33056) // SWP_STATECHANGED + SWP_NOCOPYBITS + SWP_FRAMECHANGED (characteristic of a restore operation)
                return false;

            // Now can only be one of, both of which are maximize operations.
            // 2. Restored --> Maximized
            // 4. Minimized --> Maximized
            return true;
        }

        private void SetCurrentMonitor(WINDOWPOS windowPos)
        {
            if (windowPos.x == 0 && windowPos.y == 0 && windowPos.cx == 0 && windowPos.cy == 0)
            {
                CurrentMonitor = Screen.FromControl(ParentForm);
                return;
            }

            System.Drawing.Point point = new System.Drawing.Point(windowPos.x + windowPos.cx / 2, windowPos.y + windowPos.cy / 2);
            CurrentMonitor = Screen.FromPoint(point);
        }
        #endregion

        #region Developer Info
        private void DisplayDeveloperInformationMessageBox()
        {
            string clrVersion = Environment.Version.ToString();
            string dotNetVersion = String.Empty;
            if (clrVersion.Contains("4.0.30319")) // I18N. OK.
                dotNetVersion = Get45DotVersionFromRegistry();
            else
                dotNetVersion = "v3.5 or earlier"; // I18N. OK.

            // NET6MIGRATION
            System.Windows.Forms.MessageBox.Show(
                TextManager.Current["hfui_VersionLayout", TextManager.Current["hfui_CLRVersion"], clrVersion] + Environment.NewLine +
                TextManager.Current["hfui_VersionLayout", TextManager.Current["hfui_NETVersion"], dotNetVersion] + Environment.NewLine +
                TextManager.Current["hfui_WindowsVersionLayout", TextManager.Current["hfui_VersionLayout", TextManager.Current["hfui_WindowsVersion"], EnvironmentLibrary.GetOSVersionString()], EnvironmentLibrary.OSMajorVersion.ToString(), EnvironmentLibrary.OSMinorVersion.ToString()],
                TextManager.Current["hfui_DeveloperInfoCaption"],
                MessageBoxButtons.OK, MessageBoxIcon.Information);
        }

        /// <summary>
        /// .NET Version check based on info here: https://msdn.microsoft.com/en-us/library/hh925568%28v=vs.110%29.aspx
        /// </summary>
        private string Get45DotVersionFromRegistry()
        {
            int releaseKey = 0;
            using (RegistryKey ndpKey = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry32).OpenSubKey("SOFTWARE\\Microsoft\\NET Framework Setup\\NDP\\v4\\Full\\")) // I18N. OK.
            {
                releaseKey = Convert.ToInt32(ndpKey.GetValue("Release")); // I18N. OK.
            }

            return CheckFor45DotVersion(releaseKey);
        }

        /// <summary>
        /// .NET Version check based on info here: https://msdn.microsoft.com/en-us/library/hh925568%28v=vs.110%29.aspx
        /// </summary>
        private string CheckFor45DotVersion(int releaseKey)
        {
            if (releaseKey >= 460798)
            {
                return "v4.7 or later"; // I18N. OK.
            }
            if (releaseKey >= 394802)
            {
                return "v4.6.2"; // I18N. OK.
            }
            if (releaseKey >= 394254)
            {
                return "v4.6.1"; // I18N. OK.
            }
            if (releaseKey >= 393295)
            {
                return "v4.6"; // I18N. OK.
            }
            if ((releaseKey >= 379893))
            {
                return "v4.5.2"; // I18N. OK.
            }
            if ((releaseKey >= 378675))
            {
                return "v4.5.1"; // I18N. OK.
            }
            if ((releaseKey >= 378389))
            {
                return "4.5"; // I18N. OK.
            }

            return "v4.0"; // I18N. OK.
        }
        #endregion
        #endregion

        #region Private Properties
        private Color Windows10ActiveTitleBarButtonColor
        {
            get;
            set;
        }

        private Color Windows10InActiveTitleBarButtonColor
        {
            get;
            set;
        }

        private Form ParentForm
        {
            get;
            set;
        }

        private BorderEffectWindow BorderEffectWindow
        {
            get;
            set;
        }

        private TelerikWPFRibbonHost RibbonHost
        {
            get;
            set;
        }

        private TitleBarMinimizeButton MinimizeButton
        {
            get;
            set;
        }

        private TitleBarMaximizeRestoreButton MaximizeRestoreButton
        {
            get;
            set;
        }

        private TitleBarCloseButton CloseButton
        {
            get;
            set;
        }

        private bool ActiveBorder
        {
            get;
            set;
        }

        private Brush ActiveFormBorderBrush
        {
            get
            {
                if (m_activeFormBorderBrush == null)
                    m_activeFormBorderBrush = new SolidBrush(GetActiveFormBorderColor());
                return m_activeFormBorderBrush;
            }
        }

        private Brush InactiveFormBorderBrush
        {
            get
            {
                if (m_inActiveFormBorderBrush == null)
                    m_inActiveFormBorderBrush = new SolidBrush(GetInActiveFormBorderColor());
                return m_inActiveFormBorderBrush;
            }
        }

        private IntPtr PreviousParentFormWindowsProcedure
        {
            get;
            set;
        }

        private User32Dll.WndProcDelegate ParentFormWndProcDelegate // Note: <whar> CRITICAL: Must keep a reference to avoid this becoming victim to the GC.
        {
            get;
            set;
        }

        private bool SystemMenuAdded
        {
            get;
            set;
        }

        private ApplicationIcon ApplicationIcon
        {
            get;
            set;
        }

        private bool RibbonLoaded
        {
            get;
            set;
        }

        private bool WindowsProcedureHookSet
        {
            get;
            set;
        }

        private bool JustRestoredFromMaximize
        {
            get;
            set;
        }

        private bool JustMaximized
        {
            get;
            set;
        }

        private bool IsMaximizing
        {
            get;
            set;
        }

        private bool IsRestoringDirectlyToMaximized
        {
            get;
            set;
        }

        private Screen CurrentMonitor
        {
            get;
            set;
        }
        #endregion

        #region Private Fields
        private Brush m_activeFormBorderBrush;
        private Brush m_inActiveFormBorderBrush;
        private const int LEFT_ADJUSTMENT = 5;
        #endregion
    }
}
