/*
 * AlgorithmRegistry.hpp
 *
 *  Created on: 11. 7. 2017
 *	  Author: Jan Travnicek
 */

#ifndef _ALGORITHM_REGISTRY_HPP_
#define _ALGORITHM_REGISTRY_HPP_

#include <alib/functional>
#include <alib/memory>
#include <alib/vector>
#include <alib/list>
#include <alib/string>
#include <alib/set>
#include <alib/map>
#include <alib/tuple>
#include <alib/typeinfo>

#include <abstraction/OperationAbstraction.hpp>

#include <common/ParamQualifiers.hpp>
#include <common/AlgorithmCategories.hpp>

#include "AlgorithmRegistryInfo.hpp"

namespace abstraction {

class AlgorithmRegistry {
public:
	class Entry {
		AlgorithmFullInfo m_entryInfo;

		std::string m_documentation;

	public:
		Entry ( AlgorithmFullInfo entryInfo ) : m_entryInfo ( std::move ( entryInfo ) ), m_documentation ( "Documentation not avaiable." ) {
		}

		virtual ~Entry ( ) = default;

		virtual std::shared_ptr < abstraction::OperationAbstraction > getAbstraction ( ) const = 0;

		const AlgorithmFullInfo & getEntryInfo ( ) const {
			return m_entryInfo;
		}

		const std::string & getDocumentation ( ) {
			return m_documentation;
		}

		void setDocumentation ( std::string doc ) {
			m_documentation = std::move ( doc );
		}
	};

private:
	template < class ObjectType, class Return, class ... Params >
	class MemberImpl : public Entry {
		std::function < Return ( typename std::remove_reference < ObjectType >::type *, Params ... ) > m_callback;

	public:
		MemberImpl ( std::array < std::string, sizeof ... ( Params ) > paramNames, std::function < Return ( typename std::remove_reference < ObjectType >::type *, Params ... ) > callback ) : Entry ( AlgorithmFullInfo::methodEntryInfo < ObjectType, Return, Params ... > ( std::move ( paramNames ) ) ), m_callback ( std::move ( callback ) ) {
		}

		std::shared_ptr < abstraction::OperationAbstraction > getAbstraction ( ) const override;
	};

	template < class Return, class ... Params >
	class EntryImpl : public Entry {
		std::function < Return ( Params ... ) > m_callback;

	public:
		EntryImpl ( AlgorithmCategories::AlgorithmCategory category, std::array < std::string, sizeof ... ( Params ) > paramNames, std::function < Return ( Params ... ) > callback ) : Entry ( AlgorithmFullInfo::algorithmEntryInfo < Return, Params ... > ( category, std::move ( paramNames ) ) ), m_callback ( std::move ( callback ) ) {
		}

		std::shared_ptr < abstraction::OperationAbstraction > getAbstraction ( ) const override;
	};

	template < class Return, class ... Params >
	class WrapperImpl : public Entry {
		std::function < std::shared_ptr < abstraction::OperationAbstraction > ( Params ... ) > m_wrapperFinder;

	public:
		WrapperImpl ( std::array < std::string, sizeof ... ( Params ) > paramNames, std::function < std::shared_ptr < abstraction::OperationAbstraction > ( Params ... ) > wrapperFinder ) : Entry ( AlgorithmFullInfo::wrapperEntryInfo < Return, Params ... > ( std::move ( paramNames ) ) ), m_wrapperFinder ( std::move ( wrapperFinder ) ) {
		}

		std::shared_ptr < abstraction::OperationAbstraction > getAbstraction ( ) const override;
	};

	static ext::map < ext::pair < std::string, ext::vector < std::string > >, ext::list < std::unique_ptr < Entry > > > & getEntries ( );

	enum class MatchType {
		EXACT,
		CAST,
		INCOMPATIBLE
	};

	static bool isRegistered ( const std::string & algorithm, const ext::vector < std::string > & templateParams, const AlgorithmBaseInfo & entryInfo );

	static void registerInternal ( std::string algorithm, ext::vector < std::string > templateParams, std::unique_ptr < Entry > value );

