using Quik.Media;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;

namespace Quik.CommandMachine
{
    public class CommandList : IEnumerable<Frame>
    {
        private readonly List<Frame> _frames = new List<Frame>();

        public void Clear()
        {
            _frames.Clear();
        }

        protected void Enqueue(in Frame frame)
        {
            _frames.Add(frame);
        }

        public void Invoke(QuikCommandHandler handler)
        {
            Enqueue(Command.Invoke);
            Enqueue(new Frame(handler));
        }

        public void ConditionalBegin(bool value)
        {
            Enqueue(Command.ConditionalBegin);
            Enqueue((Frame)(value ? 1 : 0));
        }

        public void ConditionalBegin(Func<bool> condition)
        {
            Enqueue(Command.ConditionalBegin);
            Enqueue(new Frame(condition));
        }

        public void ConditionalEnd()
        {
            Enqueue(Command.ConditionalEnd);
        }

        public void PushViewport()
        {
            Enqueue(Command.PushViewport);
        }

        public void IntersectViewport(in QRectangle viewport)
        {
            Enqueue(Command.IntersectViewport);
            Enqueue(viewport);
        }

        public void StoreViewport(in QRectangle viewport)
        {
            Enqueue(Command.StoreViewport);
            Enqueue(viewport);
        }

        public void PopViewport()
        {
            Enqueue(Command.PopViewport);
        }

        public void PushZ()
        {
            Enqueue(Command.PushZ);
        }

        public void IncrementZ()
        {
            Enqueue(Command.IncrementZ);
        }

        public void AddZ(int value)
        {
            if (value == 1)
            {
                IncrementZ();
            }
            else if (value == -1)
            {
                DecrementZ();
            }
            else
            {
                Enqueue(Command.AddZ);
                Enqueue((Frame)value);
            }
        }

        public void StoreZ(int value)
        {
            Enqueue(Command.StoreZ);
            Enqueue((Frame)value);
        }

        public void DecrementZ()
        {
            Enqueue(Command.DecrementZ);
        }

        public void PopZ()
        {
            Enqueue(Command.PopZ);
        }

        public void PushStyle(Style style)
        {
            Enqueue(Command.PushStyle);
            Enqueue(new Frame(style));
        }

        public void StoreStyle(Style style)
        {
            Enqueue(Command.StoreStyle);
            Enqueue(new Frame(style));
        }

        public void PopStyle()
        {
            Enqueue(Command.PopStyle);
        }

        public void Line(in QLine line)
        {
            Enqueue(Command.Line);
            Enqueue(line);
        }

        public void Line(params QLine[] lines)
        {
            Enqueue(Command.Line);
            Enqueue((Frame)lines.Length);
            foreach (QLine line in lines)
                Enqueue(line);
        }

        public void Bezier(in QBezier bezier)
        {
            Frame a, b;
            Frame.Create(bezier, out a, out b);

            Enqueue(Command.Bezier);
            Enqueue(a);
            Enqueue(b);
        }

        public void Bezier(params QBezier[] beziers)
        {
            Frame a, b;

            Enqueue(Command.Bezier);
            Enqueue((Frame)beziers.Length);

            foreach (QBezier bezier in beziers)
            {
                Frame.Create(bezier, out a, out b);
                Enqueue(a);
                Enqueue(b);
            }
        }

        public void Rectangle(in QRectangle rectangle)
        {
            Enqueue(Command.Rectangle);
            Enqueue(rectangle);
        }

        public void Rectangle(QRectangle[] rectangles)
        {
            Enqueue(Command.Rectangle);
            Enqueue((Frame)rectangles.Length);
            foreach (QRectangle rectangle in rectangles)
                Enqueue(rectangle);
        }

        public void Ellipse(in QEllipse ellipse)
        {
            Frame a, b;
            Frame.Create(ellipse, out a, out b);

            Enqueue(Command.Ellipse);
            Enqueue(a);
            Enqueue(b);
        }

        public void Ellipse(params QEllipse[] ellipses)
        {
            Frame a, b;
            Enqueue(Command.Ellipse);
            Enqueue((Frame)ellipses.Length);
            foreach (QEllipse ellipse in ellipses)
            {
                Frame.Create(ellipse, out a, out b);
                Enqueue(a);
                Enqueue(b);
            }
        }

        public void Triangle(in QTriangle triangle)
        {
            Enqueue(Command.Triangle);
            Enqueue(triangle.A);
            Enqueue(triangle.B);
            Enqueue(triangle.C);
        }

