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

#ifndef _CAST_REGISTRY_HPP_
#define _CAST_REGISTRY_HPP_

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

#include <abstraction/OperationAbstraction.hpp>
#include "BaseRegistryEntry.hpp"

namespace abstraction {

class CastRegistry {
public:
	class Entry : public BaseRegistryEntry {
		bool m_isExplicit;

	public:
		Entry ( bool p_isExplicit ) : m_isExplicit ( p_isExplicit ) {
		}

		bool isExplicit ( ) {
			return m_isExplicit;
		}
	};

private:
	template < class Return, class Param >
	class DefaultEntryImpl : public Entry {
	public:
		DefaultEntryImpl ( bool isExplicit ) : Entry ( isExplicit ) {
		}

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

	template < class Return, class Param >
	class AlgorithmEntryImpl : public Entry {
		std::function < Return ( Param ) > m_callback;

	public:
		AlgorithmEntryImpl ( std::function < Return ( Param ) > callback, bool isExplicit ) : Entry ( isExplicit ), m_callback ( std::move ( callback ) ) {
		}

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

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

public:
	static void unregisterCast ( const std::string & target, const std::string & param );

	template < class TargetType, class ParamType >
	static void unregisterCast ( ) {
		std::string target = ext::to_string < TargetType > ( );
		std::string param = ext::to_string < ParamType > ( );

		unregisterCast ( target, param );
	}

	static void registerCast ( std::string target, std::string param, std::unique_ptr < Entry > entry );

	template < class TargetType, class ParamType >
	static void registerCast ( std::string target, std::string param, bool isExplicit = false ) {
		registerCast ( std::move ( target ), std::move ( param ), std::unique_ptr < Entry > ( new DefaultEntryImpl < TargetType, ParamType > ( isExplicit ) ) );
	}

	template < class TargetType, class ParamType >
	static void registerCast ( bool isExplicit = false ) {
		std::string target = ext::to_string < TargetType > ( );
		std::string param = ext::to_string < ParamType > ( );

		registerCast < TargetType, ParamType > ( std::move ( target ), std::move ( param ), isExplicit );
	}

	template < class TargetType, class ParamType >
	static void registerCastAlgorithm ( std::string target, std::string param, TargetType ( * callback ) ( const ParamType & ), bool isExplicit = false ) {
		registerCast ( std::move ( target ), std::move ( param ), std::unique_ptr < Entry > ( new AlgorithmEntryImpl < TargetType, const ParamType & > ( callback, isExplicit ) ) );
	}

	template < class TargetType, class ParamType >
	static void registerCastAlgorithm ( TargetType ( * callback ) ( const ParamType & ), bool isExplicit = false ) {
		std::string target = ext::to_string < TargetType > ( );
		std::string param = ext::to_string < ParamType > ( );

		registerCastAlgorithm < TargetType, ParamType > ( std::move ( target ), std::move ( param ), callback, isExplicit );
	}

	template < class TargetType, class ParamType >
	static void registerCastAlgorithm ( std::string target, std::string param, TargetType ( * callback ) ( ParamType ), bool isExplicit = false ) {
		registerCast ( std::move ( target ), std::move ( param ), std::unique_ptr < Entry > ( new AlgorithmEntryImpl < TargetType, ParamType > ( callback, isExplicit ) ) );
	}

	template < class TargetType, class ParamType >
	static void registerCastAlgorithm ( TargetType ( * callback ) ( ParamType ), bool isExplicit = false ) {
		std::string target = ext::to_string < TargetType > ( );
		std::string param = ext::to_string < ParamType > ( );

		registerCastAlgorithm < TargetType, ParamType > ( std::move ( target ), std::move ( param ), callback, isExplicit );
	}

	static bool isNoOp ( const std::string & target, const std::string & param );

	static bool castAvailable ( const std::string & target, const std::string & param, bool implicitOnly );

	static std::shared_ptr < abstraction::OperationAbstraction > getAbstraction ( const std::string & target, const std::string & param );

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

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

	static ext::list < ext::tuple < std::string, std::string, bool > > list ( );
};

} /* namespace abstraction */

#include <abstraction/CastAbstraction.hpp>
#include <abstraction/AlgorithmAbstraction.hpp>

namespace abstraction {

template < class Return, class Param >
std::shared_ptr < abstraction::OperationAbstraction > CastRegistry::DefaultEntryImpl < Return, Param >::getAbstraction ( ) const {
	return std::make_shared < abstraction::CastAbstraction < Return, const Param & > > ( );
}

template < class Return, class Param >
std::shared_ptr < abstraction::OperationAbstraction > CastRegistry::AlgorithmEntryImpl < Return, Param >::getAbstraction ( ) const {
	return std::make_shared < abstraction::AlgorithmAbstraction < Return, const Param & > > ( m_callback );
}

} /* namespace abstraction */

#endif /* _CAST_REGISTRY_HPP_ */
