// TelerikWPFRibbonHost.cs
// Copyright (c) 2018 Bentley Systems, Incorporated. All Rights Reserved.

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Threading;
using System.Windows;
using System.Windows.Forms;
using System.Windows.Forms.Integration;
using System.Windows.Input;
using System.Windows.Interop;
using System.Windows.Media;
using System.Windows.Threading;
using Telerik.Windows.Controls;
using Haestad.Support.Library;
using Haestad.Framework.UI.Integration.Ribbon.Controls;

//
// Note: <whar> ** DO NOT ** add any PUBLIC methods or properties (other than those as part of IRibbonServices).
//

namespace Haestad.Framework.UI.Integration
{
    /// <summary>
    /// Class hosts a Telerik WPF Ribbon in a Windows.Forms UserControl and uses a delegate that takes 
    /// a TelerikWPFRibbonBuilder to layout the ribbon. This class is only interested in interactions that are associated with
    /// hosting the Telerik ribbon. It should not know about ribbon implementation details, except where those details relate
    /// to layout / UI element positioning of this host.
    /// </summary>
    public partial class TelerikWPFRibbonHost : System.Windows.Forms.UserControl, IRibbonServices
    {
        #region Constructors
        public TelerikWPFRibbonHost(IRibbonCallbacks ribbonCallbacks, string applicationName,
            Form parentForm, System.Windows.Forms.UserControl parentFormUI) : this(ribbonCallbacks, applicationName, parentForm, parentFormUI,
            RibbonExternalConfiguration.Instance.RibbonKeyTipsEnabled ? SupportLibrary.AllKeyNavSupportFlagsExceptBackstage() : KeyNavSupportFlags.HotKeys)
        {
        }

        public TelerikWPFRibbonHost(IRibbonCallbacks ribbonCallbacks, string applicationName,
            Form parentForm, System.Windows.Forms.UserControl parentFormUI, KeyNavSupportFlags keyboardNavigationSupportFlags)
        {
            CheckRibbonCallbacks(ribbonCallbacks);
            CheckParentForm(parentForm);
            CheckParentFormUI(parentFormUI);

            // parentFormUI is typically the ApplicationUIContainer
            ((IParentFormUI)parentFormUI).ParentForm = parentForm;

            RibbonCallbacks = ribbonCallbacks;
            ApplicationName = applicationName;
            ParentForm = parentForm;
            ParentFormUI = parentFormUI;
            KeyboardNavigationSupportFlags = keyboardNavigationSupportFlags;

            InitializeComponent();
            InitializeHost();
            InitializeParentFormUI();

            KeyboardHookSet = false;
            WasAnyOtherKeyPressedWhenAltDown = false;
            StartKeyBoardHookQueueProcessingThread();
        }
        #endregion

        #region Event Handlers

        #region Ribbon
        private void Ribbon_ToolBarPositionChanged(object sender, Telerik.Windows.RadRoutedEventArgs e)
        {
            ResetCachedRibbonHeight();
            UpdateHostHeightAndPanelLocationAndHeight();
        }

        private void Ribbon_MinimizedChanged(object sender, Telerik.Windows.RadRoutedEventArgs e)
        {
            UpdateHostHeightAndPanelLocationAndHeight();
        }

        private void Ribbon_SizeChanged(object sender, SizeChangedEventArgs e)
        {
            if (Ribbon.RibbonRootPanel == null) return;

            int scaledRibbonActualHeight = Convert.ToInt32(Ribbon.RibbonRootPanel.ActualHeight * EnvironmentLibrary.GetSystemVerticalScalingFactor());

            if (ElementHost.Height != scaledRibbonActualHeight)
            {
                if (Ribbon.IsMinimized)
                    ActualRibbonMinimizedHeight = scaledRibbonActualHeight;
                else
                    ActualRibbonMaximizedHeight = scaledRibbonActualHeight;

                UpdateHostHeightAndPanelLocationAndHeight();
            }
        }

        #region RibbonBuilder
        private void RibbonBuilder_ApplicationMenuOpenStateChanged(object sender, Telerik.Windows.RadRoutedEventArgs e)
        {
            if (ApplicationMenuOpenStateChanged != null)
                ApplicationMenuOpenStateChanged(sender, e);
        }


