/*
 * 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.
 */

/*
 * An example of a directly connected device which is capable of communicating 
 * directly with Oracle IoT Cloud Service. This sample illustrates C code 
 * for sending data to the cloud service, receiving data from the cloud 
 * service and does not fully explore the Client Library API.
 * 
 * The sample uses the messaging API without the virtualization API. 
 * It presents a simple humidity sensor device.
 * 
 * Device implements a device models. A device model is a set of related 
 * attributes, actions, and message formats that can be represented in 
 * a real device. For this example the "Humidity Sensor" device model is used. 
 * This device model must be uploaded to the server before running this example.
 */

#include <signal.h>
#include <stdio.h>
#include <stdlib.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"

#define ARRAY_LEN(X) (sizeof(X)/sizeof(X[0]))
#define MIN(a,b) (((a)<(b))?(a):(b))
#define MAX(a,b) (((a)>(b))?(a):(b))
#define FAIL_REQUEST_STATUS 400
#define OK_REQUEST_STATUS 200

/*
 *                           Constants & Variables
 */

/* used for humidity sensor work simulation */

/* The probability that the threshold will be exceeded equals PROBABILITY */
#define PROBABILITY 10
/* The max range for humidity value. The default threshold is 80. Assume this is the value when starting. */
#define HUMIDITY_VMAX 100
/* The min range for humidity value */
#define HUMIDITY_VMIN  0
/* The default value is threshold*/
#define DEFAULT_MAX_THRESHOLD 80
/* Used to generate fluctuation around the 'set point' */
static int humidity_angle = 0;
/* Current humidity sensor value */
static int current_humidity_sensor_value = HUMIDITY_VMAX / 2;
/* Current humidity sensor max threshold */
static int current_humidity_sensor_max_threshold = DEFAULT_MAX_THRESHOLD;
/* Previous humidity sensor max threshold */
static int previous_sensor_max_threshold = DEFAULT_MAX_THRESHOLD;
/* means that iotcs_init() method was successful*/
static iotcs_bool is_initialized = IOTCS_FALSE;
/* means that iotcs_request_dispatcher_init() method was successful*/
static iotcs_bool is_rd_initialized = IOTCS_FALSE;

/* other variables */

/* used as sending loop condition */
static volatile int keep_running = 1;

/*
 *                         Data & Alert 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 = {
    .format = "urn:com:oracle:iot:device:humidity_sensor:attributes"
};

static iotcs_alert_message_base alert_base = {
    .severity_level = IOTCS_MESSAGE_SEVERITY_SIGNIFICANT,
    .format = "urn:com:oracle:iot:device:humidity_sensor:too_humid",
    .description = "Sample alert when humidity reaches the maximum humidity threshold"
};

/* base for alert messages */
static iotcs_message_base alert_msg_base = {
    .type = IOTCS_MESSAGE_ALERT,
    .reliability = IOTCS_MESSAGE_RELIABILITY_BEST_EFFORT,
    .priority = IOTCS_MESSAGE_PRIORITY_HIGHEST,
    .destination = "iotserver",
};

/*
 *                             Resources
 */

/* resources description based on a device model */
static iotcs_resource_message_base device_resources[] = {
    {
        .path = "deviceModels/urn:com:oracle:iot:device:humidity_sensor/attributes",
        .name = "maxThreshold",
        .methods = IOTCS_REQUEST_METHOD_GET | IOTCS_REQUEST_METHOD_POST | IOTCS_REQUEST_METHOD_PUT,
        .description = "maxThreshold"
    }
};

/* my resource handler callback function */
typedef int (*my_resource_handler)(const char* body);

int maxThreshold_handler(const char* body) {
    // {"value":value} or {"maxThreshold":value}
    char* data = strchr(body, ':');
    if (data == NULL) {
        return FAIL_REQUEST_STATUS;
    }
    previous_sensor_max_threshold = current_humidity_sensor_max_threshold;
    // remove ":"
    data++;
    current_humidity_sensor_max_threshold = atoi(data);
    return OK_REQUEST_STATUS;
}

