// Copyright (c) 2012, 2017 Peter Ohler. All rights reserved.

#include "dump.h"
#include "odd.h"
#include "trace.h"

static const char	hex_chars[17] = "0123456789abcdef";

static void	dump_obj_attrs(VALUE obj, VALUE clas, slot_t id, int depth, Out out);

static void
dump_time(VALUE obj, Out out) {
    switch (out->opts->time_format) {
    case RubyTime:
    case XmlTime:	oj_dump_xml_time(obj, out);	break;
    case UnixZTime:	oj_dump_time(obj, out, 1);	break;
    case UnixTime:
    default:		oj_dump_time(obj, out, 0);	break;
    }
}

static void
dump_data(VALUE obj, int depth, Out out, bool as_ok) {
    VALUE	clas = rb_obj_class(obj);

    if (rb_cTime == clas) {
	assure_size(out, 6);
	*out->cur++ = '{';
	*out->cur++ = '"';
	*out->cur++ = '^';
	*out->cur++ = 't';
	*out->cur++ = '"';
	*out->cur++ = ':';
	dump_time(obj, out);
	*out->cur++ = '}';
	*out->cur = '\0';
    } else {
	if (oj_bigdecimal_class == clas) {
	    volatile VALUE	rstr = rb_funcall(obj, oj_to_s_id, 0);
	    const char		*str = rb_string_value_ptr((VALUE*)&rstr);
	    int			len = (int)RSTRING_LEN(rstr);

	    if (No != out->opts->bigdec_as_num) {
		oj_dump_raw(str, len, out);
	    } else if (0 == strcasecmp("Infinity", str)) {
		str = oj_nan_str(obj, out->opts->dump_opts.nan_dump, out->opts->mode, true, &len);
		oj_dump_raw(str, len, out);
	    } else if (0 == strcasecmp("-Infinity", str)) {
		str = oj_nan_str(obj, out->opts->dump_opts.nan_dump, out->opts->mode, false, &len);
		oj_dump_raw(str, len, out);
	    } else {
		oj_dump_cstr(str, len, 0, 0, out);
	    }
	} else {
	    long	id = oj_check_circular(obj, out);

	    if (0 <= id) {
		dump_obj_attrs(obj, clas, id, depth, out);
	    }
	}
    }
}

static void
dump_obj(VALUE obj, int depth, Out out, bool as_ok) {
    VALUE	clas = rb_obj_class(obj);

    if (oj_bigdecimal_class == clas) {
	volatile VALUE	rstr = rb_funcall(obj, oj_to_s_id, 0);
	const char	*str = rb_string_value_ptr((VALUE*)&rstr);
	int		len = (int)RSTRING_LEN(rstr);

	if (0 == strcasecmp("Infinity", str)) {
	    str = oj_nan_str(obj, out->opts->dump_opts.nan_dump, out->opts->mode, true, &len);
	    oj_dump_raw(str, len, out);
	} else if (0 == strcasecmp("-Infinity", str)) {
	    str = oj_nan_str(obj, out->opts->dump_opts.nan_dump, out->opts->mode, false, &len);
	    oj_dump_raw(str, len, out);
	} else {
	    oj_dump_raw(str, len, out);
	}
    } else {
	long	id = oj_check_circular(obj, out);

	if (0 <= id) {
	    dump_obj_attrs(obj, clas, id, depth, out);
	}
    }
}

static void
dump_class(VALUE obj, int depth, Out out, bool as_ok) {
    const char	*s = rb_class2name(obj);
    size_t	len = strlen(s);

    assure_size(out, 6);
    *out->cur++ = '{';
    *out->cur++ = '"';
    *out->cur++ = '^';
    *out->cur++ = 'c';
    *out->cur++ = '"';
    *out->cur++ = ':';
    oj_dump_cstr(s, len, 0, 0, out);
    *out->cur++ = '}';
    *out->cur = '\0';
}

