using System;
using System.Collections.Generic;
using Quik.CommandMachine;

namespace Quik.VertexGenerator
{
    public class VertexGeneratorEngine : CommandEngine
    {
        public DrawQueue DrawQueue { get; } = new DrawQueue();

        /// <summary>
        /// Granularity for rounded geometry.
        /// </summary>
        protected float CurveGranularity =>
            (Style["-vertex-curve-granularity"] is float value) ? value : 1.0f;
        protected QuikVertex StrokeVertex => new QuikVertex()
        {
            ZIndex = Style.ZIndex ?? this.ZIndex,
            Color  = Style.StrokeColor ?? QColor.Black,
        };
        protected QuikVertex FillVertex => new QuikVertex()
        {
            ZIndex = Style.ZIndex ?? this.ZIndex,
            Color  = Style.Color ?? QColor.White,
        };

        public override void Reset()
        {
            base.Reset();
            DrawQueue.Clear();
        }

        protected override void ChildProcessCommand(Command name, CommandQueue queue)
        {
            base.ChildProcessCommand(name, queue);

            switch(name)
            {
            case Command.Line:      LineProc(queue);        break;
            case Command.Bezier:    BezierProc(queue);      break;
            case Command.Rectangle: RectangleProc(queue);   break;
            default:                                        break;
            }
        }

        /// <summary>
        /// Gets the rounding resolution for a line segment or border radius.
        /// </summary>
        /// <param name="radius">The width of the line.</param>
        /// <param name="arc">The angle of the cap or joint arc.</param>
        /// <returns>The rounding resolution.</returns>
        protected int GetRoundingResolution(float radius, float arc)
        {
            return (int) Math.Ceiling(arc * radius * CurveGranularity);
        }

        private readonly List<QLine> LineList = new List<QLine>();
        private void LineProc(CommandQueue queue)
        {
            Frame frame = queue.Dequeue();

            // Clear temporary vector list and retreive all line segments.
            LineList.Clear();
            if (frame.Type == FrameType.IVec1)
            {
                int count = (int)frame;
                for (int i = 0; i < count; i++)
                {
                    frame = queue.Dequeue();
                    LineList.Add((QLine)frame);
                }
            }
            else
            {
                LineList.Add((QLine)frame);
            }

            float width = Style.StrokeWidth ?? 1;

            DrawQueue.StartDrawCall(Viewport);
            LineInfo prevBase, nextBase = default;
            for (int i = 0; i < LineList.Count; i++)
            {
                QLine line = LineList[i];
                // A line segment needs a start cap if it is the first segment in
                // the list, or the last end point is not the current start point.
                bool isStart = (i == 0                  || line.Start != LineList[i - 1].End);
                // A line segment needs an end cap if it is the last line segment
                // in the list or if the next start point is not the current end point.
                bool isEnd   = (i == LineList.Count - 1 || line.End != LineList[i+1].Start);

                // Generate the main line segment.
                prevBase = nextBase;
                nextBase = GenerateLineSegment(line);

                if (isStart)
                {
                    // Then a start cap if necessary.
                    GenerateCap(line.Start, line.Normal(), prevBase, false);
                }
                else
                {
                    // Otherwise generate the required joint.
                    GenerateJoint(line.Start, LineList[i-1].Normal(), line.Normal(), prevBase, nextBase);
                }
                if (isEnd)
                {
                    // Then generate the end cap if necessary.
                    GenerateCap(line.End, line.Normal(), nextBase, true);
                }
            }
            DrawQueue.EndDrawCall();
        }

        private LineInfo GenerateLineSegment(in QLine line)
        {
            QuikVertex vertex = StrokeVertex;
            QuikVertex a, b, c, d;
            QVec2 normal = line.Normal();
            float width = Style.StrokeWidth ?? 1;

            a = b = c = d = vertex;
            a.Position = line.Start + width / 2 * normal;
            b.Position = line.Start - width / 2 * normal;
            c.Position = line.End + width / 2 * normal;
            d.Position = line.End - width / 2 * normal;

            DrawQueue.RestoreOffset();
            DrawQueue.AddVertex(a);
            DrawQueue.AddVertex(b);
            DrawQueue.AddVertex(c);
            DrawQueue.AddVertex(d);
            DrawQueue.AddElement(1); DrawQueue.AddElement(2); DrawQueue.AddElement(0);
            DrawQueue.AddElement(1); DrawQueue.AddElement(3); DrawQueue.AddElement(2);
            return new LineInfo(DrawQueue.BaseOffset, 0, 1, 2, 3);
        }

