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

#pragma once

#include <alib/vector>
#include <alib/pair>
#include <alib/typeinfo>
#include <alib/tuple>

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

namespace abstraction {

class AlgorithmBaseInfo {
	AlgorithmCategories::AlgorithmCategory m_category;

	ext::vector < ext::pair < std::string, abstraction::TypeQualifiers::TypeQualifierSet > > m_params;

protected:
	template < class ... ParamTypes >
	static ext::vector < ext::pair < std::string, abstraction::TypeQualifiers::TypeQualifierSet > > convertTypes ( ) {
		ext::vector < ext::pair < std::string, abstraction::TypeQualifiers::TypeQualifierSet > > params { convertType < ParamTypes > ( ) ... };

		return params;
	}

	template < class ReturnType >
	static ext::pair < std::string, abstraction::TypeQualifiers::TypeQualifierSet > convertType ( ) {
		return ext::make_pair ( ext::to_string < typename std::decay < ReturnType >::type > ( ), abstraction::TypeQualifiers::typeQualifiers < ReturnType > ( ) );
	}

public:
	AlgorithmBaseInfo ( AlgorithmCategories::AlgorithmCategory category, ext::vector < ext::pair < std::string, abstraction::TypeQualifiers::TypeQualifierSet > > params ) : m_category ( category ), m_params ( std::move ( params ) ) {
	}

	AlgorithmCategories::AlgorithmCategory getCategory ( ) const {
		return m_category;
	}

	const ext::vector < ext::pair < std::string, abstraction::TypeQualifiers::TypeQualifierSet > > & getParams ( ) const {
		return m_params;
	}

	template < class ObjectType, class ... ParamTypes >
	static AlgorithmBaseInfo methodEntryInfo ( ) {
		AlgorithmCategories::AlgorithmCategory category = AlgorithmCategories::AlgorithmCategory::DEFAULT;

		ext::vector < ext::pair < std::string, abstraction::TypeQualifiers::TypeQualifierSet > > params = convertTypes < ParamTypes ... > ( );
		params.insert ( params.begin ( ), convertType < ObjectType & > ( ) );

		return AlgorithmBaseInfo ( category, std::move ( params ) );
	}

	template < class ... ParamTypes >
	static AlgorithmBaseInfo algorithmEntryInfo ( AlgorithmCategories::AlgorithmCategory category ) {
		ext::vector < ext::pair < std::string, abstraction::TypeQualifiers::TypeQualifierSet > > params = convertTypes < ParamTypes ... > ( );

		return AlgorithmBaseInfo ( category, std::move ( params ) );
	}

	template < class ... ParamTypes >
	static AlgorithmBaseInfo wrapperEntryInfo ( ) {
		AlgorithmCategories::AlgorithmCategory category = AlgorithmCategories::AlgorithmCategory::DEFAULT;

		ext::vector < ext::pair < std::string, abstraction::TypeQualifiers::TypeQualifierSet > > params = convertTypes < ParamTypes ... > ( );

		return AlgorithmBaseInfo ( category, std::move ( params ) );
	}

	static AlgorithmBaseInfo rawEntryInfo ( ext::vector < ext::pair < std::string, abstraction::TypeQualifiers::TypeQualifierSet > > paramSpec ) {
		AlgorithmCategories::AlgorithmCategory category = AlgorithmCategories::AlgorithmCategory::DEFAULT;

		return AlgorithmBaseInfo ( category, std::move ( paramSpec ) );
	}

	template < class ... ParamTypes >
	static AlgorithmBaseInfo operatorEntryInfo ( ) {
		AlgorithmCategories::AlgorithmCategory category = AlgorithmCategories::AlgorithmCategory::DEFAULT;

		ext::vector < ext::pair < std::string, abstraction::TypeQualifiers::TypeQualifierSet > > params = convertTypes < ParamTypes ... > ( );

		return AlgorithmBaseInfo ( category, std::move ( params ) );
	}
};

class AlgorithmFullInfo : public AlgorithmBaseInfo {
	ext::vector < std::string > m_paramNames;

	ext::pair < std::string, abstraction::TypeQualifiers::TypeQualifierSet > m_result;

public:
	AlgorithmFullInfo ( AlgorithmBaseInfo baseInfo, ext::vector < std::string > paramNames, ext::pair < std::string, abstraction::TypeQualifiers::TypeQualifierSet > result ) : AlgorithmBaseInfo ( std::move ( baseInfo ) ), m_paramNames ( std::move ( paramNames ) ), m_result ( std::move ( result ) ) {
	}

