/*
 * CastRegistry.cpp
 *
 *  Created on: 21. 7. 2017
 *	  Author: Jan Travnicek
 */

#include <registry/CastRegistry.hpp>

#include <alib/algorithm>
#include <exception>

namespace abstraction {

ext::map < ext::pair < std::string, std::string >, std::unique_ptr < CastRegistry::Entry > > & CastRegistry::getEntries ( ) {
	static ext::map < ext::pair < std::string, std::string >, std::unique_ptr < Entry > > casts;
	return casts;
}

void CastRegistry::unregisterCast ( const std::string & target, const std::string & param ) {
	if ( getEntries ( ).erase ( ext::tie ( target, param ) ) == 0u )
		throw std::invalid_argument ( "Entry from " + param + " to " + target + " not registered." );
}

void CastRegistry::registerCast ( std::string target, std::string param, std::unique_ptr < Entry > entry ) {
	auto iter = getEntries ( ).insert ( std::make_pair ( ext::make_pair ( std::move ( target ), std::move ( param ) ), std::move ( entry ) ) );
	if ( ! iter.second )
		throw std::invalid_argument ( "Entry from " + iter.first->first.second + " to " + iter.first->first.first + " already registered." );
}

std::shared_ptr < abstraction::OperationAbstraction > CastRegistry::getAbstraction ( const std::string & target, const std::string & param ) {
	auto entry = getEntries ( ).end ( );
	for ( auto iter = getEntries ( ).begin ( ); iter != getEntries ( ).end ( ); ++ iter )
		if ( iter->first.second == param && ext::is_same_type ( target, ext::erase_template_info ( iter->first.first ) ) ) {
			if ( entry == getEntries ( ).end ( ) )
				entry = iter;
			else
				throw std::invalid_argument ( "Entry from " + param + " to " + target + " is ambigous." );
		}

	if ( entry == getEntries ( ).end ( ) )
		throw std::invalid_argument ( "Entry from " + param + " to " + target + " not available." );

	return entry->second->getAbstraction ( );
}

bool CastRegistry::isNoOp ( const std::string & target, const std::string & param ) {
	return ext::is_same_type ( ext::erase_template_info ( target ), ext::erase_template_info ( param ) );
}

bool CastRegistry::castAvailable ( const std::string & target, const std::string & param, bool implicitOnly ) {
	return std::any_of ( getEntries ( ).begin ( ), getEntries ( ).end ( ), [ & ] ( const std::pair < const ext::pair < std::string, std::string >, std::unique_ptr < Entry > > & entry ) {
		return ( entry.first.second == param && ext::is_same_type ( target, ext::erase_template_info ( entry.first.first ) ) )
			&& ( ! implicitOnly || ! entry.second->isExplicit ( ) );
		} );
}

ext::list < ext::pair < std::string, bool > > CastRegistry::listFrom ( const std::string & type ) {
	ext::list < ext::pair < std::string, bool > > res;

	for ( const std::pair < const ext::pair < std::string, std::string >, std::unique_ptr < Entry > > & entry : getEntries ( ) )
		if ( ext::is_same_type ( type, ext::erase_template_info ( entry.first.second ) ) )
			res.push_back ( ext::make_pair ( entry.first.first, entry.second->isExplicit ( ) ) );

	return res;
}

ext::list < ext::pair < std::string, bool > > CastRegistry::listTo ( const std::string & type ) {
	ext::list < ext::pair < std::string, bool > > res;

	for ( const std::pair < const ext::pair < std::string, std::string >, std::unique_ptr < Entry > > & entry : getEntries ( ) )
		if ( ext::is_same_type ( type, ext::erase_template_info ( entry.first.first ) ) )
			res.push_back ( ext::make_pair ( entry.first.second, entry.second->isExplicit ( ) ) );

	return res;
}

ext::list < ext::tuple < std::string, std::string, bool > > CastRegistry::list ( ) {
	ext::list < ext::tuple < std::string, std::string, bool > > res;

	std::transform ( getEntries ( ).begin ( ), getEntries ( ).end ( ), std::back_inserter ( res ), [ ] ( const std::pair < const ext::pair < std::string, std::string >, std::unique_ptr < Entry > > & entry ) {
		return ext::make_tuple ( entry.first.first, entry.first.second, entry.second->isExplicit ( ) );
	} );

	return res;
}

} /* namespace abstraction */
