// The MIT License (MIT)
// Copyright (c) 2014 Nicolas Kogler

// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:

// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.

using System;
using System.Text;
using System.Drawing;
using System.Collections;
using System.Windows.Forms;
using System.ComponentModel;
using System.Drawing.Imaging;
using System.Drawing.Drawing2D;
using System.Runtime.InteropServices;
using IMLibrary.Controls.Win8form;

namespace IMLibrary.Controls
{
    public class Win8Form : Form
    {
        #region Formbounds

        private Rectangle controlbox;
        private Rectangle formborder;
        private Rectangle formborder2;
        private Rectangle formborder3;
        private Rectangle iconposition;

        private Rectangle miniposition;
        private Rectangle maxiposition;
        private Rectangle closeposition;
        private Rectangle captionposition;

        private Rectangle topside;
        private Rectangle toprightside;
        private Rectangle topleftside;
        private Rectangle leftside;
        private Rectangle rightside;
        private Rectangle bottomside;
        private Rectangle bottomrightside;
        private Rectangle bottomleftside;

        #endregion

        #region Variables

        private Color bordercolor;
        private Color middlecolor;
        private Color controlcolor;
        private Color inactivebordercolor;
        private Color inactivecontrolcolor;
        private Color inactivemiddlecolor;

        private ToolStrip toolstrip;
        private SharpMenu context;
        private Image icon;

        private Boolean isactive = true;
        private Boolean aeroactive = false;
        private SharpHelper.SelButton currentbtn;

        #endregion

        #region Properties
        //Padding _padding=new Padding(8,40,8,8);
        //public new Padding Padding
        //{
        //    get { return _padding; }
        //    set
        //    {
        //        _padding = value;
        //        //base.Padding = new Padding(
        //        //    8 ,
        //        //    40,
        //        //    8,
        //        //    8);
        //    }
        //}

        [Category("SharpForm"), Browsable(true)]
        [Description("Show System Menu.")]
        public bool IsShowSystemMenu
        {
            set;
            get;
        }


        [Category("SharpForm"), Browsable(true)]
        [Description("The control-toolstrip of the form.")]
        public ToolStrip ToolStrip
        {
            get
            {
                return toolstrip;
            }
            set
            {
                if (value.Renderer.GetType() == typeof(SharpToolRenderer))
                {
                    ((SharpToolRenderer)value.Renderer).FormBackColor = controlcolor;
                } toolstrip = value; this.Invalidate(true);
            }
        }

        [Category("SharpForm"), Browsable(true)]
        [Description("Indicates whether aerosnap is active.")]
        public Boolean AeroActive
        {
            get
            {
                return aeroactive;
            }
            set
            {
                aeroactive = value;
                this.Invalidate(true);
            }
        }

        [Category("SharpForm"), Browsable(true)]
        [Description("The border color of the form.")]
        public Color BorderColor
        {
            get
            {
                return bordercolor;
            }
            set
            {
                bordercolor = value;
                this.Invalidate(true);
            }
        }

        [Category("SharpForm"), Browsable(true)]
        [Description("The middle color of the form-border.")]
        public Color MiddleColor
        {
            get
            {
                return middlecolor;
            }
            set
            {
                middlecolor = value;
                this.Invalidate(true);
            }
        }

        [Category("SharpForm"), Browsable(true)]
        [Description("The color of the controlbox of the form.")]
        public Color ControlBoxColor
        {
            get
            {
                return controlcolor;
            }
            set
            {
                if (toolstrip!=null && toolstrip.Renderer.GetType() == typeof(SharpToolRenderer))
                {
                    ((SharpToolRenderer)toolstrip.Renderer).FormBackColor = value;
                } 
                controlcolor = value; this.Invalidate(true);
            }
        }

        [Category("SharpForm"), Browsable(true)]
        [Description("Border color of the form when not focused.")]
        public Color InactiveBorderColor
        {
            get
            {
                return inactivebordercolor;
            }
            set
            {
                inactivebordercolor = value;
                this.Invalidate(true);
            }
        }

