using System;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using Volpe.Cafe.Utils.Win32Native;

namespace Volpe.Cafe.Utils
{
    /// <summary>
    /// Provides wrappers for various win32 native calls, as well as defines various related runtime properties.
    /// </summary>
    public static class Win32
    {
        #region <<< SendMessage wrappers/overloads >>>

        /// <summary>
        /// Sends the specified message to the control. The SendMessage function calls the window procedure for the
        /// specified control and does not return until the window procedure has processed the message.
        /// </summary>
        /// <param name="control">The control whose window procedure will receive the message.</param>
        /// <param name="wMsg">The message to be sent.</param>
        /// <param name="wParam">Additional message-specific information.</param>
        /// <param name="lParam">Additional message-specific information.</param>
        /// <returns>
        /// The return value specifies the result of the message processing; it depends on the message sent.
        /// </returns>
        /// <remarks>
        /// For more info see Microsoft docs:<br/>
        /// https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-sendmessage
        /// </remarks>
        public static int SendMessage(Control control, int wMsg, IntPtr wParam, IntPtr lParam)
        {
            return UnsafeNativeMethods.SendMessage(control.Handle, wMsg, wParam, lParam);
        }
        /// <inheritdoc cref="SendMessage(Control, int, IntPtr, IntPtr)"/>
        public static int SendMessage(Control control, WM wMsg, IntPtr wParam, IntPtr lParam)
        {
            return SendMessage(control, (int)wMsg, wParam, lParam);
        }
        /// <inheritdoc cref="SendMessage(Control, int, IntPtr, IntPtr)"/>
        public static int SendMessage(Control control, EM wMsg, IntPtr wParam, IntPtr lParam)
        {
            return SendMessage(control, (int)wMsg, wParam, lParam);
        }

        /// <inheritdoc cref="SendMessage(Control, int, IntPtr, IntPtr)"/>
        public static int SendMessage(Control control, int wMsg, IntPtr wParam, string lParam)
        {
            IntPtr lParamPtr = Marshal.StringToHGlobalAuto(lParam);
            return SendMessage(control, wMsg, wParam, lParamPtr);
        }
        /// <inheritdoc cref="SendMessage(Control, int, IntPtr, IntPtr)"/>
        public static int SendMessage(Control control, WM wMsg, IntPtr wParam, string lParam)
        {
            return SendMessage(control, (int)wMsg, wParam, lParam);
        }
        /// <inheritdoc cref="SendMessage(Control, int, IntPtr, IntPtr)"/>
        public static int SendMessage(Control control, EM wMsg, IntPtr wParam, string lParam)
        {
            return SendMessage(control, (int)wMsg, wParam, lParam);
        }

        #endregion

        #region <<< DPI Awareness & Scaling >>>

        /// <summary>
        /// Gets whether DPI awareness has been enabled for this application.
        /// </summary>
        public static bool DpiAwarenessEnabled { get; private set; } = false;

        /// <summary>
        /// Gets the ratio of the default DPI scaling setting (96 dpi) and the system's current DPI scaling setting.
        /// </summary>
        /// <remarks>
        /// The system's DPI scaling breaks fixed positioning of GUI controls. The DpiScale value may be used to adjust
        /// the UI fonts to the dimensions for which the application was designed to.<br/><br/>
        /// The default value for DpiScale is 1. This value is updated only if <see cref="SetDpiAwareness"/> is called
        /// and the process-default DPI awareness level for the application has been set successfully.
        /// </remarks>
        public static float DpiScale { get; private set; } = 1;

