// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

#include "iothubtransportamqp.h"
#include "schemalib.h"
#include "iothub_client.h"
#include "serializer.h"
#include "schemaserializer.h"
#include "threadapi.h"

#ifdef MBED_BUILD_TIMESTAMP
#include "certs.h"
#endif // MBED_BUILD_TIMESTAMP

static const char* iotHubName = "[IoT Hub name goes here]";
static const char* iotHubSuffix = "[IoT Hub suffix goes here, e.g., private.azure-devices-int.net]";
static const char* deviceId = "[Device ID goes here]";
static const char* deviceKey = "[Device key goes here]";

// Define the Model
BEGIN_NAMESPACE(Contoso);

DECLARE_STRUCT(SystemProperties,
    ascii_char_ptr, DeviceID,
    _Bool, Enabled
);

DECLARE_MODEL(VendingMachine,

    /* Event data (proximity sensor value - can have the values 2 or 30) */
    WITH_DATA(int, SensorValue),

    /* Device Info - This is command metadata + some extra fields */
    WITH_DATA(ascii_char_ptr, ObjectName),
    WITH_DATA(ascii_char_ptr, ObjectType),
    WITH_DATA(ascii_char_ptr, Version),
    WITH_DATA(SystemProperties, SystemProperties),
    WITH_DATA(ascii_char_ptr_no_quotes, Commands),

    /* Commands implemented by the device */
    WITH_ACTION(SetItemPrice, ascii_char_ptr, itemId, ascii_char_ptr, price)
);

END_NAMESPACE(Contoso);

EXECUTE_COMMAND_RESULT SetItemPrice(VendingMachine* vendingMachine, ascii_char_ptr itemId, ascii_char_ptr price)
{
    (void)vendingMachine;
    (void)printf("Received price %s for itemId %s\r\n", price, itemId);
    return EXECUTE_COMMAND_SUCCESS;
}

static void sendMessage(IOTHUB_CLIENT_HANDLE iotHubClientHandle, const unsigned char* buffer, size_t size)
{
    IOTHUB_MESSAGE_HANDLE messageHandle = IoTHubMessage_CreateFromByteArray(buffer, size);
    if (messageHandle == NULL)
    {
        printf("unable to create a new IoTHubMessage\r\n");
    }
    else
    {
        if (IoTHubClient_SendEventAsync(iotHubClientHandle, messageHandle, NULL, NULL) != IOTHUB_CLIENT_OK)
        {
            printf("failed to hand over the message to IoTHubClient");
        }
        else
        {
            printf("IoTHubClient accepted the message for delivery\r\n");
        }

        IoTHubMessage_Destroy(messageHandle);
    }
    free((void*)buffer);
}

/*this functiuon "links" IoTHub to the serialization library*/
static IOTHUBMESSAGE_DISPOSITION_RESULT IoTHubNotification(IOTHUB_MESSAGE_HANDLE notificationMessage, void* userContextCallback)
{
    IOTHUBMESSAGE_DISPOSITION_RESULT result;
    const unsigned char* buffer;
    size_t size;
    if (IoTHubMessage_GetByteArray(notificationMessage, &buffer, &size) != IOTHUB_MESSAGE_OK)
    {
        printf("unable to IoTHubMessage_GetByteArray\r\n");
        result = EXECUTE_COMMAND_ERROR;
    }
    else
    {
        /*buffer is not zero terminated*/
        char* temp = malloc(size + 1);
        if (temp == NULL)
        {
            printf("failed to malloc\r\n");
            result = EXECUTE_COMMAND_ERROR;
        }
        else
        {
            memcpy(temp, buffer, size);
            temp[size] = '\0';
            EXECUTE_COMMAND_RESULT executeCommandResult = EXECUTE_COMMAND(userContextCallback, temp);
            result =
                (executeCommandResult == EXECUTE_COMMAND_ERROR) ? IOTHUBMESSAGE_ABANDONED :
                (executeCommandResult == EXECUTE_COMMAND_SUCCESS) ? IOTHUBMESSAGE_ACCEPTED :
                IOTHUBMESSAGE_REJECTED;
            free(temp);
        }
    }
    return result;
}


