// @(#)root/ruby:$Id$
// Author:  Elias Athanasopoulos, May 2004

/*  Ruby bindings 
 *
 *  Elias Athanasopoulos  <elathan@ics.forth.gr> 
 *
 *  (c) 2003, 2004, 2006, 2007, 2008
 */

#include "RConfigOptions.h"
#include "TROOT.h"
#include "TClass.h"
#include "TApplication.h"
#include "TSystem.h"
#include "TRandom.h"
#include "TF2.h"
#include "TBenchmark.h"
#include "TVirtualPad.h"
#include "TStyle.h"

#include "CallFunc.h"
#include "Class.h"
#include "strlcpy.h"

#include "ruby.h"

// Define macros for old ruby versions
#ifndef RSTRING_PTR
#define RSTRING_PTR(s) (RSTRING(s)->ptr)
#endif
#ifndef RFLOAT_VALUE
#define RFLOAT_VALUE(v) (RFLOAT(v)->value)
#endif
#ifndef RARRAY_LEN
#define RARRAY_LEN(s) (RARRAY(s)->len)
#endif

#include "rrcommon.h"

/* ROOT's global enums.  */
#include "rrenums.h"

/* Special for Unixes */
#if defined(linux) || defined(sun)
#include "dlfcn.h"
#endif

#if ((R__RUBY_MAJOR<1) || (R__RUBY_MAJOR==1)&&(R__RUBY_MINOR<=8))
#  define rb_frame_this_func rb_frame_last_func
#endif


VALUE cTObject;

VALUE rr_ary_new (TList *l)
{
   /* convert a TList to a Ruby array */
   VALUE arr = rb_ary_new();
   VALUE o;

   TObject *rro;
   TIter next (l);

   while ((rro = next()))
      {
         RRNEW(o, cTObject);
         rb_iv_set (o, "__rr__", Data_Wrap_Struct (cTObject, 0, 0, rro));
         rb_iv_set (o, "__rr_class__",
                    rb_str_new2(rro->ClassName()));
         rb_ary_push (arr, o);
      }

   return arr;
}

static VALUE rr_to_ary (VALUE self)
{
   /* convert a TCollection to a Ruby array */
   RRGRAB(self, TList, l);
   return rr_ary_new (l);
}

VALUE rr_arrayc_new (const TArrayC *a)
{
   /* convert a TArrayC to a Ruby array */
   VALUE arr = rb_ary_new();

   for (int i = 0; i < a->GetSize(); i++)
      rb_ary_push (arr, INT2NUM(a->At(i)));

   return arr;
}

VALUE rr_arrays_new (const TArrayS *a)
{
   /* convert a TArrayS to a Ruby array */
   VALUE arr = rb_ary_new();

   for (int i = 0; i < a->GetSize(); i++)
      rb_ary_push (arr, INT2NUM(a->At(i)));

   return arr;
}

VALUE rr_arrayi_new (const TArrayI *a)
{
   /* convert a TArrayI to a Ruby array */
   VALUE arr = rb_ary_new();

   for (int i = 0; i < a->GetSize(); i++)
      rb_ary_push (arr, INT2NUM(a->At(i)));

   return arr;
}

VALUE rr_arrayl_new (const TArrayL *a)
{
   /* convert a TArrayL to a Ruby array */
   VALUE arr = rb_ary_new();

   for (int i = 0; i < a->GetSize(); i++)
      rb_ary_push (arr, INT2NUM(a->At(i)));

   return arr;
}

VALUE rr_arrayf_new (const TArrayF *a)
{
   /* convert a TArrayC to a Ruby array */
   VALUE arr = rb_ary_new();

   for (int i = 0; i < a->GetSize(); i++)
      rb_ary_push (arr, rb_float_new(a->At(i)));

   return arr;
}

VALUE rr_arrayd_new (const TArrayD *a)
{
   /* convert a TArrayD to a Ruby array */
   VALUE arr = rb_ary_new();

   for (int i = 0; i < a->GetSize(); i++)
      rb_ary_push (arr, rb_float_new(a->At(i)));

   return arr;
}