        /// <summary>
        /// Sets the process-default DPI awareness level and computes the preferred <see cref="DpiScale"/> for the
        /// application on a supported operating system. If the client operating system does not support DPI-aware
        /// applications, DPI awareness will be disabled and <see cref="DpiScale"/> will be set to 1.
        /// </summary>
        public static bool SetDpiAwareness()
        {
            // suppress multiple calls
            if (DpiAwarenessEnabled) {
                return true;
            }

            try {
                // attempt per-monitor DPI awareness -- windows 8.1/server 2012 R2 and later
                SafeNativeMethods.SetProcessDpiAwareness(ProcessDpiAwareness.PerMonitorDpiAware);
            }
            catch {
                try {
                    // attempt system-wide DPI awareness -- windows vista/server 2008 and later
                    SafeNativeMethods.SetProcessDPIAware();
                }
                catch {
                    return false;
                }
            }

            DpiAwarenessEnabled = true;
            using (Graphics g = Graphics.FromHwnd(IntPtr.Zero)) {
                DpiScale = 96F / g.DpiX;
            }
            return true;
        }

        #endregion

        #region <<< Begin/End Update >>>

        /// <summary>
        /// Suspends redrawing of the control and disables events while the control is being updated.
        /// </summary>
        /// <param name="control">The control being updated.</param>
        /// <param name="updateCount">
        ///     The number of calls to <see cref="BeginUpdate(Control, ref int, ref int)"/> thus far. Required to
        ///     handle multiple calls to begin/end update properly. The updateCount is incremented when exiting this
        ///     method.
        /// </param>
        /// <param name="eventMask">
        ///     Stores the event mask to pass to <see cref="EndUpdate(Control, ref int, ref int)"/> when done.
        /// </param>
        /// <remarks>
        /// A call to <see cref="EndUpdate(Control, ref int, ref int)"/> is required when done to enable control's
        /// redrawing and event firing.
        /// </remarks>
        public static void BeginUpdate(Control control, ref int updateCount, ref int eventMask)
        {
            BeginUpdate(control.Handle, ref updateCount, ref eventMask);
        }
        /// <inheritdoc cref="BeginUpdate(Control, ref int, ref int)"/>
        internal static void BeginUpdate(IntPtr handle, ref int updateCount, ref int eventMask)
        {
            // handle multiple calls to begin update
            updateCount++;
            if (updateCount > 1) {
                return;
            }

            // disable rich edit control events and all control redraw
            eventMask = UnsafeNativeMethods.SendMessage(handle, EM.SETEVENTMASK, IntPtr.Zero, IntPtr.Zero);
            UnsafeNativeMethods.SendMessage(handle, WM.SETREDRAW, IntPtr.Zero, IntPtr.Zero);
        }

        /// <summary>
        /// Resumes redrawing of the control and enables events after the control has been updated.
        /// </summary>
        /// <param name="control">The control that was updated.</param>
        /// <param name="updateCount">
        ///     The number of calls to <see cref="BeginUpdate(Control, ref int, ref int)"/> thus far. Required to
        ///     handle multiple calls to begin/end update properly.  The updateCount is decremented when exiting this
        ///     method.
        /// </param>
        /// <param name="eventMask">Stores the event mask of the events.</param>
        /// <returns>
        /// true, if redrawing of the control event processing has been re-enabled; false, otherwise.
        /// </returns>
        public static bool EndUpdate(Control control, ref int updateCount, ref int eventMask)
        {
            return EndUpdate(control.Handle, ref updateCount, ref eventMask);
        }
        /// <inheritdoc cref="EndUpdate(Control, ref int, ref int)"/>
        internal static bool EndUpdate(IntPtr handle, ref int updateCount, ref int eventMask)
        {
            // handle multiple calls to end update
            updateCount--;
            if (updateCount > 0) {
                return false;
            }

            // prevent "rouge" calls to end update
            if (updateCount < 0) {
                updateCount = 0;
                return false;
            }

            // enable rich edit control events and all control redraw
            eventMask = UnsafeNativeMethods.SendMessage(handle, EM.SETEVENTMASK, IntPtr.Zero, new IntPtr(eventMask));
            UnsafeNativeMethods.SendMessage(handle, WM.SETREDRAW, new IntPtr(1), IntPtr.Zero);
            return true;
        }

