/*---------------------------------------------------------------------------*/
/* json.cpp                                                                  */
/* copyright (c) innovaphone 2013 - 2017                                     */
/*                                                                           */
/*---------------------------------------------------------------------------*/

#include <platform.h>
#include "../ilib/str.h"
#include "../ilib/json.h"

#define JSON_ALLOC_SIZE 128

USING_NAMESPACE

json_io::json_io(char * buffer)
{
    this->buffer = buffer;
    index = 0;
    last = 0;
    max_item = JSON_MAX_ITEM - 1;
    name_last = 0;
    incomplete = 0;
    level = 0;
}

void json_io::reset()
{
    index = 0;
    last = 0;
    name_last = 0;
    incomplete = 0;
}

void json_io::dump()
{
    static const char * types [] = {"OBJECT","ARRAY","VALUE","QUOTED"};

    for(unsigned i=0; i<index && i<256; i++) {
        debug->iprintf("(%u): base=%u count=%u flags=%u %s=%s/%s",
            i,
            content[i].base,
            content[i].count,
            content[i].flags,
            types[content[i].type],
            content[i].name,
            content[i].info);
    }
}

/*---------------------------------------------------------------------------*/
/* functions to build up the descriptor list                                 */
/*---------------------------------------------------------------------------*/

word json_io::add(byte type, byte flags, word base, const char * name, const char * info, dword len)
{
    word current;
    if(base>=index) {
        current = 0;
    }
    else {
        current = base + content[base].count + 1;
    }
    if(current!=index) return 0;

    content[current].type = type;
    content[current].name = name;
    if(type==JSON_TYPE_OBJECT || type==JSON_TYPE_ARRAY || type==JSON_TYPE_CONTENT_PACKET) {
        content[current].count = 0;
    }
    else {
        if(len==0xffffffff) len = info ? (dword)strlen(info) : 0;
        content[current].count = len;
    }
    content[current].base = base;
    content[current].flags = flags;
    content[current].info = info;

    while(base<index) {
        content[base].count++;
        base = content[base].base;
    }

    index++;
    return current;
}

#if !defined(PLATFORM_NO_PACKET)
void json_io::add_json_data(word base, class packet * data, const char * name)
{
    ASSERT(name || base == 0xffff || content[base].type == JSON_TYPE_ARRAY, "json_io::add_json_data - item must have a name");
    if (data) add(JSON_TYPE_CONTENT_PACKET, 0, base, name, (char *)data);
}
#endif

word json_io::add_object(word base, const char * name)
{
    ASSERT(name || base==0xffff || content[base].type==JSON_TYPE_ARRAY, "json_io::add_object - item must have a name");
    return add(JSON_TYPE_OBJECT,JSON_FLAG_COMPLETE,base,name,0);
}

word json_io::add_array(word base, const char * name)
{
    ASSERT(name || base==0xffff || content[base].type==JSON_TYPE_ARRAY, "json_io::add_array - item must have a name");
    return add(JSON_TYPE_ARRAY,JSON_FLAG_COMPLETE,base,name,0);
}

void json_io::add_string(word base, const char * name, const char * value, dword len)
{
    ASSERT(name || base==0xffff || content[base].type==JSON_TYPE_ARRAY, "json_io::add_string - item must have a name");
    if(value) {
        add(JSON_TYPE_VALUE,JSON_FLAG_QUOTED,base,name,value,len);
    }
}

void json_io::add_string(word base, const char * name, const word * value, dword len, char * & tmp)
{
    ASSERT(name || base == 0xffff || content[base].type == JSON_TYPE_ARRAY, "json_io::add_string - item must have a name");
    if (value) {
        int l = (len == 0xffffffff) ? str::from_ucs2(value, tmp, 0xffffffff) : str::from_ucs2_n(value, len, tmp, 0xffffffff);
        add(JSON_TYPE_VALUE,JSON_FLAG_QUOTED,base,name,tmp,l);
        tmp +=(l+1);
    }
}

void json_io::add_replace_string(word base, const char * name, const char * value, dword len)
{
    ASSERT(name, "json_io::add_replace_string - item must have a name");
    word id = get_id(base, JSON_FLAG_QUOTED, name);
    if (id == 0xffff) {
        if (value) {
            add(JSON_TYPE_VALUE, JSON_FLAG_QUOTED, base, name, value, len);
        }
    }
    else {
        content[id].info = value;
        if(len==0xffffffff) len = value ? (word)strlen(value) : 0;
        content[id].count = len;
    }
}

void json_io::add_json(word base, const char * name, const char * value, dword len)
{
    ASSERT(name || base==0xffff || content[base].type==JSON_TYPE_ARRAY, "json_io::add_json - item must have a name");
    if(value) {
        add(JSON_TYPE_VALUE,0,base,name,value,len);
    }
}

void json_io::add_int(word base, const char * name, int c, char * & tmp)
{
    ASSERT(name || base==0xffff || content[base].type==JSON_TYPE_ARRAY, "json_io::add_int - item must have a name");
    int l = isprintf(tmp,"%i",c);
    add(JSON_TYPE_VALUE,0,base,name,tmp,l);
    tmp +=(l+1);
}

