#include <iostream>
using namespace std;

#include <assert.h> 
#include <exception>
#include <map>
#include <memory>
#include <typeindex>
#include <unordered_map>

// Predefine template delegate factory
template < typename R, typename... Args > 
class brGenericDelegate ;

// C++11 template alias to announce functor definition
template < typename R, typename... Args > 
using brGenericDelegateType = std::function< std::shared_ptr<R>(Args...) > ;

class brDelegate
{
protected:
	brDelegate(){}
	
public:

    virtual ~brDelegate() = default ;

    template < typename R, typename... Args >
    static std::shared_ptr<brDelegate> create( typename brGenericDelegate<R,Args...>::functor func ) 
	{ 
		return std::make_shared<brGenericDelegate<R,Args...>>(func) ; 
	}

    template < typename R, typename... Args > std::shared_ptr<R> run( Args... args ) const
    {
        using derived_type = brGenericDelegate<R,Args...> ;
        return dynamic_cast< const derived_type& >(*this)(args...) ;
    }
};

template < typename R, typename... Args > 
class brGenericDelegate : public brDelegate
{
public:
    using functor = brGenericDelegateType< R, Args... >;
    brGenericDelegate( functor f ) : fn(f) {}

    std::shared_ptr<R> operator() ( Args... args ) const { return fn(args...) ; }

private:
    const functor fn ;
};


class brIOCContainer 
{ 	
private:	
    // Define generic resolver template
    template < class R> 
    class GenericResolver;
			
	class Resolver
	{
	protected:
		Resolver(){}
		
	public:
		/** @brief Destructor */
		virtual ~Resolver() = default;
		
		template<typename R, typename ... ARGS>
		std::shared_ptr<R> run(ARGS&& ... args) const
		{
			using derived_type = GenericResolver<R>;		
			auto rs = dynamic_cast< const derived_type& >(*this);
			return rs.template run<ARGS...>(std::forward<ARGS>(args)...);
		}		
	};
	
	template <typename T>
	class GenericResolver : public Resolver
	{
	public:	
		GenericResolver(std::shared_ptr<brDelegate> delegate)
		:m_delegate(delegate){}
		
		virtual ~GenericResolver() = default;
		
		template<typename ... ARGS>
		std::shared_ptr<T> run(ARGS&& ... args) const
		{
			return //std::make_shared<T>(
				m_delegate->run<T, ARGS...>(std::forward<ARGS>(args)...);
				//);
		}
		
		std::shared_ptr<brDelegate> getDelegate(void) const {
			return m_delegate;
		}
		
	private:
		std::shared_ptr<brDelegate> m_delegate = nullptr;
	};
	
	class Component
	{
	public:
		Component(std::type_index  type) : m_type(type){}
		~Component(){}

		std::type_index getType(void) const {
			return m_type;
		}
		
		std::shared_ptr<Resolver> getResolver(void){
			return m_resolver;
		}
		
		void setResolver(std::shared_ptr<Resolver> resolver){
			m_resolver = resolver;
		}
		
	private:
		std::type_index m_type;
		std::shared_ptr<Resolver> m_resolver = nullptr;
	};
	
public:   
	typedef std::multimap<std::type_index, std::shared_ptr<Component>> RegistryLookup_t;
	typedef std::pair<std::type_index, std::shared_ptr<Component>> RegistryEntry_t;

	public:   
	brIOCContainer(){}
	brIOCContainer(const brIOCContainer& copy){
		m_repository = copy.m_repository;
	}
	virtual ~brIOCContainer(){}
   		
	template <typename T>
	void registerType(void);	
			
	template <typename T, typename C>
	void registerByContract(void);
	
	template <typename T, typename ... ARGS>
    std::shared_ptr<T> resolve(ARGS&& ... args) const;
 	
	template <typename T>
	bool contains(void) const;
	
	template <typename T, typename C>
	bool contains(void) const;
			
private:
	template <typename T>
	void validate(void);
	
	RegistryLookup_t::const_iterator find(std::type_index type) const{
		auto iter = m_repository.begin();
		while(iter!=m_repository.end()){			
			if((iter->first) == type ||
			   (iter->second)->getType() == type){
				break;
			}
			iter++;
		}
		return iter;
	}
						
	/** Registered elements */
	RegistryLookup_t m_repository;
};

