/**
 *  Copyright (C) 2004 Alo Sarv <madcat_@users.sourceforge.net>
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

#ifndef __DYNOBJ_H__
#define __DYNOBJ_H__

/**
 * \page docs Dynamic Object Creation System (DOCS)
 *
 * The basic idea of DOCS is to provide means of constructing objects only
 * knowing either the opcode or the name of the type needed. This comes very
 * handy at, for example, reading data from streams and constructing objects
 * from that data. The motivation behind DOCS was to provide a generic framework
 * for such functionality, to decouple the object's creation from the objects
 * user. While it might sound somewhat odd, it comes really handy when, for
 * example, the context where the stream is parsed only knows about abstract
 * base classes of the objects it works with. In that situation, the stream
 * parser would have to create concrete type objects, however the design logic
 * doesn't allow it at that context. DOCS to the rescue.
 *
 * The system is implemented using Singleton and Abstract Factory design
 * patterns, template Policies and C++ RTTI. The center of the system is
 * ObjFactory Singleton, which provides user interface for constructing objects.
 * Client code which wishes to make themselves available for dynamic
 * construction need to use DECLARE_DYNAMIC_CLASS and IMPLEMENT_DYNAMIC_CLASS
 * macros, which create a static member object of type ObjCreator within
 * that class. ObjCreator is an Abstract Factory which is capable of
 * constructing the object of the desired type, optionally passing arguments
 * to the object's constructor. When client code requests an object to
 * be created, it can also specifiy the creation policy to be used during
 * construction of the object. Generally, this specifies which arguments are
 * to be passed to the object's constructor.
 */

#include <hn/types.h>
#include <hn/utils.h>
#include <boost/format.hpp>
#include <boost/any.hpp>
#include <map>
#include <string>
#include <cassert>

/**
 * Add this class to global dynamic creation mechanism. An object of this
 * class can later be created during runtime by ObjFactory by its opcode and/or
 * its name.
 *
 * @param opcode      Used only for built-in types, this is hardcoded opcode
 *                    for the object's type. For user-defined types (e.g.
 *                    those added by plugins), set opcode to 0 to indicate name
 *                    should be used instead.
 * @param Type        Type of object to be created. This is generally the parent
 *                    class's type of this macro.
 * @param Arg1Type    Supported first argument to constructor. Use `void` if
 *                    the constructors don't support arguments.
 * @param Arg2Type    Supported second argument to constructor. Use `void` if
 *                    the constructors don't support second argument.
 *
 * \note Use DECLARE_DYNAMIC_CLASS macro within the private section of your
 *       class's interface, and IMPLEMENT_DYNAMIC_CLASS macro in the
 *       corresponding implementation file to add the class to dynamic object
 *       creation mechanism.
 */
#define DECLARE_DYNAMIC_CLASS(opcode, Type, Arg1Type, Arg2Type) \
	static ObjCreator<Type, Arg1Type, Arg2Type> ms_objCreator

/**
 * Implements dynamic object creation mechanism for the specified class.
 * See DECLARE_DYNAMIC_CLASS macro for more information.
 */
