/**
  Root of all objects and classes inside opalscript, except for
  native toll free bridges.
*/
var rb_boot_root = function() {};

/**
  Returns the hash value for the receiver. By default on regular
  objects this is just the objects' id
*/
rb_boot_root.$h = function() {
  return this.$id;
};

/**
  To benefit javascript debug consoles, the toString of any ruby
  object is its' #inspect method.
*/
rb_boot_root.toString = function() {
  return this.m$inspect();
};

/**
  Boot a base class. This is only used for the very core ruby
  objects and classes (Object, Module, Class). This returns
  what will be the actual instances of our root classes.

  @param {String} id The class id
  @param {RubyClass} superklass The super
*/
function rb_boot_defclass(id, superklass) {
  var cls = function() {
    this.$id = rb_yield_hash();
    return this;
  };

  if (superklass) {
    var ctor = function() {};
    ctor.prototype = superklass.prototype;
    cls.prototype = new ctor();
  }
  else {
    cls.prototype = new rb_boot_root();
  }

  cls.prototype.constructor = cls;
  cls.prototype.$f = T_OBJECT;

  cls.prototype.$h = function() {
    return this.$id;
  };

  return cls;
};

/**
  Make the actual (meta) classes: Object, Class, Module.

  @param {String} id The class id
  @param {RubyClass} klass The class of the result
  @param {RubyClass} superklass The superklass
*/
function rb_boot_makemeta(id, klass, superklass) {
  var meta = function() {
    this.$id = rb_yield_hash();
    return this;
  };

  var ctor = function() {};
  ctor.prototype = superklass.prototype;
  meta.prototype = new ctor();

  var proto = meta.prototype;
  proto.$included_in = [];
  proto.$m           = {};
  proto.$methods     = [];

  proto.$a           = klass;
  proto.$f           = T_CLASS;
  proto.__classid__  = id;
  proto.$s           = superklass;
  proto.constructor  = meta;

  // constants
  if (superklass.prototype.$constants_alloc) {
    proto.$c = new superklass.prototype.$constants_alloc();
    proto.$constants_alloc = function() {};
    proto.$constants_alloc.prototype = proto.$c;
  }
  else {
    proto.$constants_alloc = function() {};
    proto.$c = proto.$constants_alloc.prototype;
  }

  var result = new meta();
  klass.prototype.$k = result;
  return result;
};

/**
  Fixes the class of boot classes to their meta.
*/
function rb_boot_defmetameta(klass, meta) {
  klass.$k = meta;
};

/**
  Boot class

  @param {RubyClass} superklass Class to inherit from
*/
function rb_class_boot(superklass) {
  // instances
  var cls = function() {
    this.$id = rb_yield_hash();
    return this;
  };

  var ctor = function() {};
  ctor.prototype = superklass.$a.prototype;
  cls.prototype = new ctor();

  var proto = cls.prototype;
  proto.constructor = cls;
  proto.$f = T_OBJECT;

  // class itself
  var meta = function() {
    this.$id = rb_yield_hash();
    return this;
  };

  var mtor = function() {};
  mtor.prototype = superklass.constructor.prototype;
  meta.prototype = new mtor();

  proto = meta.prototype;
  proto.$a = cls;
  proto.$f = T_CLASS;
  proto.$m = {};
  proto.$methods = [];
  proto.constructor = meta;
  proto.$s = superklass;

  // constants
  proto.$c = new superklass.$constants_alloc();
  proto.$constants_alloc = function() {};
  proto.$constants_alloc.prototype = proto.$c;

  var result = new meta();
  cls.prototype.$k = result;
  return result;
};

/**
  Get actual class ignoring singleton classes and iclasses.
*/
function rb_class_real(klass) {
  while (klass.$f & FL_SINGLETON) { klass = klass.$s; }
  return klass;
};

/**
  Name the class with the given id.
*/
function rb_name_class(klass, id) {
  klass.__classid__ = id;
};

/**
  Make metaclass for the given class
*/
function rb_make_metaclass(klass, super_class) {
  if (klass.$f & T_CLASS) {
    if ((klass.$f & T_CLASS) && (klass.$f & FL_SINGLETON)) {
      return rb_make_metametaclass(klass);
    }
    else {
      // FIXME this needs fixinfg to remove hacked stuff now in make_singleton_class
      var meta = rb_class_boot(super_class);
      // remove this??!
      meta.$a.prototype = klass.constructor.prototype;
      meta.$c = meta.$k.$c_prototype;
      meta.$f |= FL_SINGLETON;
      meta.__classid__ = "#<Class:" + klass.__classid__ + ">";
      klass.$k = meta;
      meta.$c = klass.$c;
      rb_singleton_class_attached(meta, klass);
      // console.log("meta id: " + klass.__classid__);
      return meta;
    }
  } else {
    // if we want metaclass of an object, do this
    return rb_make_singleton_class(klass);
  }
};