VALUE rr_seqcollection_new (TSeqCollection *sc)
{
   /* convert a TSeqCollection to a Ruby Array */
   VALUE arr = rb_ary_new();
   VALUE o;

   for (int i = 0; i < sc->GetSize(); i++)
      {
         RRNEW(o, cTObject);
         rb_iv_set (o, "__rr__", Data_Wrap_Struct (cTObject, 0, 0, sc->At(i)));
         rb_ary_push (arr, o);
      }

   return arr;
}

VALUE rr_bool (Bool_t q)
{
   VALUE res = Qnil;

   q == 0 ? res = Qfalse : res = Qtrue;

   return res;
}

/* Wrappers for function pointers.  */

/* TF1 */
static struct rr_fcn_info * rr_tf1_table[256];
static int rr_tf1_tblptr = 0;

double rr_ctf1_fcn (double *x, double* par)
{
   TF1 *fcn = (TF1 *)TF1::GetCurrent();
   struct rr_fcn_info *info = NULL;

   for (int i = 0; i < rr_tf1_tblptr; i++)
      {
         info = rr_tf1_table[i];
         if (!strcmp(info->name, fcn->GetName()))
            break;
         else
            info = NULL;
      }

   if (info == NULL) {
      rb_warn("Ruby user defined function has not been registered for %s (%p).",
              fcn->GetName(), fcn);
      return 0.;
   }

   int n = fcn->GetNpar();
   VALUE vx = rb_ary_new2 (n);
   VALUE vpar = rb_ary_new2 (n);
   for (int i = 0; i < n; i++)
      {
         rb_ary_push (vx, rb_float_new(x[i]));
         rb_ary_push (vpar, rb_float_new(par[i]));
      }

   double res = NUM2DBL(rb_funcall (rb_cObject, info->id, 2, vx, vpar));
   return res;
}

void rr_register_ctf1_fcn (char *name, ID id)
{
   struct rr_fcn_info *info = (struct rr_fcn_info *)malloc (sizeof *info);

   info->name = strdup(name);
   info->id = id;

   rr_tf1_table[rr_tf1_tblptr] = info;
   rr_tf1_tblptr++;

}

static struct rr_fcn_info * rr_tf2_table[256];
static int rr_tf2_tblptr = 0;

double rr_ctf2_fcn (double *x, double* par)
{
   TF2 *fcn = (TF2 *)TF2::GetCurrent();
   struct rr_fcn_info *info = NULL;

   for (int i = 0; i < rr_tf2_tblptr; i++)
      {
         info = rr_tf2_table[i];
         if (!strcmp(info->name, fcn->GetName()))
            break;
         else
            info = NULL;
      }

   if (info == NULL) {
      rb_warn("Ruby user defined function has not been registered for %s (%p).",
              fcn->GetName(), fcn);
      return 0.;
   }

   int n = fcn->GetNpar();
   VALUE vx = rb_ary_new2 (n);
   VALUE vpar = rb_ary_new2 (n);
   for (int i = 0; i < n; i++)
      {
         rb_ary_push (vx, rb_float_new(x[i]));
         rb_ary_push (vpar, rb_float_new(par[i]));
      }

   double res = NUM2DBL(rb_funcall (rb_cObject, info->id, 2, vx, vpar));
   return res;
}

void rr_register_ctf2_fcn (char *name, ID id)
{
   struct rr_fcn_info *info = (struct rr_fcn_info *)malloc (sizeof *info);

   info->name = strdup(name);
   info->id = id;

   rr_tf2_table[rr_tf2_tblptr] = info;
   rr_tf2_tblptr++;

}
/* Implementation */

/* Globals */

static VALUE rr_gsystem (void)
{
   VALUE o;

   RRNEW(o, cTObject);
   rb_iv_set (o, "__rr__", Data_Wrap_Struct(cTObject, 0, 0, gSystem));
   rb_iv_set (o, "__rr_class__", rb_str_new2("TSystem"));

   return o;
}

