/*
 * Copyright (c) 2015, 2016, Oracle and/or its affiliates. All rights reserved.
 *
 * This software is dual-licensed to you under the MIT License (MIT) and
 * the Universal Permissive License (UPL). See the LICENSE file in the root
 * directory for license terms. You may choose either license, or both.
 */


#include <stdio.h>
#include <string.h>
#if MSVC
#define PRId64 "I64d"
#else
#include <inttypes.h>
#endif
#include "json_writer.h"
#include "util/util.h"

#include "log/log.h"
#define IOTCSP_MODULE_LOG_CHANNEL LOG_CHANNEL_JSON
#include "log/log_template.h"

#if defined IOTCS_STORAGE_SUPPORT
#include "advanced/iotcs_storage_object.h"
#include "advanced/iotcs_messaging.h"
#include "scs/storage_dispatcher_private.h"
#endif

/*64 kb*/
#define JSON_MAX_STRING_VALUE_LENGTH (64 * 1024)

int json_noop_callback(json_buf *buf, int pos, void *data) {
    (void) buf;
    (void) pos;
    (void) data;
    return 0;
}

/* 0 No more data, <0 fail(Buffer overflow), >0 success */
int json_write_object(json_buf *buf, int pos, json_callback write_fields_callback, void *data) {
    int rv;
    int original_pos = pos;

    JSON_PUT_SYMBOL(buf, pos, '{');
    rv = write_fields_callback(buf, pos, data);
    JSON_MOVE_POS(pos, rv);
    JSON_PUT_SYMBOL(buf, pos, '}');

    return pos - original_pos;

error:
    return -IOTCS_RESULT_OUT_OF_MEMORY;
}

int json_write_object_null_terminated(json_buf *buf, int pos, json_callback write_fields_callback, void *data) {
    int rv;
    int original_pos = pos;
    rv = json_write_object(buf, pos, write_fields_callback, data);
    JSON_MOVE_POS(pos, rv);
    JSON_PUT_SYMBOL(buf, pos, '\0');

    return pos - original_pos;

error:
    return -IOTCS_RESULT_OUT_OF_MEMORY;
}

int json_write_object_field(json_buf *buf, int pos, const char *key, json_callback write_fields_callback, void *data) {
    int rv;
    int original_pos = pos;

    rv = json_write_string(buf, pos, key);
    JSON_MOVE_POS(pos, rv);
    JSON_PUT_SYMBOL(buf, pos, ':');
    rv = json_write_object(buf, pos, write_fields_callback, data);
    JSON_MOVE_POS(pos, rv);
    return pos - original_pos;

error:
    return -IOTCS_RESULT_OUT_OF_MEMORY;
}

/* 0 No more data, <0 fail(Buffer overflow), >0 success */
int json_write_array(json_buf *buf, int pos, json_callback write_fields_callback, void *data) {
    int rv;
    int original_pos = pos;

    JSON_PUT_SYMBOL(buf, pos, '[');
    rv = write_fields_callback(buf, pos, data);
    JSON_MOVE_POS(pos, rv);
    JSON_PUT_SYMBOL(buf, pos, ']');

    return pos - original_pos;

error:
    return -IOTCS_RESULT_OUT_OF_MEMORY;
}

int json_write_array_field(json_buf *buf, int pos, const char *key, json_callback write_next_field_callback, void *data) {
    int rv;
    int original_pos = pos;

    rv = json_write_string(buf, pos, key);
    JSON_MOVE_POS(pos, rv);
    JSON_PUT_SYMBOL(buf, pos, ':');
    rv = json_write_array(buf, pos, write_next_field_callback, data);
    JSON_MOVE_POS(pos, rv);
    return pos - original_pos;

error:
    return -IOTCS_RESULT_OUT_OF_MEMORY;
}