        private void GenerateJoint(
            in QVec2 center,
            in QVec2 prevNormal,
            in QVec2 nextNormal,
            in LineInfo prevInfo,
            in LineInfo nextInfo)
        {
            // Figure out which side needs the joint.
            QVec2 meanNormal = 0.5f * (prevNormal + nextNormal);
            QVec2 meanTangent = new QVec2(meanNormal.Y, -meanNormal.X);
            QVec2 positiveEdge = ((center + nextNormal) - (center + prevNormal)).Normalize();
            QVec2 negativeEdge = ((center - nextNormal) - (center - prevNormal)).Normalize();
            float positive, negative;
            positive = QVec2.Dot(meanTangent, positiveEdge);
            negative = QVec2.Dot(meanNormal, negativeEdge);

            if (positive == negative)
            {
                // To be fair this is highly unlikely considering the nature of
                // floats, but, generate an end cap to handle a cusp.
                GenerateCap(center, nextNormal, nextInfo, true);
                return;
            }

            QuikVertex vertex     = StrokeVertex;
            float      radius     = Style.StrokeWidth/2 ?? 0.5f;
            float      arc        = MathF.Acos(QVec2.Dot(prevNormal, nextNormal));
            int        resolution = GetRoundingResolution(radius, arc);
            bool       isNegative = positive < negative;

            vertex.Position = center;
            DrawQueue.RestoreOffset();
            DrawQueue.AddVertex(vertex);

            int lastIndex, endIndex;
            if (isNegative)
            {
                lastIndex = DrawQueue.RelativeElement(prevInfo.BaseOffset, prevInfo.EndNegative);
                endIndex  = DrawQueue.RelativeElement(nextInfo.BaseOffset, nextInfo.StartNegative);
            }
            else
            {
                lastIndex = DrawQueue.RelativeElement(nextInfo.BaseOffset, nextInfo.StartNegative);
                endIndex  = DrawQueue.RelativeElement(prevInfo.BaseOffset, prevInfo.EndPositive);
            }

            for (int i = 0; i < resolution; i++)
            {
                float angle = (float)(i+1) / (resolution + 1) * arc;
                float cos   = MathF.Cos(angle);
                float sin   = MathF.Sin(angle);

                QVec2 displacement;
                if (isNegative)
                {
                    displacement = new QVec2()
                    {
                        X = -prevNormal.X * cos + prevNormal.Y * sin,
                        Y = -prevNormal.X * sin - prevNormal.Y * cos
                    } * radius;
                }
                else
                {
                    displacement = new QVec2()
                    {
                        X = nextNormal.X * cos - nextNormal.Y * sin,
                        Y = nextNormal.X * sin + nextNormal.Y * cos
                    } * radius;
                }

                vertex.Position = center + displacement;

                DrawQueue.AddVertex(vertex);
                DrawQueue.AddElement(lastIndex);
                DrawQueue.AddElement(i);
                DrawQueue.AddElement(0);

                lastIndex = i;
            }

            DrawQueue.AddElement(lastIndex);
            DrawQueue.AddElement(endIndex);
            DrawQueue.AddElement(0);

        }

        private void GenerateCap(
            in QVec2 center,
            in QVec2 normal,
            in LineInfo info,
            bool endCap)
        {
            int        lastIndex, startIndex;
            QuikVertex vertex     = StrokeVertex;
            float      radius     = Style.StrokeWidth ?? 1.0f;
            int        resolution = GetRoundingResolution(radius, MathF.PI);

            DrawQueue.RestoreOffset();
            if (endCap)
            {
                lastIndex  = DrawQueue.RelativeElement(info.BaseOffset, info.EndPositive);
                startIndex = DrawQueue.RelativeElement(info.BaseOffset, info.EndNegative);
            }
            else
            {
                lastIndex  = DrawQueue.RelativeElement(info.BaseOffset, info.StartPositive);
                startIndex = DrawQueue.RelativeElement(info.BaseOffset, info.StartNegative);
            }

            for (int i = 0; i < resolution; i++)
            {
                float angle = (float) (i + 1) / (resolution + 1) * MathF.PI;
                float cos = MathF.Cos(angle);
                float sin = MathF.Sin(angle);

                QVec2 displacement;
                if (endCap)
                {
                    displacement = new QVec2()
                    {
                        X =  normal.X * cos + normal.Y * sin,
                        Y = -normal.X * sin + normal.Y * cos
                    } * radius;
                }
                else
                {
                    displacement = new QVec2()
                    {
                        X = normal.X * cos - normal.Y * sin,
                        Y = normal.X * sin + normal.Y * cos
                    } * radius;
                }

                vertex.Position = center + displacement;

                DrawQueue.AddVertex(vertex);
                DrawQueue.AddElement(startIndex);
                DrawQueue.AddElement(lastIndex);
                DrawQueue.AddElement(i);

                lastIndex = i;
            }
        }