static void
dump_array_class(VALUE a, VALUE clas, int depth, Out out) {
    size_t	size;
    int		i, cnt;
    int		d2 = depth + 1;
    long	id = oj_check_circular(a, out);

    if (id < 0) {
	return;
    }
    if (Qundef != clas && rb_cArray != clas && ObjectMode == out->opts->mode) {
	dump_obj_attrs(a, clas, 0, depth, out);
	return;
    }
    cnt = (int)RARRAY_LEN(a);
    *out->cur++ = '[';
    if (0 < id) {
	assure_size(out, d2 * out->indent + 16);
	fill_indent(out, d2);
	*out->cur++ = '"';
	*out->cur++ = '^';
	*out->cur++ = 'i';
	dump_ulong(id, out);
	*out->cur++ = '"';
    }
    size = 2;
    assure_size(out, 2);
    if (0 == cnt) {
	*out->cur++ = ']';
    } else {
	if (0 < id) {
	    *out->cur++ = ',';
	}
	if (out->opts->dump_opts.use) {
	    size = d2 * out->opts->dump_opts.indent_size + out->opts->dump_opts.array_size + 1;
	} else {
	    size = d2 * out->indent + 2;
	}
	cnt--;
	for (i = 0; i <= cnt; i++) {
	    assure_size(out, size);
	    if (out->opts->dump_opts.use) {
		if (0 < out->opts->dump_opts.array_size) {
		    strcpy(out->cur, out->opts->dump_opts.array_nl);
		    out->cur += out->opts->dump_opts.array_size;
		}
		if (0 < out->opts->dump_opts.indent_size) {
		    int	i;
		    for (i = d2; 0 < i; i--) {
			strcpy(out->cur, out->opts->dump_opts.indent_str);
			out->cur += out->opts->dump_opts.indent_size;
		    }
		}
	    } else {
		fill_indent(out, d2);
	    }
	    oj_dump_obj_val(rb_ary_entry(a, i), d2, out);
	    if (i < cnt) {
		*out->cur++ = ',';
	    }
	}
	size = depth * out->indent + 1;
	assure_size(out, size);
	if (out->opts->dump_opts.use) {
	    //printf("*** d2: %u  indent: %u '%s'\n", d2, out->opts->dump_opts->indent_size, out->opts->dump_opts->indent);
	    if (0 < out->opts->dump_opts.array_size) {
		strcpy(out->cur, out->opts->dump_opts.array_nl);
		out->cur += out->opts->dump_opts.array_size;
	    }
	    if (0 < out->opts->dump_opts.indent_size) {
		int	i;

		for (i = depth; 0 < i; i--) {
		    strcpy(out->cur, out->opts->dump_opts.indent_str);
		    out->cur += out->opts->dump_opts.indent_size;
		}
	    }
	} else {
	    fill_indent(out, depth);
	}
	*out->cur++ = ']';
    }
    *out->cur = '\0';
}

static void
dump_array(VALUE obj, int depth, Out out, bool as_ok) {
    dump_array_class(obj, rb_obj_class(obj), depth, out);
}

static void
dump_str_class(VALUE obj, VALUE clas, int depth, Out out) {
    if (Qundef != clas && rb_cString != clas) {
	dump_obj_attrs(obj, clas, 0, depth, out);
    } else {
	const char	*s = rb_string_value_ptr((VALUE*)&obj);
	size_t		len = (int)RSTRING_LEN(obj);
	char		s1 = s[1];

	oj_dump_cstr(s, len, 0, (':' == *s || ('^' == *s && ('r' == s1 || 'i' == s1))), out);
    }
}

static void
dump_str(VALUE obj, int depth, Out out, bool as_ok) {
    dump_str_class(obj, rb_obj_class(obj), depth, out);
}

static void
dump_sym(VALUE obj, int depth, Out out, bool as_ok) {
    volatile VALUE	s = rb_sym_to_s(obj);

    oj_dump_cstr(rb_string_value_ptr((VALUE*)&s), (int)RSTRING_LEN(s), 1, 0, out);
}