        public void Triangle(params QTriangle[] triangles)
        {
            Enqueue(Command.Triangle);
            Enqueue((Frame)triangles.Length);
            foreach (QTriangle triangle in triangles)
            {
                Enqueue(triangle.A);
                Enqueue(triangle.B);
                Enqueue(triangle.C);
            }
        }

        public void Polygon(params QVec2[] polygon)
        {
            Enqueue(Command.Polygon);
            Enqueue((Frame)polygon.Length);
            foreach (QVec2 vertex in polygon)
            {
                Enqueue(vertex);
            }
        }

        public void Image(QImage texture, in QRectangle rectangle)
        {
            Enqueue(Command.Image);
            Enqueue((Frame)(int)ImageCommandFlags.Single);
            Enqueue(new Frame(texture));
            Enqueue(rectangle);
        }

        public void Image(QImage texture, in QRectangle rectangle, in QRectangle uv)
        {
            Enqueue(Command.Image);
            Enqueue((Frame)(int)(ImageCommandFlags.Single | ImageCommandFlags.UVs));
            Enqueue(new Frame(texture));
            Enqueue(rectangle);
            Enqueue(uv);
        }

        public void Image(QImage texture, ReadOnlySpan<QRectangle> rectangles, bool interleavedUV = false)
        {
            int count = rectangles.Length;
            ImageCommandFlags flags = ImageCommandFlags.None;

            if (interleavedUV)
            {
                count /= 2;
                flags |= ImageCommandFlags.UVs;
            }

            Enqueue(Command.Image);
            Enqueue(new Frame((int)flags, count));
            Enqueue(new Frame(texture));

            foreach (QRectangle rectangle in rectangles)
            {
                Enqueue(rectangle);
            }
        }

        public void Image(QImage texture, ReadOnlySpan<QRectangle> rectangles, ReadOnlySpan<QRectangle> uvs)
        {
            int count = Math.Min(rectangles.Length, uvs.Length);
            Enqueue(Command.Image);
            Enqueue(new Frame((int)ImageCommandFlags.UVs, count));
            Enqueue(new Frame(texture));

            for (int i = 0; i < count; i++)
            {
                Enqueue(rectangles[i]);
                Enqueue(uvs[i]);
            }
        }

        public void Image3D(QImage texture, in Image3DCall call)
        {
            Enqueue(Command.Image);
            Enqueue(new Frame(ImageCommandFlags.Image3d | ImageCommandFlags.Single));
            Enqueue(new Frame(texture));
            Enqueue(call.Rectangle);
            Enqueue(call.UVs);
            Enqueue(new Frame(call.Layer));
        }

        public void Image3D(QImage texture, ReadOnlySpan<Image3DCall> calls)
        {
            Enqueue(Command.Image);
            Enqueue(new Frame((int)ImageCommandFlags.Image3d, calls.Length));
            Enqueue(new Frame(texture));

            foreach (Image3DCall call in calls)
            {
                Enqueue(call.Rectangle);
                Enqueue(call.UVs);
                Enqueue(new Frame(call.Layer));
            }
        }

        public void Splice(CommandList list)
        {
            foreach (Frame frame in list)
            {
                Enqueue(frame);
            }
        }

        public CommandQueue GetEnumerator() => new CommandQueue(_frames);
        IEnumerator<Frame> IEnumerable<Frame>.GetEnumerator() => GetEnumerator();
        IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
    }

    public class CommandQueue : IEnumerator<Frame>
    {
        private readonly IReadOnlyList<Frame> _frames;
        private int _current;

        public Frame Current => _frames[_current];

        object IEnumerator.Current => Current;

        public CommandQueue(IReadOnlyList<Frame> frames)
        {
            _current = -1;
            _frames = frames;
        }

        public void Dispose()
        {
        }

        public bool TryDequeue([NotNullWhen(true)] out Frame frame)
        {
            if (MoveNext())
            {
                frame = Current;
                return true;
            }
            else
            {
                frame = default;
                return false;
            }
        }

        public Frame Dequeue() => TryDequeue(out Frame frame) ? frame : throw new Exception("No more frames left.");
        
        public bool TryPeek([NotNullWhen(true)] out Frame frame)
        {
            if (_current + 1 < _frames.Count)
            {
                frame = _frames[_current + 1];
                return true;
            }
            else
            {
                frame = default;
                return false;
            }
        }

        public Frame Peek() => TryPeek(out Frame frame) ? frame : throw new Exception("No more frames left.");

        public bool MoveNext()
        {
            if (_current + 1 < _frames.Count)
            {
                _current++;
                return true;
            }
            return false;
        }

        public void Reset()
        {
            _current = -1;
        }
    }
}