// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include "azure_c_shared_utility/optimize_size.h"
#include "azure_c_shared_utility/platform.h"
#include "azure_c_shared_utility/threadapi.h"
#include "azure_c_shared_utility/crt_abstractions.h"
#include "azure_c_shared_utility/shared_util_options.h"
#include "iothub_client.h"
#include "iothub_message.h"
#include "iothubtransportamqp.h"
#include "iothub_client_options.h"
#include "iothub_transport_ll.h"

#ifdef MBED_BUILD_TIMESTAMP
#define SET_TRUSTED_CERT_IN_SAMPLES
#endif // MBED_BUILD_TIMESTAMP

#ifdef SET_TRUSTED_CERT_IN_SAMPLES
#include "certs.h"
#endif // SET_TRUSTED_CERT_IN_SAMPLES

static const char* hubName = "[IoT Hub Name]";
static const char* hubSuffix = "[IoT Hub Suffix]";
static const char* deviceId1 = "[device id 1]";
static const char* deviceId2 = "[device id 2]";
static const char* deviceKey1 = "[device key 1]";
static const char* deviceKey2 = "[device key 2]";

static bool g_continueRunning;
static char msgText[1024];
static char propText[1024];
#define MESSAGE_COUNT       5
#define DOWORK_LOOP_NUM     20


typedef struct EVENT_INSTANCE_TAG
{
    IOTHUB_MESSAGE_HANDLE messageHandle;
    const char* deviceId;
    size_t messageTrackingId;  // For tracking the messages within the user callback.
} EVENT_INSTANCE;

static IOTHUBMESSAGE_DISPOSITION_RESULT ReceiveMessageCallback(IOTHUB_MESSAGE_HANDLE message, void* userContextCallback)
{
    int* counter = (int*)userContextCallback;
    const unsigned char* buffer = NULL;
    size_t size = 0;
    const char* messageId;
    const char* correlationId;

    // Message properties
    if ((messageId = IoTHubMessage_GetMessageId(message)) == NULL)
    {
        messageId = "<null>";
    }

    if ((correlationId = IoTHubMessage_GetCorrelationId(message)) == NULL)
    {
        correlationId = "<null>";
    }

    // Message content
    IOTHUBMESSAGE_CONTENT_TYPE contentType = IoTHubMessage_GetContentType(message);

    if (contentType == IOTHUBMESSAGE_BYTEARRAY)
    {
        if (IoTHubMessage_GetByteArray(message, &buffer, &size) == IOTHUB_MESSAGE_OK)
        {
            (void)printf("Received Message [%d]\r\n Message ID: %s\r\n Correlation ID: %s\r\n BINARY Data: <<<%.*s>>> & Size=%d\r\n", *counter, messageId, correlationId, (int)size, buffer, (int)size);
        }
        else
        {
            (void)printf("Failed getting the BINARY body of the message received.\r\n");
        }
    }
    else if (contentType == IOTHUBMESSAGE_STRING)
    {
        if ((buffer = (const unsigned char*)IoTHubMessage_GetString(message)) != NULL && (size = strlen((const char*)buffer)) > 0)
        {
            (void)printf("Received Message [%d]\r\n Message ID: %s\r\n Correlation ID: %s\r\n STRING Data: <<<%.*s>>> & Size=%d\r\n", *counter, messageId, correlationId, (int)size, buffer, (int)size);

            // If we receive the work 'quit' then we stop running
        }
        else
        {
            (void)printf("Failed getting the STRING body of the message received.\r\n");
        }
    }
    else
    {
        (void)printf("Failed getting the body of the message received (type %i).\r\n", contentType);
    }

    // Retrieve properties from the message
    MAP_HANDLE mapProperties = IoTHubMessage_Properties(message);
    if (mapProperties != NULL)
    {
        const char*const* keys;
        const char*const* values;
        size_t propertyCount = 0;
        if (Map_GetInternals(mapProperties, &keys, &values, &propertyCount) == MAP_OK)
        {
            if (propertyCount > 0)
            {
                size_t index;

                printf(" Message Properties:\r\n");
                for (index = 0; index < propertyCount; index++)
                {
                    printf("\tKey: %s Value: %s\r\n", keys[index], values[index]);
                }
                printf("\r\n");
            }
        }
    }

    if (memcmp(buffer, "quit", size) == 0)
    {
        g_continueRunning = false;
    }

    /* Some device specific action code goes here... */
    (*counter)++;
    return IOTHUBMESSAGE_ACCEPTED;
}