	static void unregisterInternal ( const std::string & algorithm, const ext::vector < std::string > & templateParams, const AlgorithmBaseInfo & entryInfo );

	static void setDocumentation ( const std::string & algorithm, const ext::vector < std::string > & templateParams, const AlgorithmBaseInfo & entryInfo, std::string documentation );

	static ext::map < ext::pair < std::string, ext::vector < std::string > >, ext::list < std::unique_ptr < Entry > > >::const_iterator findAbstractionGroup ( const std::string & name, const ext::vector < std::string > & templateParams );

public:
	template < class Algo, class ObjectType, class ... ParamTypes >
	static void setDocumentationOfMethod ( const std::string & methodName, const std::string & documentation ) {
		std::string algorithm = ext::to_string < Algo > ( ) + "::" + methodName;
		ext::vector < std::string > templateParams;

		setDocumentation ( algorithm, templateParams, AlgorithmBaseInfo::methodEntryInfo < ObjectType &, ParamTypes ... > ( ), documentation );
	}

	template < class Algo, class ... ParamTypes >
	static void setDocumentationOfAlgorithm ( AlgorithmCategories::AlgorithmCategory category, const std::string & documentation ) {
		std::string algorithm = ext::to_string < Algo > ( );
		ext::vector < std::string > templateParams = ext::get_template_info ( algorithm );
		algorithm = ext::erase_template_info ( algorithm );

		setDocumentation ( algorithm, templateParams, AlgorithmBaseInfo::algorithmEntryInfo < ParamTypes ... > ( category ), documentation );
	}

	template < class Algo, class ... ParamTypes >
	static void setDocumentationOfWrapper ( const std::string & documentation ) {
		std::string algorithm = ext::to_string < Algo > ( );
		ext::vector < std::string > templateParams = ext::get_template_info ( algorithm );
		algorithm = ext::erase_template_info ( algorithm );

		setDocumentation ( algorithm, templateParams, AlgorithmBaseInfo::wrapperEntryInfo < ParamTypes ... > ( ), documentation );
	}

	template < class Algo, class ObjectType, class ... ParamTypes >
	static void unregisterMethod ( const std::string & methodName ) {
		std::string algorithm = ext::to_string < Algo > ( ) + "::" + methodName;
		ext::vector < std::string > templateParams;

		unregisterInternal ( algorithm, templateParams, AlgorithmBaseInfo::methodEntryInfo < ObjectType &, ParamTypes ... > ( ) );
	}

	template < class Algo, class ... ParamTypes >
	static void unregisterAlgorithm ( AlgorithmCategories::AlgorithmCategory category ) {
		std::string algorithm = ext::to_string < Algo > ( );
		ext::vector < std::string > templateParams = ext::get_template_info ( algorithm );
		algorithm = ext::erase_template_info ( algorithm );

		unregisterInternal ( algorithm, templateParams, AlgorithmBaseInfo::algorithmEntryInfo < ParamTypes ... > ( category ) );
	}

	template < class Algo, class ... ParamTypes >
	static void unregisterWrapper ( ) {
		std::string algorithm = ext::to_string < Algo > ( );
		ext::vector < std::string > templateParams = ext::get_template_info ( algorithm );
		algorithm = ext::erase_template_info ( algorithm );

		unregisterInternal ( algorithm, templateParams, AlgorithmBaseInfo::wrapperEntryInfo < ParamTypes ... > ( ) );
	}

	template < class Algo, class ObjectType, class ReturnType, class ... ParamTypes >
	static void registerMethod ( ReturnType ( ObjectType:: * callback ) ( ParamTypes ... ), const std::string & methodName, std::array < std::string, sizeof ... ( ParamTypes ) > paramNames ) {
		std::string algorithm = ext::to_string < Algo > ( ) + "::" + methodName;
		ext::vector < std::string > templateParams;

		registerInternal ( std::move ( algorithm ), std::move ( templateParams ), std::make_unique < MemberImpl < ObjectType &, ReturnType, ParamTypes ... > > ( std::move ( paramNames ), callback ) );
	}