static VALUE rr_grandom (void)
{
   VALUE o;

   RRNEW(o, cTObject);
   rb_iv_set (o, "__rr__", Data_Wrap_Struct(cTObject, 0, 0, gRandom));
   rb_iv_set (o, "__rr_class__", rb_str_new2("TRandom"));

   return o;
}

static VALUE rr_gbenchmark (void)
{
   VALUE o;

   RRNEW(o, cTObject);
   rb_iv_set (o, "__rr__", Data_Wrap_Struct(cTObject, 0, 0, gBenchmark));
   rb_iv_set (o, "__rr_class__", rb_str_new2("TBenchmark"));

   return o;
}

static VALUE rr_gpad (void)
{
   VALUE o;

   RRNEW(o, cTObject);
   rb_iv_set (o, "__rr__", Data_Wrap_Struct(cTObject, 0, 0, gPad));
   rb_iv_set (o, "__rr_class__", rb_str_new2("TPad"));

   return o;
}

static VALUE rr_gstyle (void)
{
   VALUE o;

   RRNEW(o, cTObject);
   rb_iv_set (o, "__rr__", Data_Wrap_Struct(cTObject, 0, 0, gStyle));
   rb_iv_set (o, "__rr_class__", rb_str_new2("TStyle"));

   return o;
}

static VALUE rr_gdirectory (void)
{
   VALUE o;

   RRNEW(o, cTObject);
   rb_iv_set (o, "__rr__", Data_Wrap_Struct(cTObject, 0, 0, gDirectory));
   rb_iv_set (o, "__rr_class__", rb_str_new2("TDirectory"));

   return o;
}

static VALUE rr_groot (void)
{
   VALUE o;

   RRNEW(o, cTObject);

   rb_iv_set (o, "__rr__", Data_Wrap_Struct(cTObject, 0, 0, gROOT));
   rb_iv_set (o, "__rr_class__", rb_str_new2("TROOT"));

   return o;
}

static VALUE rr_gapplication (void)
{
   VALUE o;

   RRNEW(o, cTObject);

   rb_iv_set (o, "__rr__", Data_Wrap_Struct(cTObject, 0, 0, gApplication));
   rb_iv_set (o, "__rr_class__", rb_str_new2("TApplication"));

   return o;
}

static VALUE via (VALUE self, VALUE ameth, VALUE bmeth, VALUE parms)
{
   if (TYPE(ameth) != T_SYMBOL &&
       TYPE(bmeth) != T_SYMBOL &&
       TYPE(parms) != T_HASH)
      {
         rb_fatal ("rr-via: Please call TObject#via with sym, sym, hash.");
         return Qnil;
      }

   VALUE keys = rb_funcall(parms, rb_intern("keys"), 0);
   for (int i = 0; i < RARRAY_LEN(keys); i++)
      {
         VALUE key = rb_ary_entry (keys, i);
         rb_funcall (self, rb_to_id (ameth), 2, key, rb_hash_aref (parms, key));
      }
   rb_funcall(self, rb_to_id(bmeth), 0);

   return self;
}

/* Dynamic ruby-root specific implementation.  */

TObject* drr_grab_object(VALUE self)
{
   static TObject *o;
   Data_Get_Struct(rb_iv_get (self, "__rr__"), TObject, o);
   return o;
}

