/*
 * Copyright (c) 2015, 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.
 */

/*
 * An example of a gateway device which is capable of communicating 
 * with Oracle IoT Cloud Service on behalf of other devices that are incapable 
 * of direct communication with Oracle IoT Cloud Service. This sample illustrates
 * C code for sending data to the cloud service, receiving data from the cloud 
 * service and register indirect connected devices.
 * 
 * The sample uses the messaging API without the virualization API. 
 * It presents a simple humidity sensor device and temperature sensor device which
 * communicates with server through a gateway device. The sample uses the asynchronous
 * message dispatcher to send and receive messages.
 * 
 * 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" and the "Temperature Sensor" 
 * device models are used. These device models 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 <time.h>
#include <math.h>
#include <ctype.h>

/* include methods for device client*/
#include "iotcs_device.h"
/* include async message dispatcher methods */
/* include methods for request dispatcher for handling requests */
#include "advanced/iotcs_messaging.h"

#ifndef IOTCS_CLIENT_ID_BUFFER_LENGTH
#define IOTCS_CLIENT_ID_BUFFER_LENGTH 76
#endif

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

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

/* my release message handler function */
typedef void (*release_message_handler)(iotcs_message* message);

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

/*
 *                           Constants & Variables
 */
#ifndef USE_POLICY
#define USE_POLICY 0
#endif

/* used for humidity sensor work simulation */

/* The probability that the humidity threshold will be exceeded equals PROBABILITY */
#define HUMIDITY_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_humidity_sensor_max_threshold = DEFAULT_MAX_THRESHOLD;

/* used for temperature sensor work simulation */

/* The probability that the temp threshold will be exceeded equals TEMP_PROBABILITY */
#define TEMP_PROBABILITY 20
/* The max range for temp value */
#define TEMP_VMAX 80
/* The min range for temp value */
#define TEMP_VMIN  -20
/* The start temp value */
#define START_TEMP  20.0
/* Used to generate fluctuation around the 'set point' */
static int temp_angle = 0;
/* Current temp sensor value */
static float current_temp_sensor_value = START_TEMP;
/* The maximum value measured by the sensor since power ON or reset */
static float current_temp_sensor_max_value = START_TEMP;
/* The minimum value measured by the sensor since power ON or reset */
static float current_temp_sensor_min_value = START_TEMP;

/* Current temp sensor max threshold */
static int current_temp_sensor_max_threshold = TEMP_VMAX;
/* Previous temp sensor max threshold */
static int previous_temp_sensor_max_threshold = TEMP_VMAX;

/* Current temp sensor min threshold */
static int current_temp_sensor_min_threshold = TEMP_VMIN;
/* Previous temp sensor min threshold */
static int previous_temp_sensor_min_threshold = TEMP_VMIN;

/* Current temp sensor time */
static iotcs_date_time current_temp_sensor_time;
/* Current temp sensor unit */
static const char* current_temp_sensor_unit = "Celsius";
/* Power flag of temp sensor 
 * IOTCS_FALSE - turn off, IOTCS_TRUE - turn on */
static iotcs_bool temp_power = IOTCS_FALSE;
static iotcs_bool temp_was_off = IOTCS_FALSE;

/* other variables */

/* used as sending loop condition */
static volatile int keep_running = 1;
/* endpoints for indirect activation */
static char temp_endpoint_id[IOTCS_CLIENT_ID_BUFFER_LENGTH];
static char humidity_endpoint_id[IOTCS_CLIENT_ID_BUFFER_LENGTH];
/* means that iotcs_init() method was successful*/
static iotcs_bool is_initialized = IOTCS_FALSE;

/* utility functions */

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

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

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

static iotcs_message_base temp_data_msg_base = {
    .type = IOTCS_MESSAGE_DATA,
    .reliability = IOTCS_MESSAGE_RELIABILITY_BEST_EFFORT,
    .priority = IOTCS_MESSAGE_PRIORITY_HIGH,
    .destination = "iotserver",
};

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

static iotcs_message_base temp_alert_msg_base = {
    .type = IOTCS_MESSAGE_ALERT,
    .reliability = IOTCS_MESSAGE_RELIABILITY_BEST_EFFORT,
    .priority = IOTCS_MESSAGE_PRIORITY_HIGHEST,
    .destination = "iotserver",
};