        private readonly List<QBezier> BezierList = new List<QBezier>();
        private void BezierProc(CommandQueue queue)
        {
            Frame a = queue.Dequeue();
            Frame b;

            // Clear temporary vector list and retreive all bezier segments.
            BezierList.Clear();
            if (a.Type == FrameType.IVec1)
            {
                int count = (int)a;
                for (int i = 0; i < count; i++)
                {
                    a = queue.Dequeue();
                    b = queue.Dequeue();

                    BezierList.Add(
                        new QBezier(
                            new QVec2(a.GetF(0), a.GetF(1)),
                            new QVec2(b.GetF(0), b.GetF(1)),
                            new QVec2(b.GetF(2), b.GetF(3)),
                            new QVec2(a.GetF(2), a.GetF(3))
                        )
                    );
                }
            }
            else
            {
                b = queue.Dequeue();

                BezierList.Add(
                    new QBezier(
                        new QVec2(a.GetF(0), a.GetF(1)),
                        new QVec2(b.GetF(0), b.GetF(1)),
                        new QVec2(b.GetF(2), b.GetF(3)),
                        new QVec2(a.GetF(2), a.GetF(3))
                    )
                );
            }

            float width = Style.StrokeWidth ?? 1;

            DrawQueue.StartDrawCall(Viewport);
            LineInfo prevBase, nextBase = default;
            for (int i = 0; i < LineList.Count; i++)
            {
                QBezier bezier = BezierList[i];
                // A line segment needs a start cap if it is the first segment in
                // the list, or the last end point is not the current start point.
                bool isStart = (i == 0                  || bezier.Start != BezierList[i - 1].End);
                // A line segment needs an end cap if it is the last line segment
                // in the list or if the next start point is not the current end point.
                bool isEnd   = (i == LineList.Count - 1 || bezier.End != BezierList[i+1].Start);

                // Generate the main line segment.
                prevBase = nextBase;
                nextBase = GenerateBezierSegment(bezier);

                if (isStart)
                {
                    // Then a start cap if necessary.
                    GenerateCap(bezier.Start, bezier.GetBezierNormal(0), prevBase, false);
                }
                else
                {
                    // Otherwise generate the required joint.
                    GenerateJoint(bezier.Start, BezierList[i-1].GetBezierNormal(1), bezier.GetBezierNormal(0), prevBase, nextBase);
                }
                if (isEnd)
                {
                    // Then generate the end cap if necessary.
                    GenerateCap(bezier.End, bezier.GetBezierNormal(1), nextBase, true);
                }
            }
            DrawQueue.EndDrawCall();
        }

        private LineInfo GenerateBezierSegment(in QBezier bezier)
        {
            QVec2 startTangent = bezier.GetBezierTangent(0);
            QVec2 endTangent   = bezier.GetBezierTangent(1);
            QVec2 startNormal  = new QVec2(-startTangent.Y, startTangent.X).Normalize();
            QVec2 endNormal    = new QVec2(-endTangent.Y, endTangent.X).Normalize();

            float width      = Style.StrokeWidth ?? 1;
            float radius     = 0.5f * width;
            int   resolution = GetRoundingResolution(radius, bezier.RasterizationArc);

            DrawQueue.RestoreOffset();
            QuikVertex v = StrokeVertex;
            int vbase    = DrawQueue.BaseOffset;
            int index    = 2;

            v.Position = bezier.Start + radius * startNormal;
            DrawQueue.AddVertex(v);
            v.Position = bezier.Start - radius * startNormal;
            DrawQueue.AddVertex(v);

            for (int i = 0; i < resolution; i++, index += 2)
            {
                float t = (i + 1.0f) / resolution;
                QVec2 at = bezier.GetBezierTangent(t).Normalize();
                QVec2 a = bezier.GetBezierPoint(t);
                QVec2 an = radius * new QVec2(-at.Y, at.X);

                v.Position = a + an;
                DrawQueue.AddVertex(v);
                v.Position = a - an;
                DrawQueue.AddVertex(v);

                DrawQueue.AddElement(index - 2); DrawQueue.AddElement(index - 1); DrawQueue.AddElement(index + 0);
                DrawQueue.AddElement(index - 1); DrawQueue.AddElement(index + 1); DrawQueue.AddElement(index + 0);
            }

            return new LineInfo(vbase, 0, 1, index - 2, index - 1);
        }

        private readonly List<QRectangle> RectangleList = new List<QRectangle>();
        private void RectangleProc(CommandQueue queue)
        {
            Frame frame = queue.Dequeue();
            RectangleList.Clear();
            if (frame.Type == FrameType.IVec1)
            {
                int count = (int)frame;
                for (int i = 0; i < count; i++)
                {
                    frame = queue.Dequeue();
                    RectangleList.Add((QRectangle)frame);
                }
            }
            else
            {
                RectangleList.Add((QRectangle)frame);
            }

            float stroke = Style.StrokeWidth ?? 1.0f;
            float radius = (float?)Style["radius"] ?? 0.0f; // TODO: not this.
            DrawQueue.StartDrawCall(Viewport);
            for (int i = 0; i < RectangleList.Count; i++)
            {
                QRectangle outer = RectangleList[i];
                QRectangle inner = new QRectangle(
                    outer.Right - stroke, outer.Top - stroke,
                    outer.Left + stroke, outer.Bottom + stroke);

                GenerateRectangleBase(inner, Math.Max(radius - stroke, 0.0f));

                if (stroke == 0.0f)
                    continue;
                if (radius == 0.0f)
                {
                    GenerateRectangleStripStraight(outer);
                }
                else if (radius < stroke)
                {
                    GenerateRectangleStripNarrow(outer, radius);
                }
                else
                {
                    GenerateRectangleStripWide(outer, radius);
                }
            }
            DrawQueue.EndDrawCall();
        }

