using System;
using
System.Collections.Generic;
using
System.ComponentModel;
using
System.Data;
using
System.Drawing;
using
System.Text;
using
System.Windows.Forms;
using
System.Threading;

namespace
Subro
{

    public enum MessageWindowStyle
    {
        Modal, OutlookStyle
    }

    public enum MessageType
    {
        Error, Info, OK = -1, UnKnown = -2
    }

    [DefaultEvent("MessageQueueChanged")]
    public class MessageQueue:Component
    {
        #region
MessageForm

        private class MessageForm : Form
        {

            readonly LinkedListNode<MessageForm> mynode;
            readonly MessageQueue owner;
            public readonly MessageType Type;
            Control closeControl;

            internal MessageForm(Message m, MessageQueue owner)
            {
                //add form to the opened collection
                lock (owner.opened) mynode = owner.opened.AddFirst(this);

                InitializeComponent();

                this.owner = owner;
                this.Type = m.Type;

                //set icon
                Icon icon;
                switch (m.Type)
                {
                    case MessageType.Error:
                        icon = SystemIcons.Error;
                        break;
                    case MessageType.Info:
                        icon = SystemIcons.Information;
                        break;
                    case MessageType.UnKnown:
                        icon = SystemIcons.Exclamation;
                        break;
                    default:
                        icon = null;
                        break;
                }
                if (icon == null)
                {
                    picIcon.Visible = false;
                }
                else
                    picIcon.Image = icon.ToBitmap();


                //perform messagewindowstyle specific setup
                switch (owner.Style)
                {
                    case MessageWindowStyle.Modal:
                        this.Text = AppDomain.CurrentDomain.FriendlyName + " message";
                        this.Icon = icon;
                        this.ControlBox = true;
                        break;
                    case MessageWindowStyle.OutlookStyle:
                        StartPosition = FormStartPosition.Manual;
                        Height -= pnlButtons.Height;
                        BackColor = Color.White;
                        Location = new Point(screenbounds.Right - Width, screenbounds.Bottom);
                        this.ShowInTaskbar = false;
                        timer = new System.Windows.Forms.Timer();
                        timer.Interval = 80;
                        timer.Tick += new EventHandler(timer_Tick);
                        timer.Start();

                        break;
                    default:
                        break;
                }

                SetOpacity();
                SetControl(this);
                AddMessage(m);
            }


            #region
Opacity

            void SetOpacity()
            {
                if (owner.Style == MessageWindowStyle.OutlookStyle)
                {
                    Opacity = opacity;

                }
            }
            const double opacity = .7;

            void c_MouseLeave(object sender, EventArgs e)
            {
                if (!this.Capture)
                    Opacity = opacity;
            }

            void c_MouseEnter(object sender, EventArgs e)
            {
                Opacity = 1;
            }

            #endregion

            #region
shifting
            System.Windows.Forms.Timer timer;
            void timer_Tick(object sender, EventArgs e)
            {
                ShiftUp(shift);
                if (Bottom <= screenbounds.Bottom)
                {
                    timer.Stop();
                    timer=null;
                    owner.ShowMessage();
                }
            }
            const int shift = 5;

            delegate void ShiftUpHandler(int shift);
            void ShiftUp(int shift)
            {
                if (InvokeRequired)
                    Invoke(new ShiftUpHandler(ShiftUp), shift);
                else
                {
                    Top -= shift;
                    if (mynode.Next != null)
                    {
                        MessageForm f = mynode.Next.Value;
                        shift = f.Bottom - Top + 1;
                        if (shift > 0)
                            f.ShiftUp(shift);
                    }
                }
            }
            #endregion

            #region
collection adaption

            protected override void OnClosed(EventArgs e)
            {
                lock (owner.opened) owner.opened.Remove(this);
                owner.shownmessagecount -= flPanel.Controls.Count;
                owner.onQueueChanged();
                owner.ShowMessage();
                base.OnClosed(e);
            }

            void RemoveLabel(Label l)
            {
                flPanel.Controls.Remove(l);
                owner.shownmessagecount--;
                owner.onQueueChanged();
                if (flPanel.Controls.Count == 0)
                    Close();
                else
                {
                    SetHeight();
                    owner.ShowMessage();
                }
            }

            delegate void addmessageHandler(Message m);
            public void AddMessage(Message m)
            {
                if (m.Type != Type)
                    throw new Exception("Only items of the same style can be added");

                if (flPanel.InvokeRequired)
                {
                    flPanel.Invoke(new addmessageHandler(AddMessage), m);
                    return;
                }

                Label lblMessage = new Label();
                lblMessage.Text = m.Text;
                //lblMessage.MouseHover += new EventHandler(lblMessage_MouseHover);
                lblMessage.Tag = m;
                lblMessage.AutoSize = true;

                //format the label as a link if an action is possible upon click
                if (m.ActionAvailable || owner.Style == MessageWindowStyle.OutlookStyle)
                {
                    lblMessage.Cursor = Cursors.Hand;
                    lblMessage.ForeColor = Color.Blue;
                    lblMessage.Font = new Font(lblMessage.Font, FontStyle.Underline);
                    lblMessage.Click += new EventHandler(lblMessage_Click);
                }

                SetControl(lblMessage);
                flPanel.Controls.Add(lblMessage);
                owner.shownmessagecount++;
                setClose();
                SetHeight();
            }

            #endregion

            #region
Appearance

            void SetHeight()
            {
                int h = 4;
                if (pnlButtons.Visible) h += pnlButtons.Height;
                foreach (Label l in flPanel.Controls)
                    h += l.Height;
                this.Height = h;
                if (timer == null)
                {
                    h =
                        mynode.Previous == null
                        ? screenbounds.Height
                        : mynode.Previous.Value.Top;

                    h = Bottom - h;
                    if (h > 0) ShiftUp(h);
                }
            }


            void setClose()
            {
                pnlButtons.Visible = owner.showclose;
                if (owner.showclose && closeControl == null)
                {
                    if (owner.style == MessageWindowStyle.OutlookStyle)
                    {
                        closeControl = new LinkLabel();
                        closeControl.Text = "Close";
                        (closeControl as LinkLabel).AutoSize = true;
                    }
                    else
                    {
                        closeControl = new Button();
                        closeControl.Text = "OK";
                        AcceptButton = CancelButton = closeControl as Button;
                    }

                    closeControl.Click += new EventHandler(closeControl_Click);
                    closeControl.Anchor = AnchorStyles.Top;
                    pnlButtons.Controls.Add(closeControl);
                    closeControl.Left = (Width - closeControl.Width) / 2 - picIcon.Width;
                    pnlButtons.Height = closeControl.Height;
                }
            }

            protected override Size DefaultSize
            {
                get
                {
                    return defaultsize;
                }
            }

            #endregion

            #region
Behaviour


            void c_MouseDown(object sender, MouseEventArgs e)
            {
                if (e.Button == MouseButtons.Right)
                {
                    new CM(this).Show(sender as Control, Point.Empty);
                }
            }

            void lblMessage_MouseHover(object sender, EventArgs e)
            {
                Label l = sender as Label;
                Help.ShowPopup(sender as Label, (l.Tag as Message).Time.ToString(), Point.Empty);
            }

            void lblMessage_Click(object sender, EventArgs e)
            {
                Label l = sender as Label;
                (l.Tag as Message).DoAction();
                RemoveLabel(l);
            }

            class CM : ContextMenu
            {
                MessageForm owner;
                public CM(MessageForm owner)
                {
                    this.owner = owner;
                    MenuItems.Add("Close All", new EventHandler(CloseAll));
                }

                void CloseAll(object sender, EventArgs e)
                {
                    owner.owner.CloseAll();
                }
            }

            #endregion

            #region
Setting

            void SetControl(Control c)
            {
                if (owner.Style == MessageWindowStyle.OutlookStyle)
                {
                    c.MouseEnter += new EventHandler(c_MouseEnter);
                    c.MouseLeave += new EventHandler(c_MouseLeave);
                }
                c.MouseDown += new MouseEventHandler(c_MouseDown);
                if (c.HasChildren)
                    foreach (Control child in c.Controls)
                    {
                        SetControl(child);
                    }
            }

            #endregion

            #region
closing

            /// <summary>
            /// is only called when <see cref="CloseAll"/> is used
            /// </summary>
            public void close()
            {
                if (InvokeRequired)
                    BeginInvoke(new CloseDelegate(close));
                else
                {
                    Close();

                    if (mynode.Previous != null)
                        mynode.Previous.Value.close();
                }
            }


            void closeControl_Click(object sender, EventArgs e)
            {
                Close();
            }

            #endregion

            #region
Designer generated
            /// <summary>
            /// Required designer variable.
            /// </summary>
            private System.ComponentModel.IContainer components = null;

            /// <summary>
            /// Clean up any resources being used.
            /// </summary>
            /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
            protected override void Dispose(bool disposing)
            {
                if (disposing && (components != null))
                {
                    components.Dispose();
                }
                base.Dispose(disposing);
            }

            #region
Windows Form Designer generated code

            /// <summary>
            /// Required method for Designer support - do not modify
            /// the contents of this method with the code editor.
            /// </summary>
            private void InitializeComponent()
            {
                this.picIcon = new System.Windows.Forms.PictureBox();
                this.pnlButtons = new System.Windows.Forms.Panel();
                this.flPanel = new System.Windows.Forms.FlowLayoutPanel();
                ((System.ComponentModel.ISupportInitialize)(this.picIcon)).BeginInit();
                this.SuspendLayout();
                //
                // picIcon
                //
                this.picIcon.Dock = System.Windows.Forms.DockStyle.Left;
                this.picIcon.Location = new System.Drawing.Point(0, 0);
                this.picIcon.Name = "picIcon";
                this.picIcon.Size = new System.Drawing.Size(46, 43);
                picIcon.Padding = new Padding(5);
                //this.picIcon.SizeMode = System.Windows.Forms.PictureBoxSizeMode.Normal;
                this.picIcon.TabIndex = 0;
                this.picIcon.TabStop = false;
                //
                // pnlButtons
                //
                this.pnlButtons.Dock = System.Windows.Forms.DockStyle.Bottom;
                this.pnlButtons.Location = new System.Drawing.Point(46, 23);
                this.pnlButtons.Name = "pnlButtons";
                this.pnlButtons.Size = new System.Drawing.Size(252, 20);
                this.pnlButtons.TabIndex = 3;
                //
                // flPanel
                //
                this.flPanel.AutoSize = true;
                this.flPanel.Dock = System.Windows.Forms.DockStyle.Fill;
                this.flPanel.FlowDirection = System.Windows.Forms.FlowDirection.TopDown;
                this.flPanel.Location = new System.Drawing.Point(46, 0);
                this.flPanel.Name = "flPanel";
                this.flPanel.Size = new System.Drawing.Size(252, 23);
                this.flPanel.TabIndex = 4;
                this.flPanel.WrapContents = false;
                //
                // MessageForm
                //
                this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
                this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
                this.ControlBox = false;
                this.Controls.Add(this.flPanel);
                this.Controls.Add(this.pnlButtons);
                this.Controls.Add(this.picIcon);
                this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle;
                this.MinimumSize = new System.Drawing.Size(180, 45);
                this.Name = "MessageForm";
                this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen;
                this.TopMost = true;
                ((System.ComponentModel.ISupportInitialize)(this.picIcon)).EndInit();
                this.ResumeLayout(false);

            }

            #endregion

            private System.Windows.Forms.PictureBox picIcon;
            private System.Windows.Forms.Panel pnlButtons;
            private System.Windows.Forms.FlowLayoutPanel flPanel;
            #endregion

        }

        #endregion

        public MessageQueue(MessageWindowStyle Style)
        {
            this.Style = Style;
        }
        public MessageQueue()
            : this(MessageWindowStyle.OutlookStyle)
        {

        }

        #region
Appearance

        const string catA = "Appearance";
        private MessageWindowStyle style = MessageWindowStyle.OutlookStyle;
        /// <summary>
        /// The general style for the messages to be shown. Does not apply to already
        /// created forms.
        /// </summary>
        [Category(catA)]
        [DefaultValue(MessageWindowStyle.OutlookStyle)]
        public MessageWindowStyle Style
        {
            get { return style; }
            set
            {
                style = value;
                SetShowClose();
            }
        }

        private bool categorize;
        /// <summary>
        /// If this value is <c>true</c>, messages of the same <see cref="MessageType"/>
        /// are shown in the same window.
        /// At the moment this does not apply for modal style
        /// </summary>
        [DefaultValue(false)]
        public bool Categorize
        {
            get { return categorize; }
            set
            {
                categorize = value;
                SetShowClose();
            }
        }

        static Size defaultsize = new Size(260, 45);
        static Rectangle screenbounds = Screen.PrimaryScreen.WorkingArea;
        /// <summary>
        /// Maximum number of message forms that can be shown next to existing forms. Depends
        /// on the mode (for example, the modal mode will only show one window
        /// at a time)
        /// </summary>
        int FreeSpace
        {
            get
            {
                if (style == MessageWindowStyle.Modal)
                    return 1 - opened.Count;

                int h = reservedspace;
                lock (opened)
                    foreach (MessageForm mf in opened)
                    {
                        h += mf.Height;
                    }

                h = screenbounds.Height - h;

                return h / defaultsize.Height;
            }
        }

        int reservedspace = 10;


        private bool? showclosesetting;
        bool showclose;
        /// <summary>
        /// <c>true</c> if a close label or button has to be shown, otherwise <c>false</c>.
        /// By default close is not shown for OutlookStyle, but it is for Modal.
        /// </summary>
        [Category(catA)]
        public bool ShowCloseOption
        {
            get { return showclose; }
            set { showclosesetting = showclose = value; }
        }
        bool ShouldSerializeShowCloseOption()
        {
            return showclosesetting != GetShowCloseAccordingToStyle();
        }
        void SetShowClose()
        {
            if (!showclosesetting.HasValue)
                showclose = GetShowCloseAccordingToStyle();
        }
        bool GetShowCloseAccordingToStyle()
        {
            return style == MessageWindowStyle.Modal || categorize;
        }
        #endregion

        #region
Collection

        /// <summary>
        /// Message queue waiting to be shown. Contains the raw messages (no form created yet)
        /// </summary>
        readonly Queue<Message> toshow = new Queue<Message>();
        /// <summary>
        /// Message queue currently being shown. It thus contains instances of a form
        /// </summary>
        readonly LinkedList<MessageForm> opened = new LinkedList<MessageForm>();

        /// <summary>
        /// fires the messagequeueChanged event
        /// </summary>
        void onQueueChanged()
        {
            if (SuspendQueueChanged) return;
            setMenuForm();
            if (MessageQueueChanged != null)
                MessageQueueChanged(null, EventArgs.Empty);
        }


        public event EventHandler MessageQueueChanged;
        int shownmessagecount = 0;
        public int Count { get { return toshow.Count + shownmessagecount; } }

        int suspendqueuechanged;
        bool SuspendQueueChanged
        {
            get { return suspendqueuechanged > 0; }
            set
            {
                suspendqueuechanged += value ? 1 : -1;
            }
        }

        #region
MenuForm

        MenuForm menuform;

        bool showmenuform = true;
        /// <summary>
        /// In outlookstyle if more than one messageform is shown, an extra form
        /// with a close all command is shown, unless this value is set to false.
        /// NB: does not apply to Modal style.
        /// </summary>
        [DefaultValue(true)]
        public bool ShowMenuForm
        {
            get { return showmenuform; }
            set
            {
                if (value == showmenuform) return;
                showmenuform = value;
                setMenuForm();
            }
        }

        void setMenuForm()
        {
            bool shouldshow = style== MessageWindowStyle.OutlookStyle
                && showmenuform
                && opened.Count > 1;
            bool shown = menuform != null;
            if (shouldshow != shown)
            {
                if (shouldshow)
                {
                    reservedspace += MenuForm.buttonsize.Height;
                    new Thread(new ThreadStart(OpenMenuForm)).Start();
                }
                else
                {
                    CloseMenuForm();
                }
            }
            else if (shouldshow)
                menuform.TopForm = Oldest;

        }
        void OpenMenuForm()
        {
            menuform = new MenuForm(this);
            menuform.ShowDialog();
        }

        bool closingmenuform;

        void CloseMenuForm()
        {
            if (closingmenuform || menuform == null) return;

            if (menuform.InvokeRequired)
                menuform.BeginInvoke(new ThreadStart(CloseMenuForm));
            else
            {
                closingmenuform = true;
                menuform.Close();
                menuform.Dispose();
                menuform = null;
                reservedspace -= MenuForm.buttonsize.Height;
                closingmenuform = false;
            }

        }

        MessageForm Oldest
        {
            get
            {
                if (opened.Count == 0) return null;
                return opened.Last.Value;
            }
        }

        class MenuForm:Form
        {
            MessageQueue owner;
            public MenuForm(MessageQueue owner)
            {
                this.owner = owner;

                FormBorderStyle = FormBorderStyle.None;
                BackColor = Color.White;
                TopMost = true;
                ShowInTaskbar = false;
                AutoSize = false;

                Button b = new Button();
                b.FlatStyle = FlatStyle.Flat;
                b.FlatAppearance.BorderColor = Color.Gray;
                b.AutoSize = false;
                b.Text = "Close All";
                b.Dock = DockStyle.Fill;
                b.Click += new EventHandler(ll_Click);
                Controls.Add(b);


                TopForm = owner.Oldest;
            }
            public static Size buttonsize = new Size(55, 19);

            protected override void OnLoad(EventArgs e)
            {
                Size = buttonsize;
            }
            void ll_Click(object sender, EventArgs e)
            {
                topform.LocationChanged -= new EventHandler(topform_LocationChanged);
                owner.CloseAll();
            }

            protected override Size DefaultSize
            {
                get
                {
                    return buttonsize;
                }
            }

            MessageForm topform;
            public MessageForm TopForm
            {
                get { return topform; }
                set
                {
                    if (value != topform)
                    {
                        topform = value;
                        topform.LocationChanged += new EventHandler(topform_LocationChanged);
                        setTop();
                    }
                }
            }

            void setTop()
            {
                try
                {
                    if (InvokeRequired)
                        Invoke(new ThreadStart(setTop));
                    else
                        Location =
                            new Point(
                            topform.Left + (topform.Width - Width) / 2,
                            topform.Top - Height);
                }
                catch { }
            }

            void topform_LocationChanged(object sender, EventArgs e)
            {
                setTop();
            }
        }

        #endregion

        #endregion

        #region
Show/add
        /// <summary>
        /// Adds a message to the queue and shows it if allowed
        /// </summary>
        /// <param name="m"></param>
        public void Add(Message m)
        {
            toshow.Enqueue(m);
            ShowMessage();
            onQueueChanged();

        }
        public void Add(string Text, MessageType Type)
        {
            Add(new Message(Text, Type));
        }

        /// <summary>
        /// Shows the next message in the queue (if the maximum number of windows is not superceded)
        /// </summary>
        public void ShowMessage()
        {

            while (toshow.Count > 0 && FreeSpace > 0)
            {
                if (Categorize && opened.Count > 0)
                {
                    if (showCategory()) continue;
                }

                reservedspace += defaultsize.Height;
                Thread showthread = new Thread(new ThreadStart(showNextMessage));
                showthread.Start();

            }
        }

        bool showCategory()
        {
            MessageType mt = toshow.Peek().Type;
            lock (opened)
            {
                foreach (MessageForm mf in opened)
                {
                    if (mf.Type == mt)
                    {
                        mf.AddMessage(toshow.Dequeue());
                        return true;
                    }
                }
            }
            return false;
        }


        /// <summary>
        /// Thread entry
        /// </summary>
        void showNextMessage()
        {
            MessageForm f=null;
            if (toshow.Count > 0)
                f = new MessageForm(toshow.Dequeue(), this);
            reservedspace -= defaultsize.Height;
            if(f!=null)f.ShowDialog();
        }

        #endregion

        #region
Close

        delegate void CloseDelegate();
        /// <summary>
        /// Clears all pending messages (only the ones that will be shown) and closes the current open screens. (if any)
        /// NB: this command is also available by right clicking on any of the MessageForms
        /// </summary>
        public void CloseAll()
        {
            SuspendQueueChanged = true;
            toshow.Clear();

            //lock (opened)
            {
                if (opened.Count > 0)
                    try
                    {
                        opened.Last.Value.close();
                    }
                    catch { }
            }
            SuspendQueueChanged = false;
            onQueueChanged();
        }

        #endregion

    }

    /// <summary>
    /// Base class for a message. This contains the most general information for a message
    /// Inheriting classes can implement further behaviour
    /// </summary>
    public class Message : IComparable<Message>
    {
        public string Text;
        public MessageType Type = MessageType.UnKnown;
        public DateTime Time;
        public Message(string Text)
        {
            this.Text = Text;
        }
        public Message(string Text, MessageType Type)
            : this(Text)
        {
            this.Type = Type;
        }
        public Message(string Text, MessageType Type, DateTime Time)
            : this(Text, Type)
        {
            this.Time = Time;
        }

        #region
IComparable<Message> Members

        public int CompareTo(Message other)
        {
            return Time.CompareTo(other.Time);
        }

        #endregion

        #region
Action
        public virtual bool ActionAvailable
        {
            get { return Action != null; }
        }

        public delegate void ActionHandler(Message m);
        public event ActionHandler Action;

        public virtual void DoAction()
        {
            if (ActionAvailable)
                Action(this);
        }
        #endregion

        public override string ToString()
        {
            return string.Format("[{0}] {1}", Time, Text);
        }


        public static implicit operator Message(string s)
        {
            return new Message(s);
        }
    }
}