static iotcs_data_message_base data_humidity_msg_base = {
    .format = "urn:com:oracle:iot:device:humidity_sensor:attributes"
};

static iotcs_alert_message_base alert_humidity_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"
};

static iotcs_data_message_base data_temp_msg_base = {
    .format = "urn:com:oracle:iot:device:temperature_sensor:attributes"
};

static iotcs_alert_message_base alert_temp_hot_base = {
    .severity_level = IOTCS_MESSAGE_SEVERITY_SIGNIFICANT,
    .format = "urn:com:oracle:iot:device:temperature_sensor:too_hot",
    .description = "Temperature has reached the maximum temperature threshold"
};

static iotcs_alert_message_base alert_temp_cold_base = {
    .severity_level = IOTCS_MESSAGE_SEVERITY_SIGNIFICANT,
    .format = "urn:com:oracle:iot:device:temperature_sensor:too_cold",
    .description = "Temperature has reached the minimum temperature threshold"
};

/* metadata of indirect connected devices */
const static iotcs_key_value temp_metadata[] = {
    {"manufacturer", "Temp Sample"},
    {"modelNumber", "MN-1000"},
    {"serialNumber", "SN-1000"},
    {"deviceClass", "temp sensor"},
    {"sampleAttr", "test text temp sensor"},
    {"year", "2015"},
    {NULL, NULL}
};

const static iotcs_key_value humidity_metadata[] = {
    {"manufacturer", "Humidity Sample"},
    {"modelNumber", "MN-2000"},
    {"serialNumber", "SN-2000"},
    {"deviceClass", "humidity sensor"},
    {"sampleStrAttr", "test text humidity sensor"},
    {"year", "2015"},
    {NULL, NULL}
};

/* device models supported by indirect connected devices */
static const char *ia_temp_device_urns[] = {
    "urn:com:oracle:iot:device:temperature_sensor",
    NULL
};

static const char *ia_humidity_device_urns[] = {
    "urn:com:oracle:iot:device:humidity_sensor",
    NULL
};

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

/*
 *                              Functions
 */

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

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

/* a simple implementation of the temperature sensor work */
static void temp_sensor_work() {
    if (temp_power == IOTCS_TRUE) {
        int random = randomr(0, 100);
        const int exceed = (random < TEMP_PROBABILITY / 2) || (random >= 100 - TEMP_PROBABILITY / 2);
        int add_value = 0;
        if (exceed) {
            int direction = (randomr(0, 100) < 50) ? 1 : -1;
            add_value = direction * abs(current_temp_sensor_max_threshold - current_temp_sensor_min_threshold + 10);
            printf("Temp sensor: Alert\n");
        }

        current_temp_sensor_value = MAX(TEMP_VMIN, MIN(TEMP_VMAX, (current_temp_sensor_min_threshold + current_temp_sensor_max_threshold)/2 + add_value + 5 * sin(temp_angle)));
        temp_angle = (temp_angle + 20) % 360;
        current_temp_sensor_min_value = MIN(current_temp_sensor_min_value, current_temp_sensor_value);
        current_temp_sensor_max_value = MAX(current_temp_sensor_max_value, current_temp_sensor_value);
    }
}

static void release_message_from_heap(iotcs_data_item_desc* desc, iotcs_value* value) {
    iotcs_value* value_t = value;
    do {
        if (value_t && desc->type == IOTCS_VALUE_TYPE_STRING) {
            if (value_t->string_value) {
                free((char*) value_t->string_value);
            }
        }
        desc++;
        value_t++;
    } while (desc->key != NULL);
    free(value);
}

static void release_data_message_from_heap(iotcs_message* message) {
    release_message_from_heap((iotcs_data_item_desc*) message->u.data.items_desc, message->u.data.items_value);
}

static void release_alert_message_from_heap(iotcs_message* message) {
    release_message_from_heap((iotcs_data_item_desc*) message->u.alert.items_desc, message->u.alert.items_value);
}

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

