/*
 * ToCNF.h
 *
 *  Created on: 24. 11. 2014
 *	  Author: Jan Travnicek
 */

#pragma once

#include <grammar/Grammar.h>
#include <grammar/ContextFree/CFG.h>
#include <grammar/ContextFree/EpsilonFreeCFG.h>
#include <grammar/ContextFree/CNF.h>
#include <grammar/ContextFree/GNF.h>
#include <grammar/ContextFree/LG.h>
#include <grammar/Regular/LeftLG.h>
#include <grammar/Regular/LeftRG.h>
#include <grammar/Regular/RightLG.h>
#include <grammar/Regular/RightRG.h>

#include <common/createUnique.hpp>

#include "EpsilonRemover.h"
#include "SimpleRulesRemover.h"
#include <exception/CommonException.h>

namespace grammar {

namespace simplify {

/**
 * Implements transformation of a grammar into chomsky's normal form.
 *
 */
class ToCNF {
public:
	/**
	 * Implements transformation of a grammar into chomsky's normal form.
	 *
	 * \tparam TerminalSymbolType the type of terminals in the transformed grammar
	 * \tparam NonterminalSymbolType the type of nonterminals in the transformed grammar
	 *
	 * \param grammar the transformed grammar
	 *
	 * \return an grammar in chomsky's normal form equivalent to the @p grammar
	 */
	template < class TerminalSymbolType, class NonterminalSymbolType >
	static grammar::CNF < TerminalSymbolType, ext::vector < ext::variant < TerminalSymbolType, NonterminalSymbolType > > > convert( const grammar::CFG < TerminalSymbolType, NonterminalSymbolType > & grammar );

	/**
	 * Implements transformation of a grammar into chomsky's normal form.
	 *
	 * \tparam TerminalSymbolType the type of terminals in the transformed grammar
	 * \tparam NonterminalSymbolType the type of nonterminals in the transformed grammar
	 *
	 * \param grammar the transformed grammar
	 *
	 * \return an grammar in chomsky's normal form equivalent to the @p grammar
	 */
	template < class TerminalSymbolType, class NonterminalSymbolType >
	static grammar::CNF < TerminalSymbolType, ext::vector < ext::variant < TerminalSymbolType, NonterminalSymbolType > > > convert( const grammar::EpsilonFreeCFG < TerminalSymbolType, NonterminalSymbolType > & grammar );

	/**
	 * Implements transformation of a grammar into chomsky's normal form.
	 *
	 * \tparam TerminalSymbolType the type of terminals in the transformed grammar
	 * \tparam NonterminalSymbolType the type of nonterminals in the transformed grammar
	 *
	 * \param grammar the transformed grammar
	 *
	 * \return an grammar in chomsky's normal form equivalent to the @p grammar
	 */
	template < class TerminalSymbolType, class NonterminalSymbolType >
	static grammar::CNF < TerminalSymbolType, ext::vector < ext::variant < TerminalSymbolType, NonterminalSymbolType > > > convert( const grammar::GNF < TerminalSymbolType, NonterminalSymbolType > & grammar );

	/**
	 * Implements transformation of a grammar into chomsky's normal form.
	 *
	 * \tparam TerminalSymbolType the type of terminals in the transformed grammar
	 * \tparam NonterminalSymbolType the type of nonterminals in the transformed grammar
	 *
	 * \param grammar the transformed grammar
	 *
	 * \return an grammar in chomsky's normal form equivalent to the @p grammar
	 */
	template < class TerminalSymbolType, class NonterminalSymbolType >
	static grammar::CNF < TerminalSymbolType, NonterminalSymbolType > convert( const grammar::CNF < TerminalSymbolType, NonterminalSymbolType > & grammar );

	/**
	 * Implements transformation of a grammar into chomsky's normal form.
	 *
	 * \tparam TerminalSymbolType the type of terminals in the transformed grammar
	 * \tparam NonterminalSymbolType the type of nonterminals in the transformed grammar
	 *
	 * \param grammar the transformed grammar
	 *
	 * \return an grammar in chomsky's normal form equivalent to the @p grammar
	 */
	template < class TerminalSymbolType, class NonterminalSymbolType >
	static grammar::CNF < TerminalSymbolType, ext::vector < ext::variant < TerminalSymbolType, NonterminalSymbolType > > > convert( const grammar::LG < TerminalSymbolType, NonterminalSymbolType > & grammar );

