/*
 * RandomRegExpFactory.cpp
 *
 *  Created on: 27. 3. 2014
 *	  Author: Jan Travnicek
 */

#include "RandomRegExpFactory.h"
#include <regexp/unbounded/UnboundedRegExpElements.h>
#include <exception/CommonException.h>

#include <alib/algorithm>
#include <alib/random>

#include <registration/AlgoRegistration.hpp>

namespace regexp {

namespace generate {

regexp::UnboundedRegExp < std::string > regexp::generate::RandomRegExpFactory::generateUnboundedRegExp( size_t leafNodes, size_t height, size_t alphabetSize, bool randomizedAlphabet ) {
	if ( alphabetSize > 26 )
		throw exception::CommonException( "Too big alphabet." );

	if ( alphabetSize <= 0 )
		throw exception::CommonException( "Alphabet size must be greater than 0." );

	ext::vector < std::string > symbols;
	for ( char i = 'a'; i <= 'z'; i++ )
		symbols.push_back ( std::string ( 1, i ) );

	if ( randomizedAlphabet )
		shuffle ( symbols.begin ( ), symbols.end ( ), ext::random_devices::semirandom );

	ext::set < std::string > alphabet ( symbols.begin ( ), symbols.begin ( ) + alphabetSize );

	return regexp::generate::RandomRegExpFactory::generateUnboundedRegExp ( leafNodes, height, alphabet );
}

template < class SymbolType >
regexp::UnboundedRegExp < SymbolType > regexp::generate::RandomRegExpFactory::generateUnboundedRegExp( size_t leafNodes, size_t height, ext::set < SymbolType > alphabet) {

	if( alphabet.size() > 26)
		throw exception::CommonException("Too big alphabet.");

	if( alphabet.empty ( ) )
		throw exception::CommonException( "Alphabet size must be greater than 0." );

	ext::ptr_vector < regexp::UnboundedRegExpElement < SymbolType > > elems;

	{
		elems.push_back ( regexp::UnboundedRegExpEmpty < SymbolType > ( ) );
		elems.push_back ( regexp::UnboundedRegExpEpsilon < SymbolType > ( ) );
	}
	if(alphabet.size() > 6) {
		elems.push_back ( regexp::UnboundedRegExpEmpty < SymbolType > ( ) );
		elems.push_back ( regexp::UnboundedRegExpEpsilon < SymbolType > ( ) );
	}
	if(alphabet.size() > 16) {
		elems.push_back ( regexp::UnboundedRegExpEmpty < SymbolType > ( ) );
		elems.push_back ( regexp::UnboundedRegExpEpsilon < SymbolType > ( ) );
	}

	for ( const SymbolType & symbol : alphabet) {
		elems.push_back ( regexp::UnboundedRegExpSymbol < SymbolType > ( symbol ) );
	}

	regexp::UnboundedRegExp < SymbolType > res = regexp::generate::RandomRegExpFactory::SimpleUnboundedRegExp ( leafNodes, height, elems );

	return res;
}

auto GenerateUnboundedRegExp2 = registration::AbstractRegister < regexp::generate::RandomRegExpFactory, regexp::UnboundedRegExp < >, size_t, size_t, ext::set < DefaultSymbolType > > ( regexp::generate::RandomRegExpFactory::generateUnboundedRegExp, abstraction::AlgorithmCategories::AlgorithmCategory::DEFAULT, "leafNodes", "height", "alphabet" ).setDocumentation (
"Generates a random regular expression.\n\
\n\
@param leafNodes number of leaf nodes in the generated regexp\n\
@param height the height of the generated regular expression\n\
@param alphabet the alphabet of the regular expression\n\
\n\
@return random regular expression" );

template < class SymbolType >
regexp::UnboundedRegExp < SymbolType > regexp::generate::RandomRegExpFactory::SimpleUnboundedRegExp ( size_t n, size_t h, const ext::ptr_vector < regexp::UnboundedRegExpElement < SymbolType > > & elems ) {
	return regexp::UnboundedRegExp < SymbolType > ( regexp::UnboundedRegExpStructure < SymbolType > ( SimpleUnboundedRegExpElement ( n, h, elems ) ) );
}

template < class SymbolType >
ext::ptr_value < regexp::UnboundedRegExpElement < SymbolType > > regexp::generate::RandomRegExpFactory::SimpleUnboundedRegExpElement (size_t n, size_t h, const ext::ptr_vector < regexp::UnboundedRegExpElement < SymbolType > > & elems) {
	if(h == 0 || n == 0) {
		return ext::ptr_value < UnboundedRegExpElement < SymbolType > > ( elems [ ext::random_devices::semirandom ( ) % elems.size ( ) ] );
	} else {
		unsigned childNodes = ext::random_devices::semirandom() % 10;
		if(childNodes <  4) childNodes = 1;
		else if(childNodes <  6) childNodes = 2;
		else if(childNodes <  8) childNodes = 3;
		else childNodes = 4;

		childNodes = childNodes > n ? n : childNodes;

		size_t subSizes[4] = {0};
		if(childNodes == 4) {
			subSizes[3] = ext::random_devices::semirandom() % ( n - 1 );
			subSizes[2] = ext::random_devices::semirandom() % ( n - subSizes[3] - 1 );
			subSizes[1] = ext::random_devices::semirandom() % ( n - subSizes[2] - subSizes [3] - 1 );

			subSizes[3] += 1;
			subSizes[2] += 1;
			subSizes[1] += 1;

			subSizes[0] = n - subSizes[1] - subSizes[2] - subSizes[3];
		}
		if(childNodes == 3) {
			subSizes[2] = ext::random_devices::semirandom() % ( n - 1);
			subSizes[1] = ext::random_devices::semirandom() % ( n - subSizes[2] - 1);

			subSizes[2] += 1;
			subSizes[1] += 1;

			subSizes[0] = n - subSizes[1] - subSizes[2];
		}
		if(childNodes == 2) {
			subSizes[1] = ext::random_devices::semirandom() % ( n - 1 );

			subSizes[1] += 1;

			subSizes[0] = n - subSizes[1];
		}
		if(childNodes == 1) {
			return ext::ptr_value < regexp::UnboundedRegExpElement < SymbolType > > ( regexp::UnboundedRegExpIteration < SymbolType > (SimpleUnboundedRegExpElement ( n, h - 1, elems ) ) );
		}

		int nodeType = ext::random_devices::semirandom() % 2;
		if(nodeType == 0) {
			regexp::UnboundedRegExpConcatenation < SymbolType > con;
			for ( unsigned i = 0; i < childNodes; i ++ ) {
				con.appendElement ( std::move ( SimpleUnboundedRegExpElement ( subSizes [ i ], h - 1, elems ) ) );
			}
			return ext::ptr_value < regexp::UnboundedRegExpElement < SymbolType > > ( std::move ( con ) );
		} else {
			regexp::UnboundedRegExpAlternation < SymbolType > alt;
			for ( unsigned i = 0; i < childNodes; i ++) {
				alt.appendElement ( std::move ( SimpleUnboundedRegExpElement ( subSizes [ i ], h - 1, elems ) ) );
			}
			return ext::ptr_value < regexp::UnboundedRegExpElement < SymbolType > > ( std::move ( alt ) );
		}

	}
}

} /* namespace generate */

} /* namespace regexp */

namespace {

auto GenerateUnboundedRegExp1 = registration::AbstractRegister < regexp::generate::RandomRegExpFactory, regexp::UnboundedRegExp < std::string >, size_t, size_t, size_t, bool > ( regexp::generate::RandomRegExpFactory::generateUnboundedRegExp, abstraction::AlgorithmCategories::AlgorithmCategory::DEFAULT, "leafNodes", "height", "alphabetSize", "randomizedAlphabet" ).setDocumentation (
"Generates a random regular expression.\n\
\n\
@param leafNodes number of leaf nodes in the generated regexp\n\
@param height the height of the generated regular expression\n\
@param alphabetSize size of the alphabet (1-26)\n\
@param randomizedAlphabet selects random symbols from a-z range if true\n\
\n\
@return random regular expression" );

} /* namespace */
