#pragma once

#include <ext/variant>
#include <object/Object.h>
#include <object/AnyObject.h>

namespace object {

template < class Type >
class ObjectFactoryImpl {
public:
	/**
	 * \brief
	 * Specialisation of the make method for objects that are not from the object hierarchy of Algorithms library
	 */
	static Object construct ( Type && data ) {
		return ObjectFactoryImpl < object::AnyObject < typename std::decay < Type >::type > >::construct ( object::AnyObject < typename std::decay < Type >::type > ( std::forward < Type > ( data ) ) );
	}
};

template < >
class ObjectFactoryImpl < Object > {
public:
	static Object construct ( Object && object ) {
		return std::move ( object );
	}
};

template < >
class ObjectFactoryImpl < const Object & > {
public:
	static Object construct ( const Object & object ) {
		return object;
	}
};

template < >
class ObjectFactoryImpl < Object & > {
public:
	static Object construct ( Object & object ) {
		return object;
	}
};

template < >
class ObjectFactoryImpl < const char * const & > {
public:
	static Object construct ( const char * const string ) {
		return ObjectFactoryImpl < std::string >::construct ( std::string ( string ) );
	}
};

template < >
class ObjectFactoryImpl < char * const & > {
public:
	static Object construct ( char * const string ) {
		return ObjectFactoryImpl < std::string >::construct ( std::string ( string ) );
	}
};

template < >
class ObjectFactoryImpl < const char * & > {
public:
	static Object construct ( const char * const string ) {
		return ObjectFactoryImpl < std::string >::construct ( std::string ( string ) );
	}
};

template < >
class ObjectFactoryImpl < char * & > {
public:
	static Object construct ( char * const string ) {
		return ObjectFactoryImpl < std::string >::construct ( std::string ( string ) );
	}
};

template < >
class ObjectFactoryImpl < const char * const > {
public:
	static Object construct ( const char * const string ) {
		return ObjectFactoryImpl < std::string >::construct ( std::string ( string ) );
	}
};

template < >
class ObjectFactoryImpl < char * const > {
public:
	static Object construct ( char * const string ) {
		return ObjectFactoryImpl < std::string >::construct ( std::string ( string ) );
	}
};

template < >
class ObjectFactoryImpl < const char * > {
public:
	static Object construct ( const char * string ) {
		return ObjectFactoryImpl < std::string >::construct ( std::string ( string ) );
	}
};

template < >
class ObjectFactoryImpl < char * > {
public:
	static Object construct ( char * string ) {
		return ObjectFactoryImpl < std::string >::construct ( std::string ( string ) );
	}
};

namespace details {
	/**
	 * \brief
	 * Helper method to detect variant content and call make method on the object from inside of the variant.
	 */
	template < class Variant >
	static Object processVariant ( Variant && data ) {
		auto visitor = [] < class Type > ( Type && element ) {
			return ObjectFactoryImpl < Type >::construct ( std::forward < Type > ( element ) );
		};

		return ext::visit ( visitor, std::forward < Variant > ( data ) );
	}
}

/**
 * \brief
 * Specialisation of the make method for variants.
 *
 * \details
 * The resulting object is not constructed from the variant itself but from the value stored inside. If the value stored inside the variant is variant again, the process is repeated.
 */
template < class ... Types >
class ObjectFactoryImpl < ext::variant < Types ... > > {
public:
	static Object construct ( ext::variant < Types ... > && data ) {
		return details::processVariant ( std::move ( data ) );
	}
};

/**
 * \brief
 * Specialisation of the make method for variants.
 *
 * \details
 * The resulting object is not constructed from the variant itself but from the value stored inside. If the value stored inside the variant is variant again, the process is repeated.
 */
template < class ... Types >
class ObjectFactoryImpl < const ext::variant < Types ... > & > {
public:
	static Object construct ( const ext::variant < Types ... > & data ) {
		return details::processVariant ( data );
	}
};

/**
 * \brief
 * Specialisation of the make method for variants.
 *
 * \details
 * The resulting object is not constructed from the variant itself but from the value stored inside. If the value stored inside the variant is variant again, the process is repeated.
 */
template < class ... Types >
class ObjectFactoryImpl < ext::variant < Types ... > & > {
public:
	static Object construct ( ext::variant < Types ... > & data ) {
		return details::processVariant ( data );
	}
};

/**
 * Constructor that wraps an object given by a constant reference. Uses clone of the parameter internally.
 */
template < class Type >
class ObjectFactoryImpl < const AnyObject < Type > & > {
public:
	static Object construct ( const AnyObject < Type > & data ) {
		return Object ( data.clone ( ) );
	}
};

/**
 * Constructor that wraps an object given by a reference. Uses clone of the parameter internally.
 */
template < class Type >
class ObjectFactoryImpl < AnyObject < Type > & > {
public:
	static Object construct ( AnyObject < Type > & data ) {
		return Object ( data.clone ( ) );
	}
};

/**
 * Constructor that wraps an object given by an r-value reference. Uses clone of the parameter internally.
 */
template < class Type >
class ObjectFactoryImpl < AnyObject < Type > > {
public:
	static Object construct ( AnyObject < Type > && data ) {
		return Object ( std::move ( data ).clone ( ) );
	}
};

template < class Type = Object >
class ObjectFactory {
public:
	template < class Param >
	static Type construct ( Param && param ) {
		return Type ( std::forward < Param > ( param ) );;
	}
};

template < >
class ObjectFactory < Object > {
public:
	template < class Param >
	static Object construct ( Param && param ) {
		return ObjectFactoryImpl < typename ext::array_to_ptr < Param >::type >::construct ( std::forward < Param > ( param ) );;
	}
};

} /* namespace core */