        private void GenerateRectangleBase(in QRectangle rectangle, float radius)
        {
            /*
                +--j-------i--+
                |NW|   N   |NE|
                k--d-------c--h
                |  |       |  |
                |W |   C   | E|
                |  |       |  |
                l--a-------b--g
                |SW|   S   |SE|
                +--e-------f--+

                a b c d e f g h i j k  l
                0 1 2 3 4 5 6 7 8 9 10 11
             */

            DrawQueue.RestoreOffset();

            // Draw center rectangle.

            QVec2 aPos, bPos, cPos, dPos;

            QuikVertex v = FillVertex;
            aPos = v.Position = new QVec2(rectangle.Left + radius, rectangle.Bottom + radius);
            DrawQueue.AddVertex(v);
            bPos = v.Position = new QVec2(rectangle.Right - radius, rectangle.Bottom + radius);
            DrawQueue.AddVertex(v);
            cPos = v.Position = new QVec2(rectangle.Right - radius, rectangle.Top - radius);
            DrawQueue.AddVertex(v);
            dPos = v.Position = new QVec2(rectangle.Left + radius, rectangle.Top - radius);
            DrawQueue.AddVertex(v);

            DrawQueue.AddElement(0); DrawQueue.AddElement(1); DrawQueue.AddElement(2);
            DrawQueue.AddElement(0); DrawQueue.AddElement(2); DrawQueue.AddElement(3);

            if (radius == 0.0f)
                return;

            // Draw south rectangle.

            v.Position = new QVec2(rectangle.Left + radius, rectangle.Bottom);
            DrawQueue.AddVertex(v);
            v.Position = new QVec2(rectangle.Right - radius, rectangle.Bottom);
            DrawQueue.AddVertex(v);

            DrawQueue.AddElement(4); DrawQueue.AddElement(5); DrawQueue.AddElement(1);
            DrawQueue.AddElement(4); DrawQueue.AddElement(1); DrawQueue.AddElement(0);

            // Draw east rectangle.

            v.Position = new QVec2(rectangle.Right, rectangle.Bottom + radius);
            DrawQueue.AddVertex(v);
            v.Position = new QVec2(rectangle.Right, rectangle.Top - radius);
            DrawQueue.AddVertex(v);

            DrawQueue.AddElement(1); DrawQueue.AddElement(6); DrawQueue.AddElement(7);
            DrawQueue.AddElement(1); DrawQueue.AddElement(7); DrawQueue.AddElement(3);

            // Draw north rectangle.

            v.Position = new QVec2(rectangle.Right - radius, rectangle.Top);
            DrawQueue.AddVertex(v);
            v.Position = new QVec2(rectangle.Left + radius, rectangle.Top);
            DrawQueue.AddVertex(v);

            DrawQueue.AddElement(3); DrawQueue.AddElement(2); DrawQueue.AddElement(8);
            DrawQueue.AddElement(3); DrawQueue.AddElement(8); DrawQueue.AddElement(0);

            // Draw west rectangle.

            v.Position = new QVec2(rectangle.Left, rectangle.Top - radius);
            DrawQueue.AddVertex(v);
            v.Position = new QVec2(rectangle.Left, rectangle.Bottom + radius);
            DrawQueue.AddVertex(v);

            DrawQueue.AddElement(11); DrawQueue.AddElement(0); DrawQueue.AddElement(3);
            DrawQueue.AddElement(11); DrawQueue.AddElement(3); DrawQueue.AddElement(10);

            // Draw north east corner.

            int resolution = GetRoundingResolution(radius, 0.5f * MathF.PI);
            int previous = 7, current = 12;

            for (int i = 0; i < resolution - 1; i++)
            {
                float theta = (i + 1.0f) / (resolution + 1);
                float xoff = MathF.Cos(theta) * radius;
                float yoff = MathF.Sin(theta) * radius;

                v.Position = cPos + new QVec2(xoff, yoff);
                DrawQueue.AddVertex(v);
                DrawQueue.AddElement(2); DrawQueue.AddElement(previous); DrawQueue.AddElement((previous = current++));
            }
            DrawQueue.AddElement(2); DrawQueue.AddElement(previous); DrawQueue.AddElement(8);

            // Draw the north west corner.

            previous = 9;
            for (int i = 0; i < resolution - 1; i++)
            {
                float theta = (i + 1.0f) / (resolution + 1);
                float xoff = -MathF.Sin(theta) * radius;
                float yoff = MathF.Cos(theta) * radius;

                v.Position = dPos + new QVec2(xoff, yoff);
                DrawQueue.AddVertex(v);
                DrawQueue.AddElement(3); DrawQueue.AddElement(previous); DrawQueue.AddElement((previous = current++));
            }
            DrawQueue.AddElement(3); DrawQueue.AddElement(previous); DrawQueue.AddElement(10);

            // Draw south west corner.

            previous = 11;
            for (int i = 0; i < resolution - 1; i++)
            {
                float theta = (i + 1.0f) / (resolution + 1);
                float xoff = -MathF.Cos(theta) * radius;
                float yoff = -MathF.Sin(theta) * radius;

                v.Position = aPos + new QVec2(xoff, yoff);
                DrawQueue.AddVertex(v);
                DrawQueue.AddElement(0); DrawQueue.AddElement(previous); DrawQueue.AddElement((previous = current++));
            }
            DrawQueue.AddElement(0); DrawQueue.AddElement(previous); DrawQueue.AddElement(4);

            // Draw the south east corner.

            previous = 5;
            for (int i = 0; i < resolution - 1; i++)
            {
                float theta = (i + 1.0f) / (resolution + 1);
                float xoff = -MathF.Sin(theta) * radius;
                float yoff = MathF.Cos(theta) * radius;

                v.Position = bPos + new QVec2(xoff, yoff);
                DrawQueue.AddVertex(v);
                DrawQueue.AddElement(1); DrawQueue.AddElement(previous); DrawQueue.AddElement((previous = current++));
            }
            DrawQueue.AddElement(1); DrawQueue.AddElement(previous); DrawQueue.AddElement(6);
        }

