#ifndef CLUSTER_GLOBAL_FACTORY_HH
# define CLUSTER_GLOBAL_FACTORY_HH

# include "SingletonUse.hh"
# include "KeyMap.hh"

# include <assert.h>

/** @brief Object factory with pseudo-dynamic producer declarations.
 *
 * @param AbstractProduct Mother class of all the products
 * @param ConsArg argument provided to product constructor
 *
 * This singleton class provides a way to make a factory that determines
 * the set of producers at compilation time in a distributed way. To do so it
 * uses singleton pattern design and static declaration. The resulting behavior
 * allows to declare new producers as the devlopment advance without needing to
 * modify a big global function that would declare all the producers. Counter
 * parts are : a possible extra cost at program starts and redundancy checking
 * only at execution time.
 *
 * The current policy on redundancies is to abort program execution (using assert).
 * It may be improved in the future.
 *
 * @sa FactoryDeclare
 * @author Frederic Py <fpy@mbari.org>
 */
template<class AbstractProduct, class ConsArg>
class GlobalFactory {
public:
  /** @brief Abstract entry or producers.
   *
   * This class is a dummy interface used by @e GlobalFactory to produce new instances.
   *
   * @relates GlobalFactory
   */
  class dummyDecl {
  public:
    /** @brief Producer type.
     *
     * @return The symbolic name of the producer
     */
    Symbol const &getId() const {
      return m_type;
    }
    /** @brief Productuion method
     *
     * This operator is called to produce a new product
     *
     * @param arg argument given for construction
     *
     * @return The result of production.
     */
    AbstractProduct *operator()(ConsArg const &arg) const {
      return produce(arg);
    }
    
  protected:
    /** @brief Constructor.
     * @param type An identifier for this instance.
     *
     * Create a new instance identified by @e type.
     */
    dummyDecl(Symbol const &type)
      :m_type(type) {}
    /** @brief Destructor.
     *
     * Destroy current instance and undeclare it to its corresponding @e GlobalFactory
     */
    virtual ~dummyDecl() {
      m_factory->remove(this);
    }
    /** @brief Factory declaration.
     *
     * This method declare this insatnce to the corresponding @e GlobalFactory
     */
    void declareMe() {
      m_factory->declare(this);
    }
    /** @brief production method.
     *
     * @param arg Construction argument
     * @return resulting product
     *
     * @sa AbstractProduct *operator()(ConsArg const &) const
     */
    virtual AbstractProduct *produce(ConsArg const &arg) const =0;
    
  private:
    /** @brief Producer type iodentifier
     */
    Symbol m_type;
    /** @brief GlobalFactory entry point
     */
    SingletonUse< GlobalFactory<AbstractProduct, ConsArg> > m_factory;

    // Following method has no code in purpose
    dummyDecl(dummyDecl const &other);
  }; // GlobalFactory<>::dummyDecl
  
  /** @brief Get producer.
   *
   * @param type A producer type identifier.
   *
   * @pre @e type Is an exisiting identifier
   * @return The corrresponding producer
   * @throw UnknownKey Nor producer identified by &e type
   *
   * @sa dummyDecl const &operator[](Symbol const &) const
   */
  dummyDecl const &get(Symbol const &type) const {
    return *(m_producers.get(type));
  }
  /** @brief Get producer.
   *
   * @param type A producer type identifier.
   *
   * @pre @e type Is an exisiting identifier
   * @return The corrresponding producer
   * @throw UnknownKey Nor producer identified by &e type
   *
   * @sa dummyDecl const &get(Symbol const &) const
   */
  dummyDecl const &operator[](Symbol const &type) const {
    return get(type);
  }

  /** @brief Check for emptyness.
   *
   * @return true No producer in this factory
   * @return false else
   */
  bool empty() const {
    return m_producers.empty();
  }

private:
  /** @brief Default constructor.
   */
  GlobalFactory() {}
  /** @brief Destructor
   */
  ~GlobalFactory() {}

  /** @brief Producer declaration
   *
   * @param prod A producer
   *
   * This method declare @e prod in the factory using its
   * identifier ghas entry point.
   *
   * @pre No producer already exist with the same identifier as @e prod
   *
   * @note If another producer has already been declared with the same
   * identifier has @e prodit implies the abortion of program execution
   * using assert
   */
  void declare(dummyDecl *prod) {
    assert(m_producers.add(prod->getId(), prod));
  }

  /** @brief Undeclare a producer.
   *
   * @param prod A producer.
   *
   * Thsi method removes @e prod from the list of available producers
   * from this factory.
   */
  void remove(dummyDecl *prod);
  /** @brief Producers
   *
   * This map maintains an association map between identifiers and
   * existing producers.
   */
  KeyMap<dummyDecl *> m_producers;
  
  friend class dummyDecl;
  friend class SingletonUse< GlobalFactory<AbstractProduct, ConsArg> >;
}; // GlobalFactory

template<class AbstractProduct, class ConsArg>
void GlobalFactory<AbstractProduct, ConsArg>::remove(GlobalFactory<AbstractProduct, ConsArg>::dummyDecl *prod) {
  Symbol key = prod->getId();

  if( m_producers[key]==prod )
    m_producers.remove(key);
}

#endif // CLUSTER_GLOBAL_FACTORY_HH