UInt_t drr_map_args2(VALUE inargs, char *cproto, int cproto_size, G__CallFunc *f,
                     Long_t offset=1, UInt_t reference_map=0x0)
{
   /* FIXME. Offset reminds me fortran code; make a better interface,
    * and change the function name to a better one.
    *
    * The boolean checks for cproto and f are vital. This function can
    * be called:
    *
    * 1. When we want a C prototype from a Ruby call
    * 2. When we want to set the arguments of a CINT function
    * 3. When we want both 1 and 2
    */

   int nargs = RARRAY_LEN(inargs) - offset;
   double *arr = NULL;
   TObject *ptr = NULL;
   VALUE v = 0;

   UInt_t ntobjects = 0;

   /* Transform Ruby arguments to C/C++.  */
   for (int i = 0; i < nargs; i++)
      {
         VALUE arg = rb_ary_entry (inargs, i+offset);
         switch (TYPE(arg))
            {
            case T_FIXNUM:
               if (f) f->SetArg((Long_t) NUM2INT(arg));
               if (cproto) strlcat(cproto, "int", cproto_size);
               break;
            case T_FLOAT:
               if (f) f->SetArg(NUM2DBL(arg));
               if (cproto) strlcat(cproto, "double", cproto_size);
               break;
            case T_STRING:
               if (f) f->SetArg((Long_t) StringValuePtr(arg));
               if (cproto) strlcat(cproto, "char*", cproto_size);
               break;
            case T_ARRAY:
               /* FIXME: Handle all arrays, not only
                * with floats.
                */
               if (f)
                  {
                     arr = ALLOC_N (double, RARRAY_LEN(arg));
                     for (int j = 0; j < RARRAY_LEN(arg); j++)
                        arr[j] = NUM2DBL(rb_ary_entry (arg, j));
                     f->SetArg((Long_t) arr);
                  }
               if (cproto) strlcat(cproto, "double*", cproto_size);
               break;
            case T_OBJECT:
               v = rb_iv_get (arg, "__rr__");
               if (!NIL_P(v))
                  {
                     Data_Get_Struct (v, TObject, ptr);
                     if (f) f->SetArg((Long_t) ptr);
                     if (cproto) {
                        VALUE tmp = rb_iv_get (arg, "__rr_class__");
                        strlcat(cproto, StringValuePtr(tmp), cproto_size);
                        if( ((reference_map>>ntobjects)&0x1) ) {
                           strlcat(cproto, "*", cproto_size);
                        } else {
                           strlcat(cproto, "&", cproto_size);
                        }
                     }
                  }
               ++ntobjects;
               break;
            default:
               break;
            }
         if ((i + 1 < nargs) && (nargs != 1) && cproto) 
            strlcat(cproto, ",", cproto_size);
      }
   return ntobjects;
}

void drr_find_method_prototype( G__ClassInfo *klass, char *methname, VALUE inargs,
                                char *cproto, int cproto_size, Long_t offset )
{
   /* FIXME: Brute force checking of all combinations of * and & for
    * T_Objects Since we cannot tell which one is needed (we get the type
    * from the ruby objects, which don't know) we try all.
    */

   G__MethodInfo *minfo = 0;
   Long_t dummy_offset = 0; // Not read out, but expected by GetMethod

   // Number of T_OBJECTS in argument list initialized to more than 1
   UInt_t nobjects = drr_map_args2 (inargs, cproto, cproto_size, 0, offset, 0x0);
   // 2^nobjects == number of combinations of "*" and "&"
   UInt_t bitmap_end = static_cast<UInt_t>( 0x1 << nobjects );

   // Check if method methname with prototype cproto is present in klass
   minfo = new G__MethodInfo(klass->GetMethod(methname, cproto, &dummy_offset));

   /* Loop if we have to, i.e. there are T_OBJECTS ^= TObjects and the first
    * combination is not correct.
    */
   if( nobjects > 0 and !(minfo->InterfaceMethod()) ) {
      for( UInt_t reference_map=0x1; reference_map < bitmap_end; reference_map++) {
         cproto[0] = static_cast<char>( 0 ); // reset cproto
         drr_map_args2 (inargs, cproto, cproto_size, 0, offset, reference_map);
         minfo = new G__MethodInfo(klass->GetMethod(methname, cproto, &dummy_offset));
         if (minfo->InterfaceMethod())
            break;
      }
   } 

   delete minfo;

   return;
}

void drr_set_method_args( VALUE inargs, G__CallFunc *func, Long_t offset=1 )
{
   drr_map_args2( inargs, 0, 0, func, offset );
}

enum Ektype {kint, kfloat, kchar, kunknown, kvoid, kintary, kfloatary, kstring, kroot, kbool};

