using System;
using System.Windows.Forms;
using static Vanara.PInvoke.User32;

namespace Vanara.Extensions
{
	/// <summary>Control extension methods.</summary>
	public static partial class ControlExtension
	{
		/// <summary>
		/// Performs an action on a control after its handle has been created. If the control's handle has already been created, the action is executed immediately.
		/// </summary>
		/// <param name="ctrl">This control.</param>
		/// <param name="action">The action to execute.</param>
		public static void CallWhenHandleValid(this Control ctrl, Action<Control> action)
		{
			if (ctrl.IsHandleCreated)
			{
				action(ctrl);
			}
			else if (!ctrl.IsDesignMode())
			{
				void handler(object sender, EventArgs e)
				{
					if (!ctrl.IsHandleCreated) return;
					ctrl.HandleCreated -= handler;
					action(ctrl);
				}

				ctrl.HandleCreated += handler;
			}
		}

		/// <summary>Enables all children of a control.</summary>
		/// <param name="ctl">This control.</param>
		/// <param name="enabled">If set to <c>true</c> enable all children, otherwise disable all children.</param>
		public static void EnableChildren(this Control ctl, bool enabled)
		{
			foreach (Control sub in ctl.Controls)
			{
				if (sub is ButtonBase || sub is ListControl || sub is TextBoxBase)
					sub.Enabled = enabled;
				sub.EnableChildren(enabled);
			}
		}

		/// <summary>Gets the control in the list of parents of type <c>T</c>.</summary>
		/// <typeparam name="T">The <see cref="Control"/> based <see cref="Type"/> of the parent control to retrieve.</typeparam>
		/// <param name="ctrl">This control.</param>
		/// <returns>The parent control matching T or null if not found.</returns>
		public static T GetParent<T>(this Control ctrl) where T : class
		{
			var p = ctrl.Parent;
			while (p != null & !(p is T))
				p = p.Parent;
			return p as T;
		}

		/// <summary>Gets the top-most control in the list of parents of type <c>T</c>.</summary>
		/// <typeparam name="T">The <see cref="Control"/> based <see cref="Type"/> of the parent control to retrieve.</typeparam>
		/// <param name="ctrl">This control.</param>
		/// <returns>The top-most parent control matching T or null if not found.</returns>
		public static T GetTopMostParent<T>(this Control ctrl) where T : class
		{
			var stack = new System.Collections.Generic.Stack<Control>();
			var p = ctrl.Parent;
			while (p != null)
			{
				stack.Push(p);
				p = p.Parent;
			}
			while (stack.Count > 0)
				if ((p = stack.Pop()) is T)
					return p as T;
			return null;
		}

		/// <summary>Gets the right to left property.</summary>
		/// <param name="ctrl">This control.</param>
		/// <returns>Culture defined direction of text for this control.</returns>
		public static RightToLeft GetRightToLeftProperty(this Control ctrl)
		{
			while (ctrl != null)
			{
				if (ctrl.RightToLeft != RightToLeft.Inherit)
					return ctrl.RightToLeft;
				ctrl = ctrl.Parent;
			}
			return RightToLeft.No;
		}

		/// <summary>Determines whether this control is in design mode.</summary>
		/// <param name="ctrl">This control.</param>
		/// <returns><c>true</c> if in design mode; otherwise, <c>false</c>.</returns>
		public static bool IsDesignMode(this Control ctrl)
		{
			var p = ctrl;
			while (p != null)
			{
				var site = p.Site;
				if (site != null && site.DesignMode)
					return true;
				p = p.Parent;
			}
			return false;
		}

		/// <summary>
		/// Gets a string using a message pattern that asks for the string length by sending a GetXXXLen message and then a GetXXXText message.
		/// </summary>
		/// <param name="ctrl">The control.</param>
		/// <param name="getLenMsg">The window message identifier for retrieving the string length.</param>
		/// <param name="getTextMsg">The window message identifier for retrieving the string.</param>
		/// <returns>The string result from the message call.</returns>
		public static string GetMessageString(this Control ctrl, uint getLenMsg, uint getTextMsg)
		{
			if (!ctrl.IsHandleCreated) return null;
			var cp = ctrl.SendMessage(getLenMsg).ToInt32() + 1;
			var sb = new System.Text.StringBuilder(cp);
			Vanara.PInvoke.User32.SendMessage(ctrl.Handle, getTextMsg, ref cp, sb);
			return sb.ToString();
		}

