/**
 * @file   llinitdestroyclass.h
 * @author Nat Goodspeed
 * @date   2015-05-27
 * @brief  LLInitClass / LLDestroyClass mechanism
 *
 * The LLInitClass template, extracted from llui.h, ensures that control will
 * reach a static initClass() method. LLDestroyClass does the same for a
 * static destroyClass() method.
 *
 * The distinguishing characteristics of these templates are:
 *
 * - All LLInitClass<T>::initClass() methods are triggered by an explicit call
 *   to LLInitClassList::instance().fireCallbacks(). Presumably this call
 *   happens sometime after all static objects in the program have been
 *   initialized. In other words, each LLInitClass<T>::initClass() method
 *   should be able to make some assumptions about global program state.
 *
 * - Similarly, LLDestroyClass<T>::destroyClass() methods are triggered by
 *   LLDestroyClassList::instance().fireCallbacks(). Again, presumably this
 *   happens at a well-defined moment in the program's shutdown sequence.
 *
 * - The initClass() calls happen in an unspecified sequence. You may not rely
 *   on the relative ordering of LLInitClass<T>::initClass() versus another
 *   LLInitClass<U>::initClass() method. If you need such a guarantee, use
 *   LLSingleton instead and make the dependency explicit.
 *
 * - Similarly, LLDestroyClass<T>::destroyClass() may happen either before or
 *   after LLDestroyClass<U>::destroyClass(). You cannot rely on that order.
 *
 * $LicenseInfo:firstyear=2015&license=viewerlgpl$
 * Copyright (c) 2015, Linden Research, Inc.
 * $/LicenseInfo$
 */

#if ! defined(LL_LLINITDESTROYCLASS_H)
#define LL_LLINITDESTROYCLASS_H

#include "llsingleton.h"
#include <boost/function.hpp>
#include <typeinfo>
#include <vector>
#include <utility>                  // std::pair

/**
 * LLCallbackRegistry is an implementation detail base class for
 * LLInitClassList and LLDestroyClassList. It accumulates the initClass() or
 * destroyClass() callbacks for registered classes.
 */
class LLCallbackRegistry
{
public:
	typedef boost::function<void()> func_t;

	void registerCallback(const std::string& name, const func_t& func)
	{
		mCallbacks.push_back(FuncList::value_type(name, func));
	}

	void fireCallbacks() const;

private:
	// Arguably this should be a boost::signals2::signal, which is, after all,
	// a sequence of callables. We manage it by hand so we can log a name for
	// each registered function we call.
	typedef std::vector< std::pair<std::string, func_t> > FuncList;
	FuncList mCallbacks;
};

/**
 * LLInitClassList is the LLCallbackRegistry for LLInitClass. It stores the
 * registered initClass() methods. It must be an LLSingleton because
 * LLInitClass registers its initClass() method at static construction time
 * (before main()), requiring LLInitClassList to be fully constructed on
 * demand regardless of module initialization order.
 */
class LLInitClassList : 
	public LLCallbackRegistry, 
	public LLSingleton<LLInitClassList>
{
	LLSINGLETON_EMPTY_CTOR(LLInitClassList);
};

/**
 * LLDestroyClassList is the LLCallbackRegistry for LLDestroyClass. It stores
 * the registered destroyClass() methods. It must be an LLSingleton because
 * LLDestroyClass registers its destroyClass() method at static construction
 * time (before main()), requiring LLDestroyClassList to be fully constructed
 * on demand regardless of module initialization order.
 */
class LLDestroyClassList : 
	public LLCallbackRegistry, 
	public LLSingleton<LLDestroyClassList>
{
	LLSINGLETON_EMPTY_CTOR(LLDestroyClassList);
};

/**
 * LLRegisterWith is an implementation detail for LLInitClass and
 * LLDestroyClass. It is intended to be used as a static class member whose
 * constructor registers the specified callback with the LLMumbleClassList
 * singleton registry specified as the template argument.
 */
template<typename T>
class LLRegisterWith
{
public:
	LLRegisterWith(const std::string& name, const LLCallbackRegistry::func_t& func)
	{
		T::instance().registerCallback(name, func);
	}

	// this avoids a MSVC bug where non-referenced static members are "optimized" away
	// even if their constructors have side effects
	S32 reference()
	{
		S32 dummy;
		dummy = 0;
		return dummy;
	}
};

/**
 * Derive MyClass from LLInitClass<MyClass> (the Curiously Recurring Template
 * Pattern) to ensure that the static method MyClass::initClass() will be
 * called (along with all other LLInitClass<T> subclass initClass() methods)
 * when someone calls LLInitClassList::instance().fireCallbacks(). This gives
 * the application specific control over the timing of all such
 * initializations, without having to insert calls for every such class into
 * generic application code.
 */
template<typename T>
class LLInitClass
{
public:
	LLInitClass() { sRegister.reference(); }

	// When this static member is initialized, the subclass initClass() method
	// is registered on LLInitClassList. See sRegister definition below.
	static LLRegisterWith<LLInitClassList> sRegister;
};

/**
 * Derive MyClass from LLDestroyClass<MyClass> (the Curiously Recurring
 * Template Pattern) to ensure that the static method MyClass::destroyClass()
 * will be called (along with other LLDestroyClass<T> subclass destroyClass()
 * methods) when someone calls LLDestroyClassList::instance().fireCallbacks().
 * This gives the application specific control over the timing of all such
 * cleanup calls, without having to insert calls for every such class into
 * generic application code.
 */
template<typename T>
class LLDestroyClass
{
public:
	LLDestroyClass() { sRegister.reference(); }

	// When this static member is initialized, the subclass destroyClass()
	// method is registered on LLInitClassList. See sRegister definition
	// below.
	static LLRegisterWith<LLDestroyClassList> sRegister;
};

// Here's where LLInitClass<T> specifies the subclass initClass() method.
template <typename T>
LLRegisterWith<LLInitClassList>
LLInitClass<T>::sRegister(std::string(typeid(T).name()) + "::initClass",
						  &T::initClass);
// Here's where LLDestroyClass<T> specifies the subclass destroyClass() method.
template <typename T>
LLRegisterWith<LLDestroyClassList>
LLDestroyClass<T>::sRegister(std::string(typeid(T).name()) + "::destroyClass",
							 &T::destroyClass);

#endif /* ! defined(LL_LLINITDESTROYCLASS_H) */