template <typename T>
inline void brIOCContainer::registerType(void)
{
	// check if type has been already registered 
	auto iter = this->find(typeid(T));
	if(iter!=m_repository.end()){
		std::string msg = "[brIOCContainer]:registerType: A type: <";
		msg.append(typeid(T).name());		
		msg.append("> has been already registered!");
		throw std::runtime_error(msg.c_str());
	}
	
	// create delegate for transient object creation
	auto delegate = brDelegate::create<T>([]() -> std::shared_ptr<T> { return std::make_shared<T>(); });
	
	// create component and register resolver using defined delegate
	auto component = std::make_shared<Component>(typeid(T));	
	component->setResolver(std::make_shared<GenericResolver<T>>(delegate));	
	m_repository.insert(RegistryEntry_t(typeid(T), component));
}

template <typename T, typename C>
inline void brIOCContainer::registerByContract(void)
{
    // check if contract and type definitions are compatible
	assert((std::is_base_of<C,T>::value));

	// check if type is already registered by contract
	if((this->contains<T,C>())){
		std::string msg = "[brIOCContainer]:registerByContract: An unnamed type: <";
		msg.append(typeid(T).name());		
		msg.append("> has been already registered by contract!");
		throw std::runtime_error(msg.c_str());			
	}
		
	// create delegate for transient object creation based on concrete type
	auto delegate = brDelegate::create<C>([]() -> std::shared_ptr<C> { 
		//return dynamic_pointer_cast<C>(std::make_shared<T>()); 
		return std::make_shared<T>();
	});
			
	/* Set up component of the concrete type T and register this component
	   using the contract type */
	auto component = std::make_shared<Component>(typeid(C));
	component->setResolver(std::make_shared<GenericResolver<C>>(delegate));		
	m_repository.insert(RegistryEntry_t(typeid(C), component));
}

template <typename T, typename ... ARGS>
inline std::shared_ptr<T> brIOCContainer::resolve(ARGS&& ... args) const
{	
	std::type_index type = typeid(T);
	/*if(std::is_abstract<T>::value){
		std::string msg = "[brIOCContainer]:resolve: Cant resolve object of abstract type: ";
		msg.append(type.name());		
		throw std::runtime_error(msg.c_str());
	}*/
	
	auto iter = this->find(type);
	if(iter==m_repository.end()){
		std::string msg = "[brIOCContainer]:resolve: No entry found for requested class type: ";
		msg.append(type.name());		
		throw std::runtime_error(msg.c_str());
	}
	
	auto resolver = (iter->second)->getResolver();
	return resolver->run<T, ARGS...>(std::forward<ARGS>(args)...);			
}

template <typename T>
inline bool brIOCContainer::contains(void) const
{
	std::type_index type = typeid(T);	
    auto iter = m_repository.find(type);	
	return iter!=m_repository.end() ? true : false;
}

template <typename T, typename C>
inline bool brIOCContainer::contains(void) const
{
	bool result = false;
	std::type_index type = typeid(T);
	
	for(auto range = m_repository.equal_range(typeid(C));
	    range.first != range.second; ++range.first){

		if(type == (range.first->second)->getType()){
			result = true;
			break;
		}
	}	
	return result;
}

class IVehicle
{
public:
	IVehicle(){}
	virtual ~IVehicle() = default;

	virtual const std::string& getBrand(void) const = 0;
};

class Car : public IVehicle
{
public:
	Car():IVehicle(){}
	Car(const std::string& name):IVehicle(), m_name(name){}
	
	const std::string& getBrand(void) const override{
		return m_name;
	}
	
private:
	std::string m_name = "Mustang GT 500";
};

class Truck : public IVehicle
{
public:
	Truck():IVehicle(){}
	Truck(const std::string& name):IVehicle(), m_name(name){}
	
	const std::string& getBrand(void) const override{
		return m_name;
	}
	
private:
	std::string m_name = "MAN TGX EfficientLine 2 in long-haul transport";
};


int main() {
	
	brIOCContainer container;
	container.registerType<Truck>();
	
	auto truck = container.resolve<Truck>();
	std::cout << "Got a truck: " << truck->getBrand() << std::endl;
	
	container.registerType<Car>();
	
	// This resolve is working
	auto car1 = container.resolve<Car>();
	std::cout << "Got a vehicle: " << car1->getBrand() << std::endl;
	
	container.registerByContract<Car, IVehicle>();
	
	// This resolve occurs in a compiler failure
	auto car2 = container.resolve<IVehicle>();
	std::cout << "Got a vehicle: " << car2->getBrand() << std::endl;
	
	return 0;
}