/* data message items description mentioned in the humidity device model */
static iotcs_data_item_desc humidity_data_desc[] = {
    { .type = IOTCS_VALUE_TYPE_INT, .key = "humidity"},
    { .type = IOTCS_VALUE_TYPE_NONE, .key = NULL},
    { .type = IOTCS_VALUE_TYPE_NONE, .key = NULL}
};

iotcs_value *humidity_data_values;

/* send humidity data message */
static iotcs_result send_humidity_data_message() {
    iotcs_result rv;

    /* always reset the desc to send only humidity value */
    humidity_data_desc[1].type = IOTCS_VALUE_TYPE_NONE;
    humidity_data_desc[1].key = NULL;
    
    int max_threshold = current_humidity_sensor_max_threshold;
    int prev_max_threshold = previous_humidity_sensor_max_threshold;
    previous_humidity_sensor_max_threshold = current_humidity_sensor_max_threshold;

#if (!USE_POLICY)
    if (max_threshold != prev_max_threshold) {
        /* 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 */
#endif
        humidity_data_desc[1].type = IOTCS_VALUE_TYPE_INT;
        humidity_data_desc[1].key = "maxThreshold";
        /*
         * data message items value.
         * Values will be released in callback that called by async message
         * dispatcher */
        humidity_data_values = malloc((ARRAY_LEN(humidity_data_desc) - 1) * sizeof (iotcs_value));
        humidity_data_values[1].int_value = max_threshold;

#if (!USE_POLICY)
    } else {
        humidity_data_values = malloc((ARRAY_LEN(humidity_data_desc) - 2) * sizeof (iotcs_value));
    }
#endif
    /*
     * data message items value.
     * Values will be released in callback that called by async message
     * dispatcher */
    humidity_data_values[0].int_value = current_humidity_sensor_value;

    humidity_data_msg_base.source = humidity_endpoint_id;
    iotcs_message iotcs_data_message = {
        .id = "",
        .base = &humidity_data_msg_base,
        .user_data = release_data_message_from_heap,
        .u.data.base = &data_humidity_msg_base,
        .u.data.items_desc = humidity_data_desc,
        .u.data.items_value = humidity_data_values
    };

#if (USE_POLICY)
    /* put message in async message dispatcher send queue */
    rv = iotcs_offer(&iotcs_data_message);
#else
    /* put message in async message dispatcher send queue */
    rv = iotcs_message_dispatcher_queue(&iotcs_data_message);
#endif

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

/* alert message items description mentioned in the humidity device model */
static iotcs_data_item_desc humidity_alert_desc[] = {
    { .type = IOTCS_VALUE_TYPE_INT, .key = "humidity"},
    { .type = IOTCS_VALUE_TYPE_NONE, .key = NULL}
};

iotcs_value *humidity_alert_values;

/* send humidity alert message */
static iotcs_result send_humidity_alert_message() {
    iotcs_result rv;
    /* 
     * alert message items value.
     * Values will be released in callback that called by async message dispatcher 
     */
    size_t value_size = (ARRAY_LEN(humidity_alert_desc) - 1) * sizeof (iotcs_value);
    humidity_alert_values = malloc(value_size);
    humidity_alert_values[0].int_value = current_humidity_sensor_value;

    humidity_alert_msg_base.source = humidity_endpoint_id;
    iotcs_message iotcs_alert_message = {
        .id = "",
        .base = &humidity_alert_msg_base,
        .user_data = release_alert_message_from_heap,
        .u.alert.base = &alert_humidity_base,
        .u.alert.items_desc = humidity_alert_desc,
        .u.alert.items_value = humidity_alert_values
    };

    /* put message in async message dispatcher send queue */
    rv = iotcs_message_dispatcher_queue(&iotcs_alert_message);

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

    return rv;
}

/* data message items description mentioned in the temperature device model
 * the threshold attributes are added if sent */
static iotcs_data_item_desc temp_data_desc[] = {
    { .type = IOTCS_VALUE_TYPE_NUMBER, .key = "temp"},
    { .type = IOTCS_VALUE_TYPE_STRING, .key = "unit"},
    { .type = IOTCS_VALUE_TYPE_NUMBER, .key = "minTemp"},
    { .type = IOTCS_VALUE_TYPE_NUMBER, .key = "maxTemp"},
    { .type = IOTCS_VALUE_TYPE_NONE, .key = NULL},
    { .type = IOTCS_VALUE_TYPE_NONE, .key = NULL},
    { .type = IOTCS_VALUE_TYPE_NONE, .key = NULL}
};

static iotcs_value *temp_data_values;

static iotcs_result send_temp_data_message() {
    iotcs_result rv;

    /* always reset desc to send only temp values */
    temp_data_desc[4].type = IOTCS_VALUE_TYPE_NONE;
    temp_data_desc[4].key = NULL;
    temp_data_desc[5].type = IOTCS_VALUE_TYPE_NONE;
    temp_data_desc[5].key = NULL;

    /* Adjust this if maxThreshold and/or minThreshold is sent */
    size_t array_length = (ARRAY_LEN(temp_data_desc) - 3);

    /* The next available value slot */
    int next_value = array_length;

    /* Don't send the threshold values unless they have changed 
     * The first time through they will be the same, no need
     * to send the default value */
#if (!USE_POLICY)
    if (current_temp_sensor_min_threshold != 
            previous_temp_sensor_min_threshold) {
#endif
        temp_data_desc[array_length].type = IOTCS_VALUE_TYPE_INT;
        temp_data_desc[array_length++].key = "minThreshold";
#if (!USE_POLICY)
    }
#endif
#if (!USE_POLICY)
    if (current_temp_sensor_max_threshold != 
            previous_temp_sensor_max_threshold) {
#endif
        temp_data_desc[array_length].type = IOTCS_VALUE_TYPE_INT;
        temp_data_desc[array_length++].key = "maxThreshold";
#if (!USE_POLICY)
    }
#endif

    /* 
     * data message items value.
     * Values will be released in callback that called by async message
     * dispatcher */
    size_t value_size = array_length * sizeof (iotcs_value);
    temp_data_values = malloc(value_size);
    temp_data_values[0].number_value = current_temp_sensor_value;
    temp_data_values[1].string_value = strdup(current_temp_sensor_unit);
    temp_data_values[2].number_value = current_temp_sensor_min_value;
    temp_data_values[3].number_value = current_temp_sensor_max_value;

#if (!USE_POLICY)
    /* Add min and/or max threshold values if they have changed */
    if (current_temp_sensor_min_threshold !=    
                previous_temp_sensor_min_threshold) {
#endif
        temp_data_values[next_value++].int_value =
            current_temp_sensor_min_threshold;
        /* set the previous value to the current value */
        previous_temp_sensor_min_threshold = current_temp_sensor_min_threshold;
#if (!USE_POLICY)
    }
    if (current_temp_sensor_max_threshold !=
            previous_temp_sensor_max_threshold) {
#endif
        temp_data_values[next_value].int_value =
            current_temp_sensor_max_threshold;
        /* set the previous value to the current value */
        previous_temp_sensor_max_threshold = current_temp_sensor_max_threshold;
#if (!USE_POLICY)
    }
#endif

    temp_data_msg_base.source = temp_endpoint_id;
    iotcs_message iotcs_data_message = {
        .id = "",
        .base = &temp_data_msg_base,
        .user_data = release_data_message_from_heap,
        .u.data.base = &data_temp_msg_base,
        .u.data.items_desc = temp_data_desc,
        .u.data.items_value = temp_data_values
    };
#if (USE_POLICY)
    /* put message in async message dispatcher send queue */
    rv = iotcs_offer(&iotcs_data_message);
#else
    /* put message in async message dispatcher send queue */
    rv = iotcs_message_dispatcher_queue(&iotcs_data_message);
#endif

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

    return rv;
}

/* cold alert message items description mentioned in the temperature device model */
static iotcs_data_item_desc temp_cold_alert_desc[] = {
    { .type = IOTCS_VALUE_TYPE_NUMBER, .key = "temp"},
    { .type = IOTCS_VALUE_TYPE_STRING, .key = "unit"},
    { .type = IOTCS_VALUE_TYPE_INT, .key = "minThreshold"},
    { .type = IOTCS_VALUE_TYPE_NONE, .key = NULL}
};

static iotcs_value *temp_cold_alert_values;

static iotcs_result send_temp_cold_alert_message() {
    iotcs_result rv;

    /* 
     * alert message items value.
     * Values will be released in callback that called by async message dispatcher 
     */
    size_t value_size = (ARRAY_LEN(temp_cold_alert_desc) - 1) * sizeof (iotcs_value);
    temp_cold_alert_values = malloc(value_size);
    temp_cold_alert_values[0].number_value = current_temp_sensor_value;
    temp_cold_alert_values[1].string_value = strdup(current_temp_sensor_unit);
    temp_cold_alert_values[2].int_value = current_temp_sensor_min_threshold;

    temp_alert_msg_base.source = temp_endpoint_id;

    iotcs_message iotcs_alert_message = {
        .id = "",
        .base = &temp_alert_msg_base,
        .user_data = release_alert_message_from_heap,
        .u.alert.base = &alert_temp_cold_base,
        .u.alert.items_desc = temp_cold_alert_desc,
        .u.alert.items_value = temp_cold_alert_values
    };

    /* put message in async message dispatcher send queue */
    rv = iotcs_message_dispatcher_queue(&iotcs_alert_message);

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

    return rv;
}

/* hot alert message items description mentioned in the temperature device model */
static iotcs_data_item_desc temp_hot_alert_desc[] = {
    { .type = IOTCS_VALUE_TYPE_NUMBER, .key = "temp"},
    { .type = IOTCS_VALUE_TYPE_STRING, .key = "unit"},
    { .type = IOTCS_VALUE_TYPE_INT, .key = "maxThreshold"},
    { .type = IOTCS_VALUE_TYPE_NONE, .key = NULL}
};

static iotcs_value *temp_hot_alert_values;

static iotcs_result send_temp_hot_alert_message() {
    iotcs_result rv;
    /*
     * alert message items value.
     * Values will be released in callback that called by async message dispatcher 
     */
    size_t value_size = (ARRAY_LEN(temp_hot_alert_desc) - 1) * sizeof (iotcs_value);
    temp_hot_alert_values = malloc(value_size);
    temp_hot_alert_values[0].number_value = current_temp_sensor_value;
    temp_hot_alert_values[1].string_value = strdup(current_temp_sensor_unit);
    temp_hot_alert_values[2].int_value = current_temp_sensor_max_threshold;

    temp_alert_msg_base.source = temp_endpoint_id;

    iotcs_message iotcs_alert_message = {
        .id = "",
        .base = &temp_alert_msg_base,
        .user_data = release_alert_message_from_heap,
        .u.alert.base = &alert_temp_hot_base,
        .u.alert.items_desc = temp_hot_alert_desc,
        .u.alert.items_value = temp_hot_alert_values
    };

    /* put message in async message dispatcher send queue */
    rv = iotcs_message_dispatcher_queue(&iotcs_alert_message);

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

    return rv;
}

static void temp_sensor_power(int on) {
    if (on) {
        if (temp_power == IOTCS_TRUE) {
            return;
        }
        // omit nano sec
        current_temp_sensor_time = time(NULL) * 1000;

        printf("Temp sensor start: %ld\n", (long int) current_temp_sensor_time);
        temp_power = IOTCS_TRUE;
        /* send data message with new time */
        send_temp_data_message();
        return;
    }
    temp_power = IOTCS_FALSE;
    return;
}

/* delivery callback for async message dispatcher */
static void on_delivery_cb(iotcs_message *message) {
    printf("Run on delivery callback\n");
    /* 
     * user_data contains release message handler to release specific type message after send.
     * It can be release_data_message_from_heap, release_alert_message_from_heap
     * release_response_message_from_static or release_resource_message_from_static
     */
    if (message->user_data) {
        ((release_message_handler) (message->user_data))(message);
    }
}

/* 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);
    /* 
     * user_data contains release message handler to release specific type message if error occurred.
     * It can be release_data_message_from_heap, release_alert_message_from_heap
     * release_response_message_from_static or release_resource_message_from_static
     */
    if (message->user_data) {
        ((release_message_handler) (message->user_data))(message);
    }
}