        [Category("SharpForm"), Browsable(true)]
        [Description("Color of the controlbox of the form when not focused.")]
        public Color InactiveControlBoxColor
        {
            get
            {
                return inactivecontrolcolor;
            }
            set
            {
                inactivecontrolcolor = value;
                this.Invalidate(true);
            }
        }


        [Category("SharpForm"), Browsable(true)]
        [Description("Color of the controlbox of the form when not focused.")]
        public Color InactiveMiddleColor
        {
            get
            {
                return inactivemiddlecolor;
            }
            set
            {
                inactivemiddlecolor = value;
                this.Invalidate(true);
            }
        }

        [Category("SharpForm"), Browsable(true)]
        [Description("The icon that is displayed on the top left of the form.")]
        public Image FormIcon
        {
            get
            {
                return icon;
            }
            set
            {
                icon = value.GetThumbnailImage(24, 24, null, IntPtr.Zero);  
            }
        }

        #endregion

        #region Constructor

        /// <summary>
        /// Creates our contextmenu, measures all the rectangles
        /// and also adds eventhandlers like painting for the form.
        /// </summary>
        public Win8Form()
        {
            //if (IsShowSystemMenu)
            {
                this.context = new SharpMenu(this);
                this.context.SysEvent += SystemEvent;
            }

            MeasureRects();
            SetProperties();

            this.Padding = new Padding(8, 40, 8, 8);

        }

        private void SetProperties()
        {
            this.FormBorderStyle = FormBorderStyle.None;
            this.currentbtn = SharpHelper.SelButton.BTN_NONE;
            this.MaximizedBounds = Screen.GetWorkingArea(this);
            this.MinimumSize = new Size(330, 300);
            this.DoubleBuffered = true;

            this.bordercolor = Color.FromArgb(255, 77, 77, 77);
            this.middlecolor = Color.FromArgb(255, 120, 120, 120);
            this.controlcolor = Color.FromArgb(255,250, 250, 240 );//254, 254, 254
            this.inactivebordercolor = Color.FromArgb(255, 200, 200, 200);
            this.inactivecontrolcolor = Color.FromArgb(255, 254, 254, 254);
            this.inactivemiddlecolor = Color.FromArgb(255, 224, 224, 224);

            this.Resize += FormResize;
            this.Paint += FormPaint;
        }

        /// <summary>
        /// Call this in the new forms constructor
        /// if you want to center the form on the desktop.
        /// </summary>
        public void CenterSharpForm()
        {
            this.DesktopLocation = CenterForm();
        }

        /// <summary>
        /// Call this in the new forms constructor
        /// if you want to center the form relative to its parent.
        /// </summary>
        public void CenterSharpParent()
        {
            if (Owner != null)
            {
                int oldwidth = Owner.Width;
                int oldheight = Owner.Height;
                var oldpoint = Owner.DesktopLocation;

                int newwidth = this.Width;
                int newheight = this.Height;

                oldpoint.X += ((oldwidth - newwidth) / 2);
                oldpoint.Y += ((oldheight - newheight) / 2);
                this.DesktopLocation = oldpoint;
            }
        }

        private Point CenterForm()
        {
            return new Point((Screen.PrimaryScreen.Bounds.Width / 2) - (this.Width / 2),
                (Screen.PrimaryScreen.Bounds.Height / 2) - (this.Height / 2));
        }

        /// <summary>
        /// Measures the width of the caption text.
        /// </summary>
        private int MeasureCaption()
        {
            return TextRenderer.MeasureText(Text, Font).Width;
        }

