/*
 * 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 <string.h>
#include <assert.h>

#include "iotcs/iotcs_private.h"
#include "util/util_thread_private.h"
#include "msg_private.h"
#include "iotcs_port_crypto.h"

#include "json/json_helper.h"
#include "util/util.h"
#include "util/util_key_value.h"
#include "protocol/protocol_request.h"

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

#ifndef IOTCS_REQUEST_MESSAGE_BUFFER_SIZE
#define IOTCS_REQUEST_MESSAGE_BUFFER_SIZE 4096
#endif

#define OOM_ERROR_MESSAGE_HEADER "Not enough memory in request message buffer for headers"
#define OOM_ERROR_MESSAGE_PARAM  "Not enough memory in request message buffer for params"

#ifndef IOTCS_REQUEST_MESSAGE_NUMBER
#define IOTCS_REQUEST_MESSAGE_NUMBER 8
#endif

static msg_request_message_holder g_request_objs[IOTCS_REQUEST_MESSAGE_NUMBER];

msg_request_message_holder* request_get_free() {
    int i;
    for (i = 0; i < IOTCS_REQUEST_MESSAGE_NUMBER; ++i) {
        if (g_request_objs[i].ref_cnt == 0) {
            g_request_objs[i].ref_cnt = 1;
            g_request_objs[i].mem_pool_pos = 0;
            return &g_request_objs[i];
        }
    }
    return NULL;
}

static void request_release(msg_request_message_holder *request) {
    int i;
    for (i = 0; i < IOTCS_REQUEST_MESSAGE_NUMBER; ++i) {
        if (&g_request_objs[i] == request) {
            assert(g_request_objs[i].ref_cnt > 0);
            g_request_objs[i].ref_cnt -= 1;
            return;
        }
    }
    assert(request == NULL);
    return;
}

char* request_strdup(msg_request_message_holder *request, const char *str, int len) {
    int pos = request->mem_pool_pos;
    int remain = IOTCS_REQUEST_MESSAGE_BUFFER_SIZE - pos;
    char* out = NULL;

    /* Make sure that len + 1 > remain. i.e. we have space for len + '\0' */
    if (len >= remain) {
        return NULL;
    }

    /* add zero byte to number of bytes */
    request->mem_pool_pos += len + 1;
    out = memcpy(request->mem_pool + pos, str, len);
    out[len] = '\0';
    return out;
}

#define MSG_REQUEST_MEM_ALIGNMENT (sizeof(void*))

void* request_malloc(msg_request_message_holder *request, size_t len) {
    int pos;
    int remain;

    if (0 == len) {
        return NULL;
    }

    pos = (request->mem_pool_pos + (MSG_REQUEST_MEM_ALIGNMENT - 1)) & -(int)MSG_REQUEST_MEM_ALIGNMENT;

    remain = IOTCS_REQUEST_MESSAGE_BUFFER_SIZE - pos;

    if ((int)len > remain) {
        return NULL;
    }

    request->mem_pool_pos = pos + len;
    return request->mem_pool + pos;
}

static iotcs_result get_string_field_by_key(json_tokens_t* tok,
        msg_request_message_holder *holder, const char *key, char **dst) {
    iotcs_result rv;
    char* value;
    int value_length;

    GOTO_ERR_NO_OK(rv, json_get_string_value_by_key(tok, key, &value, &value_length));
    CHECK_OOM_MSG(*dst = request_strdup(holder, value, value_length), "Out of memory in request message buffer");

error:
    return rv;
}

static iotcs_result get_request_headers_from_json(json_tokens_t* payload_tok,
        iotcs_request_message* message, msg_request_message_holder *holder) {
    iotcs_result rv = IOTCS_RESULT_OK;
    int value_length, value_cnt;
    json_tokens_t headers_tok;
    json_tokens_t header_item_tok, value_tok;
    json_object_iterator_t headers_iter;
    json_array_iterator_t value_iter;
    iotcs_key_value *headers;

    /*
     * headers: {
     *      "header-1":["header-1 value-1", "header-1 value-2",..."header-1 value-n"],
     *      ...
     *      "header-n":["header-n value-1", "header-n value-2",..."header-n value-n"]
     * }
     */
    GOTO_ERR_NO_OK(rv, json_get_object_by_key(payload_tok, "headers", &headers_tok));

    // headers items count
    message->headers_len = json_child_items_count(&headers_tok);
    if (message->headers_len == 0) {
        return IOTCS_RESULT_OK;
    }

    CHECK_OOM_MSG(message->headers = request_malloc(holder, message->headers_len * sizeof (iotcs_key_value)), OOM_ERROR_MESSAGE_HEADER);
    GOTO_ERR_NO_OK(rv, json_get_object_iterator(&headers_tok, &headers_iter));

    headers = message->headers;
    while (json_object_iterator_has_next(&headers_iter)) {
        char *str, *value_ptr;
        int str_len;
        GOTO_ERR_NO_OK(rv, json_object_iterator_next(&headers_iter, &header_item_tok));

        GOTO_ERR_NO_OK(rv, json_get_string_and_move(&header_item_tok, &str, &str_len));
        CHECK_OOM_MSG(headers->key = request_strdup(holder, str, str_len), OOM_ERROR_MESSAGE_HEADER);

        // get items count in array
        value_cnt = json_child_items_count(&header_item_tok);

        GOTO_ERR_NO_OK(rv, json_get_array_iterator(&header_item_tok, &value_iter));
        value_length = 0;
        while (value_cnt--) {
            GOTO_ERR_NO_OK(rv, json_array_iterator_next(&value_iter, &value_tok));
            value_length += json_get_length(&value_tok);
            ++value_length; /* for comma or '\0' at the end */
        }
        CHECK_OOM_MSG(value_ptr = request_malloc(holder, value_length), OOM_ERROR_MESSAGE_HEADER);
        headers->value = value_ptr;

        GOTO_ERR_NO_OK(rv, json_get_array_iterator(&header_item_tok, &value_iter));
        GOTO_ERR_NO_OK(rv, json_array_iterator_next(&value_iter, &value_tok));
        value_cnt = json_child_items_count(&header_item_tok);
        value_length = 0;
        while (value_cnt--) {
            GOTO_ERR_NO_OK(rv, json_get_string_and_move(&value_tok, &str, &str_len));
            memcpy(value_ptr, str, str_len);
            value_ptr += str_len;
            *value_ptr++ = ',';
        }
        --value_ptr;
        *value_ptr = '\0';

        headers++;
    }

error:
    return rv;
}