static int update_attribute(const char* body, 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;
}

static int update_int_attribute_by_name(const char* body, const char* name, int* value) {
    // {“attribute1”:value1,..,“attributeN”:valueN} 
    char* data = strstr(body, name);
    if (data == NULL) {
        return FAIL_REQUEST_STATUS;
    }
    data = strchr(data, ':');
    // remove ":"
    data++;
    *value = atoi(data);
    return OK_REQUEST_STATUS;
}

static int update_flaot_attribute_by_name(const char* body, const char* name, float* value) {
    // {“attribute1”:value1,..,“attributeN”:valueN} 
    char* data = strstr(body, name);
    if (data == NULL) {
        return FAIL_REQUEST_STATUS;
    }
    data = strchr(data, ':');
    // remove ":"
    data++;
    *value = atol(data);
    return OK_REQUEST_STATUS;
}

static int humidity_maxThreshold_update(const char* body) {
    /* If the update is successful update the previous threshold value */
    int retval;
    int last_value = current_humidity_sensor_max_threshold;
    retval = update_attribute(body, &current_humidity_sensor_max_threshold);
    if (retval == OK_REQUEST_STATUS) {
        previous_humidity_sensor_max_threshold = last_value;
    }
    return retval;
}

/* humidity device request handler used for resources */
static void humidity_device_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);
    response->user_data = release_response_message_from_static;
}

