using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Runtime.InteropServices;


namespace SystemX.Net.Platform.Common.ExtensionMethods
{
    /// <summary>
    /// http://lostechies.com/derickbailey/2011/01/24/asynchronous-control-updates-in-c-net-winforms/
    /// </summary>
    public static class EmControl
    {
        public static void Do<TControl>(this TControl control, Action<TControl> action)
            where TControl : Control
        {
            try
            {
                if (control.InvokeRequired)
                {
                    try
                    {
                        control.Invoke(action, control);
                    }
                    catch (Exception ex)
                    {
                        if (ex is ObjectDisposedException)
                            Trace.WriteLine(ex);
                        else
                            throw;
                    }
                }
                else if (control.IsHandleCreated)
                    action(control);
                else
                {
                    Console.WriteLine("Error : Before windows handle created.. 창 핸들을 만들기 전까지는....");
                    Trace.WriteLine("Error : Before windows handle created.. 창 핸들을 만들기 전까지는....");
                }
            }
            catch (Exception ex)
            {
                Trace.WriteLine($"Exception on Control.Do(): {ex}");
            }
        }

        /// <summary>
        /// Control.Invoke is synchronous
        /// </summary>
        /// <param name="control"></param>
        /// <param name="action"></param>
        public static void Do(this Control control, Action action)
        {
            try
            {
                if (control.InvokeRequired)
                {
                    try
                    {
                        control.Invoke(action);
                    }
                    catch (Exception ex)
                    {
                        if (ex is ObjectDisposedException)
                            Trace.WriteLine(ex);
                        else
                            throw;
                    }
                }
                else if (control.IsHandleCreated)
                    action();
                else
                {
                    Console.WriteLine("Error : Before windows handle created (2).. 창 핸들을 만들기 전까지는....");
                    Trace.WriteLine("Error : Before windows handle created (2).. 창 핸들을 만들기 전까지는....");
                }
            }
            catch (Exception ex)
            {
                Trace.WriteLine($"Exception on Control.Do(): {ex}");
            }
        }

        public static void Do(this SynchronizationContext context, Action action)
        {
            context.Send(ignore => { action(); }, null);
        }
        public static void DoAsync(this SynchronizationContext context, Action action)
        {
            context.Post(ignore => { action(); }, null);
        }

        public static SynchronizationContext GetSynchronizationContext(this Control control)
        {
            SynchronizationContext context = null;
            control.Do(() =>
            {
                context = SynchronizationContext.Current;
            });

            return context;
        }

        public static TaskScheduler GetTaskScheduler(this Control control)
        {
            TaskScheduler scheduler = null;
            control.Do(() =>
            {
                scheduler = TaskScheduler.FromCurrentSynchronizationContext();
            });

            return scheduler;
        }


        /// <summary>
        /// form 에 대해서, 아직 창 핸들이 만들어 지지 않은 경우, form 이 보여진 후에 해당 action 을 수행한다.
        /// </summary>
        /// <param name="form"></param>
        /// <param name="action"></param>
        public static void DoNowOrLater(this Form form, Action action)
        {
            if (form.InvokeRequired)
                form.Invoke(action);
            else if (form.IsHandleCreated)
                action();
            else
                form.Shown += (sender, args) => { action(); };
        }

        public static async Task DoAsync(this Control control, Action action)
        {
            try
            {
                if (control.InvokeRequired)
                    await Task.Factory.FromAsync(control.BeginInvoke(action), result => { });
                else if (control.IsHandleCreated)
                    action();
                else
                    Console.WriteLine("Error : 창 핸들을 만들기 전까지는....");
            }
            catch (Exception ex)
            {
                Trace.WriteLine($"Exception on Control.Do(): {ex}");
            }
        }


