/*
 *  call-seq:
 *     ObjectSpace.count_objects([result_hash]) -> hash
 *
 *  Counts objects for each type.
 *
 *  It returns a hash as:
 *  {:TOTAL=>10000, :FREE=>3011, :T_OBJECT=>6, :T_CLASS=>404, ...}
 *
 *  If the optional argument, result_hash, is given,
 *  it is overwritten and returned.
 *  This is intended to avoid probe effect.
 *
 *  The contents of the returned hash is implementation defined.
 *  It may be changed in future.
 *
 *  This method is not expected to work except C Ruby.
 *
 */

static VALUE
count_objects(int argc, VALUE *argv, VALUE os)
{
    rb_objspace_t *objspace = &rb_objspace;
    size_t counts[T_MASK+1];
    size_t freed = 0;
    size_t total = 0;
    size_t i;
    VALUE hash;

    if (rb_scan_args(argc, argv, "01", &hash) == 1) {
        if (TYPE(hash) != T_HASH)
            rb_raise(rb_eTypeError, "non-hash given");
        if (!RHASH_EMPTY_P(hash)) {
            st_foreach(RHASH_TBL(hash), set_zero, hash);
        }
    }

    for (i = 0; i <= T_MASK; i++) {
        counts[i] = 0;
    }

    for (i = 0; i < heaps_used; i++) {
        RVALUE *p, *pend;

        p = heaps[i].slot; pend = p + heaps[i].limit;
        for (;p < pend; p++) {
            if (p->as.basic.flags) {
                counts[BUILTIN_TYPE(p)]++;
            }
            else {
                freed++;
            }
        }
        total += heaps[i].limit;
    }

    if (hash == Qnil)
        hash = rb_hash_new();
    rb_hash_aset(hash, ID2SYM(rb_intern("TOTAL")), SIZET2NUM(total));
    rb_hash_aset(hash, ID2SYM(rb_intern("FREE")), SIZET2NUM(freed));
    for (i = 0; i <= T_MASK; i++) {
        VALUE type;
        switch (i) {
#define COUNT_TYPE(t) case t: type = ID2SYM(rb_intern(#t)); break;
            COUNT_TYPE(T_NONE);
            COUNT_TYPE(T_OBJECT);
            COUNT_TYPE(T_CLASS);
            COUNT_TYPE(T_MODULE);
            COUNT_TYPE(T_FLOAT);
            COUNT_TYPE(T_STRING);
            COUNT_TYPE(T_REGEXP);
            COUNT_TYPE(T_ARRAY);
            COUNT_TYPE(T_HASH);
            COUNT_TYPE(T_STRUCT);
            COUNT_TYPE(T_BIGNUM);
            COUNT_TYPE(T_FILE);
            COUNT_TYPE(T_DATA);
            COUNT_TYPE(T_MATCH);
            COUNT_TYPE(T_COMPLEX);
            COUNT_TYPE(T_RATIONAL);
            COUNT_TYPE(T_NIL);
            COUNT_TYPE(T_TRUE);
            COUNT_TYPE(T_FALSE);
            COUNT_TYPE(T_SYMBOL);
            COUNT_TYPE(T_FIXNUM);
            COUNT_TYPE(T_UNDEF);
            COUNT_TYPE(T_NODE);
            COUNT_TYPE(T_ICLASS);
#undef COUNT_TYPE
          default:              type = INT2NUM(i); break;
        }
        if (counts[i])
            rb_hash_aset(hash, type, SIZET2NUM(counts[i]));
    }

    return hash;
}