static int temp_maxThreshold_update(const char* body) {
    /* If the update is successful update the previous threshold value */
    int last_value = current_temp_sensor_max_threshold;
    int retval = update_attribute(body, &current_temp_sensor_max_threshold);
    if (retval == OK_REQUEST_STATUS) {
        previous_temp_sensor_max_threshold = last_value;
    }
    return retval;
}

static int temp_minThreshold_update(const char* body) {
    /* If the update is successful update the previous threshold value */
    int last_value = current_temp_sensor_min_threshold;
    int retval = update_attribute(body, &current_temp_sensor_min_threshold);
    if (retval == OK_REQUEST_STATUS) {
        previous_temp_sensor_min_threshold = last_value;
    }
    return retval;
}

static int temp_attributes_update(const char* body) {
    // {“attribute1”:value1,..,“attributeN”:valueN}
    update_int_attribute_by_name(body, "maxThreshold", &current_temp_sensor_max_threshold);
    update_int_attribute_by_name(body, "minThreshold", &current_temp_sensor_min_threshold);
    update_flaot_attribute_by_name(body, "temp", &current_temp_sensor_value);
    update_flaot_attribute_by_name(body, "maxTemp", &current_temp_sensor_max_value);
    update_flaot_attribute_by_name(body, "minTemp", &current_temp_sensor_min_value);
    return OK_REQUEST_STATUS;
}

