﻿// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System;
using Roslyn.Utilities;

namespace Microsoft.CodeAnalysis.Syntax.InternalSyntax
{
    // Avoid implementing IEnumerable so we do not get any unintentional boxing.
    internal readonly struct SyntaxDiagnosticInfoList
    {
        private readonly GreenNode _node;

        internal SyntaxDiagnosticInfoList(GreenNode node)
        {
            _node = node;
        }

        public Enumerator GetEnumerator()
        {
            return new Enumerator(_node);
        }

        internal bool Any(Func<DiagnosticInfo, bool> predicate)
        {
            var enumerator = GetEnumerator();
            while (enumerator.MoveNext())
            {
                if (predicate(enumerator.Current))
                    return true;
            }

            return false;
        }

        public struct Enumerator
        {
            private struct NodeIteration
            {
                internal readonly GreenNode Node;
                internal int DiagnosticIndex;
                internal int SlotIndex;

                internal NodeIteration(GreenNode node)
                {
                    this.Node = node;
                    this.SlotIndex = -1;
                    this.DiagnosticIndex = -1;
                }
            }

            private NodeIteration[]? _stack;
            private int _count;

            public DiagnosticInfo Current { get; private set; }

            internal Enumerator(GreenNode node)
            {
                Current = null!;
                _stack = null;
                _count = 0;
                if (node != null && node.ContainsDiagnostics)
                {
                    _stack = new NodeIteration[8];
                    this.PushNodeOrToken(node);
                }
            }

            public bool MoveNext()
            {
                while (_count > 0)
                {
                    var diagIndex = _stack![_count - 1].DiagnosticIndex;
                    var node = _stack[_count - 1].Node;
                    var diags = node.GetDiagnostics();
                    if (diagIndex < diags.Length - 1)
                    {
                        diagIndex++;
                        Current = diags[diagIndex];
                        _stack[_count - 1].DiagnosticIndex = diagIndex;
                        return true;
                    }

                    var slotIndex = _stack[_count - 1].SlotIndex;
tryAgain:
                    if (slotIndex < node.SlotCount - 1)
                    {
                        slotIndex++;
                        var child = node.GetSlot(slotIndex);
                        if (child == null || !child.ContainsDiagnostics)
                        {
                            goto tryAgain;
                        }

                        _stack[_count - 1].SlotIndex = slotIndex;
                        this.PushNodeOrToken(child);
                    }
                    else
                    {
                        this.Pop();
                    }
                }

                return false;
            }

            private void PushNodeOrToken(GreenNode node)
            {
                if (node.IsToken)
                {
                    this.PushToken(node);
                }
                else
                {
                    this.Push(node);
                }
            }

            private void PushToken(GreenNode token)
            {
                var trailing = token.GetTrailingTriviaCore();
                if (trailing != null)
                {
                    this.Push(trailing);
                }

                this.Push(token);
                var leading = token.GetLeadingTriviaCore();
                if (leading != null)
                {
                    this.Push(leading);
                }
            }

            private void Push(GreenNode node)
            {
                RoslynDebug.Assert(_stack is object);
                if (_count >= _stack.Length)
                {
                    var tmp = new NodeIteration[_stack.Length * 2];
                    Array.Copy(_stack, tmp, _stack.Length);
                    _stack = tmp;
                }

                _stack[_count] = new NodeIteration(node);
                _count++;
            }

            private void Pop()
            {
                _count--;
            }
        }
    }
}