void json_io::add_unsigned(word base, const char * name, dword c, char * & tmp)
{
    ASSERT(name || base==0xffff || content[base].type==JSON_TYPE_ARRAY, "json_io::add_unsigned - item must have a name");
    int l = isprintf(tmp,"%u",c);
    add(JSON_TYPE_VALUE,0,base,name,tmp,l);
    tmp +=(l+1);
}

void json_io::add_long64(word base, const char * name, long64 c, char * & tmp)
{
    ASSERT(name || base == 0xffff || content[base].type == JSON_TYPE_ARRAY, "json_io::add_long64 - item must have a name");
    int l = isprintf(tmp, "%lli", (long long)c);
    add(JSON_TYPE_VALUE, 0, base, name, tmp, l);
    tmp += (l + 1);
}

void json_io::add_ulong64(word base, const char * name, ulong64 c, char * & tmp)
{
    ASSERT(name || base == 0xffff || content[base].type == JSON_TYPE_ARRAY, "json_io::add_ulong64 - item must have a name");
    int l = isprintf(tmp, "%llu", (unsigned long long)c);
    add(JSON_TYPE_VALUE, 0, base, name, tmp, l);
    tmp += (l + 1);
}

#if !defined(PLATFORM_NO_IPADDR)
void json_io::add_ip(word base, const char * name, const IPaddr & a, char * & tmp)
{
    ASSERT(name || base==0xffff || content[base].type==JSON_TYPE_ARRAY, "json_io::add_ip - item must have a name");
    int l = isprintf(tmp,"%a",&a);
    add(JSON_TYPE_VALUE,JSON_FLAG_QUOTED,base,name,tmp,l);
    tmp +=(l+1);
}

void json_io::add_guid(word base, const char * name, OS_GUID * guid, char * & tmp)
{
    if (guid) add_hexstring(base, name, guid->b, sizeof(guid->b), tmp);
}
#endif

void json_io::add_printf(word base, const char * name, char * & tmp, const char * format, ...)
{
    ASSERT(name || base==0xffff || content[base].type==JSON_TYPE_ARRAY, "json_io::add_printf - item must have a name");
    va_list ap;
    va_start(ap,format);
    int l = ivsprintf(tmp, format, ap);
    va_end(ap);
    add(JSON_TYPE_VALUE,JSON_FLAG_QUOTED,base,name,tmp,l);
    tmp +=(l+1);
}

void json_io::add_printfv(word base, const char * name, char * & tmp, const char * format, va_list ap)
{
    ASSERT(name || base==0xffff || content[base].type==JSON_TYPE_ARRAY, "json_io::add_printfv - item must have a name");
    int l = ivsprintf(tmp, format, ap);
    add(JSON_TYPE_VALUE,JSON_FLAG_QUOTED,base,name,tmp,l);
    tmp +=(l+1);
}

void json_io::add_hexstring(word base, const char * name, const byte * hex, word hex_len, char * & tmp)
{
    ASSERT(name || base==0xffff || content[base].type==JSON_TYPE_ARRAY, "json_io::add_hexstring - item must have a name");
    str::from_hexmem(hex,hex_len,tmp);
    add(JSON_TYPE_VALUE,JSON_FLAG_QUOTED,base,name,tmp,hex_len*2);
    tmp +=(hex_len*2+1);
}

void json_io::add_bool(word base, const char * name, bool value)
{
    ASSERT(name || base==0xffff || content[base].type==JSON_TYPE_ARRAY, "json_io::add_bool - item must have a name");
    add(JSON_TYPE_VALUE,0,base,name,value?"true":"false");
}

void json_io::add_null(word base, const char * name)
{
    ASSERT(name || base==0xffff || content[base].type==JSON_TYPE_ARRAY, "json_io::add_null - item must have a name");
    add(JSON_TYPE_VALUE,0,base,name,"null");
}

#if defined(PLATFORM_FLOAT_SUPPORT)
void json_io::add_double(word base, const char * name, double c, char *& tmp, byte decimalPlaces)
{
    ASSERT(name || base == 0xffff || content[base].type == JSON_TYPE_ARRAY, "json_io::add_double - item must have a name");
    int l = _sprintf(tmp, "%.*f", (dword)decimalPlaces, c);     // do not use isprintf, as this doesn't support %f at all
    add(JSON_TYPE_VALUE, 0, base, name, tmp, l);
    tmp += (l + 1);
}
#endif

/*---------------------------------------------------------------------------*/
/* functions to walk thru the descriptor list                                */
/*---------------------------------------------------------------------------*/

word json_io::nextindex(word i)
{
    if(content[i].type==JSON_TYPE_OBJECT || content[i].type==JSON_TYPE_ARRAY) return i+content[i].count+1;
    return i+1;
}

word json_io::get_first(byte type, word base)
{
    word end;
    if(base==0xffff) {
        if(!index) return 0xffff;
        end = index-1;
        base = 0;
    }
    else {
        end = base + content[base].count;
        base++;
    }

    while(base<=end && type!=JSON_TYPE_ANY && content[base].type!=type) {
        base = nextindex(base);
    }
    if(base>end) return 0xffff;
    return base;
}

