#nullable disable // Consider removing in the future to minimize likelihood of NullReferenceException; refer https://learn.microsoft.com/en-us/dotnet/csharp/nullable-references

using System;
using System.Collections.Generic;
using System.Text;
using GitHub.Actions.WorkflowParser.ObjectTemplating.Tokens;

namespace GitHub.Actions.WorkflowParser.ObjectTemplating
{
    /// <summary>
    /// This class allows callers to easily traverse a template object.
    /// This class hides the details of expression expansion, depth tracking,
    /// and memory tracking.
    /// </summary>
    internal sealed class TemplateUnraveler
    {
        internal TemplateUnraveler(
            TemplateContext context,
            TemplateToken template,
            Int32 removeBytes)
        {
            m_context = context;
            m_memory = context.Memory;

            // Initialize the reader state
            MoveFirst(template, removeBytes);
        }

        internal Boolean AllowScalar(
            Boolean expand,
            out ScalarToken scalar)
        {
            m_memory.IncrementEvents();

            if (expand)
            {
                Unravel(expand: true);
            }

            if (m_current?.Value is ScalarToken scalarToken)
            {
                scalar = scalarToken;

                // Add bytes before they are emitted to the caller (so the caller doesn't have to track bytes)
                m_memory.AddBytes(scalar);

                MoveNext();
                return true;
            }

            scalar = null;
            return false;
        }

        internal Boolean AllowSequenceStart(
            Boolean expand,
            out SequenceToken sequence)
        {
            m_memory.IncrementEvents();

            if (expand)
            {
                Unravel(expand: true);
            }

            if (m_current is SequenceState sequenceState && sequenceState.IsStart)
            {
                sequence = new SequenceToken(sequenceState.Value.FileId, sequenceState.Value.Line, sequenceState.Value.Column);

                // Add bytes before they are emitted to the caller (so the caller doesn't have to track bytes)
                m_memory.AddBytes(sequence);

                MoveNext();
                return true;
            }

            sequence = null;
            return false;
        }

        internal Boolean AllowSequenceEnd(Boolean expand)
        {
            m_memory.IncrementEvents();

            if (expand)
            {
                Unravel(expand: true);
            }

            if (m_current is SequenceState sequenceState && sequenceState.IsEnd)
            {
                MoveNext();
                return true;
            }

            return false;
        }

        internal Boolean AllowMappingStart(
            Boolean expand,
            out MappingToken mapping)
        {
            m_memory.IncrementEvents();

            if (expand)
            {
                Unravel(expand: true);
            }

            if (m_current is MappingState mappingState && mappingState.IsStart)
            {
                mapping = new MappingToken(mappingState.Value.FileId, mappingState.Value.Line, mappingState.Value.Column);

                // Add bytes before they are emitted to the caller (so the caller doesn't have to track bytes)
                m_memory.AddBytes(mapping);

                MoveNext();
                return true;
            }

            mapping = null;
            return false;
        }

        internal Boolean AllowMappingEnd(Boolean expand)
        {
            m_memory.IncrementEvents();

            if (expand)
            {
                Unravel(expand: true);
            }

            if (m_current is MappingState mappingState && mappingState.IsEnd)
            {
                MoveNext();
                return true;
            }

            return false;
        }

        internal void ReadEnd()
        {
            m_memory.IncrementEvents();

            if (m_current != null)
            {
                throw new InvalidOperationException("Expected end of template object. " + DumpState());
            }
        }

        internal void ReadMappingEnd()
        {
            if (!AllowMappingEnd(expand: false))
            {
                throw new InvalidOperationException("Unexpected state while attempting to read the mapping end. " + DumpState());
            }
        }

        internal void SkipSequenceItem()
        {
            m_memory.IncrementEvents();

            if (!(m_current?.Parent is SequenceState ancestor))
            {
                throw new InvalidOperationException("Unexpected state while attempting to skip the current sequence item. " + DumpState());
            }

            MoveNext(skipNestedEvents: true);
        }

        internal void SkipMappingKey()
        {
            m_memory.IncrementEvents();

            if (!(m_current?.Parent is MappingState ancestor) || !ancestor.IsKey)
            {
                throw new InvalidOperationException("Unexpected state while attempting to skip the current mapping key. " + DumpState());
            }

            MoveNext(skipNestedEvents: true);
        }

