#ifndef objectmanagerH_included
#define objectmanagerH_included

#include <assert.h>
#include <vector>

#include <utils/rtti.h>
#include <utils/refcount.h>

// #TODO: Make IRefCount or templated class for AddRef\Release and Ref Counting implementation

// \brief Base interface for all objects.
class IObjectBase : public RefCount
{
public:
	// Virtual destructor
	virtual ~IObjectBase() {}

	// Get RuntimeClass.
	virtual const RuntimeClass* GetRuntimeClass() const = 0;
};

// \brief Dynamic Cast
template < typename T >
T* DynamicCast( IObjectBase* _pObject )
{
	const RuntimeClass* pRuntimeClass = RUNTIME_CLASS( T );

	if ( _pObject->GetRuntimeClass()->IsKindOf( pRuntimeClass ) )
		return static_cast< T* >( _pObject );

	return nullptr;
}

template <class T>
class _NoAddRefReleaseOnTPtr : public T
{
private:
	virtual uint64_t AddRef() = 0;
	virtual uint64_t Release() = 0;
};

// TPtrBase provides the basis for all other smart pointers
// The other smartpointers add their own constructors and operators
template <class T>
class TPtrBase
{
protected:
	TPtrBase()
	{
		p = NULL;
	}

	TPtrBase(T* lp)
	{
		p = lp;
		if (p != NULL)
			p->AddRef();
	}

public:
	typedef T _PtrClass;

	~TPtrBase() 
	{
		if (p)
			p->Release();
	}

	operator T* () const
	{
		return p;
	}

	T& operator*() const
	{
		assert(p != NULL);
		return *p;
	}

	//The assert on operator& usually indicates a bug.  If this is really
	//what is needed, however, take the address of the p member explicitly.
	T** operator&()
	{
		assert(p == NULL);
		return &p;
	}

	_NoAddRefReleaseOnTPtr<T>* operator->() const
	{
		assert(p != NULL);
		return (_NoAddRefReleaseOnTPtr<T>*)p;
	}

	bool operator!() const
	{
		return (p == NULL);
	}

	bool operator<(T* pT) const
	{
		return p < pT;
	}

	bool operator!=(T* pT) const
	{
		return !operator==(pT);
	}

	bool operator==(T* pT) const
	{
		return p == pT;
	}

	// Release the interface and set to NULL
	void Release()
	{
		T* pTemp = p;
		if (pTemp)
		{
			p = NULL;
			pTemp->Release();
		}
	}

	// Compare two objects for equivalence
	bool IsEqualObject(IObjectBase* pOther)
	{
		if (p == NULL && pOther == NULL)
			return true;	// They are both NULL objects

		if (p == NULL || pOther == NULL)
			return false;	// One is NULL the other is not

		return p->GetRuntimeClass()->IsKindOf( pOther->GetRuntimeClass() );

	/*	CComPtr<IUnknown> punk1;
		CComPtr<IUnknown> punk2;
		p->QueryInterface(__uuidof(IUnknown), (void**)&punk1);
		pOther->QueryInterface(__uuidof(IUnknown), (void**)&punk2);
		return punk1 == punk2;*/
	}

	// Attach to an existing interface (does not AddRef)
	void Attach(T* p2) throw()
	{
		if (p)
			p->Release();

		p = p2;
	}

	// Detach the interface (does not Release)
	T* Detach() throw()
	{
		T* pt = p;
		p = NULL;
		return pt;
	}
	
	bool CopyTo(T** ppT)
	{
		assert(ppT != NULL);
		if (ppT == NULL)
			return false;

		*ppT = p;
		if (p)
			p->AddRef();

		return true;
	}
	
	//template <class Q>
	//bool QueryInterface(__deref_out_opt Q** pp) const throw()
	//{
	//	assert(pp != NULL);
	//	
	//	const RuntimeClass* pRuntimeClass = RUNTIME_CLASS( Q );
	//	if ( pRuntimeClass->IsKindOf( pRuntimeClass ) )
	//		return (Q*)p;