	/**
	 * Implements transformation of a grammar into chomsky's normal form.
	 *
	 * \tparam TerminalSymbolType the type of terminals in the transformed grammar
	 * \tparam NonterminalSymbolType the type of nonterminals in the transformed grammar
	 *
	 * \param grammar the transformed grammar
	 *
	 * \return an grammar in chomsky's normal form equivalent to the @p grammar
	 */
	template < class TerminalSymbolType, class NonterminalSymbolType >
	static grammar::CNF < TerminalSymbolType, ext::vector < ext::variant < TerminalSymbolType, NonterminalSymbolType > > > convert( const grammar::LeftLG < TerminalSymbolType, NonterminalSymbolType > & grammar );

	/**
	 * Implements transformation of a grammar into chomsky's normal form.
	 *
	 * \tparam TerminalSymbolType the type of terminals in the transformed grammar
	 * \tparam NonterminalSymbolType the type of nonterminals in the transformed grammar
	 *
	 * \param grammar the transformed grammar
	 *
	 * \return an grammar in chomsky's normal form equivalent to the @p grammar
	 */
	template < class TerminalSymbolType, class NonterminalSymbolType >
	static grammar::CNF < TerminalSymbolType, ext::variant < TerminalSymbolType, NonterminalSymbolType > > convert( const grammar::LeftRG < TerminalSymbolType, NonterminalSymbolType > & grammar );

	/**
	 * Implements transformation of a grammar into chomsky's normal form.
	 *
	 * \tparam TerminalSymbolType the type of terminals in the transformed grammar
	 * \tparam NonterminalSymbolType the type of nonterminals in the transformed grammar
	 *
	 * \param grammar the transformed grammar
	 *
	 * \return an grammar in chomsky's normal form equivalent to the @p grammar
	 */
	template < class TerminalSymbolType, class NonterminalSymbolType >
	static grammar::CNF < TerminalSymbolType, ext::vector < ext::variant < TerminalSymbolType, NonterminalSymbolType > > > convert( const grammar::RightLG < TerminalSymbolType, NonterminalSymbolType > & grammar );

