/*
 * RandomizeGrammar.h
 *
 *  Created on: 9. 2. 2017
 *	  Author: Jan Travnicek
 */

#pragma once

#include <grammar/Regular/LeftLG.h>
#include <grammar/Regular/LeftRG.h>
#include <grammar/Regular/RightLG.h>
#include <grammar/Regular/RightRG.h>
#include <grammar/ContextFree/CFG.h>

#include <common/Permutation.hpp>

namespace grammar {

namespace generate {

class RandomizeGrammar {
public:
	/**
	 * Shuffle the set of nonterminal symbols.
	 *
	 * \tparam TerminalSymbolType the type of terminal symbols of the random grammar
	 * \tparam NonterminalSymbolType the type of nonterminal symbols of the random grammar
	 *
	 * \param gram the grammar to shuffle
	 *
	 * \return grammar with shuffled nonterminal symbols
	 */
	template < class TerminalSymbolType, class NonterminalSymbolType >
	static grammar::LeftRG < TerminalSymbolType, NonterminalSymbolType > randomize ( const grammar::LeftRG < TerminalSymbolType, NonterminalSymbolType > & gram );

	/**
	 * \override
	 */
	template < class TerminalSymbolType, class NonterminalSymbolType >
	static grammar::LeftLG < TerminalSymbolType, NonterminalSymbolType > randomize ( const grammar::LeftLG < TerminalSymbolType, NonterminalSymbolType > & gram );

	/**
	 * \override
	 */
	template < class TerminalSymbolType, class NonterminalSymbolType >
	static grammar::RightRG < TerminalSymbolType, NonterminalSymbolType > randomize ( const grammar::RightRG < TerminalSymbolType, NonterminalSymbolType > & gram );

	/**
	 * \override
	 */
	template < class TerminalSymbolType, class NonterminalSymbolType >
	static grammar::RightLG < TerminalSymbolType, NonterminalSymbolType > randomize ( const grammar::RightLG < TerminalSymbolType, NonterminalSymbolType > & gram );

