/*
 * © Copyright 1996-2012 ECMWF.
 *
 * This software is licensed under the terms of the Apache Licence Version 2.0
 * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0.
 * In applying this licence, ECMWF does not waive the privileges and immunities
 * granted to it by virtue of its status as an intergovernmental organisation nor
 * does it submit to any jurisdiction.
 */
#include <stdlib.h>
#include <stdio.h>
#include <ctype.h>
#include <string.h>
#include "json.h"

enum json_type {
    json_type_null, json_type_true, json_type_false, json_type_integer, json_type_real,
    json_type_string,  json_type_object, json_type_array
} ;

struct _json_value {
    enum json_type type;

    double real;
    long long integer;

    struct _json_value* object;
    struct _json_value* array;
    struct _json_value* next;
    struct _json_value* value;

    char *string;

};


typedef size_t (*fillproc)(char*, size_t, void*);

typedef struct json_parser {
    int error;
    char buffer[10240];
    int pos;
    int len;
    void* data;
    fillproc fill;

} json_parser;



void json_free(json_value* v) {
    if(v) {
        if(v->string) free(v->string);
        if(v->object) json_free(v->object);
        if(v->array) json_free(v->array);
        if(v->next) json_free(v->next);
        if(v->value) json_free(v->value);
        free(v);
    }
}

const char* json_get_string(const json_value* v)
{
    if(v != NULL && v->type == json_type_string)
        return v->string;
    return "";
}

long long json_get_integer(json_value* v)
{
    if(v != NULL && v->type == json_type_integer)
        return v->integer;
    return 0;
}


static void _json_print_string(const char *s, FILE* out)
{
    fputc('"',out);
    while(*s)
    {
        switch(*s)
        {
        case '"': fprintf(out,"\\\""); break;
        case '\n': fprintf(out,"\\n"); break;
        case '\b': fprintf(out,"\\b"); break;
        case '\f': fprintf(out,"\\f"); break;
        case '\r': fprintf(out,"\\r"); break;
        case '\t': fprintf(out,"\\t"); break;
        case '\\': fprintf(out,"\\\\"); break;
        default:
            fputc(*s,out);
            break;
        }
        s++;
    }

    fputc('\"', out);
}

const char* json_encode_string(const char *s, char* out)
{
    char *p = out;
    *out++ = '"';
    while(*s)
    {
        switch(*s)
        {
        case '"':  *out++ = '\\'; *out++ = '"'; break;
        case '\n': *out++ = '\\'; *out++ = 'n'; break;
        case '\b': *out++ = '\\'; *out++ = 'b'; break;
        case '\f': *out++ = '\\'; *out++ = 'f'; break;
        case '\r': *out++ = '\\'; *out++ = 'r'; break;
        case '\t': *out++ = '\\'; *out++ = 't'; break;
        case '\\': *out++ = '\\'; *out++ = '\\'; break;
        default:
            *out++ = *s;
            break;
        }
        s++;
    }

    *out++ = '"';
    *out++ = 0;
    return p;

}

static void _json_print(const json_value* v, FILE* out, int depth) {
    int i;
    json_value *p;
    if(v)
        switch(v->type) {
        case json_type_null:
            fprintf(out,"null");
            break;
        case json_type_true:
            fprintf(out,"true");
            break;
        case json_type_false:
            fprintf(out,"false");
            break;
        case json_type_integer:
            fprintf(out,"%lld",v->integer);
            break;
        case json_type_real:
            fprintf(out,"%g",v->real);
            break;
        case json_type_string:
            _json_print_string(v->string, out);
            break;
        case json_type_object:
            for(i = 0; i < depth; i++) fprintf(out,"   ");
            fprintf(out,"{");
            fprintf(out,"\n");
            p = v->object;
            while(p) {
                for(i = 0; i < depth+1; i++) fprintf(out,"   ");
                _json_print(p, out, depth);
                fprintf(out,": ");
                _json_print(p->value, out, depth+1);
                if(p->next)
                {
                    fprintf(out,", ");
                    fprintf(out,"\n");
                }
                p = p->next;
            }
            fprintf(out,"\n");
            for(i = 0; i < depth-1; i++) fprintf(out,"   ");
            fprintf(out,"}");
            fprintf(out,"\n");
            break;

        case  json_type_array:
            /* for(i = 0; i < depth; i++) fprintf(out,"   "); */
            fprintf(out,"[");
            p = v->array;
            if(p /*&& p->next*/)
            {

                fprintf(out,"\n");
                while(p) {
                    for(i = 0; i < depth+1; i++) fprintf(out,"   ");
                    _json_print(p, out, depth+2);
                    if(p->next)
                    {
                        fprintf(out,", ");
                    }
                    fprintf(out,"\n");
                    p = p->next;
                }
                /* fprintf(out,"\n"); */
                for(i = 0; i < depth+1; i++) fprintf(out,"   ");
            }
            fprintf(out,"]");
            /* fprintf(out,"\n"); */
            break;
        }
}