        internal void SkipMappingValue()
        {
            m_memory.IncrementEvents();

            if (!(m_current?.Parent is MappingState ancestor) || ancestor.IsKey)
            {
                throw new InvalidOperationException("Unexpected state while attempting to skip the current mapping value. " + DumpState());
            }

            MoveNext(skipNestedEvents: true);
        }

        private String DumpState()
        {
            var result = new StringBuilder();

            if (m_current == null)
            {
                result.AppendLine("State: (null)");
            }
            else
            {
                result.AppendLine("State:");
                result.AppendLine();

                // Push state hierarchy
                var stack = new Stack<ReaderState>();
                var curr = m_current;
                while (curr != null)
                {
                    result.AppendLine(curr.ToString());
                    curr = curr.Parent;
                }
            }

            return result.ToString();
        }

        private void MoveFirst(
            TemplateToken value,
            Int32 removeBytes)
        {
            if (!(value is LiteralToken) && !(value is SequenceToken) && !(value is MappingToken) && !(value is BasicExpressionToken))
            {
                throw new NotSupportedException($"Unexpected type '{value?.GetType().Name}' when initializing object reader state");
            }

            m_memory.IncrementEvents();
            m_current = ReaderState.CreateState(null, value, m_context, removeBytes);
        }

        private void MoveNext(Boolean skipNestedEvents = false)
        {
            m_memory.IncrementEvents();

            if (m_current == null)
            {
                return;
            }

            // Sequence start
            if (m_current is SequenceState sequenceState &&
                sequenceState.IsStart &&
                !skipNestedEvents)
            {
                // Move to the first item or sequence end
                m_current = sequenceState.Next();
            }
            // Mapping start
            else if (m_current is MappingState mappingState &&
                mappingState.IsStart &&
                !skipNestedEvents)
            {
                // Move to the first item key or mapping end
                m_current = mappingState.Next();
            }
            // Parent is a sequence
            else if (m_current.Parent is SequenceState parentSequenceState)
            {
                // Move to the next item or sequence end
                m_current.Remove();
                m_current = parentSequenceState.Next();
            }
            // Parent is a mapping
            else if (m_current.Parent is MappingState parentMappingState)
            {
                // Move to the next item value, item key, or mapping end
                m_current.Remove();
                m_current = parentMappingState.Next();
            }
            // Parent is an expression end
            else if (m_current.Parent != null)
            {
                m_current.Remove();
                m_current = m_current.Parent;
            }
            // Parent is null
            else
            {
                m_current.Remove();
                m_current = null;
            }

            m_expanded = false;
            Unravel(expand: false);
        }