        /// <summary>
        /// Draws a drop shadow around the form.
        /// </summary>
        protected override CreateParams CreateParams
        {
            get
            {
                const int CS_DROPSHADOW = 0x20000;
                const int WS_SIZEBOX = 0x40000;

                var param = base.CreateParams;
                param.Style |= CS_DROPSHADOW;

                if (aeroactive)
                {
                    param.Style |= WS_SIZEBOX;
                } return param;
            }
        }

        #endregion

        #region Functions

        /// <summary>
        /// Measures the rectangles on every resize in order to
        /// draw the buttons etc. correctly. If you want to add
        /// own buttons, you have to create a rectangle for it
        /// and calculate it correctly in this method.
        /// </summary>
        public void MeasureRects()
        {
            this.controlbox = new Rectangle(7, 7, this.Width - 14, 0); //new Rectangle(7, 7, this.Width - 14, 0);
            this.formborder = new Rectangle(0, 0, this.Width - 1, this.Height - 1);
            this.formborder2 = new Rectangle(4, 4, this.Width - 8, this.Height - 8);
            this.formborder3 = new Rectangle(6, 6, this.Width - 13, this.Height - 13);

            if (icon != null)
            {
                this.iconposition = new Rectangle(14, 11, icon.Width, icon.Height);
                this.controlbox.Height = ((icon.Width == 24 && icon.Height == 24) ? 38 : 24);
            } var measure = MeasureCaption();

            this.maxiposition = new Rectangle(this.Width - 87, 7, 40, 28);
            this.miniposition = new Rectangle(this.Width - 117, 7, 30, 32);
            this.closeposition = new Rectangle(this.Width - 47, 7, 40, 22);
            this.captionposition = new Rectangle((this.Width / 2) - (measure / 2), 12, measure, 9);

            this.topside = new Rectangle(10, 0, Width - 11, 8);
            this.topleftside = new Rectangle(0, 0, 10, 10);
            this.toprightside = new Rectangle(Width - 11, 0, Width - 1, 10);
            this.rightside = new Rectangle(Width - 11, 10, 10, Height - 11);
            this.leftside = new Rectangle(0, 10, 10, Height - 11);
            this.bottomside = new Rectangle(10, Height - 11, Width - 11, 10);
            this.bottomleftside = new Rectangle(0, Height - 11, 10, 10);
            this.bottomrightside = new Rectangle(Width - 11, Height - 11, 10, 10);
             
        }

        /// <summary>
        /// Draws the border, the buttons and other stuff from the
        /// calculated rectangles in "MeasureRects()". Feel free to
        /// draw own buttons here.
        /// </summary>
        private void FormPaint(object obj, PaintEventArgs e)
        {
            var middle_pen = new Pen(middlecolor); middle_pen.Width = 6;
            var text_brush = new SolidBrush(Color.FromArgb(255, 50, 50, 50));
            var border_pen = new Pen((isactive) ? bordercolor : inactivebordercolor);
            var back_brush = new SolidBrush((isactive) ? controlcolor : inactivecontrolcolor);

            Brush close_brush;
            if (currentbtn == SharpHelper.SelButton.BTN_CLOSE)
                close_brush = new SolidBrush(Color.FromArgb(255, 165, 52, 52));
            else
                close_brush = new SolidBrush(Color.FromArgb(255, 199, 80, 80));
            
            var point1 = new Point(7, controlbox.Height + 1);
            var point2 = new Point(controlbox.Width + 6, controlbox.Height + 1);

            var clspnt = CenterImage(Resources.Close, closeposition);
            var maxpnt = CenterImage(Resources.Maximize, maxiposition);
            var minpnt = CenterImage(Resources.Minimize, miniposition);

            e.Graphics.DrawRectangle(border_pen, formborder);
            e.Graphics.DrawRectangle(middle_pen, formborder2);
            e.Graphics.DrawRectangle(border_pen, formborder3);
            e.Graphics.FillRectangle(back_brush, controlbox);
            e.Graphics.FillRectangle(close_brush, closeposition);

            e.Graphics.DrawImage((currentbtn == SharpHelper.SelButton.BTN_MAX) ? 
                Resources.Maximize_Sel : Resources.Maximize, maxpnt);
            e.Graphics.DrawImage((currentbtn == SharpHelper.SelButton.BTN_MIN) ? 
                Resources.Minimize_Sel : Resources.Minimize, minpnt);
            e.Graphics.DrawImage(Resources.Close, clspnt);
            e.Graphics.DrawString(Text, Font, text_brush, captionposition.Location);
            e.Graphics.DrawLine(border_pen, point1, point2);

            if (icon != null)
            {
                if ((icon.Width == 16 && icon.Height == 16) || (icon.Width == 24 && icon.Height == 24))
                    e.Graphics.DrawImage(icon, iconposition);
                else
                    e.Graphics.DrawImage(icon, new Rectangle(0, 0, 16, 16));
            }

            border_pen.Dispose();
            middle_pen.Dispose();
            text_brush.Dispose();
            back_brush.Dispose();
            close_brush.Dispose();
        }

