/*
 * 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 <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <signal.h>
#include <string.h>
#include <unistd.h>
#include <math.h>

/* include methods for device client*/
#include "iotcs_device.h"
/* include send and receive methods */
/* include methods for request dispatcher for handling requests */
#include "advanced/iotcs_messaging.h"
#include "advanced/iotcs_storage_object.h"

#define ARRAY_LEN(X) (sizeof(X)/sizeof(X[0]))
#define EXIT_FAILURE 1
#define EXIT_SUCCESS 0
#define FAIL_REQUEST_STATUS 400
#define OK_REQUEST_STATUS 200

/* used as sending loop condition */
static volatile int keep_running = 1;
/* handler for interrupt signal */
static void int_handler() {
    keep_running = 0;
}

const char *endpoint_id;
/* means that iotcs_init() method was successful*/
static iotcs_bool is_initialized = IOTCS_FALSE;

static char *image_files[] = {
    "images/Frame-00.jpeg",
    "images/Frame-01.jpeg",
    "images/Frame-02.jpeg",
    "images/Frame-03.jpeg",
    "images/Frame-04.jpeg",
    "images/Frame-05.jpeg",
    "images/Frame-06.jpeg",
    "images/Frame-07.jpeg",
    "images/Frame-08.jpeg",
    "images/Frame-09.jpeg",
    "images/Frame-10.jpeg",
    "images/Frame-11.jpeg"
};

static char *video_files[] = {
    "videos/stopwatch_015s.mp4",
    "videos/stopwatch_030s.mp4",
    "videos/stopwatch_045s.mp4",
    "videos/stopwatch_060s.mp4",
    "videos/stopwatch_075s.mp4",
    "videos/stopwatch_090s.mp4",
    "videos/stopwatch_105s.mp4",
    "videos/stopwatch_120s.mp4",
    "videos/stopwatch_135s.mp4",
    "videos/stopwatch_150s.mp4",
    "videos/stopwatch_165s.mp4",
    "videos/stopwatch_180s.mp4",
};

/*
 *      Data messages
 */

/* base for data messages */
static iotcs_message_base data_msg_base = {
    .type = IOTCS_MESSAGE_DATA,
    .reliability = IOTCS_MESSAGE_RELIABILITY_BEST_EFFORT,
    .priority = IOTCS_MESSAGE_PRIORITY_HIGH,
    .destination = "iotserver",
};

static iotcs_data_message_base data_base_attributes = {
    .format = "urn:com:oracle:iot:device:motion_activated_camera:attributes"
};
static iotcs_data_message_base data_base_recording = {
    .format = "urn:com:oracle:iot:device:motion_activated_camera:recording"
};

/* action handler for temperature_sensor/actions/power */
static int duration_handler(const char* body, unsigned int* value) {
    // {"value":value} or {"attributeName":value}
    char* data = strchr(body, ':');
    if (data == NULL) {
        return FAIL_REQUEST_STATUS;
    }
    // remove ":"
    data++;
    *value = atoi(data);
    return OK_REQUEST_STATUS;
}

/* my resource structure */
typedef struct {
    iotcs_resource_message_base desc;
    iotcs_resource_handler handler;
    void* args;
    int is_action;
} my_resource;

static void finalize_library() {
    /*
     * Calling finalization of the library ensures communications channels are closed,
     * previously allocated temporary resources are released.
     */
    if (is_initialized) {
        iotcs_finalize();
        is_initialized = IOTCS_FALSE;
    }
}

/* print error message and terminate the program execution */
static void error_and_exit(const char* message) {
    fprintf(stderr, "ERROR occurred finalize library\n");
    finalize_library();
    fprintf(stderr, "Error occurred: %s\n", message);
    exit(EXIT_FAILURE);
}