static void SendConfirmationCallback(IOTHUB_CLIENT_CONFIRMATION_RESULT result, void* userContextCallback)
{
    EVENT_INSTANCE* eventInstance = (EVENT_INSTANCE*)userContextCallback;
    //(void)printf("Confirmation[%d] received for message tracking id = %lu with result = %s\r\n", callbackCounter, eventInstance->messageTrackingId, MU_ENUM_TO_STRING(IOTHUB_CLIENT_CONFIRMATION_RESULT, result));
    (void)printf("Confirmation received for message %lu from device %s with result = %s\r\n", eventInstance->messageTrackingId, eventInstance->deviceId, MU_ENUM_TO_STRING(IOTHUB_CLIENT_CONFIRMATION_RESULT, result));
    /* Some device specific action code goes here... */
}

static int create_events(EVENT_INSTANCE* events, const char* deviceId)
{
    int result = 0;

    srand((unsigned int)time(NULL));
    double avgWindSpeed = 10.0;
    double minTemperature = 20.0;
    double minHumidity = 60.0;
    double temperature = 0;
    double humidity = 0;

    int i;
    for (i = 0; i < MESSAGE_COUNT; i++)
    {
        temperature = minTemperature + (rand() % 10);
        humidity = minHumidity +  (rand() % 20);
        if (sprintf_s(msgText, sizeof(msgText), "{\"deviceId\":\"myFirstDevice\",\"windSpeed\":%.2f,\"temperature\":%.2f,\"humidity\":%.2f}", avgWindSpeed + (rand() % 4 + 2), temperature, humidity) == 0)
        {
            (void)printf("ERROR: failed creating event message for device %s\r\n", deviceId);
            result = MU_FAILURE;
        }
        else if ((events[i].messageHandle = IoTHubMessage_CreateFromByteArray((const unsigned char*)msgText, strlen(msgText))) == NULL)
        {
            (void)printf("ERROR: failed creating the IOTHUB_MESSAGE_HANDLE for device %s\r\n", deviceId);
            result = MU_FAILURE;
        }
        else
        {
            MAP_HANDLE propMap;

            if ((propMap = IoTHubMessage_Properties(events[i].messageHandle)) == NULL)
            {
                (void)printf("ERROR: failed getting device %s's message property map\r\n", deviceId);
                result = MU_FAILURE;
            }
            else if (sprintf_s(propText, sizeof(propText), temperature > 28 ? "true" : "false") == 0)
            {
                (void)printf("ERROR: sprintf_s failed for device %s's message property\r\n", deviceId);
                result = MU_FAILURE;
            }
            else if (Map_AddOrUpdate(propMap, "temperatureAlert", propText) != MAP_OK)
            {
                (void)printf("ERROR: Map_AddOrUpdate failed for device %s\r\n", deviceId);
                result = MU_FAILURE;
            }
            else
            {
                events[i].deviceId = deviceId;
                events[i].messageTrackingId = i;
                result = 0;
            }
        }
    }

    return result;
}

