/*
 * 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 <stdio.h>
#include <stdlib.h>
#include <time.h>
#include "iotcs/iotcs_private.h"
#include "device_model_private.h"
#include "util/util.h"
#include "json/json_helper.h"
#include "util/util_memory.h"
#include "util/util.h"
#ifdef IOTCS_STORAGE_SUPPORT
#include "scs/storage_dispatcher_private.h"
#endif

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

static iotcs_value_type get_value_type(char* str_type, int str_len) {
    char* types[] = {"INTEGER", "NUMBER", "BOOLEAN", "STRING", "DATETIME", "NONE", "URI"};
    int i;

    if (!str_type || str_len <= 0) {
        return IOTCS_VALUE_TYPE_NONE;
    }

    for (i = 0; i < ARRAY_SIZE(types); i++) {
        if (strncmp(str_type, types[i], str_len) == 0) {
            return i;
        }
    }

    return -1;
}

static dm_format_type get_format_type(char* str_type, int str_len) {
    char* types[] = {"DATA", "ALERT"};
    int i;

    if (!str_type || str_len <= 0) {
        return -1;
    }

    for (i = 0; i < ARRAY_SIZE(types); i++) {
        if (strncmp(str_type, types[i], str_len) == 0) {
            return i;
        }
    }

    return -1;
}

static iotcs_result get_min_range(char* str_item, int str_item_len,
        iotcs_value_type type, iotcs_value* out_item) {
    char* tmp = NULL;
    char* next = NULL;
    char* min_value = NULL;

    if (!str_item || str_item_len <= 0 || !out_item) {
        return IOTCS_RESULT_INVALID_ARGUMENT;
    }

    CHECK_OOM(tmp = util_safe_strncpy(str_item, str_item_len));
    next = tmp;

    if (NULL == (min_value = util_get_next_token(&next, ','))) {
        util_free(tmp);
        return IOTCS_RESULT_FAIL;
    }

    switch (type) {
        case IOTCS_VALUE_TYPE_INT:
            out_item->int_value = atoi(min_value);
            break;
        case IOTCS_VALUE_TYPE_NUMBER:
            //TODO: possible loss of data
            out_item->number_value = (float) atof(min_value);
            break;
        default: break;
    }

    util_free(tmp);
    return IOTCS_RESULT_OK;
}

static iotcs_result get_max_range(char* str_item, int str_item_len,
        iotcs_value_type type, iotcs_value* out_item) {
    char* max_value = NULL;
    char* next = NULL;
    char* tmp = NULL;

    if (!str_item || str_item_len <= 0 || !out_item) {
        return IOTCS_RESULT_INVALID_ARGUMENT;
    }

    CHECK_OOM(tmp = util_safe_strncpy(str_item, str_item_len));
    next = tmp;

    //min value
    util_get_next_token(&next, ',');

    if (NULL == (max_value = util_get_next_token(&next, ','))) {
        util_free(tmp);
        return IOTCS_RESULT_FAIL;
    }

    switch (type) {
        case IOTCS_VALUE_TYPE_INT:
            out_item->int_value = atoi(max_value);
            break;
        case IOTCS_VALUE_TYPE_NUMBER:
            //TODO: possible loss of data
            out_item->number_value = (float) atof(max_value);
            break;
        default: break;
    }

    util_free(tmp);
    return IOTCS_RESULT_OK;
}

static iotcs_result get_default_attribute_value_field(json_tokens_t* tok,
        iotcs_value_type type, dm_attribute* desc) {
    char* temp_str = "";
    int temp_len = 0;
    iotcs_result rv;
    const char *uri;

    switch (type) {
        case IOTCS_VALUE_TYPE_URI:
#if defined IOTCS_STORAGE_SUPPORT
            GOTO_ERR_NO_OK(rv, json_get_storage_object_url_by_key(tok, &uri));
            GOTO_ERR_NO_OK(rv, dm_create_uri_object_by_uri(uri, &desc->default_value.uri_object));
            break;
#endif
        case IOTCS_VALUE_TYPE_STRING:
            rv = json_get_string_value_by_key(tok, "defaultValue",
                    &temp_str, &temp_len);
            break;
        case IOTCS_VALUE_TYPE_DATE_TIME:
            rv = json_get_int_value_by_key(tok, "defaultValue",
                    (int64_t*) & desc->default_value.int_value);
            break;
        case IOTCS_VALUE_TYPE_INT:
            rv = json_get_int_value_by_key(tok, "defaultValue",
                    (int64_t*) & desc->default_value.int_value);
            break;
        case IOTCS_VALUE_TYPE_NUMBER:
            rv = json_get_float_value_by_key(tok, "defaultValue",
                    &desc->default_value.number_value);
            break;
        case IOTCS_VALUE_TYPE_BOOLEAN:
            rv = json_get_bool_value_by_key(tok, "defaultValue",
                    &desc->default_value.bool_value);
            break;
        default:
            return IOTCS_RESULT_INVALID_ARGUMENT;
    }

    if (rv == IOTCS_RESULT_INVALID_ARGUMENT) {
        desc->has_default_value = IOTCS_FALSE;
        return IOTCS_RESULT_OK;
    } else if (rv != IOTCS_RESULT_OK) {
        return rv;
    }

    desc->has_default_value = IOTCS_TRUE;

    if (type == IOTCS_VALUE_TYPE_STRING) {
        if (NULL == (desc->default_value.string_value = util_safe_strncpy(temp_str, temp_len))) {
            return IOTCS_RESULT_INVALID_ARGUMENT;
        }
    }

    return IOTCS_RESULT_OK;
error:
    return rv;
}

static iotcs_result get_string_field_by_key(json_tokens_t* tok, char *key,
        iotcs_bool is_mandatory, const char **dst) {
    char* temp_str = "";
    int temp_len = 0;
    iotcs_result rv;

    rv = json_get_string_value_by_key(tok, key, &temp_str, &temp_len);
    if (is_mandatory) {
        CHECK_ERR(rv);
    } else {
        rv = IOTCS_RESULT_OK;
    }
    CHECK_OOM(*dst = util_safe_strncpy(temp_str, temp_len));

    return rv;
}

static iotcs_result get_bool_field_by_key(json_tokens_t* tok, char *key,
        iotcs_bool is_mandatory, iotcs_bool *dst) {
    iotcs_bool temp_bool = IOTCS_FALSE;
    iotcs_result rv;

    rv = json_get_bool_value_by_key(tok, key, &temp_bool);
    if (is_mandatory) {
        CHECK_ERR(rv);
    } else {
        rv = IOTCS_RESULT_OK;
    }

    *dst = temp_bool;

    return rv;
}

static iotcs_result get_range_field(json_tokens_t* tok,
        iotcs_value_type type, iotcs_value *dst[]) {
    iotcs_result rv;
    char* temp_str = "";
    int temp_len = 0;
    iotcs_value item;

    rv = json_get_string_value_by_key(tok, "range", &temp_str, &temp_len);
    if (rv != IOTCS_RESULT_OK) {
        dst[0] = NULL;
        dst[1] = NULL;
        return IOTCS_RESULT_OK;
    }

    GOTO_ERR_NO_OK(rv, get_min_range(temp_str, temp_len, type, &item));
    CHECK_OOM(dst[0] = (iotcs_value*) util_malloc(sizeof (iotcs_value)));

    *dst[0] = item;
    GOTO_ERR_NO_OK(rv, get_max_range(temp_str, temp_len, type, &item));
    GOTO_ERR_CRIT(NULL == (dst[1] = (iotcs_value*) util_malloc(sizeof (iotcs_value))));
    *dst[1] = item;

    return rv;

error:
    util_free(dst[0]);
    util_free(dst[1]);
    dst[0] = NULL;
    dst[1] = NULL;
    return rv;
}

static iotcs_result get_value_type_of_string_field_by_key(json_tokens_t* tok, char *key,
        iotcs_bool is_mandatory, iotcs_value_type *dst) {
    char* temp_str = "";
    int temp_len = 0;
    iotcs_value_type temp_type;
    iotcs_result rv;

    rv = json_get_string_value_by_key(tok, key, &temp_str, &temp_len);
    if (is_mandatory) {
        CHECK_ERR(rv);
    } else {
        rv = IOTCS_RESULT_OK;
    }
    if ((temp_type = get_value_type(temp_str, temp_len)) == -1) {
        return IOTCS_RESULT_FAIL;
    }
    *dst = temp_type;

    return rv;
}

static iotcs_result get_format_type_of_string_field_by_key(json_tokens_t* tok, char *key,
        iotcs_bool is_mandatory, dm_format_type *dst) {
    char* temp_str = "";
    int temp_len = 0;
    dm_format_type temp_type;
    iotcs_result rv;

    rv = json_get_string_value_by_key(tok, key, &temp_str, &temp_len);
    if (is_mandatory) {
        CHECK_ERR(rv);
    } else {
        rv = IOTCS_RESULT_OK;
    }
    if ((temp_type = get_format_type(temp_str, temp_len)) == -1) {
        return IOTCS_RESULT_FAIL;
    }
    *dst = temp_type;

    return rv;
}

static iotcs_result device_model_attributes_fromJSON(json_tokens_t* tok, iotcs_device_model_handle_t* device_model) {
    iotcs_result rv;
    json_tokens_t attributes_tok;
    dm_attributes_t *model_attributes = &device_model->attributes;

    if ((rv = json_get_array_by_key(tok, "attributes", &attributes_tok)) == IOTCS_RESULT_INVALID_ARGUMENT) {
        model_attributes->count = 0;
        return IOTCS_RESULT_OK;
    } else {
        GOTO_ERR(rv != IOTCS_RESULT_OK);
    }

    model_attributes->count = json_child_items_count(&attributes_tok);

    if (model_attributes->count == 0) {
        return IOTCS_RESULT_OK;
    }

    {
        char *urn_str;
        int urn_len = strlen(device_model->urn);
        size_t attr_str_len = strlen(":attributes");

        CHECK_OOM(urn_str = util_malloc(sizeof (char) * (urn_len + attr_str_len + 1)));
        strncpy(urn_str, device_model->urn, urn_len + 1);
        strncat(urn_str, ":attributes", attr_str_len + 1);
        model_attributes->message_props.format = urn_str;

        json_array_iterator_t iter;
        json_tokens_t attr_item_tok;
        int i = 0;

        CHECK_OOM(model_attributes->desc =
                (dm_attribute*) util_calloc(model_attributes->count, sizeof (dm_attribute)));
        GOTO_ERR_NO_OK(rv, json_get_array_iterator(&attributes_tok, &iter));
        while (json_array_iterator_has_next(&iter)) {
            GOTO_ERR_NO_OK(rv, json_array_iterator_next(&iter, &attr_item_tok));

            //mandatory
            GOTO_ERR_NO_OK(rv, get_string_field_by_key(&attr_item_tok, "name",
                    IOTCS_TRUE, &model_attributes->desc[i].name));
            GOTO_ERR_NO_OK(rv, get_value_type_of_string_field_by_key(&attr_item_tok, "type",
                    IOTCS_TRUE, &model_attributes->desc[i].type));
            //optional
            GOTO_ERR_NO_OK(rv, get_default_attribute_value_field(&attr_item_tok, model_attributes->desc[i].type,
                    &model_attributes->desc[i]));
            GOTO_ERR_NO_OK(rv, get_string_field_by_key(&attr_item_tok, "description",
                    IOTCS_FALSE, &model_attributes->desc[i].description));
            GOTO_ERR_NO_OK(rv, get_string_field_by_key(&attr_item_tok, "alias",
                    IOTCS_FALSE, &model_attributes->desc[i].alias));
            GOTO_ERR_NO_OK(rv, get_bool_field_by_key(&attr_item_tok, "writable",
                    IOTCS_FALSE, &model_attributes->desc[i].is_writable));
            // get range
            GOTO_ERR_NO_OK(rv, get_range_field(&attr_item_tok,
                    model_attributes->desc[i].type, model_attributes->desc[i].range));

            model_attributes->desc[i].model_urn = device_model->urn;
            model_attributes->desc[i].model_name = device_model->name;
            i++;
        }
    }
    return IOTCS_RESULT_OK;

error:
    dm_free_attributes(model_attributes);
    return IOTCS_RESULT_FAIL;
}

static iotcs_result device_model_actions_fromJSON(json_tokens_t* tok, iotcs_device_model_handle_t* device_model) {
    iotcs_result rv;
    json_tokens_t actions_tok;
    dm_actions_t *model_actions = &device_model->actions;

    if ((rv = json_get_array_by_key(tok, "actions", &actions_tok)) == IOTCS_RESULT_INVALID_ARGUMENT) {
        model_actions->count = 0;
        return IOTCS_RESULT_OK;
    } else {
        GOTO_ERR(rv != IOTCS_RESULT_OK);
    }

    model_actions->count = json_child_items_count(&actions_tok);

    if (model_actions->count == 0) {
        return IOTCS_RESULT_OK;
    }

    {
        int i = 0;
        json_array_iterator_t iter;
        json_tokens_t actions_item_tok;

        CHECK_OOM(model_actions->desc =
                (dm_action*) util_calloc(model_actions->count, sizeof (dm_action)));

        GOTO_ERR_NO_OK(rv, json_get_array_iterator(&actions_tok, &iter));
        while (json_array_iterator_has_next(&iter)) {
            GOTO_ERR_NO_OK(rv, json_array_iterator_next(&iter, &actions_item_tok));

            //mandatory
            GOTO_ERR_NO_OK(rv, get_string_field_by_key(&actions_item_tok, "name",
                    IOTCS_TRUE, &model_actions->desc[i].name));

            //optional
            GOTO_ERR_NO_OK(rv, get_string_field_by_key(&actions_item_tok, "description",
                    IOTCS_FALSE, &model_actions->desc[i].description));
            GOTO_ERR_NO_OK(rv, get_string_field_by_key(&actions_item_tok, "alias",
                    IOTCS_FALSE, &model_actions->desc[i].alias));
            GOTO_ERR_NO_OK(rv, get_value_type_of_string_field_by_key(&actions_item_tok, "argType",
                    IOTCS_FALSE, &model_actions->desc[i].arg_type));

            model_actions->desc[i].model_urn = device_model->urn;
            model_actions->desc[i].model_name = device_model->name;
            i++;
        }
    }
    return IOTCS_RESULT_OK;

error:
    dm_free_actions(model_actions);
    return IOTCS_RESULT_FAIL;
}

static iotcs_result device_model_format_fromJSON(json_tokens_t* tok, dm_formats_handle_t *model_formats) {
    json_tokens_t formats_tok;
    iotcs_result rv;

    if ((rv = json_get_array_by_key(tok, "formats", &formats_tok)) == IOTCS_RESULT_INVALID_ARGUMENT) {
        model_formats->formats_len = 0;
        return IOTCS_RESULT_OK;
    } else {
        GOTO_ERR(rv != IOTCS_RESULT_OK);
    }

    model_formats->formats_len = json_child_items_count(&formats_tok);
    if (model_formats->formats_len == 0) {
        return IOTCS_RESULT_OK;
    }

    {
        int i = 0;
        json_array_iterator_t iter;
        json_tokens_t formats_item_tok, value_tok, fields_tok;
        int value_cnt;

        CHECK_OOM(model_formats->formats =
                (dm_format_t*) util_calloc(model_formats->formats_len, sizeof (dm_format_t)));

        GOTO_ERR_NO_OK(rv, json_get_array_iterator(&formats_tok, &iter));
        while (json_array_iterator_has_next(&iter)) {
            GOTO_ERR_NO_OK(rv, json_array_iterator_next(&iter, &formats_item_tok));

            //mandatory
            GOTO_ERR_NO_OK(rv, get_string_field_by_key(&formats_item_tok, "name",
                    IOTCS_TRUE, &model_formats->formats[i].name));
            GOTO_ERR_NO_OK(rv, get_string_field_by_key(&formats_item_tok, "urn",
                    IOTCS_TRUE, &model_formats->formats[i].urn));
            GOTO_ERR_NO_OK(rv, get_format_type_of_string_field_by_key(&formats_item_tok, "type",
                    IOTCS_TRUE, &model_formats->formats[i].type));

            //optional
            GOTO_ERR_NO_OK(rv, get_string_field_by_key(&formats_item_tok, "description",
                    IOTCS_FALSE, &model_formats->formats[i].description));

            if (DM_DATA_FORMAT_TYPE == model_formats->formats[i].type) {
                model_formats->formats[i].u.data_message_base.format = model_formats->formats[i].urn;
            } else {
                model_formats->formats[i].u.alert_message_base.format = model_formats->formats[i].urn;
                model_formats->formats[i].u.alert_message_base.description = model_formats->formats[i].description;
                model_formats->formats[i].u.alert_message_base.severity_level = IOTCS_MESSAGE_SEVERITY_DEFAULT;
            }

            /* value - Required: true*/
            GOTO_ERR_NO_OK(rv, json_get_object_by_key(&formats_item_tok, "value", &value_tok));
            value_cnt = json_child_items_count(&value_tok);
            if (value_cnt < 0) {
                LOG_CRIT("Unexpected error: json_child_items_count method returns %d",
                        value_cnt);
                return IOTCS_RESULT_FAIL;
            }

            /* fields - optional and could be empty*/
            if ((rv = json_get_array_by_key(&value_tok, "fields", &fields_tok)) == IOTCS_RESULT_INVALID_ARGUMENT) {
                model_formats->formats[i].fields_len = 0;
            } else {
                GOTO_ERR(rv != IOTCS_RESULT_OK);
                model_formats->formats[i].fields_len = json_child_items_count(&fields_tok);
                if (model_formats->formats[i].fields_len < 0) {
                    LOG_CRIT("Unexpected error: json_child_items_count method returns %d",
                            value_cnt);
                    return IOTCS_RESULT_FAIL;
                }
            }
            {
                int j = 0;
                json_array_iterator_t fields_iter;
                json_tokens_t fields_item_tok;

                CHECK_OOM(model_formats->formats[i].fields = (dm_format_field_t*)
                        util_calloc(model_formats->formats[i].fields_len, sizeof (dm_format_field_t)));

                GOTO_ERR_NO_OK(rv, json_get_array_iterator(&fields_tok, &fields_iter));
                while (json_array_iterator_has_next(&fields_iter)) {
                    GOTO_ERR_NO_OK(rv, json_array_iterator_next(&fields_iter, &fields_item_tok));

                    //mandatory
                    GOTO_ERR_NO_OK(rv, get_string_field_by_key(&fields_item_tok, "name",
                            IOTCS_TRUE, &model_formats->formats[i].fields[j].name));
                    GOTO_ERR_NO_OK(rv, get_value_type_of_string_field_by_key(&fields_item_tok, "type",
                            IOTCS_TRUE, &model_formats->formats[i].fields[j].type));
                    GOTO_ERR_NO_OK(rv, get_bool_field_by_key(&fields_item_tok, "optional",
                            IOTCS_TRUE, &model_formats->formats[i].fields[j].optional));
                    j++;
                }
            }
            i++;
        }
    }
    return rv;