int drr_parse_ret_type (const char *ret)
{
   char *realtype = strdup(ret), *t = realtype;
   int plevel = 0;
   Ektype type;

   while (*(t++)) {
      if (*t == '*')
         plevel++;
   }

   t--;

   if (plevel > 0)
      *(t - plevel) = '\0';

   if (!strncmp(t - 3, "int", 3) ||
       !strncmp(t - 4, "long", 4))
      type = kint;
   else
      if (!strncmp(t - 6, "double", 6) ||
          !strncmp(t - 5, "float", 5))
         type = kfloat;
      else
         if (!strncmp(t - 5, "char", 4))
            type = kchar;
         else
            if (!strncmp(t - 4, "void", 4))
               type = kvoid;
            else
               if (!strncmp(t - 4, "bool", 4))
                  type = kbool;
               else
                  type = kunknown;

   if (plevel)
      /* Quick hack to move from ordinary types to pointer types,
       * which are essntially arrays of values. For example an integer
       * (kint) is transformed to an array of integers (kintary).  */
      type = (Ektype)(type + 5);

   free (realtype);

   return type;
}

/* Function cache related.  */

struct drr_func_cache * drr_func_cache_init(struct drr_func_entry *entry)
{
   struct drr_func_cache *new_cache = (struct drr_func_cache *) malloc (sizeof *new_cache);
   new_cache->next = NULL;
   new_cache->entry = entry;
   new_cache->last = NULL;
   return new_cache;
}

void drr_func_cache_push (struct drr_func_cache *cache, struct drr_func_entry *entry)
{
   struct drr_func_cache *n = (struct drr_func_cache *) malloc(sizeof *n);
   n->entry = entry;

   if (cache->next)
      {
         n->next = cache->next;
         cache->next = n;
      }
   else
      {
         cache->next = n;
         n->next = NULL;
      }
}

struct drr_func_entry * drr_func_cache_find (struct drr_func_cache *cache, char *name)
{
   struct drr_func_cache *iter = cache;

   while (iter)
      {
         if (!strcmp (iter->entry->name, name))
            return iter->entry;
         iter = iter->next;
      }
   return NULL;
}

void drr_func_entry_free (struct drr_func_entry *entry)
{
   delete entry->func;
   delete entry->klass;
   free (entry->name);
   free (entry->cproto);
   free (entry);
}

/* Ruby generic interface.  */

VALUE drrAbstractClass;

static VALUE drr_as(VALUE self, VALUE klass)
{
   /* Pseudo C++ casting.  */
   VALUE v = 0;

   /* Check if there is a ROOT dict. available.  */
   TClass *c = TClass::GetClass(StringValuePtr(klass));
   if (c)
      {
         VALUE k;
         char *name = StringValuePtr(klass);
         if (!rb_const_defined (rb_cObject, rb_intern(name)))
            k = rb_define_class (name, drrAbstractClass);
         else
            k = rb_path2class (name);

         RRNEW(v, k);
         rb_iv_set (v, "__rr__", rb_iv_get(self, "__rr__"));
         rb_iv_set (v, "__rr_class__", klass);
      }
   else
      rb_raise( rb_eArgError, "No TClass found for %s. Is this a Root type?", StringValuePtr(klass) );

   return v;
}

static VALUE drr_init(int argc, VALUE argv[], VALUE self)
{
   VALUE inargs;
   char *classname = (char*) rb_obj_classname(self);
   char cproto[1024] = "";
   Long_t addr = 0;
   Long_t offset = 0;

   rb_scan_args (argc, argv, "0*", &inargs);

   G__CallFunc* func = new G__CallFunc();
   G__ClassInfo klass(classname);

   /* Call the requested ctor.  */

   if (RARRAY_LEN(inargs)) {
      drr_find_method_prototype (&klass, classname, inargs, cproto, sizeof(cproto), 0);
      drr_set_method_args ( inargs, func, 0);
   }

   G__MethodInfo minfo(klass.GetMethod(classname, cproto, &offset));
   if (minfo.InterfaceMethod())
      func->SetFunc(minfo);
   else
      rb_raise( rb_eArgError, "You provided an unknown prototype (%s) for (%s#%s).",
                cproto, classname, classname);

   addr = func->ExecInt((void*)((Long_t)0 + offset));
   rb_iv_set(self, "__rr__", Data_Wrap_Struct(cTObject, 0, 0, (TObject *)addr));
   rb_iv_set(self, "__rr_class__", rb_str_new2 (classname));

   func->Init();
   delete func;
   return self;
}