        private void GenerateRectangleStripStraight(in QRectangle rectangle)
        {
            /*
                h---------g
                |\   N   /|
                | \     / |
                |  d---c  |
                |W |   | E|
                |  a---b  |
                | /     \ |
                |/   S   \|
                e---------f

                a b c d e f g h
                0 1 2 3 4 5 6 7
            */

            QuikVertex v = StrokeVertex;
            float stroke = Style.StrokeWidth ?? 1.0f;

            DrawQueue.RestoreOffset();

            v.Position = new QVec2(rectangle.Left + stroke, rectangle.Bottom + stroke);
            DrawQueue.AddVertex(v);
            v.Position = new QVec2(rectangle.Right - stroke, rectangle.Bottom + stroke);
            DrawQueue.AddVertex(v);
            v.Position = new QVec2(rectangle.Right - stroke, rectangle.Top - stroke);
            DrawQueue.AddVertex(v);
            v.Position = new QVec2(rectangle.Left + stroke, rectangle.Top - stroke);
            DrawQueue.AddVertex(v);

            v.Position = new QVec2(rectangle.Left, rectangle.Bottom);
            DrawQueue.AddVertex(v);
            v.Position = new QVec2(rectangle.Right, rectangle.Bottom);
            DrawQueue.AddVertex(v);
            v.Position = new QVec2(rectangle.Right, rectangle.Top);
            DrawQueue.AddVertex(v);
            v.Position = new QVec2(rectangle.Left, rectangle.Top);
            DrawQueue.AddVertex(v);

            DrawQueue.AddElement(4); DrawQueue.AddElement(5); DrawQueue.AddElement(1); // SSW
            DrawQueue.AddElement(4); DrawQueue.AddElement(1); DrawQueue.AddElement(0); // SSE
            DrawQueue.AddElement(1); DrawQueue.AddElement(5); DrawQueue.AddElement(6); // SEE
            DrawQueue.AddElement(1); DrawQueue.AddElement(6); DrawQueue.AddElement(2); // NEE
            DrawQueue.AddElement(3); DrawQueue.AddElement(2); DrawQueue.AddElement(6); // NNE
            DrawQueue.AddElement(3); DrawQueue.AddElement(6); DrawQueue.AddElement(7); // NNW
            DrawQueue.AddElement(4); DrawQueue.AddElement(0); DrawQueue.AddElement(3); // NWW
            DrawQueue.AddElement(4); DrawQueue.AddElement(3); DrawQueue.AddElement(7); // SWW
        }

