/*
 * EvalHelper.cpp
 *
 *  Created on: 25. 8. 2017
 *	  Author: Jan Travnicek
 */

#include <common/EvalHelper.h>

#include <registry/Registry.h>
#include <exception>
#include <common/CastHelper.h>

namespace abstraction {

std::shared_ptr < abstraction::Value > EvalHelper::evalAlgorithm ( abstraction::TemporariesHolder & environment, const std::string & name, const ext::vector < std::string > & templateParams, const ext::vector < std::shared_ptr < abstraction::Value > > & params, abstraction::AlgorithmCategories::AlgorithmCategory category ) {
	ext::vector < std::string > paramTypes;
	ext::vector < abstraction::TypeQualifiers::TypeQualifierSet > paramTypeQualifiers;
	for ( const std::shared_ptr < abstraction::Value > & param : params ) {
		paramTypes.push_back ( param->getType ( ) );
		paramTypeQualifiers.push_back ( param->getTypeQualifiers ( ) );
	}

	try {
		return evalAbstraction ( environment, abstraction::Registry::getAlgorithmAbstraction ( name, templateParams, paramTypes, paramTypeQualifiers, category ), params );
	} catch ( const std::exception & executionException ) {
		std::throw_with_nested ( std::runtime_error ( "Evaluation of algorithm " + name + " failed." ) );
	}
}

std::shared_ptr < abstraction::Value > EvalHelper::evalOperator ( abstraction::TemporariesHolder & environment, abstraction::Operators::BinaryOperators type, const ext::vector < std::shared_ptr < abstraction::Value > > & params, abstraction::AlgorithmCategories::AlgorithmCategory category ) {
	ext::vector < std::string > paramTypes;
	ext::vector < abstraction::TypeQualifiers::TypeQualifierSet > paramTypeQualifiers;
	for ( const std::shared_ptr < abstraction::Value > & param : params ) {
		paramTypes.push_back ( param->getType ( ) );
		paramTypeQualifiers.push_back ( param->getTypeQualifiers ( ) );
	}

	try {
		return evalAbstraction ( environment, abstraction::Registry::getBinaryOperatorAbstraction ( type, paramTypes, paramTypeQualifiers, category ), params );
	} catch ( ... ) {
		std::throw_with_nested ( std::runtime_error ( "Evaluation of binary operator " + abstraction::Operators::toString ( type ) + " failed." ) );
	}
}

std::shared_ptr < abstraction::Value > EvalHelper::evalOperator ( abstraction::TemporariesHolder & environment, abstraction::Operators::PrefixOperators type, const ext::vector < std::shared_ptr < abstraction::Value > > & params, abstraction::AlgorithmCategories::AlgorithmCategory category ) {
	ext::vector < std::string > paramTypes;
	ext::vector < abstraction::TypeQualifiers::TypeQualifierSet > paramTypeQualifiers;
	for ( const std::shared_ptr < abstraction::Value > & param : params ) {
		paramTypes.push_back ( param->getType ( ) );
		paramTypeQualifiers.push_back ( param->getTypeQualifiers ( ) );
	}

	try {
		return evalAbstraction ( environment, abstraction::Registry::getPrefixOperatorAbstraction ( type, paramTypes, paramTypeQualifiers, category ), params );
	} catch ( ... ) {
		std::throw_with_nested ( std::runtime_error ( "Evaluation of prefix operator " + abstraction::Operators::toString ( type ) + " failed." ) );
	}
}

std::shared_ptr < abstraction::Value > EvalHelper::evalOperator ( abstraction::TemporariesHolder & environment, abstraction::Operators::PostfixOperators type, const ext::vector < std::shared_ptr < abstraction::Value > > & params, abstraction::AlgorithmCategories::AlgorithmCategory category ) {
	ext::vector < std::string > paramTypes;
	ext::vector < abstraction::TypeQualifiers::TypeQualifierSet > paramTypeQualifiers;
	for ( const std::shared_ptr < abstraction::Value > & param : params ) {
		paramTypes.push_back ( param->getType ( ) );
		paramTypeQualifiers.push_back ( param->getTypeQualifiers ( ) );
	}

	try {
		return evalAbstraction ( environment, abstraction::Registry::getPostfixOperatorAbstraction ( type, paramTypes, paramTypeQualifiers, category ), params );
	} catch ( ... ) {
		std::throw_with_nested ( std::runtime_error ( "Evaluation of postfix operator " + abstraction::Operators::toString ( type ) + " failed." ) );
	}
}

std::shared_ptr < abstraction::Value > EvalHelper::evalAbstraction ( abstraction::TemporariesHolder & environment, std::shared_ptr < abstraction::OperationAbstraction > abstraction, const ext::vector < std::shared_ptr < abstraction::Value > > & params ) {
	unsigned i = 0;
	ext::vector < std::shared_ptr < abstraction::Value > > casted_params;
	for ( const std::shared_ptr < abstraction::Value > & param : params ) {
		if ( abstraction->isParamTypeUniversal ( i ) || abstraction::Registry::isCastNoOp ( abstraction->getParamType ( i ), param->getType ( ) ) ) {
			casted_params.push_back ( param );
		} else {
			casted_params.push_back ( abstraction::CastHelper::eval ( environment, param, abstraction->getParamType ( i ) ) );
			environment.holdTemporary ( casted_params.back ( ) );
		}
		++ i;
	}

	i = 0;
	for ( const std::shared_ptr < abstraction::Value > & param : casted_params ) {
		abstraction->attachInput ( param, i );
		++ i;
	}

	std::shared_ptr < abstraction::Value > result = abstraction->eval ( );
	if ( ! result )
		throw std::invalid_argument ( "Internal error in abstraction evaluation." );

	if ( abstraction::Registry::hasNormalize ( result->getType ( ) ) ) {
		std::shared_ptr < abstraction::OperationAbstraction > normalize = abstraction::Registry::getNormalizeAbstraction ( result->getType ( ) );
		normalize->attachInput ( result, 0 );
		result = normalize->eval ( );
		if ( ! result )
			throw std::invalid_argument ( "Internal error in normalization." );
	}

	return result;
}

} /* namespace abstraction */
