
/*

  KLayout Layout Viewer
  Copyright (C) 2006-2016 Matthias Koefferlein

  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., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA

*/

 
#ifndef HDR_tlClassRegistry
#define HDR_tlClassRegistry

#include "config.h"

#include "tlCollection.h"
#include "tlLog.h"

namespace tl
{

template <class X> class Registrar;

/**
 *  @brief A helper object that is used to manage the registered classes
 */
template <class X>
class RegistrarNode
{
private:
  RegistrarNode ()
    : mp_object (0), m_owned (true), m_position (0), mp_next (0)
  {
    // .. nothing else ..
  }

  ~RegistrarNode ()
  {
    if (m_owned) {
      delete mp_object;
    }
    mp_object = 0;
  }

  X *mp_object;
  bool m_owned;
  int m_position;
  std::string m_name;
  RegistrarNode *mp_next; 

  friend class Registrar<X>;
  friend class Registrar<X>::iterator;
};

/**
 *  @brief A generic class registration facility
 *
 *  It can register a set of objects by instantiating a certain object
 *  and somewhere (else) instantiating an registrar.
 *  The objects are classified by a base class they are derived of.
 *  The collection of registered objects can be iterated.
 *  See tlClassRegistry.ut for an example.
 */ 

/**
 *  @brief A class representant that is registered
 */
template <class X>
class RegisteredClass 
{
public:
  /** 
   *  @brief register an object Y in space X (Y must be derived from X)
   *
   *  This will register the given object in X space. The Y pointer 
   *  will become owned by the registrar.
   *  The position parameter tells where to insert the class in the
   *  chain - higher positions come later.
   *  The name is an arbitrary string that is used for debugging purposes only.
   */
  RegisteredClass (X *inst, int position = 0, const char *name = "", bool owned = true) 
    : m_owned (owned)
  { 
    if (! Registrar<X>::instance) {
      Registrar<X>::instance = new Registrar<X> ();
    }
    mp_node = Registrar<X>::instance->insert (inst, owned, position, name);
    
    if (tl::verbosity () >= 40) {
      tl::info << "Registered plugin '" << name << "' with priority " << position;
    }
  }

  ~RegisteredClass ()
  {
    //  remove the associated object
    Registrar<X>::instance->remove (mp_node);

    if (Registrar<X>::instance->begin () == Registrar<X>::instance->end ()) {
      //  no more registered objects left - remove registrar
      delete Registrar<X>::instance;
      Registrar<X>::instance = 0;
    }
  }

private:
  RegistrarNode<X> *mp_node;
  bool m_owned; 
};

/**
 *  @brief The registrar capable of registering objects of type Y derived from X
 *
 *  Objects of type Y derived from X can simply be created and registered by 
 *  instantiation (statically) an object of tl::Registrar<X>::Class which
 *  will receive an new'ed object of type Y and register it in the <X> registration
 *  space. This object will then be owned by the registrar and is destroyed upon
 *  shut down.
 */
template <class X>
class Registrar
{
public:
  class iterator
  {
  public:
    iterator (RegistrarNode<X> *p)
      : mp_pos (p)
    {
      //  .. nothing yet ..
    }

    bool operator== (iterator d) const
    {
      return mp_pos == d.mp_pos;
    }

    bool operator!= (iterator d) const
    {
      return mp_pos != d.mp_pos;
    }

    iterator &operator++ () 
    {
      mp_pos = mp_pos->mp_next;
      return *this;
    }

    const std::string &current_name () const
    {
      return mp_pos->m_name;
    }

    X &operator* () const
    {
      return *(mp_pos->mp_object);
    }

    X *operator-> () const
    {
      return mp_pos->mp_object;
    }

    X *take () const
    {
      X *x = mp_pos->mp_object;
      mp_pos->mp_object = 0;
      return x;
    }

  private:
    RegistrarNode<X> *mp_pos;
  };

  /**
   *  @brief Constructor
   */
  Registrar <X> () 
    : mp_first (0)
  {
    //  .. nothing yet ..
  }

  /**
   *  @brief begin() iterator over the registered objects
   */
  static iterator begin () 
  {
    if (get_instance ()) {
      return iterator (get_instance ()->mp_first);
    } else {
      return iterator (0); 
    }
  }

  /**
   *  @brief end() iterator over the registered objects
   */
  static iterator end () 
  {
    return iterator (0); 
  }

  static Registrar<X> *get_instance () 
  {
    return instance;
  }

private:
  friend class iterator;
  template <class Y> friend class RegisteredClass;

  static Registrar<X> *instance;

  RegistrarNode<X> *insert (X *cls, bool owned, int position, const std::string &name)
  {
    RegistrarNode<X> **link = &mp_first;
    while (*link && (*link)->m_position < position) {
      link = &((*link)->mp_next);
    }

    RegistrarNode<X> *node = new RegistrarNode<X> ();
    node->mp_object = cls;
    node->m_owned = owned;
    node->m_position = position;
    node->m_name = name;
    node->mp_next = *link;
    *link = node;

    return node;
  }

  void remove (RegistrarNode<X> *node)
  {
    RegistrarNode<X> **link = &mp_first;
    while (*link && *link != node) {
      link = &((*link)->mp_next);
    }

    if (*link) {
      RegistrarNode<X> *node = *link;
      *link = node->mp_next;
      delete node;
    }
  }

  RegistrarNode<X> *mp_first;
};

template <class X> Registrar<X> *Registrar<X>::instance = 0;

}

#endif

