/*
 * Ruby Treasures 0.1
 * Copyright (C) 2001 Paul Brannan <paul@atdesk.com>
 * 
 * You may distribute this software under the same terms as Ruby (see the file
 * COPYING that was distributed with this library).
 * 
 */
#include <ruby.h>
#include <st.h>
#include <stdlib.h>

/* ---------------------------------------------------------------------------
 *  Globals
 * ---------------------------------------------------------------------------
 */

/* ---------------------------------------------------------------------------
 * Safe send
 * ---------------------------------------------------------------------------
 */
static VALUE safe_send_helper(VALUE * args) {
  return rb_funcall3(
      RARRAY(args)->ptr[0],
      SYM2ID(RARRAY(args)->ptr[1]),
      RARRAY(args)->len - 2,
      &RARRAY(args)->ptr[2]);
}

static VALUE safe_send_yield(VALUE arg, VALUE dummy) {
  return rb_yield(arg);
}

static VALUE ruby_safe_send(int argc, VALUE * argv, VALUE self) {
  if(argc < 2) {
    rb_raise(rb_eArgError, "Not enough arguments");
  }

  if(rb_block_given_p()) {
    VALUE args;
    rb_scan_args(argc, argv, "*", &args);
    return rb_iterate(safe_send_helper, args, safe_send_yield, Qnil);
  } else {
    return rb_funcall3(argv[0], SYM2ID(argv[1]), argc - 2, &argv[2]);
  }
}

/* ---------------------------------------------------------------------------
 * Call stack
 * ---------------------------------------------------------------------------
 */

VALUE rb_call_stack = Qnil;

static VALUE call_set_trace_func(VALUE foo) {
  return rb_funcall(rb_mKernel, rb_intern("set_trace_func"), rb_f_lambda());
}

/* TODO: shift/unshift is slow */
static VALUE ruby_trace_func(VALUE self, VALUE args) {
  char const * event_str = RSTRING(RARRAY(args)->ptr[0])->ptr;

  /* if(event_str[0] == 'c' && event_str[1] == '-') event_str += 2; */
  switch(event_str[0]) {
    case 'c':
      /* call or c-call */
      if(!strcmp(event_str + 1, "all")) {
        rb_ary_unshift(rb_call_stack, rb_gv_get("current_call"));
      }
      break;
    case 'r':
      /* return or c-return */
      if(!strcmp(event_str + 1, "eturn")) {
        rb_ary_shift(rb_call_stack);
      }
      break;
  }

  rb_gv_set("current_call", args);
  return Qnil;
}

static VALUE ruby_init_call_stack(VALUE self) {
  VALUE method = Qnil, proc = Qnil;

  rb_undef_method(rb_mKernel, "init_call_stack");

  rb_call_stack = rb_ary_new();
  rb_gv_set("call_stack", rb_call_stack);

  rb_define_global_function("trace_func", ruby_trace_func, -2);
  method = rb_funcall(
      rb_mKernel, rb_intern("method"), 1, ID2SYM(rb_intern("trace_func")));
  proc = rb_funcall3(method, rb_intern("to_proc"), 0, 0);
  rb_funcall(rb_mKernel, rb_intern("set_trace_func"), 1, proc);

  return Qnil;
}

/* ---------------------------------------------------------------------------
 * Kernelless Object
 * ---------------------------------------------------------------------------
 */

static VALUE rb_kernelless_object = Qnil;

/* From the Ruby source (object.c) */
static VALUE boot_defclass(char *name, VALUE super) {
  extern st_table *rb_class_tbl;
  VALUE obj = rb_class_new(super);
  ID id = rb_intern(name);
  
  rb_name_class(obj, id);
  st_add_direct(rb_class_tbl, id, obj);
  return obj;
}   

static VALUE rb_obj_dummy() {
  return Qnil;
}

static VALUE ruby_init_kernelless_object(VALUE self) {
  VALUE metaclass;

  rb_undef_method(rb_mKernel, "init_kernelless_object");
  
  rb_kernelless_object = boot_defclass("KernellessObject", 0);
  metaclass = RBASIC(rb_kernelless_object)->klass =
    rb_singleton_class_new(rb_cClass);
  rb_singleton_class_attached(metaclass, rb_kernelless_object);

  rb_define_private_method(rb_kernelless_object, "initialize", rb_obj_dummy, 0);
  rb_define_method(rb_kernelless_object, "__instance_eval__",
      rb_obj_instance_eval, -1);
}

void Init_hacks_helper(void) {
  rb_define_global_function("safe_send", ruby_safe_send, -1);
  rb_define_global_function("init_call_stack", ruby_init_call_stack, 0);
  rb_define_global_function("init_kernelless_object",
      ruby_init_kernelless_object, 0);
}
