// Copyright (c) 2017 Peter Ohler. All rights reserved.
// Licensed under the MIT License. See LICENSE file in the project root for license details.

#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++ = '"';
        APPEND_CHARS(out->cur, out->opts->create_id, out->opts->create_id_len);
        *out->cur++ = '"';
        if (0 < out->opts->dump_opts.before_size) {
            APPEND_CHARS(out->cur, out->opts->dump_opts.before_sep, out->opts->dump_opts.before_size);
        }
        *out->cur++ = ':';
        if (0 < out->opts->dump_opts.after_size) {
            APPEND_CHARS(out->cur, out->opts->dump_opts.after_sep, out->opts->dump_opts.after_size);
        }
        *out->cur++ = '"';
        APPEND_CHARS(out->cur, classname, 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++ = '"';
        APPEND_CHARS(out->cur, attrs->name, attrs->len);
        *out->cur++ = '"';
        if (0 < out->opts->dump_opts.before_size) {
            APPEND_CHARS(out->cur, out->opts->dump_opts.before_sep, out->opts->dump_opts.before_size);
        }
        *out->cur++ = ':';
        if (0 < out->opts->dump_opts.after_size) {
            APPEND_CHARS(out->cur, out->opts->dump_opts.after_sep, 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;
                bool   neg = false;
                long   num = attrs->num;
                size_t cnt = 0;

                if (0 > num) {
                    neg = true;
                    num = -num;
                }
                *b-- = '\0';
                if (0 < num) {
                    b = oj_longlong_to_string(num, neg, b);
                } else {
                    *b = '0';
                }
                cnt = sizeof(buf) - (b - buf) - 1;
                assure_size(out, cnt);
                APPEND_CHARS(out->cur, b, cnt);
            }
        } 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';
}