static VALUE drr_return(int rtype, Long_t value_address, double dvalue_address, VALUE self)
{
   VALUE vret;

   switch (rtype)
      {
      case kint:
         vret = INT2NUM(value_address);
         break;
      case kfloat:
         vret = rb_float_new(dvalue_address);
         break;
      case kstring:
         vret = rb_str_new2((char *)value_address);
         break;
      case kbool:
         vret = rr_bool((bool)value_address);
         break;
      case kroot:
         if (!value_address)
            return Qnil;

         if (!strcmp(((TObject*)(value_address))->ClassName(), "TList"))
            vret = rr_ary_new((TList*)value_address);
         else
            {
               VALUE res;
               RRNEW(res, cTObject);
               rb_iv_set(res, "__rr__", Data_Wrap_Struct(cTObject, 0, 0, (TObject*)value_address));
               rb_iv_set(res, "__rr_class__", rb_str_new2 (((TObject*)(value_address))->ClassName()));
               vret = res;
            }

         break;

      default:
         vret = self;
         break;
      }

   return vret;
}


static VALUE drr_const_missing(VALUE self, VALUE klass)
{
   /* Define a new ROOT Class dynammically.  */

   char *name = (char*) rb_id2name (rb_to_id(klass));

   /* Check if there is a ROOT dict. available.  */
   TClass *c = new TClass(name);
   if (c && c->GetClassInfo()) {
      VALUE new_klass = rb_define_class (name, drrAbstractClass);
      delete c;
      return new_klass;
   } else {
      delete c;
      /* If there is no ROOT dict available, call the original Object::const_missing */
      return rb_funcall(self,rb_intern("__drr_orig_const_missing"),1,klass);
   }
}

static VALUE drr_singleton_missing(int argc, VALUE argv[], VALUE self)
{
   VALUE inargs;
   char cproto[1024] = "";
   int nargs;
   Long_t offset = 0;
   Long_t address = 0;
   double dbladdr = 0;

   /* Call a singleton method.  */
   char * methname = (char*) rb_id2name (rb_to_id(argv[0]));
   char * classname = (char *) rb_class2name(self);
	
   rb_scan_args (argc, argv, "0*", &inargs);
   nargs = RARRAY_LEN(inargs) - 1;

   G__CallFunc* func = new G__CallFunc;
   G__ClassInfo *klass = new G__ClassInfo (classname);
   G__MethodInfo *minfo = 0;

   if (nargs) {
      drr_find_method_prototype( klass, methname, inargs, cproto, sizeof(cproto), 1 );
      drr_set_method_args( inargs, func, 1 );
   }

   /* FIXME: minfo is really used only for the return type.  */
   minfo = new G__MethodInfo(klass->GetMethod(methname, cproto, &offset));
   if (minfo->InterfaceMethod())
      func->SetFunc(*minfo);
   else
      rb_raise( rb_eArgError, "You provided an unknown prototype (%s) for (%s#%s).",
                cproto, classname, methname);

   int rtype = drr_parse_ret_type (minfo->Type()->TrueName());
   delete minfo;

   if (rtype != kfloat)
      address = func->ExecInt((void*)(offset));
   else
      dbladdr = func->ExecDouble((void*)(offset));

   VALUE ret = drr_return(rtype, address, dbladdr, self);
   delete func;
   return ret;
}