	/**
	 * \override
	 */
	template < class TerminalSymbolType, class NonterminalSymbolType >
	static grammar::CFG < TerminalSymbolType, NonterminalSymbolType > randomize ( const grammar::CFG < TerminalSymbolType, NonterminalSymbolType > & gram );

};

template < class TerminalSymbolType, class NonterminalSymbolType >
grammar::LeftRG < TerminalSymbolType, NonterminalSymbolType > RandomizeGrammar::randomize ( const grammar::LeftRG < TerminalSymbolType, NonterminalSymbolType > & gram ) {
	ext::map < NonterminalSymbolType, NonterminalSymbolType > symbolPermutationMap = common::Permutation::permutationMap ( gram.getNonterminalAlphabet ( ) );

	grammar::LeftRG < TerminalSymbolType, NonterminalSymbolType > res ( symbolPermutationMap.at ( gram.getInitialSymbol ( ) ) );

	res.setNonterminalAlphabet ( gram.getNonterminalAlphabet ( ) );
	res.setTerminalAlphabet ( gram.getTerminalAlphabet ( ) );

	for ( const std::pair < const NonterminalSymbolType, ext::set < ext::variant < TerminalSymbolType, ext::pair < NonterminalSymbolType, TerminalSymbolType > > > > & rule : gram.getRules ( ) )
		for ( const ext::variant < TerminalSymbolType, ext::pair < NonterminalSymbolType, NonterminalSymbolType > > & rhs : rule.second )
			if ( rhs.template is < TerminalSymbolType > ( ) )
				res.addRule ( symbolPermutationMap.at ( rule.first ), rhs );
			else
				res.addRule ( symbolPermutationMap.at ( rule.first ), ext::make_pair ( symbolPermutationMap.at ( rhs.template get < ext::pair < NonterminalSymbolType, TerminalSymbolType > > ( ).first ), rhs.template get < ext::pair < NonterminalSymbolType, TerminalSymbolType > > ( ).second ) );

	return res;
}

template < class TerminalSymbolType, class NonterminalSymbolType >
grammar::LeftLG < TerminalSymbolType, NonterminalSymbolType > RandomizeGrammar::randomize ( const grammar::LeftLG < TerminalSymbolType, NonterminalSymbolType > & gram ) {
	ext::map < NonterminalSymbolType, NonterminalSymbolType > symbolPermutationMap = common::Permutation::permutationMap ( gram.getNonterminalAlphabet ( ) );

	grammar::LeftLG < TerminalSymbolType, NonterminalSymbolType > res ( symbolPermutationMap.at ( gram.getInitialSymbol ( ) ) );

	res.setNonterminalAlphabet ( gram.getNonterminalAlphabet ( ) );
	res.setTerminalAlphabet ( gram.getTerminalAlphabet ( ) );

	for ( const std::pair < const NonterminalSymbolType, ext::set < ext::variant < ext::vector < TerminalSymbolType >, ext::pair < NonterminalSymbolType, ext::vector < TerminalSymbolType > > > > > & rule : gram.getRules ( ) )
		for ( const ext::variant < ext::vector < TerminalSymbolType >, ext::pair < NonterminalSymbolType, ext::vector < TerminalSymbolType > > > & rhs : rule.second )
			if ( rhs.template is < ext::vector < TerminalSymbolType > > ( ) )
				res.addRule ( symbolPermutationMap.at ( rule.first ), rhs );
			else
				res.addRule ( symbolPermutationMap.at ( rule.first ), ext::make_pair ( symbolPermutationMap.at ( rhs.template get < ext::pair < NonterminalSymbolType, ext::vector < TerminalSymbolType > > > ( ).first ), rhs.template get < ext::pair < NonterminalSymbolType, ext::vector < TerminalSymbolType > > > ( ).second ) );

	return res;
}

template < class TerminalSymbolType, class NonterminalSymbolType >
grammar::RightRG < TerminalSymbolType, NonterminalSymbolType > RandomizeGrammar::randomize ( const grammar::RightRG < TerminalSymbolType, NonterminalSymbolType > & gram ) {
	ext::map < NonterminalSymbolType, NonterminalSymbolType > symbolPermutationMap = common::Permutation::permutationMap ( gram.getNonterminalAlphabet ( ) );

	grammar::RightRG < TerminalSymbolType, NonterminalSymbolType > res ( symbolPermutationMap.at ( gram.getInitialSymbol ( ) ) );

	res.setNonterminalAlphabet ( gram.getNonterminalAlphabet ( ) );
	res.setTerminalAlphabet ( gram.getTerminalAlphabet ( ) );

	for ( const std::pair < const NonterminalSymbolType, ext::set < ext::variant < TerminalSymbolType, ext::pair < TerminalSymbolType, NonterminalSymbolType > > > > & rule : gram.getRules ( ) )
		for ( const ext::variant < TerminalSymbolType, ext::pair < TerminalSymbolType, NonterminalSymbolType > > & rhs : rule.second )
			if ( rhs.template is < TerminalSymbolType > ( ) )
				res.addRule ( symbolPermutationMap.at ( rule.first ), rhs );
			else
				res.addRule ( symbolPermutationMap.at ( rule.first ), ext::make_pair ( rhs.template get < ext::pair < TerminalSymbolType, NonterminalSymbolType > > ( ).first, symbolPermutationMap.at ( rhs.template get < ext::pair < TerminalSymbolType, NonterminalSymbolType > > ( ).second ) ) );

	return res;
}

template < class TerminalSymbolType, class NonterminalSymbolType >
grammar::RightLG < TerminalSymbolType, NonterminalSymbolType > RandomizeGrammar::randomize ( const grammar::RightLG < TerminalSymbolType, NonterminalSymbolType > & gram ) {
	ext::map < NonterminalSymbolType, NonterminalSymbolType > symbolPermutationMap = common::Permutation::permutationMap ( gram.getNonterminalAlphabet ( ) );

	grammar::RightLG < TerminalSymbolType, NonterminalSymbolType > res ( symbolPermutationMap.at ( gram.getInitialSymbol ( ) ) );

	res.setNonterminalAlphabet ( gram.getNonterminalAlphabet ( ) );
	res.setTerminalAlphabet ( gram.getTerminalAlphabet ( ) );

	for ( const std::pair < const NonterminalSymbolType, ext::set < ext::variant < ext::vector < TerminalSymbolType >, ext::pair < ext::vector < TerminalSymbolType >, NonterminalSymbolType > > > > & rule : gram.getRules ( ) )
		for ( const ext::variant < ext::vector < TerminalSymbolType >, ext::pair < ext::vector < TerminalSymbolType >, NonterminalSymbolType > > & rhs : rule.second )
			if ( rhs.template is < ext::vector < TerminalSymbolType > > ( ) )
				res.addRule ( symbolPermutationMap.at ( rule.first ), rhs );
			else
				res.addRule ( symbolPermutationMap.at ( rule.first ), ext::make_pair ( rhs.template get < ext::pair < ext::vector < TerminalSymbolType >, NonterminalSymbolType > > ( ).first, symbolPermutationMap.at ( rhs.template get < ext::pair < ext::vector < TerminalSymbolType >, NonterminalSymbolType > > ( ).second ) ) );

	return res;
}

template < class TerminalSymbolType, class NonterminalSymbolType >
grammar::CFG < TerminalSymbolType, NonterminalSymbolType > RandomizeGrammar::randomize ( const grammar::CFG < TerminalSymbolType, NonterminalSymbolType > & gram ) {
	ext::map < NonterminalSymbolType, NonterminalSymbolType > symbolPermutationMap = common::Permutation::permutationMap ( gram.getNonterminalAlphabet ( ) );

	grammar::CFG < TerminalSymbolType, NonterminalSymbolType > res ( symbolPermutationMap.find ( gram.getInitialSymbol ( ) )->second );

	res.setNonterminalAlphabet ( gram.getNonterminalAlphabet ( ) );
	res.setTerminalAlphabet ( gram.getTerminalAlphabet ( ) );

	for ( const std::pair < const NonterminalSymbolType, ext::set < ext::vector < ext::variant < TerminalSymbolType, NonterminalSymbolType > > > > & rule : gram.getRules ( ) )
		for ( const ext::vector < ext::variant < TerminalSymbolType, NonterminalSymbolType > > & rhs : rule.second )
			res.addRule ( symbolPermutationMap.find ( rule.first )->second, ext::transform < ext::variant < TerminalSymbolType, NonterminalSymbolType > > ( rhs, [ & ] ( const ext::variant < TerminalSymbolType, NonterminalSymbolType > & elem ) {
						if ( gram.getNonterminalAlphabet ( ).count ( elem ) )
							return ext::variant < TerminalSymbolType, NonterminalSymbolType > ( symbolPermutationMap.find ( elem )->second );
						else
							return elem;
					} ) );

	return res;
}

} /* namespace generate */

} /* namespace grammar */