static int
hash_cb(VALUE key, VALUE value, VALUE ov) {
    Out		out = (Out)ov;
    int		depth = out->depth;
    long	size = depth * out->indent + 1;

    if (oj_dump_ignore(out->opts, value)) {
	return ST_CONTINUE;
    }
    if (out->omit_nil && Qnil == value) {
	return ST_CONTINUE;
    }
    assure_size(out, size);
    fill_indent(out, depth);
    if (rb_type(key) == T_STRING) {
	dump_str_class(key, Qundef, depth, out);
	*out->cur++ = ':';
	oj_dump_obj_val(value, depth, out);
    } else if (rb_type(key) == T_SYMBOL) {
	dump_sym(key, 0, out, false);
	*out->cur++ = ':';
	oj_dump_obj_val(value, depth, out);
    } else {
	int	d2 = depth + 1;
	long	s2 = size + out->indent + 1;
	int	i;
	int	started = 0;
	uint8_t	b;

	assure_size(out, s2 + 15);
	*out->cur++ = '"';
	*out->cur++ = '^';
	*out->cur++ = '#';
	out->hash_cnt++;
	for (i = 28; 0 <= i; i -= 4) {
	    b = (uint8_t)((out->hash_cnt >> i) & 0x0000000F);
	    if ('\0' != b) {
		started = 1;
	    }
	    if (started) {
		*out->cur++ = hex_chars[b];
	    }
	}
	*out->cur++ = '"';
	*out->cur++ = ':';
	*out->cur++ = '[';
	fill_indent(out, d2);
	oj_dump_obj_val(key, d2, out);
	assure_size(out, s2);
	*out->cur++ = ',';
	fill_indent(out, d2);
	oj_dump_obj_val(value, d2, out);
	assure_size(out, size);
	fill_indent(out, depth);
	*out->cur++ = ']';
    }
    out->depth = depth;
    *out->cur++ = ',';

    return ST_CONTINUE;
}

static void
dump_hash_class(VALUE obj, VALUE clas, int depth, Out out) {
    int		cnt;
    size_t	size;

    if (Qundef != clas && rb_cHash != clas) {
	dump_obj_attrs(obj, clas, 0, depth, out);
	return;
    }
    cnt = (int)RHASH_SIZE(obj);
    size = depth * out->indent + 2;
    assure_size(out, 2);
    if (0 == cnt) {
	*out->cur++ = '{';
	*out->cur++ = '}';
    } else {
	long	id = oj_check_circular(obj, out);

	if (0 > id) {
	    return;
	}
	*out->cur++ = '{';
	if (0 < id) {
	    assure_size(out, size + 16);
	    fill_indent(out, depth + 1);
	    *out->cur++ = '"';
	    *out->cur++ = '^';
	    *out->cur++ = 'i';
	    *out->cur++ = '"';
	    *out->cur++ = ':';
	    dump_ulong(id, out);
	    *out->cur++ = ',';
	}
	out->depth = depth + 1;
	rb_hash_foreach(obj, hash_cb, (VALUE)out);
	if (',' == *(out->cur - 1)) {
	    out->cur--; // backup to overwrite last comma
	}
	if (!out->opts->dump_opts.use) {
	    assure_size(out, size);
	    fill_indent(out, depth);
	} else {
	    size = depth * out->opts->dump_opts.indent_size + out->opts->dump_opts.hash_size + 1;
	    assure_size(out, size);
	    if (0 < out->opts->dump_opts.hash_size) {
		strcpy(out->cur, out->opts->dump_opts.hash_nl);
		out->cur += out->opts->dump_opts.hash_size;
	    }
	    if (0 < out->opts->dump_opts.indent_size) {
		int	i;

		for (i = depth; 0 < i; i--) {
		    strcpy(out->cur, out->opts->dump_opts.indent_str);
		    out->cur += out->opts->dump_opts.indent_size;
		}
	    }
	}
	*out->cur++ = '}';
    }
    *out->cur = '\0';
}

#ifdef HAVE_RB_IVAR_FOREACH
static int
dump_attr_cb(ID key, VALUE value, VALUE ov) {
    Out		out = (Out)ov;
    int		depth = out->depth;
    size_t	size = depth * out->indent + 1;
    const char	*attr = rb_id2name(key);

    if (oj_dump_ignore(out->opts, value)) {
	return ST_CONTINUE;
    }
    if (out->omit_nil && Qnil == value) {
	return ST_CONTINUE;
    }
    // Some exceptions such as NoMethodError have an invisible attribute where
    // the key name is NULL. Not an empty string but NULL.
    if (NULL == attr) {
	attr = "";
    } else if (Yes == out->opts->ignore_under && '@' == *attr && '_' == attr[1]) {
	return ST_CONTINUE;
    }
    if (0 == strcmp("bt", attr) || 0 == strcmp("mesg", attr)) {
	return ST_CONTINUE;
    }
    assure_size(out, size);
    fill_indent(out, depth);
    if ('@' == *attr) {
	attr++;
	oj_dump_cstr(attr, strlen(attr), 0, 0, out);
    } else {
	char	buf[32];

	*buf = '~';
	strncpy(buf + 1, attr, sizeof(buf) - 2);
	buf[sizeof(buf) - 1] = '\0';
	oj_dump_cstr(buf, strlen(buf), 0, 0, out);
    }
    *out->cur++ = ':';
    oj_dump_obj_val(value, depth, out);
    out->depth = depth;
    *out->cur++ = ',';

    return ST_CONTINUE;
}
#endif