        #endregion

        #region <<< RedrawWindow wrappers/overloads >>>

        /// <summary>
        /// Updates the specified rectangle or region in a window's client area.
        /// </summary>
        /// <param name="control">The control to be redrawn.</param>
        /// <param name="lprcUpdate">
        ///     A pointer to a RECT structure containing the coordinates, in device units, of the update rectangle.
        ///     This parameter is ignored if the hrgnUpdate parameter identifies a region.
        /// </param>
        /// <param name="hrgnUpdate">
        ///     A handle to the update region. If both the hrgnUpdate and lprcUpdate parameters are NULL, the entire
        ///     client area is added to the update region.
        /// </param>
        /// <param name="flags">
        ///     One or more redraw flags. This parameter can be used to invalidate or validate a window, control
        ///     repainting, and control which windows are affected by RedrawWindow.
        /// </param>
        /// <returns><c>true</c>, if the function succeeds; <c>false</c>, otherwise.</returns>
        /// <remarks>
        /// For more info see Microsoft docs:<br/>
        /// https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-redrawwindow
        /// </remarks>
        public static bool RedrawWindow(Control control, IntPtr lprcUpdate, IntPtr hrgnUpdate, int flags)
        {
            return SafeNativeMethods.RedrawWindow(control.Handle, lprcUpdate, hrgnUpdate, flags);
        }
        /// <inheritdoc cref="RedrawWindow(Control, IntPtr, IntPtr, int)"/>
        public static bool RedrawWindow(Control control, IntPtr lprcUpdate, IntPtr hrgnUpdate, RDW flags)
        {
            return RedrawWindow(control, lprcUpdate, hrgnUpdate, (int)flags);
        }

        /// <inheritdoc cref="RedrawWindow(Control, IntPtr, IntPtr, int)"/>
        public static bool RedrawWindow(
            Control control, ref NativeMethods.RECT lprcUpdate, IntPtr hrgnUpdate, int flags)
        {
            return RedrawWindow(control, lprcUpdate.ToPtr(), hrgnUpdate, flags);
        }
        /// <inheritdoc cref="RedrawWindow(Control, IntPtr, IntPtr, int)"/>
        public static bool RedrawWindow(
            Control control, ref NativeMethods.RECT lprcUpdate, IntPtr hrgnUpdate, RDW flags)
        {
            return RedrawWindow(control, ref lprcUpdate, hrgnUpdate, (int)flags);
        }

        #endregion

        #region <<< SetWindowPos wrappers/overloads >>>

        /// <summary>
        /// Changes the size and position of the control.
        /// </summary>
        /// <param name="control">The control to resize and/or reposition.</param>
        /// <param name="x">The new position of the left side of the window, in client coordinates.</param>
        /// <param name="y">The new position of the top of the window, in client coordinates.</param>
        /// <param name="cx">The new width of the window, in pixels.</param>
        /// <param name="cy">The new height of the window, in pixels.</param>
        /// <param name="flags">The window sizing and positioning flags.</param>
        /// <returns><c>true</c>, if the function succeeds; <c>false</c>, otherwise.</returns>
        /// <remarks>
        /// For more info see Microsoft docs:<br/>
        /// https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-setwindowpos
        /// </remarks>
        public static bool SetWindowPos(Control control, int x, int y, int cx, int cy, int flags)
        {
            return SafeNativeMethods.SetWindowPos(control.Handle, IntPtr.Zero, x, y, cx, cy, flags);
        }
        /// <inheritdoc cref="SetWindowPos(Control, int, int, int, int, int)"/>
        public static bool SetWindowPos(Control control, int x, int y, int cx, int cy, SWP flags)
        {
            return SetWindowPos(control, x, y, cx, cy, (int)flags);
        }

