
/*

  KLayout Layout Viewer
  Copyright (C) 2006-2019 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

*/


#if defined(HAVE_RUBY)

#include "rba.h"
#include "rbaInternal.h"
#include "rbaUtils.h"
#include "rbaMarshal.h"

#include "tlLog.h"

namespace rba
{

// --------------------------------------------------------------------------
//  Implementation of the locked object vault

class LockedObjectVault
{
public:
  static void init (VALUE module, const char *name);

  LockedObjectVault ();
  ~LockedObjectVault ();

  static LockedObjectVault *instance ()
  {
    return mp_instance;
  }

  void add (VALUE object);
  void remove (VALUE object);
  void mark_this ();

private:
  std::set<VALUE> m_objects;

  static VALUE m_klass;
  static VALUE m_instance;
  static LockedObjectVault *mp_instance;

  static void free (void *p);
  static void mark (void *p);
  static VALUE alloc (VALUE klass);
};

VALUE LockedObjectVault::m_klass = Qnil;
VALUE LockedObjectVault::m_instance = Qnil;
LockedObjectVault *LockedObjectVault::mp_instance = 0;

LockedObjectVault::LockedObjectVault ()
{
  mp_instance = this;
}

LockedObjectVault::~LockedObjectVault ()
{
  if (mp_instance == this) {
    mp_instance = 0;
  }
}

void
LockedObjectVault::add (VALUE object)
{
  m_objects.insert (object);
}

void
LockedObjectVault::remove (VALUE object)
{
  m_objects.erase (object);
}

void
LockedObjectVault::mark_this ()
{
  for (std::set<VALUE>::iterator o = m_objects.begin (); o != m_objects.end (); ++o) {
    rb_gc_mark (*o);
  }
}

void
LockedObjectVault::mark (void *p)
{
  ((LockedObjectVault *) p)->mark_this ();
}

void
LockedObjectVault::free (void *p)
{
  delete ((LockedObjectVault *) p);
}

VALUE
LockedObjectVault::alloc (VALUE klass)
{
  tl_assert (TYPE (klass) == T_CLASS);

  LockedObjectVault *vault = new LockedObjectVault ();
  return Data_Wrap_Struct (klass, &LockedObjectVault::mark, &LockedObjectVault::free, vault);
}

void
LockedObjectVault::init (VALUE module, const char *name)
{
  if (m_instance != Qnil) {
    return;
  }

  m_klass = rb_define_class_under (module, name, rb_cObject);
  rb_define_alloc_func (m_klass, LockedObjectVault::alloc);

  m_instance = rba_class_new_instance_checked (0, 0, m_klass);
  rb_gc_register_address (&m_instance);
}

void
make_locked_object_vault (VALUE module)
{
  LockedObjectVault::init (module, "RBALockedObjectVault");
}

void
gc_lock_object (VALUE value)
{
  if (LockedObjectVault::instance ()) {
    LockedObjectVault::instance ()->add (value);
  }
}

void
gc_unlock_object (VALUE value)
{
  if (LockedObjectVault::instance ()) {
    LockedObjectVault::instance ()->remove (value);
  }
}

// --------------------------------------------------------------------------

/**
 *  @brief A final finalizer
 *  Final destruction of finalized objects is delegated to this class.
 *  It's main purpose is to temporarily disable C++ destruction while
 *  in a callback. Deleting C++ objects in a callback leads to unpredictable
 *  side effects and must be avoided.
 */
class InternalGC
{
public:
  InternalGC ()
    : m_enabled (true)
  {
    //  .. nothing yet ..
  }

  /**
   *  @brief Enables or disables the GC
   *  Returns the previous state
   */
  bool enable (bool e)
  {
    std::swap (e, m_enabled);
    return e;
  }