        /// <summary>
        /// How to get return value when BeginInvoke/Invoke is called in C#
        /// http://stackoverflow.com/questions/2214002/how-to-get-return-value-when-begininvoke-invoke-is-called-in-c-sharp
        /// </summary>
        public static T DoGet<T>(this Control control, Func<T> func)
        {
            if (control.InvokeRequired)
                return (T)control.Invoke(func);

            return func();
        }

        public static IEnumerable<Control> CollectAncestors(this Control control, bool includeMe = false)
        {
            if (includeMe)
                yield return control;
            if (control == null || control.Parent == null)
                yield break;

            foreach (var ancestor in control.Parent.CollectAncestors(true))
                yield return ancestor;
        }

        public static IEnumerable<Control> CollectAncestorsTo(this Control control, Control stopAncestorControl,
            bool includeMe = false)
        {
            if (includeMe)
                yield return control;

            if (control == stopAncestorControl || control.Parent == null)
                yield break;

            foreach (var ancestor in control.Parent.CollectAncestorsTo(stopAncestorControl, true))
                yield return ancestor;
        }


        public static IEnumerable<Control> CollectChildren(this Control control, bool includeMe = false)
        {
            if (includeMe)
                yield return control;

            foreach (var child in control.Controls.Cast<Control>())
            {
                foreach (var descendant in child.CollectChildren(true))
                    yield return descendant;
            }

            if (control is TabControl)
            {
                var tab = (TabControl) control;
                foreach (var page in tab.TabPages.Cast<TabPage>())
                {
                    foreach (var descendant in page.CollectChildren(true))
                    {
                        yield return descendant;

                    }
                }
            }
        }


        public static IEnumerable<T> CollectChildren<T>(this Control control, bool includeMe = false) where T : Control
        {
            return control.CollectChildren(includeMe).OfType<T>();

            //if (includeMe && control is T)
            //    yield return control as T;

            //foreach (var child in control.Controls.Cast<Control>())
            //{
            //    foreach (var descendant in child.CollectChildren<T>(true))
            //        yield return descendant;
            //}

            //if (control is TabControl)
            //{
            //    var tab = (TabControl)control;
            //    foreach (var page in tab.TabPages.Cast<TabPage>())
            //    {
            //        foreach (var descendant in page.CollectChildren<T>(true))
            //        {
            //            yield return descendant;

            //        }
            //    }
            //}
        }



        /// <summary>
        /// reference 의 LT 좌표 기준으로, control 의 LT 좌표의 offset 값을 반환한다.
        /// </summary>
        /// <param name="control"></param>
        /// <param name="reference"></param>
        /// <returns></returns>
        public static Point GetRelativePoint(this Control control, Control reference)
        {
            return reference.PointToClient(control.PointToScreen(control.Location));
        }

        public static Rectangle GetRelativeRectangle(this Control control, Control reference)
        {
            return reference.RectangleToClient(control.RectangleToScreen(control.ClientRectangle));
        }

        public static void SetBackgroundImage(this Control control, Image image)
        {
            if (control.BackgroundImage != null)
                control.BackgroundImage.Dispose();

            control.BackgroundImage = image;
        }

        public static void MakeTransparent(this Control control)
        {
            try
            {
                if (control is Button)
                    ((Button) control).FlatStyle = FlatStyle.Flat;

                control.ForceMakeTransparent();
                control.BackColor = Color.Transparent;
            }
            catch (Exception)
            {
            }
        }


