#include "jsmn.h"
#include "../libnetdata.h"
#include "json.h"
#include "libnetdata/libnetdata.h"
#include "../../health/health.h"

#define JSON_TOKENS 1024

int json_tokens = JSON_TOKENS;

/**
 * Json Tokenise
 *
 * Map the string given inside tokens.
 *
 * @param js is the string used to create the tokens
 * @param len is the string length
 * @param count the number of tokens present in the string
 *
 * @return it returns the json parsed in tokens
 */
#ifdef ENABLE_JSONC
json_object *json_tokenise(char *js) {
    if(!js) {
        error("JSON: json string is empty.");
        return NULL;
    }

    json_object *token = json_tokener_parse(js);
    if(!token) {
        error("JSON: Invalid json string.");
        return NULL;
    }

    return token;
}
#else
jsmntok_t *json_tokenise(char *js, size_t len, size_t *count)
{
    int n = json_tokens;
    if(!js || !len) {
        error("JSON: json string is empty.");
        return NULL;
    }

    jsmn_parser parser;
    jsmn_init(&parser);

    jsmntok_t *tokens = mallocz(sizeof(jsmntok_t) * n);
    if(!tokens) return NULL;

    int ret = jsmn_parse(&parser, js, len, tokens, n);
    while (ret == JSMN_ERROR_NOMEM) {
        n *= 2;
        jsmntok_t *new = reallocz(tokens, sizeof(jsmntok_t) * n);
        if(!new) {
            freez(tokens);
            return NULL;
        }
        tokens = new;
        ret = jsmn_parse(&parser, js, len, tokens, n);
    }

    if (ret == JSMN_ERROR_INVAL) {
        error("JSON: Invalid json string.");
        freez(tokens);
        return NULL;
    }
    else if (ret == JSMN_ERROR_PART) {
        error("JSON: Truncated JSON string.");
        freez(tokens);
        return NULL;
    }

    if(count) *count = (size_t)ret;

    if(json_tokens < n) json_tokens = n;
    return tokens;
}
#endif

/**
 * Callback Print
 *
 * Set callback print case necesary and wrinte an information inside a buffer to write in the log.
 *
 * @param e a pointer for a structure that has the complete information about json structure.
 *
 * @return It always return 0
 */
int json_callback_print(JSON_ENTRY *e)
{
    BUFFER *wb=buffer_create(300);

    buffer_sprintf(wb,"%s = ", e->name);
    char txt[50];
    switch(e->type) {
        case JSON_OBJECT:
            e->callback_function = json_callback_print;
            buffer_strcat(wb,"OBJECT");
            break;

        case JSON_ARRAY:
            e->callback_function = json_callback_print;
            sprintf(txt,"ARRAY[%lu]", e->data.items);
            buffer_strcat(wb, txt);
            break;

        case JSON_STRING:
            buffer_strcat(wb, e->data.string);
            break;

        case JSON_NUMBER:
            sprintf(txt,"%Lf", e->data.number);
            buffer_strcat(wb,txt);

            break;

        case JSON_BOOLEAN:
            buffer_strcat(wb, e->data.boolean?"TRUE":"FALSE");
            break;

        case JSON_NULL:
            buffer_strcat(wb,"NULL");
            break;
    }
    info("JSON: %s", buffer_tostring(wb));
    buffer_free(wb);
    return 0;
}

/**
 * JSONC Set String
 *
 * Set the string value of the structure JSON_ENTRY.
 *
 * @param e the output structure
 */
static inline void json_jsonc_set_string(JSON_ENTRY *e,char *key,const char *value) {
    size_t length = strlen(key);
    e->type = JSON_STRING;
    memcpy(e->name,key,length);
    e->name[length] = 0x00;
    e->data.string = (char *) value;
}


#ifdef ENABLE_JSONC
/**
 * JSONC set Boolean
 *
 * Set the boolean value of the structure JSON_ENTRY
 *
 * @param e the output structure
 * @param value the input value
 */
static inline void json_jsonc_set_boolean(JSON_ENTRY *e,int value) {
    e->type = JSON_BOOLEAN;
    e->data.boolean = value;
}

/**
 * Parse Array
 *
 * Parse the array object.
 *
 * @param ptr the pointer for the object that we will parse.
 * @param callback_data additional data to be used together the callback function
 * @param callback_function function used to create a silencer.
 */
static inline void json_jsonc_parse_array(json_object *ptr, void *callback_data,int (*callback_function)(struct json_entry *)) {
    int end = json_object_array_length(ptr);
    JSON_ENTRY e;

    if(end) {
        int i;
        i = 0;

        enum json_type type;
        do {
            json_object *jvalue =  json_object_array_get_idx(ptr, i);
            if(jvalue) {
                e.callback_data = callback_data;
                e.type = JSON_OBJECT;
                callback_function(&e);
                json_object_object_foreach(jvalue, key, val) {
                    type = json_object_get_type(val);
                    if (type == json_type_array) {
                        e.type = JSON_ARRAY;
                        json_jsonc_parse_array(val, callback_data, callback_function);
                    } else if (type == json_type_object) {
                        json_walk(val,callback_data,callback_function);
                    } else if (type == json_type_string) {
                        json_jsonc_set_string(&e,key,json_object_get_string(val));
                        callback_function(&e);
                    } else if (type == json_type_boolean) {
                        json_jsonc_set_boolean(&e,json_object_get_boolean(val));
                        callback_function(&e);
                    }
                }
            }

        } while (++i < end);
    }
}
#else