/* send data message */
static iotcs_result send_data_message_record(iotcs_storage_object *storage_object, int64_t date_time, int duration) {
    iotcs_result rv;
    /* data message items description mentioned in the device model */
    iotcs_data_item_desc desc[] = {
        { .type = IOTCS_VALUE_TYPE_URI, .key = "video"},
        { .type = IOTCS_VALUE_TYPE_DATE_TIME, .key = "startTime"},
        { .type = IOTCS_VALUE_TYPE_INT, .key = "duration"},
        { .type = IOTCS_VALUE_TYPE_NONE, .key = NULL}
    };

    /* data message items value */
    iotcs_value values[3];
    values[0].uri_object = storage_object;
    values[1].date_time_value = date_time;
    values[2].int_value = duration;

    data_msg_base.source = endpoint_id;
    /* making data message */
    iotcs_message iotcs_data_message = {
        // message id will be generated
        .id = "",
        // message type, destination, priority and reliability
        .base = &data_msg_base,
        // message format
        .u.data.base = &data_base_recording,
        // message items description
        .u.data.items_desc = desc,
        // message items value
        .u.data.items_value = values
    };

    /* send data message */
    if (IOTCS_RESULT_OK != (rv = iotcs_message_dispatcher_queue(&iotcs_data_message))) {
        error_and_exit("send_data_message: iotcs_message_dispatcher_queue failed");
    }

    return rv;
}

/* error callback for async message dispatcher */
static void on_error_cb(iotcs_message *message, iotcs_result result,
        const char *fail_reason) {
    printf("Run on ERROR callback: result = %d with reason %s\n", result, fail_reason);
}

/* send data message */
static iotcs_result send_data_message_image(iotcs_storage_object *storage_object) {
    iotcs_result rv;
    /* data message items description mentioned in the device model */
    static iotcs_data_item_desc desc[] = {
        { .type = IOTCS_VALUE_TYPE_DATE_TIME, .key = "imageTime"},
        { .type = IOTCS_VALUE_TYPE_URI, .key = "image"},
        { .type = IOTCS_VALUE_TYPE_NONE, .key = NULL}
    };

    /* data message items value */
    static iotcs_value values[2];
    values[0].date_time_value = ((int64_t) time(NULL)) * 1000;
    values[1].uri_object = storage_object;

    data_msg_base.source = endpoint_id;
    /* making data message */
    iotcs_message iotcs_data_message = {
        // message id will be generated
        .id = "",
        // message type, destination, priority and reliability
        .base = &data_msg_base,
        // message format
        .u.data.base = &data_base_attributes,
        // message items description
        .u.data.items_desc = desc,
        // message items value
        .u.data.items_value = values
    };

    /* send data message */
    if (IOTCS_RESULT_OK != (rv = iotcs_message_dispatcher_queue(&iotcs_data_message))) {
        printf("send_data_message: iotcs_message_dispatcher_queue failed %d", rv);
        error_and_exit("send_data_message: iotcs_message_dispatcher_queue failed");
    }

    return rv;
}

static void release_response_message_from_static(iotcs_message* message) {
    printf("Release response message with status code = %d\n", message->u.response.status_code);
}

static void release_resource_message_from_static(iotcs_message* message) {
    printf("Release resource message %s %s\n", message->u.resource.base->path, message->u.resource.base->name);
}

/* my resource handler callback function */
typedef int (*my_resource_handler)(const char* body, unsigned int* value);
/* record video device callback */

static void record_video_action_handler(iotcs_request_message* request, void* args, iotcs_message* response) {
    // round to nearest increment of 15
    unsigned int duration = 0;
    /* by default response get "priority" and "reliability" of request */
    /*
     * args contain my action handler for recording video that registered as resource argument.
     * record_video_action_handler.
     */
    printf("\nAction handler %s\n", request->body);
    response->u.response.status_code = ((my_resource_handler) args)(request->body, &duration);
    printf("\n%s : Call : record : %d\n", endpoint_id, duration);
    int64_t start_time = ((int64_t) time(NULL)) * 1000;
    iotcs_result rv;
    if (!duration)
        return;
    // assumes videos.length > 0 and videos are 15 second increments.
    // Convert duration to an index
    char* video = video_files[(duration / 15 - 1) < 11 ? duration / 15 - 1 : 11];

    char storage_object_name [128];
    snprintf(storage_object_name, 128, "motion_activated_camera_%s", video + strlen("videos/"));
    iotcs_storage_object *storage_object_camera = NULL;
    printf("\nCreate storage object %s\n", storage_object_name);
    if (iotcs_create_storage_object(storage_object_name, "video/mp4", &storage_object_camera) != IOTCS_RESULT_OK) {
        error_and_exit("iotcs_create_storage_object method failed\n");
    }
    printf("\nSet input file %s\n", video);
    if (iotcs_storage_object_set_input_path(storage_object_camera, video) != IOTCS_RESULT_OK) {
        error_and_exit("iotcs_storage_object_set_input_path method failed\n");
    }
    if (iotcs_storage_object_sync(storage_object_camera) != IOTCS_RESULT_OK) {
        error_and_exit("iotcs_storage_object_sync method failed\n");
    }

    /* async data send */
    send_data_message_record(storage_object_camera, start_time, duration);
    response->user_data = release_response_message_from_static;
}

