/*
 * UnboundedRegExp.h
 *
 * Created on: Sep 27, 2017
 * Author: Jan Travnicek
 */

#ifndef _STRING_UNBOUNDED_REG_EXP_H_
#define _STRING_UNBOUNDED_REG_EXP_H_

#include <alib/memory>
#include <alib/utility>

#include <regexp/unbounded/UnboundedRegExpElements.h>
#include <regexp/unbounded/UnboundedRegExpStructure.h>
#include <regexp/unbounded/UnboundedRegExp.h>
#include <core/stringApi.hpp>

#include <regexp/RegExpFromStringLexer.h>

namespace core {

template<class SymbolType >
struct stringApi < regexp::UnboundedRegExpStructure < SymbolType > > {
	static regexp::UnboundedRegExpStructure < SymbolType > parse ( std::istream & input );
	static bool first ( std::istream & input );
	static void compose ( std::ostream & output, const regexp::UnboundedRegExpStructure < SymbolType > & regexp );
private:
	static ext::ptr_value < regexp::UnboundedRegExpElement < SymbolType > > alternation(std::istream & input);
	static ext::ptr_value < regexp::UnboundedRegExpElement < SymbolType > > alternationCont(std::istream & input, ext::ptr_value < regexp::UnboundedRegExpElement < SymbolType > > left);
	static ext::ptr_value < regexp::UnboundedRegExpElement < SymbolType > > alternationContCont(std::istream & input, regexp::UnboundedRegExpAlternation < SymbolType > res);

	static ext::ptr_value < regexp::UnboundedRegExpElement < SymbolType > > concatenation(std::istream & input);
	static ext::ptr_value < regexp::UnboundedRegExpElement < SymbolType > > concatenationCont(std::istream & input, ext::ptr_value < regexp::UnboundedRegExpElement < SymbolType > > left);
	static ext::ptr_value < regexp::UnboundedRegExpElement < SymbolType > > concatenationContCont(std::istream & input, regexp::UnboundedRegExpConcatenation < SymbolType > res);

	static ext::ptr_value < regexp::UnboundedRegExpElement < SymbolType > > factor(std::istream & input);
	static ext::ptr_value < regexp::UnboundedRegExpElement < SymbolType > > star(std::istream & input, regexp::UnboundedRegExpElement < SymbolType > && elem);

	enum class Priority {
		ALTERNATION,
		CONCATENATION,
		FACTOR
	};