word json_io::get_next(byte type, word base, word last)
{
    word end;
    if(base==0xffff) {
        end = index-1;
        base = 0;
    }
    else {
        end = base + content[base].count;
    }
    last = nextindex(last);

    while(last<=end && type!=JSON_TYPE_ANY && content[last].type!=type) {
        last = nextindex(last);
    }
    if(last>end) return 0xffff;
    return last;
}

const char * json_io::get_value(word base, byte flags, const char * name, bool * present)
{
    if (present) *present = false;
    word id = get_first(JSON_TYPE_VALUE, base);
    while(id!=0xffff) {
        if (content[id].name && !strcmp(content[id].name, name)) {
            if (present) *present = true;
            if ((content[id].flags & JSON_FLAG_QUOTED) == flags)  return content[id].info;
        }
        id = get_next(JSON_TYPE_VALUE, base, id);
    }
    return 0;
}

const char * json_io::get_value(word base, byte flags, word & last, bool * present)
{
    if (present) *present = false;
    if(last==0xffff) return 0;
    word id = (last<=base) ? get_first(JSON_TYPE_ANY, base): get_next(JSON_TYPE_ANY, base, last);
    last = base==0xffff?0xffff:id;
    if(id!=0xffff) {
        if (content[id].type == JSON_TYPE_VALUE) {
            if (present) *present = true;
            if ((content[id].flags  &JSON_FLAG_QUOTED) == flags) return content[id].info;
        }
    }
    return 0;
}

word json_io::get_id(word base, byte flags, const char * name)
{
    word id = get_first(JSON_TYPE_VALUE, base);
    while(id!=0xffff) {
        if ((content[id].flags & JSON_FLAG_QUOTED) == flags && content[id].name && !strcmp(content[id].name, name)) return id;
        id = get_next(JSON_TYPE_VALUE, base, id);
    }
    return 0xffff;
}

word json_io::get_object(word base, const char * name)
{
    word id = get_first(JSON_TYPE_OBJECT, base);
    while(id!=0xffff) {
        if((!name && !content[id].name) || (name && content[id].name && !strcmp(content[id].name,name))) return id;
        id = get_next(JSON_TYPE_OBJECT, base, id);
    }
    return 0xffff;
}

word json_io::get_object(word base, word & last)
{
    if(last==0xffff) return 0;
    word id = (last<=base) ? get_first(JSON_TYPE_ANY, base): get_next(JSON_TYPE_ANY, base, last);
    last = base==0xffff?0xffff:id;
    if(id!=0xffff) {
        if(content[id].type==JSON_TYPE_OBJECT) return id;
    }
    return 0xffff;
}

word json_io::get_array(word base, const char * name)
{
    word id = get_first(JSON_TYPE_ARRAY, base);
    while(id!=0xffff) {
        if((!name && !content[id].name) || (name && content[id].name && !strcmp(content[id].name,name))) return id;
        id = get_next(JSON_TYPE_ARRAY, base, id);
    }
    return 0xffff;
}

word json_io::get_array(word base, word & last)
{
    if(last==0xffff) return 0;
    word id = (last<=base) ? get_first(JSON_TYPE_ANY, base): get_next(JSON_TYPE_ANY, base, last);
    last = base==0xffff?0xffff:id;
    if(id!=0xffff) {
        if(content[id].type==JSON_TYPE_ARRAY) return id;
    }
    return 0xffff;
}

word json_io::get_next(word base, word last, byte & type, byte & flags, const char * & name, const char * & info)
{
    if(last==0xffff) return 0;
    word id = (last<=base) ? get_first(JSON_TYPE_ANY, base): get_next(JSON_TYPE_ANY, base, last);
    last = base==0xffff?0xffff:id;
    if(id!=0xffff) {
        type = content[id].type;
        flags = content[id].flags;
        name = content[id].name;
        info = content[id].info;
    }
    return id;
}

const char * json_io::get_string(word base, const char * name, bool * present)
{
    return get_value(base, JSON_FLAG_QUOTED, name, present);
}

const char * json_io::get_string(word base, word & last, bool * present)
{
    return get_value(base, JSON_FLAG_QUOTED, last, present);
}

int json_io::get_int(word base, const char * name, bool * present)
{
    const char * a = get_value(base, 0, name);
    if(present) *present=true;
    if(a) return strtol(a,0,0);
    if(present) *present=false;
    return 0;
}

int json_io::get_int(word base, word & last, bool * present)
{
    const char * a = get_value(base, 0, last);
    if(present) *present=true;
    if(a) return strtol(a,0,0);
    if(present) *present=false;
    return 0;
}

dword json_io::get_unsigned(word base, const char * name, bool * present)
{
    const char * a = get_value(base, 0, name);
    if(present) *present=true;
    if(a) return strtoul(a,0,0);
    if(present) *present=false;
    return 0;
}

dword json_io::get_unsigned(word base, word & last, bool * present)
{
    const char * a = get_value(base, 0, last);
    if(present) *present=true;
    if(a) return strtoul(a,0,0);
    if(present) *present=false;
    return 0;
}