/* used for resources handling */
static void device_resource_handler(iotcs_request_message* request, void* args, iotcs_message* response) {
    /* by default response get "priority" and "reliability" of request */
    /* 
     * args contain my resource handler maxThreshold_handler for humidity device 
     * that registered as resource argument 
     */
    response->u.response.status_code = ((my_resource_handler) args)(request->body);
}

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 resources */
static iotcs_result register_resources(void) {
    iotcs_result status;
    int i;
    /* get endpoint of current directly connected device from TAM */
    const char *endpoint_id = iotcs_get_endpoint_id();

    /* generate message for resource */
    iotcs_message message = {
        .base = &resource_msg_base,
        .u.resource.base = device_resources,
        .u.resource.resource_len = ARRAY_LEN(device_resources),
        // endpoint which supports resource
        .u.resource.endpointName = endpoint_id,
        // register/update resource
        .u.resource.report_type = IOTCS_RESOURCE_MESSAGE_UPDATE
    };

    /* Send resource message to the server */
    status = iotcs_send(&message, 1);

    if (status != IOTCS_RESULT_OK) {
        printf("register_resources: iotcs_send failed with code: %d\n", status);
        return status;
    }

    /* register handler for given resource path and endpoint */
    for (i = 0; i < ARRAY_LEN(device_resources); i++) {
        status |= iotcs_register_request_handler(endpoint_id, device_resources[i].path,
                device_resource_handler, maxThreshold_handler);
    }

    return status;
}

/* Un-register request handler for resources */
static iotcs_result unregister_resources(void) {
    iotcs_result status;
    int i;
    const char *endpoint_id = iotcs_get_endpoint_id();

    iotcs_message message = {
        .base = &resource_msg_base,
        .u.resource.base = device_resources,
        .u.resource.resource_len = ARRAY_LEN(device_resources),
        .u.resource.endpointName = endpoint_id,
        // delete resource
        .u.resource.report_type = IOTCS_RESOURCE_MESSAGE_DELETE
    };

    status = iotcs_send(&message, 1);

    if (status != IOTCS_RESULT_OK) {
        printf("unregister_resources: iotcs_send failed with code: %d\n", status);
    }

    /* un-register handler for given resource path and endpoint */
    for (i = 0; i < ARRAY_LEN(device_resources); ++i) {
        status |= iotcs_unregister_request_handler(endpoint_id, device_resources[i].path);
    }

    return status;
}

/*
 *                              Functions
 */

