/*
 * 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.h"
/* include iot cs device model APIs */
#include "iotcs_virtual_device.h"
/* include methods for device client*/
#include "iotcs_device.h"
#include "iotcs_storage_object.h"

#define EXIT_FAILURE 1
#define EXIT_SUCCESS 0

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

/* means that iotcs_init() method was successful*/
static iotcs_bool is_initialized = IOTCS_FALSE;
/* motion camera device model handle */
static iotcs_device_model_handle camera_dm_handle = NULL;
/* motion camera device handle */
static iotcs_virtual_device_handle device_handle = NULL;

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",
};

static void finalize_library() {
    /* free device handle */
    if (device_handle) {
        iotcs_free_virtual_device_handle(device_handle);
        device_handle = NULL;
    }

    /* free device model handle */
    if (camera_dm_handle) {
        iotcs_free_device_model(camera_dm_handle);
        camera_dm_handle = NULL;
    }

    /* 
     * 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);
}

static void sync_callback(iotcs_storage_object_sync_event *event) {
    printf("\n%s : onSync : %s = %d\n", iotcs_get_endpoint_id(), iotcs_storage_object_get_name(event->source), iotcs_storage_object_get_sync_status(event->source));
}

/* error data callback */
static void data_on_error(iotcs_virtual_device_error_event *event) {
    printf("Run on ERROR callback for data message with reason %s\n", event->message);
}

const char *endpoint_id;
/* record video device callback */
static void record_video_cb(iotcs_virtual_device_handle device_handle, iotcs_typed_value argument) {
    printf("\n%s : Call : record : %d\n", endpoint_id, argument.value.int_value);
    // round to nearest increment of 15
    unsigned int duration = (unsigned int)(argument.value.int_value + 14) / 15 * 15;
    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 sduration[32];
    printf("\nUpload video\n");

    char storage_object_name [128];
    snprintf(storage_object_name, 128, "motion_activated_camera_%s", video + strlen("videos/"));
    snprintf(sduration, 32, "%d", (int)duration);
    iotcs_storage_object_handle storage_object_handle_camera = NULL;
    printf("\nCreate storage object %s\n", storage_object_name);
    if (iotcs_create_storage_object_handle(storage_object_name, "video/mp4", &storage_object_handle_camera) != IOTCS_RESULT_OK) {
        error_and_exit("iotcs_create_storage_object_handle method failed\n");
    }
    if (iotcs_storage_object_set_callback(storage_object_handle_camera, sync_callback) != IOTCS_RESULT_OK) {
        error_and_exit("iotcs_storage_object_set_callback method failed\n");
    }
    printf("\nSet input file %s\n", video);
    if (iotcs_storage_object_set_input_path(storage_object_handle_camera, video) != IOTCS_RESULT_OK) {
        error_and_exit("iotcs_storage_object_set_input_path method failed\n");
    }
    if (iotcs_storage_object_set_ttl(storage_object_handle_camera, 60 * 60 * 24 * 3) != IOTCS_RESULT_OK) {
        error_and_exit("iotcs_storage_object_set_ttl method failed\n");
    }
    if (iotcs_storage_object_set_custom_metadata(storage_object_handle_camera, "duration", sduration) != IOTCS_RESULT_OK) {
        error_and_exit("iotcs_storage_object_set_custom_metadata method failed\n");
    }


    iotcs_virtual_device_start_update(device_handle);
    printf("Start update device handle\n");

    iotcs_data_handle camera_data = NULL;
    /* get data handle */
    if (iotcs_virtual_device_create_data_handle(device_handle, "urn:com:oracle:iot:device:motion_activated_camera:recording", &camera_data) != IOTCS_RESULT_OK) {
        error_and_exit("iotcs_virtual_device_create_data_handle method failed\n");
    }
    printf("Start update device handle1\n");
    if (iotcs_data_set_storage_object(camera_data, "video", storage_object_handle_camera) != IOTCS_RESULT_OK) {
        error_and_exit("iotcs_data_set_storage_object method failed\n");
    }
    if (iotcs_data_set_date_time(camera_data, "startTime", start_time) != IOTCS_RESULT_OK) {
        error_and_exit("iotcs_data_set_date_time method failed\n");
    }
    if (iotcs_data_set_integer(camera_data, "duration", duration) != IOTCS_RESULT_OK) {
        error_and_exit("iotcs_data_set_integer method failed\n");
    }
    printf("Finish update device handle\n");
    iotcs_virtual_device_finish_update(device_handle);

    iotcs_data_set_on_error(camera_data, data_on_error);
    /* async data send */
    rv = iotcs_data_submit(camera_data);
    if (rv != IOTCS_RESULT_OK) {
        printf("iotcs_data_submit method failed\n");
    }
}

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);

    /* 
     * 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");
        }
    }
    /* get device model handle */
    if (iotcs_get_device_model("urn:com:oracle:iot:device:motion_activated_camera", &camera_dm_handle) != IOTCS_RESULT_OK) {
        error_and_exit("iotcs_get_device_model method failed\n");
    }

    /* get device handle */
    if (iotcs_create_virtual_device_handle(iotcs_get_endpoint_id(), camera_dm_handle, &device_handle) != IOTCS_RESULT_OK) {
        error_and_exit("iotcs_get_device_handle method failed\n");
    }

    sleep(5);
    iotcs_virtual_device_set_callback(device_handle, "record", record_video_cb);
    endpoint_id = iotcs_get_endpoint_id();
    int i = -1;
    char storage_object_name [128];
    while (keep_running) {
        i = (i + 1) % 12;
        iotcs_storage_object_handle storage_object_handle_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_handle(storage_object_name, "image/jpeg", &storage_object_handle_camera) != IOTCS_RESULT_OK) {
            error_and_exit("iotcs_get_storage_object\n");
        }
        if (iotcs_storage_object_set_callback(storage_object_handle_camera, sync_callback) != IOTCS_RESULT_OK) {
            error_and_exit("iotcs_storage_object_set_callback\n");
        }
        printf("\nSet input file %s\n", image_files[i]);
        if (iotcs_storage_object_set_input_path(storage_object_handle_camera, image_files[i]) != IOTCS_RESULT_OK) {
            error_and_exit("iotcs_storage_object_set_input_path\n");
        }
        if (iotcs_storage_object_set_ttl(storage_object_handle_camera, 60 * 60 * 24) != IOTCS_RESULT_OK) {
            error_and_exit("iotcs_storage_object_set_ttl method failed\n");
        }

        printf("Start update device handle\n");
        iotcs_virtual_device_start_update(device_handle);

        if (iotcs_virtual_device_set_storage_object(device_handle, "image", storage_object_handle_camera) != IOTCS_RESULT_OK) {
            error_and_exit("iotcs_virtual_device_set_storage_object method failed\n");
        }
        if (iotcs_virtual_device_set_date_time(device_handle, "imageTime", ((int64_t) time(NULL)) * 1000) != IOTCS_RESULT_OK) {
            error_and_exit("iotcs_virtual_device_set_date_time method failed\n");
        }
        /* Resume background network notifications of changed attribute values and 
         * send all changes to server */
        printf("Finish update device handle\n");
        iotcs_virtual_device_finish_update(device_handle);
        printf("Free storage object handle\n");
        iotcs_free_storage_object_handle(storage_object_handle_camera);
        storage_object_handle_camera = NULL;
        printf("Sleep 30 seconds!\nPress Ctrl + C to exit\n");
        sleep(30);
    }

    finalize_library();
    return EXIT_SUCCESS;
}