/**
 * Walk string
 *
 * Set JSON_ENTRY to string and map the values from jsmntok_t.
 *
 * @param js the original string
 * @param t the tokens
 * @param start the first position
 * @param e the output structure.
 *
 * @return It always return 1
 */
size_t json_walk_string(char *js, jsmntok_t *t, size_t start, JSON_ENTRY *e)
{
    char old = js[t[start].end];
    js[t[start].end] = '\0';
    e->original_string = &js[t[start].start];

    e->type = JSON_STRING;
    e->data.string = e->original_string;
    if(e->callback_function) e->callback_function(e);
    js[t[start].end] = old;
    return 1;
}

/**
 * Walk Primitive
 *
 * Define the data type of the string
 *
 * @param js the original string
 * @param t the tokens
 * @param start the first position
 * @param e the output structure.
 *
 * @return It always return 1
 */
size_t json_walk_primitive(char *js, jsmntok_t *t, size_t start, JSON_ENTRY *e)
{
    char old = js[t[start].end];
    js[t[start].end] = '\0';
    e->original_string = &js[t[start].start];

    switch(e->original_string[0]) {
        case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7':
        case '8': case '9': case '-': case '.':
            e->type = JSON_NUMBER;
            e->data.number = strtold(e->original_string, NULL);
            break;

        case 't': case 'T':
            e->type = JSON_BOOLEAN;
            e->data.boolean = 1;
            break;

        case 'f': case 'F':
            e->type = JSON_BOOLEAN;
            e->data.boolean = 0;
            break;

        case 'n': case 'N':
        default:
            e->type = JSON_NULL;
            break;
    }
    if(e->callback_function) e->callback_function(e);
    js[t[start].end] = old;
    return 1;
}

/**
 * Array
 *
 * Measure the array length
 *
 * @param js the original string
 * @param t the tokens
 * @param nest the length of structure t
 * @param start the first position
 * @param e the structure with values and callback to be used inside the function.
 *
 * @return It returns the array length
 */
size_t json_walk_array(char *js, jsmntok_t *t, size_t nest, size_t start, JSON_ENTRY *e)
{
    JSON_ENTRY ne;

    char old = js[t[start].end];
    js[t[start].end] = '\0';
    ne.original_string = &js[t[start].start];

    memcpy(&ne, e, sizeof(JSON_ENTRY));
    ne.type = JSON_ARRAY;
    ne.data.items = t[start].size;
    ne.callback_function = e->callback_function;
    ne.name[0]='\0';
    ne.fullname[0]='\0';
    if(e->callback_function) e->callback_function(&ne);
    js[t[start].end] = old;

    size_t i, init = start, size = t[start].size;

    start++;
    for(i = 0; i < size ; i++) {
        ne.pos = i;
        if (strlen(e->name) > JSON_NAME_LEN  - 24 || strlen(e->fullname) > JSON_FULLNAME_LEN -24) {
            info("JSON: JSON walk_array ignoring element with name:%s fullname:%s",e->name, e->fullname);
            continue;
        }
        snprintfz(ne.name, JSON_NAME_LEN, "%s[%lu]", e->name, i);
        snprintfz(ne.fullname, JSON_FULLNAME_LEN, "%s[%lu]", e->fullname, i);

        switch(t[start].type) {
            case JSMN_PRIMITIVE:
                start += json_walk_primitive(js, t, start, &ne);
                break;

            case JSMN_OBJECT:
                start += json_walk_object(js, t, nest + 1, start, &ne);
                break;

            case JSMN_ARRAY:
                start += json_walk_array(js, t, nest + 1, start, &ne);
                break;

            case JSMN_STRING:
                start += json_walk_string(js, t, start, &ne);
                break;
        }
    }
    return start - init;
}

/**
 * Object
 *
 * Measure the Object length
 *
 * @param js the original string
 * @param t the tokens
 * @param nest the length of structure t
 * @param start the first position
 * @param e the output structure.
 *
 * @return It returns the Object length
 */
