#pragma once

#include <alib/list>
#include <common/AlgorithmCategories.hpp>

namespace abstraction {

template < class Entry >
std::shared_ptr < abstraction::OperationAbstraction > getOverload ( const ext::list < std::unique_ptr < Entry > > & overloads, const ext::vector < std::string > & paramTypes, const ext::vector < abstraction::TypeQualifiers::TypeQualifierSet > &, AlgorithmCategories::AlgorithmCategory category ) {
	enum class MatchType {
		EXACT,
		CAST,
		INCOMPATIBLE
	};

	auto incompatibleLambda = [ ] ( MatchType compatibility ) {
		return compatibility == MatchType::INCOMPATIBLE;
	};

	// determine how one can actually map what we have ( paramTypes ) as params to what is available as overloads ( overloads )
	std::vector < std::pair < ext::vector < MatchType >, Entry * > > compatibilityData;
	for ( const std::unique_ptr < Entry > & entry : overloads ) {
		if ( entry->getEntryInfo ( ).getCategory ( ) != category && AlgorithmCategories::AlgorithmCategory::NONE != category )
			continue;

		if ( entry->getEntryInfo ( ).getParams ( ).size ( ) != paramTypes.size ( ) )
			continue;

		ext::vector < MatchType > compatibilityVector;
		for ( size_t i = 0; i < paramTypes.size ( ); ++ i ) {
			MatchType matchType;
			if ( std::get < 0 > ( entry->getEntryInfo ( ).getParams ( ) [ i ] ) == paramTypes [ i ] || std::get < 0 > ( entry->getEntryInfo ( ).getParams ( ) [ i ] ) == "auto" ) {
				matchType = MatchType::EXACT;
			} else if ( abstraction::CastRegistry::castAvailable ( std::get < 0 > ( entry->getEntryInfo ( ).getParams ( ) [ i ] ), paramTypes [ i ], true ) ) {
				matchType = MatchType::CAST;
			} else {
				matchType = MatchType::INCOMPATIBLE;
			}

			compatibilityVector.push_back ( matchType );
		}

		// clear incompatibilities are fitered out
		if ( std::none_of ( compatibilityVector.begin ( ), compatibilityVector.end ( ), incompatibleLambda ) )
			compatibilityData.emplace_back ( std::move ( compatibilityVector ), entry.get ( ) );
	}

	// remaining compatible overloads are examined per parameter and the best option is remembered as overload index that achieved it
	ext::vector < ext::set < unsigned > > winnerList;
	for ( size_t i = 0; i < paramTypes.size ( ); ++ i ) {
		ext::set < unsigned > best;

		unsigned overload = 0;
		for ( const std::pair < ext::vector < MatchType >, Entry * > & data : compatibilityData ) {
			if ( data.first [ i ] == MatchType::EXACT )
				best.insert ( overload );

			++ overload;
		}

		if ( !best.empty ( ) ) {
			winnerList.push_back ( std::move ( best ) );
			continue;
		}

		overload = 0;
		for ( const std::pair < ext::vector < MatchType >, Entry * > & data : compatibilityData ) {
			if ( data.first [ i ] == MatchType::CAST )
				best.insert ( overload );

			++ overload;
		}

		winnerList.push_back ( std::move ( best ) );
	}

	// intersection of everything together finds indexes which are better or of the same quality for all params over all overloads
	ext::set < unsigned > winner { ext::sequence < unsigned > ( 0 ).begin ( ), ext::sequence < unsigned > ( compatibilityData.size ( ) ).end ( ) };
	for ( const ext::set < unsigned > & best : winnerList ) {
		ext::set < unsigned > filtered;
		std::set_intersection ( winner.begin ( ), winner.end ( ), best.begin ( ), best.end ( ), std::inserter ( filtered, filtered.end ( ) ) );
		winner = std::move ( filtered );
	}

	// if there is a sinle winner, return it
	if ( winner.size ( ) == 1 ) {
		return compatibilityData [ * winner.begin ( ) ].second->getAbstraction ( );
	} else if ( winner.size ( ) > 1 ) {
//		throw std::invalid_argument ( "Entry overload " + ext::to_string ( paramTypes ) + " ambiguous." ); FIXME make better overload select algorithm
		return compatibilityData [ * std::next ( winner.begin ( ) ) ].second->getAbstraction ( ); // FIXME terrible hack to get to const variant of get on datastructures...

/*		if(common::GlobalData::verbose)
			common::Streams::err << "Entry overload " + ss.str ( ) + " ambiguous. First selected." << std::endl;*/
	} else {
		throw std::invalid_argument ( "Entry overload not found" );
	}
}

} /* namespace abstraction */