/* temperature device request handler used for resources */
static void temp_device_handler(iotcs_request_message* request, void* args, iotcs_message* response) {
    /* by default response get "priority" and "reliability" of request */
    /* 
     * args contain my resource handler for temperature device that registered as resource argument.
     * It can be temp_maxThreshold_update, temp_minThreshold_update or temp_attributes_update
     */
    response->u.response.status_code = ((my_resource_handler) args)(request->body);
    response->user_data = release_response_message_from_static;
}

/* action handler for temperature_sensor/actions/reset */
static int reset_action_handler(const char* body) {
    /* set temperature sensor min and max values to current value */
    current_temp_sensor_max_value = current_temp_sensor_value;
    current_temp_sensor_min_value = current_temp_sensor_value;
    return OK_REQUEST_STATUS;
}

/* action handler for temperature_sensor/actions/power */
static int power_action_handler(const char* body) {
    int power = 0;
    // {"value":value}
    char* data = strchr(body, ':');
    // remove ":"
    data++;
    // trim leading spaces
    while (isspace((int) *data))
        data++;

    if (strncasecmp(data, "true", strlen("true")) == 0) {
        power = 1;
        printf("Power on temp sensor!!!\n");
    } else if (strncasecmp(data, "false", strlen("false")) == 0) {
        power = 0;
        printf("Power off temp sensor!!!\n");
    } else {
        printf("unexpected value\n");
        return FAIL_REQUEST_STATUS;
    }
    // call power action
    temp_sensor_power(power);
    return OK_REQUEST_STATUS;
}