void iothub_client_sample_amqp_run(void)
{
    TRANSPORT_HANDLE transport_handle;
    IOTHUB_CLIENT_LL_HANDLE iotHubClientHandle1;
    IOTHUB_CLIENT_LL_HANDLE iotHubClientHandle2;

    EVENT_INSTANCE messages_device1[MESSAGE_COUNT];
    EVENT_INSTANCE messages_device2[MESSAGE_COUNT];

    g_continueRunning = true;

    //callbackCounter = 0;
    int receiveContext1 = 0;
    int receiveContext2 = 0;

    (void)printf("Starting the IoTHub client sample AMQP...\r\n");

    if (platform_init() != 0)
    {
        printf("Failed to initialize the platform.\r\n");
    }
    else
    {
        if ((transport_handle = IoTHubTransport_Create(AMQP_Protocol, hubName, hubSuffix)) == NULL)
        {
            printf("Failed to creating the protocol handle.\r\n");
        }
        else
        {
            IOTHUB_CLIENT_DEVICE_CONFIG config1;
            config1.deviceId = deviceId1;
            config1.deviceKey = deviceKey1;
            config1.deviceSasToken = NULL;
            config1.protocol = AMQP_Protocol;
            config1.transportHandle = IoTHubTransport_GetLLTransport(transport_handle);

            IOTHUB_CLIENT_DEVICE_CONFIG config2;
            config2.deviceId = deviceId2;
            config2.deviceKey = deviceKey2;
            config2.deviceSasToken = NULL;
            config2.protocol = AMQP_Protocol;
            config2.transportHandle = IoTHubTransport_GetLLTransport(transport_handle);

            if ((iotHubClientHandle1 = IoTHubClient_LL_CreateWithTransport(&config1)) == NULL)
            {
                (void)printf("ERROR: iotHubClientHandle1 is NULL!\r\n");
            }
            else if ((iotHubClientHandle2 = IoTHubClient_LL_CreateWithTransport(&config2)) == NULL)
            {
                (void)printf("ERROR: iotHubClientHandle1 is NULL!\r\n");
            }
            else
            {
                bool traceOn = true;
                IoTHubClient_LL_SetOption(iotHubClientHandle1, OPTION_LOG_TRACE, &traceOn);

    #ifdef SET_TRUSTED_CERT_IN_SAMPLES
                // For mbed add the certificate information
                if (IoTHubClient_LL_SetOption(iotHubClientHandle1, OPTION_TRUSTED_CERT, certificates) != IOTHUB_CLIENT_OK)
                {
                    printf("failure to set option \"TrustedCerts\"\r\n");
                }
    #endif // SET_TRUSTED_CERT_IN_SAMPLES

                if (create_events(messages_device1, config1.deviceId) != 0 || create_events(messages_device2, config2.deviceId) != 0)
                {
                    (void)printf("ERROR: failed creating events for the devices..........FAILED!\r\n");
                }
                /* Setting Message call back, so we can receive Commands. */
                else if (IoTHubClient_LL_SetMessageCallback(iotHubClientHandle1, ReceiveMessageCallback, &receiveContext1) != IOTHUB_CLIENT_OK)
                {
                    (void)printf("ERROR: IoTHubClient_SetMessageCallback for device 1..........FAILED!\r\n");
                }
                else if (IoTHubClient_LL_SetMessageCallback(iotHubClientHandle2, ReceiveMessageCallback, &receiveContext2) != IOTHUB_CLIENT_OK)
                {
                    (void)printf("ERROR: IoTHubClient_SetMessageCallback for device 2..........FAILED!\r\n");
                }
                else
                {
                    (void)printf("IoTHubClient_SetMessageCallback...successful.\r\n");

                    /* Now that we are ready to receive commands, let's send some messages */
                    size_t iterator = 0;
                    do
                    {
                        if (iterator < MESSAGE_COUNT)
                        {
                            if (IoTHubClient_LL_SendEventAsync(iotHubClientHandle1, messages_device1[iterator].messageHandle, SendConfirmationCallback, &messages_device1[iterator]) != IOTHUB_CLIENT_OK)
                            {
                                (void)printf("ERROR: IoTHubClient_SendEventAsync for device 1..........FAILED!\r\n");
                            }
                            else if (IoTHubClient_LL_SendEventAsync(iotHubClientHandle2, messages_device2[iterator].messageHandle, SendConfirmationCallback, &messages_device2[iterator]) != IOTHUB_CLIENT_OK)
                            {
                                (void)printf("ERROR: IoTHubClient_SendEventAsync for device 2..........FAILED!\r\n");
                            }
                            else
                            {
                                (void)printf("IoTHubClient_SendEventAsync accepted data for transmission to IoT Hub.\r\n");
                            }

                            IoTHubMessage_Destroy(messages_device1[iterator].messageHandle);
                            IoTHubMessage_Destroy(messages_device2[iterator].messageHandle);
                        }

                        IoTHubClient_LL_DoWork(iotHubClientHandle1);
                        IoTHubClient_LL_DoWork(iotHubClientHandle2);
                        ThreadAPI_Sleep(1);

                        iterator++;
                    } while (g_continueRunning);

                    (void)printf("iothub_client_sample_mqtt has gotten quit message, call DoWork %d more time to complete final sending...\r\n", DOWORK_LOOP_NUM);
                    for (size_t index = 0; index < DOWORK_LOOP_NUM; index++)
                    {
                        IoTHubClient_LL_DoWork(iotHubClientHandle1);
                        ThreadAPI_Sleep(1);
                    }
                }
                IoTHubClient_LL_Destroy(iotHubClientHandle1);
                IoTHubClient_LL_Destroy(iotHubClientHandle2);
            }
            IoTHubTransport_Destroy(transport_handle);
        }
        platform_deinit();
    }
}

int main(void)
{
    iothub_client_sample_amqp_run();
    return 0;
}