        private void Unravel(Boolean expand)
        {
            if (m_expanded)
            {
                return;
            }

            do
            {
                if (m_current == null)
                {
                    break;
                }
                // Literal
                else if (m_current is LiteralState literalState)
                {
                    break;
                }
                else if (m_current is BasicExpressionState basicExpressionState)
                {
                    // Sequence item is a basic expression start
                    // For example:
                    //   steps:
                    //   - script: credScan
                    //   - ${{ parameters.preBuild }}
                    //   - script: build
                    if (basicExpressionState.IsStart &&
                        m_current.Parent is SequenceState)
                    {
                        if (expand)
                        {
                            SequenceItemBasicExpression();
                        }
                        else
                        {
                            break;
                        }
                    }
                    // Mapping key is a basic expression start
                    // For example:
                    //   steps:
                    //   - ${{ parameters.scriptHost }}: echo hi
                    else if (basicExpressionState.IsStart &&
                        m_current.Parent is MappingState parentMappingState &&
                        parentMappingState.IsKey)
                    {
                        if (expand)
                        {
                            MappingKeyBasicExpression();
                        }
                        else
                        {
                            break;
                        }
                    }
                    // Mapping value is a basic expression start
                    // For example:
                    //   steps:
                    //   - script: credScan
                    //   - script: ${{ parameters.tool }}
                    else if (basicExpressionState.IsStart &&
                        m_current.Parent is MappingState parentMappingState2 &&
                        !parentMappingState2.IsKey)
                    {
                        if (expand)
                        {
                            MappingValueBasicExpression();
                        }
                        else
                        {
                            break;
                        }
                    }
                    else if (basicExpressionState.IsStart &&
                        m_current.Parent is null)
                    {
                        if (expand)
                        {
                            RootBasicExpression();
                        }
                        else
                        {
                            break;
                        }
                    }
                    // Basic expression end
                    else if (basicExpressionState.IsEnd)
                    {
                        EndExpression();
                    }
                    else
                    {
                        UnexpectedState();
                    }
                }
                else if (m_current is MappingState mappingState)
                {
                    // Mapping end, closing an "insert" mapping insertion
                    if (mappingState.IsEnd &&
                        m_current.Parent is InsertExpressionState)
                    {
                        m_current.Remove();
                        m_current = m_current.Parent; // Skip to the expression end
                    }
                    // Normal mapping start
                    else if (mappingState.IsStart)
                    {
                        break;
                    }
                    // Normal mapping end
                    else if (mappingState.IsEnd)
                    {
                        break;
                    }
                    else
                    {
                        UnexpectedState();
                    }
                }
                else if (m_current is SequenceState sequenceState)
                {
                    // Sequence end, closing a sequence insertion
                    if (sequenceState.IsEnd &&
                        m_current.Parent is BasicExpressionState &&
                        m_current.Parent.Parent is SequenceState)
                    {
                        m_current.Remove();
                        m_current = m_current.Parent; // Skip to the expression end
                    }
                    // Normal sequence start
                    else if (sequenceState.IsStart)
                    {
                        break;
                    }
                    // Normal sequence end
                    else if (sequenceState.IsEnd)
                    {
                        break;
                    }
                    else
                    {
                        UnexpectedState();
                    }
                }
                else if (m_current is InsertExpressionState insertExpressionState)
                {
                    // Mapping key, beginning an "insert" mapping insertion
                    // For example:
                    //   - job: a
                    //     variables:
                    //       ${{ insert }}: ${{ parameters.jobVariables }}
                    if (insertExpressionState.IsStart &&
                        m_current.Parent is MappingState parentMappingState &&
                        parentMappingState.IsKey)
                    {
                        if (expand)
                        {
                            StartMappingInsertion();
                        }
                        else
                        {
                            break;
                        }
                    }
                    // Expression end
                    else if (insertExpressionState.IsEnd)
                    {
                        EndExpression();
                    }
                    // Not allowed
                    else if (insertExpressionState.IsStart)
                    {
                        m_context.Error(insertExpressionState.Value, TemplateStrings.DirectiveNotAllowed(insertExpressionState.Value.Directive));
                        m_current.Remove();
                        m_current = insertExpressionState.ToStringToken();
                    }
                    else
                    {
                        UnexpectedState();
                    }
                }
                else
                {
                    UnexpectedState();
                }

                m_memory.IncrementEvents();
            } while (true);

            m_expanded = expand;
        }

        private void SequenceItemBasicExpression()
        {
            // The template looks like:
            //
            //   steps:
            //   - ${{ parameters.preSteps }}
            //   - script: build
            //
            // The current state looks like:
            //
            //   MappingState   // The document starts with a mapping
            //
            //   SequenceState  // The "steps" sequence
            //
            //   BasicExpressionState   // m_current

            var expressionState = m_current as BasicExpressionState;
            var expression = expressionState.Value;
            TemplateToken value;
            var removeBytes = 0;
            try
            {
                value = expression.EvaluateTemplateToken(expressionState.Context, out removeBytes);
            }
            catch (Exception ex)
            {
                m_context.Error(expression, ex);
                value = null;
            }

            // Move to the nested sequence, skip the sequence start
            if (value is SequenceToken nestedSequence)
            {
                m_current = expressionState.Next(nestedSequence, isSequenceInsertion: true, removeBytes: removeBytes);
            }
            // Move to the new value
            else if (value != null)
            {
                m_current = expressionState.Next(value, removeBytes);
            }
            // Move to the expression end
            else if (value == null)
            {
                expressionState.End();
            }
        }