	//	return p->QueryInterface(__uuidof(Q), (void**)pp);
	//}

	T* p;
};

inline IObjectBase* TPtrAssign(IObjectBase** pp, IObjectBase* lp)
{
	if (pp == NULL)
		return NULL;

	if (lp != NULL)
		lp->AddRef();
	
	if (*pp)
		(*pp)->Release();

	*pp = lp;
	return lp;
}

template <class T>
class TPtr : public TPtrBase<T>
{
public:
	TPtr()
	{
	}

	TPtr(T* lp) : TPtrBase<T>(lp)
	{
	}

	TPtr(__in const TPtr<T>& lp) : TPtrBase<T>(lp.p)
	{
	}

	T* operator=(T* lp)
	{
		if (*this != lp)
		{
			return static_cast<T*>(TPtrAssign((IObjectBase**)&p, lp));
		}

		return *this;
	}

	//template <typename Q>
	//T* operator=(__in const TPtr<Q>& lp)
	//{
	//	if (!IsEqualObject(lp))
	//	{
	//		return static_cast<T*>(AtlComQIPtrAssign((IUnknown**)&p, lp, __uuidof(T)));
	//	}
	//	return *this;
	//}

	T* operator=(__in const TPtr<T>& lp)
	{
		if (*this != lp)
		{
			return static_cast<T*>(TPtrAssign((IObjectBase**)&p, lp));
		}
		return *this;
	}
};

// \brief Dynamic Cast
template < typename T >
TPtr< T >* DynamicCast( TPtr< IObjectBase >& _pObject )
{
	const RuntimeClass* pRuntimeClass = RUNTIME_CLASS( T );

	if ( _pObject->GetRuntimeClass()->IsKindOf( pRuntimeClass ) )
		return TPtr< T >( static_cast< T* >( _pObject ) );

	return nullptr;
}

// \brief Interface for ObjectConstructor
class IObjectConstructor
{
public:
	virtual ~IObjectConstructor() {}

	// Create object
	virtual IObjectBase* Create() = 0;
};

// \brief A helper class for creating object from inplace new.
template <typename T>
class ObjectConstructor : public IObjectConstructor
{
public:
	// Singleton
	static ObjectConstructor* GetInstance();

public:
	IObjectBase* Create() override;
};

template<typename T>
inline ObjectConstructor<T>* ObjectConstructor<T>::GetInstance()
{
	static ObjectConstructor<T> s_Instance;
	return &s_Instance;
}

template<typename T>
inline IObjectBase* ObjectConstructor<T>::Create()
{
	return new T();
}

// \brief The Object Manager. Store all Runtime Classes and create refcount objects
class ObjectManager
{
public:
	ObjectManager();
	~ObjectManager();

	// Register IObjectBase to the object manager
	void Register( const RuntimeClass* _pRuntimeClass, IObjectConstructor* _pObjectConstructor );

	// Create object
	IObjectBase* Create( const RuntimeClass* _pRuntimeClass );
	IObjectBase* CreateByName( const char* _pClassName );

	// Templated version of Create
	template < typename T >
	T* Create();

private:
	struct ObjectRegistryEntry
	{
		const RuntimeClass* pRuntimeClass;
		IObjectConstructor* pObjectConstructor;
	};

	std::vector<ObjectRegistryEntry> m_objectRegistry;
};

template< typename T >
inline T* ObjectManager::Create()
{
	return Create(RUNTIME_CLASS(T));
}

extern ObjectManager g_ObjectManager;

// Helper function for registering object
template < typename T >
void RegisterObject()
{
	g_ObjectManager.Register( RUNTIME_CLASS( T ), ObjectConstructor< T >::GetInstance() );
}

// Helper function for creating object
template < typename T >
TPtr< T > CreateObject()
{
	IObjectBase* pObject = g_ObjectManager.Create( RUNTIME_CLASS( T ) );
	return TPtr< T >( ( T* )pObject );
}

#endif // !objectmanagerH_included
