using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Windows.Forms;

namespace Optimizer.Controls
{
    [DefaultProperty("Color")]
    [DefaultEvent("ColorChanged")]
    public partial class ColorPicker : Control
    {
        #region Fields

        private Brush _brush;
        private PointF _centerPoint;
        private Color _color;
        private int _colorStep;
        private bool _dragStartedWithinWheel;
        private HslColor _hslColor;
        private int _largeChange;
        private float _radius;
        private int _selectionSize;
        private int _smallChange;
        private int _updateCount;

        #endregion

        #region Constructors

        public ColorPicker()
        {
            this.SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.UserPaint | ControlStyles.OptimizedDoubleBuffer | ControlStyles.Selectable | ControlStyles.StandardClick | ControlStyles.StandardDoubleClick, true);
            this.Color = Color.Black;
            this.ColorStep = 4;
            this.SelectionSize = 10;
            this.SmallChange = 1;
            this.LargeChange = 5;
            this.SelectionGlyph = this.CreateSelectionGlyph();
        }

        #endregion

        #region Events

        [Category("Property Changed")]
        public event EventHandler ColorChanged;

        [Category("Property Changed")]
        public event EventHandler ColorStepChanged;

        [Category("Property Changed")]
        public event EventHandler HslColorChanged;

        [Category("Property Changed")]
        public event EventHandler LargeChangeChanged;

        [Category("Property Changed")]
        public event EventHandler SelectionSizeChanged;

        [Category("Property Changed")]
        public event EventHandler SmallChangeChanged;

        #endregion

        #region Overridden Methods
        protected override void Dispose(bool disposing)
        {
            if (disposing)
            {
                if (_brush != null)
                {
                    _brush.Dispose();
                }

                if (this.SelectionGlyph != null)
                {
                    this.SelectionGlyph.Dispose();
                }
            }

            base.Dispose(disposing);
        }

        protected override bool IsInputKey(Keys keyData)
        {
            bool result;

            if ((keyData & Keys.Left) == Keys.Left || (keyData & Keys.Up) == Keys.Up || (keyData & Keys.Down) == Keys.Down || (keyData & Keys.Right) == Keys.Right || (keyData & Keys.PageUp) == Keys.PageUp || (keyData & Keys.PageDown) == Keys.PageDown)
            {
                result = true;
            }
            else
            {
                result = base.IsInputKey(keyData);
            }

            return result;
        }

        protected override void OnGotFocus(EventArgs e)
        {
            base.OnGotFocus(e);

            this.Invalidate();
        }

        protected override void OnKeyDown(KeyEventArgs e)
        {
            HslColor color;
            double hue;
            int step;

            color = this.HslColor;
            hue = color.H;

            step = e.Shift ? this.LargeChange : this.SmallChange;

            switch (e.KeyCode)
            {
                case Keys.Right:
                case Keys.Up:
                    hue += step;
                    break;
                case Keys.Left:
                case Keys.Down:
                    hue -= step;
                    break;
                case Keys.PageUp:
                    hue += this.LargeChange;
                    break;
                case Keys.PageDown:
                    hue -= this.LargeChange;
                    break;
            }

            if (hue >= 360)
            {
                hue = 0;
            }
            if (hue < 0)
            {
                hue = 359;
            }

            if (hue != color.H)
            {
                color.H = hue;

                this.LockUpdates = true;
                this.Color = color.ToRgbColor();
                this.HslColor = color;
                this.LockUpdates = false;

                e.Handled = true;
            }

            base.OnKeyDown(e);
        }

        protected override void OnLostFocus(EventArgs e)
        {
            base.OnLostFocus(e);

            this.Invalidate();
        }

        protected override void OnMouseDown(MouseEventArgs e)
        {
            base.OnMouseDown(e);

            if (!this.Focused && this.TabStop)
            {
                this.Focus();
            }

            if (e.Button == MouseButtons.Left && this.IsPointInWheel(e.Location))
            {
                _dragStartedWithinWheel = true;
                this.SetColor(e.Location);
            }
        }