void json_save(const json_value* v, FILE *f)
{
    _json_print(v,f,0);
}


void json_print(const json_value* v)
{
    _json_print(v,stdout,0);
}

void json_println(const json_value* v)
{
    _json_print(v,stdout,0);
    printf("\n");
}

static char next(json_parser* p)
{
    char c;


    if(p->pos == p->len) {
        p->pos = 0;
        p->len = p->fill(p->buffer, sizeof(p->buffer), p->data);
    }

    if(p->pos == p->len) {
        printf("next: eof reached\n");
        p->error++;
        return 0;
    }
    c = p->buffer[p->pos];
    while(isspace(c))
    {
        p->pos++;

        if(p->pos == p->len) {
            p->pos = 0;
            p->len = p->fill(p->buffer, sizeof(p->buffer), p->data);
        }

        if(p->pos == p->len) {
            printf("next: eof reached\n");
            p->error++;
            return 0;
        }
        c = p->buffer[p->pos];
    }
    p->pos++;
    return c;
}

static char next_space(json_parser* p)
{
    char c;
    if(p->pos == p->len) {
        p->pos = 0;
        p->len = p->fill(p->buffer, sizeof(p->buffer), p->data);
    }
    if(p->pos == p->len) {
        printf("next_space: eof reached\n");
        p->error++;
        return 0;
    }
    c = p->buffer[p->pos];
    p->pos++;
    return c;
}

static char peek(json_parser* p)
{
    char c;
    if(p->pos == p->len) {
        p->pos = 0;
        p->len = p->fill(p->buffer, sizeof(p->buffer), p->data);
    }
    if(p->pos == p->len) return 0;
    c = p->buffer[p->pos];
    while(isspace(c))
    {
        p->pos++;
        if(p->pos == p->len) {
            p->pos = 0;
            p->len = p->fill(p->buffer, sizeof(p->buffer), p->data);
        }
        if(p->pos == p->len) return 0;
        c = p->buffer[p->pos];
    }
    return c;
}

static int consume_char(json_parser* p, char s)
{
    char c = next(p);
    if(c != s)
    {
        printf("consume_char: expect %c, got %c\n", s, c);
        p->error = 1;
        return 0;
    }
    return 1;
}

static int consume_str(json_parser* p, const char* s)
{
    while(*s) {
        if(!consume_char(p,*s)) {
            return 0;
        }
        s++;
    }
    return 1;
}

static json_value* parse_value(json_parser* p);

static json_value* parse_true(json_parser* p)
{
    if(consume_str(p,"true")) {
        json_value* j = calloc(1,sizeof(json_value));
        j->type = json_type_true;
        return j;
    }
    return NULL;
}

static json_value* parse_false(json_parser* p)
{
    if(consume_str(p,"false")) {
        json_value* j = calloc(1,sizeof(json_value));
        j->type = json_type_false;
        return j;
    }
    return NULL;
}

static json_value* parse_null(json_parser* p)
{
    if(consume_str(p,"null")) {
        json_value* j = calloc(1,sizeof(json_value));
        j->type = json_type_null;
        return j;
    }
    return NULL;
}


