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

#include "code.h"
#include "dump.h"

inline static VALUE
resolve_classname(VALUE mod, const char *classname) {
    VALUE	clas = Qundef;
    ID		ci = rb_intern(classname);

    if (rb_const_defined_at(mod, ci)) {
	clas = rb_const_get_at(mod, ci);
    }
    return clas;
}

static VALUE
path2class(const char *name) {
    char	class_name[1024];
    VALUE	clas;
    char	*end = class_name + sizeof(class_name) - 1;
    char	*s;
    const char	*n = name;

    clas = rb_cObject;
    for (s = class_name; '\0' != *n; n++) {
	if (':' == *n) {
	    *s = '\0';
	    n++;
	    if (':' != *n) {
		return Qundef;
	    }
	    if (Qundef == (clas = resolve_classname(clas, class_name))) {
		return Qundef;
	    }
	    s = class_name;
	} else if (end <= s) {
	    return Qundef;
	} else {
	    *s++ = *n;
	}
    }
    *s = '\0';

    return resolve_classname(clas, class_name);
}

bool
oj_code_dump(Code codes, VALUE obj, int depth, Out out) {
    VALUE	clas = rb_obj_class(obj);
    Code	c = codes;

    for (; NULL != c->name; c++) {
	if (Qundef == c->clas) { // indicates not defined
	    continue;
	}
	if (Qnil == c->clas) {
	    c->clas = path2class(c->name);
	}
	if (clas == c->clas && c->active) {
	    c->encode(obj, depth, out);
	    return true;
	}
    }
    return false;
}

VALUE
oj_code_load(Code codes, VALUE clas, VALUE args) {
    Code	c = codes;

    for (; NULL != c->name; c++) {
	if (Qundef == c->clas) { // indicates not defined
	    continue;
	}
	if (Qnil == c->clas) {
	    c->clas = path2class(c->name);
	}
	if (clas == c->clas) {
	    if (NULL == c->decode) {
		break;
	    }
	    return c->decode(clas, args);
	}
    }
    return Qnil;
}

void
oj_code_set_active(Code codes, VALUE clas, bool active) {
    Code	c = codes;

    for (; NULL != c->name; c++) {
	if (Qundef == c->clas) { // indicates not defined
	    continue;
	}
	if (Qnil == c->clas) {
	    c->clas = path2class(c->name);
	}
	if (clas == c->clas || Qnil == clas) {
	    c->active = active;
	    if (Qnil != clas) {
		break;
	    }
	}
    }
}

bool
oj_code_has(Code codes, VALUE clas, bool encode) {
    Code	c = codes;

    for (; NULL != c->name; c++) {
	if (Qundef == c->clas) { // indicates not defined
	    continue;
	}
	if (Qnil == c->clas) {
	    c->clas = path2class(c->name);
	}
	if (clas == c->clas) {
	    if (encode) {
		return c->active && NULL != c->encode;
	    } else {
		return c->active && NULL != c->decode;
	    }
	}
    }
    return false;
}

void
oj_code_attrs(VALUE obj, Attr attrs, int depth, Out out, bool with_class) {
    int		d2 = depth + 1;
    int		d3 = d2 + 1;
    size_t	sep_len = out->opts->dump_opts.before_size + out->opts->dump_opts.after_size + 2;
    const char	*classname = rb_obj_classname(obj);
    size_t	len = strlen(classname);
    size_t	size = d2 * out->indent + 10 + len + out->opts->create_id_len + sep_len;
    bool	no_comma = true;

    assure_size(out, size);
    *out->cur++ = '{';

    if (with_class) {
	fill_indent(out, d2);
	*out->cur++ = '"';
	memcpy(out->cur, out->opts->create_id, out->opts->create_id_len);
	out->cur += out->opts->create_id_len;
	*out->cur++ = '"';
	if (0 < out->opts->dump_opts.before_size) {
	    strcpy(out->cur, out->opts->dump_opts.before_sep);
	    out->cur += out->opts->dump_opts.before_size;
	}
	*out->cur++ = ':';
	if (0 < out->opts->dump_opts.after_size) {
	    strcpy(out->cur, out->opts->dump_opts.after_sep);
	    out->cur += out->opts->dump_opts.after_size;
	}
	*out->cur++ = '"';
	memcpy(out->cur, classname, len);
	out->cur += len;
	*out->cur++ = '"';
	no_comma = false;
    }
    size = d3 * out->indent + 2;
    for (; NULL != attrs->name; attrs++) {
	assure_size(out, size + attrs->len + sep_len + 2);
	if (no_comma) {
	    no_comma = false;
	} else {
	    *out->cur++ = ',';
	}
	fill_indent(out, d2);
	*out->cur++ = '"';
	memcpy(out->cur, attrs->name, attrs->len);
	out->cur += attrs->len;
	*out->cur++ = '"';
	if (0 < out->opts->dump_opts.before_size) {
	    strcpy(out->cur, out->opts->dump_opts.before_sep);
	    out->cur += out->opts->dump_opts.before_size;
	}
	*out->cur++ = ':';
	if (0 < out->opts->dump_opts.after_size) {
	    strcpy(out->cur, out->opts->dump_opts.after_sep);
	    out->cur += out->opts->dump_opts.after_size;
	}
	if (Qundef == attrs->value) {
	    if (Qundef != attrs->time) {
		switch (out->opts->time_format) {
		case RubyTime:	oj_dump_ruby_time(attrs->time, out);	break;
		case XmlTime:	oj_dump_xml_time(attrs->time, out);	break;
		case UnixZTime:	oj_dump_time(attrs->time, out, true);	break;
		case UnixTime:
		default:	oj_dump_time(attrs->time, out, false);	break;
		}
	    } else {
		char	buf[32];
		char	*b = buf + sizeof(buf) - 1;
		int	neg = 0;
		long	num = attrs->num;

		if (0 > num) {
		    neg = 1;
		    num = -num;
		}
		*b-- = '\0';
		if (0 < num) {
		    for (; 0 < num; num /= 10, b--) {
			*b = (num % 10) + '0';
		    }
		    if (neg) {
			*b = '-';
		    } else {
			b++;
		    }
		} else {
		    *b = '0';
		}
		assure_size(out, (sizeof(buf) - (b - buf)));
		for (; '\0' != *b; b++) {
		    *out->cur++ = *b;
		}
	    }
	} else {
	    oj_dump_compat_val(attrs->value, d3, out, true);
	}
    }
    assure_size(out, depth * out->indent + 2);
    fill_indent(out, depth);
    *out->cur++ = '}';
    *out->cur = '\0';
}
