// WindowLibrary.cs
// Copyright (c) 2016 Bentley Systems, Incorporated. All Rights Reserved.

using System;
using System.Drawing;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Windows.Media;
using System.Windows.Forms;
using System.Text;
using Haestad.Support.Win32Api;

namespace Haestad.Framework.UI.Integration
{
    /// <summary>
    /// Class provies static utility methods for working with Windows (the rectangular UI boxes, not the OS).
    /// </summary>
    internal sealed class WindowLibrary
    {
        #region Constructor
        private WindowLibrary()
        {
        }
        #endregion

        #region Internal Static Methods
        internal static Point GetClientCursorPosition(Control control)
        {
            Point cursorScreenPosition = System.Windows.Forms.Cursor.Position;
            return control.PointToClient(cursorScreenPosition);
        }

        internal static System.Windows.Point GetClientCursorPosition(Visual visual)
        {
            Point cursorScreenPosition = System.Windows.Forms.Cursor.Position;
            return visual.PointFromScreen(GeometryLibrary.GetWindowsPoint(cursorScreenPosition));
        }

        internal static void OpenSystemMenu(IntPtr windowHandle)
        {
            Point cursorScreenPosition = System.Windows.Forms.Cursor.Position;
            OpenSystemMenu(windowHandle, cursorScreenPosition);
        }
        
        internal static void OpenSystemMenu(IntPtr windowHandle, Point screenPosition)
        {
            IntPtr hMenu = Win32Library.GetSystemMenu(windowHandle, false);
            ConfigureSystemMenu(windowHandle, hMenu);

            // https://msdn.microsoft.com/en-us/library/windows/desktop/ms648002%28v=vs.85%29.aspx
            // Call GetSystemMetrics with SM_MENUDROPALIGNMENT to determine the correct horizontal alignment flag (TPM_LEFTALIGN or TPM_RIGHTALIGN) and/or horizontal animation 
            // direction flag (TPM_HORPOSANIMATION or TPM_HORNEGANIMATION) to pass to TrackPopupMenu or TrackPopupMenuEx. This is essential for creating an optimal user 
            // experience, especially when developing Microsoft Tablet PC applications.
            uint menuDropAlignment = (uint)Win32Library.GetSystemMetrics(Win32Library.SystemMetric.SM_MENUDROPALIGNMENT);

            const uint TPM_RETURNCMD = 0x0100; // Note: <whar> Causes TrackPopupMenu to return the menu item command indentifier.
            int menuCommandIdentifer = Win32Library.TrackPopupMenu(hMenu, TPM_RETURNCMD | menuDropAlignment, screenPosition.X, screenPosition.Y, 0, windowHandle, IntPtr.Zero);
            User32Dll.SendMessage(windowHandle, WINDOWS_MESSAGES.WM_SYSCOMMAND, (IntPtr)menuCommandIdentifer, IntPtr.Zero);
        }

        internal static void AddWindowStyle(IntPtr windowHandle, int styleFlagToAdd)
        {
            int windowStyle = User32Dll.GetWindowLong(windowHandle, User32Dll.GWL_STYLE);
            windowStyle |= styleFlagToAdd;
            User32Dll.SetWindowLong(windowHandle, User32Dll.GWL_STYLE, windowStyle);
        }

        internal static void RemoveWindowStyle(IntPtr windowHandle, int styleFlagToRemove)
        {
            int windowStyle = User32Dll.GetWindowLong(windowHandle, User32Dll.GWL_STYLE);
            windowStyle &= ~styleFlagToRemove;
            User32Dll.SetWindowLong(windowHandle, User32Dll.GWL_STYLE, windowStyle);
        }

        internal static void AddExtendedWindowStyle(IntPtr windowHandle, int extendedStyleFlagToAdd)
        {
            int windowStyle = User32Dll.GetWindowLong(windowHandle, User32Dll.GWL_EXSTYLE);
            windowStyle |= extendedStyleFlagToAdd;
            User32Dll.SetWindowLong(windowHandle, User32Dll.GWL_EXSTYLE, windowStyle);
        }

        internal static void RemoveExtendedWindowStyle(IntPtr windowHandle, int extendedStyleFlagToRemove)
        {
            int windowStyle = User32Dll.GetWindowLong(windowHandle, User32Dll.GWL_EXSTYLE);
            windowStyle &= ~extendedStyleFlagToRemove;
            User32Dll.SetWindowLong(windowHandle, User32Dll.GWL_EXSTYLE, windowStyle);
        }