        private void MappingKeyBasicExpression()
        {
            // The template looks like:
            //
            //   steps:
            //   - ${{ parameters.scriptHost }}: echo hi
            //
            // The current state looks like:
            //
            //   MappingState   // The document starts with a mapping
            //
            //   SequenceState  // The "steps" sequence
            //
            //   MappingState   // The step mapping
            //
            //   BasicExpressionState   // m_current

            // The expression should evaluate to a string
            var expressionState = m_current as BasicExpressionState;
            var expression = expressionState.Value as BasicExpressionToken;
            StringToken stringToken;
            var removeBytes = 0;
            try
            {
                stringToken = expression.EvaluateStringToken(expressionState.Context, out removeBytes);
            }
            catch (Exception ex)
            {
                m_context.Error(expression, ex);
                stringToken = null;
            }

            // Move to the stringToken
            if (stringToken != null)
            {
                m_current = expressionState.Next(stringToken, removeBytes);
            }
            // Move to the next key or mapping end
            else
            {
                m_current.Remove();
                var parentMappingState = m_current.Parent as MappingState;
                parentMappingState.Next().Remove(); // Skip the value
                m_current = parentMappingState.Next(); // Next key or mapping end
            }
        }

        private void MappingValueBasicExpression()
        {
            // The template looks like:
            //
            //   steps:
            //   - script: credScan
            //   - script: ${{ parameters.tool }}
            //
            // The current state looks like:
            //
            //   MappingState   // The document starts with a mapping
            //
            //   SequenceState  // The "steps" sequence
            //
            //   MappingState   // The step mapping
            //
            //   BasicExpressionState   // m_current

            var expressionState = m_current as BasicExpressionState;
            var expression = expressionState.Value;
            TemplateToken value;
            var removeBytes = 0;
            try
            {
                value = expression.EvaluateTemplateToken(expressionState.Context, out removeBytes);
            }
            catch (Exception ex)
            {
                m_context.Error(expression, ex);
                value = new StringToken(expression.FileId, expression.Line, expression.Column, String.Empty);
            }

            // Move to the new value
            m_current = expressionState.Next(value, removeBytes);
        }

        private void RootBasicExpression()
        {
            // The template looks like:
            //
            //   ${{ parameters.tool }}
            //
            // The current state looks like:
            //
            //   BasicExpressionState   // m_current

            var expressionState = m_current as BasicExpressionState;
            var expression = expressionState.Value;
            TemplateToken value;
            var removeBytes = 0;
            try
            {
                value = expression.EvaluateTemplateToken(expressionState.Context, out removeBytes);
            }
            catch (Exception ex)
            {
                m_context.Error(expression, ex);
                value = new StringToken(expression.FileId, expression.Line, expression.Column, String.Empty);
            }

            // Move to the new value
            m_current = expressionState.Next(value, removeBytes);
        }

        private void StartMappingInsertion()
        {
            // The template looks like:
            //
            //   jobs:
            //   - job: a
            //     variables:
            //       ${{ insert }}: ${{ parameters.jobVariables }}
            //
            // The current state looks like:
            //
            //   MappingState       // The document starts with a mapping
            //
            //   SequenceState      // The "jobs" sequence
            //
            //   MappingState       // The "job" mapping
            //
            //   MappingState       // The "variables" mapping
            //
            //   InsertExpressionState  // m_current

            var expressionState = m_current as InsertExpressionState;
            var parentMappingState = expressionState.Parent as MappingState;
            var nestedValue = parentMappingState.Value[parentMappingState.Index].Value;
            var nestedMapping = nestedValue as MappingToken;
            var removeBytes = 0;
            if (nestedMapping != null)
            {
                // Intentionally empty
            }
            else if (nestedValue is BasicExpressionToken basicExpression)
            {
                // The expression should evaluate to a mapping
                try
                {
                    nestedMapping = basicExpression.EvaluateMappingToken(expressionState.Context, out removeBytes);
                }
                catch (Exception ex)
                {
                    m_context.Error(basicExpression, ex);
                    nestedMapping = null;
                }
            }
            else
            {
                m_context.Error(nestedValue, TemplateStrings.ExpectedMapping());
                nestedMapping = null;
            }

            // Move to the nested first key
            if (nestedMapping?.Count > 0)
            {
                m_current = expressionState.Next(nestedMapping, removeBytes);
            }
            // Move to the expression end
            else
            {
                if (removeBytes > 0)
                {
                    m_memory.SubtractBytes(removeBytes);
                }

                expressionState.End();
            }
        }