        /// <summary>
        /// http://solvedstack.com/questions/transparency-for-windows-forms-textbox
        /// <c>
        /// bool itWorked = SetStyle(xControl, ControlStyles.SupportsTransparentBackColor, true);
        /// </c>
        /// </summary>
        /// <param name="control"></param>
        /// <param name="style"></param>
        /// <param name="value"></param>
        /// <returns></returns>
        public static bool ForceSetStyle(this Control control, ControlStyles style, bool value)
        {
            Type typeTB = typeof (Control);
            System.Reflection.MethodInfo misSetStyle = typeTB.GetMethod("SetStyle",
                System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
            if (misSetStyle != null && control != null)
            {
                misSetStyle.Invoke(control, new object[] {style, value});
                return true;
            }
            return false;
        }

        public static bool ForceMakeTransparent(this Control control, bool transparent = true)
        {
            return control.ForceSetStyle(ControlStyles.SupportsTransparentBackColor, transparent);
        }


        /// <summary>
        /// http://stackoverflow.com/questions/435433/what-is-the-preferred-way-to-find-focused-control-in-winforms-app
        /// </summary>
        /// <param name="control"></param>
        /// <returns></returns>
        public static Control FindFocusedControl(this Control control)
        {
            var container = control as IContainerControl;
            while (container != null)
            {
                control = container.ActiveControl;
                container = control as IContainerControl;
            }
            return control;
        }


        // http://stackoverflow.com/questions/4747935/c-sharp-winform-check-if-control-is-physicaly-visible
        [DllImport("user32.dll")]
        static extern IntPtr WindowFromPoint(POINT Point);

        [StructLayout(LayoutKind.Sequential)]
        public struct POINT
        {
            public int X;
            public int Y;

            public POINT(int x, int y) { X = x;  Y = y; }
            public static implicit operator Point(POINT p) => new Point(p.X, p.Y);
            public static implicit operator POINT(Point p) => new POINT(p.X, p.Y);
        }

        /// control 이 실제로 사용자에게 보이는지 여부를 반환
        public static bool IsControlVisibleToUser(this Control control)
        {
            return control.DoGet(() =>
            {
                if (!control.IsHandleCreated)
                    return false;

                var pos = control.PointToScreen(control.Location);
                var pointsToCheck = new POINT[] {
                                    pos,
                                    new Point(pos.X + control.Width - 1, pos.Y),
                                    new Point(pos.X, pos.Y + control.Height - 1),
                                    new Point(pos.X + control.Width - 1, pos.Y + control.Height - 1),
                                    new Point(pos.X + control.Width/2, pos.Y + control.Height/2),
                };

                foreach (var p in pointsToCheck)
                {
                    var hwnd = WindowFromPoint(p);
                    var other = Control.FromChildHandle(hwnd);
                    if (other == null)
                        continue;

                    if (control == other || control.Contains(other))
                        return true;
                }

                return false;
            });
        }


        #region Detect cursor
        //// http://stackoverflow.com/questions/586479/is-there-a-quick-way-to-get-the-control-thats-under-the-mouse
        //[DllImport("user32.dll")]
        //private static extern IntPtr WindowFromPoint(Point pnt);

        //public static Control ControlUnderPoint(this Point pt)
        //{
        //    IntPtr hWnd = WindowFromPoint(pt);
        //    if (hWnd != IntPtr.Zero)
        //        return Control.FromHandle(hWnd);

        //    return null;
        //}

        //public static Control ControlUnderMouseCursor()
        //{
        //    return Control.MousePosition.ControlUnderPoint();
        //}

        /// <summary> Checks whether mouse is over given control </summary>
        public static bool IsMouseOver(this Control control)
        {
            return control.ClientRectangle.Contains(control.PointToClient(Cursor.Position));
        }

        /// <summary> Collects controls under mouse cursor </summary>
        public static IEnumerable<Control> GetChildrenUnderMouse(this Control parent)
        {
            foreach (var c in parent.Controls.Cast<Control>())
            {
                if (c.IsMouseOver())
                    yield return c;
                foreach (var cc in GetChildrenUnderMouse(c))
                    yield return cc;
            }
        }
        #endregion


        /// Button 에 image 를 입힌다.  text 는 왼쪽, image 는 오른쪽
        public static void AddImage(this Button button, Image image)
        {
            button.Image = image;
            button.ImageAlign = ContentAlignment.MiddleRight;
            button.TextAlign = ContentAlignment.MiddleLeft;
        }
    }
}
