using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Windows.Forms;

namespace DatePicker
{
    #region Event Argument Classes
    /// <summary>
    /// Contains event information for a <see cref="PopupClosed"/> event.
    /// </summary>
    public class PopupClosedEventArgs : EventArgs
    {
        /// <summary>
        /// The popup form.
        /// </summary>
        private Form popup = null;

        /// <summary>
        /// Gets the popup form which is being closed.
        /// </summary>
        public Form Popup
        {
            get
            {
                return this.popup;
            }
        }

        /// <summary>
        /// Constructs a new instance of this class for the specified
        /// popup form.
        /// </summary>
        /// <param name="popup">Popup Form which is being closed.</param>
        public PopupClosedEventArgs(Form popup)
        {
            this.popup = popup;
        }
    }

    /// <summary>
    /// Arguments to a <see cref="PopupCancelEvent"/>.  Provides a
    /// reference to the popup form that is to be closed and 
    /// allows the operation to be cancelled.
    /// </summary>
    public class PopupCancelEventArgs : EventArgs
    {
        /// <summary>
        /// Whether to cancel the operation
        /// </summary>
        private bool cancel = false;
        /// <summary>
        /// Mouse down location
        /// </summary>
        private Point location;
        /// <summary>
        /// Popup form.
        /// </summary>
        private Form popup = null;

        /// <summary>
        /// Constructs a new instance of this class.
        /// </summary>
        /// <param name="popup">The popup form</param>
        /// <param name="location">The mouse location, if any, where the
        /// mouse event that would cancel the popup occured.</param>
        public PopupCancelEventArgs(Form popup, Point location)
        {
            this.popup = popup;
            this.location = location;
            this.cancel = false;
        }

        /// <summary>
        /// Gets the popup form
        /// </summary>
        public Form Popup
        {
            get
            {
                return this.popup;
            }
        }

        /// <summary>
        /// Gets the location that the mouse down which would cancel this 
        /// popup occurred
        /// </summary>
        public Point CursorLocation
        {
            get
            {
                return this.location;
            }
        }

        /// <summary>
        /// Gets/sets whether to cancel closing the form. Set to
        /// <c>true</c> to prevent the popup from being closed.
        /// </summary>
        public bool Cancel
        {
            get
            {
                return this.cancel;
            }
            set
            {
                this.cancel = value;
            }
        }

    }
    #endregion

    #region Delegates
    /// <summary>
    /// Represents the method which responds to a <see cref="PopupClosed"/> event.
    /// </summary>
    public delegate void PopupClosedEventHandler(object sender, PopupClosedEventArgs e);

    /// <summary>
    /// Represents the method which responds to a <see cref="PopupCancel"/> event.
    /// </summary>
    public delegate void PopupCancelEventHandler(object sender, PopupCancelEventArgs e);
    #endregion

    #region PopupWindowHelper
    /// <summary>
    /// A class to assist in creating popup windows like Combo Box drop-downs and Menus.
    /// This class includes functionality to keep the title bar of the popup owner form
    /// active whilst the popup is displayed, and to automatically cancel the popup
    /// whenever the user clicks outside the popup window or shifts focus to another 
    /// application.
    /// </summary>
    public class PopupWindowHelper : NativeWindow
    {
        #region Unmanaged Code
        [DllImport("user32", CharSet = CharSet.Auto)]
        private extern static int SendMessage(IntPtr handle, int msg, int wParam, IntPtr lParam);

        [DllImport("user32", CharSet = CharSet.Auto)]
        private extern static int PostMessage(IntPtr handle, int msg, int wParam, IntPtr lParam);

        private const int WM_ACTIVATE = 0x006;
        private const int WM_ACTIVATEAPP = 0x01C;
        private const int WM_NCACTIVATE = 0x086;

        [DllImport("user32")]
        private extern static void keybd_event(byte bVk, byte bScan, int dwFlags, int dwExtraInfo);

        private const int KEYEVENTF_KEYUP = 0x0002;
        #endregion

