using System;
using System.Collections.Generic;
using Dacryptero.Search.AST;

namespace Dacryptero.Search.Parser
{
    public class AstBuilder
    {
        public AstBase Build(string source, Grouper.Group group)
        {
            var col = GetCollection(group);
            for (int i = 0; i < group.Entries.Count; ++i)
            {
                var next = BuildNext(source, group, ref i);
                if (next is not null)
                    col.Items.Add(next);
            }
            return col;
        }

        private AstBase? BuildNext(string source, Grouper.Group group, ref int i)
        {
            if (i >= group.Entries.Count)
                return null;
            var entry = group.Entries[i];
            if (entry is Grouper.Group subGroup)
                return Build(source, subGroup);
            if (entry is not Grouper.TokenEntry token)
                return null;
            switch (token.Token.Type)
            {
                case TokenType.Word:
                    return GetWord(source, token.Token.Range);
                case TokenType.OptionName:
                    if (i + 1 < group.Entries.Count 
                        && group.Entries[i + 1] is Grouper.TokenEntry otherToken
                        && otherToken.Token.Type == TokenType.OptionValue)
                    {
                        var result = new Group();
                        var opt = GetOption(source, token.Token.Range, otherToken.Token.Range);
                        i++;
                        if (opt is not null)
                        {
                            result.Options.Add(opt);
                            return result;
                        }
                        else return result;
                    }
                    break;
                case TokenType.Negation:
                {
                    i++;
                    var next = BuildNext(source, group, ref i);
                    if (next is not null)
                        return new Negation(next);
                    else return null;
                }
            }
            return null;
        }

        private AstCollectionBase GetCollection(Grouper.Group group)
        {
            return group.Operator switch
            {
                "&" => new AndGroup(false),
                "&&" => new AndGroup(true),
                "|" => new OrGroup(false),
                "||" => new OrGroup(true),
                "^" => new XorGroup(),
                _ => new Group(),
            };
        }
    
        private Word GetWord(string source, Range range)
        {
            return new Word(StringTools.UnescapeText(source[range]));
        }

        private static StringTools.TwoOpHandler opParser = StringTools.SplitTwoOp(
            "@=|\\$=|\\^=|~=|<=|<|>=|>|="
        );

        private static StringTools.ListOpHandler sortParser = StringTools.SplitList("\\:", false);

        private OptionBase? GetOption(string source, Range name, Range value)
        {
            switch (source[name].ToLowerInvariant())
            {
                case "attr":
                {
                    var result = opParser(source[value]);
                    return new AttributeOption(
                        StringTools.UnescapeText(result.key), 
                        GetFieldComparer(result.v?.op) ?? FieldComparer.Equality,
                        result.v is null ? null :
                            StringTools.UnescapeText(result.v.Value.value)
                    );
                }
                case "field":
                {
                    var result = opParser(source[value]);
                    return new FieldOption(
                        StringTools.UnescapeText(result.key), 
                        GetFieldComparer(result.v?.op) ?? FieldComparer.Equality,
                        result.v is null ? null :
                            StringTools.UnescapeText(result.v.Value.value)
                    );
                }
                case "type":
                {
                    var result = GetResultType(StringTools.UnescapeText(source[value]));
                    return result is null ? null :
                        new TypeOption(result.Value);
                }
                case "role":
                {
                    return new RoleOption(StringTools.UnescapeText(source[value]));
                }
                case "sort":
                {
                    var result = sortParser(source[value]);
                    return GetSort(result.Span);
                }
                default: return null;
            }
        }

        private FieldComparer? GetFieldComparer(string? op)
        {
            return op is null ? null : StringTools.UnescapeText(op) switch
            {
                "=" => FieldComparer.Equality,
                "<=" => FieldComparer.LowerOrEqual,
                "<" => FieldComparer.Lower,
                ">=" => FieldComparer.HigherOrEqual,
                ">" => FieldComparer.Higher,
                "~=" => FieldComparer.Contains,
                "^=" => FieldComparer.StartWith,
                "$=" => FieldComparer.EndsWith,
                "@=" => FieldComparer.Regex,
                _ => null,
            };
        }
    
        private SearchResultType? GetResultType(string type)
        {
            return type.ToLowerInvariant() switch
            {
                "interview" => SearchResultType.Interview,
                "person" => SearchResultType.Person,
                "file" => SearchResultType.File,
                _ => null,
            };
        }

        private SortOption? GetSort(ReadOnlySpan<string> parts)
        {
            if (parts.Length == 0)
                return null;
            var target = StringTools.UnescapeText(parts[0]).ToLowerInvariant() switch
            {
                "score" => SortOption.SortTarget.Score,
                "attr" => SortOption.SortTarget.Attribute,
                "field" => SortOption.SortTarget.Field,
                "none" => SortOption.SortTarget.None,
                _ => (SortOption.SortTarget?)null,
            };
            if (target is null)
                return null;
            
            switch (target)
            {
                case SortOption.SortTarget.Score:
                    return new SortOption(target.Value, null,
                        GetSortDirection(parts, 1) ?? SortOption.SortDirection.Descending
                    );
                case SortOption.SortTarget.None:
                    return new SortOption(target.Value, null, SortOption.SortDirection.Ascending);
                case SortOption.SortTarget.Attribute:
                case SortOption.SortTarget.Field:
                    return new SortOption(target.Value,
                        parts.Length >= 1 ? StringTools.UnescapeText(parts[1]) : null,
                        GetSortDirection(parts, 2) ?? SortOption.SortDirection.Ascending
                    );
                default: return null;
            }
        }

        private SortOption.SortDirection? GetSortDirection(ReadOnlySpan<string> parts, int index)
        {
            if (parts.Length <= index)
                return null;
            return StringTools.UnescapeText(parts[index]).ToLowerInvariant() switch
            {
                "asc" => SortOption.SortDirection.Ascending,
                "desc" => SortOption.SortDirection.Descending,
                _ => null,
            };
        }
    }
}