static void
dump_hash(VALUE obj, int depth, Out out, bool as_ok) {
    dump_hash_class(obj, rb_obj_class(obj), depth, out);
}

static void
dump_odd(VALUE obj, Odd odd, VALUE clas, int depth, Out out) {
    ID			*idp;
    AttrGetFunc		*fp;
    volatile VALUE	v;
    const char		*name;
    size_t		size;
    int			d2 = depth + 1;

    assure_size(out, 2);
    *out->cur++ = '{';
    if (Qundef != clas) {
	const char	*class_name = rb_class2name(clas);
	int		clen = (int)strlen(class_name);

	size = d2 * out->indent + clen + 10;
	assure_size(out, size);
	fill_indent(out, d2);
	*out->cur++ = '"';
	*out->cur++ = '^';
	*out->cur++ = 'O';
	*out->cur++ = '"';
	*out->cur++ = ':';
	oj_dump_cstr(class_name, clen, 0, 0, out);
	*out->cur++ = ',';
    }
    if (odd->raw) {
	v = rb_funcall(obj, *odd->attrs, 0);
	if (Qundef == v || T_STRING != rb_type(v)) {
	    rb_raise(rb_eEncodingError, "Invalid type for raw JSON.");
	} else {
	    const char	*s = rb_string_value_ptr((VALUE*)&v);
	    int		len = (int)RSTRING_LEN(v);
	    const char	*name = rb_id2name(*odd->attrs);
	    size_t	nlen = strlen(name);

	    size = len + d2 * out->indent + nlen + 10;
	    assure_size(out, size);
	    fill_indent(out, d2);
	    *out->cur++ = '"';
	    memcpy(out->cur, name, nlen);
	    out->cur += nlen;
	    *out->cur++ = '"';
	    *out->cur++ = ':';
	    memcpy(out->cur, s, len);
	    out->cur += len;
	    *out->cur = '\0';
	}
    } else {
	size = d2 * out->indent + 1;
	for (idp = odd->attrs, fp = odd->attrFuncs; 0 != *idp; idp++, fp++) {
	    size_t	nlen;

	    assure_size(out, size);
	    name = rb_id2name(*idp);
	    nlen = strlen(name);
	    if (0 != *fp) {
		v = (*fp)(obj);
	    } else if (0 == strchr(name, '.')) {
		v = rb_funcall(obj, *idp, 0);
	    } else {
		char	nbuf[256];
		char	*n2 = nbuf;
		char	*n;
		char	*end;
		ID	i;

		if (sizeof(nbuf) <= nlen) {
		    if (NULL == (n2 = strdup(name))) {
			rb_raise(rb_eNoMemError, "for attribute name.");
		    }
		} else {
		    strcpy(n2, name);
		}
		n = n2;
		v = obj;
		while (0 != (end = strchr(n, '.'))) {
		    *end = '\0';
		    i = rb_intern(n);
		    v = rb_funcall(v, i, 0);
		    n = end + 1;
		}
		i = rb_intern(n);
		v = rb_funcall(v, i, 0);
		if (nbuf != n2) {
		    free(n2);
		}
	    }
	    fill_indent(out, d2);
	    oj_dump_cstr(name, nlen, 0, 0, out);
	    *out->cur++ = ':';
	    oj_dump_obj_val(v, d2, out);
	    assure_size(out, 2);
	    *out->cur++ = ',';
	}
	out->cur--;
    }
    *out->cur++ = '}';
    *out->cur = '\0';
}