        private void RibbonBuilder_RibbonTabChanged(object sender, TabChangeEventArgs e)
        {
            if (RibbonTabChanged != null)
                RibbonTabChanged(sender, e);
        }
        #endregion

        #endregion

        #region Backstage
        private void Backstage_Loaded(object sender, RoutedEventArgs e)
        {
            ElementHost.Height = Height;
            WindowsFormsPanel.Visible = false;

            HiddenWindows = WindowLibrary.HideAllVisibleWindows(ParentForm.Handle);
        }

        private void Backstage_Unloaded(object sender, RoutedEventArgs e)
        {
            ElementHost.Height = GetFullRibbonHeight();
            WindowsFormsPanel.Visible = true;

            UpdateHostHeightAndPanelLocationAndHeight();

            RestoreWindows();
        }
        #endregion

        #region TelerikWPFRibbonHost
        private void TelerikWPFRibbonHost_BindingContextChanged(object sender, EventArgs e)
        {
            AddKeyboardHook();

            // Note: <whar> Definitely needed, else the ParentFormUI is not positioned properly.
            Ribbon_SizeChanged(this, null);
        }
        #endregion

        #region ParentForm
        private void ParentForm_FormClosed(object sender, FormClosedEventArgs e)
        {
            ShouldProcessKeyboardHookKeyEventQueue = false;
            RemoveKeyboardHook();

            RibbonBuilder.SaveRibbonSettings();
        }
        #endregion

        #endregion

        //
        // Note: <whar> ** DO NOT ** add any PUBLIC methods or properties (other than those as part of IRibbonServices).
        //

        #region IRibbonServices Members
        public void ResetToDefaults()
        {
            RibbonBuilder.ResetRibbonSettings();

            if (OnResetRibbonSettings != null)
                OnResetRibbonSettings(this, EventArgs.Empty);

            RibbonBuilder.AfterResetRibbonSettings();
        }

        public event EventHandler<EventArgs> OnResetRibbonSettings;

        public bool IsRibbonEnabled
        {
            get { return Ribbon.IsEnabled; }
            set { Ribbon.IsEnabled = value; }
        }

        public bool IsBackstageOpen
        {
            get { return Ribbon.IsBackstageOpen; }
            set
            {
                Ribbon.IsBackstageOpen = value;

                // Note: <whar> If explicitly set to close, we should do this right now (rather than waiting for the event to be handled)
                // to ensure our forms are back in position before any command executes that may display additional UI.
                RestoreWindows();
            }
        }

        public bool CanBackStageBeOpened
        {
            get { return Ribbon.IsApplicationButtonEnabled; }
            set { Ribbon.IsApplicationButtonEnabled = value; }
        }

        public void EnableHotKeys(bool enable)
        {
            if (enable)
                AddKeyboardHook();
            else
                RemoveKeyboardHook();
        }

        /// <summary>
        /// Right now this answers the Telerik ribbon, but it would not be wise to depend upon that.
        /// If extra support is needed, please extend IRibbonServices.
        /// </summary>
        public FrameworkElement DispatcherElement
        {
            get { return Ribbon; }
        }

        public event EventHandler<Telerik.Windows.RadRoutedEventArgs> ApplicationMenuOpenStateChanged;

        /// <summary>
        /// Returns the well known, unique, default command name for the specified DropDownToggle button.
        /// </summary>
        /// <param name="buttonName">The name of the drop down toggle button that the default command needs to be known for.</param>
        /// <returns>The default command name. If no default command, if the button name is invalid, or if the button is not a DropDownToggle button, returns String.Empty</returns>
        public string GetDefaultCommandNameForDropDownToggleButton(string buttonName)
        {
            return RibbonBuilder.GetDefaultCommandNameForDropDownToggleButton(buttonName);
        }

        public event EventHandler<TabChangeEventArgs> RibbonTabChanged;
        #endregion

        //
        // Note: <whar> ** DO NOT ** add any PUBLIC methods or properties (other than those as part of IRibbonServices).
        //

        #region Internal Methods