        #region Member Variables
        /// <summary>
        /// Event Handler to detect when the popup window is closed
        /// </summary>
        private EventHandler popClosedHandler = null;
        /// <summary>
        /// Message filter to detect mouse clicks anywhere in the application
        /// whilst the popup window is being displayed.
        /// </summary>
        private PopupWindowHelperMessageFilter filter = null;
        /// <summary>
        /// The popup form that is being shown.
        /// </summary>
        private Form popup = null;
        /// <summary>
        /// The owner of the popup form that is being shown:
        /// </summary>
        private Form owner = null;
        /// <summary>
        /// Whether the popup is showing or not.
        /// </summary>
        private bool popupShowing = false;
        /// <summary>
        /// Whether the popup has been cancelled, notified by PopupCancel,
        /// rather than closed.
        /// </summary>
        private bool skipClose = false;


        #endregion


        /// <summary>
        /// Raised when the popup form is closed.
        /// </summary>
        public event PopupClosedEventHandler PopupClosed;
        /// <summary>
        /// Raised when the Popup Window is about to be cancelled.  The
        /// <see cref="PopupCancelEventArgs.Cancel"/> property can be
        /// set to <c>true</c> to prevent the form from being cancelled.
        /// </summary>
        public event PopupCancelEventHandler PopupCancel;

        /// <summary>
        /// Shows the specified Form as a popup window, keeping the
        /// Owner's title bar active and preparing to cancel the popup
        /// should the user click anywhere outside the popup window.
        /// <para>Typical code to use this message is as follows:</para>
        /// <code>
        ///    frmPopup popup = new frmPopup();
        ///    Point location = this.PointToScreen(new Point(button1.Left, button1.Bottom));
        ///    popupHelper.ShowPopup(this, popup, location);
        /// </code>
        /// <para>Put as much initialisation code as possible
        /// into the popup form's constructor, rather than the <see cref="System.Windows.Forms.Load"/>
        /// event as this will improve visual appearance.</para>
        /// </summary>
        /// <param name="owner">Main form which owns the popup</param>
        /// <param name="popup">Window to show as a popup</param>
        /// <param name="location">Location relative to the screen to show the popup at.</param>
        public void ShowPopup(Form owner, Form popup, Point location)
        {

            this.owner = owner;
            this.popup = popup;

            // Start checking for the popup being cancelled
            Application.AddMessageFilter(filter);

            // Set the location of the popup form:
            popup.StartPosition = FormStartPosition.Manual;
            popup.Location = location;
            // Make it owned by the window that's displaying it:
            owner.AddOwnedForm(popup);
            // Respond to the Closed event in case the popup
            // is closed by its own internal means
            popClosedHandler = new EventHandler(popup_Closed);
            popup.Closed += popClosedHandler;

            // Show the popup:
            this.popupShowing = true;
            popup.Show();
            popup.Activate();

            // A little bit of fun.  We've shown the popup,
            // but because we've kept the main window's
            // title bar in focus the tab sequence isn't quite
            // right.  This can be fixed by sending a tab,
            // but that on its own would shift focus to the
            // second control in the form.  So send a tab,
            // followed by a reverse-tab.

            // Send a Tab command:
            keybd_event((byte)Keys.Tab, 0, 0, 0);
            keybd_event((byte)Keys.Tab, 0, KEYEVENTF_KEYUP, 0);

            // Send a reverse Tab command:
            keybd_event((byte)Keys.ShiftKey, 0, 0, 0);
            keybd_event((byte)Keys.Tab, 0, 0, 0);
            keybd_event((byte)Keys.Tab, 0, KEYEVENTF_KEYUP, 0);
            keybd_event((byte)Keys.ShiftKey, 0, KEYEVENTF_KEYUP, 0);


            // Start filtering for mouse clicks outside the popup
            filter.Popup = popup;

        }