        private void EndExpression()
        {
            // End of document
            if (m_current.Parent == null)
            {
                m_current.Remove();
                m_current = null;
            }
            // End basic expression
            else if (m_current is BasicExpressionState)
            {
                // Move to the next item or sequence end
                if (m_current.Parent is SequenceState parentSequenceState)
                {
                    m_current.Remove();
                    m_current = parentSequenceState.Next();
                }
                // Move to the next key, next value, or mapping end
                else
                {
                    m_current.Remove();
                    var parentMappingState = m_current.Parent as MappingState;
                    m_current = parentMappingState.Next();
                }
            }
            // End "insert" mapping insertion
            else
            {
                // Move to the next key or mapping end
                m_current.Remove();
                var parentMappingState = m_current.Parent as MappingState;
                parentMappingState.Next().Remove(); // Skip the value
                m_current = parentMappingState.Next();
            }
        }

        private void UnexpectedState()
        {
            throw new InvalidOperationException("Expected state while unraveling expressions. " + DumpState());
        }

        private abstract class ReaderState
        {
            public ReaderState(
                ReaderState parent,
                TemplateToken value,
                TemplateContext context)
            {
                Parent = parent;
                Value = value;
                Context = context;
            }

            public static ReaderState CreateState(
                ReaderState parent,
                TemplateToken value,
                TemplateContext context,
                Int32 removeBytes = 0)
            {
                switch (value.Type)
                {
                    case TokenType.Null:
                    case TokenType.Boolean:
                    case TokenType.Number:
                    case TokenType.String:
                        return new LiteralState(parent, value as LiteralToken, context, removeBytes);

                    case TokenType.Sequence:
                        return new SequenceState(parent, value as SequenceToken, context, removeBytes);

                    case TokenType.Mapping:
                        return new MappingState(parent, value as MappingToken, context, removeBytes);

                    case TokenType.BasicExpression:
                        return new BasicExpressionState(parent, value as BasicExpressionToken, context, removeBytes);

                    case TokenType.InsertExpression:
                        if (removeBytes > 0)
                        {
                            throw new InvalidOperationException($"Unexpected {nameof(removeBytes)}");
                        }

                        return new InsertExpressionState(parent, value as InsertExpressionToken, context);

                    default:
                        throw new NotSupportedException($"Unexpected {nameof(ReaderState)} type: {value?.GetType().Name}");
                }
            }

            public ReaderState Parent { get; }
            public TemplateContext Context { get; protected set; }
            public TemplateToken Value { get; }

            public abstract void Remove();
        }

        private abstract class ReaderState<T> : ReaderState
            where T : class
        {
            public ReaderState(
                ReaderState parent,
                TemplateToken value,
                TemplateContext context)
                : base(parent, value, context)
            {
            }

            public new T Value
            {
                get
                {
                    if (!Object.ReferenceEquals(base.Value, m_value))
                    {
                        m_value = base.Value as T;
                    }

                    return m_value;
                }
            }

            private T m_value;
        }

        private sealed class LiteralState : ReaderState<LiteralToken>
        {
            public LiteralState(
                ReaderState parent,
                LiteralToken literal,
                TemplateContext context,
                Int32 removeBytes)
                : base(parent, literal, context)
            {
                context.Memory.AddBytes(literal);
                context.Memory.IncrementDepth();
                m_removeBytes = removeBytes;
            }

            public override void Remove()
            {
                Context.Memory.SubtractBytes(Value);
                Context.Memory.DecrementDepth();

                // Subtract the memory overhead of the template token.
                // We are now done traversing it and pointers to it no longer need to exist.
                if (m_removeBytes > 0)
                {
                    Context.Memory.SubtractBytes(m_removeBytes);
                }
            }