long64 json_io::get_long64(word base, const char * name, bool * present)
{
    const char * a = get_value(base, 0, name);
    if (present) *present = true;
    if (a) return strtoll(a, 0, 0);
    if (present) *present = false;
    return 0;
}

long64 json_io::get_long64(word base, word & last, bool * present)
{
    const char * a = get_value(base, 0, last);
    if (present) *present = true;
    if (a) return strtoll(a, 0, 0);
    if (present) *present = false;
    return 0;
}

ulong64 json_io::get_ulong64(word base, const char * name, bool * present )
{
    const char * a = get_value(base, 0, name);
    if (present) *present = true;
    if (a) return strtoull(a, 0, 0);
    if (present) *present = false;
    return 0;
}

ulong64 json_io::get_ulong64(word base, word & last, bool * present)
{
    const char * a = get_value(base, 0, last);
    if (present) *present = true;
    if (a) return strtoull(a, 0, 0);
    if (present) *present = false;
    return 0;
}

#if !defined(PLATFORM_NO_IPADDR)
IPaddr json_io::get_ip(word base, const char * name, bool * present)
{
    const char * a = get_value(base, JSON_FLAG_QUOTED, name);
    if(present) *present=true;
    if(a) return str::to_ip(a,0);
    if(present) *present=false;
    return ip_anyaddr;
}

IPaddr json_io::get_ip(word base, word & last, bool * present)
{
    const char * a = get_value(base, JSON_FLAG_QUOTED, last);
    if(present) *present=true;
    if(a) return str::to_ip(a,0);
    if(present) *present=false;
    return ip_anyaddr;
}

OS_GUID json_io::get_guid(word base, const char * name, bool * present)
{
    OS_GUID guid;
    if (const char * s = get_string(base, name)) {
        if (present) *present = true;
        str::to_hexmem(s, guid.b, sizeof(guid.b), true);
    }
    else {
        if (present) *present = false;
        memset(guid.b, 0, sizeof(guid.b));
    }
    return guid;
}
#endif

bool json_io::get_bool(word base, const char * name, bool * present)
{
    const char * a = get_value(base, 0, name, present);
    if(a) {
        if(!strcmp(a,"true")) return true;
        if(!strcmp(a,"false")) return false;
    }
    return false;
}

bool json_io::get_bool(word base, word & last, bool * present)
{
    const char * a = get_value(base, 0, last);
    if(present) *present=true;
    if(a) {
        if(!strcmp(a,"true")) return true;
        if(!strcmp(a,"false")) return false;
    }
    if(present) *present=false;
    return false;
}

bool json_io::get_bool_int(word base, const char * name, int & iret, byte * present)
{
    const char * a = get_value(base, 0, name);
    iret = 0;
    if(present) *present=0;
    if(a) {
        if(present) *present=1;
        if (!strcmp(a, "true")) {
            iret = 1;
            return true;
        }
        if(!strcmp(a,"false")) return false;
        if(present) *present=(!strcmp(a, "null") ? 2 : 3);
        iret = strtol(a,0,0);
        return iret != 0;
    }
    return false;
}

#if defined(PLATFORM_FLOAT_SUPPORT)
double json_io::get_double(word base, const char * name, bool * present)
{
    const char * a = get_value(base, 0, name);
    if (present) *present = true;
    if (a) return strtod(a, 0);
    if (present) *present = false;
    return 0;
}

double json_io::get_double(word base, word & last, bool * present)
{
    const char * a = get_value(base, 0, last);
    if (present) *present = true;
    if (a) return strtod(a, 0);
    if (present) *present = false;
    return 0;
}
#endif

/*---------------------------------------------------------------------------*/
/* decode functions                                                          */
/*---------------------------------------------------------------------------*/

bool json_io::decode()
{
    if(!buffer) return false;
    if(!read(buffer)) return false;
    return true;
}

bool json_io::read(char * & p)
{
    index = 0;
    content[0].name = 0;
    if(!read(p, 0xffff)) return false;
    if(skip_whitespace(p)) return false;
    return true;
}