        /// <summary>
        /// Responds to the <see cref="System.Windows.Forms.Form.Closed"/>
        /// event from the popup form.
        /// </summary>
        /// <param name="sender">Popup form that has been closed.</param>
        /// <param name="e">Not used.</param>
        private void popup_Closed(object sender, EventArgs e)
        {

            ClosePopup();

        }

        /// <summary>
        /// Subclasses the owning form's existing Window Procedure to enables the 
        /// title bar to remain active when a popup is show, and to detect if
        /// the user clicks onto another application whilst the popup is visible.
        /// </summary>
        /// <param name="m">Window Procedure Message</param>
        protected override void WndProc(ref Message m)
        {
            base.WndProc(ref m);
            if (this.popupShowing)
            {
                // check for WM_ACTIVATE and WM_NCACTIVATE
                if (m.Msg == WM_NCACTIVATE)
                {
                    // Check if the title bar will made inactive:
                    if (((int)m.WParam) == 0)
                    {
                        // If so reactivate it.
                        SendMessage(this.Handle, WM_NCACTIVATE, 1, IntPtr.Zero);

                        // Note it's no good to try and consume this message;
                        // if you try to do that you'll end up with windows
                        // that don't respond.
                    }

                }
                else if (m.Msg == WM_ACTIVATEAPP)
                {
                    // Check if the application is being deactivated.
                    if ((int)m.WParam == 0)
                    {
                        // It is so cancel the popup:
                        ClosePopup();
                        // And put the title bar into the inactive state:
                        PostMessage(this.Handle, WM_NCACTIVATE, 0, IntPtr.Zero);
                    }
                }
            }
        }

        /// <summary>
        /// Called when the popup is being hidden.
        /// </summary>
        public void ClosePopup()
        {
            if (this.popupShowing)
            {
                if (!skipClose)
                {
                    // Raise event to owner
                    OnPopupClosed(new PopupClosedEventArgs(this.popup));
                }
                skipClose = false;

                // Make sure the popup is closed and we've cleaned
                // up:
                this.owner.RemoveOwnedForm(this.popup);
                this.popupShowing = false;
                this.popup.Closed -= popClosedHandler;
                this.popClosedHandler = null;
                this.popup.Close();
                // No longer need to filter for clicks outside the
                // popup.
                Application.RemoveMessageFilter(filter);

                // If we did something from the popup which shifted
                // focus to a new form, like showing another popup
                // or dialog, then Windows won't know how to bring
                // the original owner back to the foreground, so
                // force it here:
                this.owner.Activate();

                // Null out references for GC
                this.popup = null;
                this.owner = null;

            }
        }

        /// <summary>
        /// Raises the <see cref="PopupClosed"/> event.
        /// </summary>
        /// <param name="e"><see cref="PopupClosedEventArgs"/> describing the
        /// popup form that is being closed.</param>
        protected virtual void OnPopupClosed(PopupClosedEventArgs e)
        {
            if (this.PopupClosed != null)
            {
                this.PopupClosed(this, e);
            }
        }

        private void popup_Cancel(object sender, PopupCancelEventArgs e)
        {
            OnPopupCancel(e);
        }

        /// <summary>
        /// Raises the <see cref="PopupCancel"/> event.
        /// </summary>
        /// <param name="e"><see cref="PopupCancelEventArgs"/> describing the
        /// popup form that about to be cancelled.</param>
        protected virtual void OnPopupCancel(PopupCancelEventArgs e)
        {
            if (this.PopupCancel != null)
            {
                this.PopupCancel(this, e);
                if (!e.Cancel)
                {
                    skipClose = true;
                }
            }
        }

        /// <summary>
        /// Default constructor.
        /// </summary>
        /// <remarks>Use the <see cref="System.Windows.Forms.NativeWindow.AssignHandle"/>
        /// method to attach this class to the form you want to show popups from.</remarks>
        public PopupWindowHelper()
        {
            filter = new PopupWindowHelperMessageFilter(this);
            filter.PopupCancel += new PopupCancelEventHandler(popup_Cancel);
        }


    }
    #endregion

