using System;
using System.Linq;
using System.Collections.Generic;
using System.Text.RegularExpressions;

namespace Dacryptero.Search.Parser
{
    public class Lexer
    {
        const string escapedStringFetch = StringTools.EscapingPattern;

        private static List<(TokenType[], Regex)> Rules = new List<(TokenType[], Regex)>
        {
            (new[] {TokenType.Negation}, new Regex("(!|-)", RegexOptions.Compiled)),
            (new[] {TokenType.Operator}, new Regex("(&&|\\|\\||&|\\||\\^)", RegexOptions.Compiled)),
            (new[] {TokenType.GroupStart}, new Regex("(\\()", RegexOptions.Compiled)),
            (new[] {TokenType.GroupEnd}, new Regex("(\\))", RegexOptions.Compiled)),
            (new[] {TokenType.OptionName, TokenType.OptionValue}, new Regex($"(\\w+):({escapedStringFetch}*)", RegexOptions.Compiled)),
            (new[] {TokenType.Word}, new Regex($"({escapedStringFetch}+)", RegexOptions.Compiled)),
            (new TokenType[0], new Regex("\\s*", RegexOptions.Compiled)),
        };

        public string Source { get; }

        public Lexer(string source)
        {
            Source = source;
        }

        public IEnumerable<Token> Tokenize()
        {
            for (int i = 0; i < Source.Length; )
            {
                // we are at the beginning of the next token
                (TokenType[] types, Match match)? best = null;
                foreach (var (types, rule) in Rules)
                {
                    var match = rule.Match(Source, i);
                    if (!match.Success)
                        continue;
                    if (best is null || best.Value.match.Index > match.Index)
                        best = (types, match);
                }
                if (best is null)
                {
                    ++i;
                    continue;
                }
                for (int g = 0; g < best.Value.types.Length; ++g)
                {
                    var group = best.Value.match.Groups[g + 1];
                    yield return new Token(
                        new Range(group.Index, group.Index + group.Length),
                        best.Value.types[g]
                    );
                }
                i = best.Value.match.Index + best.Value.match.Length;
            }
        }
    }
}