	class Unbounded {
	public:
		static void visit( const regexp::UnboundedRegExpAlternation < SymbolType > & alternation, ext::tuple < Priority &, std::ostream & > & output);
		static void visit( const regexp::UnboundedRegExpConcatenation < SymbolType > & concatenation, ext::tuple < Priority &, std::ostream & > & output);
		static void visit( const regexp::UnboundedRegExpIteration < SymbolType > & iteration, ext::tuple < Priority &, std::ostream & > & output);
		static void visit( const regexp::UnboundedRegExpSymbol < SymbolType > & symbol, ext::tuple < Priority &, std::ostream & > & output);
		static void visit( const regexp::UnboundedRegExpEpsilon < SymbolType > & epsilon, ext::tuple < Priority &, std::ostream & > & output);
		static void visit( const regexp::UnboundedRegExpEmpty < SymbolType > & empty, ext::tuple < Priority &, std::ostream & > & output);
	};
};

template<class SymbolType >
regexp::UnboundedRegExpStructure < SymbolType > stringApi < regexp::UnboundedRegExpStructure < SymbolType > >::parse ( std::istream & input ) {
	return regexp::UnboundedRegExpStructure < SymbolType > ( alternation ( input ) );
}

template<class SymbolType >
ext::ptr_value < regexp::UnboundedRegExpElement < SymbolType > > stringApi < regexp::UnboundedRegExpStructure < SymbolType > >::alternation(std::istream & input) {
	return alternationCont(input, concatenation(input));
}

template<class SymbolType >
ext::ptr_value < regexp::UnboundedRegExpElement < SymbolType > > stringApi < regexp::UnboundedRegExpStructure < SymbolType > >::alternationCont(std::istream & input, ext::ptr_value < regexp::UnboundedRegExpElement < SymbolType > > left) {
	regexp::RegExpFromStringLexer::Token token = regexp::RegExpFromStringLexer::next(input);
	if(token.type == regexp::RegExpFromStringLexer::TokenType::PLUS) {
		regexp::UnboundedRegExpAlternation < SymbolType > res;
		res.appendElement(std::move(left));
		res.appendElement(concatenation(input));

		return alternationContCont(input, std::move ( res ) );
	} else {
		regexp::RegExpFromStringLexer::putback(input, token);
		return left;
	}
}

template<class SymbolType >
ext::ptr_value < regexp::UnboundedRegExpElement < SymbolType > > stringApi < regexp::UnboundedRegExpStructure < SymbolType > >::alternationContCont(std::istream & input, regexp::UnboundedRegExpAlternation < SymbolType > res) {
	regexp::RegExpFromStringLexer::Token token = regexp::RegExpFromStringLexer::next(input);
	if(token.type == regexp::RegExpFromStringLexer::TokenType::PLUS) {
		res.appendElement(concatenation(input));

		return alternationContCont(input, std::move ( res ) );
	} else {
		regexp::RegExpFromStringLexer::putback(input, token);
		return ext::ptr_value < regexp::UnboundedRegExpElement < SymbolType > > ( std::move ( res ) );
	}
}

template<class SymbolType >
ext::ptr_value < regexp::UnboundedRegExpElement < SymbolType > > stringApi < regexp::UnboundedRegExpStructure < SymbolType > >::concatenation(std::istream & input) {
	return concatenationCont(input, factor(input));
}

template<class SymbolType >
ext::ptr_value < regexp::UnboundedRegExpElement < SymbolType > > stringApi < regexp::UnboundedRegExpStructure < SymbolType > >::concatenationCont(std::istream & input, ext::ptr_value < regexp::UnboundedRegExpElement < SymbolType > > left) {
	regexp::RegExpFromStringLexer::Token token = regexp::RegExpFromStringLexer::next(input);
	if(token.type == ext::any_of ( regexp::RegExpFromStringLexer::TokenType::ERROR, regexp::RegExpFromStringLexer::TokenType::LPAR, regexp::RegExpFromStringLexer::TokenType::EPS, regexp::RegExpFromStringLexer::TokenType::EMPTY ) ) {
		regexp::RegExpFromStringLexer::putback(input, token);
		regexp::UnboundedRegExpConcatenation < SymbolType > res;
		res.appendElement(std::move(left));
		res.appendElement(factor(input));

		return concatenationContCont(input, std::move ( res ) );
	} else {
		regexp::RegExpFromStringLexer::putback(input, token);
		return left;
	}
}

template<class SymbolType >
ext::ptr_value < regexp::UnboundedRegExpElement < SymbolType > > stringApi < regexp::UnboundedRegExpStructure < SymbolType > >::concatenationContCont(std::istream & input, regexp::UnboundedRegExpConcatenation < SymbolType > res) {
	regexp::RegExpFromStringLexer::Token token = regexp::RegExpFromStringLexer::next(input);
	if(token.type == regexp::RegExpFromStringLexer::TokenType::ERROR || token.type == regexp::RegExpFromStringLexer::TokenType::LPAR || token.type == regexp::RegExpFromStringLexer::TokenType::EPS || token.type == regexp::RegExpFromStringLexer::TokenType::EMPTY) {
		regexp::RegExpFromStringLexer::putback(input, token);
		res.appendElement(factor(input));

		return concatenationContCont(input, std::move ( res ) );
	} else {
		regexp::RegExpFromStringLexer::putback(input, token);
		return ext::ptr_value < regexp::UnboundedRegExpElement < SymbolType > > ( std::move ( res ) );
	}
}

template<class SymbolType >
ext::ptr_value < regexp::UnboundedRegExpElement < SymbolType > > stringApi < regexp::UnboundedRegExpStructure < SymbolType > >::factor(std::istream & input) {
	regexp::RegExpFromStringLexer::Token token = regexp::RegExpFromStringLexer::next(input);
	if(token.type == regexp::RegExpFromStringLexer::TokenType::LPAR) {
		ext::ptr_value < regexp::UnboundedRegExpElement < SymbolType > > base = alternation(input);
		token = regexp::RegExpFromStringLexer::next(input);
		if(token.type != regexp::RegExpFromStringLexer::TokenType::RPAR) throw exception::CommonException("Expected RPAR");
		return star(input, std::move ( base ) );
	} else if(token.type == regexp::RegExpFromStringLexer::TokenType::EPS) {
		return star(input, ext::ptr_value < regexp::UnboundedRegExpElement < SymbolType > > ( regexp::UnboundedRegExpEpsilon < SymbolType > ( ) ) );
	} else if(token.type == regexp::RegExpFromStringLexer::TokenType::EMPTY) {
		return star(input, ext::ptr_value < regexp::UnboundedRegExpElement < SymbolType > > ( regexp::UnboundedRegExpEmpty < SymbolType > ( ) ) );
	} else if(token.type == regexp::RegExpFromStringLexer::TokenType::ERROR) {
		regexp::RegExpFromStringLexer::putback(input, token);
		regexp::UnboundedRegExpSymbol < SymbolType > res(core::stringApi<SymbolType>::parse(input));
		return star(input, std::move ( res ) );
	} else {
		throw exception::CommonException("Unrecognised token at factor rule");
	}
}

template<class SymbolType >
ext::ptr_value < regexp::UnboundedRegExpElement < SymbolType > > stringApi < regexp::UnboundedRegExpStructure < SymbolType > >::star(std::istream & input, regexp::UnboundedRegExpElement < SymbolType > && elem) {
	regexp::RegExpFromStringLexer::Token token = regexp::RegExpFromStringLexer::next(input);
	if(token.type == regexp::RegExpFromStringLexer::TokenType::STAR) {
		regexp::UnboundedRegExpIteration < SymbolType > iter ( std::move ( elem ) );
		return star(input, std::move ( iter ) );
	} else {
		regexp::RegExpFromStringLexer::putback(input, token);
		return ext::ptr_value < regexp::UnboundedRegExpElement < SymbolType > > ( std::move ( elem ) );
	}
}

template<class SymbolType >
bool stringApi < regexp::UnboundedRegExpStructure < SymbolType > >::first ( std::istream & ) {
	return true;
}

template<class SymbolType >
void stringApi < regexp::UnboundedRegExpStructure < SymbolType > >::compose ( std::ostream & output, const regexp::UnboundedRegExpStructure < SymbolType > & regexp ) {
	Priority tmp = Priority::ALTERNATION;
	ext::tuple < Priority &, std::ostream & > out = ext::tie ( tmp, output );
	regexp.getStructure ( ).template accept < void, stringApi < regexp::UnboundedRegExpStructure < SymbolType > >::Unbounded > ( out );
}

template < class SymbolType >
void stringApi < regexp::UnboundedRegExpStructure < SymbolType > >::Unbounded::visit( const regexp::UnboundedRegExpAlternation < SymbolType > & alternation, ext::tuple < Priority &, std::ostream & > & output ) {
	if ( alternation.getElements ( ).empty ( )) {
		std::get < 1 > ( output ) << "#0";
	} else if ( alternation.getElements ( ).size ( ) == 1) {
		alternation.getElements ( ) [ 0 ].template accept < void, core::stringApi < regexp::UnboundedRegExpStructure < SymbolType > >::Unbounded > ( output );
	} else {
		Priority outerPriorityMinimum = std::get < 0 > ( output );
		if ( outerPriorityMinimum == ext::any_of ( Priority::CONCATENATION, Priority::FACTOR ) )
			std::get < 1 > ( output ) << '(';
		bool first = true;
		for ( const auto & element : alternation.getElements ( ) ) {
			if ( first ) {
				first = false;
			} else {
				std::get < 1 > ( output ) << '+';
			}
			std::get < 0 > ( output ) = Priority::ALTERNATION;
			element.template accept < void, core::stringApi < regexp::UnboundedRegExpStructure < SymbolType > >::Unbounded > ( output );
		}
		if ( outerPriorityMinimum == ext::any_of ( Priority::CONCATENATION, Priority::FACTOR ) )
			std::get < 1 > ( output ) << ')';
	}
}

template < class SymbolType >
void stringApi < regexp::UnboundedRegExpStructure < SymbolType > >::Unbounded::visit( const regexp::UnboundedRegExpConcatenation < SymbolType > & concatenation, ext::tuple < Priority &, std::ostream & > & output ) {
	Priority outerPriorityMinimum = std::get < 0 > ( output );
	if ( concatenation.getElements ( ).empty ( )) {
		std::get < 1 > ( output ) << "#E";
	} else if ( concatenation.getElements ( ).size ( ) == 1 ) {
		concatenation.getElements ( ) [ 0 ].template accept < void, core::stringApi < regexp::UnboundedRegExpStructure < SymbolType > >::Unbounded > ( output );
	} else {
		if ( outerPriorityMinimum == Priority::FACTOR )
			std::get < 1 > ( output ) << '(';
		bool first = true;
		for ( const auto & element : concatenation.getElements ( ) ) {
			if ( first ) {
				first = false;
			} else {
				std::get < 1 > ( output ) << ' ';
			}
			std::get < 0 > ( output ) = Priority::CONCATENATION;
			element.template accept < void, core::stringApi < regexp::UnboundedRegExpStructure < SymbolType > >::Unbounded > ( output );
		}
		if ( outerPriorityMinimum == Priority::FACTOR )
			std::get < 1 > ( output ) << ')';
	}
}

template < class SymbolType >
void stringApi < regexp::UnboundedRegExpStructure < SymbolType > >::Unbounded::visit( const regexp::UnboundedRegExpIteration < SymbolType > & iteration, ext::tuple < Priority &, std::ostream & > & output ) {
	std::get < 0 > ( output ) = Priority::FACTOR;
	iteration.getElement ( ).template accept < void, core::stringApi < regexp::UnboundedRegExpStructure < SymbolType > >::Unbounded > ( output );
	std::get < 1 > ( output ) << "*";
}

template < class SymbolType >
void stringApi < regexp::UnboundedRegExpStructure < SymbolType > >::Unbounded::visit( const regexp::UnboundedRegExpSymbol < SymbolType > & symbol, ext::tuple < Priority &, std::ostream & > & output ) {
	core::stringApi < SymbolType >::compose ( std::get < 1 > ( output ), symbol.getSymbol ( ) );
}

template < class SymbolType >
void stringApi < regexp::UnboundedRegExpStructure < SymbolType > >::Unbounded::visit( const regexp::UnboundedRegExpEpsilon < SymbolType > &, ext::tuple < Priority &, std::ostream & > & output ) {
	std::get < 1 > ( output ) << "#E";
}

template < class SymbolType >
void stringApi < regexp::UnboundedRegExpStructure < SymbolType > >::Unbounded::visit( const regexp::UnboundedRegExpEmpty < SymbolType > &, ext::tuple < Priority &, std::ostream & > & output ) {
	std::get < 1 > ( output ) << "#0";
}

template<class SymbolType >
struct stringApi < regexp::UnboundedRegExp < SymbolType > > {
	static regexp::UnboundedRegExp < SymbolType > parse ( std::istream & input );
	static bool first ( std::istream & input );
	static void compose ( std::ostream & output, const regexp::UnboundedRegExp < SymbolType > & regexp );
};

template<class SymbolType >
regexp::UnboundedRegExp < SymbolType > stringApi < regexp::UnboundedRegExp < SymbolType > >::parse ( std::istream & input ) {
	return regexp::UnboundedRegExp < SymbolType > ( core::stringApi < regexp::UnboundedRegExpStructure < SymbolType > >::parse ( input ) );
}

template<class SymbolType >
bool stringApi < regexp::UnboundedRegExp < SymbolType > >::first ( std::istream & input ) {
	return core::stringApi < regexp::UnboundedRegExpStructure < SymbolType > >::first ( input );
}

template<class SymbolType >
void stringApi < regexp::UnboundedRegExp < SymbolType > >::compose ( std::ostream & output, const regexp::UnboundedRegExp < SymbolType > & regexp ) {
	core::stringApi < regexp::UnboundedRegExpStructure < SymbolType > >::compose ( output, regexp.getRegExp ( ) );
}

} /* namespace core */

#endif /* _STRING_UNBOUNDED_REG_EXP_H_ */