static my_resource device_resources[] = {
    {
        .desc =
        {
            .path = "deviceModels/urn:com:oracle:iot:device:motion_activated_camera/actions/record",
            .name = "record",
            .description = "recording action"
        },
        .handler = record_video_action_handler,
        .args = duration_handler,
        .is_action = 1
    },
};

static iotcs_message_base resource_msg_base = {
    .type = IOTCS_MESSAGE_RESOURCE,
    .priority = IOTCS_MESSAGE_PRIORITY_HIGHEST,
    .reliability = IOTCS_MESSAGE_RELIABILITY_GUARANTED_DELIVERY
};

/* register request handler for given resource/action by index and endpoint */
static iotcs_result register_resources(void) {
    iotcs_result status;
    int i;
    for (i = 0; i < ARRAY_LEN(device_resources); i++) {
        resource_msg_base.source = endpoint_id;
        iotcs_message message = {
            .base = &resource_msg_base,
            .user_data = release_resource_message_from_static,
            .u.resource.report_type = IOTCS_RESOURCE_MESSAGE_UPDATE,
            .u.resource.endpointName = endpoint_id,
            .u.resource.base = &device_resources[i].desc,
            .u.resource.resource_len = 1
        };

        if (!device_resources[i].is_action) {
            /* put message in async message dispatcher send queue */
            status = iotcs_message_dispatcher_queue(&message);

            if (status != IOTCS_RESULT_OK) {
                printf("register_resources: iotcs_message_dispatcher_queue failed\n");
                return status;
            }
        }

        /* register handler for given resource path and endpoint */
        status = iotcs_register_request_handler(endpoint_id,
                device_resources[i].desc.path, device_resources[i].handler, device_resources[i].args);
        printf("Register resources with path %s done\n", device_resources[i].desc.path);
    }
    return status;
}

/* unregister request handler for given resource/action by index and endpoint */
static iotcs_result unregister_resources(void) {
    iotcs_result status = IOTCS_RESULT_OK;
    int i;
    for (i = 0; i < ARRAY_LEN(device_resources); i++) {
        iotcs_message message = {
            .base = &resource_msg_base,
            .user_data = release_resource_message_from_static,
            .u.resource.report_type = IOTCS_RESOURCE_MESSAGE_UPDATE,
            .u.resource.endpointName = endpoint_id,
            .u.resource.base = &device_resources[i].desc,
            .u.resource.resource_len = 1
        };

        if (!device_resources[i].is_action) {
            /* put message in async message dispatcher send queue */
            status = iotcs_message_dispatcher_queue(&message);

            if (status != IOTCS_RESULT_OK) {
                printf("unregister_resources: iotcs_message_dispatcher_queue failed\n");
            }
        }

        /* register handler for given resource path and endpoint */
        status |= iotcs_unregister_request_handler(endpoint_id,
                device_resources[i].desc.path);
    }
    return status;
}

static void progress_callback(iotcs_storage_object *storage_object,
            iotcs_storage_dispatcher_progress_state progress_state,
            int64_t bytes_transfered, const char *fail_reason) {
    switch (progress_state) {
        case IOTCS_IN_PROGRESS:
            if(iotcs_storage_object_get_input_path(storage_object)) {
                printf("*** scs %s file upload progress = %" PRId64 " bytes***\n", iotcs_storage_object_get_name(storage_object), bytes_transfered);
            } else {
                printf("*** scs %s file download progress = %" PRId64 " bytes***\n", iotcs_storage_object_get_name(storage_object), bytes_transfered);
            }
            break;
        case IOTCS_CANCELED:
            if(iotcs_storage_object_get_input_path(storage_object)) {
                printf("*** scs %s file upload was canceled. Total bytes: %" PRId64 " bytes***\n", iotcs_storage_object_get_name(storage_object), bytes_transfered);
            } else {
                printf("*** scs %s file download was canceled. Total bytes: %" PRId64 " bytes***\n", iotcs_storage_object_get_name(storage_object), bytes_transfered);
            }
            break;
        case IOTCS_COMPLETED:
            printf("*** scs operation for %s was succesful. Total bytes: %" PRId64 "***\n",iotcs_storage_object_get_name(storage_object), bytes_transfered);
            break;
        case IOTCS_FAILED:
        default:
            printf("*** scs operation failed***\n");
            if(fail_reason) {
                printf("*** Fail reason: %s", fail_reason);
            }
            break;
    }
}


