/*
 * MultiInitialStateEpsilonNFA.cpp
 *
 * Created on: Mar 17, 2020
 * Author: Tomas Pecka
 */

#pragma once

#include <automaton/FSM/MultiInitialStateEpsilonNFA.h>
#include <core/stringApi.hpp>

#include <automaton/AutomatonFromStringLexer.h>

#include <automaton/string/common/AutomatonFromStringParserCommon.h>

namespace core {

template<class SymbolType, class StateType >
struct stringApi < automaton::MultiInitialStateEpsilonNFA < SymbolType, StateType > > {
	static automaton::MultiInitialStateEpsilonNFA < SymbolType, StateType > parse ( std::istream & input );
	static bool first ( std::istream & input );
	static void compose ( std::ostream & output, const automaton::MultiInitialStateEpsilonNFA < SymbolType, StateType > & automaton );
private:
	static void parseTransition(std::istream& input, automaton::MultiInitialStateEpsilonNFA < SymbolType, StateType > & res, const ext::vector < common::symbol_or_epsilon < SymbolType > >& symbols);
	static void composeTransitionsFromState(std::ostream& output, const automaton::MultiInitialStateEpsilonNFA < SymbolType, StateType > & automaton, const StateType & from);
};

template<class SymbolType, class StateType >
automaton::MultiInitialStateEpsilonNFA < SymbolType, StateType > stringApi < automaton::MultiInitialStateEpsilonNFA < SymbolType, StateType > >::parse ( std::istream & input ) {
	automaton::MultiInitialStateEpsilonNFA < > res;

	automaton::AutomatonFromStringLexer::Token token = automaton::AutomatonFromStringLexer::next(input);

	while(token.type == automaton::AutomatonFromStringLexer::TokenType::NEW_LINE) {
		token = automaton::AutomatonFromStringLexer::next(input);
	}

	if(token.type != automaton::AutomatonFromStringLexer::TokenType::MULTI_INITIAL_STATE_EPSILON_NFA) {
		throw exception::CommonException("Unrecognised MISNFA token.");
	}
	ext::vector<common::symbol_or_epsilon < SymbolType > > symbols;

	token = automaton::AutomatonFromStringLexer::next(input);
	while(token.type != automaton::AutomatonFromStringLexer::TokenType::NEW_LINE && token.type != automaton::AutomatonFromStringLexer::TokenType::TEOF) {
		if ( token.type == automaton::AutomatonFromStringLexer::TokenType::EPSILON ) {
			symbols.push_back ( common::symbol_or_epsilon < SymbolType > ( ) );
		} else {
			automaton::AutomatonFromStringLexer::putback(input, token);
			SymbolType symbol = core::stringApi<SymbolType>::parse(input);

			common::symbol_or_epsilon < SymbolType > symbolVariant ( symbol );
			res.addInputSymbol(symbol);
			symbols.push_back ( common::symbol_or_epsilon < SymbolType > ( symbol ) );
		}

		token = automaton::AutomatonFromStringLexer::next(input);
	}

	while(token.type == automaton::AutomatonFromStringLexer::TokenType::NEW_LINE) {
		token = automaton::AutomatonFromStringLexer::next(input);
		if(token.type == automaton::AutomatonFromStringLexer::TokenType::TEOF)
			break;
		else if (token.type == automaton::AutomatonFromStringLexer::TokenType::NEW_LINE)
			continue;
		else
			automaton::AutomatonFromStringLexer::putback(input, token);

		parseTransition(input, res, symbols);
		token = automaton::AutomatonFromStringLexer::next(input);
	}

	if(token.type != automaton::AutomatonFromStringLexer::TokenType::TEOF)
		throw exception::CommonException("Extra data after the automaton.");

	return res;
}

template<class SymbolType, class StateType >
void stringApi < automaton::MultiInitialStateEpsilonNFA < SymbolType, StateType > >::parseTransition(std::istream& input, automaton::MultiInitialStateEpsilonNFA < SymbolType, StateType > & res, const ext::vector < common::symbol_or_epsilon < SymbolType > >& symbols) {
	bool initial = false;
	bool final = false;

	automaton::AutomatonFromStringParserCommon::initialFinalState(input, initial, final);

	StateType from = core::stringApi<StateType>::parse(input);
	res.addState(from);
	if(initial) res.addInitialState(from);
	if(final) res.addFinalState(from);

	automaton::AutomatonFromStringLexer::Token token = automaton::AutomatonFromStringLexer::next(input);
	typename ext::vector < common::symbol_or_epsilon < SymbolType > >::const_iterator iter = symbols.begin();

	while ( token.type != automaton::AutomatonFromStringLexer::TokenType::NEW_LINE && token.type != automaton::AutomatonFromStringLexer::TokenType::TEOF ) {
		if(iter == symbols.end())
			throw exception::CommonException("Invalid line format");

		if(token.type != automaton::AutomatonFromStringLexer::TokenType::NONE) {
			automaton::AutomatonFromStringLexer::putback(input, token);
			do {
				StateType to(core::stringApi<StateType>::parse(input));
				res.addState(to);
				res.addTransition(from, *iter, to);

				token = automaton::AutomatonFromStringLexer::next(input);
				if(token.type != automaton::AutomatonFromStringLexer::TokenType::SEPARATOR) break;
			} while(true);
		} else {
			token = automaton::AutomatonFromStringLexer::next(input);
		}
		++iter;
	}
	automaton::AutomatonFromStringLexer::putback(input, token);

	if(iter != symbols.end()) throw exception::CommonException("Invalid line format");
}

template<class SymbolType, class StateType >
bool stringApi < automaton::MultiInitialStateEpsilonNFA < SymbolType, StateType > >::first ( std::istream & input ) {
	return automaton::AutomatonFromStringLexer::peek ( input ).type == automaton::AutomatonFromStringLexer::TokenType::MULTI_INITIAL_STATE_EPSILON_NFA;
}

template<class SymbolType, class StateType >
void stringApi < automaton::MultiInitialStateEpsilonNFA < SymbolType, StateType > >::compose ( std::ostream & output, const automaton::MultiInitialStateEpsilonNFA < SymbolType, StateType > & automaton ) {
	output << "MISENFA";
	for(const auto& symbol : automaton.getInputAlphabet()) {
		output << " ";
		core::stringApi<SymbolType>::compose(output, symbol);
	}

	output << " #E";
	output << std::endl;

	for(const auto& state : automaton.getStates()) {
		if(automaton.getInitialStates().find(state) != automaton.getInitialStates().end()) {
			output << ">";
		}
		if(automaton.getFinalStates().find(state) != automaton.getFinalStates().end()) {
			output << "<";
		}
		core::stringApi<StateType>::compose(output, state);

		composeTransitionsFromState(output, automaton, state);

		output << std::endl;
	}
}

template < class SymbolType, class StateType >
void stringApi < automaton::MultiInitialStateEpsilonNFA < SymbolType, StateType > >::composeTransitionsFromState(std::ostream& output, const automaton::MultiInitialStateEpsilonNFA < SymbolType, StateType > & automaton, const StateType & from) {
	ext::multimap<ext::pair<StateType, SymbolType>, StateType > symbolTransitionsFromState = automaton.getSymbolTransitionsFromState(from);

	for(const SymbolType& inputSymbol : automaton.getInputAlphabet()) {
		const auto toStates = symbolTransitionsFromState.equal_range(ext::make_pair(from, inputSymbol));
		if ( toStates.empty ( ) ) {
			output << " -";
		} else {
			bool sign = false;
			for(const auto & transition : toStates ) {
				output << (sign ? "|" : " ");
				core::stringApi<StateType>::compose(output, transition.second);
				sign = true;
			}
		}
	}

	ext::multimap<StateType, StateType > epsilonTransitionsFromState = automaton.getEpsilonTransitionsFromState(from);
	if ( epsilonTransitionsFromState.empty ( ) ) {
		output << " -";
	} else {
		bool sign = false;
		for(const auto & transition : epsilonTransitionsFromState ) {
			output << (sign ? "|" : " ");
			core::stringApi<StateType>::compose(output, transition.second);
			sign = true;
		}
	}
}

} /* namespace core */