        private void GenerateRectangleStripNarrow(in QRectangle rectangle, float radius)
        {
            /*
                  v-j---i-u
                  | |   | |
                x-w | N | t-s
                |  \|   |/  |
                k---d---c---h
                |   |   |   |
                | W |   | E |
                |   |   |   |
                l---a---b---g
                |  /|   |\  |
                m-n | S | q-r
                  | |   | |
                  o-e---f-p

                    a b c d e f g h i j
                00: 0 1 2 3 4 5 6 7 8 9
                    k l m n o p q r s t
                10: 0 1 2 3 4 5 6 7 8 9
                    u v w x
                20: 0 1 2 3
            */
            QuikVertex v = StrokeVertex;
            QVec2 nPos, qPos, tPos, wPos;
            float stroke = Style.StrokeWidth ?? 1.0f;

            DrawQueue.RestoreOffset();

            // a-b-c-d

            v.Position = new QVec2(rectangle.Left + stroke, rectangle.Bottom + stroke);
            DrawQueue.AddVertex(v);
            v.Position = new QVec2(rectangle.Right - stroke, rectangle.Bottom + stroke);
            DrawQueue.AddVertex(v);
            v.Position = new QVec2(rectangle.Right - stroke, rectangle.Top - stroke);
            DrawQueue.AddVertex(v);
            v.Position = new QVec2(rectangle.Left + stroke, rectangle.Top - stroke);
            DrawQueue.AddVertex(v);

            // ef-gh-ij-kl

            v.Position = new QVec2(rectangle.Left + stroke, rectangle.Bottom);
            DrawQueue.AddVertex(v);
            v.Position = new QVec2(rectangle.Left + stroke, rectangle.Bottom);
            DrawQueue.AddVertex(v);
            v.Position = new QVec2(rectangle.Right, rectangle.Bottom + stroke);
            DrawQueue.AddVertex(v);
            v.Position = new QVec2(rectangle.Right, rectangle.Top - stroke);
            DrawQueue.AddVertex(v);
            v.Position = new QVec2(rectangle.Right - stroke, rectangle.Top);
            DrawQueue.AddVertex(v);
            v.Position = new QVec2(rectangle.Right - stroke, rectangle.Top);
            DrawQueue.AddVertex(v);
            v.Position = new QVec2(rectangle.Left, rectangle.Top - stroke);
            DrawQueue.AddVertex(v);
            v.Position = new QVec2(rectangle.Left, rectangle.Bottom + stroke);
            DrawQueue.AddVertex(v);

            // mno

            v.Position = new QVec2(rectangle.Left, rectangle.Bottom + radius);
            DrawQueue.AddVertex(v);
            nPos = v.Position = new QVec2(rectangle.Left + radius, rectangle.Bottom + radius);
            DrawQueue.AddVertex(v);
            v.Position = new QVec2(rectangle.Left + radius, rectangle.Bottom);
            DrawQueue.AddVertex(v);

            // pqr
            v.Position = new QVec2(rectangle.Right - radius, rectangle.Bottom);
            DrawQueue.AddVertex(v);
            qPos = v.Position = new QVec2(rectangle.Right - radius, rectangle.Bottom + radius);
            DrawQueue.AddVertex(v);
            v.Position = new QVec2(rectangle.Right, rectangle.Bottom + radius);
            DrawQueue.AddVertex(v);

            // stu
            v.Position = new QVec2(rectangle.Right, rectangle.Top - radius);
            DrawQueue.AddVertex(v);
            tPos = v.Position = new QVec2(rectangle.Right - radius, rectangle.Top - radius);
            DrawQueue.AddVertex(v);
            v.Position = new QVec2(rectangle.Right - radius, rectangle.Top);
            DrawQueue.AddVertex(v);

            // vwx

            v.Position = new QVec2(rectangle.Left + radius, rectangle.Top);
            DrawQueue.AddVertex(v);
            wPos = v.Position = new QVec2(rectangle.Left + radius, rectangle.Top - radius);
            DrawQueue.AddVertex(v);
            v.Position = new QVec2(rectangle.Left, rectangle.Top - radius);
            DrawQueue.AddVertex(v);

            // E
            DrawQueue.AddElement(1); DrawQueue.AddElement(6); DrawQueue.AddElement(7);
            DrawQueue.AddElement(1); DrawQueue.AddElement(7); DrawQueue.AddElement(2);

            // N
            DrawQueue.AddElement(3); DrawQueue.AddElement(2); DrawQueue.AddElement(8);
            DrawQueue.AddElement(3); DrawQueue.AddElement(8); DrawQueue.AddElement(9);

            // W
            DrawQueue.AddElement(11); DrawQueue.AddElement(0); DrawQueue.AddElement(3);
            DrawQueue.AddElement(11); DrawQueue.AddElement(3); DrawQueue.AddElement(10);

            // S
            DrawQueue.AddElement(4); DrawQueue.AddElement(5); DrawQueue.AddElement(1);
            DrawQueue.AddElement(4); DrawQueue.AddElement(1); DrawQueue.AddElement(0);

            // NEE
            DrawQueue.AddElement(2); DrawQueue.AddElement(7); DrawQueue.AddElement(18);
            DrawQueue.AddElement(2); DrawQueue.AddElement(18); DrawQueue.AddElement(19);
            // NNE
            DrawQueue.AddElement(2); DrawQueue.AddElement(19); DrawQueue.AddElement(20);
            DrawQueue.AddElement(2); DrawQueue.AddElement(20); DrawQueue.AddElement(8);

            // NNW
            DrawQueue.AddElement(22); DrawQueue.AddElement(3); DrawQueue.AddElement(19);
            DrawQueue.AddElement(22); DrawQueue.AddElement(19); DrawQueue.AddElement(21);
            // NWW
            DrawQueue.AddElement(10); DrawQueue.AddElement(3); DrawQueue.AddElement(22);
            DrawQueue.AddElement(10); DrawQueue.AddElement(22); DrawQueue.AddElement(23);

            // SWW
            DrawQueue.AddElement(12); DrawQueue.AddElement(13); DrawQueue.AddElement(0);
            DrawQueue.AddElement(12); DrawQueue.AddElement(0); DrawQueue.AddElement(11);
            // SSW
            DrawQueue.AddElement(14); DrawQueue.AddElement(4); DrawQueue.AddElement(0);
            DrawQueue.AddElement(14); DrawQueue.AddElement(0); DrawQueue.AddElement(13);

            // NE

            int resolution = GetRoundingResolution(radius, 0.5f * MathF.PI);
            int previous = 18, current = 24;

            for (int i = 0; i < resolution - 1; i++)
            {
                float theta = (i + 1.0f) / (resolution + 1);
                float xoff = MathF.Cos(theta) * radius;
                float yoff = MathF.Sin(theta) * radius;

                v.Position = tPos + new QVec2(xoff, yoff);
                DrawQueue.AddVertex(v);
                DrawQueue.AddElement(19); DrawQueue.AddElement(previous); DrawQueue.AddElement((previous = current++));
            }
            DrawQueue.AddElement(19); DrawQueue.AddElement(previous); DrawQueue.AddElement(20);

            // NW

            previous = 21;
            for (int i = 0; i < resolution - 1; i++)
            {
                float theta = (i + 1.0f) / (resolution + 1);
                float xoff = -MathF.Sin(theta) * radius;
                float yoff = MathF.Cos(theta) * radius;

                v.Position = wPos + new QVec2(xoff, yoff);
                DrawQueue.AddVertex(v);
                DrawQueue.AddElement(22); DrawQueue.AddElement(previous); DrawQueue.AddElement((previous = current++));
            }
            DrawQueue.AddElement(22); DrawQueue.AddElement(previous); DrawQueue.AddElement(23);

            // SW

            previous = 12;
            for (int i = 0; i < resolution - 1; i++)
            {
                float theta = (i + 1.0f) / (resolution + 1);
                float xoff = -MathF.Cos(theta) * radius;
                float yoff = -MathF.Sin(theta) * radius;

                v.Position = nPos + new QVec2(xoff, yoff);
                DrawQueue.AddVertex(v);
                DrawQueue.AddElement(23); DrawQueue.AddElement(previous); DrawQueue.AddElement((previous = current++));
            }
            DrawQueue.AddElement(23); DrawQueue.AddElement(previous); DrawQueue.AddElement(14);

            // SE

            previous = 15;
            for (int i = 0; i < resolution - 1; i++)
            {
                float theta = (i + 1.0f) / (resolution + 1);
                float xoff = -MathF.Sin(theta) * radius;
                float yoff = MathF.Cos(theta) * radius;

                v.Position = qPos + new QVec2(xoff, yoff);
                DrawQueue.AddVertex(v);
                DrawQueue.AddElement(16); DrawQueue.AddElement(previous); DrawQueue.AddElement((previous = current++));
            }
            DrawQueue.AddElement(16); DrawQueue.AddElement(previous); DrawQueue.AddElement(17);
        }

