﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using ExpressionEvaluator.Parsers.Parsing.AbstractSyntaxTree;
using ExpressionEvaluator.Parsers.Parsing.GrammarRules;
using ExpressionEvaluator.Parsers.Parsing.GrammarRules.Symbols;

namespace ExpressionEvaluator.Parsers.Parsing.SRLParsing.NFA
{
    [DebuggerDisplay("{" + nameof(Description) + "}")]
    public class Item : IState
    {
        private readonly int _nextSymbolIndex;
        private readonly List<Symbol> _rightHandSide;
        public HashSet<Transition<Item>> Transitions { get; }

        public Item(NonTerminal leftHandSide, List<Symbol> rightHandSide, int nextSymbolIndex)
        {
            LeftHandSide = leftHandSide;
            _rightHandSide = rightHandSide;
            _nextSymbolIndex = nextSymbolIndex;

            Transitions = new HashSet<Transition<Item>>();
        }

        public NonTerminal LeftHandSide { get; }

        public bool IsHandle => _nextSymbolIndex == _rightHandSide.Count;

        private Symbol NextSymbol => IsHandle ? null : _rightHandSide[_nextSymbolIndex];

        public string Description
        {
            get
            {
                string rightHandSide = _rightHandSide
                        .Select((symbol, index) => new {Symbol = symbol, Index = index})
                        .Aggregate("", (seed, s) => seed + (s.Index == _nextSymbolIndex ? "." : "") + s.Symbol.Description);

                if (!rightHandSide.Contains("."))
                    rightHandSide += ".";

                return LeftHandSide.Description + " => " + rightHandSide;
            }
        }

        public void ProduceTransitions(NonDeterministicFiniteStateMachine automaton)
        {
            if (IsHandle)
                return;

            var nextItem = new Item(LeftHandSide, _rightHandSide, _nextSymbolIndex + 1);

            Transitions.Add(new Transition<Item>(NextSymbol, nextItem));

            if (NextSymbol is NonTerminal nonTerminal)
                automaton.AddEpsilonTransitions(nonTerminal, this);

            automaton.AddState(nextItem);
        }

        public void AddEpsilonTransition(Item destination)
        {
            Transitions.Add(new EpsilonTransition(destination));
        }

        #region Equality (for the set of states)

        private bool Equals(Item other)
        {
            return string.Equals(Description, other.Description);
        }

        public override bool Equals(object obj)
        {
            if (ReferenceEquals(null, obj)) return false;
            if (ReferenceEquals(this, obj)) return true;
            if (obj.GetType() != GetType()) return false;
            return Equals((Item)obj);
        }

        public override int GetHashCode()
        {
            return Description.GetHashCode();
        }

        #endregion

        public void Reduce(IGrammar grammar, Stack<Symbol> symbols, Stack<AbstractSyntaxTreeNode> nodes, bool createExpressions = true)
        {
            if (!IsHandle)
                throw new Exception("Non-handle has been asked to reduce!");

            foreach (var symbol in _rightHandSide.AsEnumerable().Reverse())
            {
                var terminal = PopAndCheckType(symbols, symbol) as Terminal;

                if (createExpressions && terminal != null && !terminal.GetIsLeaf(grammar))
                {
                    var node = new AbstractSyntaxTreeNode(LeftHandSide, _rightHandSide);
                    var numberOfTerminals = _rightHandSide.OfType<Terminal>().Count();
                    for (int i = 0; i < _rightHandSide.Count - numberOfTerminals; i++)
                        node.Children.Insert(0, nodes.Pop());

#warning fix token position
                    node.SetInputTokens(new List<PositionedToken> { new PositionedToken(terminal.Token, -1) });

                    nodes.Push(node);
                }
            }

            symbols.Push(LeftHandSide);
        }

        private static Symbol PopAndCheckType(Stack<Symbol> symbols, Symbol symbol)
        {
            var topOfStack = symbols.Pop();
            if (topOfStack.GetType() != symbol.GetType())
                throw new Exception("Expected symbol of type '" + symbol.GetType().Name + "' on top of stack.");
            return topOfStack;
        }
    }
}