/* temperature device request handler used for actions */
static void temp_action_handler(iotcs_request_message* request, void* args, iotcs_message* response) {
    /* by default response get "priority" and "reliability" of request */
    /* 
     * args contain my action handler for temperature device that registered as resource argument.
     * It can be reset_action_handler or power_action_handler.
     */
    response->u.response.status_code = ((my_resource_handler) args)(request->body);
    response->user_data = release_response_message_from_static;
}

static my_resource device_resources[] = {
    {
        .endpoint_id = humidity_endpoint_id,
        .desc =
        {
            .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 = "The max humidity threshold attributes"
        },
        .handler = humidity_device_handler,
        .args = humidity_maxThreshold_update,
        .is_action = 0
    },
    {
        .endpoint_id = temp_endpoint_id,
        .desc =
        {
            .path = "deviceModels/urn:com:oracle:iot:device:temperature_sensor/attributes",
            .name = "temp_attributes",
            .methods = IOTCS_REQUEST_METHOD_GET | IOTCS_REQUEST_METHOD_POST,
            .description = "temp attributes"
        },
        .handler = temp_device_handler,
        .args = temp_attributes_update,
        .is_action = 0
    },
    {
        .endpoint_id = temp_endpoint_id,
        .desc =
        {
            .path = "deviceModels/urn:com:oracle:iot:device:temperature_sensor/attributes/maxThreshold",
            .name = "maxThreshold",
            .methods = IOTCS_REQUEST_METHOD_PUT,
            .description = "The max temperature threshold attributes"
        },
        .handler = temp_device_handler,
        .args = temp_maxThreshold_update,
        .is_action = 0
    },
    {
        .endpoint_id = temp_endpoint_id,
        .desc =
        {
            .path = "deviceModels/urn:com:oracle:iot:device:temperature_sensor/attributes/minThreshold",
            .name = "minThreshold",
            .methods = IOTCS_REQUEST_METHOD_PUT,
            .description = "The min temperature threshold attributes"
        },
        .handler = temp_device_handler,
        .args = temp_minThreshold_update,
        .is_action = 0
    },
    {
        .endpoint_id = temp_endpoint_id,
        .desc =
        {
            .path = "deviceModels/urn:com:oracle:iot:device:temperature_sensor/actions/reset",
            .name = "reset",
            .description = "reset action"
        },
        .handler = temp_action_handler,
        .args = reset_action_handler,
        .is_action = 1
    },
    {
        .endpoint_id = temp_endpoint_id,
        .desc =
        {
            .path = "deviceModels/urn:com:oracle:iot:device:temperature_sensor/actions/power",
            .name = "power",
            .description = "power action"
        },
        .handler = temp_action_handler,
        .args = power_action_handler,
        .is_action = 1
    }
};