        private void GenerateRectangleStripWide(in QRectangle rectangle, float radius)
        {
            /*
                      l---k
                      | N |
                      i---j
                p---o       h---g
                | W |       | E |
                m---n       e---f
                      d---c
                      | S |
                      a---b

                    a b c d e f g h i j
                00: 0 1 2 3 4 5 6 7 8 9
                    k l m n o p q r s t
                10: 0 1 2 3 4 5
            */

            QuikVertex v = StrokeVertex;
            float stroke = Style.StrokeWidth ?? 1.0f;
            float innerRadius = radius - stroke;
            DrawQueue.RestoreOffset();

            v.Position = new QVec2(rectangle.Left + radius, rectangle.Bottom);
            DrawQueue.AddVertex(v);
            v.Position = new QVec2(rectangle.Right - radius, rectangle.Bottom);
            DrawQueue.AddVertex(v);
            v.Position = new QVec2(rectangle.Right - radius, rectangle.Bottom + stroke);
            DrawQueue.AddVertex(v);
            v.Position = new QVec2(rectangle.Left + radius, rectangle.Bottom + stroke);
            DrawQueue.AddVertex(v);

            v.Position = new QVec2(rectangle.Right - stroke, rectangle.Bottom + radius);
            DrawQueue.AddVertex(v);
            v.Position = new QVec2(rectangle.Right, rectangle.Top - radius);
            DrawQueue.AddVertex(v);
            v.Position = new QVec2(rectangle.Right, rectangle.Top - radius);
            DrawQueue.AddVertex(v);
            v.Position = new QVec2(rectangle.Right - stroke, rectangle.Bottom + radius);
            DrawQueue.AddVertex(v);

            v.Position = new QVec2(rectangle.Left + radius, rectangle.Top - stroke);
            DrawQueue.AddVertex(v);
            v.Position = new QVec2(rectangle.Right - radius, rectangle.Top - stroke);
            DrawQueue.AddVertex(v);
            v.Position = new QVec2(rectangle.Right - radius, rectangle.Top);
            DrawQueue.AddVertex(v);
            v.Position = new QVec2(rectangle.Left + radius, rectangle.Top);
            DrawQueue.AddVertex(v);

            v.Position = new QVec2(rectangle.Left, rectangle.Bottom + radius);
            DrawQueue.AddVertex(v);
            v.Position = new QVec2(rectangle.Left + stroke, rectangle.Top - radius);
            DrawQueue.AddVertex(v);
            v.Position = new QVec2(rectangle.Left + stroke, rectangle.Top - radius);
            DrawQueue.AddVertex(v);
            v.Position = new QVec2(rectangle.Left, rectangle.Bottom + radius);
            DrawQueue.AddVertex(v);

            // S
            DrawQueue.AddElement(0); DrawQueue.AddElement(1); DrawQueue.AddElement(2);
            DrawQueue.AddElement(0); DrawQueue.AddElement(2); DrawQueue.AddElement(3);
            // E
            DrawQueue.AddElement(4); DrawQueue.AddElement(5); DrawQueue.AddElement(6);
            DrawQueue.AddElement(4); DrawQueue.AddElement(6); DrawQueue.AddElement(7);
            // N
            DrawQueue.AddElement(8); DrawQueue.AddElement(9); DrawQueue.AddElement(10);
            DrawQueue.AddElement(8); DrawQueue.AddElement(10); DrawQueue.AddElement(11);
            // W
            DrawQueue.AddElement(12); DrawQueue.AddElement(13); DrawQueue.AddElement(14);
            DrawQueue.AddElement(12); DrawQueue.AddElement(14); DrawQueue.AddElement(15);

            // Draw NE arc.
            int resolution  = GetRoundingResolution(radius, 0.5f * MathF.PI);
            int current     = 16;

            QVec2 center = new QVec2(rectangle.Right - radius, rectangle.Top - radius);
            int s1 = 7, s2 = 6;
            for (int i = 0; i < resolution - 1; i++)
            {
                float theta = (i + 1.0f) / (resolution + 1);
                float xoff = MathF.Cos(theta);
                float yoff = MathF.Sin(theta);

                v.Position = center + radius * new QVec2(xoff, yoff);
                DrawQueue.AddVertex(v);
                v.Position = center + innerRadius * new QVec2(xoff, yoff);
                DrawQueue.AddVertex(v);

                DrawQueue.AddElement(s1); DrawQueue.AddElement(s2); DrawQueue.AddElement(current + 0);
                DrawQueue.AddElement(s1); DrawQueue.AddElement(s2); DrawQueue.AddElement(current + 1);

                s1 = current; s2 = current + 1;
                current += 2;
            }
            DrawQueue.AddElement(s1); DrawQueue.AddElement(s2); DrawQueue.AddElement(10);
            DrawQueue.AddElement(s1); DrawQueue.AddElement(s2); DrawQueue.AddElement(9);

            // Draw NW arc
            center = new QVec2(rectangle.Left + radius, rectangle.Top - radius);
            s1 = 8; s2 = 11;
            for (int i = 0; i < resolution - 1; i++)
            {
                float theta = (i + 1.0f) / (resolution + 1);
                float xoff = -MathF.Sin(theta);
                float yoff = MathF.Cos(theta);

                v.Position = center + radius * new QVec2(xoff, yoff);
                DrawQueue.AddVertex(v);
                v.Position = center + innerRadius * new QVec2(xoff, yoff);
                DrawQueue.AddVertex(v);

                DrawQueue.AddElement(s1); DrawQueue.AddElement(s2); DrawQueue.AddElement(current + 0);
                DrawQueue.AddElement(s1); DrawQueue.AddElement(s2); DrawQueue.AddElement(current + 1);

                s1 = current; s2 = current + 1;
                current += 2;
            }
            DrawQueue.AddElement(s1); DrawQueue.AddElement(s2); DrawQueue.AddElement(15);
            DrawQueue.AddElement(s1); DrawQueue.AddElement(s2); DrawQueue.AddElement(14);

            // Draw SW arc
            center = new QVec2(rectangle.Left + radius, rectangle.Bottom + radius);
            s1 = 13; s2 = 12;
            for (int i = 0; i < resolution - 1; i++)
            {
                float theta = (i + 1.0f) / (resolution + 1);
                float xoff = -MathF.Cos(theta);
                float yoff = -MathF.Sin(theta);

                v.Position = center + radius * new QVec2(xoff, yoff);
                DrawQueue.AddVertex(v);
                v.Position = center + innerRadius * new QVec2(xoff, yoff);
                DrawQueue.AddVertex(v);

                DrawQueue.AddElement(s1); DrawQueue.AddElement(s2); DrawQueue.AddElement(current + 0);
                DrawQueue.AddElement(s1); DrawQueue.AddElement(s2); DrawQueue.AddElement(current + 1);

                s1 = current; s2 = current + 1;
                current += 2;
            }
            DrawQueue.AddElement(s1); DrawQueue.AddElement(s2); DrawQueue.AddElement(0);
            DrawQueue.AddElement(s1); DrawQueue.AddElement(s2); DrawQueue.AddElement(3);

            // Draw SW arc
            center = new QVec2(rectangle.Right - radius, rectangle.Bottom + radius);
            s1 = 2; s2 = 1;
            for (int i = 0; i < resolution - 1; i++)
            {
                float theta = (i + 1.0f) / (resolution + 1);
                float xoff = MathF.Sin(theta);
                float yoff = -MathF.Cos(theta);

                v.Position = center + radius * new QVec2(xoff, yoff);
                DrawQueue.AddVertex(v);
                v.Position = center + innerRadius * new QVec2(xoff, yoff);
                DrawQueue.AddVertex(v);

                DrawQueue.AddElement(s1); DrawQueue.AddElement(s2); DrawQueue.AddElement(current + 0);
                DrawQueue.AddElement(s1); DrawQueue.AddElement(s2); DrawQueue.AddElement(current + 1);

                s1 = current; s2 = current + 1;
                current += 2;
            }
            DrawQueue.AddElement(s1); DrawQueue.AddElement(s2); DrawQueue.AddElement(5);
            DrawQueue.AddElement(s1); DrawQueue.AddElement(s2); DrawQueue.AddElement(4);
        }

        private struct LineInfo
        {
            public int BaseOffset { get; }
            public int StartPositive { get; }
            public int StartNegative { get; }
            public int EndPositive { get; }
            public int EndNegative { get; }

            public LineInfo(int baseOffset, int startPositive, int startNegative, int endPositive, int endNegative)
            {
                BaseOffset = baseOffset;
                StartPositive = startPositive;
                StartNegative = startNegative;
                EndPositive = endPositive;
                EndNegative = endNegative;
            }
        }
    }
}