static void finalize_library() {
    /* Finalize the request dispatcher to clean up before end program */
    if (is_rd_initialized) {
        iotcs_request_dispatcher_finalize();
        is_rd_initialized = IOTCS_FALSE;
    }

    /* 
     * 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(const char* message) {
    printf("Error occurred: %s\n", message);
    finalize_library();
    exit(EXIT_FAILURE);
}

/* handler for interrupt signal */
static void int_handler() {
    keep_running = 0;
}

/* get random number between min and max values */
static int randomr(unsigned int min, unsigned int max) {
    return (rand() % max) +min;
}

/* a simple implementation of the humidity sensor work */
static void sensor_work() {
    int random = randomr(0, 100);
    /* this variable equals 1 with probability = PROBABILITY.It's simulate alert */
    const int exceed = (random < PROBABILITY / 2) || (random >= 100 - PROBABILITY / 2);

    int add_value = 0;
    if (exceed) {
        add_value = current_humidity_sensor_max_threshold - current_humidity_sensor_max_threshold / 2 + 1;
        printf("Humidity sensor: Alert\n");
    }

    /* 
     * get new sensor value. 
     * If exceed threshold (alert) value will be bigger then max threshold
     */
    current_humidity_sensor_value = MAX(HUMIDITY_VMIN, MIN(HUMIDITY_VMAX,
            (current_humidity_sensor_max_threshold / 2) + add_value + fabs(10 * sin(humidity_angle))));
    humidity_angle = (humidity_angle + 20) % 360;
}

/* send data message */
static iotcs_result send_data_message() {
    iotcs_result rv;
    /* data message items description mentioned in the device model
     * maxThreshold will be added if it is sent */
    iotcs_data_item_desc desc[] = {
        { .type = IOTCS_VALUE_TYPE_INT, .key = "humidity"},
        { .type = IOTCS_VALUE_TYPE_NONE, .key = NULL},
        { .type = IOTCS_VALUE_TYPE_NONE, .key = NULL}
    };

    /* data message items value
     * If maxThreshold is not sent, values[1] will be ignored. */
    iotcs_value values[2];
    values[0].int_value = current_humidity_sensor_value;

    /* Don't send the threshold value unless it has changed 
     * The first time through they will be the same, no need
     * to send the default value */
    if (current_humidity_sensor_max_threshold != 
                previous_sensor_max_threshold) {
        desc[1].type = IOTCS_VALUE_TYPE_INT;
        desc[1].key = "maxThreshold";
        values[1].int_value = current_humidity_sensor_max_threshold;
        /* set the previous value to the current value */
        previous_sensor_max_threshold = current_humidity_sensor_max_threshold;
    }

    /* 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,
        // 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_send(&iotcs_data_message, 1))) {
        error("send_data_message: iotcs_send failed");
    }

    return rv;
}

/* send alert message */
static iotcs_result send_alert_message() {
    iotcs_result rv;
    iotcs_data_item_desc desc[] = {
        { .type = IOTCS_VALUE_TYPE_INT, .key = "humidity"},
        { .type = IOTCS_VALUE_TYPE_NONE, .key = NULL}
    };

    iotcs_value values[1];

    values[0].int_value = current_humidity_sensor_value;

    iotcs_message alert_message = {
        .id = "",
        .base = &alert_msg_base,
        .u.alert.base = &alert_base,
        .u.alert.items_desc = desc,
        .u.alert.items_value = values
    };

    if (IOTCS_RESULT_OK != (rv = iotcs_send(&alert_message, 1))) {
        error("send_alert_message: iotcs_send failed");
    }

    return rv;
}

int main(int argc, char** argv) {
    iotcs_request_message *request_from_server;
    iotcs_message response;
    
    /* This is the URN of our humidity device model. */
    const char* device_urns[] = {
        "urn:com:oracle:iot:device:humidity_sensor",
        NULL
    };

    if (argc < 3) {
        error("Too few parameters.\n"
                "\nUsage:"
                "\n\tdirectly_connected_device.out <PATH> <PASSWORD>"
                "\n\t<PATH> is a path to trusted assets store."
                "\n\t<PASSWORD> is a password for trusted assets store."
                "\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, etc 
     * which needed for correct library work. 
     */
    if (iotcs_init(ts_path, ts_password) != IOTCS_RESULT_OK) {
        error("Initialization failed");
    }

    /* Marks that iotcs_finalize() method should be called before exit*/
    is_initialized = IOTCS_TRUE;

    /* 
     * 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("Sending activation request failed");
        }
    }

    /* 
     * Initialize the request dispatcher.
     * It's needed for handling requests from server and generating responses.
     * Custom resource handlers can be register after request dispatcher initialization.
     */
    if (iotcs_request_dispatcher_init() != IOTCS_RESULT_OK) {
        error("Request dispatcher initialization dispatcher failed");
    }

    /* Marks that iotcs_request_dispatcher_finalize() method should be called before exit*/
    is_rd_initialized = IOTCS_TRUE;

    /* 
     * Register custom request handlers for resources.
     * Resource map to the device model fields entry. For example,
     * register handlers to get humidity_sensor/attributes/humidity resource and
     * to get and change humidity_sensor/attributes/maxThreshold resource
     */
    if (register_resources() == IOTCS_RESULT_OK) {
        printf("Resource registration succeeded!\n");
    } else {
        error("Resource registration failed!\n");
    }

    /* Set handler for interrupt signal */
    signal(SIGINT, int_handler);

    /* 
     * Emulate device work. 
     * Sensor changed values, then device send data message with new values 
     * to the server. If humidity sensor values is out of threshold then 
     * send alert message.
     * Handle incoming requests from the server using request dispatcher.
     * Request dispatcher generate response. Then response have to be sent. 
     */
    while (keep_running) {
        /* Emulate humidity sensor work */
        sensor_work();
        /* Send current sensor values */
        send_data_message();
        /* Send alert if current value more than max threshold */
        if (current_humidity_sensor_value >= current_humidity_sensor_max_threshold) {
            send_alert_message();
        }
        /* Process all pending requests */
        while (NULL != (request_from_server = iotcs_receive(5000))) {
            printf("Request from server was gotten!\n");
            /* 
             * Match the request to a registered handler and invoke the handler.
             * Handler will generate response.
             */
            iotcs_request_dispatcher_dispatch(request_from_server, &response);
            /* Send generated response to the server*/
            if (iotcs_send(&response, 1) != IOTCS_RESULT_OK) {
                error("Response sending has failed\n");
            } else {
                printf("Response has been sent!\n");
            }
        }
    }

    /* Clean up */
    /* Un-register resources handler to clean up before end program */
    if (unregister_resources() == IOTCS_RESULT_OK) {
        printf("Resource un-registration succeeded!\n");
    } else {
        error("Resource un-registration failed!\n");
    }

    finalize_library();
    printf("OK\n");
    return EXIT_SUCCESS;
}