static VALUE drr_method_missing(int argc, VALUE argv[], VALUE self)
{
   /* When a ROOT method is called, we try to resolve it here. If
    * CINT is able to resolve it then we define a Ruby method using
    * a similar generic function (drr_generic_method), so as
    * Ruby not to use the Object#method_missing every time.
    */

   VALUE inargs;
   char *methname, *classname ;
   Long_t offset = 0;
   Long_t address = 0;
   double dbladdr = 0;
   char cproto[1024] = "";
   int nargs;

   /* Grab method, class and the instance pointer.  */
   methname = (char*) rb_id2name (rb_to_id(argv[0]));
   VALUE tmp = rb_iv_get (self, "__rr_class__");
   classname = StringValuePtr(tmp);
   TObject *caller = drr_grab_object (self);

   rb_scan_args (argc, argv, "0*", &inargs);

   nargs = RARRAY_LEN(inargs) - 1;
   VALUE rklass = rb_class_of (self);

   G__CallFunc *func = new G__CallFunc();
   G__ClassInfo *klass = new G__ClassInfo (classname);
   G__MethodInfo *minfo = 0;

   if (nargs) {
      drr_find_method_prototype( klass, methname, inargs, cproto, sizeof(cproto), 1 );
      drr_set_method_args( inargs, func, 1 );
   }

   /* FIXME: minfo is really used only for the return type.  */
   minfo = new G__MethodInfo(klass->GetMethod(methname, cproto, &offset));
   if (minfo->InterfaceMethod())
      func->SetFunc(*minfo);
   else
      rb_raise( rb_eArgError, "You provided an unknown prototype (%s) for (%s#%s).",
                cproto, classname, methname);

   /* This is the first time this method is called. Create a cash entry.  */
   struct drr_func_entry *entry = (struct drr_func_entry *) malloc (sizeof *entry);
   entry->func = func;
   entry->klass = klass;
   entry->name = strdup(methname);
   entry->cproto = strdup(cproto);
   entry->rtype = drr_parse_ret_type (minfo->Type()->TrueName());

   delete minfo;

   struct drr_func_cache *cache;
   /* If there is no cache available, create one (per Class scope).  */
   if (!rb_cvar_defined (rklass, rb_intern("@@__func_table__")))
      cache = drr_func_cache_init (entry);
   else
      Data_Get_Struct(rb_cv_get(rklass, "@@__func_table__"), struct drr_func_cache, cache);

   /* Push the method to the cache and save it back to the Class.  */
   drr_func_cache_push (cache, entry);
   rb_cv_set (rklass, "@@__func_table__",
              Data_Wrap_Struct(cTObject, 0, 0, cache));

   if (entry->rtype != kfloat)
      address = func->ExecInt((void*)((Long_t)caller + offset));
   else
      dbladdr = func->ExecDouble((void*)((Long_t)caller + offset));

   /* Define method.  */
   rb_define_method (rklass, methname, VALUEFUNC(drr_generic_method), -1);

   return(drr_return(entry->rtype, address, dbladdr, self));
}

static VALUE drr_generic_method(int argc, VALUE argv[], VALUE self)
{
   VALUE inargs;
   VALUE rklass;
   int nargs;
   Long_t offset = 0, address = 0;
   double dbladdr = 0;
   char cproto[1024] = "";

   /* Grab class, method name and instance pointer.  */
   rklass = rb_class_of (self);
   char *methname = (char*) rb_id2name (rb_frame_this_func());
   TObject *caller = drr_grab_object (self);

   rb_scan_args (argc, argv, "0*", &inargs);

   nargs = RARRAY_LEN(inargs);

   G__CallFunc *func = NULL;

   struct drr_func_cache *cache;
   struct drr_func_entry *entry;

   Data_Get_Struct (rb_cv_get(rklass, "@@__func_table__"), struct drr_func_cache, cache);
   entry = drr_func_cache_find (cache, methname);

   if (entry)
      {
         func = entry->func;
         if (nargs)
            drr_find_method_prototype (entry->klass, methname, inargs, cproto, sizeof(cproto), 0);
         func->SetFuncProto (entry->klass, methname, cproto, &offset);
         /* FIXME: Why on earth CINT resets the arguments when
          * SetFuncProto() is called?
          */
         if (nargs)
            drr_set_method_args (inargs, func, 0);
      }
   else {
      /* FIXME: This can never be happened.  */
      rb_warn ("Proto conflict with cache. Expected %s, but found no match for %s", cproto, methname);
      return VALUE();
   }

   if (entry->rtype != kfloat)
      address = func->ExecInt((void*)((Long_t)caller + offset));
   else
      dbladdr = func->ExecDouble((void*)((Long_t)caller + offset));

   return(drr_return(entry->rtype, address, dbladdr, self));
}