        /// <summary>
        /// If the form resizes we must recalculate the rectangles
        /// and redraw the form afterwards to avoid buggy painting.
        /// </summary>
        /// <param name="obj"></param>
        /// <param name="e"></param>
        private void FormResize(object obj, EventArgs e)
        {
            if (this.Created)
            {
                this.MeasureRects();
                this.Invalidate();
            }
        }

        /// <summary>
        /// Centers an image (buttons etc) from the given rectangle.
        /// </summary>
        private Point CenterImage(Image img, Rectangle rect)
        {
            return new Point((rect.X + (rect.Width / 2) - (img.Width / 2)),
                rect.Y + (rect.Height / 2) - (img.Height / 2));
        }

        /// <summary>
        /// Processes the messages sent by the normal
        /// message loop but also from the contextmenu.
        /// </summary>
        protected override void WndProc(ref Message m)
        {
            if (this.WindowState == FormWindowState.Maximized)
            {
                // Avoid message processing if the form is maximized
                // and certain parameteres are given.
                if (m.Msg == SharpHelper.WM_SYSCOMMAND &&
                    m.WParam.ToInt32() == (int)SharpHelper.SystemCmd.CMD_MOVE ||
                    m.Msg == (int)SharpHelper.NCMouseMsg.WM_NCLBUTTONDOWN &&
                    m.WParam.ToInt32() == (int)SharpHelper.NCHitTest.HTCAPTION)
                {
                    m.Msg = SharpHelper.WM_NOTHING;
                }
            } base.WndProc(ref m);

            switch (m.Msg)
            {
                case SharpHelper.WM_NCCALCSIZE:
                    // Aero-Snap implementation
                    if (aeroactive)
                    {
                        if (m.WParam.Equals(IntPtr.Zero))
                        {
                            SharpHelper.RECT structrect = (SharpHelper.RECT)m.GetLParam(typeof(SharpHelper.RECT));
                            Rectangle normalrect = structrect.ToRectangle(); normalrect.Inflate(7, 7);
                            Marshal.StructureToPtr(new SharpHelper.RECT(normalrect), m.LParam, true);
                        }
                        else
                        {
                            SharpHelper.NCCALCSIZE_PARAMS csp = (SharpHelper.NCCALCSIZE_PARAMS)m.GetLParam(typeof(SharpHelper.NCCALCSIZE_PARAMS));
                            Rectangle normalrect = csp.rgrc0.ToRectangle(); normalrect.Inflate(7, 7); csp.rgrc0 = new SharpHelper.RECT(normalrect);
                            Marshal.StructureToPtr(csp, m.LParam, true);
                        } m.Result = IntPtr.Zero;
                    }
                    break;
                case (int)SharpHelper.WM_GETSYSMENU:
                    // Shows the contextmenu if we clicked on the
                    // forms caption rectangle.
                    if (IsShowSystemMenu)
                    this.context.Show(this, this.PointToClient(new Point(m.LParam.ToInt32())));
                    break;
                case SharpHelper.WM_NCACTIVATE:
                    // If the form is focused, redraw the form.
                    this.isactive = m.WParam.ToInt32() != 0;
                    this.Invalidate();
                    break;
                case SharpHelper.WM_NCHITTEST:
                    // If the cursor overlaps with certain areas of
                    // the form border, change the cursor and enable
                    // moving/resizing/etc functions.
                    m.Result = OnNonClientHit(m.LParam);
                    break;
                case (int)SharpHelper.NCMouseMsg.WM_NCLBUTTONDBLCLK:
                    // If you double clicked.
                    OnNonClientMouseDouble(m.LParam);
                    break;
                case (int)SharpHelper.NCMouseMsg.WM_NCLBUTTONUP:
                    // If the left mouse button is pressed in a
                    // non-client area.
                    OnNonClientLButtonUp(m.LParam);
                    break;
                case (int)SharpHelper.NCMouseMsg.WM_NCRBUTTONUP:
                    // If the right mouse button is pressed in a
                    // non-client area.
                    OnNonClientRButtonUp(m.LParam);
                    break;
                case (int)SharpHelper.NCMouseMsg.WM_NCMOUSEMOVE:
                    // If the mouse if beeing moved.
                    OnNonClientMouseMove(m.LParam);
                    break;
            }
        }