        internal void AfterParentFormLoaded()
        {
            RibbonBuilder.AfterParentFormLoaded();
        }

        internal System.Drawing.Color GetTitleBarBackColor()
        {
            System.Windows.Media.Color color = Windows8Palette.Palette.MainColor;
            return ColorLibrary.FromColor(color);
        }

        internal System.Drawing.Color GetTitleBarButtonForeColor(bool activeBorder)
        {
            if (activeBorder)
            {
                System.Windows.Media.Color color = Windows8Palette.Palette.AccentColor;
                return ColorLibrary.FromColor(color);
            }
            else
            {
                System.Windows.Media.Color color = Windows8Palette.Palette.BasicColor;
                return ColorLibrary.FromColor(color);
            }
        }

        internal System.Drawing.Color GetTitleBarButtonHighlightColor()
        {
            // TODO 1: <whar> The ribbon actually takes MainColor and darkens it a little. Time to resurrect my DarkenColor code?
            System.Windows.Media.Color color = Windows8Palette.Palette.BasicColor;
            return ColorLibrary.FromColor(color);
        }

        internal System.Windows.Media.Color GetTitleBarTextForeColor(bool activeBorder)
        {
            if (activeBorder)
                return Windows8Palette.Palette.StrongColor;
            else
                return Windows8Palette.Palette.BasicColor;
        }

        internal System.Drawing.Color GetActiveFormBorderColor()
        {
            System.Windows.Media.Color color = Windows8Palette.Palette.AccentColor;
            return ColorLibrary.FromColor(color);
        }

        internal System.Drawing.Color GetInActiveFormBorderColor()
        {
            System.Windows.Media.Color color = Windows8Palette.Palette.BasicColor;
            return ColorLibrary.FromColor(color);
        }

        #region Form Layout Fixup Issue

        // |---------------------|
        // ||-------------------||
        // ||    ElementHost    ||
        // ||-------------------||
        // ||-------------------||
        // ||                   ||
        // || WindowsFormsPanel ||
        // ||                   ||
        // ||-------------------||
        // |---------------------|

        internal void CheckControlSizes()
        {
            if (WindowsFormsPanel.Width > Width || (WindowsFormsPanel.Height + ElementHost.Height) > Height)
            {
                TraceLibrary.WriteLine(HmTraceLevel.Debug, "TelerikWPFRibbonHost::OnResize(). WindowsFormsPanel is oversized. Sizes are:");  // I18N. OK.
                OutputCurrentSizesToLog();

                AttemptWindowsFormsPanelFixUp();

                TraceLibrary.WriteLine(HmTraceLevel.Debug, "TelerikWPFRibbonHost::OnResize(). Fix up attempt complete. Sizes are now:");  // I18N. OK.
                OutputCurrentSizesToLog();
            }
        }
        #endregion

        #endregion

        #region Protected Override Methods
        protected override void OnLoad(EventArgs e)
        {
            LayoutHostStructure();

            base.OnLoad(e);
        }

        protected override void OnResize(EventArgs e)
        {
            // TFS#795794 ///////////////
            int borderWidth = EnvironmentLibrary.VisibleBorderWidth;
            if (Width != ParentForm.Width - (2 * borderWidth))
                SetRibbonHostPlacement();
            /////////////////////////////

            if (!WindowsFormsPanel.Visible)
            {
                ElementHost.Height = Height;
                ElementHost.Width = Width - WIDTH_ADJUSTMENT;
            }
            else
                CheckControlSizes();

            base.OnResize(e);
        }
        #endregion

        #region Private Methods

        #region Form Layout Fixup Issue

        // |---------------------|
        // ||-------------------||
        // ||    ElementHost    ||
        // ||-------------------||
        // ||-------------------||
        // ||                   ||
        // || WindowsFormsPanel ||
        // ||                   ||
        // ||-------------------||
        // |---------------------|

        private void AttemptWindowsFormsPanelFixUp()
        {
            TraceLibrary.WriteLine(HmTraceLevel.Debug, "TelerikWPFRibbonHost::OnResize(). Attempting fix up.");  // I18N. OK.

            SuspendLayout();

            WindowsFormsPanel.Width = Width - WIDTH_ADJUSTMENT;
            WindowsFormsPanel.Height = Height - ElementHost.Height - HEIGHT_ADJUSTMENT;

            ResumeLayout();
        }

