/*
 * MultiInitialStateNFA.h
 *
 * Created on: Sep 26, 2017
 * Author: Jan Travnicek
 */

#ifndef _STRING_MULTI_INITIAL_STATE_NFA_H_
#define _STRING_MULTI_INITIAL_STATE_NFA_H_

#include <automaton/FSM/MultiInitialStateNFA.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::MultiInitialStateNFA < SymbolType, StateType > > {
	static automaton::MultiInitialStateNFA < SymbolType, StateType > parse ( std::istream & input );
	static bool first ( std::istream & input );
	static void compose ( std::ostream & output, const automaton::MultiInitialStateNFA < SymbolType, StateType > & automaton );
private:
	static void parseTransition(std::istream& input, automaton::MultiInitialStateNFA < SymbolType, StateType > & res, const ext::vector<SymbolType>& symbols);
	static void composeTransitionsFromState(std::ostream& output, const automaton::MultiInitialStateNFA < SymbolType, StateType > & automaton, const StateType & from);
};

template<class SymbolType, class StateType >
automaton::MultiInitialStateNFA < SymbolType, StateType > stringApi < automaton::MultiInitialStateNFA < SymbolType, StateType > >::parse ( std::istream & input ) {
	automaton::MultiInitialStateNFA < > 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_NFA) {
		throw exception::CommonException("Unrecognised MISNFA token.");
	}
	ext::vector<SymbolType> symbols;

	token = automaton::AutomatonFromStringLexer::next(input);
	while(token.type != automaton::AutomatonFromStringLexer::TokenType::NEW_LINE && token.type != automaton::AutomatonFromStringLexer::TokenType::TEOF) {
		automaton::AutomatonFromStringLexer::putback(input, token);
		SymbolType symbol = core::stringApi<SymbolType>::parse(input);
		res.addInputSymbol(symbol);
		symbols.push_back(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::MultiInitialStateNFA < SymbolType, StateType > >::parseTransition(std::istream& input, automaton::MultiInitialStateNFA < SymbolType, StateType > & res, const ext::vector<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<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::MultiInitialStateNFA < SymbolType, StateType > >::first ( std::istream & input ) {
	return automaton::AutomatonFromStringLexer::peek ( input ).type == automaton::AutomatonFromStringLexer::TokenType::MULTI_INITIAL_STATE_NFA;
}

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

	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::MultiInitialStateNFA < SymbolType, StateType > >::composeTransitionsFromState(std::ostream& output, const automaton::MultiInitialStateNFA < SymbolType, StateType > & automaton, const StateType & from) {
	ext::multimap<ext::pair<StateType, SymbolType>, StateType > symbolTransitionsFromState = automaton.getTransitionsFromState(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;
			}
		}
	}
}

} /* namespace core */

#endif /* _STRING_MULTI_INITIAL_STATE_NFA_H_ */
