/*
 * Copyright (c) 2017, 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 "advanced/iotcs_storage_object.h"
#include "iotcs_storage_object.h"
#include "util/util.h"
#include "util/util_memory.h"
#include "util/util_thread_private.h"
#include "iotcs_port_queue.h"
#include "log/log.h"
#define IOTCSP_MODULE_LOG_CHANNEL LOG_CHANNEL_DM
#include "log/log_template.h"
#include "scs/storage_dispatcher_private.h"
#include "util/util_thread_private.h"
#include "advanced/iotcs_messaging.h"
#include "device_model/device_model_private.h"
#include "trusted_assets_manager/iotcs_tam.h"

iotcs_storage_dispatcher_progress_callback progress_callback = cl_storage_progress_cb;
extern char *g_storage_container_url;
#ifdef IOTCS_MESSAGE_DISPATCHER
extern iotcs_message_dispatcher_error_callback g_error_cb;
#endif

void dm_free_uri_object(struct iotcs_uri_object_t *uri_object) {
    sd_storage_object *storage_object;
    if (!uri_object)
        return;
    --uri_object->ref_cnt;
    if (uri_object->ref_cnt == 0) {
        switch (uri_object->type) {
            case IOTCS_STORAGE_OBJECT:
                storage_object = (sd_storage_object *)uri_object->object;
                util_free((void*)storage_object->content_type);
                util_free((void*)storage_object->name);
                util_free((void*)storage_object->endpoint_id);
                util_free((void*)storage_object->input_path);
                util_free((void*)storage_object->output_path);
                util_free((void*)storage_object->encoding);
                util_free((void*)storage_object->uri);
                util_free((void*)storage_object);
                break;
            case IOTCS_EXTERNAL_OBJECT:
                util_free((void*)uri_object->object); // char*
                util_free((void*)uri_object);
                break;
            default:
                LOG_CRITS("Unknown type of object");
                break;
        }
    }
}

void iotcs_storage_object_cancel(struct iotcs_uri_object_t *object) {
    if (IS_STORAGE_OBJECT(object)) {
        sd_storage_object *storage_object = (sd_storage_object *)object->object;
        storage_object->progress_state = IOTCS_CANCELED;
    }
}


iotcs_result iotcs_create_external_object_handle(const char* uri,
        struct iotcs_uri_object_t **external_object) {
    return iotcs_create_external_object(uri, external_object);
}

void iotcs_free_external_object_handle(struct iotcs_uri_object_t *external_object) {
    iotcs_free_external_object(external_object);
}

const char* iotcs_external_object_get_uri(const struct iotcs_uri_object_t *external_object) {
    if (IS_EXTERNAL_OBJECT(external_object)) {
        return (const char*)external_object->object;
    } else {
        return NULL;
    }
}

iotcs_result iotcs_create_external_object(const char* uri,
        struct iotcs_uri_object_t **external_object) {
    if (!uri || (*uri == 0))
        return IOTCS_RESULT_INVALID_ARGUMENT;
    CHECK_OOM(*external_object = (iotcs_external_object_handle)util_malloc(sizeof(struct iotcs_uri_object_t)));
    CHECK_OOM((*external_object)->object = util_safe_strcpy(uri));
    (*external_object)->type = IOTCS_EXTERNAL_OBJECT;
    (*external_object)->ref_cnt = 1;
    return IOTCS_RESULT_OK;
}

void iotcs_free_external_object(struct iotcs_uri_object_t *external_object) {
    if (IS_EXTERNAL_OBJECT(external_object)) {
        util_free((void*)external_object->object);
        util_free((void*)external_object);
    } else {
        LOG_WARNS("Not a external object");
    }
}

iotcs_bool cl_storage_progress_cb_h(struct iotcs_uri_object_t *uri_object,
            iotcs_storage_dispatcher_progress_state state,
            int64_t bytes_transfered, const char *fail_reason) {
    struct sd_storage_object_t* storage_object = (struct sd_storage_object_t*)uri_object->object;
    // If it is call from cancel mark
    if (state == IOTCS_CANCELED) {
        (*progress_callback)(uri_object, IOTCS_CANCELED, bytes_transfered, "Syncronization was canceled");
        return IOTCS_TRUE;
    }

    // Check if someone sets cancel state for storage object
    if (storage_object->progress_state == IOTCS_CANCELED) {
        return IOTCS_TRUE;
    }
    if (SCS_IS_SYNCING(storage_object->progress_state)) {
        return IOTCS_FALSE;
    }
    // Check if someone wants to cancel
    (*progress_callback)(uri_object, state, bytes_transfered, fail_reason);

#ifdef IOTCS_VIRTUALIZATION_SUPPORT
    if (storage_object->sync_callback) {
        iotcs_storage_object_sync_event event;
        event.name = storage_object->name;
        event.source = uri_object;
        event.virtual_device = storage_object->device_handle;
        (*(storage_object->sync_callback))(&event);
    }
#endif
    storage_object->content_length = bytes_transfered;

    return IOTCS_FALSE;
}

void cl_storage_progress_cb(struct iotcs_uri_object_t *storage,
            iotcs_storage_dispatcher_progress_state state,
            int64_t bytes_transfered, const char *fail_reason) {
    return;
}

iotcs_result dm_create_uri_object_by_uri(const char* uri, struct iotcs_uri_object_t **object) {
    iotcs_result rv;
    char *name, *endpoint_id;
    int endpoint_id_len = 0;

    if (!g_storage_initialized || !g_storage_container_url) {
        return iotcs_create_external_object(uri, object);
    }
    *object = (struct iotcs_uri_object_t *)util_calloc(1, sizeof(struct iotcs_uri_object_t));
    if (*object == NULL) {
        return IOTCS_RESULT_OUT_OF_MEMORY;
    }
    if (strlen(uri) > strlen(g_storage_container_url) &&
            strncmp(uri, g_storage_container_url, strlen(g_storage_container_url)) == 0) {
        GOTO_ERR_OOM(rv, (*object)->object = (sd_storage_object *)util_calloc(1, sizeof(struct sd_storage_object_t)));
        sd_storage_object *storage = (*object)->object;
        (*object)->type = IOTCS_STORAGE_OBJECT;
        name = uri + strlen(g_storage_container_url) + 1;
        endpoint_id = name;
        endpoint_id_len = 0;
        if (!DISABLE_STORAGE_OBJECT_PREFIX) {
            while (1) {
                if (*name == '0') {
                    name = endpoint_id;
                    endpoint_id = NULL;
                    endpoint_id_len = 0;
                    break;
                }
                if (*name == '/') {
                    name++;
                    break;
                }
                name++;
                endpoint_id_len++;
            }
        }
        if (endpoint_id_len) {
            GOTO_ERR_OOM(rv, storage->endpoint_id = util_safe_strncpy(endpoint_id, endpoint_id_len));
        }
        GOTO_ERR_OOM(rv, storage->name = util_safe_strcpy(name));
        GOTO_ERR_OOM(rv, storage->uri = util_safe_strcpy(uri));
        GOTO_ERR_NO_OK(rv, iotcs_storage_object_fill_file_info(*object));
        storage->progress_state = IOTCS_INITIATED;
        storage->content_length = -1;
        (*object)->ref_cnt = 1;
    } else {
        GOTO_ERR_NO_OK(rv, iotcs_create_external_object(uri, object));
    }

    return IOTCS_RESULT_OK;
error:
    dm_free_uri_object(*object);
    *object = NULL;
    return rv;
}

iotcs_result dm_create_storage_object(const char* endpoint_id, const char* name, const char* content_type,
            struct iotcs_uri_object_t **object) {
    if (!g_storage_initialized) {
        LOG_ERRS("IoTCS Storage is not initialized");
        return IOTCS_RESULT_FAIL;
    }
    iotcs_result rv;
    const char* content_type_cpy = NULL;
    GOTO_ERR_OOM(rv, *object = (struct iotcs_uri_object_t *)util_calloc(1, sizeof(struct iotcs_uri_object_t)));
    GOTO_ERR_OOM(rv, (*object)->object = (sd_storage_object *)util_calloc(1, sizeof(struct sd_storage_object_t)));
    sd_storage_object *storage = (*object)->object;

    if (!content_type) {
        GOTO_ERR_OOM(rv, content_type_cpy = util_safe_strcpy("application/octet-stream"));
    } else {
        GOTO_ERR_OOM(rv, content_type_cpy = util_safe_strcpy(content_type));
    }

    (*object)->ref_cnt = 1;
    (*object)->type = IOTCS_STORAGE_OBJECT;
    storage->name = name;
    storage->endpoint_id = endpoint_id;
    storage->content_type = content_type_cpy;
    storage->content_length = -1;
    storage->progress_state = IOTCS_INITIATED;
    if ((!storage->uri) && g_storage_container_url && name) {
        int expected_size = strlen(g_storage_container_url)+strlen(storage->name)+2;
        if (storage->endpoint_id && !DISABLE_STORAGE_OBJECT_PREFIX) {
            expected_size += strlen(storage->endpoint_id) + 1;
        }
        char *tmp_buffer;
        GOTO_ERR_OOM(rv, tmp_buffer = (char*)util_calloc(expected_size, sizeof(char)));
        if (storage->endpoint_id && !DISABLE_STORAGE_OBJECT_PREFIX) {
            util_safe_snprintf(tmp_buffer, expected_size, "%s/%s/%s", g_storage_container_url, storage->endpoint_id, storage->name);
        } else {
            util_safe_snprintf(tmp_buffer, expected_size, "%s/%s", g_storage_container_url, storage->name);
        }
        storage->uri = tmp_buffer;
    }
    return IOTCS_RESULT_OK;
error:
    if (*object == NULL) {
        util_free((void*)(*object)->object);
    }
    util_free((void*)content_type_cpy);
    util_free((void*)*object);
    *object = NULL;
    return rv;
}

iotcs_result iotcs_create_storage_object_handle(const char* name, const char* content_type,
        iotcs_storage_object_handle *storage_object_handle) {
    iotcs_result rv;
    const char* name_copy = NULL, *endpoint_id_copy = NULL;

    if (!name) {
        return IOTCS_RESULT_INVALID_ARGUMENT;
    }

    DM_LOCK();
    GOTO_ERR_OOM(rv, name_copy = util_safe_strcpy(name));
    GOTO_ERR_OOM(rv, endpoint_id_copy = util_safe_strcpy(tam_get_endpoint_id()));
    GOTO_ERR_NO_OK(rv, dm_create_storage_object(endpoint_id_copy, name_copy, content_type, storage_object_handle));
    DM_UNLOCK();
    return IOTCS_RESULT_OK;
error:
    util_free((void*)name_copy);
    util_free((void*)endpoint_id_copy);
    DM_UNLOCK();
    return rv;
}

void iotcs_free_storage_object_handle(iotcs_storage_object_handle storage_object_handle) {
    iotcs_free_storage_object(storage_object_handle);
}

iotcs_result iotcs_create_storage_object(const char* name,
            const char* content_type, struct iotcs_uri_object_t **storage_object) {
    iotcs_result rv;
    const char* name_copy = NULL, *endpoint_id_copy = NULL;

    if (!name) {
        return IOTCS_RESULT_INVALID_ARGUMENT;
    }

    DM_LOCK();
    GOTO_ERR_OOM(rv, name_copy = util_safe_strcpy(name));
    GOTO_ERR_OOM(rv, endpoint_id_copy = util_safe_strcpy(tam_get_endpoint_id()));
    GOTO_ERR_NO_OK(rv, dm_create_storage_object(endpoint_id_copy, name_copy, content_type, storage_object));
    DM_UNLOCK();
    return IOTCS_RESULT_OK;
error:
    util_free((void*)name_copy);
    util_free((void*)endpoint_id_copy);
    DM_UNLOCK();
    return rv;
}

iotcs_result iotcs_create_storage_object_by_uri(const char* uri, struct iotcs_uri_object_t **storage_object) {
    iotcs_result rv;
    if (!uri) {
        return IOTCS_RESULT_INVALID_ARGUMENT;
    }
    DM_LOCK();
    rv = dm_create_uri_object_by_uri(uri, storage_object);
    // if uri is link to external sources, so user must use iotcs_create_external_object
    if ((rv == IOTCS_RESULT_OK) && IS_EXTERNAL_OBJECT(*storage_object)) {
        iotcs_free_external_object(*storage_object);
        rv = IOTCS_RESULT_INVALID_ARGUMENT;
    }
    DM_UNLOCK();
    return rv;
}

iotcs_storage_object_sync_status iotcs_storage_object_get_sync_status(const struct iotcs_uri_object_t *object) {
    if (IS_STORAGE_OBJECT(object)) {
        sd_storage_object *storage_object = (sd_storage_object *)object->object;
        if (storage_object->progress_state == IOTCS_COMPLETED) {
            return IOTCS_IN_SYNC;
        } else if (storage_object->progress_state == IOTCS_CANCELED ||
                   storage_object->progress_state == IOTCS_FAILED) {
            return IOTCS_SYNC_FAILED;
        } else if (storage_object->progress_state == IOTCS_INITIATED) {
            return IOTCS_NOT_IN_SYNC;
        } else {
            return IOTCS_SYNC_PENDING;
        }
    } else {
        return IOTCS_NOT_IN_SYNC;
    }
}

const char* iotcs_storage_object_get_input_path(const struct iotcs_uri_object_t *object) {
    if (IS_STORAGE_OBJECT(object)) {
        sd_storage_object *storage_object = (sd_storage_object *)object->object;
        return storage_object->input_path;
    }
    return NULL;
}

const char* iotcs_storage_object_get_output_path(const struct iotcs_uri_object_t *object) {
    if (IS_STORAGE_OBJECT(object)) {
        sd_storage_object *storage_object = (sd_storage_object *)object->object;
        return storage_object->output_path;
    }
    return NULL;
}

const char* iotcs_storage_object_get_name(const struct iotcs_uri_object_t *object) {
    if (IS_STORAGE_OBJECT(object)) {
        sd_storage_object *storage_object = (sd_storage_object *)object->object;
        return storage_object->name;
    }
    return NULL;
}

const char* iotcs_storage_object_get_uri(const struct iotcs_uri_object_t *object) {
    if (IS_STORAGE_OBJECT(object)) {
        sd_storage_object *storage_object = (sd_storage_object *)object->object;
        return storage_object->uri;
    }
    return NULL;
}

long iotcs_storage_object_get_length(const struct iotcs_uri_object_t *object) {
    if (IS_STORAGE_OBJECT(object)) {
        sd_storage_object *storage_object = (sd_storage_object *)object->object;
        return storage_object->content_length;
    }
    return 0;
}

const char* iotcs_storage_object_get_type(const struct iotcs_uri_object_t *object) {
    if (IS_STORAGE_OBJECT(object)) {
        sd_storage_object *storage_object = (sd_storage_object *)object->object;
        return storage_object->content_type;
    }
    return NULL;
}

const char* iotcs_storage_object_get_encoding(const struct iotcs_uri_object_t *object) {
    if (IS_STORAGE_OBJECT(object)) {
        sd_storage_object *storage_object = (sd_storage_object *)object->object;
        return storage_object->encoding;
    }
    return NULL;
}

iotcs_date_time iotcs_storage_object_get_date(const struct iotcs_uri_object_t *object) {
    if (IS_STORAGE_OBJECT(object)) {
        sd_storage_object *storage_object = (sd_storage_object *)object->object;
        return storage_object->content_date;
    }
    return 0;
}

iotcs_result iotcs_storage_object_set_input_path(struct iotcs_uri_object_t *object, const char *local_path) {
    iotcs_result rv;
    if (!(IS_STORAGE_OBJECT(object)) || !local_path) {
        return IOTCS_RESULT_INVALID_ARGUMENT;
    }
    DM_LOCK();
    sd_storage_object *storage_object = (sd_storage_object *)object->object;
    if (storage_object->progress_state == IOTCS_QUEUED || storage_object->progress_state == IOTCS_IN_PROGRESS) {
        DM_UNLOCK();
        return IOTCS_RESULT_INVALID_ARGUMENT;
    }
    storage_object->progress_state = IOTCS_INITIATED;
    util_free((void*)(storage_object->input_path));
    GOTO_ERR_OOM(rv, storage_object->input_path = util_safe_strcpy(local_path));
    util_free((void*)(storage_object->output_path));
    storage_object->output_path = NULL;
    DM_UNLOCK();
    return IOTCS_RESULT_OK;
error:
    DM_UNLOCK();
    return rv; 
}

iotcs_result iotcs_storage_object_set_output_path(struct iotcs_uri_object_t *object, const char *output_path) {
    iotcs_result rv;
    if (!(IS_STORAGE_OBJECT(object)) || !output_path) {
        return IOTCS_RESULT_INVALID_ARGUMENT;
    }
    DM_LOCK();
    sd_storage_object *storage_object = (sd_storage_object *)object->object;
    if (storage_object->progress_state == IOTCS_QUEUED || storage_object->progress_state == IOTCS_IN_PROGRESS) {
        DM_UNLOCK();
        return IOTCS_RESULT_INVALID_ARGUMENT;
    }
    storage_object->progress_state = IOTCS_INITIATED;
    util_free((void*)(storage_object->output_path));
    GOTO_ERR_OOM(rv, storage_object->output_path = util_safe_strcpy(output_path));
    util_free((void*)(storage_object->input_path));
    storage_object->input_path = NULL;
    DM_UNLOCK();
    return IOTCS_RESULT_OK;
error:
    DM_UNLOCK();
    return rv;
}

void iotcs_free_storage_object(struct iotcs_uri_object_t *object) {
    if (IS_STORAGE_OBJECT(object)) {
        DM_LOCK();
        dm_free_uri_object(object);
        DM_UNLOCK();
    } else {
        LOG_WARNS("Not a storage object");
    }
}

iotcs_result iotcs_storage_object_set_callback(struct iotcs_uri_object_t *object, iotcs_storage_object_sync_callback callback) {
    if (!(IS_STORAGE_OBJECT(object))) {
        return IOTCS_RESULT_INVALID_ARGUMENT;
    }
    sd_storage_object *storage_object = (sd_storage_object *)object->object;
    if (!storage_object) {
        return IOTCS_RESULT_INVALID_ARGUMENT;
    }
    DM_LOCK();
    storage_object->sync_callback = callback;
    DM_UNLOCK();
    return IOTCS_RESULT_OK;
}

iotcs_result iotcs_storage_object_sync(struct iotcs_uri_object_t *object) {
    if (!(IS_STORAGE_OBJECT(object))) {
        return IOTCS_RESULT_INVALID_ARGUMENT;
    }
    sd_storage_object *storage_object = (sd_storage_object *)object->object;
    iotcs_storage_request *request = (iotcs_storage_request *) util_malloc(sizeof(iotcs_storage_request));
    iotcs_result rv;

    if (storage_object->progress_state == IOTCS_COMPLETED) {
        return IOTCS_RESULT_OK;
    }

    if (!storage_object->input_path && !storage_object->output_path) {
        return IOTCS_RESULT_INVALID_ARGUMENT;
    }

    if (!storage_object->uri && g_storage_container_url) {
        int expected_size = strlen(g_storage_container_url)+strlen(storage_object->name)+2;
        if (storage_object->endpoint_id && !DISABLE_STORAGE_OBJECT_PREFIX) {
            expected_size += strlen(storage_object->endpoint_id) + 1;
        }
        char *tmp_buffer;
        tmp_buffer = (char*)util_calloc(expected_size, sizeof(char));
        if (tmp_buffer) {
            if (storage_object->endpoint_id && !DISABLE_STORAGE_OBJECT_PREFIX) {
                util_safe_snprintf(tmp_buffer, expected_size, "%s/%s/%s", g_storage_container_url, storage_object->endpoint_id, storage_object->name);
            } else {
                util_safe_snprintf(tmp_buffer, expected_size, "%s/%s", g_storage_container_url, storage_object->name);
            }
            storage_object->uri = tmp_buffer;
        } else {
            util_free((void*)request);
            return IOTCS_RESULT_OUT_OF_MEMORY;
        }
        storage_object->uri = tmp_buffer;
    }

    if (storage_object->progress_state == IOTCS_IN_PROGRESS || storage_object->progress_state == IOTCS_QUEUED) {
        return IOTCS_RESULT_OK;
    }
    storage_object->progress_state = IOTCS_QUEUED;
    object->ref_cnt++;

    request->storage = object;
    if (IOTCS_RESULT_OK != (rv = cl_internal_storage_dispatcher_queue_safe(request))) {
        return rv;
    }
    
    return rv;
}

iotcs_result cl_storage_object_sync(struct iotcs_uri_object_t *object) {
    if (!(IS_STORAGE_OBJECT(object))) {
        return IOTCS_RESULT_INVALID_ARGUMENT;
    }
    sd_storage_object *storage_object = (sd_storage_object *)object->object;
    if (storage_object->progress_state == IOTCS_COMPLETED) {
        return IOTCS_RESULT_OK;
    }

    if (!storage_object->input_path && !storage_object->output_path) {
        return IOTCS_RESULT_INVALID_ARGUMENT;
    }
    iotcs_storage_request request;
    request.storage = object;
    
    if (storage_object->input_path) {
        return cl_internal_storage_file_upload(&request);
    }
    return cl_internal_storage_file_download(&request);
}