/* 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 = device_resources[i].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 = device_resources[i].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(device_resources[i].endpoint_id,
                device_resources[i].desc.path, device_resources[i].handler, device_resources[i].args);
    }
    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 = device_resources[i].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(device_resources[i].endpoint_id,
                device_resources[i].desc.path);
    }
    return status;
}

int main(int argc, char** argv) {
    
    /* This is the URNs of humidity and temperature device models
     * which supported by gateway device
     */
    const char* device_urns[] = {
        "urn:com:oracle:iot:device:temperature_sensor",
        "urn:com:oracle:iot:device:humidity_sensor",
        NULL
    };

    if (argc < 3) {
        error("Too few parameters.\n"
              "\nUsage:"
                "\n\tgateway_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\t<CONNECTED_DEVICE_ID1>, <CONNECTED_DEVICE_ID2>... are connected device ids from 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];

    srand(time(NULL));

    /* 
     * 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("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");
        }
    }
  
    /*
     * The hardware id for the sensor instances are fabricated
     * from the gateway's endpoint id and a suffix, the combination of
     * which is likely to be unique. On subsequent runs of the
     * gateway_device_sample (with the same gateway device), the
     * endpoint id from the previously registered indirectly connected
     * device will be returned by the registerDevice call. In other
     * words, the same indirectly connected device will be used.
     */
    char hardware_id[IOTCS_CLIENT_ID_BUFFER_LENGTH];
    iotcs_bool restricted;
    int written;

    /**
     * If the user gave a hardware id for the temperature sensor,
     * then restrict the sensor to this gateway. This means that
     * the sensor cannot be connected through other gateways.
     */
    if (argc > 3) {
        written = snprintf(hardware_id, ARRAY_LEN(hardware_id), "%s", argv[3]);
        restricted = IOTCS_TRUE;
    } else {
        written = snprintf(hardware_id, ARRAY_LEN(hardware_id), "%s_sample_ts", iotcs_get_endpoint_id());
        restricted = IOTCS_FALSE;
    }
    
    if (written >= IOTCS_CLIENT_ID_BUFFER_LENGTH || written < 0) {
        error("snprintf function failed");
    }
    /* register an indirectly-connected temperature device */
    if (IOTCS_RESULT_OK == iotcs_register_device(restricted, hardware_id, temp_metadata, ia_temp_device_urns, temp_endpoint_id)) {
        printf("Indirect activation was completed. Temp sensor endpoint = %s\n", temp_endpoint_id);
    } else {
        error("Indirect activation was failed.\n");
    }
    
    /**
     * If the user gave a hardware id for the humidity sensor,
     * then restrict the sensor to this gateway. This means that
     * the sensor cannot be connected through other gateways.
     */
    if (argc > 4) {
        written = snprintf(hardware_id, ARRAY_LEN(hardware_id), "%s", argv[4]);
        restricted = IOTCS_TRUE;
    } else {
        written = snprintf(hardware_id, ARRAY_LEN(hardware_id), "%s_sample_hs", iotcs_get_endpoint_id());
        restricted = IOTCS_FALSE;
    }

    if (written >= IOTCS_CLIENT_ID_BUFFER_LENGTH || written < 0) {
        error("snprintf function failed");
    }
    
    /* register an indirectly-connected humidity device */
    if (IOTCS_RESULT_OK == iotcs_register_device(restricted, hardware_id, humidity_metadata, ia_humidity_device_urns, humidity_endpoint_id)) {
        printf("Indirect activation was completed. Humidity sensor endpoint = %s\n", humidity_endpoint_id);
    } else {
        error("Indirect activation was failed.\n");
    }

    /* set delivery callback for async message dispatcher */
    iotcs_message_dispatcher_set_delivery_callback(on_delivery_cb);
    /* set error callback for async message dispatcher */
    iotcs_message_dispatcher_set_error_callback(on_error_cb);

    /* 
     * 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) {
        error("Resources registration failed");
    }

    /* turn on temperature sensor */
    temp_sensor_power(1);
    
    /* Set handler for interrupt signal */
    signal(SIGINT, int_handler);

    /*
     * Emulate device work. 
     * Sensors changed values, then device send data message with new values 
     * to the server. If sensors values is out of threshold then 
     * send alert message.
     * Async message dispatcher send messages to the server and handle 
     * message delivery and errors during send. 
     * Request dispatcher handle incoming requests from the server using 
     * custom request handlers.
     */
    while (keep_running) {
        printf("Sleep 5 seconds!\n");
        sleep(5);
        /* Emulate humidity sensor work */
        humidity_sensor_work();
        /* Emulate temperature sensor work */
        temp_sensor_work();

        /* Send current humidity sensor values */
        send_humidity_data_message();
#if (!USE_POLICY)
        /* Send alert if current humidity value more than max threshold */
        if (current_humidity_sensor_value >= current_humidity_sensor_max_threshold) {
            send_humidity_alert_message();
        }
#endif

        if (temp_power == IOTCS_FALSE) {
            temp_was_off = IOTCS_TRUE;
            continue;
        }

        if (temp_was_off == IOTCS_TRUE) {
            temp_was_off = IOTCS_FALSE;
            printf("Power back on, update all attributes\n");
            /* Send current temperature sensor value after turn on */
            send_temp_data_message();
            continue;
        }

        /* Send current temperature sensor value */
        send_temp_data_message();
#if (!USE_POLICY)
        /* Send hot alert if current temperature value more than max threshold */
        if (current_temp_sensor_value >= current_temp_sensor_max_threshold) {
            send_temp_hot_alert_message();
        }
        /* Send cold alert if current temperature value less than min threshold */
        if (current_temp_sensor_value <= current_temp_sensor_min_threshold) {
            send_temp_cold_alert_message();
        }
#endif
    }

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