	template < class Algo, class ObjectType, class ReturnType, class ... ParamTypes >
	static void registerMethod ( ReturnType ( ObjectType:: * callback ) ( ParamTypes ... ) const, const std::string & methodName, std::array < std::string, sizeof ... ( ParamTypes ) > paramNames ) {
		std::string algorithm = ext::to_string < Algo > ( ) + "::" + methodName;
		ext::vector < std::string > templateParams;

		registerInternal ( std::move ( algorithm ), std::move ( templateParams ), std::make_unique < MemberImpl < const ObjectType &, ReturnType, ParamTypes ... > > ( std::move ( paramNames ), callback ) );
	}

	template < class Algo, class ReturnType, class ... ParamTypes >
	static void registerAlgorithm ( ReturnType ( * callback ) ( ParamTypes ... ), AlgorithmCategories::AlgorithmCategory category, std::array < std::string, sizeof ... ( ParamTypes ) > paramNames ) {
		std::string algorithm = ext::to_string < Algo > ( );
		ext::vector < std::string > templateParams = ext::get_template_info ( algorithm );
		algorithm = ext::erase_template_info ( algorithm );

		registerInternal ( std::move ( algorithm ), std::move ( templateParams ), std::make_unique < EntryImpl < ReturnType, ParamTypes ... > > ( category, std::move ( paramNames ), callback ) );
	}

	template < class Algo, class ReturnType, class ... ParamTypes >
	static void registerWrapper ( std::shared_ptr < abstraction::OperationAbstraction > ( * callback ) ( ParamTypes ... ), std::array < std::string, sizeof ... ( ParamTypes ) > paramNames ) {
		std::string algorithm = ext::to_string < Algo > ( );
		ext::vector < std::string > templateParams = ext::get_template_info ( algorithm );
		algorithm = ext::erase_template_info ( algorithm );

		registerInternal ( std::move ( algorithm ), std::move ( templateParams ), std::make_unique < WrapperImpl < ReturnType, ParamTypes ... > > ( std::move ( paramNames ), callback ) );
	}

	static std::shared_ptr < abstraction::OperationAbstraction > getAbstraction ( const std::string & name, const ext::vector < std::string > & templateParams, const ext::vector < std::string > & paramTypes, const ext::vector < ext::set < abstraction::ParamQualifiers::ParamQualifier > > & paramQualifiers, AlgorithmCategories::AlgorithmCategory category );

	static ext::set < ext::pair < std::string, ext::vector < std::string > > > listGroup ( const std::string & group );

	static ext::list < ext::tuple < AlgorithmFullInfo, std::string > > listOverloads ( const std::string & algorithm, const ext::vector < std::string > & templateParams );

	static ext::set < ext::pair < std::string, ext::vector < std::string > > > list ( );
};

} /* namespace abstraction */

#include <abstraction/MemberAbstraction.hpp>
#include <abstraction/AlgorithmAbstraction.hpp>
#include <abstraction/WrapperAbstraction.hpp>

namespace abstraction {

template < class Object, class Return, class ... Params >
std::shared_ptr < abstraction::OperationAbstraction > AlgorithmRegistry::MemberImpl < Object, Return, Params ... >::getAbstraction ( ) const {
	return std::make_shared < abstraction::MemberAbstraction < Object, Return, Params ... > > ( m_callback );
}

template < class Return, class ... Params >
std::shared_ptr < abstraction::OperationAbstraction > AlgorithmRegistry::EntryImpl < Return, Params ... >::getAbstraction ( ) const {
	return std::make_shared < abstraction::AlgorithmAbstraction < Return, Params ... > > ( m_callback );
}

template < class Return, class ... Params >
std::shared_ptr < abstraction::OperationAbstraction > AlgorithmRegistry::WrapperImpl < Return, Params ... >::getAbstraction ( ) const {
	return std::make_shared < abstraction::WrapperAbstraction < Params ... > > ( m_wrapperFinder );
}

} /* namespace abstraction */

#endif /* _ALGORITHM_REGISTRY_HPP_ */