static void
dump_obj_attrs(VALUE obj, VALUE clas, slot_t id, int depth, Out out) {
    size_t	size = 0;
    int		d2 = depth + 1;
    int		type = rb_type(obj);
    Odd		odd;

    if (0 != (odd = oj_get_odd(clas))) {
	dump_odd(obj, odd, clas, depth + 1, out);
	return;
    }
    assure_size(out, 2);
    *out->cur++ = '{';
    if (Qundef != clas) {
	const char	*class_name = rb_class2name(clas);
	int		clen = (int)strlen(class_name);

	assure_size(out, d2 * out->indent + clen + 10);
	fill_indent(out, d2);
	*out->cur++ = '"';
	*out->cur++ = '^';
	*out->cur++ = 'o';
	*out->cur++ = '"';
	*out->cur++ = ':';
	oj_dump_cstr(class_name, clen, 0, 0, out);
    }
    if (0 < id) {
	assure_size(out, d2 * out->indent + 16);
	*out->cur++ = ',';
	fill_indent(out, d2);
	*out->cur++ = '"';
	*out->cur++ = '^';
	*out->cur++ = 'i';
	*out->cur++ = '"';
	*out->cur++ = ':';
	dump_ulong(id, out);
    }
    switch (type) {
    case T_STRING:
	assure_size(out, d2 * out->indent + 14);
	*out->cur++ = ',';
	fill_indent(out, d2);
	*out->cur++ = '"';
	*out->cur++ = 's';
	*out->cur++ = 'e';
	*out->cur++ = 'l';
	*out->cur++ = 'f';
	*out->cur++ = '"';
	*out->cur++ = ':';
	oj_dump_cstr(rb_string_value_ptr((VALUE*)&obj), (int)RSTRING_LEN(obj), 0, 0, out);
	break;
    case T_ARRAY:
	assure_size(out, d2 * out->indent + 14);
	*out->cur++ = ',';
	fill_indent(out, d2);
	*out->cur++ = '"';
	*out->cur++ = 's';
	*out->cur++ = 'e';
	*out->cur++ = 'l';
	*out->cur++ = 'f';
	*out->cur++ = '"';
	*out->cur++ = ':';
	dump_array_class(obj, Qundef, depth + 1, out);
	break;
    case T_HASH:
	assure_size(out, d2 * out->indent + 14);
	*out->cur++ = ',';
	fill_indent(out, d2);
	*out->cur++ = '"';
	*out->cur++ = 's';
	*out->cur++ = 'e';
	*out->cur++ = 'l';
	*out->cur++ = 'f';
	*out->cur++ = '"';
	*out->cur++ = ':';
	dump_hash_class(obj, Qundef, depth + 1, out);
	break;
    default:
	break;
    }
    {
	int	cnt;
#ifdef HAVE_RB_IVAR_COUNT
	cnt = (int)rb_ivar_count(obj);
#else
	volatile VALUE	vars = rb_funcall2(obj, oj_instance_variables_id, 0, 0);
	VALUE		*np = RARRAY_PTR(vars);
	ID		vid;
	const char	*attr;
	int		i;
	int		first = 1;

	cnt = (int)RARRAY_LEN(vars);
#endif
	if (Qundef != clas && 0 < cnt) {
	    *out->cur++ = ',';
	}
	if (0 == cnt && Qundef == clas) {
	    // Might be something special like an Enumerable.
	    if (Qtrue == rb_obj_is_kind_of(obj, oj_enumerable_class)) {
		out->cur--;
		oj_dump_obj_val(rb_funcall(obj, rb_intern("entries"), 0), depth, out);
		return;
	    }
	}
	out->depth = depth + 1;
#ifdef HAVE_RB_IVAR_FOREACH
	rb_ivar_foreach(obj, dump_attr_cb, (VALUE)out);
	if (',' == *(out->cur - 1)) {
	    out->cur--; // backup to overwrite last comma
	}
#else
	size = d2 * out->indent + 1;
	for (i = cnt; 0 < i; i--, np++) {
	    VALUE	value;

	    vid = rb_to_id(*np);
	    attr = rb_id2name(vid);
	    if (Yes == out->opts->ignore_under && '@' == *attr && '_' == attr[1]) {
		continue;
	    }
	    value = rb_ivar_get(obj, vid);

	    if (oj_dump_ignore(out->opts, value)) {
		continue;
	    }
	    if (out->omit_nil && Qnil == value) {
		continue;
	    }
	    if (first) {
		first = 0;
	    } else {
		*out->cur++ = ',';
	    }
	    assure_size(out, size);
	    fill_indent(out, d2);
	    if ('@' == *attr) {
		attr++;
		oj_dump_cstr(attr, strlen(attr), 0, 0, out);
	    } else {
		char	buf[32];

		*buf = '~';
		strncpy(buf + 1, attr, sizeof(buf) - 2);
		buf[sizeof(buf) - 1] = '\0';
		oj_dump_cstr(buf, strlen(attr) + 1, 0, 0, out);
	    }
	    *out->cur++ = ':';
	    oj_dump_obj_val(value, d2, out);
	    assure_size(out, 2);
	}
#endif
	if (rb_obj_is_kind_of(obj, rb_eException)) {
	    volatile VALUE	rv;

	    if (',' != *(out->cur - 1)) {
		*out->cur++ = ',';
	    }
	    // message
	    assure_size(out, size);
	    fill_indent(out, d2);
	    oj_dump_cstr("~mesg", 5, 0, 0, out);
	    *out->cur++ = ':';
	    rv = rb_funcall2(obj, rb_intern("message"), 0, 0);
	    oj_dump_obj_val(rv, d2, out);
	    assure_size(out, 2);
	    *out->cur++ = ',';
	    // backtrace
	    assure_size(out, size);
	    fill_indent(out, d2);
	    oj_dump_cstr("~bt", 3, 0, 0, out);
	    *out->cur++ = ':';
	    rv = rb_funcall2(obj, rb_intern("backtrace"), 0, 0);
	    oj_dump_obj_val(rv, d2, out);
	    assure_size(out, 2);
	}
	out->depth = depth;
    }
    fill_indent(out, depth);
    *out->cur++ = '}';
    *out->cur = '\0';
}