int json_write_iotcs_value_field(json_buf *buf, int pos, const char *key, const iotcs_value value, iotcs_value_type type) {
    int rv;
    int original_pos = pos;

    rv = json_write_string(buf, pos, key);
    JSON_MOVE_POS(pos, rv);
    JSON_PUT_SYMBOL(buf, pos, ':');
    switch (type) {
        case IOTCS_VALUE_TYPE_URI:
#if defined IOTCS_STORAGE_SUPPORT
            rv = json_write_uri(buf, pos, value.uri_object);
            break;
#endif
        case IOTCS_VALUE_TYPE_STRING:
            if (value.string_value == NULL) {
                LOG_ERRS("Null string values are not permitted.");
                return -IOTCS_RESULT_INVALID_ARGUMENT;
            }
            int str_len;
            if((str_len = strlen(value.string_value)) > JSON_MAX_STRING_VALUE_LENGTH) {
                LOG_ERR("Max JSON string value was exceeded 64 kb. Current: %d", str_len);
                return -IOTCS_RESULT_INVALID_ARGUMENT;
            }
            rv = json_write_string(buf, pos, value.string_value);
            break;
        case IOTCS_VALUE_TYPE_NUMBER:
            if (!util_is_valid_number(value.number_value)) {
                LOG_ERRS("Numeric values that cannot be represented as sequences "
                        "of digits (such as Infinity and NaN) are not permitted in JSON.");
                return -IOTCS_RESULT_INVALID_ARGUMENT;
            }
            rv = json_write_float(buf, pos, value.number_value);
            break;
        case IOTCS_VALUE_TYPE_DATE_TIME:
            rv = json_write_int(buf, pos, value.date_time_value);
            break;
        case IOTCS_VALUE_TYPE_INT:
            rv = json_write_int(buf, pos, value.int_value);
            break;
        case IOTCS_VALUE_TYPE_BOOLEAN:
            rv = json_write_bool(buf, pos, value.bool_value);
            break;
        default:
            return -IOTCS_RESULT_INVALID_ARGUMENT;
    }
    JSON_MOVE_POS(pos, rv);

    return pos - original_pos;

error:
    return -IOTCS_RESULT_OUT_OF_MEMORY;
}

#if defined IOTCS_STORAGE_SUPPORT
int json_write_uri_field(json_buf *buf, int pos, const char *key, struct iotcs_uri_object_t *value) {
    int rv;
    int original_pos = pos;

    rv = json_write_string(buf, pos, key);
    JSON_MOVE_POS(pos, rv);
    JSON_PUT_SYMBOL(buf, pos, ':');
    rv = json_write_uri(buf, pos, value);
    JSON_MOVE_POS(pos, rv);

    return pos - original_pos;

error:
    return -IOTCS_RESULT_OUT_OF_MEMORY;
}

int json_write_uri(json_buf *buf, int pos, struct iotcs_uri_object_t *str) {
    int rv;
    int original_pos = pos;

    JSON_PUT_SYMBOL(buf, pos, '"');
    if (IS_STORAGE_OBJECT(str)) {
        rv = json_write_string_escaped(buf, pos, iotcs_storage_object_get_uri(str));
    } else if (IS_EXTERNAL_OBJECT(str)) {
        rv = json_write_string_escaped(buf, pos, (char*)str->object);
    } else {
        rv = IOTCS_RESULT_FAIL;
    }
    JSON_MOVE_POS(pos, rv);
    JSON_PUT_SYMBOL(buf, pos, '"');
    return pos - original_pos;
error:
    return -IOTCS_RESULT_OUT_OF_MEMORY;
}
#endif

int json_write_string_field(json_buf *buf, int pos, const char *key, const char *value) {
    int rv;
    int original_pos = pos;

    rv = json_write_string(buf, pos, key);
    JSON_MOVE_POS(pos, rv);
    JSON_PUT_SYMBOL(buf, pos, ':');
    rv = json_write_string(buf, pos, value);
    JSON_MOVE_POS(pos, rv);

    return pos - original_pos;

error:
    return -IOTCS_RESULT_OUT_OF_MEMORY;
}

int json_write_string(json_buf *buf, int pos, const char *str) {
    int rv;
    int original_pos = pos;

    JSON_PUT_SYMBOL(buf, pos, '"');
    rv = json_write_string_escaped(buf, pos, str);
    JSON_MOVE_POS(pos, rv);
    JSON_PUT_SYMBOL(buf, pos, '"');
    return pos - original_pos;
error:
    return -IOTCS_RESULT_OUT_OF_MEMORY;
}