function rb_make_singleton_class(obj) {
  var orig_class = obj.$k;
  var klass = rb_class_boot(orig_class);

  klass.$f |= FL_SINGLETON;

  obj.$k = klass;

  // make methods we define here actually point to instance
  // FIXME: we could just take advantage of $bridge_prototype like we
  // use for bridged classes?? means we can make more instances...
  klass.$bridge_prototype = obj;

  rb_singleton_class_attached(klass, obj);

  klass.$k = rb_class_real(orig_class).$k;
  klass.__classid__ = "#<Class:#<" + orig_class.__classid__ + ":" + klass.$id + ">>";

  return klass;
};

function rb_singleton_class_attached(klass, obj) {
  if (klass.$f & FL_SINGLETON) {
    klass.__attached__ = obj;
  }
};

function rb_make_metametaclass(metaclass) {
  var metametaclass, super_of_metaclass;

  if (metaclass.$k == metaclass) {
    metametaclass = rb_class_boot(null);
    metametaclass.$k = metametaclass;
  }
  else {
    metametaclass = rb_class_boot(null);
    metametaclass.$k = metaclass.$k.$k == metaclass.$k
      ? rb_make_metametaclass(metaclass.$k)
      : metaclass.$k.$k;
  }

  metametaclass.$f |= FL_SINGLETON;

  rb_singleton_class_attached(metametaclass, metaclass);
  rb_metaclass.$k = metametaclass;
  metaclass.$m = metametaclass.$m_tbl;
  super_of_metaclass = metaclass.$s;

  metametaclass.$s = super_of_metaclass.$k.__attached__
    == super_of_metaclass
    ? super_of_metaclass.$k
    : rb_make_metametaclass(super_of_metaclass);

  return metametaclass;
};

function rb_boot_defmetametaclass(klass, metametaclass) {
  klass.$k.$k = metametaclass;
};

// Holds an array of all prototypes that are bridged. Any method defined on
// Object in ruby will also be added to the bridge classes.
var rb_bridged_classes = [];

/**
  Define toll free bridged class
*/
function rb_bridge_class(prototype, flags, id, super_class) {
  var klass = rb_define_class(id, super_class);

  klass.$bridge_prototype = prototype;
  rb_bridged_classes.push(prototype);

  prototype.$k = klass;
  prototype.$m = klass.$m_tbl;
  prototype.$f = flags;
  prototype.$r = true;

  prototype.$h = function() { return flags + '_' + this; };

  return klass;
};

// make native prototype from class
function rb_native_prototype(cls, proto) {
  var sup = cls.$s;

  if (sup != rb_cObject) {
    rb_raise(rb_eRuntimeError, "native_error must be used on subclass of Object only");
  }

  proto.$k = cls;
  proto.$f = T_OBJECT;

  proto.$h = function() { return this.$id || (this.$id = rb_yield_hash()); };

  return cls;
}

/**
  Define a new class (normal way), with the given id and superclass. Will be
  top level.
*/
function rb_define_class(id, super_klass) {
  return rb_define_class_under(rb_cObject, id, super_klass);
};

function rb_define_class_under(base, id, super_klass) {
  var klass;

  if (rb_const_defined(base, id)) {
    klass = rb_const_get(base, id);

    if (!(klass.$f & T_CLASS)) {
      rb_raise(rb_eException, id + " is not a class");
    }

    if (klass.$s != super_klass && super_klass != rb_cObject) {
      rb_raise(rb_eException, "Wrong superclass given for " + id);
    }

    return klass;
  }

  klass = rb_define_class_id(id, super_klass);

  if (base == rb_cObject) {
    rb_name_class(klass, id);
  } else {
    rb_name_class(klass, base.__classid__ + '::' + id);
  }

  rb_const_set(base, id, klass);
  klass.$parent = base;

  // Class#inherited hook - here is a good place to call. We check method
  // is actually defined first (incase we are calling it during boot). We
  // can't do this earlier as an error will cause constant names not to be
  // set etc (this is the last place before returning back to scope).
  if (super_klass.m$inherited) {
    super_klass.m$inherited(klass);
  }

  return klass;
};

/**
  Actually create class
*/
function rb_define_class_id(id, super_klass) {
  var klass;

  if (!super_klass) {
    super_klass = rb_cObject;
  }
  klass = rb_class_create(super_klass);
  rb_name_class(klass, id);
  rb_make_metaclass(klass, super_klass.$k);

  return klass;
};

function rb_class_create(super_klass) {
  return rb_class_boot(super_klass);
};

/**
  Get singleton class of obj
*/
function rb_singleton_class(obj) {
  var klass;

  if (obj.$f & T_OBJECT) {
    if ((obj.$f & T_NUMBER) || (obj.$f & T_SYMBOL)) {
      rb_raise(rb_eTypeError, "can't define singleton");
    }
  }

  if ((obj.$k.$f & FL_SINGLETON) && obj.$k.__attached__ == obj) {
    klass = obj.$k;
  }
  else {
    var class_id = obj.$k.__classid__;
    klass = rb_make_metaclass(obj, obj.$k);
  }

  return klass;
};