        /// <summary>
        /// Checks if you double clicked the left
        /// mouse button on the icon and forces
        /// the form to close in case you did.
        /// </summary>
        private void OnNonClientMouseDouble(IntPtr param)
        {
            if (this.iconposition.Contains(this.PointToClient(new Point(param.ToInt32()))))
            {
                SendMessage(SharpHelper.WM_SYSCOMMAND, (IntPtr)SharpHelper.SystemCmd.CMD_CLOSE, IntPtr.Zero);
            }
        }

        /// <summary>
        /// Checks for several positions in the controlbox
        /// and sends a message if close, maximize or minimize
        /// was clicked with the left mouse button. You can also
        /// implement your custom functions by checking if the pnt
        /// contains your button-rectangle.
        /// </summary>
        private void OnNonClientLButtonUp(IntPtr param)
        {
            var msg = SharpHelper.SystemCmd.CMD_DEFAULT;
            var pnt = this.PointToClient(new Point(param.ToInt32()));

            if (this.iconposition.Contains(pnt))
            {
                if (IsShowSystemMenu)
                {
                    // Shows context menu when clicked on icon.
                    if (this.icon != null)
                    {
                        if (this.icon.Width == 24 && this.icon.Height == 24)
                            this.context.Show(this, new Point(8, 32));
                        else
                            this.context.Show(this, new Point(8, 24));
                    }
                    else
                    {
                        this.context.Show(this, new Point(8, 24));
                    }
                }
            }
            else
            {
                // If the close-button or the mini/maximize-button
                // is pressed, send a message to the message loop
                // and executes the wanted function
                if (this.closeposition.Contains(pnt))
                {
                    msg = SharpHelper.SystemCmd.CMD_CLOSE;
                }
                else if (this.maxiposition.Contains(pnt))
                {
                    msg = (this.WindowState == FormWindowState.Normal) ? SharpHelper.SystemCmd.CMD_MAXI : SharpHelper.SystemCmd.CMD_RESTORE;
                }
                else if(this.miniposition.Contains(pnt))
                {
                    msg = SharpHelper.SystemCmd.CMD_MINI;
                } SendMessage(SharpHelper.WM_SYSCOMMAND, (IntPtr)msg, IntPtr.Zero);
            }
        }

