using System;
using System.Collections.Generic;
using System.Linq;

namespace Dacryptero.Search.Parser
{
    public class Grouper
    {
        public interface IEntry {}

        public class TokenEntry : IEntry
        {
            public Token Token { get; }

            public TokenEntry(Token token)
                => Token = token;
        }

        public class Group : IEntry
        {
            public string? Operator { get; set; }

            public List<IEntry> Entries { get; set; } = new List<IEntry>();

            public Group(string? @operator)
            {
                Operator = @operator;
            }
        }

        private static ReadOnlyMemory<string> OperatorOrder = new []
        {
            "|", "||",
            "&", "&&",
            "^",
        };

        public Group GroupTokens(string source, IEnumerable<Token> tokens)
        {
            var root = new Group(null);
            Stack<Group> stack = new Stack<Group>();
            var current = root;

            foreach (var token in tokens)
            {
                switch (token.Type)
                {
                    case TokenType.GroupStart:
                        var old = current;
                        stack.Push(current);
                        current = new Group(null);
                        old.Entries.Add(current);
                        break;
                    case TokenType.GroupEnd:
                        if (stack.Count > 0)
                        {
                            GroupByOperator(source, current, OperatorOrder);
                            current = stack.Pop();
                        }
                        break;
                    default:
                        current.Entries.Add(new TokenEntry(token));
                        break;
                }
            }
            GroupByOperator(source, root, OperatorOrder);
            return root;
        }

        private void GroupByOperator(string source, Group group, ReadOnlyMemory<string> operators)
        {
            if (operators.Length == 0)
            {
                // no operations left, this transformation is finished
                return;
            }
            var @operator = operators.Span[0];
            // check if this operator has to be transformed
            var hasAnyWithThis = group.Entries.Any(
                x => x is TokenEntry t && t.Token.Type == TokenType.Operator
                    && source[t.Token.Range] == @operator
            );
            if (!hasAnyWithThis)
            {
                GroupByOperator(source, group, operators[1..]);
                return;
            }
            // build up new list
            group.Operator = @operator;
            var list = new List<IEntry>();
            var current = new Group(null);
            list.Add(current);
            foreach (var entry in group.Entries)
            {
                var isOperator = entry is TokenEntry t && t.Token.Type == TokenType.Operator
                    && source[t.Token.Range] == @operator;
                if (isOperator)
                {
                    if (current.Entries.Count > 0)
                        GroupByOperator(source, current, operators[1..]);
                    current = new Group(null);
                    list.Add(current);
                }
                else
                {
                    current.Entries.Add(entry);
                }
            }
            if (current.Entries.Count > 0)
                GroupByOperator(source, current, operators[1..]);
            // replace lists
            group.Entries = list;
        }
    }
}