        protected override void OnMouseMove(MouseEventArgs e)
        {
            base.OnMouseMove(e);

            if (e.Button == MouseButtons.Left && _dragStartedWithinWheel)
            {
                this.SetColor(e.Location);
            }
        }

        protected override void OnMouseUp(MouseEventArgs e)
        {
            base.OnMouseUp(e);

            _dragStartedWithinWheel = false;
        }

        protected override void OnPaddingChanged(EventArgs e)
        {
            base.OnPaddingChanged(e);

            this.RefreshWheel();
        }

        protected override void OnPaint(PaintEventArgs e)
        {
            base.OnPaint(e);

            if (this.AllowPainting)
            {
                base.OnPaintBackground(e);

                if (this.BackgroundImage == null && this.Parent != null && (this.BackColor == this.Parent.BackColor || this.Parent.BackColor.A != 255))
                {
                    ButtonRenderer.DrawParentBackground(e.Graphics, this.DisplayRectangle, this);
                }

                if (_brush != null)
                {
                    e.Graphics.FillPie(_brush, this.ClientRectangle, 0, 360);
                }

                e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
                using (Pen pen = new Pen(this.BackColor, 2))
                {
                    e.Graphics.DrawEllipse(pen, new RectangleF(_centerPoint.X - _radius, _centerPoint.Y - _radius, _radius * 2, _radius * 2));
                }

                if (!this.Color.IsEmpty)
                {
                    this.PaintCurrentColor(e);
                }
            }
        }

        protected override void OnResize(EventArgs e)
        {
            base.OnResize(e);

            this.RefreshWheel();
        }

        #endregion

        #region Public Properties

        [Category("Appearance")]
        [DefaultValue(typeof(Color), "Black")]
        public virtual Color Color
        {
            get { return _color; }
            set
            {
                if (this.Color != value)
                {
                    _color = value;

                    this.OnColorChanged(EventArgs.Empty);
                }
            }
        }

        [Category("Appearance")]
        [DefaultValue(4)]
        public virtual int ColorStep
        {
            get { return _colorStep; }
            set
            {
                if (value < 1 || value > 359)
                {
                    throw new ArgumentOutOfRangeException("value", value, "Value must be between 1 and 359");
                }

                if (this.ColorStep != value)
                {
                    _colorStep = value;

                    this.OnColorStepChanged(EventArgs.Empty);
                }
            }
        }