static json_value* parse_number(json_parser* p)
{
    json_value* j;
    int real = 0;
    char s[1024];
    int i = 0;
    char c = next(p);
    if(c == '-') {
        s[i++] = c;
        c = next(p);
    }

    switch(c) {
    case '0': s[i++] = c; break;
    case '1':
    case '2':
    case '3':
    case '4':
    case '5':
    case '6':
    case '7':
    case '8':
    case '9':
        s[i++] = c;
        while(isdigit(peek(p))) {
            s[i++] = next(p);
        }
        break;
    default:
        printf("parse_number: invalid char %c\n", c);
        p->error++;
        return NULL;
        break;
    }

    if(peek(p) == '.') {
        real = 0;
        s[i++] = next(p);
        c = next(p);
        if(!isdigit(c))
        {
            printf("parse_number: invalid char %c\n", c);
            p->error++;
            return NULL;
        }
        s[i++] = c;
        while(isdigit(peek(p))) {
            s[i++] = next(p);
        }
    }


    c = peek(p);
    if(c == 'e' || c == 'E') {
        real = 1;
        s[i++] = next(p);

        c = next(p);
        if(c == '-' || c == '+')
        {
            s[i++] = c;
            c = next(p);
        }

        if(!isdigit(c))
        {
            p->error++;
            printf("parse_number: invalid char %c\n", c);
            return NULL;
        }
        s[i++] = c;
        while(isdigit(peek(p))) {
            s[i++] = next(p);
        }

    }

    s[i] = 0;
    j = calloc(1,sizeof(json_value));

    if(real) {
        j->type = json_type_real;
        j->real = atof(s);
    }
    else
    {
        j->type    = json_type_integer;
        j->integer = atoll(s);
    }
    return j;
}

static json_value* parse_string(json_parser* p)
{
    consume_char(p,'"');
    char s[10240];
    int i = 0;
    for(;;)
    {
        char c = next_space(p);
        if(c == '\\')
        {
            c = next_space(p);
            switch(c) {

            case '"':
                s[i++] = '"';
                break;

            case '\\':
                s[i++] = '\\';
                break;

            case '/':
                s[i++] = '/';
                break;

            case 'b':
                s[i++] = '\b';
                break;

            case 'f':
                s[i++] = '\f';
                break;

            case 'n':
                s[i++] = '\n';
                break;

            case 'r':
                s[i++] = '\r';
                break;

            case 't':
                s[i++] = '\t';
                break;

            case 'u':
                p->error++;
                printf("parse_string: \\uXXXX format not supported\n");
                return NULL;
                break;

            case 0:
                p->error++;
                printf("parse_string: missing closing quote\n");
                return NULL;

            default:
                p->error++;
                printf("parse_string: invalid \\ char %c\n",c);
                return NULL;
                break;
            }
        }
        else
        {
            if(c == '"')
            {
                json_value* j = calloc(1,sizeof(json_value));
                j->type = json_type_string;
                s[i] = 0;
                j->string = strdup(s);
                return j;
            }
            if (c ==  0) {
                p->error++;
                printf("parse_string: missing closing quote\n");
                return NULL;
            }

            s[i++] = c;
        }

    }

}

json_value *json_new_string(const char *s)
{
	json_value* j = calloc(1,sizeof(json_value));
	j->type = json_type_string;
	j->string = strdup(s);
	return j;
}

json_value *json_new_array()
{
	json_value* j = calloc(1,sizeof(json_value));
	j->type = json_type_array;
	return j;
}

json_value *json_new_object()
{
	json_value* j = calloc(1,sizeof(json_value));
	j->type = json_type_object;
	return j;
}

void json_object_set_item(json_value *object, const char *key,json_value *value)
{
	json_value* last = 0;
	json_value* first = 0;
	json_value *k = json_new_string(key);
	k->value = value;

	first = object->object;
	while(first)
	{
		last = first;
		first = first->next;
	}

	if(last)
		last->next = k;
	else
		object->object = k;
}

void json_array_push_item(json_value *object, json_value *k)
{
	json_value* last = 0;
	json_value* first = 0;

	first = object->array;
	while(first)
	{
		last = first;
		first = first->next;
	}

	if(last)
		last->next = k;
	else
		object->array = k;
}

static json_value* parse_object(json_parser* p)
{

    json_value* last = 0;
    json_value* j = calloc(1,sizeof(json_value));
    j->type = json_type_object;

    consume_char(p, '{');
    char c = peek(p);
    if(c == '}')
    {
        consume_char(p, c);
        return j;
    }

    for(;;) {

        json_value* k = parse_string(p);

        if(!k) return j;

        consume_char(p, ':');
        k->value = parse_value(p);

        if(j->object == NULL)
            j->object = k;

        if(last)
            last->next = k;

        last = k;

        char c = peek(p);
        if(c == '}')
        {
            consume_char(p, c);
            return j;
        }
        consume_char(p, ',');
    }
}

