// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

#nullable disable

using System.Collections;
using System.Diagnostics;

namespace Microsoft.AspNetCore.Routing.Tree;

internal struct TreeEnumerator : IEnumerator<UrlMatchingNode>
{
    private readonly Stack<UrlMatchingNode> _stack;
    private readonly PathTokenizer _tokenizer;

    public TreeEnumerator(UrlMatchingNode root, PathTokenizer tokenizer)
    {
        _stack = new Stack<UrlMatchingNode>();
        _tokenizer = tokenizer;
        Current = null;

        _stack.Push(root);
    }

    public UrlMatchingNode Current { get; private set; }

    object IEnumerator.Current => Current;

    public void Dispose()
    {
    }

    public bool MoveNext()
    {
        if (_stack == null)
        {
            return false;
        }

        while (_stack.Count > 0)
        {
            var next = _stack.Pop();

            // In case of wild card segment, the request path segment length can be greater
            // Example:
            // Template:    a/{*path}
            // Request Url: a/b/c/d
            if (next.IsCatchAll && next.Matches.Count > 0)
            {
                Current = next;
                return true;
            }
            // Next template has the same length as the url we are trying to match
            // The only possible matching segments are either our current matches or
            // any catch-all segment after this segment in which the catch all is empty.
            else if (next.Depth == _tokenizer.Count)
            {
                if (next.Matches.Count > 0)
                {
                    Current = next;
                    return true;
                }
                else
                {
                    // We can stop looking as any other child node from this node will be
                    // either a literal, a constrained parameter or a parameter.
                    // (Catch alls and constrained catch alls will show up as candidate matches).
                    continue;
                }
            }

            if (next.CatchAlls != null)
            {
                _stack.Push(next.CatchAlls);
            }

            if (next.ConstrainedCatchAlls != null)
            {
                _stack.Push(next.ConstrainedCatchAlls);
            }

            if (next.Parameters != null)
            {
                _stack.Push(next.Parameters);
            }

            if (next.ConstrainedParameters != null)
            {
                _stack.Push(next.ConstrainedParameters);
            }

            if (next.Literals.Count > 0)
            {
                Debug.Assert(next.Depth < _tokenizer.Count);
                if (next.Literals.TryGetValue(_tokenizer[next.Depth].Value, out var node))
                {
                    _stack.Push(node);
                }
            }
        }

        return false;
    }

    public void Reset()
    {
        _stack.Clear();
        Current = null;
    }
}