bool json_io::read(char * & p, word base, const char * name)
{
    skip_whitespace(p);

    level_last[level] = index;
    if(*p=='{') {
        ++p;
        bool first = true;
        word root = index;
        if (index == max_item) return false;
        content[index].base = base;
        content[index].type = JSON_TYPE_OBJECT;
        content[index].flags = JSON_FLAG_INCOMPLETE;
        content[index].name = name;
        content[index].info = 0;
        content[index].count = 0;
        ++index;
        incomplete = p;
        if (last) *last = 0;
        if (name_last) * name_last = 0;
        word last_index = JSON_ID_NONE;

        do {
            word current_index = index;
            if(skip(p,'}')) {
                content[root].count = index-root-1;
                content[root].flags |= JSON_FLAG_COMPLETE;
                incomplete = p;
                if (last) *last = 0;
                if (name_last) * name_last = 0;
                return true;
            }
            if(!first) {
                if (!skip(p, ',')) {
                    content[root].count = index-root-1;
                    return false;
                }
            }
            if (!skip(p, '"')) {
                content[root].count = index-root-1;
                return false;
            }

            const char * _name = p;
            while(*p && *p!='"') ++p;
            if(!*p) return false;
            name_last = p;
            ++p;

            if (!skip(p, ':')) {
                content[root].count = index-root-1;
                return false;
            }

            level++;
            bool read_ok = read(p, root, _name);
            if (!read_ok && index == max_item) {
                content[root].count = index-root-1;
                return read_ok;
            }
            level--;
            if (read_ok) {
                if (content[level_last[level + 1]].type != JSON_TYPE_VALUE) content[level_last[level + 1]].flags &= ~JSON_FLAG_INCOMPLETE;
            }
            else {
                content[root].count = index-root-1;
                return false;
            }
            if (last_index != JSON_ID_NONE) {
                content[last_index].flags &= ~JSON_FLAG_INCOMPLETE;
                content[last_index].flags |= JSON_FLAG_COMPLETE;
            }
            last_index = current_index;
            level_last[level] = current_index;
            content[root].count = index-root-1;
            first = false;
        }
        while(*p);
        return false;
    }
    else if(*p=='[') {
        ++p;
        bool first = true;
        word root = index;
        if (index == max_item) return false;
        content[index].base = base;
        content[index].type = JSON_TYPE_ARRAY;
        content[index].flags = JSON_FLAG_INCOMPLETE;
        content[index].name = name;
        content[index].info = 0;
        content[index].count = 0;
        ++index;
        incomplete = p;
        if (last) *last = 0;
        if (name_last) * name_last = 0;

        word last_index = JSON_ID_NONE;
        do {
            word current_index = index;
            if(skip(p,']')) {
                content[root].count = index-root-1;
                content[root].flags |= JSON_FLAG_COMPLETE;
                incomplete = p;
                return true;
            }
            if(!first) {
                if (!skip(p, ',')) {
                    content[root].count = index-root-1;
                    return false;
                }
            }

            level++;
            bool read_ok = read(p, root, 0);
            if (!read_ok && index == max_item) {
                content[root].count = index - root - 1;
                return read_ok;
            }
            level--;
            if (read_ok) {
                if (content[level_last[level + 1]].type != JSON_TYPE_VALUE) content[level_last[level + 1]].flags &= ~JSON_FLAG_INCOMPLETE;
            }
            else {
                content[root].count = index-root-1;
                return false;
            }
            if (last_index != JSON_ID_NONE) {
                content[last_index].flags &= ~JSON_FLAG_INCOMPLETE;
                content[last_index].flags |= JSON_FLAG_COMPLETE;
            }
            last_index = current_index;
            level_last[level] = current_index;
            content[root].count = index-root-1;
            first = false;
        }
        while(*p);

        return false;
    }
    else if(*p=='"') {
        ++p;
        char * s = p;
        while (*p && *p != '"') {
            if (*p == '\\') ++p;
            if (*p) ++p;
        }
        if (*p) {
            if (index == max_item) return false;
            content[index].base = base;
            content[index].type = JSON_TYPE_VALUE;
            content[index].flags = JSON_FLAG_QUOTED | JSON_FLAG_INCOMPLETE;
            content[index].name = name;
            content[index].info = s;
            char save_p = *p;
            *p = 0;
            content[index].count = unescape(s, s);
            *p = save_p;
            if (last) *last = 0;
            if (name_last) *name_last = 0;
            last = &s[content[index].count];
            ++index;
            ++p;
            incomplete = p;
            return true;
        }
    }
    else if(unquoted(*p)) {
        char * s = p;
        while(*p && unquoted(*p)) ++p;
        if (*p || (index == 0)) {
            if (index == max_item) return false;
            content[index].base = base;
            content[index].type = JSON_TYPE_VALUE;
            content[index].flags = JSON_FLAG_INCOMPLETE;
            content[index].name = name;
            content[index].info = s;
            content[index].count = (word)(p - content[index].info);
            ++index;
            if (last) *last = 0;
            if (name_last) *name_last = 0;
            last = p;
            incomplete = p;
            return true;
        }
    }
    else if(base==0xffff && !*p) {
        return true;
    }

    return false;
}

/*---------------------------------------------------------------------------*/
/* encode functions                                                          */
/*---------------------------------------------------------------------------*/

dword json_io::encode()
{
    char * p = buffer;
    if(index) write(0,p);
    return (dword)(p-buffer);
}

#if !defined(PLATFORM_NO_PACKET)
class packet * json_io::encode_to_packet(class packet * data)
{
    word count = 0;
    if(!data) data = new packet();
    if(!index) return data;
    write_to_packet(0,count,data);
    return data;
}
#endif

