﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Text;
using System.Windows.Forms;
using System.Drawing;
using System.Globalization;
using Demo.WindowsForms.Properties;
using System.Drawing.Drawing2D;

namespace BSE.Windows.Forms
{
    /// <summary>
    /// Represents a Windows progress bar control. 
    /// </summary>
    /// <copyright>Copyright © 2008 Uwe Eichkorn
    /// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
    /// KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
    /// IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
    /// PURPOSE. IT CAN BE DISTRIBUTED FREE OF CHARGE AS LONG AS THIS HEADER 
    /// REMAINS UNCHANGED.
    /// </copyright>
    [ToolboxBitmap(typeof(System.Windows.Forms.ProgressBar))]
    public partial class ProgressBar : Control
    {
        #region Events
        /// <summary>
        /// Occurs when the value of the BorderColor property changes.
        /// </summary>
        [Description("Occurs when the value of the BorderColor property is changed on the control.")]
        public event EventHandler<EventArgs> BorderColorChanged;
        /// <summary>
        /// Occurs when the value of the BackgroundColor property changes.
        /// </summary>
        [Description("Occurs when the value of the BackgroundColor property is changed on the control.")]
        public event EventHandler<EventArgs> BackgroundColorChanged;
        /// <summary>
        /// Occurs when the value of the ValueColor property changes.
        /// </summary>
        [Description("Occurs when the value of the ValueColor property is changed on the control.")]
        public event EventHandler<EventArgs> ValueColorChanged;
        #endregion

        #region FieldsPrivate
        private Color m_backgroundColor;
        private Color m_valueColor;
        private Color m_borderColor;
        private int m_iMinimum;
        private int m_iMaximum;
        private int m_iValue;
        #endregion

