/***************************************************************************
*                                                                          *
*  Copyright (C) Golaem S.A.  All Rights Reserved.                         *
*                                                                          *
****************************************************************************/

#pragma once

#include <utility>
#include "glmMap.h"

#include "glmSingleton.h"
#include "glmAssert.h"

namespace glm
{
    /**
	* \brief Template factory prototype class.
	*
	* Helper class to provide you access to a template factory implementation.
	* TIdentifier : type of the identifier used to register and retrieve a Creator.
	* TCreator : creation functor ancestor, defines the base class of the family of classes to be created by the factory and
	* its constructor signature.
	*
	* \li Example, in order to instantiate the B and C classes, descendants of a class A you must define A creators :
	*
	*	\code
	// base class creator
	class ACreator
	{
	public:

	// the operator() argument must match A constructor arguments
	virtual A* operator() (	glm::GlmString name, unsigned int identifier ) const
	{
	return NULL ;
	}
	} ;

	// descendants will parametrize this class to register to the prototype factory
	template <class T>
	class ACreatorT : public ACreator
	{
	public:

	// this typedef is mandatory
	typedef T object_to_create ;

	// creates and returns a new instance of type T
	A* operator() (	glm::GlmString name, unsigned int identifier ) const
	{
	return new T( name, identifier ) ;
	}
	} ;
	*	\endcode
	* \li The factory is a singleton to avoid different factories for the same
	* classes family
	*\code
	typedef glm::Singleton< glm::PrototypeFactory< unsigned int, ACreator > > AFactory ;
	*\endcode
	* \li B and C, the descendants of A, will then parametrize ACreatorT when they
	* register to the PrototypeFactory :
	*\code
	AFactory::getInstance().registerCreator< ACreatorT< B > >( B::s_id ) ;
	AFactory::getInstance().registerCreator< ACreatorT< C > >( C::s_id ) ;
	*\endcode
	* \li To create a new instance of class B you will use the create method :
	*	\code
	A* myB = AFactory::getInstance().create( B::s_id )( aName, anIdentifier ) ;
	*	\endcode
	*
	* \ingroup genericPatterns
	* \author Golaem <dev@golaem.com>
	*/
    template <class TIdentifier, class TCreator>
    class PrototypeFactory
    {
        typedef glm::PrototypeFactory<TIdentifier, TCreator> ThisPrototypeFactory;
        GLM_CLASS_STAT_ID( getDefaultMemoryStatId())

    public:
        friend class Singleton<PrototypeFactory<TIdentifier, TCreator>>;

        /// destructor
        virtual ~PrototypeFactory();

        /**
		* \brief returns the creator registered to classId
		* if no creator has been registered TCreator is returned
		*/
        const TCreator& create(const TIdentifier& classId) const;

        /// register a creator to classId
        template <class TDerivedCreator>
        bool registerCreator(const TIdentifier& classId);

        /// register a creator to classId
        template <class TDerivedCreator>
        bool unregisterCreator(const TIdentifier& classId);

    private:
        /// constructor
        PrototypeFactory(const MemAllocStatId memAllocStatId = getDefaultMemoryStatId());

        /// copy constructor
        PrototypeFactory(const PrototypeFactory&);

        /// assignment operator
        PrototypeFactory& operator=(const PrototypeFactory&);

        // ----------------------------------------------------------------------------------
        static GLM_FORCE_INLINE MemAllocStatId getDefaultMemoryStatId()
        {
            return glm::MemoryStatIds::GolaemShared;
        }

        /// map of the creators
        GlmMap<TIdentifier, TCreator*> _creators;
    };

    //-------------------------------------------------------------------------
    // constructor
    //-------------------------------------------------------------------------
    template <class TIdentifier, class TCreator>
    PrototypeFactory<TIdentifier, TCreator>::PrototypeFactory(const MemAllocStatId memAllocStatId)
        : _creators(memAllocStatId)
    {
    }

    //-------------------------------------------------------------------------
    // destructor
    //-------------------------------------------------------------------------
    template <class TIdentifier, class TCreator>
    PrototypeFactory<TIdentifier, TCreator>::~PrototypeFactory()
    {
        typename GlmMap<TIdentifier, TCreator*>::const_iterator ite(_creators.begin());
        while (ite != _creators.end())
        {
            delete ite.getValue();
            ite++;
        }
        _creators.clear();
    }

    //-------------------------------------------------------------------------
    // create
    //-------------------------------------------------------------------------
    template <class TIdentifier, class TCreator>
    const TCreator&
    PrototypeFactory<TIdentifier, TCreator>::create(const TIdentifier& classId) const
    {
        typename GlmMap<TIdentifier, TCreator*>::const_iterator ite(_creators.find(classId));
        if (ite != _creators.end())
        {
            return *ite.getValue();
        }

        // this is a fallback to return a creator reference, it should never occur outside of unit tests.
        static TCreator creator;
        return creator;
    }

    //-------------------------------------------------------------------------
    // registerCreator
    //-------------------------------------------------------------------------
    template <class TIdentifier, class TCreator>
    template <class TDerivedCreator>
    bool
    PrototypeFactory<TIdentifier, TCreator>::registerCreator(const TIdentifier& classId)
    {
        TDerivedCreator* creator = new TDerivedCreator();
        _creators[classId] = creator;
        return true;
    }

    //-------------------------------------------------------------------------
    // unregisterCreator
    //-------------------------------------------------------------------------
    template <class TIdentifier, class TCreator>
    template <class TDerivedCreator>
    bool
    PrototypeFactory<TIdentifier, TCreator>::unregisterCreator(const TIdentifier& classId)
    {
        typename GlmMap<TIdentifier, TCreator*>::iterator ite(_creators.find(classId));
        GLM_DEBUG_ASSERT(ite != _creators.end());
        delete ite.getValue();
        _creators.erase(ite);
        return true;
    }
} // namespace glm