        private void OutputCurrentSizesToLog()
        {
            TraceLibrary.WriteLine(HmTraceLevel.Debug, "Width: " + Width.ToString());  // I18N. OK.
            TraceLibrary.WriteLine(HmTraceLevel.Debug, "Height: " + Height.ToString());  // I18N. OK.
            TraceLibrary.WriteLine(HmTraceLevel.Debug, "ElementHost.Width: " + ElementHost.Width.ToString());  // I18N. OK.
            TraceLibrary.WriteLine(HmTraceLevel.Debug, "ElementHost.Height: " + ElementHost.Height.ToString());  // I18N. OK.
            TraceLibrary.WriteLine(HmTraceLevel.Debug, "WindowsFormsPanel.Width: " + WindowsFormsPanel.Width.ToString());  // I18N. OK.
            TraceLibrary.WriteLine(HmTraceLevel.Debug, "WindowsFormsPanel.Height: " + WindowsFormsPanel.Height.ToString());  // I18N. OK.
        }
        #endregion

        private void RestoreWindows()
        {
            if (HiddenWindows != null)
            {
                WindowLibrary.RestoreWindows(HiddenWindows);
                HiddenWindows = null;
            }
        }

        private delegate void UpdateLayoutDelegate();

        private void LayoutHostStructure()
        {
            //
            // TelerikWPFRibbonHost structure.
            //
            // (WinForms)      (Integration)      (WPF)
            // UserControl --> ElementHost -----> RadRibbonView
            //                 (Anc L/R/T)        (Child fill)
            //            |
            //            |    (WinForms)        (WinForms)
            //            ---> Panel ----------> ParentFormUI (passed in).
            //                 (Anc L/R/T/B)     (Dock.Fill)

            SuspendLayout();

            ElementHost.Location = new System.Drawing.Point(0, 0);
            ElementHost.Width = Width - WIDTH_ADJUSTMENT;
            ElementHost.Anchor = AnchorStyles.Left | AnchorStyles.Right | AnchorStyles.Top;
            ElementHost.Child = Ribbon;

            WindowsFormsPanel.Width = Width - WIDTH_ADJUSTMENT;
            WindowsFormsPanel.Anchor = AnchorStyles.Left | AnchorStyles.Right | AnchorStyles.Top | AnchorStyles.Bottom;

            UpdateHostHeightAndPanelLocationAndHeight(); // Note: <whar> Absolutely required here.

            ParentFormUI.Dock = DockStyle.Fill;
            WindowsFormsPanel.Controls.Add(ParentFormUI);

            Controls.Add(WindowsFormsPanel);
            Controls.Add(ElementHost);

            InitializeRibbonBuilder(); // Note: <whar> With the introduction of persisted settings instantiation can no longer be lazy.
            RibbonCallbacks.LayoutRibbon();
            RibbonBuilder.AfterRibbonLayoutComplete();

            ResumeLayout(true);

            // Note: <whar> Not needed for the prototype to work, but seems to be CRITICAL for our applications. I can make an intelligent 
            // guess that the amount of processing being done during start-up for our main applications cause this logic (if waiting for 
            // normal event processing) to get pushed back too late and this affects the ribbon and/or dependent layout. After about half 
            // a day trying everything that the WWW had to say on the matter, I came up with this one myself. Simply calling UpdateLayout() 
            // does not work here. I really had to use this Invoke technique. This unbricks the layout so at least one UpdateLayout() pass
            // is made synchronously, thus dependent logic now seems to work out.
            ElementHost.HostContainer.InvalidateVisual();
            ElementHost.HostContainer.Dispatcher.Invoke(new UpdateLayoutDelegate(ElementHost.HostContainer.UpdateLayout), System.Windows.Threading.DispatcherPriority.Render, null);

            InitializeEvents();
        }