        internal static void MakeWindowTransparent(IntPtr windowHandle, bool transparentToMessages)
        {
            const int WS_EX_LAYERED = 0x80000;
            AddExtendedWindowStyle(windowHandle, WS_EX_LAYERED);
            Win32Library.SetWindowTransparency(windowHandle, true, transparentToMessages);
        }

        internal static void RemoveWindowGlass(IntPtr windowHandle)
        {
            int renderingPolicy = (int)Win32Library.DWMNCRENDERINGPOLICY.DWMNCRP_DISABLED;
            Win32Library.DwmSetWindowAttribute(windowHandle, Win32Library.DWMWINDOWATTRIBUTE.DWMWA_NCRENDERING_POLICY, ref renderingPolicy, sizeof(int));
        }

        internal static int GetNonClientHitTestConstant(MouseNonClientAreaPosition mouseNonClientAreaPosition)
        {
            switch (mouseNonClientAreaPosition)
            {
                case MouseNonClientAreaPosition.TopLeft:
                    return NCHITTEST_CONSTANTS.HTTOPLEFT;
                case MouseNonClientAreaPosition.Top:
                    return NCHITTEST_CONSTANTS.HTTOP;
                case MouseNonClientAreaPosition.TopRight:
                    return NCHITTEST_CONSTANTS.HTTOPRIGHT;
                case MouseNonClientAreaPosition.Left:
                    return NCHITTEST_CONSTANTS.HTLEFT;
                case MouseNonClientAreaPosition.Right:
                    return NCHITTEST_CONSTANTS.HTRIGHT;
                case MouseNonClientAreaPosition.BottomLeft:
                    return NCHITTEST_CONSTANTS.HTBOTTOMLEFT;
                case MouseNonClientAreaPosition.Bottom:
                    return NCHITTEST_CONSTANTS.HTBOTTOM;
                case MouseNonClientAreaPosition.BottomRight:
                    return NCHITTEST_CONSTANTS.HTBOTTOMRIGHT;
                case MouseNonClientAreaPosition.TitleBar:
                    return NCHITTEST_CONSTANTS.HTCAPTION;
                case MouseNonClientAreaPosition.SystemMenu:
                    return NCHITTEST_CONSTANTS.HYSYSMENU;
                default:
                    return NCHITTEST_CONSTANTS.LET_SYSTEM_DECIDE;
            }
        }

        internal static IList<IntPtr> HideAllVisibleWindows(IntPtr parentFormHandle)
        {
            IList<IntPtr> windowHandles = GetAllTopLevelWindowHandles();
            IList<IntPtr> hiddenWindows = new List<IntPtr>();
            foreach (IntPtr windowHandle in windowHandles)
            {
                if (User32Dll.IsWindowVisible(windowHandle) || User32Dll.IsIconic(windowHandle))
                {
                    if (WindowLibrary.IsWindowHandlePartOfThisApplication(parentFormHandle, windowHandle))
                    {
                        User32Dll.ShowWindow(windowHandle, SHOW_WINDOW_CONSTANTS.SW_HIDE);
                        hiddenWindows.Add(windowHandle);
                    }
                }
            }
            return hiddenWindows;
        }

        internal static void RestoreWindows(IList<IntPtr> windowHandles)
        {
            for (int i = windowHandles.Count - 1; i >= 0; i--)
                User32Dll.ShowWindow(windowHandles[i], SHOW_WINDOW_CONSTANTS.SW_SHOW);
        }

        internal static bool GetIsForegroundWindowFromThisApplication(IntPtr parentFormHandle)
        {
            IntPtr foregroundWindowHandle = User32Dll.GetForegroundWindow();

            if (foregroundWindowHandle == parentFormHandle)
                return true;

            // Note: <whar> Docking windows, when un-docked, are top level windows.
            IList<IntPtr> windowHandles = GetAllTopLevelWindowHandles();
            foreach (IntPtr windowHandle in windowHandles)
            {
                if (windowHandle == foregroundWindowHandle)
                    return IsWindowHandlePartOfThisApplication(parentFormHandle, windowHandle);
            }

            return false;
        }

        /// <summary>
        /// Determines whether a specified window handle belongs to this application (as denoted by a specified parent window handle)
        /// by following the form owner heirarchy, and where no owner exists, the parent/child heiarchy.
        /// </summary>
        /// <param name="parentFormHandle">The handle to the parent form that represents the application.</param>
        /// <param name="windowHandle">The handle of the window to check.</param>
        /// <returns>Whether the handle to check is part of this application.</returns>
        internal static bool IsWindowHandlePartOfThisApplication(IntPtr parentFormHandle, IntPtr windowHandle)
        {
            IntPtr lastWindowHandle = windowHandle;
            IntPtr currentWindowHandle = IntPtr.Zero;
            while (lastWindowHandle != IntPtr.Zero)
            {
                currentWindowHandle = GetWindowOwner(lastWindowHandle);
                if (currentWindowHandle == IntPtr.Zero)
                    currentWindowHandle = GetWindowParent(lastWindowHandle);
                if (currentWindowHandle == parentFormHandle)
                    return true;
                lastWindowHandle = currentWindowHandle;
            }
            return false;
        }
        #endregion

