#ifndef _ALGO_REGISTRATION_HPP_
#define _ALGO_REGISTRATION_HPP_

#include <alib/registration>

#include <registry/AlgorithmRegistry.hpp>

#include <registration/NormalizationRegistration.hpp>

namespace registration {

class AlgoRegisterHelper {
public:
	template < size_t ParameterTypesNumber, class ... ParamNames >
	static std::array < std::string, ParameterTypesNumber > generateNames ( ParamNames ... paramNames ) {
		if constexpr ( sizeof ... ( ParamNames ) > ParameterTypesNumber ) {
			static_assert ( "Too many parameter names" );
		} else {
			std::array < std::string, ParameterTypesNumber > parameterNames = { { paramNames ... } };
			for ( size_t i = sizeof ... ( ParamNames ); i < ParameterTypesNumber; ++ i )
				parameterNames [ i ] = "arg" + ext::to_string ( i );

			return parameterNames;
		}
	}
};

template < class Algorithm, class ReturnType, class ... ParameterTypes >
class AbstractRegister : public ext::Register < void > {
	registration::NormalizationRegister < ReturnType > normalize;

	abstraction::AlgorithmCategories::AlgorithmCategory m_category;

public:
	template < class ... ParamNames >
	AbstractRegister ( ReturnType ( * callback ) ( ParameterTypes ... ), abstraction::AlgorithmCategories::AlgorithmCategory category, ParamNames ... paramNames ) : ext::Register < void > ( [=] ( ) {
				std::array < std::string, sizeof ... ( ParameterTypes ) > parameterNames = AlgoRegisterHelper::generateNames < sizeof ... ( ParameterTypes ) > ( paramNames ... );

				abstraction::AlgorithmRegistry::registerAlgorithm < Algorithm, ReturnType, ParameterTypes ... > ( callback, category, std::move ( parameterNames ) );
			}, [=] ( ) {
				abstraction::AlgorithmRegistry::unregisterAlgorithm < Algorithm, ParameterTypes ... > ( category );
			} ), m_category ( category ) {
	}

	template < class ... ParamNames >
	AbstractRegister ( ReturnType ( * callback ) ( ParameterTypes ... ), ParamNames ... paramNames ) : AbstractRegister ( callback, abstraction::AlgorithmCategories::AlgorithmCategory::DEFAULT, paramNames ... ) {
	}

	AbstractRegister ( AbstractRegister && ) noexcept = default;

	AbstractRegister && setDocumentation ( std::string documentation ) && {
		abstraction::AlgorithmRegistry::setDocumentationOfAlgorithm < Algorithm, ParameterTypes ... > ( m_category, std::move ( documentation ) );
		return std::move ( * this );
	}
};

template < class Algorithm, class ReturnType, class ... ParameterTypes >
class WrapperRegister : public ext::Register < void > {
public:
	template < class ... ParamNames >
	WrapperRegister ( std::shared_ptr < abstraction::OperationAbstraction > ( * callback ) ( ParameterTypes ... ), ParamNames ... paramNames ) : ext::Register < void > ( [=] ( ) {
				std::array < std::string, sizeof ... ( ParameterTypes ) > parameterNames = AlgoRegisterHelper::generateNames < sizeof ... ( ParameterTypes ) > ( paramNames ... );

				abstraction::AlgorithmRegistry::registerWrapper < Algorithm, ReturnType, ParameterTypes ... > ( callback, std::move ( parameterNames ) );
			}, [=] ( ) {
				abstraction::AlgorithmRegistry::unregisterWrapper < Algorithm, ParameterTypes ... > ( );
			} ) {
	}

	WrapperRegister ( WrapperRegister && ) noexcept = default;

	WrapperRegister && setDocumentation ( std::string documentation ) && {
		abstraction::AlgorithmRegistry::setDocumentationOfWrapper < Algorithm, ParameterTypes ... > ( std::move ( documentation ) );
		return std::move ( * this );
	}
};

template < class Algorithm, class ReturnType, class ObjectType, class ... ParameterTypes >
class MethodRegister : public ext::Register < void > {
	registration::NormalizationRegister < ReturnType > normalize;

	std::string m_methodName;

public:
	template < class ... ParamNames >
	MethodRegister ( ReturnType ( ObjectType::* callback ) ( ParameterTypes ... ), std::string methodName, ParamNames ... paramNames ) : ext::Register < void > ( [=] ( ) {
				std::array < std::string, sizeof ... ( ParameterTypes ) > parameterNames = AlgoRegisterHelper::generateNames < sizeof ... ( ParameterTypes ) > ( paramNames ... );

				abstraction::AlgorithmRegistry::registerMethod < Algorithm > ( callback, methodName, std::move ( parameterNames ) );
			}, [=] ( ) {
				abstraction::AlgorithmRegistry::unregisterMethod < Algorithm, ObjectType, ParameterTypes ... > ( methodName );
			} ), m_methodName ( methodName ) {
	}

	MethodRegister ( MethodRegister && ) noexcept = default;

	MethodRegister && setDocumentation ( std::string documentation ) && {
		abstraction::AlgorithmRegistry::setDocumentationOfMethod < Algorithm, ObjectType, ParameterTypes ... > ( m_methodName, std::move ( documentation ) );
		return std::move ( * this );
	}

};

template < class Algorithm, class ReturnType, class ObjectType, class ... ParameterTypes >
class MethodRegister < Algorithm, ReturnType, const ObjectType, ParameterTypes ... > : public ext::Register < void > {
	registration::NormalizationRegister < ReturnType > normalize;

	std::string m_methodName;

public:
	template < class ... ParamNames >
	MethodRegister ( ReturnType ( ObjectType::* callback ) ( ParameterTypes ... ) const, std::string methodName, ParamNames ... paramNames ) : ext::Register < void > ( [=] ( ) {
				std::array < std::string, sizeof ... ( ParameterTypes ) > parameterNames = AlgoRegisterHelper::generateNames < sizeof ... ( ParameterTypes ) > ( paramNames ... );

				abstraction::AlgorithmRegistry::registerMethod < Algorithm > ( callback, methodName, std::move ( parameterNames ) );
			}, [=] ( ) {
				abstraction::AlgorithmRegistry::unregisterMethod < Algorithm, const ObjectType, ParameterTypes ... > ( methodName );
			} ), m_methodName ( methodName ) {
	}

	MethodRegister ( MethodRegister && ) noexcept = default;

	MethodRegister && setDocumentation ( std::string documentation ) && {
		abstraction::AlgorithmRegistry::setDocumentationOfMethod < Algorithm, const ObjectType, ParameterTypes ... > ( m_methodName, std::move ( documentation ) );
		return std::move ( * this );
	}

};

} /* namespace registration */

#endif // _ALGO_REGISTRATION_HPP_