        private void UpdateHostHeightAndPanelLocationAndHeight()
        {
            if (Ribbon.IsMinimized)
                ElementHost.Height = GetMinimizedRibbonHeight();
            else
                ElementHost.Height = GetFullRibbonHeight();

            WindowsFormsPanel.Location = new System.Drawing.Point(0, ElementHost.Height);
            WindowsFormsPanel.Height = Height - ElementHost.Height - HEIGHT_ADJUSTMENT;
        }

        private int GetFullRibbonHeight()
        {
            if (ActualRibbonMaximizedHeight > 0)
                return ActualRibbonMaximizedHeight;
            else
                return 0;
        }

        private int GetMinimizedRibbonHeight()
        {
            if (ActualRibbonMinimizedHeight > 0)
                return ActualRibbonMinimizedHeight;
            else
                return 0;
        }

        private void ResetCachedRibbonHeight()
        {
            ActualRibbonMaximizedHeight = 0;
            ActualRibbonMinimizedHeight = 0;
        }

        private void InitializeHost()
        {
            SetRibbonHostPlacement();

            ToolTip = new System.Windows.Forms.ToolTip(components);

            if (RibbonExternalConfiguration.Instance.RibbonDebuggingEnabled)
            {
                BackColor = System.Drawing.Color.Red;
                ElementHost.CheckForIllegalCrossThreadCalls = true;
                ElementHost.BackColor = System.Drawing.Color.Orange;
                ElementHost.HostContainer.Background = new SolidColorBrush(System.Windows.Media.Colors.Green);
            }
        }

        private void SetRibbonHostPlacement()
        {
            int borderWidth = EnvironmentLibrary.VisibleBorderWidth;
            int adjustmentFactor = 2;

            Location = new System.Drawing.Point(borderWidth, borderWidth);
            Width = ParentForm.Width - (adjustmentFactor * borderWidth) - WIDTH_ADJUSTMENT;
            Height = ParentForm.Height - (adjustmentFactor * borderWidth);
            Anchor = AnchorStyles.Left | AnchorStyles.Top | AnchorStyles.Right | AnchorStyles.Bottom;
        }

        private void InitializeParentFormUI()
        {
            // Note: <whar> Important: To allow keyboard navigation of the ribbon (prevents the keyboard giving focus to this window).
            ParentFormUI.TabStop = false;
        }

        private void InitializeEvents()
        {
            BindingContextChanged += TelerikWPFRibbonHost_BindingContextChanged;

            Ribbon.Backstage.Loaded += Backstage_Loaded;
            Ribbon.Backstage.Unloaded += Backstage_Unloaded;

            Ribbon.MinimizedChanged += Ribbon_MinimizedChanged;
            Ribbon.ToolBarPositionChanged += Ribbon_ToolBarPositionChanged;
            Ribbon.SizeChanged += Ribbon_SizeChanged;

            RibbonBuilder.ApplicationMenuOpenStateChanged += RibbonBuilder_ApplicationMenuOpenStateChanged;
            RibbonBuilder.RibbonTabChanged += RibbonBuilder_RibbonTabChanged;

            ParentForm.FormClosed += ParentForm_FormClosed;
        }

        private delegate void InvokeCommandDelegate(object parameter);

        private void ProcessKeyBoardHookQueue()
        {
            const int SLEEP_TIME = 10; // ms
            do
            {
                KeyboardHookKeyEvent keyboardHookKeyEvent = null;
                if (KeyboardHookKeyEventQueue.TryDequeue(out keyboardHookKeyEvent))
                {
                    DispatcherOperation dispatcherOperation = Ribbon.Dispatcher.BeginInvoke(
                      new InvokeCommandDelegate(ProcessKeyboardHookKeyEvent), DispatcherPriority.Input, new object[] { keyboardHookKeyEvent });

                    while (!(dispatcherOperation.Status == DispatcherOperationStatus.Completed
                        || dispatcherOperation.Status == DispatcherOperationStatus.Aborted))
                        Thread.Sleep(SLEEP_TIME);
                }
                else
                    Thread.Sleep(SLEEP_TIME);

            } while (ShouldProcessKeyboardHookKeyEventQueue);
        }