int json_write_string_escaped(json_buf *buf, int pos, const char *str) {
    int original_pos = pos;

    if (str != NULL) {
        while (*str != '\0') {
            char c = *str++;

            switch (c) {
                case '"': JSON_PUT_SYMBOL_ESCAPED(buf, pos, '\"');
                    break;
                case '\\': JSON_PUT_SYMBOL_ESCAPED(buf, pos, '\\');
                    break;
                case '/': JSON_PUT_SYMBOL_ESCAPED(buf, pos, '/');
                    break;
                case '\b': JSON_PUT_SYMBOL_ESCAPED(buf, pos, 'b');
                    break;
                case '\f': JSON_PUT_SYMBOL_ESCAPED(buf, pos, 'f');
                    break;
                case '\n': JSON_PUT_SYMBOL_ESCAPED(buf, pos, 'n');
                    break;
                case '\r': JSON_PUT_SYMBOL_ESCAPED(buf, pos, 'r');
                    break;
                case '\t': JSON_PUT_SYMBOL_ESCAPED(buf, pos, 't');
                    break;
                default: JSON_PUT_SYMBOL(buf, pos, c);
            }
        }
    }
    return pos - original_pos;
error:
    return -IOTCS_RESULT_OUT_OF_MEMORY;
}

int json_write_int_field(json_buf *buf, int pos, const char *key, int64_t value) {
    int rv;
    int original_pos = pos;

    rv = json_write_string(buf, pos, key);
    JSON_MOVE_POS(pos, rv);
    JSON_PUT_SYMBOL(buf, pos, ':');
    rv = json_write_int(buf, pos, value);
    JSON_MOVE_POS(pos, rv);

    return pos - original_pos;

error:
    return -IOTCS_RESULT_OUT_OF_MEMORY;
}

int json_write_int(json_buf *buf, int pos, int64_t value) {
    int rv;
    int original_pos = pos;

    GOTO_ERR(0 > (rv = util_safe_snprintf(buf->buf + pos, buf->len - pos, "%" PRId64, value)));
    JSON_MOVE_POS(pos, rv);

    return pos - original_pos;

error:
    return -IOTCS_RESULT_OUT_OF_MEMORY;
}

int json_write_float_field(json_buf *buf, int pos, const char *key, float value) {
    int rv;
    int original_pos = pos;

    rv = json_write_string(buf, pos, key);
    JSON_MOVE_POS(pos, rv);
    JSON_PUT_SYMBOL(buf, pos, ':');
    rv = json_write_float(buf, pos, value);
    JSON_MOVE_POS(pos, rv);

    return pos - original_pos;

error:
    return -IOTCS_RESULT_OUT_OF_MEMORY;
}

int json_write_float(json_buf *buf, int pos, float value) {
    int rv;
    int original_pos = pos;

    GOTO_ERR(0 > (rv = util_safe_snprintf(buf->buf + pos, buf->len - pos, "%g", value)));
    JSON_MOVE_POS(pos, rv);

    return pos - original_pos;

error:
    return -IOTCS_RESULT_OUT_OF_MEMORY;
}

int json_write_bool_field(json_buf *buf, int pos, const char *key, int value) {
    int rv;
    int original_pos = pos;

    rv = json_write_string(buf, pos, key);
    JSON_MOVE_POS(pos, rv);
    JSON_PUT_SYMBOL(buf, pos, ':');
    rv = json_write_bool(buf, pos, value);
    JSON_MOVE_POS(pos, rv);

    return pos - original_pos;

error:
    return -IOTCS_RESULT_OUT_OF_MEMORY;
}

int json_write_bool(json_buf *buf, int pos, int value) {
    int rv;
    int original_pos = pos;

    if (value) {
        rv = json_write_string_escaped(buf, pos, "true");
    } else {
        rv = json_write_string_escaped(buf, pos, "false");
    }
    JSON_MOVE_POS(pos, rv);

    return pos - original_pos;
}