static iotcs_result get_request_params_from_json(json_tokens_t* payload_tok, iotcs_request_message* message, msg_request_message_holder *holder) {
    iotcs_result rv;
    json_tokens_t params_tok, params_item_tok;
    json_object_iterator_t iter;
    iotcs_key_value *params;
    /*
     * params: {
     *      "param-1":"param-1 value",
     *      ...
     *      "param-n":"param-n value"
     * }
     */
    GOTO_ERR_NO_OK(rv, json_get_object_by_key(payload_tok, "params", &params_tok));
    // params length
    message->params_len = json_child_items_count(&params_tok);
    if (message->params_len == 0) {
        return IOTCS_RESULT_OK;
    }

    CHECK_OOM_MSG(message->params = request_malloc(holder, message->params_len * sizeof (iotcs_key_value)), OOM_ERROR_MESSAGE_PARAM);
    GOTO_ERR_NO_OK(rv, json_get_object_iterator(&params_tok, &iter));

    params = message->params;
    while (json_object_iterator_has_next(&iter)) {
        char *str;
        int str_len;

        GOTO_ERR_NO_OK(rv, json_object_iterator_next(&iter, &params_item_tok));

        GOTO_ERR_NO_OK(rv, json_get_string_and_move(&params_item_tok, &str, &str_len));
        CHECK_OOM_MSG(params->key = request_strdup(holder, str, str_len), OOM_ERROR_MESSAGE_PARAM);

        GOTO_ERR_NO_OK(rv, json_get_string_and_move(&params_item_tok, &str, &str_len));
        CHECK_OOM_MSG(params->value = request_strdup(holder, str, str_len), OOM_ERROR_MESSAGE_PARAM);

        params++;
    }

error:
    return rv;
}

iotcs_request_message* msg_request_fromJSON(json_tokens_t* tok) {
    msg_request_message_holder *holder;
    iotcs_request_message* message = NULL;
    //temp variables
    iotcs_result rv;

    holder = request_get_free();
    GOTO_ERR_MSG(!holder, "No more room in the request message holder. Operation failed");
    message = &holder->request;

    memset(message, 0, sizeof (iotcs_request_message));

    GOTO_ERR_NO_OK(rv, get_string_field_by_key(tok, holder, "id", &message->id));
    GOTO_ERR_NO_OK(rv, get_string_field_by_key(tok, holder, "source", &message->source));
    GOTO_ERR_NO_OK(rv, get_string_field_by_key(tok, holder, "destination", &message->destination));

    {
        json_tokens_t payload_tok;
        char* value;
        int value_length;

        GOTO_ERR_NO_OK(rv, json_get_object_by_key(tok, "payload", &payload_tok));

        GOTO_ERR_NO_OK(rv, get_string_field_by_key(&payload_tok, holder, "url", &message->url));

        GOTO_ERR_NO_OK(rv, json_get_string_value_by_key(&payload_tok, "method", &value, &value_length));
        GOTO_ERR_NO_OK(rv, protocol_string_to_request_method(value, value_length, &message->method));

        GOTO_ERR_NO_OK(rv, json_get_string_value_by_key(&payload_tok, "body", &value, &value_length));
        if (value_length > 0) {
            size_t decoded_len = 3 * (value_length>>2);
            rv = IOTCS_RESULT_OUT_OF_MEMORY;
            GOTO_ERR_CRIT_MSG(NULL == (message->body = request_malloc(holder, decoded_len + 1)), "Not enough memory in request message buffer for body");
            GOTO_ERR_NO_OK(rv, iotcs_port_crypto_decode_base64(message->body, &decoded_len, value, value_length));
            message->body[decoded_len] = '\0';
        }

        GOTO_ERR_NO_OK(rv, get_request_headers_from_json(&payload_tok, message, holder));
        GOTO_ERR_NO_OK(rv, get_request_params_from_json(&payload_tok, message, holder));
    }

    return message;

error:
    request_release(holder);
    return NULL;
}

//Not implemented yet.

int msg_request_toJSON(json_buf *buf, int pos, void* data) {
    (void) buf;
    (void) pos;
    (void) data;
    return -IOTCS_RESULT_INVALID_ARGUMENT;
}

#ifndef IOTCS_MESSAGE_DISPATCHER

void iotcs_request_message_free(iotcs_request_message* message) {
    cl_internal_request_message_free(message);
}
#endif

void cl_internal_request_message_free(iotcs_request_message* message) {
    UTIL_LOCK_LL();
    cl_internal_request_message_free_unsafe(message);
    UTIL_UNLOCK_LL();
}

void cl_internal_request_message_free_unsafe(iotcs_request_message* message) {
    request_release((msg_request_message_holder *)((char*)message - offsetof(msg_request_message_holder, request)));
}