#define IMPLEMENT_DYNAMIC_CLASS(opcode, Type, Arg1Type, Arg2Type) \
	ObjCreator<Type, Arg1Type, Arg2Type> Type::ms_objCreator(opcode, #Type)

/**
 * Abstract Base class of object creators. It defines a standard constructor,
 * which adds this object to global object factory, and provides a pure virtual
 * function which derived classes must override to implement concrete object
 * creators.
 */
class ObjCreatorBase {
public:
	/**
	 * Constructor.
	 *
	 * @param opcode       Opcode of the objects this creator can create.
	 * @param name         Name of the objects this creator can create.
	 */
	ObjCreatorBase(uint8_t opcode, const std::string &name);

	/**
	 * Pure virtual function which creates the object using the object's
	 * default constructor.
	 */
	/**
	 * @name Pure virtual construction functions, overridden by derived
	 * classes. Three flavours of construction are supported - no arguments,
	 * one argument, and two arguments. Arguments are dynamically casted
	 * to right types by derived classes after passing through boost::any
	 * here.
	 *
	 * All of these functions return the newly created object, wrapped into
	 * boost::any, which client code should dynamically cast into right
	 * type before returning to user.
	 */
	//{@
	virtual boost::any create() = 0;
	virtual boost::any create(boost::any arg1);
	virtual boost::any create(boost::any arg1, boost::any arg2);
	virtual boost::any create(std::istream &i);
	//@}
protected:
	virtual ~ObjCreatorBase();                            //!< Forbidden
private:
	ObjCreatorBase();                                     //!< Forbidden
	ObjCreatorBase(ObjCreatorBase&);                      //!< Forbidden
	ObjCreatorBase& operator=(const ObjCreatorBase&);     //!< Forbidden
};

/**
 * Concrete object creator class template.
 */
//! Primary template - two arguments
template<typename T, typename Arg1Type, typename Arg2Type>
class ObjCreator : public ObjCreatorBase {
public:
	/**
	 * Constructor.
	 *
	 * @param opcode       Opcode of the objects this creator can create.
	 * @param name         Name of the objects this creator can create.
	 */
	ObjCreator(uint8_t opcode, const std::string &name)
	: ObjCreatorBase(opcode, name) {
	}

	//! Destructor
	virtual ~ObjCreator() {}

	/**
	 * Create an object using the object's default constructor.
	 *
	 * @return       Downcasted pointer to the newly created object.
	 */
	virtual boost::any create() {
		return new T();
	}

	/**
	 * Create an object, passing argument to objects constructor.
	 *
	 * @param arg1   Argument to be passed to object's constructor.
	 * @return       The newly created object.
	 *
	 * \throws boost::bad_any_cast if argument type is incorrect
	 */
	virtual boost::any create(boost::any arg1) {
		return new T(boost::any_cast<Arg1Type>(arg1));
	}

	/**
	 * Create an object, passing two arguments to objects constructor
	 *
	 * @param arg1   Argument to be passed to object's constructor
	 * @param arg2   Argument to be passed to object's constructor
	 * @return       The newly created object
	 *
	 * \throws boost::bad_any_cast if any of argument conversions fail
	 */
	virtual boost::any create(boost::any arg1, boost::any arg2) {
		return new T(
			boost::any_cast<Arg1Type>(arg1),
			boost::any_cast<Arg2Type>(arg2)
		);
	}
private:
	ObjCreator();                                      //!< Forbidden
	ObjCreator(ObjCreator&);                           //!< Forbidden
	ObjCreator& operator=(const ObjCreator&);          //!< Forbidden
};
//! Specialization - 1 argument
template<typename T, typename Arg1Type>
class ObjCreator<T, Arg1Type, void> : public ObjCreatorBase {
public:
	ObjCreator(uint8_t opcode, const std::string &name)
	: ObjCreatorBase(opcode, name) {}
	virtual ~ObjCreator() {}
	virtual boost::any create() {
		return new T();
	}
	virtual boost::any create(boost::any arg1) {
		return new T(boost::any_cast<Arg1Type>(arg1));
	}
};
//! Specialization - 0 arguments
template<typename T> class ObjCreator<T, void, void> : public ObjCreatorBase {
public:
	ObjCreator(uint8_t opcode, const std::string &name)
	: ObjCreatorBase(opcode, name) {}
	virtual ~ObjCreator() {}
	virtual boost::any create() {
		return new T();
	}
};
//! Specialization - 1 argument of type std::istream&
template<typename T>
class ObjCreator<T, std::istream&, void> : public ObjCreatorBase {
public:
	ObjCreator(uint8_t opcode, const std::string &name)
	:ObjCreatorBase(opcode, name) {}
	virtual ~ObjCreator() {}
	virtual boost::any create() {
		return new T();
	}
	virtual boost::any create(std::istream &i) {
		return new T(i);
	}
};

/**
 * Master object factory singleton. This class is capable of creating objects
 * on runtime knowing either the OPCODE of the object, or the name of the
 * object. Alternatively, it is capable of reading the needed data from
 * specified input streams.
 */
class ObjFactory {
public:
	/**
	 * Retrieve the instance of this Singleton class.
	 */
	static ObjFactory& instance() {
		static ObjFactory of;
		return of;
	}

	/**
	 * @name Construct w/o passing any arguments
	 */
	//@{

	/**
	 * Creates an object dynamically by reading the object's description
	 * from input stream.
	 *
	 * @param T         Type of object to be created.
	 * @param Creator   Optional argument describing what to pass to the
	 *                  object's constructor.
	 * @param i         Input stream to read the object's data from.
	 *
	 * \throws std::runtime_error if creation fails.
	 * \throws boost::bad_any_cast on internal casting errors.
	 * \throws indirectly any exceptions from object's constructor.
	 * \note   This function assumes the next byte in specified stream
	 *         is the opcode.
	 * \note   Opcode 0 found in stream indicates string-named object.
	 */
	template<typename T> T* create(std::istream &i) {
		uint8_t opcode = Utils::readInt(i, 1);
		if (opcode != 0) {
			return create<T>(opcode);
		}
		std::string name = Utils::readStr(i);
		return create<T>(name);
	}

	/**
	 * Create an object knowing its opcode, using the object's default
	 * constructor. This function should be used
	 * for all built-in types.
	 *
	 * @param opcode       Opcode of the object.
	 * @return             Pointer to newly allocated object.
	 *
	 * \throws std::runtime_error if object creation fails.
	 * \note May also throw exceptions indirectly from object's constructor.
	 */
	template<typename T> T* create(uint8_t opcode) {
		return boost::any_cast<T*>(findById(opcode)->create());
	}

	/**
	 * Create an object knowing its name, using the object's default
	 * constructor. This function should be used by client code (e.g.
	 * plugins) for their types. Built-in types should use opcode-based
	 * creation.
	 *
	 * @param name          Name of the object to be created.
	 * @return              Pointer to the newly allocated object.
	 *
	 * \throws std::runtime_error if object creation fails.
	 * \note May also throw exceptions indirectly from object's constructor.
	 */
	template<typename T> T* create(const std::string &name) {
		return boost::any_cast<T*>(findByName(name)->create());
	}

	//@}

	//! @name One argument
	//@{
	template<typename T, typename Arg1Type>
	T* create(std::istream &i, Arg1Type arg1) {
		uint8_t id = Utils::readInt(i, 1);
		if (id != 0) {
			return create<T, Arg1Type>(id, arg1);
		}
		return create<T, Arg1Type>(Utils::readStr(i), arg1);
	}
	template<typename T, typename Arg1Type>
	T* create(uint8_t id, Arg1Type arg1) {
		return boost::any_cast<T*>(findById(id)->create(arg1));
	}
	template<typename T, typename Arg1Type>
	T* create(const std::string &name, Arg1Type arg1) {
		return boost::any_cast<T*>(findByName(name)->create(arg1));
	}
	//@}

	//! @name Two arguments
	//@{
	template<typename T, typename Arg1Type, typename Arg2Type>
	T* create(std::istream &i, Arg1Type arg1, Arg2Type arg2) {
		uint8_t id = Utils::readInt(i, 1);
		if (opcode != 0) {
			return create<T, Arg1Type, Arg2Type>(id, arg1, arg2);
		}
		return create<T, Arg1Type, Arg2Type>(
			Utils::readStr(i), arg1, arg2
		);
	}
	template<typename T, typename Arg1Type, typename Arg2Type>
	T* create(uint8_t opcode, Arg1Type arg1, Arg2Type arg2) {
		return boost::any_cast<T*>(findById(id)->create(arg1, arg2));
	}
	template<typename T, typename Arg1Type, typename Arg2Type>
	T* create(const std::string &nam, Arg1Type arg1, Arg2Type arg2) {
		return boost::any_cast<T*>(findByName(nam)->create(arg1, arg2));
	}
	//@}

	/**
	 * Add an object creator to supported object's list.
	 *
	 * @param opcode       Opcode of the new object. For built-in types,
	 *                     should be nonzero, for user-defined types,
	 *                     should be 0.
	 * @param name         Name of the object.
	 * @param creator      Object factory capable of creating objects of
	 *                     this type.
	 */
	void addCreator(
		uint8_t opcode, const std::string &name, ObjCreatorBase *creator
	) {
		if (opcode != 0) {
			m_ocreators[opcode] = creator;
		}
		m_screators[name] = creator;
	}
private:
	ObjFactory();                                   //!< Singleton
	ObjFactory(ObjFactory&);                        //!< Forbidden
	ObjFactory& operator=(const ObjFactory&);       //!< Forbidden
	~ObjFactory();                                  //!< Singleton

	//! Map of supported opcodes for creation
	std::map<uint8_t, ObjCreatorBase*> m_ocreators;

	//! Iterator for the above map
	typedef std::map<uint8_t, ObjCreatorBase*>::iterator OIter;

	//! Map of object names we support creation of
	std::map<std::string, ObjCreatorBase*> m_screators;

	//! Iterator for the above map
	typedef std::map<std::string, ObjCreatorBase*>::iterator SIter;

	/**
	 * Locate the creator by searching with id
	 *
	 * @param id       Id to search for
	 * @return         The corresponding creator
	 *
	 * \throws std::runtime_error if the creator could not be found.
	 */
	ObjCreatorBase* findById(uint8_t id) {
		OIter i = instance().m_ocreators.find(id);
		if (i == instance().m_ocreators.end()) {
			throw std::runtime_error(
				(boost::format("ObjFactory: Requested creation "
				"of unsupported object type `%d`")
				% static_cast<int>(id)).str()
			);
		}
		return (*i).second;
	}

	/**
	 * Locate the creator by searching with name
	 *
	 * @param name       Name to search for
	 * @return           The corresponding creator
	 *
	 * \throws std::runtime_error if the creator could not be found.
	 */
	ObjCreatorBase* findByName(const std::string &name) {
		SIter i = instance().m_screators.find(name);
		if (i == instance().m_screators.end()) {
			throw std::runtime_error(
				(boost::format("ObjFactory: Requested creation "
				"of unsupported object type `%s`") % name).str()
			);
		}
		return (*i).second;
	}
};

#endif