        /// <summary>
        /// Handles the mouse moves in the NC-area.
        /// Is used to indicate which button on the
        /// controlbox is pressed -> different drawing.
        /// </summary>
        private void OnNonClientMouseMove(IntPtr param)
        {
            var pnt = this.PointToClient(new Point(param.ToInt32()));
            if (this.closeposition.Contains(pnt))
            {
                currentbtn = SharpHelper.SelButton.BTN_CLOSE;
            }
            else if (this.maxiposition.Contains(pnt))
            {
                currentbtn = SharpHelper.SelButton.BTN_MAX;
            }
            else if (this.miniposition.Contains(pnt))
            {
                currentbtn = SharpHelper.SelButton.BTN_MIN;
            }
            else
            {
                currentbtn = SharpHelper.SelButton.BTN_NONE;
            } Invalidate();
        }

        private void OnNonClientRButtonUp(IntPtr param)
        {
            if (this.controlbox.Contains(this.PointToClient(new Point(param.ToInt32()))))
            {
                SendMessage(SharpHelper.WM_GETSYSMENU, IntPtr.Zero, param);
            }
        }

        /// <summary>
        /// Changes he cursor depending on the border-part 
        /// that you are hovering with your mouse.
        /// </summary>
        private IntPtr OnNonClientHit(IntPtr param)
        {
            var msg = SharpHelper.NCHitTest.HTCLIENT;
            var pnt = this.PointToClient(new Point(param.ToInt32()));

            if (this.controlbox.Contains(pnt))
            {
                msg = SharpHelper.NCHitTest.HTCAPTION;
            }
            if (this.WindowState == FormWindowState.Normal)
            {
                if (this.topside.Contains(pnt))
                {
                    msg = SharpHelper.NCHitTest.HTTOP;
                }
                else if (this.topleftside.Contains(pnt))
                {
                    msg = SharpHelper.NCHitTest.HTTOPLEFT;
                }
                else if (this.toprightside.Contains(pnt))
                {
                    msg = SharpHelper.NCHitTest.HTTOPRIGHT;
                }
                else if (this.bottomleftside.Contains(pnt))
                {
                    msg = SharpHelper.NCHitTest.HTBOTTOMLEFT;
                }
                else if (this.bottomrightside.Contains(pnt))
                {
                    msg = SharpHelper.NCHitTest.HTBOTTOMRIGHT;
                }
                else if (this.rightside.Contains(pnt))
                {
                    msg = SharpHelper.NCHitTest.HTRIGHT;
                }
                else if (this.leftside.Contains(pnt))
                {
                    msg = SharpHelper.NCHitTest.HTLEFT;
                }
                else if (this.bottomside.Contains(pnt))
                {
                    msg = SharpHelper.NCHitTest.HTBOTTOM;
                }
            }
            // Checks if the controlbox contains the point,
            // if yes, change the mouse cursor to HTBORDER.
            if (this.closeposition.Contains(pnt) || this.maxiposition.Contains(pnt)
                || this.miniposition.Contains(pnt) || this.iconposition.Contains(pnt))
            {
                msg = SharpHelper.NCHitTest.HTBORDER;
            } return (IntPtr)msg;
        }

        /// <summary>
        /// Sends a message into the message loop.
        /// </summary>
        private void SendMessage(int msg, IntPtr wParam, IntPtr lParam)
        {
            var message = Message.Create(this.Handle, msg, wParam, lParam);
            this.WndProc(ref message);
        }

        /// <summary>
        /// The event-delegate for the ContextMenu.
        /// </summary>
        /// <param name="obj"></param>
        /// <param name="e"></param>
        private void SystemEvent(object obj, SharpMenuEventArgs e)
        {
            SendMessage(SharpHelper.WM_SYSCOMMAND, (IntPtr)e.Command, IntPtr.Zero);
        }

        protected override void OnLostFocus(EventArgs e)
        {
            this.isactive = false;
            base.OnLostFocus(e);
        }

        protected override void OnGotFocus(EventArgs e)
        {
            this.Refresh();
            this.isactive = true;
            base.OnGotFocus(e);
        }

        #endregion
    }
}