        private void ProcessKeyboardHookKeyEvent(object parameter)
        {
            KeyboardHookKeyEvent keyboardHookKeyEvent = parameter as KeyboardHookKeyEvent;

            if (WindowLibrary.GetIsForegroundWindowFromThisApplication(ParentForm.Handle))
            {
                switch (keyboardHookKeyEvent.KeyEventType)
                {
                    case KeyEventType.KeyDown:
                        HandleKeyDown(keyboardHookKeyEvent);
                        break;

                    case KeyEventType.SysKeyDown:
                        HandleSysKeyDown(keyboardHookKeyEvent);
                        break;

                    case KeyEventType.KeyUp:
                        HandleKeyUp(keyboardHookKeyEvent);
                        break;

                    default:
                        break;
                }
            }
        }

        private void RemoveKeyboardHook()
        {
            lock (m_keyboardHookLockObject)
            {
                // Note: <whar> If the hook got removed by the OS (for some reason), we should get this message in the log file.
                if (!Win32Library.UnhookWindowsHookEx(KeyboardHook))
                    TraceLibrary.WriteLine(HmTraceLevel.Release, "TelerikWPFRibbonHost::RemoveKeyboardHook(). Global hook removal failed.");  // I18N. OK.

                KeyboardProcDelegate = null;
                KeyboardHook = IntPtr.Zero;
                KeyboardHookSet = false;
            }
        }

        private void AddKeyboardHook()
        {
            lock (m_keyboardHookLockObject)
            {
                if (!KeyboardHookSet)
                {
                    KeyboardHookSet = true;

                    KeyboardProcDelegate = new Win32Library.LowLevelKeyboardProcDelegate(KeyboardHookCallback);
                    KeyboardHook = Win32Library.SetKeyboardHook(KeyboardProcDelegate);
                }
            }
        }

        private void StartKeyBoardHookQueueProcessingThread()
        {
            ShouldProcessKeyboardHookKeyEventQueue = true;

            Thread queueProcessingThread = new Thread(new ThreadStart(ProcessKeyBoardHookQueue));
            queueProcessingThread.SetApartmentState(ApartmentState.STA);
            queueProcessingThread.Name = "_keyboard_queue_processor_"; // I18N. OK.
            queueProcessingThread.Start();
        }

        // Using a global keyboard hook for hot keys.
        // https://msdn.microsoft.com/en-us/library/windows/desktop/ms644985%28v=vs.85%29.aspx
        // 1. We should do the LEAST amount of work in the HookCallback as possible. M$ says:
        // "If the application must use low level hooks, it should run the hooks on a dedicated thread that passes the work off to a worker thread and then immediately returns."
        // 2. If we exceed the value specified in ms in HKEY_CURRENT_USER\Control Panel\Desktop\LowLevelHooksTimeout\ (typically 5 seconds) we get silently removed from the hook-chain.
        // 3. If nCode is less than zero, we must go right to the next hook procedure and do no processing of our own.
        private IntPtr KeyboardHookCallback(int nCode, IntPtr wParam, IntPtr lParam)
        {
            if (nCode < 0)
                return Win32Library.CallNextHookEx(KeyboardHook, nCode, wParam, lParam);

            // Note: <whar> Grab the state we need and then process that state on a different thread, as per M$ recommendation.
            Win32Library.KBDLLHOOKSTRUCT hookStruct = (Win32Library.KBDLLHOOKSTRUCT)Marshal.PtrToStructure(lParam, typeof(Win32Library.KBDLLHOOKSTRUCT));
            KeyboardHookKeyEventQueue.Enqueue(new KeyboardHookKeyEvent((int)hookStruct.vkCode, (int)wParam));

            return Win32Library.CallNextHookEx(KeyboardHook, nCode, wParam, lParam);
        }

        /// <summary>
        /// Handles the KEYDOWN message from the keyboard hook.
        /// </summary>
        /// <param name="keyboardHookKeyEvent">A KeyboardHookKeyEvent instance with information about the KEYDOWN.</param>
        private void HandleKeyDown(KeyboardHookKeyEvent keyboardHookKeyEvent)
        {
            Key keyPressed = keyboardHookKeyEvent.Key;

            if (RibbonBuilder.HandleRadRibbonKeyPress(keyPressed, keyboardHookKeyEvent.ModifierKeys))
                ProcessedOnKeyDown = keyboardHookKeyEvent;
            else
                ProcessedOnKeyDown = null;
        }