dword json_io::size(word current, word incomplete)
{
    if (!index) return 0;
    dword result = 0;

    switch (content[current].type) {
    case JSON_TYPE_ARRAY:
    {
        result += 1;
        bool first = true;
        for (word i = 0; i < content[current].count; ) {
            word c = current + i + 1;
            if ((incomplete == 0xffff) ||
                (incomplete == current && (!(content[c].flags & JSON_FLAG_COMPLETE) || (content[c].flags & JSON_FLAG_INCOMPLETE))) ||
                (!(content[c].flags & JSON_FLAG_COMPLETE) && content[c].flags & JSON_FLAG_INCOMPLETE)) {

                if (content[c].type != JSON_TYPE_VALUE || content[c].info) {
                    if (!first) result += 1;
                    else first = false;
                    result += size(c, incomplete == current ? 0xffff : incomplete);
                }
            }
            if (content[c].type == JSON_TYPE_OBJECT || content[c].type == JSON_TYPE_ARRAY) i += content[c].count;
            i++;
        }
        if ((content[current].flags & (JSON_FLAG_COMPLETE))) result += 1;
    }
    break;
    case JSON_TYPE_OBJECT:
    {
        result += 1;
        bool first = true;
        for (word i = 0; i < content[current].count; ) {
            word c = current + i + 1;
            if ((incomplete == 0xffff) ||
                (incomplete == current && (!(content[c].flags & JSON_FLAG_COMPLETE) || (content[c].flags & JSON_FLAG_INCOMPLETE))) ||
                (!(content[c].flags & JSON_FLAG_COMPLETE) && content[c].flags & JSON_FLAG_INCOMPLETE)) {

                if (content[c].type != JSON_TYPE_VALUE || content[c].info) {
                    if (!first) result += 1;
                    else first = false;
                    result += (dword)strlen(content[c].name) + 3;
                    result += size(c, incomplete == current ? 0xffff : incomplete);
                }
            }
            if (content[c].type == JSON_TYPE_OBJECT || content[c].type == JSON_TYPE_ARRAY) i += content[c].count;
            i++;
        }
        if ((content[current].flags & (JSON_FLAG_COMPLETE))) result += 1;
    }
    break;
    case JSON_TYPE_VALUE:
    {
        if ((content[current].flags & JSON_FLAG_QUOTED) != 0) {
            result += 2 + escape_size(content[current].info, content[current].count);
        }
        else {
            result += content[current].count;
        }
    }
    break;
#if !defined(PLATFORM_NO_PACKET)
    case JSON_TYPE_CONTENT_PACKET:
    {
        result += content[current].count;
    }
    break;
#endif
    default:
        break;
    }
    if (current == 0 && incomplete != 0xffff && this->incomplete) result += (dword)strlen(this->incomplete);

    return result;
}

void json_io::write(word current, char * & p, word incomplete)
{
    switch(content[current].type) {
        case JSON_TYPE_ARRAY:
            {
                p += isprintf(p,"[");
                bool first = true;
                for(word i=0; i<content[current].count; ) {
                    word c = current + i + 1;
                    if ((incomplete == 0xffff) ||
                        (incomplete == current && (!(content[c].flags & JSON_FLAG_COMPLETE) || (content[c].flags & JSON_FLAG_INCOMPLETE))) ||
                        (!(content[c].flags & JSON_FLAG_COMPLETE) && content[c].flags & JSON_FLAG_INCOMPLETE)) {

                        if (content[c].type != JSON_TYPE_VALUE || content[c].info) {
                            if (!first) p += isprintf(p, ",");
                            else first = false;
                            write(c, p, incomplete == current ? 0xffff : incomplete);
                        }
                    }
                    if (content[c].type == JSON_TYPE_OBJECT || content[c].type == JSON_TYPE_ARRAY) i += content[c].count;
                    i++;
                }
                if ((content[current].flags & (JSON_FLAG_COMPLETE))) p += isprintf(p, "]");
            }
            break;
        case JSON_TYPE_OBJECT:
            {
                p += isprintf(p,"{");
                bool first = true;
                for(word i=0; i<content[current].count; ) {
                    word c = current + i + 1;
                    if ((incomplete == 0xffff) ||
                        (incomplete == current && (!(content[c].flags & JSON_FLAG_COMPLETE) || (content[c].flags & JSON_FLAG_INCOMPLETE))) ||
                        (!(content[c].flags & JSON_FLAG_COMPLETE) && content[c].flags & JSON_FLAG_INCOMPLETE)) {

                        if (content[c].type != JSON_TYPE_VALUE || content[c].info) {
                            if (!first) p += isprintf(p, ",");
                            else first = false;
                            p += isprintf(p, "\"%s\":", content[c].name);
                            write(c, p, incomplete == current ? 0xffff : incomplete);
                        }
                    }
                    if (content[c].type == JSON_TYPE_OBJECT || content[c].type == JSON_TYPE_ARRAY) i += content[c].count;
                    i++;
                }
                if ((content[current].flags & (JSON_FLAG_COMPLETE))) p += isprintf(p,"}");
            }
            break;
        case JSON_TYPE_VALUE:
            {
                if((content[current].flags&JSON_FLAG_QUOTED)!=0) {
                    p += isprintf(p,"\"");
                    p += escape(content[current].info,content[current].count,p);
                    p += isprintf(p,"\"");
                }
                else {
                    memcpy(p,content[current].info,content[current].count);
                    p += content[current].count;
                }
            }
            break;
#if !defined(PLATFORM_NO_PACKET)
        case JSON_TYPE_CONTENT_PACKET:
            {
                class packet * data = (class packet *)content[current].info;
                p += data->get_head(p, data->length());
                delete data;
            }
            break;
#endif
        default:
            break;
    }
    if (current == 0 && incomplete != 0xffff && this->incomplete) p += isprintf(p, "%s", this->incomplete);
}