void asset_monitoring_run(void)
{

    if (serializer_init(NULL) != SERIALIZER_OK)
    {
        printf("Failed on serializer_init\r\n");
    }
    else
    {
        IOTHUB_CLIENT_CONFIG iotHubClientConfig;

        /* Setup IoTHub client configuration */
        iotHubClientConfig.iotHubName = iotHubName;
        iotHubClientConfig.iotHubSuffix = iotHubSuffix;
        iotHubClientConfig.deviceId = deviceId;
        iotHubClientConfig.deviceKey = deviceKey;
        iotHubClientConfig.protocol = IoTHubTransportAmqp_ProvideTransportInterface;

        IOTHUB_CLIENT_HANDLE iotHubClientHandle = IoTHubClient_Create(&iotHubClientConfig);

        if (iotHubClientHandle == NULL)
        {
            (void)printf("Failed on IoTHubClient_Create\r\n");
        }
        else
        {
            unsigned int minimumPollingTime = 9; /*because it can poll "after 9 seconds" polls will happen effectively at ~10 seconds*/
            if (IoTHubClient_SetOption(iotHubClientHandle, "MinimumPollingTime", &minimumPollingTime) != IOTHUB_CLIENT_OK)
            {
                printf("failure to set option \"MinimumPollingTime\"\r\n");
            }

#ifdef MBED_BUILD_TIMESTAMP
            // For mbed add the certificate information
            if (IoTHubClient_SetOption(iotHubClientHandle, "TrustedCerts", certificates) != IOTHUB_CLIENT_OK)
            {
                printf("failure to set option \"TrustedCerts\"\r\n");
            }
#endif // MBED_BUILD_TIMESTAMP

            VendingMachine* vendingMachine = CREATE_MODEL_INSTANCE(Contoso, VendingMachine);
            if (vendingMachine == NULL)
            {
                (void)printf("Failed on CREATE_MODEL_INSTANCE\r\n");
            }
            else
            {
                STRING_HANDLE commandsMetadata;
                int counter;

                if (IoTHubClient_SetNotificationCallback(iotHubClientHandle, IoTHubNotification, vendingMachine) != IOTHUB_CLIENT_OK)
                {
                    printf("unable to IoTHubClient_SetNotificationCallback\r\n");
                }
                else
                {

                    /* send the device info upon startup so that the cloud app knows
                    what commands are available and the fact that the device is up */
                    vendingMachine->ObjectType = "DeviceInfo-HW";
                    vendingMachine->ObjectName = "My vending machine";
                    vendingMachine->Version = "1.0";
                    vendingMachine->SystemProperties.DeviceID = (char*)deviceId;
                    vendingMachine->SystemProperties.Enabled = true;

                    commandsMetadata = STRING_new();
                    if (commandsMetadata == NULL)
                    {
                        (void)printf("Failed on creating string for commands metadata\r\n");
                    }
                    else
                    {
                        /* Serialize the commands metadata as a JSON string before sending */
                        if (SchemaSerializer_SerializeCommandMetadata(GET_MODEL_HANDLE(Contoso, VendingMachine), commandsMetadata) != SCHEMA_SERIALIZER_OK)
                        {
                            (void)printf("Failed serializing commands metadata\r\n");
                        }
                        else
                        {
                            unsigned char* buffer;
                            size_t bufferSize;
                            vendingMachine->Commands = (char*)STRING_c_str(commandsMetadata);

                            /* Here is the actual send of the Device Info */
                            if (SERIALIZE(&buffer, &bufferSize, vendingMachine->ObjectName, vendingMachine->ObjectType, vendingMachine->SystemProperties, vendingMachine->Version, vendingMachine->Commands) != IOT_AGENT_OK)
                            {
                                (void)printf("Failed serializing\r\n");
                            }
                            else
                            {
                                sendMessage(iotHubClientHandle, buffer, bufferSize);
                            }

                        }

                        STRING_delete(commandsMetadata);
                    }

                    /* Start an endless loop and send every 1 second the value 2 and every 10 seconds
                    simulate a proximity sensor value change */
                    counter = 0;

                    while (1)
                    {
                        unsigned char*buffer;
                        size_t bufferSize;
                        if (counter == 10)
                        {
                            counter = 0;

                            vendingMachine->SensorValue = 30;
                            (void)printf("Sending sensor value %d\r\n", vendingMachine->SensorValue);
                        }
                        else
                        {
                            vendingMachine->SensorValue = 2;
                            (void)printf("Sending sensor value %d\r\n", vendingMachine->SensorValue);
                        }

                        if (SERIALIZE(&buffer, &bufferSize, vendingMachine->SensorValue) != IOT_AGENT_OK)
                        {
                            (void)printf("Failed sending sensor value\r\n");
                        }
                        else
                        {
                            sendMessage(iotHubClientHandle, buffer, bufferSize);
                        }

                        ThreadAPI_Sleep(1000);
                        counter++;
                    }
                }

                DESTROY_MODEL_INSTANCE(vendingMachine);
            }
            IoTHubClient_Destroy(iotHubClientHandle);
        }
        serializer_deinit();
    }

}