  /**
   *  @brief Queues (if disabled) or deletes the given object (plus all queued ones) if enabled
   */
  void destroy_maybe (const gsi::ClassBase *cls, void *obj)
  {
    if (! m_enabled) {
      m_queue.push_back (std::make_pair (cls, obj));
    } else {
      m_queue.clear ();
      cls->destroy (obj);
      for (std::vector<std::pair<const gsi::ClassBase *, void *> >::const_iterator q = m_queue.begin (); q != m_queue.end (); ++q) {
        q->first->destroy (q->second);
      }
    }
  }

private:
  std::vector<std::pair<const gsi::ClassBase *, void *> > m_queue;
  bool m_enabled;
};

static InternalGC s_gc;

/**
 *  @brief Destroys the object through the internal GC
 */
inline void destroy_object (const gsi::ClassBase *cls, void *obj)
{
  s_gc.destroy_maybe (cls, obj);
}

/**
 *  @brief A GC disabler
 *  By instantiating this object, the gc is disabled while the object is alife.
 *  Specifically in callbacks it's important to disable the GC to avoid undesired
 *  side effects.
 */
class GCDisabler
{
public:
  GCDisabler (InternalGC *gc = 0)
    : mp_gc (gc ? gc : &s_gc)
  {
    m_was_enabled = mp_gc->enable (false);
  }

