using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Windows.Forms;
namespace DevComponents.Instrumentation.Primitives
{
    public class KnobStyle3 : BaseKnob
    {
        /// 
        /// Constructor
        /// 
        /// Associated knob control
        public KnobStyle3(KnobControl knobControl)
            : base(knobControl)
        {
        }
        #region Knob Configuration
        #region ConfigureKnob
        /// 
        /// Configures the given knob control
        /// by establishing various default object parameters
        /// 
        /// 
        public override void ConfigureKnob(PaintEventArgs e)
        {
            base.ConfigureKnob(e);
            // Calculate default sizes and bounding object rectangles
            ZoneIndWidth = KnobWidth * 0.034f;
            MajorTickSize = new Size((int)(KnobWidth * 0.018), (int)(KnobWidth * 0.05));
            MinorTickSize = new Size(MajorTickSize.Width / 2, MajorTickSize.Height);
            IndTickHeight = MajorTickSize.Height / 3;
            CalculateBoundingRects();
            // Fill in the default knob colors
            DefaultColorTable.MajorTickColor = Color.Black;
            DefaultColorTable.MinorTickColor = Color.Black;
            DefaultColorTable.ZoneIndicatorColor = Color.Black;
            DefaultColorTable.KnobIndicatorPointerColor = Color.Black;
            DefaultColorTable.KnobIndicatorPointerBorderColor = Color.Gray;
            DefaultColorTable.KnobFaceColor = new LinearGradientColorTable(Color.Goldenrod, Color.Goldenrod, 40);
            DefaultColorTable.KnobIndicatorColor = new LinearGradientColorTable(Color.White, Color.LightGray);
            DefaultColorTable.MinZoneIndicatorColor = new LinearGradientColorTable(Color.White, Color.Green);
            DefaultColorTable.MaxZoneIndicatorColor = new LinearGradientColorTable(Color.Yellow, Color.Red);
            DefaultColorTable.MidZoneIndicatorColor = new LinearGradientColorTable(Color.CornflowerBlue);
        }
        #endregion
        #region CalculateBoundingRects
        /// 
        /// Calculates several default control
        /// // bounding rectangles
        /// 
        private void CalculateBoundingRects()
        {
            // Calculate the bounding Zone indicator rectangle and width
            int delta = MaxLabelWidth + MajorTickSize.Height;
            ZoneIndicatorBounds = new Rectangle(delta, delta,
                KnobWidth - (delta * 2), KnobWidth - (delta * 2));
            // Calculate the KnobFace and inset face rectangles
            delta = (int)(ZoneIndicatorBounds.Width * .025f + ZoneIndWidth);
            KnobFaceBounds = ZoneIndicatorBounds;
            KnobFaceBounds.Inflate(-delta, -delta);
            // Calculate the KnobIndicator bounding rectangle
            delta = (int)(ZoneIndicatorBounds.Width * .14f);
            KnobIndicatorBounds = ZoneIndicatorBounds;
            KnobIndicatorBounds.Inflate(-delta, -delta);
            // Calculate the TickLabel bounding rect
            delta = (int)((MaxLabelWidth + IndTickHeight) * .60f);
            TickLabelBounds = new Rectangle(ZoneIndicatorBounds.Location, ZoneIndicatorBounds.Size);
            TickLabelBounds.Inflate(delta, delta);
            // Calculate the focus rectangle
            FocusRectBounds = KnobIndicatorBounds;
            delta = (int)(KnobIndicatorBounds.Width * .025);
            FocusRectBounds.Inflate(-delta, -delta);
        }
        #endregion
        #endregion
        #region Part rendering code
        #region RenderZoneIndicator
        #region RenderZoneIndicator
        /// 
        /// Renders the zone indicator
        /// 
        /// 
        public override void RenderZoneIndicator(PaintEventArgs e)
        {
            if (ZoneIndicatorBounds.Width > 10 && ZoneIndicatorBounds.Height > 10)
            {
                Graphics g = e.Graphics;
                int leftEndAngle = Knob.StartAngle;
                int rightStartAngle = Knob.StartAngle;
                // Render the left zone - if present
                if (Knob.MinZonePercentage > 0)
                {
                    float pct = Knob.MinZonePercentage / 100f;
                    RenderArc(g, Knob.StartAngle,
                              Knob.SweepAngle * pct, LeftZoneIndicatorColor);
                    leftEndAngle = (int)(Knob.StartAngle + Knob.SweepAngle * pct);
                }
                
                // Render the right-hand zone - if present
                if (Knob.MaxZonePercentage > 0)
                {
                    float pct = Knob.MaxZonePercentage / 100f;
                    RenderArc(g, Knob.StartAngle + Knob.SweepAngle * (1 - pct),
                              Knob.SweepAngle * pct, RightZoneIndicatorColor);
                    rightStartAngle = (int)(Knob.StartAngle + Knob.SweepAngle * (1 - pct));
                }
                // Render the middle zone
                if (rightStartAngle != leftEndAngle)
                {
                    RenderArc(g, leftEndAngle,
                        rightStartAngle - leftEndAngle, MiddleZoneIndicatorColor);
                }
            }
        }
        #endregion
        #region RenderArc
        /// 
        /// Renders a gradient indicator arc by dividing
        /// the arc into sub-arcs, enabling us to utilize normal
        /// rectangle gradient support
        /// 
        /// 
        /// Starting angle
        /// Sweep angle
        /// 
        private void RenderArc(Graphics g, float a1, float s1, LinearGradientColorTable ct)
        {
            float n1 = Math.Abs(s1);
            Color c1 = ct.Start;
            Color c2 = ct.End.IsEmpty == false ? ct.End : ct.Start;
            // Calculate our rect and inflate to
            // make room for the indicator arc
            Rectangle rect = new
                Rectangle(ZoneIndicatorBounds.Location, ZoneIndicatorBounds.Size);
            int dw = (int)(ZoneIndWidth / 2);
            rect.Inflate(-dw, -dw);
            // Calculate the RGB color deltas
            float dr = (c2.R - c1.R) / n1;
            float dg = (c2.G - c1.G) / n1;
            float db = (c2.B - c1.B) / n1;
            // Set our initial starting color and range
            Color c3 = Color.FromArgb(c1.ToArgb());
            float s2 = s1;
            float a2 = a1;
            int pa = (s1 > 0) ? 100 : -100;
            // Loop through the arc, processing sub-arcs less
            // than 180 degrees so that we can use GDI+ built-in
            // gradient rectangle support
            while (s2 != 0)
            {
                // Limit our sweep angle pass to 90 degrees
                float sw = (s2 > 0) ? 
                    Math.Min(s2, 90) : Math.Max(s2, -90);
                // Calculate our sub-sweep angle points.  This
                // enables us to create an associated bounding
                // rectangle for the sub-sweep arc
                Point pt1 = CalcCoord(a2);
                Point pt2 = CalcCoord(a2 + sw);
                Rectangle r = new Rectangle();
                r.Location = new Point(Math.Min(pt1.X, pt2.X), Math.Min(pt1.Y, pt2.Y));
                r.Size = new Size(Math.Abs(pt1.X - pt2.X), Math.Abs(pt1.Y - pt2.Y));
                if (r.Width == 0)
                    r.Width = 1;
                if (r.Height == 0)
                    r.Height = 1;
                // Calculate the terminal color for the
                // sub-sweep arc
                float n = Math.Abs(sw);
                int red = (int)(c3.R + n * dr);
                red = Math.Max(Math.Min(red, 255), 0);
                int green = (int)(c3.G + n * dg);
                green = Math.Max(Math.Min(green, 255), 0);
                int blue = (int)(c3.B + n * db);
                blue = Math.Max(Math.Min(blue, 255), 0);
                
                Color c4 = Color.FromArgb(red, green, blue);
                // Tally up this sub-sweep
                s2 -= sw;
                // Render the sub-arc with an appropriately
                // orientated gradient brush, and draw the arc
                using (LinearGradientBrush lbr = new LinearGradientBrush(r, c3, c4, a2 + pa))
                {
                    using (Pen pen = new Pen(lbr, (int)ZoneIndWidth))
                        g.DrawArc(pen, rect, a2, sw);
                }
                // Bump up our starting angle to reflect the
                // processing of this sub-arc
                a2 += sw;
                // Set the next starting color to the
                // ending color for this arc
                c3 = c4;
            }
            // Render the bounding indicator arcs
            using (GraphicsPath p1 = new GraphicsPath())
            {
                rect.Inflate(-dw, -dw);
                p1.AddArc(ZoneIndicatorBounds, a1, s1);
                p1.AddArc(rect, a1 + s1, -s1);
                // Let Windows close the arcs, and then
                // render them
                p1.CloseFigure();
                using (Pen pen = new Pen(ZoneIndicatorColor, MinorTickSize.Width))
                    g.DrawPath(pen, p1);
            }
        }
        #endregion
        #region CalcCoord
        /// 
        /// Calculates the arc coordinates for 
        /// a given angle
        /// 
        /// Angle
        /// 
        private Point CalcCoord(float a2)
        {
            Point pt = new Point();
            // Normalize the angle and calculate some
            // working vars
            if (a2 < 0)
                a2 += 360;
            a2 = a2 % 360;
            int delta = ZoneIndicatorBounds.X + ZoneIndicatorBounds.Width / 2;
            int radius = ZoneIndicatorBounds.Width / 2;
            int d = (int)(ZoneIndWidth / 2);
            // Determine the angle quadrant, and then calculate
            // the intersecting coordinate accordingly
            if (a2 < 90)
            {
                pt.X = (int)(Math.Cos(GetRadians(a2 % 90)) * (radius + d));
                pt.Y = (int)(Math.Sin(GetRadians(a2 % 90)) * (radius + d));
            }
            else if (a2 < 180)
            {
                pt.X = -(int)(Math.Sin(GetRadians(a2 % 90)) * (radius + d));
                pt.Y = (int)(Math.Cos(GetRadians(a2 % 90)) * (radius + d));
            }
            else if (a2 < 270)
            {
                pt.X = -(int)(Math.Cos(GetRadians(a2 % 90)) * (radius + d));
                pt.Y = -(int)(Math.Sin(GetRadians(a2 % 90)) * (radius + d));
            }
            else
            {
                pt.X = (int)(Math.Sin(GetRadians(a2 % 90)) * (radius + d));
                pt.Y = -(int)(Math.Cos(GetRadians(a2 % 90)) * (radius + d));
            }
            // Adjust the point to the intersecting arc
            pt.X += delta;
            pt.Y += delta;
            return (pt);
        }
        #endregion
        #endregion
        #region RenderTickMinor
        #region RenderTickMinor
        /// 
        /// Renders the minor tick marks
        /// 
        /// 
        public override void RenderTickMinor(PaintEventArgs e)
        {
            Graphics g = e.Graphics;
            using (Pen pen = new Pen(MinorTickColor, MinorTickSize.Width))
            {
                for (int i = 0; i < MinorTicks; i++)
                {
                    // Don't draw a minor tick if it overlaps
                    // with a previous major tick
                    if (Knob.MajorTickAmount > 0)
                    {
                        if ((i == 0 || i == MinorTicks - 1) ||
                            ((Knob.MinorTickAmount * i) % Knob.MajorTickAmount == 0))
                            continue;
                    }
                    g.DrawLines(pen, GetMinorTickPoints(i));
                }
            }
        }
        #endregion
        #region GetMinorTickPoints
        /// 
        /// Calculates a series of points
        /// that defines the tick mark
        /// 
        /// Tick to calculate
        /// An array of points that defines the tick
        private Point[] GetMinorTickPoints(int tick)
        {
            float degree = GetTickDegree((float)Knob.MinorTickAmount, tick);
            double rad = GetRadians(degree);
            int dx = ZoneIndicatorBounds.X + ZoneIndicatorBounds.Width / 2;
            int dy = ZoneIndicatorBounds.Y + ZoneIndicatorBounds.Height / 2;
            int h = ZoneIndicatorBounds.Width / 2 - (int)ZoneIndWidth;
            Point[] pts = new Point[2];
            pts[0].X = (int)(Math.Cos(rad) * (h) + dx);
            pts[0].Y = (int)(Math.Sin(rad) * (h) + dy);
            pts[1].X = (int)(Math.Cos(rad) * (h + MinorTickSize.Height) + dx);
            pts[1].Y = (int)(Math.Sin(rad) * (h + MinorTickSize.Height) + dy);
            return (pts);
        }
        #endregion
        #endregion
        #region RenderTickMajor
        #region RenderTickMajor
        /// 
        /// Renders the Major Tick marks
        /// 
        /// 
        public override void RenderTickMajor(PaintEventArgs e)
        {
            Graphics g = e.Graphics;
            // Loop through each tick
            using (Pen pen1 = new Pen(MajorTickColor, MajorTickSize.Width))
            {
                for (int i = 0; i < MajorTicks; i++)
                    g.DrawLines(pen1, GetMajorTickPoints(i));
            }
        }
        #endregion
        #region GetMajorTickPoints
        /// 
        /// Calculates a series of points
        /// that defines the tick mark
        /// 
        /// Tick to calculate
        /// An array of points that defines the tick
        private Point[] GetMajorTickPoints(int tick)
        {
            float degree = GetTickDegree((float)Knob.MajorTickAmount, tick);
            double rad = GetRadians(degree);
            int dx = ZoneIndicatorBounds.X + ZoneIndicatorBounds.Width / 2;
            int dy = ZoneIndicatorBounds.Y + ZoneIndicatorBounds.Height / 2;
            int h = ZoneIndicatorBounds.Width / 2 - (int)ZoneIndWidth;
            Point[] pts = new Point[2];
            pts[0].X = (int)(Math.Cos(rad) * h + dx);
            pts[0].Y = (int)(Math.Sin(rad) * h + dy);
            pts[1].X = (int)(Math.Cos(rad) * (h + MajorTickSize.Height + 1) + dx);
            pts[1].Y = (int)(Math.Sin(rad) * (h + MajorTickSize.Height + 1) + dy);
            return (pts);
        }
        #endregion
        #endregion
        #region RenderKnobFace
        #region RenderKnobFace
        /// 
        /// Renders the knob face
        /// 
        /// 
        public override void RenderKnobFace(PaintEventArgs e)
        {
            if (KnobFaceBounds.Width > 10 && KnobFaceBounds.Height > 10)
            {
                Graphics g = e.Graphics;
                // Allocate a path to accumulate the knob
                // drawing pieces
                using (GraphicsPath path = new GraphicsPath())
                {
                    // Render the face shadow
                    RenderInset(g, path, Pens.Gray, MinorTickSize.Width);
                    using (SolidBrush br =
                        new SolidBrush(Color.FromArgb(160, Color.Gray)))
                    {
                        g.FillPath(br, path);
                    }
                    path.Reset();
                    // Render the face front
                    using (Pen pen = new Pen(Color.White, MinorTickSize.Width))
                        RenderInset(g, path, pen, 0);
                    using (Brush br = KnobFaceColor.GetBrush(KnobFaceBounds))
                        g.FillPath(br, path);
                }
            }
        }
        #endregion
        #region RenderInset
        /// 
        /// Renders the face, including the arc insets
        /// as well as the connecting segments
        /// 
        /// 
        /// Path to render to
        /// Outlining pen
        /// Delta offset - used for shadowing
        private void RenderInset(Graphics g, GraphicsPath path, Pen pen, int offset)
        {
            // Calculate the bounding rectangle
            // for the inset segments
            Rectangle rInset = KnobFaceBounds;
            int w = (int)(ZoneIndWidth);
            rInset.Inflate(-w, -w);
            // Calculate the bounding rectangle
            // for the inset arcs
            int radius = (int)(KnobWidth * .055f);
            Rectangle r = new Rectangle(0, 0, radius, radius);
            // Loop through each arc point and
            // render the individual arc insets
            for (int i = 0; i < 12; i++)
            {
                // Calculate the inset location and
                // render the inset arc
                r.Location = GetArcPoint(rInset, i * 30f, (int)(offset - radius * .5f));
                path.AddArc(r, (i * 30) - 90, -180);
            }
            // Let Windows connect the arcs, and
            // then draw the completed path
            path.CloseFigure();
            g.DrawPath(pen, path);
        }
        #endregion
        #region GetArcPoint
        /// 
        /// Calculates the arc point at the given
        /// degree and offset
        /// 
        /// Inset bounding rectangle
        /// Degree to position arc inset
        /// Offset (used for shading)
        /// 
        private Point GetArcPoint(Rectangle rInset, float degree, int offset)
        {
            // Calculate the default position
            double rad = GetRadians(degree);
            int dx = rInset.X + rInset.Width / 2;
            int dy = rInset.Y + rInset.Height / 2;
            int h = rInset.Width / 2 + (int)ZoneIndWidth;
            Point pt = new Point();
            pt.X = (int)(Math.Cos(rad) * h + dx);
            pt.Y = (int)(Math.Sin(rad) * h + dy);
            // Offset the point if so requested
            // (face shadow support)
            pt.Offset(offset, offset);
            return (pt);
        }
        #endregion
        #endregion
        #region RenderKnobIndicator
        #region RenderKnobIndicator
        /// 
        /// Renders the knob face
        /// 
        /// 
        public override void RenderKnobIndicator(PaintEventArgs e)
        {
            if (KnobIndicatorBounds.Width > 10 && KnobIndicatorBounds.Height > 10)
            {
                Graphics g = e.Graphics;
                // Allocate a graphics path and calculate
                // its bounding rectangle
                using (GraphicsPath path = new GraphicsPath())
                {
                    Rectangle r = new
                        Rectangle(KnobIndicatorBounds.Location, KnobIndicatorBounds.Size);
                    int delta = (int) (KnobWidth*.020f);
                    // Render the knob, it's shadow, and
                    // the hilight crescent
                    r.Offset(delta, delta);
                    RenderIndFace(g, path, r);
                    r.Offset(-delta*2, -delta*2);
                    RenderIndCrescent(g, path, r);
                    // Render the indicator arrow
                    path.AddLines(GetIndicatorPoints());
                    path.CloseFigure();
                    // Fill the closed arrow
                    using (SolidBrush br = new SolidBrush(KnobIndicatorPointerColor))
                    {
                        g.FillPath(br, path);
                        // Outline the arrow
                        if (KnobIndicatorPointerBorderWidth > 0 &&
                            KnobIndicatorPointerColor.Equals(KnobIndicatorPointerBorderColor) == false)
                        {
                            using (Pen pen = new Pen(KnobIndicatorPointerBorderColor, KnobIndicatorPointerBorderWidth))
                                g.DrawPath(pen, path);
                        }
                    }
                }
            }
        }
        #endregion
        #region RenderIndCrescent
        /// 
        /// Renders the hilight crescent
        /// 
        /// 
        /// Accumulating GraphicsPath
        /// Bounding rectangle
        private void RenderIndCrescent(Graphics g, GraphicsPath path, Rectangle r)
        {
            // Add the bottom crescent arc
            path.AddArc(r, 8, 78);
            // Calculate the intersecting upper arc
            // and add it to the path
            int cradius = r.Width / 2;
            int nradius = (int)(cradius * 1.5f);
            int dr = nradius - cradius;
            int dz = (int)(dr * Math.Cos(45) * 1.6f);
            Rectangle r2 = new Rectangle(r.X - dr - dz, r.Y - dr - dz, nradius * 2, nradius * 2);
            path.AddArc(r2, 70, -50);
            Color c = Color.FromArgb(170, KnobIndicatorColor.Start);
            using (LinearGradientBrush lbr =
                new LinearGradientBrush(KnobIndicatorBounds, Color.White, c, 45f))
            {
                g.FillPath(lbr, path);
            }
            path.Reset();
        }
        #endregion
        #region RenderIndFace
        /// 
        /// Renders the face of the knob indicator
        /// 
        /// 
        /// Accumulating GraphicsPath
        /// Bounding rectangle
        private void RenderIndFace(Graphics g, GraphicsPath path, Rectangle r)
        {
            // Render the knob shadow
            Color c3 = Color.FromArgb(100, ControlPaint.Dark(KnobIndicatorColor.End));
            using (SolidBrush br = new SolidBrush(c3))
                g.FillEllipse(br, r);
            // Render the knob
            using (Brush br = KnobIndicatorColor.GetBrush(KnobIndicatorBounds))
                g.FillEllipse(br, KnobIndicatorBounds);
            path.Reset();
        }
        #endregion
        #region GetIndicatorPoints
        /// 
        /// Calculates a series of points that
        /// defines the indicator arrow
        /// 
        /// An array of defining points
        private Point[] GetIndicatorPoints()
        {
            float degrees = (float)(Knob.SweepAngle / ValueCount) *
                (float)(Knob.Value - Knob.MinValue) + Knob.StartAngle;
            int arrowHeight = (int)(KnobWidth * .025f);
            double rad0 = GetRadians(degrees - 19);
            double rad1 = GetRadians(degrees);
            double rad2 = GetRadians(degrees + 19);
            int dx = KnobIndicatorBounds.X + KnobIndicatorBounds.Width / 2 + 1;
            int dy = KnobIndicatorBounds.Y + KnobIndicatorBounds.Height / 2 + 1;
            int h = KnobIndicatorBounds.Width / 2;
            Point[] pts = new Point[3];
            pts[0].X = (int)(Math.Cos(rad0) * h + dx);
            pts[0].Y = (int)(Math.Sin(rad0) * h + dy);
            pts[1].X = (int)(Math.Cos(rad1) * (h + arrowHeight) + dx);
            pts[1].Y = (int)(Math.Sin(rad1) * (h + arrowHeight) + dy);
            pts[2].X = (int)(Math.Cos(rad2) * h + dx);
            pts[2].Y = (int)(Math.Sin(rad2) * h + dy);
            return (pts);
        }
        #endregion
        #endregion
        #endregion
    }
}