        /// <summary>
        /// Handles the SYSKEYDOWN message from the keyboard hook.
        /// If ALT is down, regular (non system) key presses come through here.
        /// </summary>
        /// <param name="keyboardHookKeyEvent">A KeyboardHookKeyEvent instance with information about the SYSKEYDOWN.</param>
        /// <returns>Whether the key down message should be swallowed.</returns>
        private bool HandleSysKeyDown(KeyboardHookKeyEvent keyboardHookKeyEvent)
        {
            Key keyPressed = keyboardHookKeyEvent.Key;

            if (RibbonBuilder.HandleRadRibbonKeyPress(keyPressed, keyboardHookKeyEvent.ModifierKeys))
                ProcessedOnKeyDown = keyboardHookKeyEvent;
            else
                ProcessedOnKeyDown = null;

            // Note: <whar> If the key press wasn't ALT, flag that some other key was pressed whilst ALT was down.
            // This means that the key up for ALT should not be treated as if it was *ONLY* ALT being pressed (which is linked to Ribbon keytips).
            if (keyPressed != Key.LeftAlt && keyPressed != Key.RightAlt)
                WasAnyOtherKeyPressedWhenAltDown = true;

            return false;
        }

        /// <summary>
        /// Handles the KEYUP message from the keyboard hook.
        /// </summary>
        /// <param name="keyboardHookKeyEvent">A KeyboardHookKeyEvent instance with information about the KEYUP.</param>
        private void HandleKeyUp(KeyboardHookKeyEvent keyboardHookKeyEvent)
        {
            Key keyReleased = keyboardHookKeyEvent.Key;

            // Note: <whar> This is just a pure ALT key event (no other keys involved).
            if (!WasAnyOtherKeyPressedWhenAltDown && (keyReleased == Key.LeftAlt || keyReleased == Key.RightAlt))
                RibbonBuilder.HandleRadRibbonKeyPress(keyReleased, keyboardHookKeyEvent.ModifierKeys);

            if (keyReleased == Key.LeftAlt || keyReleased == Key.RightAlt)
                WasAnyOtherKeyPressedWhenAltDown = false;
        }

        private void CheckRibbonCallbacks(IRibbonCallbacks ribbonCallbacks)
        {
            if (ribbonCallbacks == null)
                throw new ArgumentNullException("ribbonCallbacks", "ribbonCallbacks cannot be null in TelerikWPFRibbonHost."); // I18N. OK
        }

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

        private void CheckParentFormUI(System.Windows.Forms.UserControl parentFormUI)
        {
            if (parentFormUI == null)
                throw new ArgumentNullException("parentFormUI", "parentFormUI cannot be null in TelerikWPFRibbonHost."); // I18N. OK

            if (!(parentFormUI is IParentFormUI))
                throw new ArgumentException("parentFormUI argument must implement IParentFormUI in TelerikWPFRibbonHost."); // I18N. OK.
        }

        private void InitializeRibbonBuilder()
        {
            RibbonBuilder = new TelerikWPFRibbonBuilder(Ribbon, RibbonCallbacks, NewTheme(), ApplicationName, KeyboardNavigationSupportFlags);
        }

        private Theme NewTheme()
        {
            // Note: <whar> We have made a number of Windows8 theme specific fixes/tweaks to Telerik source to get things to work the
            // way we want, so changing to a new theme here (or Implicit themes, if they can be made to work), will likely require that
            // certain things are re-fixed or re-tweaked. 
            Theme theme = new Windows8Theme();

            Windows8Palette.Palette.AccentColor = RibbonExternalConfiguration.Instance.AccentColor;
            Windows8Palette.Palette.BasicColor = RibbonExternalConfiguration.Instance.BasicColor;
            Windows8Palette.Palette.MainColor = RibbonExternalConfiguration.Instance.MainColor;
            Windows8Palette.Palette.MarkerColor = RibbonExternalConfiguration.Instance.MarkerColor;
            Windows8Palette.Palette.StrongColor = RibbonExternalConfiguration.Instance.StrongColor;
            Windows8Palette.Palette.ValidationColor = Colors.Red; // No obvious current use.

            // NET6MIGRATION
            //Windows8Palette.Palette.BackstageHighlightColor = RibbonExternalConfiguration.Instance.BackstageHighlightColor;
            //Windows8Palette.Palette.BackstageSelectedColor = RibbonExternalConfiguration.Instance.BackstageSelectedColor;

            Windows8Palette.Palette.FontSize = RibbonExternalConfiguration.Instance.FontSize;

            return theme;
        }
        #endregion