size_t json_walk_object(char *js, jsmntok_t *t, size_t nest, size_t start, JSON_ENTRY *e)
{
    JSON_ENTRY ne = {
            .name = "",
            .fullname = "",
            .callback_data = NULL,
            .callback_function = NULL
    };

    char old = js[t[start].end];
    js[t[start].end] = '\0';
    ne.original_string = &js[t[start].start];
    memcpy(&ne, e, sizeof(JSON_ENTRY));
    ne.type = JSON_OBJECT;
    ne.callback_function = e->callback_function;
    if(e->callback_function) e->callback_function(&ne);
    js[t[start].end] = old;

    int key = 1;
    size_t i, init = start, size = t[start].size;

    start++;
    for(i = 0; i < size ; i++) {
        switch(t[start].type) {
            case JSMN_PRIMITIVE:
                start += json_walk_primitive(js, t, start, &ne);
                key = 1;
                break;

            case JSMN_OBJECT:
                start += json_walk_object(js, t, nest + 1, start, &ne);
                key = 1;
                break;

            case JSMN_ARRAY:
                start += json_walk_array(js, t, nest + 1, start, &ne);
                key = 1;
                break;

            case JSMN_STRING:
            default:
                if(key) {
                    int len = t[start].end - t[start].start;
                    if (unlikely(len>JSON_NAME_LEN)) len=JSON_NAME_LEN;
                    strncpy(ne.name, &js[t[start].start], len);
                    ne.name[len] = '\0';
                    len=strlen(e->fullname) + strlen(e->fullname[0]?".":"") + strlen(ne.name);
                    char *c = mallocz((len+1)*sizeof(char));
                    sprintf(c,"%s%s%s", e->fullname, e->fullname[0]?".":"", ne.name);
                    if (unlikely(len>JSON_FULLNAME_LEN)) len=JSON_FULLNAME_LEN;
                    strncpy(ne.fullname, c, len);
                    freez(c);
                    start++;
                    key = 0;
                }
                else {
                    start += json_walk_string(js, t, start, &ne);
                    key = 1;
                }
                break;
        }
    }
    return start - init;
}
#endif

/**
 * Tree
 *
 * Call the correct walk function according its type.
 *
 * @param t the json object to work
 * @param callback_data additional data to be used together the callback function
 * @param callback_function function used to create a silencer.
 *
 * @return It always return 1
 */
#ifdef ENABLE_JSONC
size_t json_walk(json_object *t, void *callback_data, int (*callback_function)(struct json_entry *)) {
    JSON_ENTRY e;

    e.callback_data = callback_data;
    enum json_type type;
    json_object_object_foreach(t, key, val) {
        type = json_object_get_type(val);
        if (type == json_type_array) {
            e.type = JSON_ARRAY;
            json_jsonc_parse_array(val,NULL,health_silencers_json_read_callback);
        } else if (type == json_type_object) {
            e.type = JSON_OBJECT;
        } else if (type == json_type_string) {
            json_jsonc_set_string(&e,key,json_object_get_string(val));
            callback_function(&e);
        } else if (type == json_type_boolean) {
            json_jsonc_set_boolean(&e,json_object_get_boolean(val));
            callback_function(&e);
        }
    }

    return 1;
}
#else
/**
 * Tree
 *
 * Call the correct walk function according its type.
 *
 * @param js the original string
 * @param t the tokens
 * @param callback_data additional data to be used together the callback function
 * @param callback_function function used to create a silencer.
 *
 * @return It always return 1
 */
size_t json_walk_tree(char *js, jsmntok_t *t, void *callback_data, int (*callback_function)(struct json_entry *))
{
    JSON_ENTRY e = {
            .name = "",
            .fullname = "",
            .callback_data = callback_data,
            .callback_function = callback_function
    };

    switch (t[0].type) {
        case JSMN_OBJECT:
            e.type = JSON_OBJECT;
            json_walk_object(js, t, 0, 0, &e);
            break;

        case JSMN_ARRAY:
            e.type = JSON_ARRAY;
            json_walk_array(js, t, 0, 0, &e);
            break;

        case JSMN_PRIMITIVE:
        case JSMN_STRING:
            break;
    }

    return 1;
}
#endif

/**
 * JSON Parse
 *
 * Parse the json message with the callback function
 *
 * @param js the string that the callback function will parse
 * @param callback_data additional data to be used together the callback function
 * @param callback_function function used to create a silencer.
 *
 * @return JSON_OK  case everything happend as expected, JSON_CANNOT_PARSE case there were errors in the
 * parsing procces and JSON_CANNOT_DOWNLOAD case the string given(js) is NULL.
 */
int json_parse(char *js, void *callback_data, int (*callback_function)(JSON_ENTRY *))
{
    if(js) {
#ifdef ENABLE_JSONC
        json_object *tokens = json_tokenise(js);
#else
        size_t count;
        jsmntok_t *tokens = json_tokenise(js, strlen(js), &count);
#endif

        if(tokens) {
#ifdef ENABLE_JSONC
            json_walk(tokens, callback_data, callback_function);
            json_object_put(tokens);
#else
            json_walk_tree(js, tokens, callback_data, callback_function);
            freez(tokens);
#endif
            return JSON_OK;
        }

        return JSON_CANNOT_PARSE;
    }

    return JSON_CANNOT_DOWNLOAD;
}

/*
int json_test(char *str)
{
    return json_parse(str, NULL, json_callback_print);
}
 */