        #region Properties
        /// <summary>
        /// Gets or sets the color used for the background rectangle of this control.
        /// </summary>
        /// <value>
        /// Type: <see cref="System.Drawing.Color"/>
        /// A Color used for the background rectangle of this control.
        /// </value>
        [Browsable(true)]
        [Description("The color used for the background rectangle of this control.")]
        public Color BackgroundColor
        {
            get { return this.m_backgroundColor; }
            set
            {
                if (this.m_backgroundColor != value)
                {
                    this.m_backgroundColor = value;
                    OnBackgroundColorChanged(this, EventArgs.Empty);
                }
            }
        }
        /// <summary>
        /// Gets or sets the color used for the value rectangle of this control.
        /// </summary>
        /// <value>
        /// Type: <see cref="System.Drawing.Color"/>
        /// A Color used for the value rectangle of this control.
        /// </value>
        [Browsable(true)]
        [Description("The color used for the value rectangle of this control.")]
        public Color ValueColor
        {
            get { return this.m_valueColor; }
            set
            {
                if (this.m_valueColor != value)
                {
                    this.m_valueColor = value;
                    OnValueColorChanged(this, EventArgs.Empty);
                }
            }
        }
        /// <summary>
        /// Gets or sets the border color for the control.
        /// </summary>
        /// <value>
        /// Type: <see cref="System.Drawing.Color "/>
        /// A Color that represents the border color of the control.
        /// </value>
        public Color BorderColor
        {
            get { return this.m_borderColor; }
            set
            {
                if (this.m_borderColor != value)
                {
                    this.m_borderColor = value;
                    OnBorderColorChanged(this, EventArgs.Empty);
                }
            }
        }
        /// <summary>
        /// Gets or sets the background color for the control.
        /// </summary>
        [Browsable(false)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public new Color BackColor
        {
            get { return base.BackColor; }
            set { base.BackColor = value; }
        }
        /// <summary>
        /// Gets or sets the maximum value of the range of the control.
        /// </summary>
        /// <value>
        /// Type: <see cref="System.Int32"/>
        /// The maximum value of the range. The default is 100.
        /// </value>
        [Browsable(true)]
        [Description("The upper bound of range this ProgressBar is working with.")]
        public int Maximum
        {
            get { return this.m_iMaximum; }
            set
            {
                if (this.m_iMaximum != value)
                {
                    if (value < 0)
                    {
                        object[] args = new object[] { "Maximum", value.ToString(CultureInfo.CurrentCulture), "Maximum" };
                        throw new ArgumentOutOfRangeException("Maximum", string.Format(CultureInfo.InvariantCulture, Resources.IDS_InvalidLowBoundArgument, args));
                    }
                    if (this.m_iMinimum > value)
                    {
                        this.m_iMinimum = value;
                    }
                    this.m_iMaximum = value;
                    if (this.m_iValue > this.m_iMaximum)
                    {
                        this.m_iValue = this.m_iMaximum;
                    }
                    UpdatePos();
                }
            }
        }
        /// <summary>
        /// Gets or sets the minimum value of the range of the control.
        /// </summary>
        /// <value>
        /// Type: <see cref="System.Int32"/>
        /// The minimum value of the range. The default is 0.
        /// </value>
        [Browsable(true)]
        [Description("The lower bound of range this ProgressBar is working with.")]
        public int Minimum
        {
            get { return this.m_iMinimum; }
            set
            {
                if (this.m_iMinimum != value)
                {
                    if (value < 0)
                    {
                        object[] args = new object[] { "Minimum", value.ToString(CultureInfo.CurrentCulture), "Minimum" };
                        throw new ArgumentOutOfRangeException("Minimum", string.Format(CultureInfo.InvariantCulture, Resources.IDS_InvalidLowBoundArgument, args));
                    }
                    if (this.m_iMaximum < value)
                    {
                        this.m_iMaximum = value;
                    }
                    this.m_iMinimum = value;
                    if (this.m_iValue < this.m_iMinimum)
                    {
                        this.m_iValue = this.m_iMinimum;
                    }
                    UpdatePos();
                }
            }
        }
        /// <summary>
        /// Gets or sets the current position of the progress bar.
        /// </summary>
        /// <value>
        /// Type: <see cref="System.Int32"/>
        /// The position within the range of the progress bar. The default is 0.
        /// </value>
        [Browsable(true)]
        [Description("The current value for the ProgressBar, in the range specified by the minimum and maximum properties.")]
        public int Value
        {
            get { return this.m_iValue; }
            set
            {
                if (this.m_iValue != value)
                {
                    if ((value < this.m_iMinimum) || (value > this.m_iMaximum))
                    {
                        throw new ArgumentOutOfRangeException("Value", string.Format(CultureInfo.InvariantCulture, Resources.IDS_InvalidBoundArgument, new object[] { "Value", value.ToString(CultureInfo.CurrentCulture), "'minimum'", "'maximum'" }));
                    }
                    this.m_iValue = value;
                    UpdatePos();
                }
            }
        }
        #endregion

        #region MethodsPublic
        /// <summary>
        /// Initializes a new instance of the ProgressBar class.
        /// </summary>
        public ProgressBar()
        {
            this.SetStyle(ControlStyles.AllPaintingInWmPaint, true);
            this.SetStyle(ControlStyles.UserPaint, true);
            this.SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
            this.SetStyle(ControlStyles.ResizeRedraw, true);
            this.SetStyle(ControlStyles.SupportsTransparentBackColor, true);
            InitializeComponent();

            this.m_iMaximum = 100;
            this.m_backgroundColor = Color.FromArgb(20, 20, 255);
            this.m_valueColor = Color.FromArgb(255, 0, 255);
            this.m_borderColor = SystemColors.ActiveBorder;
            this.BackColor = Color.Transparent;
        }
        #endregion

        #region MethodsProtected
        /// <summary>
        /// Raises the Paint event.
        /// </summary>
        /// <param name="e">A PaintEventArgs that contains the event data.</param>
        protected override void OnPaint(PaintEventArgs e)
        {
            using (UseAntiAlias antiAlias = new UseAntiAlias(e.Graphics))
            {
                Graphics graphics = e.Graphics;
                        DrawProgressBar(
                            graphics,
                            this.ClientRectangle,
                            this.m_backgroundColor,
                            this.m_valueColor,
                            this.m_borderColor,
                            this.RightToLeft,
                            this.Minimum,
                            this.Maximum,
                            this.Value);

                if (string.IsNullOrEmpty(this.Text) == false)
                {
                    using (UseClearTypeGridFit useClearTypeGridFit = new UseClearTypeGridFit(graphics))
                    {
                        using (SolidBrush textBrush = new SolidBrush(this.ForeColor))
                        {
                            using (StringFormat stringFormat = new StringFormat())
                            {
                                stringFormat.FormatFlags = StringFormatFlags.NoWrap;
                                if (this.RightToLeft == RightToLeft.Yes)
                                {
                                    stringFormat.FormatFlags |= StringFormatFlags.DirectionRightToLeft;
                                }
                                stringFormat.Trimming = StringTrimming.EllipsisCharacter;
                                stringFormat.LineAlignment = StringAlignment.Center;
                                stringFormat.Alignment = StringAlignment.Center;

                                Rectangle stringRectangle = this.ClientRectangle;
                                graphics.DrawString(this.Text, this.Font, textBrush, stringRectangle, stringFormat);
                            }
                        }
                    }
                }
            }
        }
        /// <summary>
        /// Raises the BorderColor changed event.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">A EventArgs that contains the event data.</param>
		protected virtual void OnBorderColorChanged(object sender, EventArgs e)
        {
            this.Invalidate(true);
            if (this.BorderColorChanged != null)
            {
                this.BorderColorChanged(sender, e);
            }
        }
        /// <summary>
        /// Raises the BackgroundColor changed event.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">A EventArgs that contains the event data.</param>
        protected virtual void OnBackgroundColorChanged(object sender, EventArgs e)
        {
            Invalidate();
            if (this.BackgroundColorChanged != null)
            {
                this.BackgroundColorChanged(sender, e);
            }
        }
        /// <summary>
        /// Raises the ValueColor changed event.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">A EventArgs that contains the event data.</param>
        protected virtual void OnValueColorChanged(object sender, EventArgs e)
        {
            Invalidate(true);
            if (this.ValueColorChanged != null)
            {
                this.ValueColorChanged(sender, e);
            }
        }
        #endregion

        #region MethodsPrivate
        private void UpdatePos()
        {
            this.Invalidate(true);
        }

        private static void DrawProgressBar(
            Graphics graphics,
            Rectangle clientRectangle,
            Color colorBackgroundEnd,
            Color colorValueEnd,
            Color borderColor,
            RightToLeft rightToLeft,
            int iMinimum,
            int iMaximum,
            int iValue)
        {

            Rectangle outerRectangle = GetRectangleBackground(clientRectangle);

            using (GraphicsPath outerRectangleGraphicsPath = GetBackgroundPath(outerRectangle, 4))
            {
                if (outerRectangleGraphicsPath != null)
                {
                    using (LinearGradientBrush gradientBrush = GetGradientBackBrush(outerRectangle, colorBackgroundEnd))
                    {
                        if (gradientBrush != null)
                        {
                            graphics.FillPath(gradientBrush, outerRectangleGraphicsPath);
                        }
                    }

                    // Draws the value rectangle
                    if (iValue > 0)
                    {
                        Rectangle valueRectangle = GetRectangleValue(outerRectangle, rightToLeft, iMinimum, iMaximum, iValue);
                        using (GraphicsPath valueGraphicsPath = GetValuePath(valueRectangle, rightToLeft, 5))
                        {
                            using (LinearGradientBrush gradientBrush = GetGradientBackBrush(valueRectangle, colorValueEnd))
                            {
                                if (gradientBrush != null)
                                {
                                    graphics.FillPath(gradientBrush, valueGraphicsPath);
                                }
                            }
                        }
                    }
                    using (Pen borderPen = new Pen(borderColor))
                    {
                        graphics.DrawPath(borderPen, outerRectangleGraphicsPath);
                    }
                }
            }
        }
        private static Rectangle GetRectangleBackground(Rectangle clientRectangle)
        {
            Rectangle rectangleBackground = clientRectangle;
            rectangleBackground.Inflate(-1, -1);
            return rectangleBackground;
        }
        private static Rectangle GetRectangleValue(Rectangle backgroundRectangle, RightToLeft rightToLeft, int iMinimum, int iMaximum, int iValue)
        {
            Rectangle valueRectangle = backgroundRectangle;
            int iProgressRange = iMaximum - iMinimum;
            int iValueRange = iValue - iMinimum;
            int iRange = (int)((float)iValueRange / (float)iProgressRange * backgroundRectangle.Width);
            valueRectangle.Width = iRange;
            if (rightToLeft == RightToLeft.Yes)
            {
                valueRectangle.X = backgroundRectangle.Width - valueRectangle.Width;
            }
            return valueRectangle;
        }
        private static GraphicsPath GetBackgroundPath(Rectangle bounds, int radius)
        {
            int x = bounds.X;
            int y = bounds.Y;
            int width = bounds.Width;
            int height = bounds.Height;
            GraphicsPath graphicsPath = new GraphicsPath();
            graphicsPath.AddArc(x, y, radius, radius, 180, 90);				                    //Upper left corner
            graphicsPath.AddArc(x + width - radius, y, radius, radius, 270, 90);			    //Upper right corner
            graphicsPath.AddArc(x + width - radius, y + height - radius, radius, radius, 0, 90);//Lower right corner
            graphicsPath.AddArc(x, y + height - radius, radius, radius, 90, 90);			    //Lower left corner
            graphicsPath.CloseFigure();
            return graphicsPath;
        }

        private static GraphicsPath GetValuePath(Rectangle bounds, RightToLeft rightToLeft, int radius)
        {
            int x = bounds.X;
            int y = bounds.Y;
            int width = bounds.Width;
            int height = bounds.Height;
            GraphicsPath graphicsPath = new GraphicsPath();
            if (rightToLeft == RightToLeft.No)
            {
                graphicsPath.AddArc(x, y, radius, radius, 180, 90);				                    //Upper left corner
                graphicsPath.AddLine(x + radius, y, x + width, y);                                  //Upper line
                graphicsPath.AddLine(x + width, y, x + width, y + height);                          //Right line
                graphicsPath.AddArc(x, y + height - radius, radius, radius, 90, 90);			    //Lower left corner
            }
            else
            {
                graphicsPath.AddLine(x, y, width - radius, y);                                      //Upper Line
                graphicsPath.AddArc(x + width - radius, y, radius, radius, 270, 90);                // Upper right corner
                graphicsPath.AddLine(x + width, y + radius, x + width, y + radius + height - (2 * radius)); // right line
                graphicsPath.AddArc(x + width - radius, y + radius + height - (2 * radius), radius, radius, 360, 90); // Lower right corner
                graphicsPath.AddLine(x + width - radius, y + height, x, y + height);                // Lower line
            }
            graphicsPath.CloseFigure();
            return graphicsPath;
        }

        private static LinearGradientBrush GetGradientBackBrush(Rectangle bounds, Color backColor)
        {
            if (IsZeroWidthOrHeight(bounds))
            {
                return null;
            }
            LinearGradientBrush linearGradientBrush = linearGradientBrush = new LinearGradientBrush(bounds, Color.White, backColor, LinearGradientMode.Vertical);
            if (linearGradientBrush != null)
            {
                Blend blend = new Blend();
                blend.Positions = new float[] { 0.0F, 0.2F, 0.3F, 0.5F, 0.6F, 0.8F, 1.0F };
                blend.Factors = new float[] { 0.3F, 0.4F, 0.5F, 0.8F, 1.0F, 1.0F, 0.9F };
                linearGradientBrush.Blend = blend;
            }
            return linearGradientBrush;
        }
        /// <summary>
        /// Checks if the rectangle width or height is equal to 0.
        /// </summary>
        /// <param name="rectangle">the rectangle to check</param>
        /// <returns>true if the with or height of the rectangle is 0 else false</returns>
        private static bool IsZeroWidthOrHeight(Rectangle rectangle)
        {
            if (rectangle.Width != 0)
            {
                return (rectangle.Height == 0);
            }
            return true;
        }
        #endregion
    }
}