static void
dump_regexp(VALUE obj, int depth, Out out, bool as_ok) {
    dump_obj_attrs(obj, rb_obj_class(obj), 0, depth, out);
}

static void
dump_struct(VALUE obj, int depth, Out out, bool as_ok) {
    VALUE	clas = rb_obj_class(obj);
    const char	*class_name = rb_class2name(clas);
    int		i;
    int		d2 = depth + 1;
    int		d3 = d2 + 1;
    size_t	len = strlen(class_name);
    size_t	size = d2 * out->indent + d3 * out->indent + 10 + len;

    assure_size(out, size);
    *out->cur++ = '{';
    fill_indent(out, d2);
    *out->cur++ = '"';
    *out->cur++ = '^';
    *out->cur++ = 'u';
    *out->cur++ = '"';
    *out->cur++ = ':';
    *out->cur++ = '[';
    if ('#' == *class_name) {
	VALUE		ma = rb_struct_s_members(clas);
	const char	*name;
	int		cnt = (int)RARRAY_LEN(ma);

	*out->cur++ = '[';
	for (i = 0; i < cnt; i++) {
	    volatile VALUE	s = rb_sym_to_s(rb_ary_entry(ma, i));

	    name = rb_string_value_ptr((VALUE*)&s);
	    len = (int)RSTRING_LEN(s);
	    size = len + 3;
	    assure_size(out, size);
	    if (0 < i) {
		*out->cur++ = ',';
	    }
	    *out->cur++ = '"';
	    memcpy(out->cur, name, len);
	    out->cur += len;
	    *out->cur++ = '"';
	}
	*out->cur++ = ']';
    } else {
	fill_indent(out, d3);
	*out->cur++ = '"';
	memcpy(out->cur, class_name, len);
	out->cur += len;
	*out->cur++ = '"';
    }
    *out->cur++ = ',';
    size = d3 * out->indent + 2;
#ifdef RSTRUCT_LEN
    {
	VALUE	v;
	int	cnt;
#if RSTRUCT_LEN_RETURNS_INTEGER_OBJECT
	cnt = (int)NUM2LONG(RSTRUCT_LEN(obj));
#else // RSTRUCT_LEN_RETURNS_INTEGER_OBJECT
	cnt = (int)RSTRUCT_LEN(obj);
#endif // RSTRUCT_LEN_RETURNS_INTEGER_OBJECT

	for (i = 0; i < cnt; i++) {
	    v = RSTRUCT_GET(obj, i);
	    if (oj_dump_ignore(out->opts, v)) {
		v = Qnil;
	    }
	    assure_size(out, size);
	    fill_indent(out, d3);
	    oj_dump_obj_val(v, d3, out);
	    *out->cur++ = ',';
	}
    }
#else
    {
	// This is a bit risky as a struct in C ruby is not the same as a Struct
	// class in interpreted Ruby so length() may not be defined.
	int	slen = FIX2INT(rb_funcall2(obj, oj_length_id, 0, 0));

	for (i = 0; i < slen; i++) {
	    assure_size(out, size);
	    fill_indent(out, d3);
	    if (oj_dump_ignore(out->opts, v)) {
		v = Qnil;
	    }
	    oj_dump_obj_val(rb_struct_aref(obj, INT2FIX(i)), d3, out, 0, 0, true);
	    *out->cur++ = ',';
	}
    }
#endif
    out->cur--;
    *out->cur++ = ']';
    *out->cur++ = '}';
    *out->cur = '\0';
}

