/*
 * © 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 <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "json.h"

static int indent = 1;

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:
                if (indent)
                    for (i = 0; i < depth; i++)
                        fprintf(out, "   ");
                fprintf(out, "{");
                if (indent)
                    fprintf(out, "\n");
                p = v->object;
                while (p) {
                    if (indent)
                        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, ", ");
                        if (indent)
                            fprintf(out, "\n");
                    }
                    p = p->next;
                }
                if (indent)
                    fprintf(out, "\n");
                if (indent)
                    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*/) {

                    if (indent)
                        fprintf(out, "\n");
                    while (p) {
                        if (indent)
                            for (i = 0; i < depth + 1; i++)
                                fprintf(out, "   ");
                        _json_print(p, out, depth + 2);
                        if (p->next) {
                            fprintf(out, ", ");
                        }
                        if (indent)
                            fprintf(out, "\n");
                        p = p->next;
                    }
                    /* fprintf(out,"\n"); */
                    if (indent)
                        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_dump(const json_value* v) {
    indent = 0;
    _json_print(v, stdout, 0);
    indent = 1;
}

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;
}
