#include "tomato.h"

static Handle<Value> js_array_from(V8Tomato *tomato, VALUE value);
static Handle<Value> js_hash_from(V8Tomato *tomato, VALUE value);
static Handle<Value> js_symbol_to_string(const Arguments& args);
static Handle<Value> js_symbol_from(VALUE value);
static Handle<Value> js_date_from(VALUE value);

Handle<Value> js_value_of(V8Tomato *tomato,  VALUE value)
{
  switch(TYPE(value))
  {
    case T_NIL     : return Null();
    //case T_OBJECT  :
    //case T_CLASS   :
    //case T_MODULE  :
    case T_FLOAT   : return Number::New(NUM2DBL(value));
    case T_STRING  : return String::New(StringValuePtr(value));
//    case T_REGEXP  :
    case T_ARRAY   : return js_array_from(tomato, value);
    case T_HASH    : return js_hash_from(tomato, value);
    //case T_STRUCT  :
    case T_BIGNUM  : return Number::New(NUM2LONG(value));
    case T_FIXNUM  : return Int32::New(FIX2INT(value));
    //case T_COMPLEX :
    //case T_RATIONAL:
    //case T_FILE    :
    case T_TRUE    : return True();
    case T_FALSE   : return False();
    //case T_DATA    :
    case T_SYMBOL  :
      if (SYM2ID(value) == rb_intern("undefined")) return Undefined();
      else return js_symbol_from(value);
      //return String::New(rb_id2name(SYM2ID(value)));
  };
  
  /* take care of some JS types that aren't reflected above */
  if (rb_funcall(value, rb_intern("kind_of?"), 1, rb_const_get(rb_cObject, rb_intern("Date"))) == Qtrue ||
      rb_funcall(value, rb_intern("kind_of?"), 1, rb_const_get(rb_cObject, rb_intern("Time"))) == Qtrue ||
      rb_funcall(value, rb_intern("kind_of?"), 1, rb_const_get(rb_cObject, rb_intern("DateTime"))) == Qtrue ||
      rb_funcall(value, rb_intern("kind_of?"), 1, rb_const_get(rb_const_get(rb_cObject, rb_intern("ActiveSupport")), rb_intern("TimeWithZone"))) == Qtrue)
  {
    return js_date_from(value);
  }
  
  return inspect_rb(value);  
}

Handle<Value> js_array_from(V8Tomato *tomato, VALUE value)
{
  Handle<Array> array;
  int size, i;
  VALUE *ptr;
  
  switch(TYPE(value))
  {
    case T_ARRAY:
      size = RARRAY_LEN(value);
      ptr  = RARRAY_PTR(value);
      array = Array::New(size);
      for (i = 0; i < size; i++)
        array->Set(i, js_value_of(tomato, *(ptr+i)));
      break;
    default:
      return ThrowException(String::New("Could not construct JS array: unexpected Ruby type (BUG: please report)"));
  }
  return array;
}

static Handle<Value> js_symbol_from(VALUE value)
{
  ID id = SYM2ID(value);
  Handle<Object> symbol = Object::New();
  symbol->Set(String::New("_tomato_symbol"), Boolean::New(true), DontEnum);
  symbol->Set(String::New("symbol"), String::New(rb_id2name(id)));

  // make it transparently convert to string
  Handle<Function> function = FunctionTemplate::New(js_symbol_to_string)->GetFunction();
  function->SetName(String::New("toString"));
  function->Set(String::New("symbol"), symbol, DontEnum);
  symbol->Set(String::New("toString"), function);
  return symbol;
}

static Handle<Value> js_symbol_to_string(const Arguments& args)
{
  Handle<Function> function = args.Callee();
  Handle<Object> symbol = Handle<Object>::Cast(function->Get(String::New("symbol")));
  if (!symbol->IsObject())
    return ThrowException(String::New("Could not convert symbol to string: symbol handle missing (BUG: please report)"));
  return symbol->Get(String::New("symbol"));
}

Handle<Value> js_hash_from(V8Tomato *tomato, VALUE value)
{
  VALUE rb_keys   = rb_funcall(value, rb_intern("keys"), 0);
  VALUE rb_values = rb_funcall(value, rb_intern("values"), 0);
  VALUE *keys = RARRAY_PTR(rb_keys),
        *values = RARRAY_PTR(rb_values);

  int size = RARRAY_LEN(rb_keys), i;

  Handle<Object> js_hash = Object::New();
  Handle<Array> js_keys = Array::New(size);
  Handle<Array> js_values = Array::New(size);
  
  for (i = 0; i < size; i++)
  {
    js_keys->Set(i,   js_value_of(tomato, *(keys+i)));
    js_values->Set(i, js_value_of(tomato, *(values+i)));
  }
  
  js_hash->Set(String::New("_tomato_hash_keys"), js_keys, DontEnum);
  js_hash->Set(String::New("_tomato_hash_values"), js_values, DontEnum);
  js_hash->Set(String::New("_tomato_hash"), Boolean::New(true), DontEnum);
  
  return js_hash;
}

Handle<Value> inspect_rb(VALUE value)
{
  VALUE string = rb_funcall(value, rb_intern("inspect"), 0);
  return String::New(StringValuePtr(string));
}

Handle<Value> js_date_from(VALUE value)
{
  VALUE ival = rb_funcall(value, rb_intern("to_f"), 0);
  double time = NUM2DBL(ival) * 1000.0;
  return Date::New(time);
}