#if !defined(PLATFORM_NO_PACKET)
class packet * json_io::write_to_packet(word current, word & count, class packet * data)
{
    word c;
    count = 0;

    switch(content[current].type) {
        case JSON_TYPE_ARRAY:
            {
                data->put_tail("[",1,JSON_ALLOC_SIZE);
                for(word i=0; i<content[current].count; ) {
                    if(i>0) data->put_tail(",",1,JSON_ALLOC_SIZE);
                    data = write_to_packet(current+i+1,c,data);
                    i += c;
                    count += c;
                }
                data->put_tail("]",1,JSON_ALLOC_SIZE);
                count++;
            }
            break;
        case JSON_TYPE_OBJECT:
            {
                data->put_tail("{",1,JSON_ALLOC_SIZE);
                for(word i=0; i<content[current].count; ) {
                    if (content[current + i + 1].type != JSON_TYPE_VALUE || content[current + i + 1].info) {
                        if(i>0) data->put_tail(",",1,JSON_ALLOC_SIZE);
                        data->put_tail("\"",1,JSON_ALLOC_SIZE);
                        data->put_tail(content[current+i+1].name,strlen(content[current+i+1].name),JSON_ALLOC_SIZE);
                        data->put_tail("\":",2,JSON_ALLOC_SIZE);
                        data = write_to_packet(current+i+1,c,data);
                        i += c;
                        count += c;
                    }
                    else {
                        i++;
                    }
                }
                data->put_tail("}",1,JSON_ALLOC_SIZE);
                count++;
            }
            break;
        case JSON_TYPE_VALUE:
            {
                if((content[current].flags&JSON_FLAG_QUOTED)!=0) {
                    char tmp[BUFMAN_MAX];
                    int l = escape(content[current].info,content[current].count,tmp);
                    data->put_tail("\"",1,JSON_ALLOC_SIZE);
                    data->put_tail(tmp,l,JSON_ALLOC_SIZE);
                    data->put_tail("\"",1,JSON_ALLOC_SIZE);
                }
                else {
                    data->put_tail(content[current].info,content[current].count,JSON_ALLOC_SIZE);
                }
                count++;
            }
            break;
        case JSON_TYPE_CONTENT_PACKET:
            {
                data->join((class packet *)content[current].info);
                count++;
            }
            break;
        default:
            {
                count++;
            }
            break;
    }

    return data;
}
#endif

/*---------------------------------------------------------------------------*/
/* static conversion functions                                               */
/*---------------------------------------------------------------------------*/

dword json_io::escape(const char * in, dword length, char * out)
{
    dword count = 0;
    const byte * p = (byte *)in;

    while (length && *p) {
        switch (*p) {
            case '"':
                out[count++] = '\\';
                out[count++] = '"';
                break;
            case '\\':
                out[count++] = '\\';
                out[count++] = '\\';
                break;
            case '\b':
                out[count++] = '\\';
                out[count++] = 'b';
                break;
            case '\f':
                out[count++] = '\\';
                out[count++] = 'f';
                break;
            case '\n':
                out[count++] = '\\';
                out[count++] = 'n';
                break;
            case '\r':
                out[count++] = '\\';
                out[count++] = 'r';
                break;
            case '\t':
                out[count++] = '\\';
                out[count++] = 't';
                break;
            default:
                if(*p<=0x1f) {
                    isprintf(&out[count], "\\u00%02x", *p);
                    count += 6;
                }
                else {
                    out[count++] = *p;
                }
                break;
        }
        --length;
        ++p;
    }

    out[count] = 0;
    return count;
}

dword json_io::escape_size(const char * in, dword length)
{
    dword count = 0;
    const byte * p = (byte *)in;

    while (length && *p) {
        switch (*p) {
        case '"':
        case '\\':
        case '\b':
        case '\f':
        case '\n':
        case '\r':
        case '\t':
            count += 2;
            break;
        default:
            if (*p <= 0x1f) {
                count += 6;
            }
            else {
                count += 1;
            }
            break;
        }
        --length;
        ++p;
    }

    return count;
}