        #region Public Properties

        //
        // Note: <whar> ** DO NOT ** add any more public properties.
        //

        public TelerikWPFRibbonBuilder RibbonBuilder
        {
            get;
            private set;
        }
        #endregion

        #region Internal Properties
        internal RadRibbonViewEx Ribbon
        {
            get
            {
                if (m_ribbon == null)
                    m_ribbon = new RadRibbonViewEx();
                return m_ribbon;
            }
        }

        internal System.Windows.Forms.ToolTip ToolTip
        {
            get;
            set;
        }
        #endregion

        #region Private Properties
        private ElementHost ElementHost
        {
            get
            {
                if (m_elementHost == null)
                    m_elementHost = new ElementHost();
                return m_elementHost;
            }
        }

        private System.Windows.Forms.Panel WindowsFormsPanel
        {
            get
            {
                if (m_windowsFormsPanel == null)
                    m_windowsFormsPanel = new System.Windows.Forms.Panel();
                return m_windowsFormsPanel;
            }
        }

        private new Form ParentForm
        {
            get;
            set;
        }

        private System.Windows.Forms.UserControl ParentFormUI
        {
            get;
            set;
        }

        private IRibbonCallbacks RibbonCallbacks
        {
            get;
            set;
        }

        private KeyNavSupportFlags KeyboardNavigationSupportFlags
        {
            get;
            set;
        }

        private string ApplicationName
        {
            get;
            set;
        }

        private IntPtr RadRibbonWindowHandle
        {
            get
            {
                if (m_radRibbonWindowHandle == IntPtr.Zero)
                {
                    HwndSource hwndSource = HwndSource.FromVisual(Ribbon) as HwndSource;
                    m_radRibbonWindowHandle = hwndSource.Handle;
                }
                return m_radRibbonWindowHandle;
            }
        }

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

        private IntPtr KeyboardHook
        {
            get;
            set;
        }

        private bool KeyboardHookSet
        {
            get;
            set;
        }

        private KeyboardHookKeyEvent ProcessedOnKeyDown
        {
            get;
            set;
        }

        private bool WasAnyOtherKeyPressedWhenAltDown
        {
            get;
            set;
        }

        private int ActualRibbonMaximizedHeight
        {
            get;
            set;
        }

        private int ActualRibbonMinimizedHeight
        {
            get;
            set;
        }

        private IList<IntPtr> HiddenWindows
        {
            get;
            set;
        }

        private ConcurrentQueue<KeyboardHookKeyEvent> KeyboardHookKeyEventQueue
        {
            get
            {
                if (m_keyboardHookKeyEventQueue == null)
                    m_keyboardHookKeyEventQueue = new ConcurrentQueue<KeyboardHookKeyEvent>();
                return m_keyboardHookKeyEventQueue;
            }
        }

        private bool ShouldProcessKeyboardHookKeyEventQueue
        {
            get;
            set;
        }
        #endregion

        #region Private Fields
        private ElementHost m_elementHost;
        private Panel m_windowsFormsPanel;
        private RadRibbonViewEx m_ribbon;
        private IntPtr m_radRibbonWindowHandle = IntPtr.Zero;
        private ConcurrentQueue<KeyboardHookKeyEvent> m_keyboardHookKeyEventQueue;
        private const int HEIGHT_ADJUSTMENT = 15;
        private const int WIDTH_ADJUSTMENT = 7;
        #endregion

        #region Lock Objects
        private object m_keyboardHookLockObject = new object();
        #endregion
    }
}