        #region Private Static Methods
        private static IntPtr GetWindowOwner(IntPtr windowHandle)
        {
            return User32Dll.GetWindow(windowHandle, User32Dll.GW_OWNER);
        }

        private static IntPtr GetWindowParent(IntPtr windowHandle)
        {
            return Win32Library.GetParent(windowHandle);
        }

        private static void ConfigureSystemMenu(IntPtr windowHandle, IntPtr hMenu)
        {
            const int MIIM_STATE = 1;
            Win32Library.MENUITEMINFO mii = new Win32Library.MENUITEMINFO();
            mii.cbSize = (uint)Marshal.SizeOf(typeof(Win32Library.MENUITEMINFO));
            mii.fMask = MIIM_STATE;
            mii.fType = 0;

            const int MF_ENABLED = 0;
            mii.fState = MF_ENABLED;
            Win32Library.SetMenuItemInfo(hMenu, SYSTEM_COMMAND_CONSTANTS.SC_SIZE, false, ref mii);
            Win32Library.SetMenuItemInfo(hMenu, SYSTEM_COMMAND_CONSTANTS.SC_RESTORE, false, ref mii);
            Win32Library.SetMenuItemInfo(hMenu, SYSTEM_COMMAND_CONSTANTS.SC_MOVE, false, ref mii);
            Win32Library.SetMenuItemInfo(hMenu, SYSTEM_COMMAND_CONSTANTS.SC_MAXIMIZE, false, ref mii);
            Win32Library.SetMenuItemInfo(hMenu, SYSTEM_COMMAND_CONSTANTS.SC_MINIMIZE, false, ref mii);

            Win32Library.WINDOWPLACEMENT wp = Win32Library.GetWindowPlacement(windowHandle);
            const int MF_GRAYED = 1;
            mii.fState = MF_GRAYED;
            switch (wp.showCmd)
            {
                case SHOW_WINDOW_CONSTANTS.SW_SHOWMAXIMIZED:
                    Win32Library.SetMenuItemInfo(hMenu, SYSTEM_COMMAND_CONSTANTS.SC_SIZE, false, ref mii);
                    Win32Library.SetMenuItemInfo(hMenu, SYSTEM_COMMAND_CONSTANTS.SC_MOVE, false, ref mii);
                    Win32Library.SetMenuItemInfo(hMenu, SYSTEM_COMMAND_CONSTANTS.SC_MAXIMIZE, false, ref mii);
                    Win32Library.SetMenuDefaultItem(hMenu, SYSTEM_COMMAND_CONSTANTS.SC_CLOSE, false);
                    break;
                case SHOW_WINDOW_CONSTANTS.SW_SHOWMINIMIZED:
                    Win32Library.SetMenuItemInfo(hMenu, SYSTEM_COMMAND_CONSTANTS.SC_MINIMIZE, false, ref mii);
                    Win32Library.SetMenuDefaultItem(hMenu, SYSTEM_COMMAND_CONSTANTS.SC_RESTORE, false);
                    break;
                case SHOW_WINDOW_CONSTANTS.SW_SHOWNORMAL:
                    Win32Library.SetMenuItemInfo(hMenu, SYSTEM_COMMAND_CONSTANTS.SC_RESTORE, false, ref mii);
                    Win32Library.SetMenuDefaultItem(hMenu, SYSTEM_COMMAND_CONSTANTS.SC_CLOSE, false);
                    break;
            }
        }

        private static IList<IntPtr> GetAllTopLevelWindowHandles()
        {
            WindowHandles = new List<IntPtr>();
            Win32Library.EnumWindowsProc enumProc = new Win32Library.EnumWindowsProc(EnumWindow);
            Win32Library.EnumWindows(enumProc, IntPtr.Zero);
            return WindowHandles;
        }

        private static bool EnumWindow(IntPtr hWnd, IntPtr lParam)
        {
            WindowHandles.Add(hWnd);
            return true;
        }

        private static IList<IntPtr> WindowHandles
        {
            get;
            set;
        }
        #endregion

    }
}