static void
dump_complex(VALUE obj, int depth, Out out, bool as_ok) {
    dump_obj_attrs(obj, rb_obj_class(obj), 0, depth, out);
}

static void
dump_rational(VALUE obj, int depth, Out out, bool as_ok) {
    dump_obj_attrs(obj, rb_obj_class(obj), 0, depth, out);
}

static DumpFunc	obj_funcs[] = {
    NULL,	 	// RUBY_T_NONE   = 0x00,
    dump_obj,		// RUBY_T_OBJECT = 0x01,
    dump_class, 	// RUBY_T_CLASS  = 0x02,
    dump_class,		// RUBY_T_MODULE = 0x03,
    oj_dump_float, 	// RUBY_T_FLOAT  = 0x04,
    dump_str,	 	// RUBY_T_STRING = 0x05,
    dump_regexp,	// RUBY_T_REGEXP = 0x06,
    dump_array,		// RUBY_T_ARRAY  = 0x07,
    dump_hash,	 	// RUBY_T_HASH   = 0x08,
    dump_struct,	// RUBY_T_STRUCT = 0x09,
    oj_dump_bignum,	// RUBY_T_BIGNUM = 0x0a,
    NULL, 		// RUBY_T_FILE   = 0x0b,
    dump_data,		// RUBY_T_DATA   = 0x0c,
    NULL, 		// RUBY_T_MATCH  = 0x0d,
    dump_complex, 	// RUBY_T_COMPLEX  = 0x0e,
    dump_rational, 	// RUBY_T_RATIONAL = 0x0f,
    NULL, 		// 0x10
    oj_dump_nil, 	// RUBY_T_NIL    = 0x11,
    oj_dump_true, 	// RUBY_T_TRUE   = 0x12,
    oj_dump_false,	// RUBY_T_FALSE  = 0x13,
    dump_sym,		// RUBY_T_SYMBOL = 0x14,
    oj_dump_fixnum,	// RUBY_T_FIXNUM = 0x15,
};

void
oj_dump_obj_val(VALUE obj, int depth, Out out) {
    int	type = rb_type(obj);

    if (Yes == out->opts->trace) {
	oj_trace("dump", obj, __FILE__, __LINE__, depth, TraceIn);
    }
    if (MAX_DEPTH < depth) {
	rb_raise(rb_eNoMemError, "Too deeply nested.\n");
    }
    if (0 < type && type <= RUBY_T_FIXNUM) {
	DumpFunc	f = obj_funcs[type];

	if (NULL != f) {
	    f(obj, depth, out, false);
	    if (Yes == out->opts->trace) {
		oj_trace("dump", obj, __FILE__, __LINE__, depth, TraceOut);
	    }
	    return;
	}
    }
    oj_dump_nil(Qnil, depth, out, false);
    if (Yes == out->opts->trace) {
	oj_trace("dump", Qnil, __FILE__, __LINE__, depth, TraceOut);
    }
}