            public override String ToString()
            {
                var result = new StringBuilder();
                result.AppendLine($"{GetType().Name}");
                return result.ToString();
            }

            private Int32 m_removeBytes;
        }

        private sealed class SequenceState : ReaderState<SequenceToken>
        {
            public SequenceState(
                ReaderState parent,
                SequenceToken sequence,
                TemplateContext context,
                Int32 removeBytes)
                : base(parent, sequence, context)
            {
                context.Memory.AddBytes(sequence);
                context.Memory.IncrementDepth();
                m_removeBytes = removeBytes;
            }

            /// <summary>
            /// Indicates whether the state represents the sequence-start event
            /// </summary>
            public Boolean IsStart { get; private set; } = true;

            /// <summary>
            /// The current index within the sequence
            /// </summary>
            public Int32 Index { get; private set; }

            /// <summary>
            /// Indicates whether the state represents the sequence-end event
            /// </summary>
            public Boolean IsEnd => !IsStart && Index >= Value.Count;

            public ReaderState Next()
            {
                // Adjust the state
                if (IsStart)
                {
                    IsStart = false;
                }
                else
                {
                    Index++;
                }

                // Return the next event
                if (!IsEnd)
                {
                    return CreateState(this, Value[Index], Context);
                }
                else
                {
                    return this;
                }
            }

            public override void Remove()
            {
                Context.Memory.SubtractBytes(Value);
                Context.Memory.DecrementDepth();

                // Subtract the memory overhead of the template token.
                // We are now done traversing it and pointers to it no longer need to exist.
                if (m_removeBytes > 0)
                {
                    Context.Memory.SubtractBytes(m_removeBytes);
                }
            }

            public override String ToString()
            {
                var result = new StringBuilder();
                result.AppendLine($"{GetType().Name}:");
                result.AppendLine($"  IsStart: {IsStart}");
                result.AppendLine($"  Index: {Index}");
                result.AppendLine($"  IsEnd: {IsEnd}");
                return result.ToString();
            }

            private Int32 m_removeBytes;
        }

        private sealed class MappingState : ReaderState<MappingToken>
        {
            public MappingState(
                ReaderState parent,
                MappingToken mapping,
                TemplateContext context,
                Int32 removeBytes)
                : base(parent, mapping, context)
            {
                context.Memory.AddBytes(mapping);
                context.Memory.IncrementDepth();
                m_removeBytes = removeBytes;
            }

            /// <summary>
            /// Indicates whether the state represents the mapping-start event
            /// </summary>
            public Boolean IsStart { get; private set; } = true;

            /// <summary>
            /// The current index within the mapping
            /// </summary>
            public Int32 Index { get; private set; }

            /// <summary>
            /// Indicates whether the state represents a mapping-key position
            /// </summary>
            public Boolean IsKey { get; private set; }

            /// <summary>
            /// Indicates whether the state represents the mapping-end event
            /// </summary>
            public Boolean IsEnd => !IsStart && Index >= Value.Count;

            public ReaderState Next()
            {
                // Adjust the state
                if (IsStart)
                {
                    IsStart = false;
                    IsKey = true;
                }
                else if (IsKey)
                {
                    IsKey = false;
                }
                else
                {
                    Index++;
                    IsKey = true;
                }

                // Return the next event
                if (!IsEnd)
                {
                    if (IsKey)
                    {
                        return CreateState(this, Value[Index].Key, Context);
                    }
                    else
                    {
                        return CreateState(this, Value[Index].Value, Context);
                    }
                }
                else
                {
                    return this;
                }
            }

            public override void Remove()
            {
                Context.Memory.SubtractBytes(Value);
                Context.Memory.DecrementDepth();

                // Subtract the memory overhead of the template token.
                // We are now done traversing it and pointers to it no longer need to exist.
                if (m_removeBytes > 0)
                {
                    Context.Memory.SubtractBytes(m_removeBytes);
                }
            }

            public override String ToString()
            {
                var result = new StringBuilder();
                result.AppendLine($"{GetType().Name}:");
                result.AppendLine($"  IsStart: {IsStart}");
                result.AppendLine($"  Index: {Index}");
                result.AppendLine($"  IsKey: {IsKey}");
                result.AppendLine($"  IsEnd: {IsEnd}");
                return result.ToString();
            }