	/**
	 * Implements transformation of a grammar into chomsky's normal form.
	 *
	 * \tparam TerminalSymbolType the type of terminals in the transformed grammar
	 * \tparam NonterminalSymbolType the type of nonterminals in the transformed grammar
	 *
	 * \param grammar the transformed grammar
	 *
	 * \return an grammar in chomsky's normal form equivalent to the @p grammar
	 */
	template < class TerminalSymbolType, class NonterminalSymbolType >
	static grammar::CNF < TerminalSymbolType, ext::variant < TerminalSymbolType, NonterminalSymbolType > > convert( const grammar::RightRG < TerminalSymbolType, NonterminalSymbolType > & grammar );
};

template < class TerminalSymbolType, class NonterminalSymbolType >
grammar::CNF < TerminalSymbolType, NonterminalSymbolType > ToCNF::convert(const grammar::CNF < TerminalSymbolType, NonterminalSymbolType > & grammar) {
	return grammar;
}

template < class TerminalSymbolType, class NonterminalSymbolType >
grammar::CNF < TerminalSymbolType,  ext::variant < TerminalSymbolType, NonterminalSymbolType > > ToCNF::convert ( const grammar::LeftRG < TerminalSymbolType, NonterminalSymbolType > & grammar ) {
	grammar::CNF < TerminalSymbolType, ext::variant < TerminalSymbolType, NonterminalSymbolType > > result ( grammar.getInitialSymbol ( ) );

	for ( const NonterminalSymbolType & nonterminal : grammar.getNonterminalAlphabet ( ) )
		result.addNonterminalSymbol ( nonterminal );
	result.setTerminalAlphabet ( grammar.getTerminalAlphabet ( ) );

	ext::map < TerminalSymbolType, TerminalSymbolType > terminalToShadowNonterminal;
	for ( const TerminalSymbolType & symbol : grammar.getTerminalAlphabet ( ) ) {
		TerminalSymbolType shadowSymbol = common::createUnique ( symbol, result.getTerminalAlphabet ( ), result.getNonterminalAlphabet ( ) );
		terminalToShadowNonterminal.insert ( std::make_pair ( symbol, shadowSymbol ) );
		result.addNonterminalSymbol ( shadowSymbol );
		result.addRule ( std::move ( shadowSymbol ), symbol );
	}

	for ( const std::pair < const NonterminalSymbolType, ext::set < ext::variant < TerminalSymbolType, ext::pair < NonterminalSymbolType, TerminalSymbolType > > > > & rules : grammar.getRules ( ) ) {
		for ( const ext::variant < TerminalSymbolType, ext::pair < NonterminalSymbolType, TerminalSymbolType > > & rhs : rules.second ) {
			if ( rhs.template is < TerminalSymbolType > ( ) ) {
				result.addRule ( rules.first, rhs.template get < TerminalSymbolType > ( ) );
			} else {
				const ext::pair < NonterminalSymbolType, TerminalSymbolType > & rhsPair = rhs.template get < ext::pair < NonterminalSymbolType, TerminalSymbolType > > ( );
				result.addRule ( rules.first, ext::make_pair ( rhsPair.first, terminalToShadowNonterminal.at ( rhsPair.second ) ) );
			}
		}
	}

	result.setGeneratesEpsilon ( grammar.getGeneratesEpsilon ( ) );

	return result;
}

template < class TerminalSymbolType, class NonterminalSymbolType >
grammar::CNF < TerminalSymbolType,  ext::variant < TerminalSymbolType, NonterminalSymbolType > > ToCNF::convert ( const grammar::RightRG < TerminalSymbolType, NonterminalSymbolType > & grammar ) {
	grammar::CNF < TerminalSymbolType,  ext::variant < TerminalSymbolType, NonterminalSymbolType > > result ( grammar.getInitialSymbol ( ) );

	for ( const NonterminalSymbolType & nonterminal : grammar.getNonterminalAlphabet ( ) )
		result.addNonterminalSymbol ( nonterminal );
	result.setTerminalAlphabet ( grammar.getTerminalAlphabet ( ) );

	ext::map < TerminalSymbolType, TerminalSymbolType > terminalToShadowNonterminal;
	for ( const TerminalSymbolType & symbol : grammar.getTerminalAlphabet ( ) ) {
		TerminalSymbolType shadowSymbol = common::createUnique ( symbol, result.getTerminalAlphabet ( ), result.getNonterminalAlphabet ( ) );
		terminalToShadowNonterminal.insert ( std::make_pair ( symbol, shadowSymbol ) );
		result.addNonterminalSymbol ( shadowSymbol );
		result.addRule ( std::move ( shadowSymbol ), symbol );
	}

	for ( const std::pair < const NonterminalSymbolType, ext::set < ext::variant < TerminalSymbolType, ext::pair < TerminalSymbolType, NonterminalSymbolType > > > > & rules : grammar.getRules ( ) ) {
		for ( const ext::variant < TerminalSymbolType, ext::pair < TerminalSymbolType, NonterminalSymbolType > > & rhs : rules.second ) {
			if ( rhs.template is < TerminalSymbolType > ( ) ) {
				result.addRule ( rules.first, rhs.template get < TerminalSymbolType > ( ) );
			} else {
				const ext::pair < TerminalSymbolType, NonterminalSymbolType > & rhsPair = rhs.template get < ext::pair < TerminalSymbolType, NonterminalSymbolType > > ( );
				result.addRule ( rules.first, ext::make_pair ( terminalToShadowNonterminal.at ( rhsPair.first ), rhsPair.second ) );
			}
		}
	}

	result.setGeneratesEpsilon ( grammar.getGeneratesEpsilon ( ) );

	return result;
}

template < class TerminalSymbolType, class NonterminalSymbolType >
void splitRule ( ext::vector < ext::variant < TerminalSymbolType, NonterminalSymbolType > > lhs, const ext::vector < ext::variant < TerminalSymbolType, NonterminalSymbolType > > & rhs, grammar::CNF < TerminalSymbolType, ext::vector < ext::variant < TerminalSymbolType, NonterminalSymbolType > > > & result ) {
	ext::vector < ext::variant < TerminalSymbolType, NonterminalSymbolType > > left;
	ext::vector < ext::variant < TerminalSymbolType, NonterminalSymbolType > > right;

	for ( unsigned i = 0; i < rhs.size ( ) / 2; ++ i )
		left.push_back ( rhs [ i ] );

	if ( result.addNonterminalSymbol ( left ) && left.size ( ) > 1 )
		splitRule ( left, left, result );

	for ( unsigned i = rhs.size ( ) / 2; i < rhs.size ( ); ++ i )
		right.push_back ( rhs [ i ] );

	if ( result.addNonterminalSymbol ( right ), right.size ( ) > 1 )
		splitRule ( right, right, result );

	result.addRule ( std::move ( lhs ), ext::make_pair ( std::move ( left ), std::move ( right ) ) );
}

template < class T, class TerminalSymbolType = typename grammar::TerminalSymbolTypeOfGrammar < T >, class NonterminalSymbolType = typename grammar::NonterminalSymbolTypeOfGrammar < T > >
grammar::CNF < TerminalSymbolType, ext::vector < ext::variant < TerminalSymbolType, NonterminalSymbolType > > > convertInternal( const T & grammar ) {
	grammar::CNF < TerminalSymbolType, ext::vector < ext::variant < TerminalSymbolType, NonterminalSymbolType > > > result ( ext::vector < ext::variant < TerminalSymbolType, NonterminalSymbolType > > { grammar.getInitialSymbol ( ) } );

	for ( const NonterminalSymbolType & nonterminal : grammar.getNonterminalAlphabet ( ) )
		result.addNonterminalSymbol ( ext::vector < ext::variant < TerminalSymbolType, NonterminalSymbolType > > { nonterminal } );

	result.setTerminalAlphabet ( grammar.getTerminalAlphabet ( ) );

	for ( const TerminalSymbolType & symbol : grammar.getTerminalAlphabet ( ) ) {
		result.addNonterminalSymbol ( ext::vector < ext::variant < TerminalSymbolType, NonterminalSymbolType > > { symbol } );
		result.addRule ( ext::vector < ext::variant < TerminalSymbolType, NonterminalSymbolType > > { symbol }, symbol );
	}

	for ( const auto & rules : grammar.getRules ( ) ) {
		for ( const ext::vector < ext::variant < TerminalSymbolType, NonterminalSymbolType > > & rhs : rules.second ) {
			if ( rhs.size ( ) == 1 ) {
				result.addRule ( ext::vector < ext::variant < TerminalSymbolType, NonterminalSymbolType > > { rules.first }, rhs [ 0 ].template get < TerminalSymbolType > ( ) );
			} else {
				ext::vector < ext::variant < TerminalSymbolType, NonterminalSymbolType > > rawRule;
				for ( const ext::variant < TerminalSymbolType, NonterminalSymbolType > & symbol : rhs ) {
					if ( grammar.getTerminalAlphabet ( ).count ( symbol ) )
						rawRule.push_back ( symbol.template get < TerminalSymbolType > ( ) );
					else
						rawRule.push_back ( symbol );
				}

				splitRule ( ext::vector < ext::variant < TerminalSymbolType, NonterminalSymbolType > > { rules.first }, std::move ( rawRule ), result );
			}
		}
	}

	result.setGeneratesEpsilon ( grammar.getGeneratesEpsilon ( ) );

	return result;
}

template < class TerminalSymbolType, class NonterminalSymbolType >
grammar::CNF < TerminalSymbolType, ext::vector < ext::variant < TerminalSymbolType, NonterminalSymbolType > > > ToCNF::convert(const grammar::CFG < TerminalSymbolType, NonterminalSymbolType > & grammar) {
	return convert(grammar::simplify::EpsilonRemover::remove(grammar));
}

template < class TerminalSymbolType, class NonterminalSymbolType >
grammar::CNF < TerminalSymbolType, ext::vector < ext::variant < TerminalSymbolType, NonterminalSymbolType > > > ToCNF::convert(const grammar::EpsilonFreeCFG < TerminalSymbolType, NonterminalSymbolType > & grammar) {
	return convertInternal(grammar::simplify::SimpleRulesRemover::remove(grammar));
}

template < class TerminalSymbolType, class NonterminalSymbolType >
grammar::CNF < TerminalSymbolType, ext::vector < ext::variant < TerminalSymbolType, NonterminalSymbolType > > > ToCNF::convert ( const grammar::GNF < TerminalSymbolType, NonterminalSymbolType > & grammar ) {
	grammar::CNF < TerminalSymbolType, ext::vector < ext::variant < TerminalSymbolType, NonterminalSymbolType > > > result ( ext::vector < ext::variant < TerminalSymbolType, NonterminalSymbolType > > { grammar.getInitialSymbol ( ) } );

	for ( const NonterminalSymbolType & nonterminal : grammar.getNonterminalAlphabet ( ) )
		result.addNonterminalSymbol ( ext::vector < ext::variant < TerminalSymbolType, NonterminalSymbolType > > { nonterminal } );

	result.setTerminalAlphabet ( grammar.getTerminalAlphabet ( ) );

	for ( const TerminalSymbolType & symbol : grammar.getTerminalAlphabet ( ) ) {
		result.addNonterminalSymbol ( ext::vector < ext::variant < TerminalSymbolType, NonterminalSymbolType > > { symbol } );
		result.addRule ( ext::vector < ext::variant < TerminalSymbolType, NonterminalSymbolType > > { symbol }, symbol );
	}

	for ( const auto & rules : grammar.getRules ( ) ) {
		for ( const ext::pair < TerminalSymbolType, ext::vector < NonterminalSymbolType > > & rhs : rules.second ) {
			if ( rhs.second.empty ( ) )
				result.addRule ( ext::vector < ext::variant < TerminalSymbolType, NonterminalSymbolType > > { rules.first }, rhs.first );
			else {
				ext::vector < ext::variant < TerminalSymbolType, NonterminalSymbolType > > rawRule { rhs.first };
				rawRule.insert ( rawRule.end ( ), rhs.second.begin ( ), rhs.second.end ( ) );
				splitRule ( ext::vector < ext::variant < TerminalSymbolType, NonterminalSymbolType > > { rules.first }, std::move ( rawRule ), result );
			}
		}
	}

	result.setGeneratesEpsilon ( grammar.getGeneratesEpsilon ( ) );

	return result;
}

template < class TerminalSymbolType, class NonterminalSymbolType >
grammar::CNF < TerminalSymbolType, ext::vector < ext::variant < TerminalSymbolType, NonterminalSymbolType > > > ToCNF::convert(const grammar::LG < TerminalSymbolType, NonterminalSymbolType > & grammar) {
	return convertInternal(grammar::simplify::SimpleRulesRemover::remove(grammar::simplify::EpsilonRemover::remove(grammar)));
}

template < class TerminalSymbolType, class NonterminalSymbolType >
grammar::CNF < TerminalSymbolType, ext::vector < ext::variant < TerminalSymbolType, NonterminalSymbolType > > > ToCNF::convert(const grammar::LeftLG< TerminalSymbolType, NonterminalSymbolType > & grammar) {
	return convertInternal(grammar::simplify::SimpleRulesRemover::remove(grammar::simplify::EpsilonRemover::remove(grammar)));
}

template < class TerminalSymbolType, class NonterminalSymbolType >
grammar::CNF < TerminalSymbolType, ext::vector < ext::variant < TerminalSymbolType, NonterminalSymbolType > > > ToCNF::convert(const grammar::RightLG < TerminalSymbolType, NonterminalSymbolType > & grammar) {
	return convertInternal(grammar::simplify::SimpleRulesRemover::remove(grammar::simplify::EpsilonRemover::remove(grammar)));
}

} /* namespace simplify */

} /* namespace grammar */