        [Category("Appearance")]
        [DefaultValue(typeof(HslColor), "0, 0, 0")]
        [Browsable(false)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public virtual HslColor HslColor
        {
            get { return _hslColor; }
            set
            {
                if (this.HslColor != value)
                {
                    _hslColor = value;

                    this.OnHslColorChanged(EventArgs.Empty);
                }
            }
        }

        [Category("Behavior")]
        [DefaultValue(5)]
        public virtual int LargeChange
        {
            get { return _largeChange; }
            set
            {
                if (this.LargeChange != value)
                {
                    _largeChange = value;

                    this.OnLargeChangeChanged(EventArgs.Empty);
                }
            }
        }

        [Category("Appearance")]
        [DefaultValue(10)]
        public virtual int SelectionSize
        {
            get { return _selectionSize; }
            set
            {
                if (this.SelectionSize != value)
                {
                    _selectionSize = value;

                    this.OnSelectionSizeChanged(EventArgs.Empty);
                }
            }
        }

        [Category("Behavior")]
        [DefaultValue(1)]
        public virtual int SmallChange
        {
            get { return _smallChange; }
            set
            {
                if (this.SmallChange != value)
                {
                    _smallChange = value;

                    this.OnSmallChangeChanged(EventArgs.Empty);
                }
            }
        }

        #endregion

        #region Protected Properties

        protected virtual bool AllowPainting
        {
            get { return _updateCount == 0; }
        }

        protected Color[] Colors { get; set; }

        protected bool LockUpdates { get; set; }

        protected PointF[] Points { get; set; }

        protected Image SelectionGlyph { get; set; }

        #endregion

        #region Public Members

        public virtual void BeginUpdate()
        {
            _updateCount++;
        }

        public virtual void EndUpdate()
        {
            if (_updateCount > 0)
            {
                _updateCount--;
            }

            if (this.AllowPainting)
            {
                this.Invalidate();
            }
        }

        #endregion

        #region Protected Members

        protected virtual void CalculateWheel()
        {
            List<PointF> points;
            List<Color> colors;

            points = new List<PointF>();
            colors = new List<Color>();

            if (this.ClientSize.Width > 16 && this.ClientSize.Height > 16)
            {
                int w;
                int h;

                w = this.ClientSize.Width;
                h = this.ClientSize.Height;

                _centerPoint = new PointF(w / 2.0F, h / 2.0F);
                _radius = this.GetRadius(_centerPoint);

                for (double angle = 0; angle < 360; angle += this.ColorStep)
                {
                    double angleR;
                    PointF location;

                    angleR = angle * (Math.PI / 180);
                    location = this.GetColorLocation(angleR, _radius);

                    points.Add(location);
                    colors.Add(new HslColor(angle, 1, 0.5).ToRgbColor());
                }
            }

            this.Points = points.ToArray();
            this.Colors = colors.ToArray();
        }
        protected virtual Brush CreateGradientBrush()
        {
            Brush result;

            if (this.Points.Length != 0 && this.Points.Length == this.Colors.Length)
            {
                result = new PathGradientBrush(this.Points, WrapMode.Clamp)
                {
                    CenterPoint = _centerPoint,
                    CenterColor = Color.White,
                    SurroundColors = this.Colors
                };
            }
            else
            {
                result = null;
            }

            return result;
        }
        
        protected virtual Color GetContrastColor(Color c)
        {
            double brightness = c.R * 0.299 + c.G * 0.587 + c.B * 0.114;
            return brightness > 149 ? Color.Black : Color.White;
        }

        protected virtual Image CreateSelectionGlyph()
        {
            Image image;
            int halfSize;

            halfSize = this.SelectionSize / 2;
            image = new Bitmap(this.SelectionSize + 1, this.SelectionSize + 1);

            using (Graphics g = Graphics.FromImage(image))
            {
                g.SmoothingMode = SmoothingMode.AntiAlias;
                g.PixelOffsetMode = PixelOffsetMode.HighQuality;
                g.InterpolationMode = InterpolationMode.High;

                g.DrawEllipse(new Pen(GetContrastColor(Color)), halfSize - 4.5f, halfSize - 4.5f, 4.5f + 4.5f, 4.5f + 4.5f);
                g.FillEllipse(new SolidBrush(this.Color), halfSize - 4, halfSize - 4, 4 + 4, 4 + 4);
            }

            return image;
        }

        protected PointF GetColorLocation(Color color)
        {
            return this.GetColorLocation(new HslColor(color));
        }

        protected virtual PointF GetColorLocation(HslColor color)
        {
            double angle;
            double radius;

            angle = color.H * Math.PI / 180;
            radius = _radius * color.S;

            return this.GetColorLocation(angle, radius);
        }

        protected PointF GetColorLocation(double angleR, double radius)
        {
            double x;
            double y;

            x = this.Padding.Left + _centerPoint.X + Math.Cos(angleR) * radius;
            y = this.Padding.Top + _centerPoint.Y - Math.Sin(angleR) * radius;

            return new PointF((float)x, (float)y);
        }

        protected float GetRadius(PointF centerPoint)
        {
            return Math.Min(centerPoint.X, centerPoint.Y) - (Math.Max(this.Padding.Horizontal, this.Padding.Vertical) + (this.SelectionSize / 2));
        }

        protected bool IsPointInWheel(Point point)
        {
            PointF normalized;

            normalized = new PointF(point.X - _centerPoint.X, point.Y - _centerPoint.Y);

            return (normalized.X * normalized.X + normalized.Y * normalized.Y) <= (_radius * _radius);
        }

        protected virtual void OnColorChanged(EventArgs e)
        {
            EventHandler handler;

            if (!this.LockUpdates)
            {
                this.HslColor = new HslColor(this.Color);
            }
            
            this.SelectionGlyph = this.CreateSelectionGlyph();
            this.Refresh();

            handler = this.ColorChanged;

            if (handler != null)
            {
                handler(this, e);
            }
        }

        protected virtual void OnColorStepChanged(EventArgs e)
        {
            EventHandler handler;

            this.RefreshWheel();

            handler = this.ColorStepChanged;

            if (handler != null)
            {
                handler(this, e);
            }
        }

        protected virtual void OnHslColorChanged(EventArgs e)
        {
            EventHandler handler;

            if (!this.LockUpdates)
            {
                this.Color = this.HslColor.ToRgbColor();
            }
            this.Invalidate();

            handler = this.HslColorChanged;

            if (handler != null)
            {
                handler(this, e);
            }
        }

        protected virtual void OnLargeChangeChanged(EventArgs e)
        {
            EventHandler handler;

            handler = this.LargeChangeChanged;

            if (handler != null)
            {
                handler(this, e);
            }
        }

        protected virtual void OnSelectionSizeChanged(EventArgs e)
        {
            EventHandler handler;

            if (this.SelectionGlyph != null)
            {
                this.SelectionGlyph.Dispose();
            }

            this.SelectionGlyph = this.CreateSelectionGlyph();
            this.RefreshWheel();

            handler = this.SelectionSizeChanged;

            if (handler != null)
            {
                handler(this, e);
            }
        }

        protected virtual void OnSmallChangeChanged(EventArgs e)
        {
            EventHandler handler;

            handler = this.SmallChangeChanged;

            if (handler != null)
            {
                handler(this, e);
            }
        }

        protected void PaintColor(PaintEventArgs e, HslColor color)
        {
            this.PaintColor(e, color, false);
        }

        protected virtual void PaintColor(PaintEventArgs e, HslColor color, bool includeFocus)
        {
            PointF location;

            location = this.GetColorLocation(color);

            if (!float.IsNaN(location.X) && !float.IsNaN(location.Y))
            {
                int x;
                int y;

                x = (int)location.X - (this.SelectionSize / 2);
                y = (int)location.Y - (this.SelectionSize / 2);

                if (this.SelectionGlyph == null)
                {
                    e.Graphics.DrawRectangle(Pens.Black, x, y, this.SelectionSize, this.SelectionSize);
                }
                else
                {
                    e.Graphics.DrawImage(this.SelectionGlyph, x, y);
                }
            }
        }

        protected virtual void PaintCurrentColor(PaintEventArgs e)
        {
            this.PaintColor(e, this.HslColor, true);
        }

        protected virtual void SetColor(Point point)
        {
            double dx;
            double dy;
            double angle;
            double distance;
            double saturation;

            dx = Math.Abs(point.X - _centerPoint.X - this.Padding.Left);
            dy = Math.Abs(point.Y - _centerPoint.Y - this.Padding.Top);
            angle = Math.Atan(dy / dx) / Math.PI * 180;
            distance = Math.Pow((Math.Pow(dx, 2) + (Math.Pow(dy, 2))), 0.5);
            saturation = distance / _radius;

            if (distance < 6)
            {
                saturation = 0;
            }

            if (point.X < _centerPoint.X)
            {
                angle = 180 - angle;
            }
            if (point.Y > _centerPoint.Y)
            {
                angle = 360 - angle;
            }

            this.LockUpdates = true;
            this.HslColor = new HslColor(angle, saturation, 0.5);
            this.Color = this.HslColor.ToRgbColor();
            this.LockUpdates = false;
        }

        #endregion

        #region Private Members

        private void RefreshWheel()
        {
            if (_brush != null)
            {
                _brush.Dispose();
            }

            this.CalculateWheel();
            _brush = this.CreateGradientBrush();
            this.Invalidate();
        }

        #endregion
    }
}