	const ext::vector < std::string > & getParamNames ( ) const {
		return m_paramNames;
	}

	const ext::pair < std::string, abstraction::TypeQualifiers::TypeQualifierSet > & getResult ( ) const {
		return m_result;
	}

	ext::pair < std::string, abstraction::TypeQualifiers::TypeQualifierSet > getNormalizedResult ( ) const;

	template < class ObjectType, class ReturnType, class ... ParamTypes >
	static AlgorithmFullInfo methodEntryInfo ( std::array < std::string, sizeof ... ( ParamTypes ) > paramNames ) {
		ext::vector < std::string > parameterNames;
		parameterNames.insert ( parameterNames.end ( ), "object" );
		parameterNames.insert ( parameterNames.end ( ), paramNames.begin ( ), paramNames.end ( ) );

		ext::pair < std::string, abstraction::TypeQualifiers::TypeQualifierSet > result = convertType < ReturnType > ( );

		return AlgorithmFullInfo ( AlgorithmBaseInfo::methodEntryInfo < ObjectType, ParamTypes ... > ( ), std::move ( parameterNames ), std::move ( result ) );
	}

	template < class ReturnType, class ... ParamTypes >
	static AlgorithmFullInfo algorithmEntryInfo ( AlgorithmCategories::AlgorithmCategory category, std::array < std::string, sizeof ... ( ParamTypes ) > paramNames ) {
		ext::pair < std::string, abstraction::TypeQualifiers::TypeQualifierSet > result = convertType < ReturnType > ( );

		ext::vector < std::string > parameterNames ( paramNames.begin ( ), paramNames.end ( ) );

		return AlgorithmFullInfo ( AlgorithmBaseInfo::algorithmEntryInfo < ParamTypes ... > ( category ), std::move ( parameterNames ), std::move ( result ) );
	}

	template < class ReturnType, class ... ParamTypes >
	static AlgorithmFullInfo wrapperEntryInfo ( std::array < std::string, sizeof ... ( ParamTypes ) > paramNames ) {
		ext::pair < std::string, abstraction::TypeQualifiers::TypeQualifierSet > result = convertType < ReturnType > ( );

		ext::vector < std::string > parameterNames ( paramNames.begin ( ), paramNames.end ( ) );

		return AlgorithmFullInfo ( AlgorithmBaseInfo::wrapperEntryInfo < ParamTypes ... > ( ), std::move ( parameterNames ), std::move ( result ) );
	}

	static AlgorithmFullInfo rawEntryInfo ( ext::pair < std::string, abstraction::TypeQualifiers::TypeQualifierSet > result, ext::vector < ext::tuple < std::string, abstraction::TypeQualifiers::TypeQualifierSet, std::string > > paramSpecs ) {
		ext::vector < std::string > parameterNames;
		ext::vector < ext::pair < std::string, abstraction::TypeQualifiers::TypeQualifierSet > > parameterSpecs;
		for ( ext::tuple < std::string, abstraction::TypeQualifiers::TypeQualifierSet, std::string > & paramSpec : paramSpecs ) {
			parameterNames.push_back ( std::move ( std::get < 2 > ( paramSpec ) ) );
			parameterSpecs.push_back ( ext::make_pair ( std::move ( std::get < 0 > ( paramSpec ) ), std::get < 1 > ( paramSpec ) ) );
		}

		return AlgorithmFullInfo ( AlgorithmBaseInfo::rawEntryInfo ( std::move ( parameterSpecs ) ), std::move ( parameterNames ), std::move ( result ) ) ;
	}

	template < class ReturnType, class ... ParamTypes >
	static AlgorithmFullInfo operatorEntryInfo ( ) {
		ext::pair < std::string, abstraction::TypeQualifiers::TypeQualifierSet > result = convertType < ReturnType > ( );

		ext::vector < std::string > parameterNames;
		if constexpr ( sizeof ... ( ParamTypes ) == 1 ) {
			parameterNames.push_back ( "arg" );
		} else if constexpr ( sizeof ... ( ParamTypes ) == 2 ) {
			parameterNames.push_back ( "lhs" );
			parameterNames.push_back ( "rhs" );
		} else {
			static_assert ( "Invalid number of params or operator" );
		}

		return AlgorithmFullInfo ( AlgorithmBaseInfo::operatorEntryInfo < ParamTypes ... > ( ), std::move ( parameterNames ), std::move ( result ) );
	}

};

} /* namespace abstraction */