  ~GCDisabler()
  {
    mp_gc->enable (m_was_enabled);
  }

private:
  bool m_was_enabled;
  InternalGC *mp_gc;
};

// --------------------------------------------------------------------------
//  Proxy implementation

Proxy::Proxy (const gsi::ClassBase *_cls_decl)
  : m_cls_decl (_cls_decl),
    m_obj (0),
    m_owned (false),
    m_const_ref (false),
    m_destroyed (false),
    m_can_destroy (false),
    m_self (Qnil)
{
  //  .. nothing yet ..
}

Proxy::~Proxy ()
{
  try {
    reset ();
  } catch (std::exception &ex) {
    tl::warn << "Caught exception in object destructor: " << ex.what ();
  } catch (tl::Exception &ex) {
    tl::warn << "Caught exception in object destructor: " << ex.msg ();
  } catch (...) {
    tl::warn << "Caught unspecified exception in object destructor";
  }
  m_destroyed = true;
}

void
Proxy::mark ()
{
  for (std::map <const gsi::MethodBase *, VALUE>::const_iterator pt = m_signal_handlers.begin (); pt != m_signal_handlers.end (); ++pt) {
    rb_gc_mark (pt->second);
  }
}

VALUE
Proxy::signal_handler (const gsi::MethodBase *meth)
{
  std::map <const gsi::MethodBase *, VALUE>::iterator pt = m_signal_handlers.find (meth);
  if (pt != m_signal_handlers.end ()) {
    return pt->second;
  }

  //  end register the new one
  VALUE args[] = { m_self };
  VALUE sh = rba_class_new_instance_checked (1, args, SignalHandler::klass);

  m_signal_handlers.insert (std::make_pair (meth, sh));

  //  install the handler
  SignalHandler *sig_handler = 0;
  Data_Get_Struct (sh, SignalHandler, sig_handler);
  meth->add_handler (obj (), sig_handler);

  return sh;
}

bool Proxy::can_call () const
{
  return rba::RubyInterpreter::instance () != 0;
}

void
Proxy::call (int id, gsi::SerialArgs &args, gsi::SerialArgs &ret) const
{
  GCDisabler gc_disabler;

  tl_assert (id < int (m_cbfuncs.size ()) && id >= 0);

  const gsi::MethodBase *meth = m_cbfuncs [id].method;
  ID mid  = m_cbfuncs [id].method_id;

  try {

    VALUE argv = rb_ary_new2 (long (std::distance (meth->begin_arguments (), meth->end_arguments ())));
    RB_GC_GUARD (argv);

    tl::Heap heap;

    //  TODO: callbacks with default arguments?
    for (gsi::MethodBase::argument_iterator a = meth->begin_arguments (); args && a != meth->end_arguments (); ++a) {
      rb_ary_push (argv, pop_arg (*a, 0, args, heap));
    }

    VALUE rb_ret = rba_funcall2_checked (m_self, mid, RARRAY_LEN (argv), RARRAY_PTR (argv));

    push_arg (meth->ret_type (), ret, rb_ret, heap);

    if (meth->ret_type ().pass_obj ()) {
      //  In factory callbacks, make sure the returned object is not deleted by
      //  anyone except the caller.
      Proxy *p = 0;
      Data_Get_Struct (rb_ret, Proxy, p);
      p->keep ();
    }

    //  a Ruby callback must not leave temporary objects
    tl_assert (heap.empty ());

  } catch (rba::RubyError &err) {
    rba::RubyError err_with_context (err);
    err_with_context.set_context (m_cls_decl->name () + "::" + meth->names ());
    throw err_with_context;
  } catch (tl::ExitException & /*ex*/) {
    throw;
  } catch (tl::Exception &ex) {
    throw tl::Exception (tl::to_string (tr ("Error calling method")) + " '" + m_cls_decl->name () + "::" + meth->names () + "': " + ex.msg ());
  } catch (...) {
    throw;
  }
}

void
Proxy::destroy ()
{
  if (! m_cls_decl) {
    m_obj = 0;
    return;
  }

  if (! (m_owned || m_can_destroy) && m_obj) {
    throw tl::Exception (tl::to_string (tr ("Object cannot be destroyed explicitly")));
  }

  //  first create the object if it was not created yet and check if it has not been
  //  destroyed already (the former is to ensure that the object is created at least)
  if (! m_obj) {
    if (m_destroyed) {
      throw tl::Exception (tl::to_string (tr ("Object has been destroyed already")));
    } else {
      m_obj = m_cls_decl->create ();
      m_owned = true;
    }
  }

  void *o = 0;
  if (m_owned || m_can_destroy) {
    o = m_obj;
  }
  detach ();
  if (o) {
    m_cls_decl->destroy (o);
  }
}

void
Proxy::detach ()
{
  if (! m_destroyed && m_cls_decl && m_cls_decl->is_managed ()) {
    gsi::ObjectBase *gsi_object = m_cls_decl->gsi_object (m_obj, false);
    if (gsi_object) {
      gsi_object->status_changed_event ().remove (this, &Proxy::object_status_changed);
    }
    if (!m_owned && m_self != Qnil) {
      gc_unlock_object (m_self);
    }
  }

  clear_callbacks ();

  m_self = Qnil;
  m_obj = 0;
  m_destroyed = true;
  m_const_ref = false;
  m_owned = false;
  m_can_destroy = false;
}

Proxy::callbacks_cache Proxy::s_callbacks_cache;

void
Proxy::initialize_callbacks ()
{
#if 0

  callbacks_cache::iterator cb = s_callbacks_cache.find (CLASS_OF (m_self));
  if (cb == s_callbacks_cache.end ()) {

    cb = s_callbacks_cache.insert (std::make_pair (CLASS_OF (m_self), callback_methods_type ())).first;

    const gsi::ClassBase *cls = m_cls_decl;

    // we got a new object - hence we have to attach event handlers.
    // We don't need to install virtual function callbacks because in that case, no overload is possible
    // (the object has been created on C++ side).
    while (cls) {

      for (gsi::ClassBase::method_iterator m = cls->begin_methods (); m != cls->end_methods (); ++m) {

        if ((*m)->is_callback ()) {

          //  HINT: all callback may not have aliases nor overloads
          const char *nstr = (*m)->primary_name ().c_str ();

          //  There is no place in the ruby API to determine whether a method is defined.
          //  Instead we explicitly call "method_defined?" to check, whether the given method
          //  is defined.
          VALUE name = rb_str_new (nstr, strlen (nstr));

          for (int prot = 0; prot < 2; ++prot) {

            VALUE rb_ret;
            if (prot) {
              rb_ret = rb_funcall2_checked (rb_class_of (self), rb_intern ("protected_method_defined?"), 1, &name);
            } else {
              rb_ret = rb_funcall2_checked (rb_class_of (self), rb_intern ("method_defined?"), 1, &name);
            }
            if (RTEST (rb_ret)) {
              //  Only if the class defines that method we can link the virtual method call to the
              //  Ruby method
              cb->second.push_back (*m);
              break;
            }

          }

        }

      }

      //  consider base classes as well.
      cls = cls->base ();

    }

  }

  for (callback_methods_type::const_iterator m = cb->second.begin (); m != cb->second.end (); ++m) {

    bool prot = false;
    const char *nstr = extract_protected ((*m)->name ().c_str (), prot);

    ID method_id = rb_intern (nstr);

    int id = add_callback (Proxy::CallbackFunction (method_id, m_self, *m));
    (*m)->set_callback (m_obj, gsi::Callback (id, this, (*m)->argsize (), (*m)->retsize ()));

  }

#else

  const gsi::ClassBase *cls = m_cls_decl;

  // we got a new object - hence we have to attach callback handlers.
  // We don't need to install virtual function callbacks because in that case, no overload is possible
  // (the object has been created on C++ side).
  while (cls) {

    for (gsi::ClassBase::method_iterator m = cls->begin_callbacks (); m != cls->end_callbacks (); ++m) {

      //  HINT: all callback may not have aliases nor overloads
      const char *nstr = (*m)->primary_name ().c_str ();

      //  There is no place in the ruby API to determine whether a method is defined.
      //  Instead we explicitly call "method_defined?" to check, whether the given method
      //  is defined.
      VALUE name = rb_str_new (nstr, long (strlen (nstr)));
      RB_GC_GUARD (name);

      for (int prot = 0; prot < 2; ++prot) {

        VALUE rb_ret;
        if (prot) {
          rb_ret = rba_funcall2_checked (rb_class_of (m_self), rb_intern ("protected_method_defined?"), 1, &name);
        } else {
          rb_ret = rba_funcall2_checked (rb_class_of (m_self), rb_intern ("method_defined?"), 1, &name);
        }
        if (RTEST (rb_ret)) {

          //  Only if the class defines that method we can link the virtual method call to the
          //  Ruby method
          int id = add_callback (Proxy::CallbackFunction (rb_intern (nstr), *m));
          (*m)->set_callback (m_obj, gsi::Callback (id, this, (*m)->argsize (), (*m)->retsize ()));

          break;

        }

      }

    }

    //  consider base classes as well.
    cls = cls->base ();

  }

#endif
}

void
Proxy::clear_callbacks ()
{
  m_cbfuncs.clear ();

  if (! m_obj) {
    return;
  }

  const gsi::ClassBase *cls = m_cls_decl;
  while (cls) {

    //  reset all callbacks
    for (gsi::ClassBase::method_iterator m = cls->begin_callbacks (); m != cls->end_callbacks (); ++m) {
      (*m)->set_callback (m_obj, gsi::Callback ());
    }

    //  consider base classes as well.
    cls = cls->base ();

  }
}

void
Proxy::release ()
{
  //  If the object is managed we first reset the ownership of all other clients
  //  and then make us the owner
  const gsi::ClassBase *cls = cls_decl ();
  if (cls) {

    if (cls->is_managed ()) {
      void *o = obj ();
      if (o) {
        cls->gsi_object (o)->keep ();
      }
    }

    if (!m_owned) {
      if (cls->is_managed () && m_self != Qnil) {
        gc_unlock_object (m_self);
      }
      m_owned = true;
    }

  }
}

void
Proxy::keep ()
{
  const gsi::ClassBase *cls = cls_decl ();
  if (cls) {
    void *o = obj ();
    if (o) {
      if (cls->is_managed ()) {
        cls->gsi_object (o)->keep ();
      } else {
        keep_internal ();
      }
    }
  }
}

void
Proxy::keep_internal ()
{
  if (m_owned) {
    //  Fallback: the object is not gsi-enabled: we use the ownership flag in this
    //  case to keep it alive. This will not reset the ownership flag for all clients.
    m_owned = false;
    tl_assert (m_self != Qnil);
    if (m_cls_decl->is_managed ()) {
      gc_lock_object (m_self);
    }
  }
}

void
Proxy::set (void *obj, bool owned, bool const_ref, bool can_destroy, VALUE self)
{
  const gsi::ClassBase *cls = cls_decl ();
  tl_assert (cls);

  if (obj != m_obj) {

    //  cleanup
    if (m_obj) {

      bool prev_owned = m_owned;
      void *prev_obj = m_obj;

      detach ();

      //  Destroy the object if we are owner. We don't destroy the object if it was locked
      //  (either because we are not owner or from C++ side using keep())
      if (prev_owned) {
        destroy_object (cls, prev_obj);
      }

    }

    m_obj = obj;
    m_self = self;
    m_owned = owned;
    m_can_destroy = can_destroy;
    m_const_ref = const_ref;

    if (m_obj) {

      if (cls->is_managed ()) {

        gsi::ObjectBase *gsi_object = cls->gsi_object (m_obj);
        //  Consider the case of "keep inside constructor"
        if (m_owned && gsi_object->already_kept ()) {
          m_owned = false;
        }
        gsi_object->status_changed_event ().add (this, &Proxy::object_status_changed);

        if (! m_owned) {
          gc_lock_object (m_self);
        }

      }

      initialize_callbacks ();

    }

  } else {

    //  m_owned = owned;  - thou shalt not change the ownership status
    m_can_destroy = can_destroy;
    m_const_ref = const_ref;

  }

  //  now we have a valid object (or nil) - we can reset "destroyed" state. Note: this has to be done
  //  here because before detach might be called on *this which resets m_destroyed.
  m_destroyed = false;
}

void *
Proxy::obj ()
{
  if (! m_obj) {
    if (m_destroyed) {
      throw tl::Exception (tl::to_string (tr ("Object has been destroyed already")));
    } else if (cls_decl ()->can_default_create()) {
      //  delayed creation of a detached C++ object ..
      set (cls_decl ()->create (), true, false, true, m_self);
    } else {
      throw tl::Exception (tl::to_string (tr ("Object cannot be default-created (missing arguments to 'new'?)")));
    }
  }

  return m_obj;
}

void
Proxy::object_status_changed (gsi::ObjectBase::StatusEventType type)
{
  if (type == gsi::ObjectBase::ObjectDestroyed) {
    m_destroyed = true;  //  NOTE: must be set before detach and indicates that the object was destroyed externally.
    detach ();
  } else if (type == gsi::ObjectBase::ObjectKeep) {
    keep_internal ();
  } else if (type == gsi::ObjectBase::ObjectRelease) {
    release ();
  }
}

// --------------------------------------------------------------------------
//  SignalHandler implementation

VALUE SignalHandler::klass = Qnil;

SignalHandler::SignalHandler ()
  : m_obj (Qnil)
{
}

SignalHandler::~SignalHandler ()
{
  clear_procs ();
}

void
SignalHandler::initialize (VALUE obj)
{
  m_obj = obj;
}

void
SignalHandler::assign (VALUE proc)
{
  clear_procs ();
  add (proc);
}

void
SignalHandler::clear ()
{
  clear_procs ();
}

void
SignalHandler::add (VALUE proc)
{
  remove (proc);
  m_procs.push_back (proc);
}

void
SignalHandler::remove (VALUE proc)
{
  for (std::list<VALUE>::iterator p = m_procs.begin (); p != m_procs.end (); ++p) {
    if (*p == proc) {
      m_procs.erase (p);
      break;
    }
  }
}

void
SignalHandler::mark_this ()
{
  if (m_obj != Qnil) {
    rb_gc_mark (m_obj);
  }
  for (std::list<VALUE>::iterator p = m_procs.begin (); p != m_procs.end (); ++p) {
    rb_gc_mark (*p);
  }
}

void
SignalHandler::clear_procs ()
{
  m_procs.clear ();
}

void
SignalHandler::free (void *p)
{
  delete ((SignalHandler *) p);
}

void
SignalHandler::mark (void *p)
{
  ((SignalHandler *) p)->mark_this ();
}

VALUE
SignalHandler::alloc (VALUE klass)
{
  tl_assert (TYPE (klass) == T_CLASS);

  SignalHandler *signal = new SignalHandler ();
  return Data_Wrap_Struct (klass, &SignalHandler::mark, &SignalHandler::free, signal);
}

VALUE
SignalHandler::static_initialize (VALUE self, VALUE obj)
{
  SignalHandler *p = 0;
  Data_Get_Struct (self, SignalHandler, p);
  if (p) {
    p->initialize (obj);
  }
  return Qnil;
}

VALUE
SignalHandler::static_assign (VALUE self, VALUE proc)
{
  //  NOTE: self-assignment happens on "signal+=proc" which is resolved to
  //  "signal=(signal+proc)" and "signal+proc" returns the signal itself.
  //    and:
  if (proc != self) {

    if (TYPE (proc) != T_DATA || rb_obj_is_kind_of (proc, rb_cProc) != Qtrue) {
      std::string msg = tl::to_string (tr ("Single argument to signal must be a Proc object"));
      VALUE args [1];
      args [0] = rb_str_new2 (msg.c_str ());
      rb_exc_raise (rb_class_new_instance(1, args, rb_eRuntimeError));
    }

    SignalHandler *p = 0;
    Data_Get_Struct (self, SignalHandler, p);
    if (p) {
      p->assign (proc);
    }

  }

  return Qnil;
}

VALUE
SignalHandler::static_add (VALUE self, VALUE proc)
{
  if (TYPE (proc) != T_DATA || rb_obj_is_kind_of (proc, rb_cProc) != Qtrue) {
    std::string msg = tl::to_string (tr ("Single argument to signal's add method must be a Proc object"));
    VALUE args [1];
    args [0] = rb_str_new2 (msg.c_str ());
    rb_exc_raise (rb_class_new_instance(1, args, rb_eRuntimeError));
  }

  SignalHandler *p = 0;
  Data_Get_Struct (self, SignalHandler, p);
  if (p) {
    p->add (proc);
  }
  return self;
}

VALUE
SignalHandler::static_clear (VALUE self)
{
  SignalHandler *p = 0;
  Data_Get_Struct (self, SignalHandler, p);
  if (p) {
    p->clear ();
  }
  return self;
}

VALUE
SignalHandler::static_remove (VALUE self, VALUE proc)
{
  SignalHandler *p = 0;
  Data_Get_Struct (self, SignalHandler, p);
  if (p) {
    p->remove (proc);
  }
  return self;
}

void
SignalHandler::define_class (VALUE module, const char *name)
{
  klass = rb_define_class_under (module, name, rb_cObject);
  rb_define_alloc_func (klass, SignalHandler::alloc);
  rb_define_method (klass, "initialize", (ruby_func) &SignalHandler::static_initialize, 1);
  rb_define_method (klass, "set", (ruby_func) &SignalHandler::static_assign, 1);
  rb_define_method (klass, "clear", (ruby_func) &SignalHandler::static_clear, 0);
  rb_define_method (klass, "+", (ruby_func) &SignalHandler::static_add, 1);
  rb_define_method (klass, "-", (ruby_func) &SignalHandler::static_remove, 1);
}

void SignalHandler::call (const gsi::MethodBase *meth, gsi::SerialArgs &args, gsi::SerialArgs &ret) const
{
  GCDisabler gc_disabler;

  VALUE argv = rb_ary_new2 (long (std::distance (meth->begin_arguments (), meth->end_arguments ())));
  RB_GC_GUARD (argv);

  tl::Heap heap;

  //  TODO: signals with default arguments?
  for (gsi::MethodBase::argument_iterator a = meth->begin_arguments (); args && a != meth->end_arguments (); ++a) {
    rb_ary_push (argv, pop_arg (*a, 0, args, heap));
  }

  //  call the signal handlers ... the last one will deliver the return value
  VALUE rb_ret = Qnil;
  for (std::list<VALUE>::const_iterator p = m_procs.begin (); p != m_procs.end (); ++p) {
    rb_ret = rba_funcall2_checked (*p, rb_intern ("call"), RARRAY_LEN (argv), RARRAY_PTR (argv));
  }

  push_arg (meth->ret_type (), ret, rb_ret, heap);

  //  a Ruby callback must not leave temporary objects
  tl_assert (heap.empty ());
}

// --------------------------------------------------------------------------
//  Class map management

static std::map <VALUE, const gsi::ClassBase *> cls_map;
static std::map <const gsi::ClassBase *, VALUE> rev_cls_map;

void register_class (VALUE ruby_cls, const gsi::ClassBase *gsi_cls)
{
  cls_map.insert (std::make_pair (ruby_cls, gsi_cls));
  rev_cls_map.insert (std::make_pair (gsi_cls, ruby_cls));
}

VALUE ruby_cls (const gsi::ClassBase *cls)
{
  std::map <const gsi::ClassBase *, VALUE>::const_iterator c = rev_cls_map.find (cls);
  tl_assert (c != rev_cls_map.end ());
  return c->second;
}

bool is_registered (const gsi::ClassBase *cls)
{
  return rev_cls_map.find (cls) != rev_cls_map.end ();
}

const gsi::ClassBase *find_cclass (VALUE k)
{
  std::map <VALUE, const gsi::ClassBase *>::const_iterator cls;

  //  find the class that is bound to C++ (maybe a super class)
  while (k != rb_cObject) {
    cls = cls_map.find (k);
    if (cls == cls_map.end ()) {
      //  if not found advance to super class
      k = RCLASS_SUPER (k);
    } else {
      break;
    }
  }

  tl_assert (cls != cls_map.end ());
  return cls->second;
}

}

#endif