int main(int argc, char** argv) {
    /* This is the URN of our motion camera device model. */
    const char* device_urns[] = {
        "urn:com:oracle:iot:device:motion_activated_camera",
        NULL
    };

    srand(time(NULL));

    if (argc < 3) {
        error_and_exit("Too few parameters.\n"
                "\nUsage:"
                "\n\tmotion_activated_camera_sample.out <PATH> <PASSWORD> <FILE NAME>"
                "\n\t<PATH> is a path to trusted assets store."
                "\n\t<PASSWORD> is a password for trusted assets store."
                "\n\tPlace videos/ and images/ directory with sample binary file"
                "\n\tNote: IOTCS_OS_NAME and IOTCS_OS_VERSION must be setted before using");
    }

    const char* ts_path = argv[1];
    const char* ts_password = argv[2];

    /*
     * Initialize the library before any other calls.
     * Initiate all subsystems like ssl, TAM, request dispatcher,
     * async message dispatcher, etc which needed for correct library work.
     */
    if (iotcs_init(ts_path, ts_password) != IOTCS_RESULT_OK) {
        error_and_exit("Initialization failed");
    }
    /* Marks that iotcs_finalize() method should be called before exit*/
    is_initialized = IOTCS_TRUE;
    /* Set handler for interrupt signal */
    signal(SIGINT, int_handler);
    endpoint_id = iotcs_get_endpoint_id();

    /*
     * Activate the device, if it's not already activated.
     * Always check if the device is activated before calling activate.
     * The device model URN is passed into the activate call to tell
     * the server the device model(s) that are supported by this
     * directly connected device
     */
    if (!iotcs_is_activated()) {
        if (iotcs_activate(device_urns) != IOTCS_RESULT_OK) {
            error_and_exit("Sending activation request failed");
        }
    }

    /*
     * Register custom request handlers for resources.
     * Resource map to the device model fields entry.
     */

    if (register_resources() != IOTCS_RESULT_OK) {
        error_and_exit("Resources registration failed");
    }
    iotcs_storage_dispatcher_set_callback(progress_callback);
    /* set error callback for async message dispatcher */
    iotcs_message_dispatcher_set_error_callback(on_error_cb);

    sleep(5);
    int i = -1;
    char storage_object_name [128];
    while (keep_running) {
        i = (i + 1) % 12;
        iotcs_storage_object *storage_object_camera = NULL;
        snprintf(storage_object_name, 128, "motion_activated_camera_%s", image_files[i] +  + strlen("images/"));
        printf("\nCreate Storage object %s\n", storage_object_name);
        if (iotcs_create_storage_object(storage_object_name, "image/jpeg", &storage_object_camera) != IOTCS_RESULT_OK) {
            error_and_exit("iotcs_get_storage_object method failed\n");
        }
        printf("\nSet input file %s\n", image_files[i]);
        if (iotcs_storage_object_set_input_path(storage_object_camera, image_files[i]) != IOTCS_RESULT_OK) {
            error_and_exit("iotcs_storage_object_set_input_path method failed\n");
        }
        if (iotcs_storage_object_sync(storage_object_camera) != IOTCS_RESULT_OK) {
            error_and_exit("iotcs_storage_object_sync method failed\n");
        }
        send_data_message_image(storage_object_camera);
        storage_object_camera = NULL;
        printf("Sleep 30 seconds!\nPress Ctrl + C to exit\n");
        sleep(30);
    }

    finalize_library();
    return EXIT_SUCCESS;
}
