/*
 * OperatorRegistry.cpp
 *
 *  Created on: 19. 8. 2017
 *	  Author: Jan Travnicek
 */

#include <registry/OperatorRegistry.hpp>
#include <registry/CastRegistry.hpp>

#include <alib/foreach>
#include <alib/algorithm>

#include <exception>

#include <common/OverloadResolution.hpp>

namespace abstraction {

ext::map < Operators::BinaryOperators, ext::list < std::unique_ptr < OperatorRegistry::BinaryEntry > > > & OperatorRegistry::getBinaryEntries ( ) {
	static ext::map < Operators::BinaryOperators, ext::list < std::unique_ptr < BinaryEntry > > > algorithmGroups;
	return algorithmGroups;
}

bool OperatorRegistry::isRegisteredBinary ( Operators::BinaryOperators type, const AlgorithmBaseInfo & entryInfo ) {
	const auto & group = getBinaryEntries ( ) [ type ];

	return std::any_of ( group.begin ( ), group.end ( ), [ & ] ( const std::unique_ptr < BinaryEntry > & entry ) { return entry->getEntryInfo ( ).getCategory ( ) == entryInfo.getCategory ( ) && entry->getEntryInfo ( ).getParams ( ) == entryInfo.getParams ( ); } );
}

void OperatorRegistry::registerBinaryInternal ( Operators::BinaryOperators type, std::unique_ptr < BinaryEntry > value ) {
	if ( isRegisteredBinary ( type, value->getEntryInfo ( ) ) )
		throw std::invalid_argument ( "Callback for operator " + Operators::toString ( type ) + " with params " + ext::to_string ( value->getEntryInfo ( ).getParams ( ) ) + " already registered." );

	auto & group = getBinaryEntries ( ) [ type ];

	group.insert ( group.end ( ), std::move ( value ) );
}

void OperatorRegistry::unregisterBinaryInternal ( Operators::BinaryOperators type, const AlgorithmBaseInfo & entryInfo ) {
	auto & group = getBinaryEntries ( ) [ type ];
	auto iter = find_if ( group.begin ( ), group.end ( ), [ & ] ( const std::unique_ptr < BinaryEntry > & entry ) {
				return entry->getEntryInfo ( ).getCategory ( ) == entryInfo.getCategory ( ) && entry->getEntryInfo ( ).getParams ( ) == entryInfo.getParams ( );
			} );

	if ( iter == group.end ( ) )
		throw std::invalid_argument ( "Entry for operator " + Operators::toString ( type ) + " with parameters " + ext::to_string ( entryInfo.getParams ( ) ) + " not registered." );

	group.erase ( iter );
	if ( group.empty ( ) )
		getBinaryEntries ( ).erase ( type );
}

std::shared_ptr < abstraction::OperationAbstraction > OperatorRegistry::getBinaryAbstraction ( Operators::BinaryOperators type, const ext::vector < std::string > & paramTypes, const ext::vector < abstraction::TypeQualifiers::TypeQualifierSet > & typeQualifiers, AlgorithmCategories::AlgorithmCategory category ) {
	const auto & group = getBinaryEntries ( ) [ type ];

	return getOverload ( group, paramTypes, typeQualifiers, category );
}

ext::list < ext::pair < Operators::BinaryOperators, AlgorithmFullInfo > > OperatorRegistry::listBinaryOverloads ( ) {
	const auto & group = getBinaryEntries ( );

	ext::list < ext::pair < Operators::BinaryOperators, AlgorithmFullInfo > > res;
	for ( const std::pair < const Operators::BinaryOperators, ext::list < std::unique_ptr < BinaryEntry > > > & overloads : group )
		for ( const std::unique_ptr < BinaryEntry > & entry : overloads.second )
			res.push_back ( ext::make_pair ( overloads.first, entry->getEntryInfo ( ) ) );

	return res;
}

ext::map < Operators::PrefixOperators, ext::list < std::unique_ptr < OperatorRegistry::PrefixEntry > > > & OperatorRegistry::getPrefixEntries ( ) {
	static ext::map < Operators::PrefixOperators, ext::list < std::unique_ptr < PrefixEntry > > > algorithmGroups;
	return algorithmGroups;
}

bool OperatorRegistry::isRegisteredPrefix ( Operators::PrefixOperators type, const AlgorithmBaseInfo & entryInfo ) {
	const auto & group = getPrefixEntries ( ) [ type ];

	return std::any_of ( group.begin ( ), group.end ( ), [ & ] ( const std::unique_ptr < PrefixEntry > & entry ) { return entry->getEntryInfo ( ).getCategory ( ) == entryInfo.getCategory ( ) && entry->getEntryInfo ( ).getParams ( ) == entryInfo.getParams ( ); } );
}

void OperatorRegistry::registerPrefixInternal ( Operators::PrefixOperators type, std::unique_ptr < PrefixEntry > value ) {
	if ( isRegisteredPrefix ( type, value->getEntryInfo ( ) ) )
		throw std::invalid_argument ( "Callback for operator " + Operators::toString ( type ) + " with params " + ext::to_string ( value->getEntryInfo ( ).getParams ( ) ) + " already registered." );

	auto & group = getPrefixEntries ( ) [ type ];

	group.insert ( group.end ( ), std::move ( value ) );
}

void OperatorRegistry::unregisterPrefixInternal ( Operators::PrefixOperators type, const AlgorithmBaseInfo & entryInfo ) {
	auto & group = getPrefixEntries ( ) [ type ];
	auto iter = find_if ( group.begin ( ), group.end ( ), [ & ] ( const std::unique_ptr < PrefixEntry > & entry ) {
				return entry->getEntryInfo ( ).getCategory ( ) == entryInfo.getCategory ( ) && entry->getEntryInfo ( ).getParams ( ) == entryInfo.getParams ( );
			} );

	if ( iter == group.end ( ) )
		throw std::invalid_argument ( "Entry for operator " + Operators::toString ( type ) + " with parameters " + ext::to_string ( entryInfo.getParams ( ) ) + " not registered." );

	group.erase ( iter );
	if ( group.empty ( ) )
		getPrefixEntries ( ).erase ( type );
}

std::shared_ptr < abstraction::OperationAbstraction > OperatorRegistry::getPrefixAbstraction ( Operators::PrefixOperators type, const ext::vector < std::string > & paramTypes, const ext::vector < abstraction::TypeQualifiers::TypeQualifierSet > & typeQualifiers, AlgorithmCategories::AlgorithmCategory category ) {
	const auto & group = getPrefixEntries ( ) [ type ];

	return getOverload ( group, paramTypes, typeQualifiers, category );
}

ext::list < ext::pair < Operators::PrefixOperators, AlgorithmFullInfo > > OperatorRegistry::listPrefixOverloads ( ) {
	const auto & group = getPrefixEntries ( );

	ext::list < ext::pair < Operators::PrefixOperators, AlgorithmFullInfo > > res;
	for ( const std::pair < const Operators::PrefixOperators, ext::list < std::unique_ptr < PrefixEntry > > > & overloads : group )
		for ( const std::unique_ptr < PrefixEntry > & entry : overloads.second )
			res.push_back ( ext::make_pair ( overloads.first, entry->getEntryInfo ( ) ) );

	return res;
}

ext::map < Operators::PostfixOperators, ext::list < std::unique_ptr < OperatorRegistry::PostfixEntry > > > & OperatorRegistry::getPostfixEntries ( ) {
	static ext::map < Operators::PostfixOperators, ext::list < std::unique_ptr < PostfixEntry > > > algorithmGroups;
	return algorithmGroups;
}

bool OperatorRegistry::isRegisteredPostfix ( Operators::PostfixOperators type, const AlgorithmBaseInfo & entryInfo ) {
	const auto & group = getPostfixEntries ( ) [ type ];

	return std::any_of ( group.begin ( ), group.end ( ), [ & ] ( const std::unique_ptr < PostfixEntry > & entry ) { return entry->getEntryInfo ( ).getCategory ( ) == entryInfo.getCategory ( ) && entry->getEntryInfo ( ).getParams ( ) == entryInfo.getParams ( ); } );
}

void OperatorRegistry::registerPostfixInternal ( Operators::PostfixOperators type, std::unique_ptr < PostfixEntry > value ) {
	if ( isRegisteredPostfix ( type, value->getEntryInfo ( ) ) )
		throw std::invalid_argument ( "Callback for operator " + Operators::toString ( type ) + " with params " + ext::to_string ( value->getEntryInfo ( ).getParams ( ) ) + " already registered." );

	auto & group = getPostfixEntries ( ) [ type ];

	group.insert ( group.end ( ), std::move ( value ) );
}

void OperatorRegistry::unregisterPostfixInternal ( Operators::PostfixOperators type, const AlgorithmBaseInfo & entryInfo ) {
	auto & group = getPostfixEntries ( ) [ type ];
	auto iter = find_if ( group.begin ( ), group.end ( ), [ & ] ( const std::unique_ptr < PostfixEntry > & entry ) {
				return entry->getEntryInfo ( ).getCategory ( ) == entryInfo.getCategory ( ) && entry->getEntryInfo ( ).getParams ( ) == entryInfo.getParams ( );
			} );

	if ( iter == group.end ( ) )
		throw std::invalid_argument ( "Entry for operator " + Operators::toString ( type ) + " with parameters " + ext::to_string ( entryInfo.getParams ( ) ) + " not registered." );

	group.erase ( iter );
	if ( group.empty ( ) )
		getPostfixEntries ( ).erase ( type );
}

std::shared_ptr < abstraction::OperationAbstraction > OperatorRegistry::getPostfixAbstraction ( Operators::PostfixOperators type, const ext::vector < std::string > & paramTypes, const ext::vector < abstraction::TypeQualifiers::TypeQualifierSet > & typeQualifiers, AlgorithmCategories::AlgorithmCategory category ) {
	const auto & group = getPostfixEntries ( ) [ type ];

	return getOverload ( group, paramTypes, typeQualifiers, category );
}

ext::list < ext::pair < Operators::PostfixOperators, AlgorithmFullInfo > > OperatorRegistry::listPostfixOverloads ( ) {
	const auto & group = getPostfixEntries ( );

	ext::list < ext::pair < Operators::PostfixOperators, AlgorithmFullInfo > > res;
	for ( const std::pair < const Operators::PostfixOperators, ext::list < std::unique_ptr < PostfixEntry > > > & overloads : group )
		for ( const std::unique_ptr < PostfixEntry > & entry : overloads.second )
			res.push_back ( ext::make_pair ( overloads.first, entry->getEntryInfo ( ) ) );

	return res;
}

} /* namespace abstraction */
