using System;
using System.Collections.Generic;

namespace Quik.CommandMachine
{
    public class CommandEngine
    {
        private int _zIndex = 0;
        private readonly Stack<int> _zStack = new Stack<int>();
        public int ZIndex => _zIndex;

        private QRectangle _viewport;
        private readonly Stack<QRectangle> _viewportStack = new Stack<QRectangle>();
        private readonly Stack<QMat4>  _matrixStack = new Stack<QMat4>();

        private Command _customCommandBase = Command.CustomCommandBase;
        private readonly List<QuikCommandHandler> _customCommands = new List<QuikCommandHandler>();

        public QRectangle Viewport => _viewport;

        public QMat4 ActiveTransforms { get; }

        public StyleStack Style { get; } = new StyleStack(new Quik.Style());

        protected CommandEngine()
        {
            Reset();
        }

        public Command RegisterCustomCommand(QuikCommandHandler handler)
        {
            Command id = _customCommandBase++;
            _customCommands.Insert(id - Command.CustomCommandBase, handler);
            return id;
        }

        public void ProcessCommands(QRectangle bounds, CommandQueue queue)
        {
            if (!queue.Peek().IsCommand)
                throw new ArgumentException("The first element in the queue must be a command frame.");

            Reset();

            _viewport = bounds;
            _viewportStack.Push(_viewport);

            Frame frame;
            while (queue.TryDequeue(out frame))
            {
                Command cmd = (Command)frame;
                switch (cmd)
                {
                default:
                    if (cmd > Command.CustomCommandBase)
                    {
                        _customCommands[cmd - Command.CustomCommandBase].Invoke(this, queue);
                    }
                    else
                    {
                        ChildProcessCommand(cmd, queue);
                    }
                    break;

                case Command.ConditionalBegin:  ConditionalHandler(queue);       break;
                case Command.ConditionalEnd:    /* nop */                        break;

                case Command.Invoke:
                    queue.Dequeue().As<QuikCommandHandler>().Invoke(this, queue);
                    break;
                case Command.PushViewport:
                    _viewportStack.Push(_viewport);
                    break;
                case Command.IntersectViewport:
                    _viewport = QRectangle.Intersect((QRectangle)queue.Dequeue(), _viewport);
                    break;
                case Command.StoreViewport:
                    _viewport = (QRectangle)queue.Dequeue();
                    break;
                case Command.PopViewport:
                    _viewport = _viewportStack.TryPop(out QRectangle viewport) ? viewport : bounds;
                    break;
                case Command.PushZ:
                    _zStack.Push(_zIndex);
                    break;
                case Command.IncrementZ:
                    _zIndex++;
                    break;
                case Command.AddZ:
                    _zIndex += (int)queue.Dequeue();
                    break;
                case Command.StoreZ:
                    _zIndex = (int)queue.Dequeue();
                    break;
                case Command.DecrementZ:
                    _zIndex--;
                    break;
                case Command.PopZ:
                    _zIndex = _zStack.TryPop(out int zindex) ? zindex : 0;
                    break;
                }
            }
        }

        protected virtual void ChildProcessCommand(Command name, CommandQueue queue)
        {
        }

        public virtual void Reset()
        {
            _zIndex = 0;
            _zStack.Clear();

            _viewport = new QRectangle(float.MaxValue, float.MaxValue, float.MinValue, float.MinValue);
            _viewportStack.Clear();

            _matrixStack.Clear();
            _matrixStack.Push(QMat4.Identity);
        }

        private void ConditionalHandler(CommandQueue queue)
        {
            Frame frame = queue.Dequeue();

            if (
                frame.IsInteger && (int)frame != 0 ||
                frame.As<Func<bool>>().Invoke())
            {
                // Take this branch.
                return;
            }

            // Skip this branch.
            int depth = 1;
            while (queue.TryPeek(out frame))
            {
                if (!frame.IsCommand)
                {
                    queue.Dequeue();
                    continue;
                }

                switch ((Command)frame)
                {
                case Command.ConditionalBegin:
                    // Increment conditional depth.
                    depth++;
                    break;
                case Command.ConditionalEnd:
                    // Decrement condional depth, exit if zero.
                    if (--depth == 0)
                    {
                        queue.Dequeue();
                        return;
                    }
                    break;
                }

                queue.Dequeue();
            }
        }
    }
}