		/// <summary>Retrieves the window styles.</summary>
		/// <param name="ctrl">The control.</param>
		/// <returns>The window styles</returns>
		public static int GetStyle(this Control ctrl) => GetWindowLongAuto(ctrl.Handle, WindowLongFlags.GWL_STYLE).ToInt32();

		/// <summary>Removes the mnemonic, if one exists, from the string.</summary>
		/// <param name="str">The string.</param>
		/// <returns>A mnemonic free string.</returns>
		public static string RemoveMnemonic(this string str)
		{
			if (str == null) return null;
			var idx = str?.IndexOf('&');
			if (idx >= 0)
			{
				var sb = new System.Text.StringBuilder(str);
				sb.Remove(idx.Value, 1);
				return sb.ToString();
			}
			return str;
		}

		/// <summary>
		/// <para>
		/// Sends the specified message to a window or windows. The <c>SendMessage</c> function calls the window procedure for the specified window and does not
		/// return until the window procedure has processed the message.
		/// </para>
		/// <para>
		/// To send a message and return immediately, use the <c>SendMessageCallback</c> or <c>SendNotifyMessage</c> function. To post a message to a thread's
		/// message queue and return immediately, use the <c>PostMessage</c> or <c>PostThreadMessage</c> function.
		/// </para>
		/// </summary>
		/// <param name="wnd">
		/// <para>
		/// A window whose window procedure will receive the message.
		/// </para>
		/// <para>
		/// Message sending is subject to UIPI. The thread of a process can send messages only to message queues of threads in processes of lesser or equal
		/// integrity level.
		/// </para>
		/// </param>
		/// <param name="msg">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>
		public static IntPtr SendMessage(this IWin32Window wnd, uint msg, IntPtr wParam = default, IntPtr lParam = default) =>
			wnd.Handle != IntPtr.Zero && wnd.Handle.ToInt32() != -1
				? Vanara.PInvoke.User32.SendMessage(wnd.Handle, msg, wParam, lParam)
				: IntPtr.Zero;

		/// <summary>Sets the windows styles.</summary>
		/// <param name="ctrl">The control.</param>
		/// <param name="style">The style flags.</param>
		/// <param name="on">if set to <c>true</c> add the style, otherwise remove it.</param>
		public static void SetStyle(this Control ctrl, int style, bool on = true)
		{
			var href = ctrl.Handle;
			int oldstyle = GetWindowLongAuto(href, WindowLongFlags.GWL_STYLE).ToInt32();
			if ((oldstyle & style) != style && on)
				SetWindowLong(href, WindowLongFlags.GWL_STYLE, new IntPtr(oldstyle | style));
			else if ((oldstyle & style) == style && !on)
				SetWindowLong(href, WindowLongFlags.GWL_STYLE, new IntPtr(oldstyle & ~style));
			ctrl.Refresh();
		}

		/// <summary>Converts a <see cref="PInvoke.MSG"/> structure to a <see cref="Message"/> structure.</summary>
		/// <param name="msg">The MSG instance.</param>
		/// <returns>An equivalent <see cref="Message"/> structure.</returns>
		public static Message ToMessage(this PInvoke.MSG msg) => Message.Create((IntPtr)msg.hwnd, (int)msg.message, msg.wParam, msg.lParam);

		/// <summary>Converts a <see cref="Message"/> structure to a <see cref="PInvoke.MSG"/> structure.</summary>
		/// <param name="msg">The Message instance.</param>
		/// <returns>An equivalent <see cref="PInvoke.MSG"/> structure.</returns>
		public static PInvoke.MSG ToMSG(this Message msg) => new PInvoke.MSG { message = (uint)msg.Msg, hwnd = msg.HWnd, wParam = msg.WParam, lParam = msg.LParam };
	}
}