error:
    dm_free_formats(model_formats);
    return rv;
}

iotcs_device_model_handle_t* device_model_fromJSON(json_tokens_t* tok) {
    iotcs_device_model_handle_t* device_model = NULL;

    /* If IOTCS_USE_DRAFT_DEVICE_MODELS isn't defined then treat "draft" models as invalid */
#ifndef IOTCS_USE_DRAFT_DEVICE_MODELS
    {
        iotcs_bool is_draft_model = 0;
        json_get_bool_value_by_key(tok, "draft", &is_draft_model);
        if (IOTCS_TRUE == is_draft_model) {
            return NULL;
        }
    }
#endif

    GOTO_ERR_CRIT(NULL == (device_model =
            (iotcs_device_model_handle_t*) util_calloc(1, sizeof (iotcs_device_model_handle_t))));

    //parse model base
    GOTO_ERR(get_string_field_by_key(tok, "urn", IOTCS_TRUE, &device_model->urn) != IOTCS_RESULT_OK);
    GOTO_ERR(get_string_field_by_key(tok, "name", IOTCS_FALSE, &device_model->name) != IOTCS_RESULT_OK);
    GOTO_ERR(get_string_field_by_key(tok, "description", IOTCS_FALSE, &device_model->description) != IOTCS_RESULT_OK);

    //attributes
    GOTO_ERR(device_model_attributes_fromJSON(tok, device_model) != IOTCS_RESULT_OK);

    //actions
    GOTO_ERR(device_model_actions_fromJSON(tok, device_model) != IOTCS_RESULT_OK);

    //formats
    GOTO_ERR(device_model_format_fromJSON(tok, &device_model->formats) != IOTCS_RESULT_OK);

    return device_model;

error:
    dm_release_model(device_model);
    return NULL;
}