static json_value* parse_array(json_parser* p)
{
    json_value* last = 0;
    json_value* j = calloc(1,sizeof(json_value));
    j->type = json_type_array;

    consume_char(p, '[');
    char c = peek(p);
    if(c == ']')
    {
        consume_char(p, c);
        return j;
    }

    for(;;) {

        json_value* v = parse_value(p);

        if(j->array == NULL)
            j->array = v;

        if(last)
            last->next = v;
        last = v;

        char c = peek(p);
        if(c == ']')
        {
            consume_char(p, c);
            return j;
        }

        consume_char(p, ',');

    }
}


static json_value* parse_value(json_parser* p)
{
    char c = peek(p);
    switch(c)
    {

    case 't': return parse_true(p); break;
    case 'f': return parse_false(p); break;
    case 'n': return parse_null(p); break;
    case '{': return parse_object(p); break;
    case '[': return parse_array(p); break;
    case '\"': return parse_string(p); break;

    case '-': return parse_number(p); break;
    case '0': return parse_number(p); break;
    case '1': return parse_number(p); break;
    case '2': return parse_number(p); break;
    case '3': return parse_number(p); break;
    case '4': return parse_number(p); break;
    case '5': return parse_number(p); break;
    case '6': return parse_number(p); break;
    case '7': return parse_number(p); break;
    case '8': return parse_number(p); break;
    case '9': return parse_number(p); break;

    default:
        p->error++;
        printf("parse_value unexpected char %c %x\n",c, c);
        return NULL;
        break;
    }
}

struct string_reader_data {
    const char* buf;
    size_t len;
    size_t pos;
};

static size_t string_reader(char* buffer, size_t len, void *data)
{
    struct string_reader_data* p = (struct string_reader_data*) data;
    size_t left = p->len - p->pos;
    if(left < len) len = left;
    memcpy(buffer,p->buf + p->pos, len);
    p->pos += len;
    return len;
}

json_value* json_parse_string(const char* str, size_t len)
{
    char c;
    json_value* v;
    json_parser p= {0,};
    struct string_reader_data r= {0,};

    if(!len) return NULL;

    r.buf = str;
    r.len = len;

    p.fill = &string_reader;
    p.data = &r;

    v = parse_value(&p);
    c = peek(&p);
    if(c != 0)
    {
        printf("json_parse_string: extra char %c",c);
        printf("json_parse_string: [%s]", str);
        p.error++;
    }

    if(p.error)
    {
        json_free(v);
        v = NULL;
    }

    return v;

}

void json_array_each(json_value* a, void (*proc)(int, json_value*, void*), void* data)
{
    if(a != NULL && a->type == json_type_array)
    {
        json_value *e = a->array;
        int i = 0;
        while(e) {
            proc(i++, e, data);
            e = e->next;
        }
    }
}

void json_object_each(json_value* a, void (*proc)(const char*, json_value*, void*), void* data)
{
    if(a != NULL && a->type == json_type_object)
    {
        json_value *e = a->object;
        while(e) {
            proc(e->string, e->value, data);
            e = e->next;
        }
    }
}

json_value* json_object_find(const json_value* a, const char* key)
{
    if(a != NULL && a->type == json_type_object)
    {
        json_value *e = a->object;
        while(e) {
            if(strcmp(e->string, key) == 0)
                return e->value;
            e = e->next;
        }
    }
    return NULL;
}

struct file_reader_data {
    FILE *file;
};

static size_t file_reader(char* buffer, size_t len, void *data)
{
    struct file_reader_data* p = (struct file_reader_data*) data;
    int size = fread(buffer,1,len,p->file);
    return size;
}

json_value* json_read_file(const char* path)
{
    char c;
    json_value* v;
    json_parser p = {0,};
    struct file_reader_data r= {0,};

    r.file = fopen(path, "r");
    if(!r.file) {
        perror(path);
        return NULL;
    }

    p.fill = &file_reader;
    p.data = &r;

    v = parse_value(&p);
    c = peek(&p);
    if(c != 0)
    {
        printf("json_parse_string: extra char %c",c);
        p.error++;
    }

    fclose(r.file);

    if(p.error)
    {
        json_free(v);
        v = NULL;
    }

    return v;

}