dword json_io::unescape(char * out, const char * in)
{
    dword length = 0;
    word w[3] = { 0, 0, 0 };
    const byte * p = (byte *)in;

    while(*p) {
        if(*p=='\\') {
            ++p;
            switch(*p) {
                case '"':
                case '\\':
                case '/':
                    out[length++] = *p++;
                    break;
                case 'b':
                    out[length++] = '\b';
                    ++p;
                    break;
                case 'f':
                    out[length++] = '\f';
                    ++p;
                    break;
                case 'n':
                    out[length++] = '\n';
                    ++p;
                    break;
                case 'r':
                    out[length++] = '\r';
                    ++p;
                    break;
                case 't':
                    out[length++] = '\t';
                    ++p;
                    break;
                case 'u':
                    char tmp[5];
                    for (int i = 0; i < 4; i++) {
                        tmp[i] = *(++p);
                        if (!tmp[i]) {
                            // error
                            out[length] = 0;
                            return length;
                        }
                    }
                    tmp[4] = 0;
                    { 
                        word x = (word)strtoul(tmp, 0, 16);
                        if (x >= 0xD800 && x <= 0xDB7F) {
                            if (!w[0] && !w[1]) {
                                // high surrogate
                                w[0] = x;
                            }
                            else {
                                // error
                                out[length] = 0;
                                return length;
                            }
                        }
                        else if (x >= 0xDC00 && x <= 0xDFFF) {
                            if (w[0] && !w[1]) {
                                // low surrogate
                                w[1] = x;
                                length += str::from_utf16(w, &out[length], 4);
                                w[0] = 0;
                                w[1] = 0;
                            }
                            else {
                                // error
                                out[length] = 0;
                                return length;
                            }
                        }
                        else {
                            if (!w[0] && !w[1]) {
                                // multi-lingual plane
                                length += str::from_ucs2_n(&x, 1, &out[length], 4);
                            }
                            else {
                                // error
                                out[length] = 0;
                                return length;
                            }
                        }
                        ++p;
                    }
                    break;
                default:
                    // error
                    out[length] = 0;
                    return length;
                    break;
            }
        }
        else {
            out[length++] = *p++;
        }
    }

    out[length] = 0;
    return length;
}

bool json_io::whitespace(char c)
{
    return c==' ' || c=='\r' || c=='\n' || c=='\t';
}

bool json_io::unquoted(char c)
{
    return c=='-' || c=='.' || (c>='0'&&c<='9') || (c>='a'&&c<='z') || (c>='A'&&c<='Z');  
}

char json_io::skip_whitespace(char * & p)
{
    while(*p && whitespace(*p)) p++;
    return *p;
}

bool json_io::skip(char * & p, char skip)
{
    if(skip_whitespace(p)!=skip) return false;
    ++p;
    return true;
}

word json_io::to_url(word base, char * b, word l, const char * prefix, bool cont)
{
    word o = 0;
    for (word handle = get_first(JSON_TYPE_ANY, base); handle != 0xffff; handle = get_next(JSON_TYPE_ANY, base, handle)) {
        if (get_type(handle) == JSON_TYPE_OBJECT || get_type(handle) == JSON_TYPE_ARRAY) {
            const char * name = get_name(handle);
            if (name) {
                if (prefix) {
                    char p[100];
                    word pl = sizeof(p);
                    word po = str::to_strx(prefix, p, pl);
                    po += str::to_strx(".", &p[po], pl - po);
                    po += str::to_strx(name, &p[po], pl - po);
                    o += to_url(handle, &b[o], l-o, p, cont);
                }
                else {
                    o += to_url(handle, &b[o], l-o, name, cont);
                }
            }
            else {
                o += to_url(handle, &b[o], l-o, prefix, cont);
            }
        }
        else {
            if(cont) o += str::to_strx("&", &b[o], l - o);
            else cont = true;
            o += str::to_strx(prefix, &b[o], l - o);
            if (prefix) o += str::to_strx(".", &b[o], l - o);
            o += str::to_strx(get_name(handle), &b[o], l - o);
            o += str::to_strx("=", &b[o], l - o);
            o += str::to_url(get_info(handle), &b[o], l - o);
        }
    }
    return o;
}

void json_io::trace(class debug_printf * dbg, word base, word level)
{
    static const char * space = "                                                          ";
    const char * name = get_name(base);
    level++;
    if (get_type(base) == JSON_TYPE_OBJECT) {
        dbg->iprintf("\n%.*s%s%s{", level * 4, space, name ? name : "", name ? ": " : "");
        for (word handle = get_first(JSON_TYPE_ANY, base); handle != 0xffff; ) {
            trace(dbg, handle, level);
            handle = get_next(JSON_TYPE_ANY, base, handle);
            if (handle != 0xffff) dbg->iprintf(",");
        }
        dbg->iprintf("\n%.*s}", level * 4, space);
    }
    else if (get_type(base) == JSON_TYPE_ARRAY) {
        dbg->iprintf("\n%.*s%s%s [", level * 4, space, name ? name : "", name ? ":" : "");
        for (word handle = get_first(JSON_TYPE_ANY, base); handle != 0xffff; ) {
            trace(dbg, handle, level);
            handle = get_next(JSON_TYPE_ANY, base, handle);
            if (handle != 0xffff) dbg->iprintf(",");
        }
        dbg->iprintf("\n%.*s]", level * 4, space);
    }
    else {
        const char * quote = get_flags(base) & JSON_FLAG_QUOTED ? "\"" : "";
        dbg->iprintf("\n%.*s%s%s%s%.*s%s", level * 4, space, name ? name : "", name ? ": " : "", quote, get_count(base), get_info(base), quote);
    }
}