    #region PopupWindowHelperMessageFilter
    /// <summary>
    /// A Message Loop filter which detect mouse events whilst the popup form is shown
    /// and notifies the owning <see cref="PopupWindowHelper"/> class when a mouse
    /// click outside the popup occurs.
    /// </summary>
    public class PopupWindowHelperMessageFilter : IMessageFilter
    {
        private const int WM_LBUTTONDOWN = 0x201;
        private const int WM_RBUTTONDOWN = 0x204;
        private const int WM_MBUTTONDOWN = 0x207;
        private const int WM_NCLBUTTONDOWN = 0x0A1;
        private const int WM_NCRBUTTONDOWN = 0x0A4;
        private const int WM_NCMBUTTONDOWN = 0x0A7;

        /// <summary>
        /// Raised when the Popup Window is about to be cancelled.  The
        /// <see cref="PopupCancelEventArgs.Cancel"/> property can be
        /// set to <c>true</c> to prevent the form from being cancelled.
        /// </summary>
        public event PopupCancelEventHandler PopupCancel;

        /// <summary>
        /// The popup form
        /// </summary>
        private Form popup = null;
        /// <summary>
        /// The owning <see cref="PopupWindowHelper"/> object.
        /// </summary>
        private PopupWindowHelper owner = null;

        /// <summary>
        /// Constructs a new instance of this class and sets the owning
        /// object.
        /// </summary>
        /// <param name="owner">The <see cref="PopupWindowHelper"/> object
        /// which owns this class.</param>
        public PopupWindowHelperMessageFilter(PopupWindowHelper owner)
        {
            this.owner = owner;
        }

        /// <summary>
        /// Gets/sets the popup form which is being displayed.
        /// </summary>
        public Form Popup
        {
            get
            {
                return this.popup;
            }
            set
            {
                this.popup = value;
            }
        }

        /// <summary>
        /// Checks the message loop for mouse messages whilst the popup
        /// window is displayed.  If one is detected the position is
        /// checked to see if it is outside the form, and the owner
        /// is notified if so.
        /// </summary>
        /// <param name="m">Windows Message about to be processed by the
        /// message loop</param>
        /// <returns><c>true</c> to filter the message, <c>false</c> otherwise.
        /// This implementation always returns <c>false</c>.</returns>
        public bool PreFilterMessage(ref Message m)
        {
            if (this.popup != null)
            {
                switch (m.Msg)
                {
                    case WM_LBUTTONDOWN:
                    case WM_RBUTTONDOWN:
                    case WM_MBUTTONDOWN:
                    case WM_NCLBUTTONDOWN:
                    case WM_NCRBUTTONDOWN:
                    case WM_NCMBUTTONDOWN:
                        OnMouseDown();
                        break;
                }
            }
            return false;
        }

        /// <summary>
        /// Checks the mouse location and calls the OnCancelPopup method
        /// if the mouse is outside the popup form.		
        /// </summary>
        private void OnMouseDown()
        {
            // Get the cursor location
            Point cursorPos = Cursor.Position;
            // Check if it is within the popup form
            if (!popup.Bounds.Contains(cursorPos))
            {
                // If not, then call to see if it should be closed
                OnCancelPopup(new PopupCancelEventArgs(popup, cursorPos));
            }
        }

        /// <summary>
        /// Raises the <see cref="PopupCancel"/> event.
        /// </summary>
        /// <param name="e">The <see cref="PopupCancelEventArgs"/> associated 
        /// with the cancel event.</param>
        protected virtual void OnCancelPopup(PopupCancelEventArgs e)
        {
            if (this.PopupCancel != null)
            {
                this.PopupCancel(this, e);
            }
            if (!e.Cancel)
            {
                //IsModal = false;
                owner.ClosePopup();
                //Debug.WriteLine(owner.IsModal.ToString());
                // Clear reference for GC
                popup = null;
            }
        }


    }
    #endregion
}