        /// <summary>
        /// Changes the size and position of the control.
        /// </summary>
        /// <param name="control">The control to resize and/or reposition.</param>
        /// <param name="rect">The new bounding rectangle of the control, relative to the parent control.</param>
        /// <param name="flags">The window sizing and positioning flags.</param>
        /// <returns><c>true</c>, if the function succeeds; <c>false</c>, otherwise.</returns>
        /// <inheritdoc cref="SetWindowPos(Control, int, int, int, int, int)" path="/remarks"/>
        public static bool SetWindowPos(Control control, Rectangle rect, int flags)
        {
            return SetWindowPos(control, rect.X, rect.Y, rect.Width, rect.Height, flags);
        }
        /// <inheritdoc cref="SetWindowPos(Control, Rectangle, int)"/>
        public static bool SetWindowPos(Control control, Rectangle rect, SWP flags)
        {
            return SetWindowPos(control, rect, (int)flags);
        }

        #endregion

        #region <<< Timer Resolution >>>

        private static int TIMERR_NOERROR = 0;
        private static uint TimePeriod = 0;
        private static bool TimePeriodSet = false;

        /// <summary>
        /// Requests a minimum resolution for periodic timers.
        /// <br/><br/>
        /// If an application has already requested a <see cref="TimeBeginPeriod"/>, additional calls to this function
        /// have no effect until a <see cref="TimeEndPeriod"/> is called.
        /// </summary>
        /// <param name="period">
        ///     Minimum timer resolution, in milliseconds, for the application or device driver. A lower value
        ///     specifies a higher (more accurate) resolution.
        /// </param>
        /// <remarks>
        /// For more info see Microsoft docs:<br/>
        /// https://learn.microsoft.com/en-us/windows/win32/api/timeapi/nf-timeapi-timebeginperiod <br/>
        /// https://learn.microsoft.com/en-us/windows/win32/api/timeapi/nf-timeapi-timeendperiod
        /// </remarks>
        public static void TimeBeginPeriod(uint period)
        {
            if (TimePeriodSet) {
                return;
            }

            if (UnsafeNativeMethods.timeBeginPeriod(TimePeriod) == TIMERR_NOERROR) {
                TimePeriod = period;
                TimePeriodSet = true;
            }
        }

        /// <summary>
        /// Clears a previously set minimum timer resolution.
        /// <br/><br/>
        /// This function call has no effect if the timer resolution is not currently set.
        /// </summary>
        /// <inheritdoc cref="TimeBeginPeriod(uint)" path="/remarks"/>
        public static void TimeEndPeriod()
        {
            if (!TimePeriodSet) {
                return;
            }

            if (UnsafeNativeMethods.timeEndPeriod(TimePeriod) == TIMERR_NOERROR) {
                TimePeriodSet = false;
            }
        }

        #endregion

        //----- misc helpers -----
        /// <summary>
        /// Extracts the low order bits from the specified value.
        /// </summary>
        public static int LoWord(int value)
        {
            return (short)value;
        }
        /// <inheritdoc cref="LoWord(int)"/>
        public static int LoWord(IntPtr value)
        {
            return (short)value.ToInt64();
        }

        /// <summary>
        /// Extracts the high order bits from the specified value.
        /// </summary>
        public static int HiWord(int value)
        {
            return (short)(value >> 16);
        }
        /// <inheritdoc cref="HiWord(int)"/>
        public static int HiWord(IntPtr value)
        {
            return (short)(value.ToInt64() >> 16);
        }

        /// <summary>
        /// Combines the specified low and high order bits into an integer/IntPtr.
        /// </summary>
        public static int MakeInt(int loWord, int hiWord)
        {
            return (hiWord << 16) | (loWord & 0xffff);
        }
        /// <inheritdoc cref="MakeInt(int, int)"/>
        public static IntPtr MakeLParam(int loWord, int hiWord)
        {
            return (IntPtr)MakeInt(loWord, hiWord);
        }

    }
}