extern "C"
void Init_libRuby() {

   /* In order to have the most frequently used dictionaries
    * loaded by default. THIS MUST BE REPLACED BY PORTABLE CODE  */
#if defined(linux) || defined(sun)
   dlopen( "libCint.so",   RTLD_GLOBAL | RTLD_LAZY );
   dlopen( "libCore.so",   RTLD_GLOBAL | RTLD_LAZY );
   dlopen( "libGpad.so",   RTLD_GLOBAL | RTLD_LAZY );
   dlopen( "libGraf.so",   RTLD_GLOBAL | RTLD_LAZY );
   dlopen( "libMatrix.so", RTLD_GLOBAL | RTLD_LAZY );
   dlopen( "libHist.so",   RTLD_GLOBAL | RTLD_LAZY );
   dlopen( "libTree.so",   RTLD_GLOBAL | RTLD_LAZY );
   dlopen( "libGraf3d.so", RTLD_GLOBAL | RTLD_LAZY );
   dlopen( "libGeom.so",   RTLD_GLOBAL | RTLD_LAZY );
#endif

   /* Create a new ROOT Application if it doesn't already exist.  */
   if (!gApplication)
      gApplication = new TApplication("ruby root app", NULL, NULL);

   drrAbstractClass = rb_define_class("DRRAbstractClass", rb_cObject);
   rb_define_method(drrAbstractClass, "initialize", VALUEFUNC(drr_init), -1);
   rb_define_method(drrAbstractClass, "method_missing", VALUEFUNC(drr_method_missing), -1);
   rb_define_method (drrAbstractClass, "as", VALUEFUNC(drr_as), 1);
   /* For singleton function calls.  */
   rb_define_singleton_method (drrAbstractClass, "method_missing", VALUEFUNC(drr_singleton_missing), -1);

   cTObject = rb_define_class("TObject", drrAbstractClass);

   rb_define_method (cTObject, "to_ary", VALUEFUNC(rr_to_ary), 0);
   rb_define_method (rb_cObject, "via", VALUEFUNC(via), 3);

   /* Save the original Object::const_missing before overriding it
      Object::__drr_orig_const_missing will be called if Cint is unable to resolve the class name */
   rb_eval_string("Object.instance_eval { alias __drr_orig_const_missing const_missing }");
   rb_define_singleton_method (rb_cObject, "const_missing", VALUEFUNC(drr_const_missing), 1);

   /* usefull globals */
   rb_define_method (rb_cObject, "gSystem", VALUEFUNC(rr_gsystem), 0);
   rb_define_method (rb_cObject, "gRandom", VALUEFUNC(rr_grandom), 0);
   rb_define_method (rb_cObject, "gBenchmark", VALUEFUNC(rr_gbenchmark), 0);
   rb_define_method (rb_cObject, "gPad", VALUEFUNC(rr_gpad), 0);
   rb_define_method (rb_cObject, "gStyle", VALUEFUNC(rr_gstyle), 0);
   rb_define_method (rb_cObject, "gDirectory", VALUEFUNC(rr_gdirectory), 0);
   rb_define_method (rb_cObject, "gROOT", VALUEFUNC(rr_groot), 0);
   rb_define_method (rb_cObject, "gApplication", VALUEFUNC(rr_gapplication), 0);

   /* enums */
   init_global_enums();
}