            private Int32 m_removeBytes;
        }

        private sealed class BasicExpressionState : ReaderState<BasicExpressionToken>
        {
            public BasicExpressionState(
                ReaderState parent,
                BasicExpressionToken expression,
                TemplateContext context,
                Int32 removeBytes)
                : base(parent, expression, context)
            {
                context.Memory.AddBytes(expression);
                context.Memory.IncrementDepth();
                m_removeBytes = removeBytes;
            }

            /// <summary>
            /// Indicates whether entering the expression
            /// </summary>
            public Boolean IsStart { get; private set; } = true;

            /// <summary>
            /// Indicates whether leaving the expression
            /// </summary>
            public Boolean IsEnd => !IsStart;

            public ReaderState Next(
                TemplateToken value,
                Int32 removeBytes = 0)
            {
                // Adjust the state
                IsStart = false;

                // Return the nested state
                return CreateState(this, value, Context, removeBytes);
            }

            public ReaderState Next(
                SequenceToken value,
                Boolean isSequenceInsertion = false,
                Int32 removeBytes = 0)
            {
                // Adjust the state
                IsStart = false;

                // Create the nested state
                var nestedState = CreateState(this, value, Context, removeBytes);
                if (isSequenceInsertion)
                {
                    var nestedSequenceState = nestedState as SequenceState;
                    return nestedSequenceState.Next(); // Skip the sequence start
                }
                else
                {
                    return nestedState;
                }
            }

            public ReaderState End()
            {
                IsStart = false;
                return this;
            }

            public override void Remove()
            {
                Context.Memory.SubtractBytes(Value);
                Context.Memory.DecrementDepth();

                // Subtract the memory overhead of the template token.
                // We are now done traversing it and pointers to it no longer need to exist.
                if (m_removeBytes > 0)
                {
                    Context.Memory.SubtractBytes(m_removeBytes);
                }
            }

            public override String ToString()
            {
                var result = new StringBuilder();
                result.AppendLine($"{GetType().Name}:");
                result.AppendLine($"  IsStart: {IsStart}");
                return result.ToString();
            }

            private Int32 m_removeBytes;
        }

        private sealed class InsertExpressionState : ReaderState<InsertExpressionToken>
        {
            public InsertExpressionState(
                ReaderState parent,
                InsertExpressionToken expression,
                TemplateContext context)
                : base(parent, expression, context)
            {
                Context.Memory.AddBytes(expression);
                Context.Memory.IncrementDepth();
            }

            /// <summary>
            /// Indicates whether entering or leaving the expression
            /// </summary>
            public Boolean IsStart { get; private set; } = true;

            /// <summary>
            /// Indicates whether leaving the expression
            /// </summary>
            public Boolean IsEnd => !IsStart;

            public ReaderState Next(
                MappingToken value,
                Int32 removeBytes = 0)
            {
                // Adjust the state
                IsStart = false;

                // Create the nested state
                var nestedState = CreateState(this, value, Context, removeBytes) as MappingState;
                return nestedState.Next(); // Skip the mapping start
            }

            public ReaderState End()
            {
                IsStart = false;
                return this;
            }

            /// <summary>
            /// This happens when the expression is not allowed
            /// </summary>
            public ReaderState ToStringToken()
            {
                var literal = new StringToken(Value.FileId, Value.Line, Value.Column, $"{TemplateConstants.OpenExpression} {Value.Directive} {TemplateConstants.CloseExpression}");
                return CreateState(Parent, literal, Context);
            }

            public override void Remove()
            {
                Context.Memory.SubtractBytes(Value);
                Context.Memory.DecrementDepth();
            }

            public override String ToString()
            {
                var result = new StringBuilder();
                result.AppendLine($"{GetType().Name}:");
                result.AppendLine($"  IsStart: {IsStart}");
                return result.ToString();
            }
        }

        private readonly TemplateContext m_context;
        private readonly TemplateMemory m_memory;
        private ReaderState m_current;
        private Boolean m_expanded;
    }
}