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

#include <stdlib.h>
#include <stdarg.h>
#include "azure_c_shared_utility/gballoc.h"

#include "codefirst.h"
#include "azure_macro_utils/macro_utils.h"
#include "azure_c_shared_utility/crt_abstractions.h"
#include "azure_c_shared_utility/xlogging.h"
#include <stddef.h>
#include "iotdevice.h"
#include "azure_c_shared_utility/safe_math.h"

MU_DEFINE_ENUM_STRINGS_WITHOUT_INVALID(CODEFIRST_RESULT, CODEFIRST_RESULT_VALUES)
MU_DEFINE_ENUM_STRINGS_WITHOUT_INVALID(EXECUTE_COMMAND_RESULT, EXECUTE_COMMAND_RESULT_VALUES)

#define LOG_CODEFIRST_ERROR \
    LogError("(result = %s)", MU_ENUM_TO_STRING(CODEFIRST_RESULT, result))

typedef struct DEVICE_HEADER_DATA_TAG
{
    DEVICE_HANDLE DeviceHandle;
    const REFLECTED_DATA_FROM_DATAPROVIDER* ReflectedData;
    SCHEMA_MODEL_TYPE_HANDLE ModelHandle;
    size_t DataSize;
    unsigned char* data;
} DEVICE_HEADER_DATA;

#define COUNT_OF(A) (sizeof(A) / sizeof((A)[0]))

/*design considerations for lazy init of CodeFirst:
    There are 2 main states: either CodeFirst is in an initialized state, or it is not initialized.
    The initialized state means there's a g_OverrideSchemaNamespace set (even when it is set to NULL).
    The uninitialized state means g_OverrideSchemaNamespace is not set.

    To switch to Init state, either call CodeFirst_Init (that sets g_OverrideSchemaNamespace to something)
    or call directly an API (that will set automatically g_OverrideSchemaNamespace to NULL).

    To switch to NOT INIT state, depending on the method used to initialize:
        - if CodeFirst_Init was called, then only by a call to CodeFirst_Deinit the switch will take place
            (note how in this case g_OverrideSchemaNamespace survives destruction of all devices).
        - if the init has been done "lazily" by an API call then the module returns to uninitialized state
            when the number of devices reaches zero.

                              +-----------------------------+
     Start  +---------------->|                             |
                              |  NOT INIT                   |
            +---------------->|                             |
            |                 +------------+----------------+
            |                              |
            |                              |
            |                              | _Init | APIs
            |                              |
            |                              v
            |     +---------------------------------------------------+
            |     | Init State                                        |
            |     | +-----------------+       +-----------------+     |
            |     | |                 |       |                 |     |
            |     | |  init by _Init  |       |   init by API   |     |
            |     | +------+----------+       +---------+-------+     |
            |     |        |                            |             |
            |     |        |_DeInit                     | nDevices==0 |
            |     |        |                            |             |
            |     +--------v----------------+-----------v-------------+
            |                               |
            +-------------------------------+

*/


#define CODEFIRST_STATE_VALUES \
    CODEFIRST_STATE_NOT_INIT, \
    CODEFIRST_STATE_INIT_BY_INIT, \
    CODEFIRST_STATE_INIT_BY_API

MU_DEFINE_ENUM(CODEFIRST_STATE, CODEFIRST_STATE_VALUES)

static CODEFIRST_STATE g_state = CODEFIRST_STATE_NOT_INIT;
static const char* g_OverrideSchemaNamespace;
static size_t g_DeviceCount = 0;
static DEVICE_HEADER_DATA** g_Devices = NULL;

static void deinitializeDesiredProperties(SCHEMA_MODEL_TYPE_HANDLE model, void* destination)
{
    size_t nDesiredProperties;
    if (Schema_GetModelDesiredPropertyCount(model, &nDesiredProperties) != SCHEMA_OK)
    {
        LogError("unexpected error in Schema_GetModelDesiredPropertyCount");
    }
    else
    {
        size_t nProcessedDesiredProperties = 0;
        for (size_t i = 0;i < nDesiredProperties;i++)
        {
            SCHEMA_DESIRED_PROPERTY_HANDLE desiredPropertyHandle = Schema_GetModelDesiredPropertyByIndex(model, i);
            if (desiredPropertyHandle == NULL)
            {
                LogError("unexpected error in Schema_GetModelDesiredPropertyByIndex");
                i = nDesiredProperties;
            }
            else
            {
                pfDesiredPropertyDeinitialize desiredPropertyDeinitialize = Schema_GetModelDesiredProperty_pfDesiredPropertyDeinitialize(desiredPropertyHandle);
                if (desiredPropertyDeinitialize == NULL)
                {
                    LogError("unexpected error in Schema_GetModelDesiredProperty_pfDesiredPropertyDeinitialize");
                    i = nDesiredProperties;
                }
                else
                {
                    size_t offset = Schema_GetModelDesiredProperty_offset(desiredPropertyHandle);
                    desiredPropertyDeinitialize((char*)destination + offset);
                    nProcessedDesiredProperties++;
                }
            }
        }

        if (nDesiredProperties == nProcessedDesiredProperties)
        {
            /*recursively go in the model and initialize the other fields*/
            size_t nModelInModel;
            if (Schema_GetModelModelCount(model, &nModelInModel) != SCHEMA_OK)
            {
                LogError("unexpected error in Schema_GetModelModelCount");
            }
            else
            {
                size_t nProcessedModelInModel = 0;
                for (size_t i = 0; i < nModelInModel;i++)
                {
                    SCHEMA_MODEL_TYPE_HANDLE modelInModel = Schema_GetModelModelyByIndex(model, i);
                    if (modelInModel == NULL)
                    {
                        LogError("unexpected failure in Schema_GetModelModelyByIndex");
                        i = nModelInModel;
                    }
                    else
                    {
                        size_t offset = Schema_GetModelModelByIndex_Offset(model, i);
                        deinitializeDesiredProperties(modelInModel, (char*)destination + offset);
                        nProcessedModelInModel++;
                    }
                }

                if (nProcessedModelInModel == nModelInModel)
                {
                    /*all is fine... */
                }
            }
        }
    }
}

static void DestroyDevice(DEVICE_HEADER_DATA* deviceHeader)
{

    Device_Destroy(deviceHeader->DeviceHandle);
    free(deviceHeader->data);
    free(deviceHeader);
}

static CODEFIRST_RESULT buildStructTypes(SCHEMA_HANDLE schemaHandle, const REFLECTED_DATA_FROM_DATAPROVIDER* reflectedData)
{
    CODEFIRST_RESULT result = CODEFIRST_OK;

    const REFLECTED_SOMETHING* something;
    for (something = reflectedData->reflectedData; something != NULL; something = something->next)
    {
        if (something->type == REFLECTION_STRUCT_TYPE)
        {
            SCHEMA_STRUCT_TYPE_HANDLE structTypeHandle;
            structTypeHandle = Schema_CreateStructType(schemaHandle, something->what.structure.name);

            if (structTypeHandle == NULL)
            {
                result = CODEFIRST_SCHEMA_ERROR;
                LogError("create struct failed %s", MU_ENUM_TO_STRING(CODEFIRST_RESULT, result));
                break;
            }
            else
            {
                const REFLECTED_SOMETHING* maybeField;
                /*look for the field... */
                for (maybeField = reflectedData->reflectedData; maybeField != NULL; maybeField = maybeField->next)
                {
                    if (maybeField->type == REFLECTION_FIELD_TYPE)
                    {
                        if (strcmp(maybeField->what.field.structName, something->what.structure.name) == 0)
                        {
                            if (Schema_AddStructTypeProperty(structTypeHandle, maybeField->what.field.fieldName, maybeField->what.field.fieldType) != SCHEMA_OK)
                            {
                                result = CODEFIRST_SCHEMA_ERROR;
                                LogError("add struct property failed %s", MU_ENUM_TO_STRING(CODEFIRST_RESULT, result));
                                break;
                            }
                        }
                    }
                }
            }
        }
    }

    return result;
}

static CODEFIRST_RESULT buildModel(SCHEMA_HANDLE schemaHandle, const REFLECTED_DATA_FROM_DATAPROVIDER* reflectedData, const REFLECTED_SOMETHING* modelReflectedData)
{
    CODEFIRST_RESULT result = CODEFIRST_OK;
    const REFLECTED_SOMETHING* something;
    SCHEMA_MODEL_TYPE_HANDLE modelTypeHandle;

    modelTypeHandle = Schema_GetModelByName(schemaHandle, modelReflectedData->what.model.name);
    if (modelTypeHandle == NULL)
    {
        result = CODEFIRST_SCHEMA_ERROR;
        LogError("cannot get model %s %s", modelReflectedData->what.model.name, MU_ENUM_TO_STRING(CODEFIRST_RESULT, result));
        goto out;
    }

    for (something = reflectedData->reflectedData; something != NULL; something = something->next)
    {
        /* looking for all elements that belong to a model: properties and actions */
        if ((something->type == REFLECTION_PROPERTY_TYPE) &&
            (strcmp(something->what.property.modelName, modelReflectedData->what.model.name) == 0))
        {
            SCHEMA_MODEL_TYPE_HANDLE childModelHande = Schema_GetModelByName(schemaHandle, something->what.property.type);

            /* if this is a model type use the appropriate APIs for it */
            if (childModelHande != NULL)
            {
                if (Schema_AddModelModel(modelTypeHandle, something->what.property.name, childModelHande, something->what.property.offset, NULL) != SCHEMA_OK)
                {
                    result = CODEFIRST_SCHEMA_ERROR;
                    LogError("add model failed %s", MU_ENUM_TO_STRING(CODEFIRST_RESULT, result));
                    goto out;
                }
            }
            else
            {
                if (Schema_AddModelProperty(modelTypeHandle, something->what.property.name, something->what.property.type) != SCHEMA_OK)
                {
                    result = CODEFIRST_SCHEMA_ERROR;
                    LogError("add property failed %s", MU_ENUM_TO_STRING(CODEFIRST_RESULT, result));
                    goto out;
                }
            }
        }

        if ((something->type == REFLECTION_REPORTED_PROPERTY_TYPE) &&
            (strcmp(something->what.reportedProperty.modelName, modelReflectedData->what.model.name) == 0))
        {
            SCHEMA_MODEL_TYPE_HANDLE childModelHande = Schema_GetModelByName(schemaHandle, something->what.reportedProperty.type);

            /* if this is a model type use the appropriate APIs for it */
            if (childModelHande != NULL)
            {
                if (Schema_AddModelModel(modelTypeHandle, something->what.reportedProperty.name, childModelHande, something->what.reportedProperty.offset, NULL) != SCHEMA_OK)
                {
                    result = CODEFIRST_SCHEMA_ERROR;
                    LogError("add model failed %s", MU_ENUM_TO_STRING(CODEFIRST_RESULT, result));
                    goto out;
                }
            }
            else
            {
                if (Schema_AddModelReportedProperty(modelTypeHandle, something->what.reportedProperty.name, something->what.reportedProperty.type) != SCHEMA_OK)
                {
                    result = CODEFIRST_SCHEMA_ERROR;
                    LogError("add reported property failed %s", MU_ENUM_TO_STRING(CODEFIRST_RESULT, result));
                    goto out;
                }
            }
        }

        if ((something->type == REFLECTION_DESIRED_PROPERTY_TYPE) &&
            (strcmp(something->what.desiredProperty.modelName, modelReflectedData->what.model.name) == 0))
        {
            SCHEMA_MODEL_TYPE_HANDLE childModelHande = Schema_GetModelByName(schemaHandle, something->what.desiredProperty.type);

            /* if this is a model type use the appropriate APIs for it */
            if (childModelHande != NULL)
            {
                if (Schema_AddModelModel(modelTypeHandle, something->what.desiredProperty.name, childModelHande, something->what.desiredProperty.offset, something->what.desiredProperty.onDesiredProperty) != SCHEMA_OK)
                {
                    result = CODEFIRST_SCHEMA_ERROR;
                    LogError("add model failed %s", MU_ENUM_TO_STRING(CODEFIRST_RESULT, result));
                    goto out;
                }
            }
            else
            {
                if (Schema_AddModelDesiredProperty(modelTypeHandle,
                    something->what.desiredProperty.name,
                    something->what.desiredProperty.type,
                    something->what.desiredProperty.FromAGENT_DATA_TYPE,
                    something->what.desiredProperty.desiredPropertInitialize,
                    something->what.desiredProperty.desiredPropertDeinitialize,
                    something->what.desiredProperty.offset,
                    something->what.desiredProperty.onDesiredProperty) != SCHEMA_OK)
                {
                    result = CODEFIRST_SCHEMA_ERROR;
                    LogError("add desired property failed %s", MU_ENUM_TO_STRING(CODEFIRST_RESULT, result));
                    goto out;
                }
            }
        }

        if ((something->type == REFLECTION_ACTION_TYPE) &&
            (strcmp(something->what.action.modelName, modelReflectedData->what.model.name) == 0))
        {
            SCHEMA_ACTION_HANDLE actionHandle;
            size_t i;

            if ((actionHandle = Schema_CreateModelAction(modelTypeHandle, something->what.action.name)) == NULL)
            {
                result = CODEFIRST_SCHEMA_ERROR;
                LogError("add model action failed %s", MU_ENUM_TO_STRING(CODEFIRST_RESULT, result));
                goto out;
            }

            for (i = 0; i < something->what.action.nArguments; i++)
            {
                if (Schema_AddModelActionArgument(actionHandle, something->what.action.arguments[i].name, something->what.action.arguments[i].type) != SCHEMA_OK)
                {
                    result = CODEFIRST_SCHEMA_ERROR;
                    LogError("add model action argument failed %s", MU_ENUM_TO_STRING(CODEFIRST_RESULT, result));
                    goto out;
                }
            }
        }

        if ((something->type == REFLECTION_METHOD_TYPE) &&
            (strcmp(something->what.method.modelName, modelReflectedData->what.model.name) == 0))
        {
            SCHEMA_METHOD_HANDLE methodHandle;
            size_t i;

            if ((methodHandle = Schema_CreateModelMethod(modelTypeHandle, something->what.method.name)) == NULL)
            {
                result = CODEFIRST_SCHEMA_ERROR;
                LogError("add model method failed %s", MU_ENUM_TO_STRING(CODEFIRST_RESULT, result));
                goto out;
            }

            for (i = 0; i < something->what.method.nArguments; i++)
            {
                if (Schema_AddModelMethodArgument(methodHandle, something->what.method.arguments[i].name, something->what.method.arguments[i].type) != SCHEMA_OK)
                {
                    result = CODEFIRST_SCHEMA_ERROR;
                    LogError("add model method argument failed %s", MU_ENUM_TO_STRING(CODEFIRST_RESULT, result));
                    goto out;
                }
            }
        }
    }

out:
    return result;
}

static CODEFIRST_RESULT buildModelTypes(SCHEMA_HANDLE schemaHandle, const REFLECTED_DATA_FROM_DATAPROVIDER* reflectedData)
{
    CODEFIRST_RESULT result = CODEFIRST_OK;
    const REFLECTED_SOMETHING* something;

    /* first have a pass and add all the model types */
    for (something = reflectedData->reflectedData; something != NULL; something = something->next)
    {
        if (something->type == REFLECTION_MODEL_TYPE)
        {
            if (Schema_CreateModelType(schemaHandle, something->what.model.name) == NULL)
            {
                result = CODEFIRST_SCHEMA_ERROR;
                LogError("create model failed %s", MU_ENUM_TO_STRING(CODEFIRST_RESULT, result));
                goto out;
            }
        }
    }

    for (something = reflectedData->reflectedData; something != NULL; something = something->next)
    {
        if (something->type == REFLECTION_MODEL_TYPE)
        {
            result = buildModel(schemaHandle, reflectedData, something);
            if (result != CODEFIRST_OK)
            {
                break;
            }
        }
    }

out:
    return result;
}

static CODEFIRST_RESULT CodeFirst_Init_impl(const char* overrideSchemaNamespace, bool calledFromCodeFirst_Init)
{
    /*shall build the default EntityContainer*/
    CODEFIRST_RESULT result;

    if (g_state != CODEFIRST_STATE_NOT_INIT)
    {
        result = CODEFIRST_ALREADY_INIT;
        if(calledFromCodeFirst_Init) /*do not log this error when APIs attempt lazy init*/
        {
             LogError("CodeFirst was already init %s", MU_ENUM_TO_STRING(CODEFIRST_RESULT, result));
        }
    }
    else
    {
        g_DeviceCount = 0;
        g_OverrideSchemaNamespace = overrideSchemaNamespace;
        g_Devices = NULL;

        g_state = calledFromCodeFirst_Init? CODEFIRST_STATE_INIT_BY_INIT: CODEFIRST_STATE_INIT_BY_API;
        result = CODEFIRST_OK;
    }
    return result;
}

CODEFIRST_RESULT CodeFirst_Init(const char* overrideSchemaNamespace)
{
    return CodeFirst_Init_impl(overrideSchemaNamespace, true);
}

void CodeFirst_Deinit(void)
{
    if (g_state != CODEFIRST_STATE_INIT_BY_INIT)
    {
        LogError("CodeFirst_Deinit called when CodeFirst was not initialized by CodeFirst_Init");
    }
    else
    {
        size_t i;

        for (i = 0; i < g_DeviceCount; i++)
        {
            DestroyDevice(g_Devices[i]);
        }

        free(g_Devices);
        g_Devices = NULL;
        g_DeviceCount = 0;

        g_state = CODEFIRST_STATE_NOT_INIT;
    }
}

static const REFLECTED_SOMETHING* FindModelInCodeFirstMetadata(const REFLECTED_SOMETHING* reflectedData, const char* modelName)
{
    const REFLECTED_SOMETHING* result;

    for (result = reflectedData; result != NULL; result = result->next)
    {
        if ((result->type == REFLECTION_MODEL_TYPE) &&
            (strcmp(result->what.model.name, modelName) == 0))
        {
            /* found model type */
            break;
        }
    }

    return result;
}

static const REFLECTED_SOMETHING* FindChildModelInCodeFirstMetadata(const REFLECTED_SOMETHING* reflectedData, const REFLECTED_SOMETHING* startModel, const char* relativePath, size_t* offset)
{
    const REFLECTED_SOMETHING* result = startModel;
    *offset = 0;

    while ((*relativePath != 0) && (result != NULL))
    {
        const REFLECTED_SOMETHING* childModelProperty;
        size_t propertyNameLength;
        const char* slashPos = strchr(relativePath, '/');
        if (slashPos == NULL)
        {
            slashPos = &relativePath[strlen(relativePath)];
        }

        propertyNameLength = slashPos - relativePath;

        for (childModelProperty = reflectedData; childModelProperty != NULL; childModelProperty = childModelProperty->next)
        {
            if ((childModelProperty->type == REFLECTION_PROPERTY_TYPE) &&
                (strcmp(childModelProperty->what.property.modelName, result->what.model.name) == 0) &&
                (strncmp(childModelProperty->what.property.name, relativePath, propertyNameLength) == 0) &&
                (strlen(childModelProperty->what.property.name) == propertyNameLength))
            {
                /* property found, now let's find the model */
                *offset += childModelProperty->what.property.offset;
                break;
            }
        }

        if (childModelProperty == NULL)
        {
            /* not found */
            result = NULL;
        }
        else
        {
            result = FindModelInCodeFirstMetadata(reflectedData, childModelProperty->what.property.type);
        }

        relativePath = slashPos;
    }

    return result;
}

EXECUTE_COMMAND_RESULT CodeFirst_InvokeAction(DEVICE_HANDLE deviceHandle, void* callbackUserContext, const char* relativeActionPath, const char* actionName, size_t parameterCount, const AGENT_DATA_TYPE* parameterValues)
{
    EXECUTE_COMMAND_RESULT result;
    DEVICE_HEADER_DATA* deviceHeader = (DEVICE_HEADER_DATA*)callbackUserContext;

    if (g_state == CODEFIRST_STATE_NOT_INIT)
    {
        result = EXECUTE_COMMAND_ERROR;
        LogError("CodeFirst_InvokeAction called before init has an error %s ", MU_ENUM_TO_STRING(EXECUTE_COMMAND_RESULT, result));
    }
    else if ((actionName == NULL) ||
        (deviceHandle == NULL) ||
        (relativeActionPath == NULL))
    {
        result = EXECUTE_COMMAND_ERROR;
        LogError("action Name is NULL %s ", MU_ENUM_TO_STRING(EXECUTE_COMMAND_RESULT, result));
    }
    else if ((parameterCount > 0) && (parameterValues == NULL))
    {
        result = EXECUTE_COMMAND_ERROR;
        LogError("parameterValues error %s ", MU_ENUM_TO_STRING(EXECUTE_COMMAND_RESULT, result));
    }
    else if (deviceHeader == NULL)
    {
        result = EXECUTE_COMMAND_ERROR;
        LogError("callback User Context error %s ", MU_ENUM_TO_STRING(EXECUTE_COMMAND_RESULT, result));
    }
    else
    {
        const REFLECTED_SOMETHING* something;
        const REFLECTED_SOMETHING* childModel;
        const char* modelName;
        size_t offset;

        modelName = Schema_GetModelName(deviceHeader->ModelHandle);
        if (modelName == NULL)
        {
            result = EXECUTE_COMMAND_ERROR;
            LogError("modelName was not defined %s ", MU_ENUM_TO_STRING(EXECUTE_COMMAND_RESULT, result));
        }
        else if (((childModel = FindModelInCodeFirstMetadata(deviceHeader->ReflectedData->reflectedData, modelName)) == NULL) ||
                ((childModel = FindChildModelInCodeFirstMetadata(deviceHeader->ReflectedData->reflectedData, childModel, relativeActionPath, &offset)) == NULL))
        {
            result = EXECUTE_COMMAND_ERROR;
            LogError("action %s was not found %s ", actionName, MU_ENUM_TO_STRING(EXECUTE_COMMAND_RESULT, result));
        }
        else
        {
            result = EXECUTE_COMMAND_ERROR;
            for (something = deviceHeader->ReflectedData->reflectedData; something != NULL; something = something->next)
            {
                if ((something->type == REFLECTION_ACTION_TYPE) &&
                    (strcmp(actionName, something->what.action.name) == 0) &&
                    (strcmp(childModel->what.model.name, something->what.action.modelName) == 0))
                {
                    result = something->what.action.wrapper(deviceHeader->data + offset, parameterCount, parameterValues);
                    break;
                }
            }
        }
    }

    if (result == EXECUTE_COMMAND_ERROR)
    {
        LogError(" %s ", MU_ENUM_TO_STRING(EXECUTE_COMMAND_RESULT, result));
    }
    return result;
}

METHODRETURN_HANDLE CodeFirst_InvokeMethod(DEVICE_HANDLE deviceHandle, void* callbackUserContext, const char* relativeMethodPath, const char* methodName, size_t parameterCount, const AGENT_DATA_TYPE* parameterValues)
{
    METHODRETURN_HANDLE result;
    DEVICE_HEADER_DATA* deviceHeader = (DEVICE_HEADER_DATA*)callbackUserContext;

    if (g_state == CODEFIRST_STATE_NOT_INIT)
    {
        result = NULL;
        LogError("CodeFirst_InvokeMethod called before CodeFirst_Init");
    }
    else if ((methodName == NULL) ||
        (deviceHandle == NULL) ||
        (relativeMethodPath == NULL))
    {
        result = NULL;
        LogError("invalid args: DEVICE_HANDLE deviceHandle=%p, void* callbackUserContext=%p, const char* relativeMethodPath=%p, const char* methodName=%p, size_t parameterCount=%lu, const AGENT_DATA_TYPE* parameterValues=%p",
            deviceHandle, callbackUserContext, relativeMethodPath, methodName, (unsigned long)parameterCount, parameterValues);
    }
    else if ((parameterCount > 0) && (parameterValues == NULL))
    {
        result = NULL;
        LogError("parameterValues error ");
    }
    else if (deviceHeader == NULL)
    {
        result = NULL;
        LogError("callback User Context error ");
    }
    else
    {
        const REFLECTED_SOMETHING* something;
        const REFLECTED_SOMETHING* childModel;
        const char* modelName;
        size_t offset;

        modelName = Schema_GetModelName(deviceHeader->ModelHandle);

        if (modelName == NULL)
        {
            result = NULL;
            LogError("model name was not defined");
        }
        else if (((childModel = FindModelInCodeFirstMetadata(deviceHeader->ReflectedData->reflectedData, modelName)) == NULL) ||
                ((childModel = FindChildModelInCodeFirstMetadata(deviceHeader->ReflectedData->reflectedData, childModel, relativeMethodPath, &offset)) == NULL))
        {
            result = NULL;
            LogError("method %s was not found", methodName);
        }
        else
        {
            result = NULL;
            for (something = deviceHeader->ReflectedData->reflectedData; something != NULL; something = something->next)
            {
                if ((something->type == REFLECTION_METHOD_TYPE) &&
                    (strcmp(methodName, something->what.method.name) == 0) &&
                    (strcmp(childModel->what.model.name, something->what.method.modelName) == 0))
                {
                    break; /*found...*/
                }
            }

            if (something == NULL)
            {
                LogError("method \"%s\" not found", methodName);
                result = NULL;
            }
            else
            {
                result = something->what.method.wrapper(deviceHeader->data + offset, parameterCount, parameterValues);
                if (result == NULL)
                {
                    LogError("method \"%s\" execution error (returned NULL)", methodName);
                }
            }
        }
    }
    return result;
}


SCHEMA_HANDLE CodeFirst_RegisterSchema(const char* schemaNamespace, const REFLECTED_DATA_FROM_DATAPROVIDER* metadata)
{
    SCHEMA_HANDLE result;
    if (
        (schemaNamespace == NULL) ||
        (metadata == NULL)
        )
    {
        LogError("invalid arg const char* schemaNamespace=%p, const REFLECTED_DATA_FROM_DATAPROVIDER* metadata=%p", schemaNamespace, metadata);
        result = NULL;
    }
    else
    {
        if (g_OverrideSchemaNamespace != NULL)
        {
            schemaNamespace = g_OverrideSchemaNamespace;
        }

        result = Schema_GetSchemaByNamespace(schemaNamespace);
        if (result == NULL)
        {
            if ((result = Schema_Create(schemaNamespace, (void*)metadata)) == NULL)
            {
                result = NULL;
                LogError("schema init failed %s", MU_ENUM_TO_STRING(CODEFIRST_RESULT, CODEFIRST_SCHEMA_ERROR));
            }
            else
            {
                if ((buildStructTypes(result, metadata) != CODEFIRST_OK) ||
                    (buildModelTypes(result, metadata) != CODEFIRST_OK))
                {
                    Schema_Destroy(result);
                    result = NULL;
                }
                else
                {
                    /* do nothing, everything is OK */
                }
            }
        }
    }

    return result;
}

AGENT_DATA_TYPE_TYPE CodeFirst_GetPrimitiveType(const char* typeName)
{
#ifndef NO_FLOATS
    if (strcmp(typeName, "double") == 0)
    {
        return EDM_DOUBLE_TYPE;
    }
    else if (strcmp(typeName, "float") == 0)
    {
        return EDM_SINGLE_TYPE;
    }
    else
#endif
    if (strcmp(typeName, "int") == 0)
    {
        return EDM_INT32_TYPE;
    }
    else if (strcmp(typeName, "long") == 0)
    {
        return EDM_INT64_TYPE;
    }
    else if (strcmp(typeName, "int8_t") == 0)
    {
        return EDM_SBYTE_TYPE;
    }
    else if (strcmp(typeName, "uint8_t") == 0)
    {
        return EDM_BYTE_TYPE;
    }
    else if (strcmp(typeName, "int16_t") == 0)
    {
        return EDM_INT16_TYPE;
    }
    else if (strcmp(typeName, "int32_t") == 0)
    {
        return EDM_INT32_TYPE;
    }
    else if (strcmp(typeName, "int64_t") == 0)
    {
        return EDM_INT64_TYPE;
    }
    else if (
        (strcmp(typeName, "_Bool") == 0) ||
        (strcmp(typeName, "bool") == 0)
        )
    {
        return EDM_BOOLEAN_TYPE;
    }
    else if (strcmp(typeName, "ascii_char_ptr") == 0)
    {
        return EDM_STRING_TYPE;
    }
    else if (strcmp(typeName, "ascii_char_ptr_no_quotes") == 0)
    {
        return EDM_STRING_NO_QUOTES_TYPE;
    }
    else if (strcmp(typeName, "EDM_DATE_TIME_OFFSET") == 0)
    {
        return EDM_DATE_TIME_OFFSET_TYPE;
    }
    else if (strcmp(typeName, "EDM_GUID") == 0)
    {
        return EDM_GUID_TYPE;
    }
    else if (strcmp(typeName, "EDM_BINARY") == 0)
    {
        return EDM_BINARY_TYPE;
    }
    else
    {
        return EDM_NO_TYPE;
    }
}

static void initializeDesiredProperties(SCHEMA_MODEL_TYPE_HANDLE model, void* destination)
{
    /*this function assumes that at the address given by destination there is an instance of a model*/
    /*some constituents of the model need to be initialized - ascii_char_ptr for example should be set to NULL*/

    size_t nDesiredProperties;
    if (Schema_GetModelDesiredPropertyCount(model, &nDesiredProperties) != SCHEMA_OK)
    {
        LogError("unexpected error in Schema_GetModelDesiredPropertyCount");
    }
    else
    {
        size_t nProcessedDesiredProperties = 0;
        for (size_t i = 0;i < nDesiredProperties;i++)
        {
            SCHEMA_DESIRED_PROPERTY_HANDLE desiredPropertyHandle = Schema_GetModelDesiredPropertyByIndex(model, i);
            if (desiredPropertyHandle == NULL)
            {
                LogError("unexpected error in Schema_GetModelDesiredPropertyByIndex");
                i = nDesiredProperties;
            }
            else
            {
                pfDesiredPropertyInitialize desiredPropertyInitialize = Schema_GetModelDesiredProperty_pfDesiredPropertyInitialize(desiredPropertyHandle);
                if (desiredPropertyInitialize == NULL)
                {
                    LogError("unexpected error in Schema_GetModelDesiredProperty_pfDesiredPropertyInitialize");
                    i = nDesiredProperties;
                }
                else
                {
                    size_t offset = Schema_GetModelDesiredProperty_offset(desiredPropertyHandle);
                    desiredPropertyInitialize((char*)destination + offset);
                    nProcessedDesiredProperties++;
                }
            }
        }

        if (nDesiredProperties == nProcessedDesiredProperties)
        {
            /*recursively go in the model and initialize the other fields*/
            size_t nModelInModel;
            if (Schema_GetModelModelCount(model, &nModelInModel) != SCHEMA_OK)
            {
                LogError("unexpected error in Schema_GetModelModelCount");
            }
            else
            {
                size_t nProcessedModelInModel = 0;
                for (size_t i = 0; i < nModelInModel;i++)
                {
                    SCHEMA_MODEL_TYPE_HANDLE modelInModel = Schema_GetModelModelyByIndex(model, i);
                    if (modelInModel == NULL)
                    {
                        LogError("unexpected failure in Schema_GetModelModelyByIndex");
                        i = nModelInModel;
                    }
                    else
                    {
                        size_t offset = Schema_GetModelModelByIndex_Offset(model, i);
                        initializeDesiredProperties(modelInModel, (char*)destination + offset);
                        nProcessedModelInModel++;
                    }
                }

                if (nProcessedModelInModel == nModelInModel)
                {
                    /*all is fine... */
                }
            }
        }
    }
}

void* CodeFirst_CreateDevice(SCHEMA_MODEL_TYPE_HANDLE model, const REFLECTED_DATA_FROM_DATAPROVIDER* metadata, size_t dataSize, bool includePropertyPath)
{
    void* result;
    DEVICE_HEADER_DATA* deviceHeader;

    if (model == NULL)
    {
        result = NULL;
        LogError(" %s ", MU_ENUM_TO_STRING(CODEFIRST_RESULT, CODEFIRST_INVALID_ARG));
    }
    else
    {
        (void)CodeFirst_Init_impl(NULL, false); /*lazy init*/

        if ((deviceHeader = (DEVICE_HEADER_DATA*)calloc(1, sizeof(DEVICE_HEADER_DATA))) == NULL)
        {
            result = NULL;
            LogError(" %s ", MU_ENUM_TO_STRING(CODEFIRST_RESULT, CODEFIRST_ERROR));
        }
        else
        {
            if ((deviceHeader->data = malloc(dataSize)) == NULL)
            {
                free(deviceHeader);
                deviceHeader = NULL;
                result = NULL;
                LogError(" %s ", MU_ENUM_TO_STRING(CODEFIRST_RESULT, CODEFIRST_ERROR));
            }
            else
            {
                DEVICE_HEADER_DATA** newDevices;
                size_t realloc_size;

                initializeDesiredProperties(model, deviceHeader->data);

                if (Device_Create(model, CodeFirst_InvokeAction, deviceHeader, CodeFirst_InvokeMethod, deviceHeader,
                    includePropertyPath, &deviceHeader->DeviceHandle) != DEVICE_OK)
                {
                    free(deviceHeader->data);
                    free(deviceHeader);

                    result = NULL;
                    LogError(" %s ", MU_ENUM_TO_STRING(CODEFIRST_RESULT, CODEFIRST_DEVICE_FAILED));
                }
                else if ((realloc_size = safe_multiply_size_t(safe_add_size_t(g_DeviceCount, 1), sizeof(DEVICE_HEADER_DATA*))) == SIZE_MAX ||
                    (newDevices = (DEVICE_HEADER_DATA**)realloc(g_Devices, realloc_size)) == NULL)
                {
                    Device_Destroy(deviceHeader->DeviceHandle);
                    free(deviceHeader->data);
                    free(deviceHeader);

                    result = NULL;
                    LogError(" %s, size:%zu", MU_ENUM_TO_STRING(CODEFIRST_RESULT, CODEFIRST_ERROR), realloc_size);
                }
                else
                {
                    SCHEMA_RESULT schemaResult;
                    deviceHeader->ReflectedData = metadata;
                    deviceHeader->DataSize = dataSize;
                    deviceHeader->ModelHandle = model;
                    schemaResult = Schema_AddDeviceRef(model);
                    if (schemaResult != SCHEMA_OK)
                    {
                        Device_Destroy(deviceHeader->DeviceHandle);
                        free(newDevices);
                        free(deviceHeader->data);
                        free(deviceHeader);

                        result = NULL;
                    }
                    else
                    {
                        g_Devices = newDevices;
                        g_Devices[g_DeviceCount] = deviceHeader;
                        g_DeviceCount++;

                        result = deviceHeader->data;
                    }
                }
            }
        }

    }

    return result;
}

void CodeFirst_DestroyDevice(void* device)
{
    if (device != NULL)
    {
        size_t i;

        for (i = 0; i < g_DeviceCount; i++)
        {
            if (g_Devices[i]->data == device)
            {
                deinitializeDesiredProperties(g_Devices[i]->ModelHandle, g_Devices[i]->data);
                Schema_ReleaseDeviceRef(g_Devices[i]->ModelHandle);

                // Delete the Created Schema if all the devices are unassociated
                Schema_DestroyIfUnused(g_Devices[i]->ModelHandle);

                DestroyDevice(g_Devices[i]);
                (void)memcpy(&g_Devices[i], &g_Devices[i + 1], (g_DeviceCount - i - 1) * sizeof(DEVICE_HEADER_DATA*));
                g_DeviceCount--;
                break;
            }
        }

        if ((g_state == CODEFIRST_STATE_INIT_BY_API) && (g_DeviceCount == 0))
        {
            free(g_Devices);
            g_Devices = NULL;
            g_state = CODEFIRST_STATE_NOT_INIT;
        }
    }
}

static DEVICE_HEADER_DATA* FindDevice(void* value)
{
    size_t i;
    DEVICE_HEADER_DATA* result = NULL;

    for (i = 0; i < g_DeviceCount; i++)
    {
        if ((g_Devices[i]->data <= (unsigned char*)value) &&
            (g_Devices[i]->data + g_Devices[i]->DataSize > (unsigned char*)value))
        {
            result = g_Devices[i];
            break;
        }
    }

    return result;
}

static const REFLECTED_SOMETHING* FindValue(DEVICE_HEADER_DATA* deviceHeader, void* value, const char* modelName, size_t startOffset, STRING_HANDLE valuePath)
{
    const REFLECTED_SOMETHING* result;
    size_t valueOffset = (size_t)((unsigned char*)value - (unsigned char*)deviceHeader->data) - startOffset;

    for (result = deviceHeader->ReflectedData->reflectedData; result != NULL; result = result->next)
    {
        if (result->type == REFLECTION_PROPERTY_TYPE &&
            (strcmp(result->what.property.modelName, modelName) == 0) &&
            (result->what.property.offset <= valueOffset) &&
            (result->what.property.offset + result->what.property.size > valueOffset))
        {
            if (startOffset != 0)
            {
                STRING_concat(valuePath, "/");
            }

            STRING_concat(valuePath, result->what.property.name);
            break;
        }
    }

    if (result != NULL)
    {
        if (result->what.property.offset < valueOffset)
        {
            /* find recursively the property in the inner model, if there is one */
            result = FindValue(deviceHeader, value, result->what.property.type, startOffset + result->what.property.offset, valuePath);
        }
    }

    return result;
}

static const REFLECTED_SOMETHING* FindReportedProperty(DEVICE_HEADER_DATA* deviceHeader, void* value, const char* modelName, size_t startOffset, STRING_HANDLE valuePath)
{
    const REFLECTED_SOMETHING* result;
    size_t valueOffset = (size_t)((unsigned char*)value - (unsigned char*)deviceHeader->data) - startOffset;

    for (result = deviceHeader->ReflectedData->reflectedData; result != NULL; result = result->next)
    {
        if (result->type == REFLECTION_REPORTED_PROPERTY_TYPE &&
            (strcmp(result->what.reportedProperty.modelName, modelName) == 0) &&
            (result->what.reportedProperty.offset <= valueOffset) &&
            (result->what.reportedProperty.offset + result->what.reportedProperty.size > valueOffset))
        {
            if (startOffset != 0)
            {
                if (STRING_concat(valuePath, "/") != 0)
                {
                    LogError("unable to STRING_concat");
                    result = NULL;
                    break;
                }
            }

            if (STRING_concat(valuePath, result->what.reportedProperty.name) != 0)
            {
                LogError("unable to STRING_concat");
                result = NULL;
                break;
            }
            break;
        }
    }

    if (result != NULL)
    {
        if (result->what.reportedProperty.offset < valueOffset)
        {
            /* find recursively the property in the inner model, if there is one */
            result = FindReportedProperty(deviceHeader, value, result->what.reportedProperty.type, startOffset + result->what.reportedProperty.offset, valuePath);
        }
    }

    return result;
}

static CODEFIRST_RESULT SendAllDeviceProperties(DEVICE_HEADER_DATA* deviceHeader, TRANSACTION_HANDLE transaction)
{
    CODEFIRST_RESULT result = CODEFIRST_OK;
    const char* modelName = Schema_GetModelName(deviceHeader->ModelHandle);
    if (modelName == NULL)
    {
        result = CODEFIRST_ERROR;
    }
    else
    {
        const REFLECTED_SOMETHING* something;
        unsigned char* deviceAddress = (unsigned char*)deviceHeader->data;

        for (something = deviceHeader->ReflectedData->reflectedData; something != NULL; something = something->next)
        {
            if ((something->type == REFLECTION_PROPERTY_TYPE) &&
                (strcmp(something->what.property.modelName, modelName) == 0))
            {
                AGENT_DATA_TYPE agentDataType;

                if (something->what.property.Create_AGENT_DATA_TYPE_from_Ptr(deviceAddress + something->what.property.offset, &agentDataType) != AGENT_DATA_TYPES_OK)
                {
                    result = CODEFIRST_AGENT_DATA_TYPE_ERROR;
                    LOG_CODEFIRST_ERROR;
                    break;
                }
                else
                {
                    if (Device_PublishTransacted(transaction, something->what.property.name, &agentDataType) != DEVICE_OK)
                    {
                        Destroy_AGENT_DATA_TYPE(&agentDataType);

                        result = CODEFIRST_DEVICE_PUBLISH_FAILED;
                        LOG_CODEFIRST_ERROR;
                        break;
                    }

                    Destroy_AGENT_DATA_TYPE(&agentDataType);
                }
            }
        }
    }

    return result;
}

static CODEFIRST_RESULT SendAllDeviceReportedProperties(DEVICE_HEADER_DATA* deviceHeader, REPORTED_PROPERTIES_TRANSACTION_HANDLE transaction)
{
    CODEFIRST_RESULT result = CODEFIRST_OK;
    const char* modelName = Schema_GetModelName(deviceHeader->ModelHandle);
    if (modelName == NULL)
    {
        result = CODEFIRST_ERROR;
    }
    else
    {
        const REFLECTED_SOMETHING* something;
        unsigned char* deviceAddress = (unsigned char*)deviceHeader->data;
        for (something = deviceHeader->ReflectedData->reflectedData; something != NULL; something = something->next)
        {
            if ((something->type == REFLECTION_REPORTED_PROPERTY_TYPE) &&
                (strcmp(something->what.reportedProperty.modelName, modelName) == 0))
            {
                AGENT_DATA_TYPE agentDataType;

                if (something->what.reportedProperty.Create_AGENT_DATA_TYPE_from_Ptr(deviceAddress + something->what.reportedProperty.offset, &agentDataType) != AGENT_DATA_TYPES_OK)
                {
                    result = CODEFIRST_AGENT_DATA_TYPE_ERROR;
                    LOG_CODEFIRST_ERROR;
                    break;
                }
                else
                {
                    if (Device_PublishTransacted_ReportedProperty(transaction, something->what.reportedProperty.name, &agentDataType) != DEVICE_OK)
                    {
                        Destroy_AGENT_DATA_TYPE(&agentDataType);
                        result = CODEFIRST_DEVICE_PUBLISH_FAILED;
                        LOG_CODEFIRST_ERROR;
                        break;
                    }

                    Destroy_AGENT_DATA_TYPE(&agentDataType);
                }
            }
        }
    }

    return result;
}


CODEFIRST_RESULT CodeFirst_SendAsync(unsigned char** destination, size_t* destinationSize, size_t numProperties, ...)
{
    CODEFIRST_RESULT result;
    va_list ap;

    if (
        (numProperties == 0) ||
        (destination == NULL) ||
        (destinationSize == NULL)
        )
    {
        result = CODEFIRST_INVALID_ARG;
        LOG_CODEFIRST_ERROR;
    }
    else
    {
        (void)CodeFirst_Init_impl(NULL, false); /*lazy init*/

        DEVICE_HEADER_DATA* deviceHeader = NULL;
        size_t i;
        TRANSACTION_HANDLE transaction = NULL;
        result = CODEFIRST_OK;

        va_start(ap, numProperties);

        for (i = 0; i < numProperties; i++)
        {
            void* value = (void*)va_arg(ap, void*);

            DEVICE_HEADER_DATA* currentValueDeviceHeader = FindDevice(value);
            if (currentValueDeviceHeader == NULL)
            {
                result = CODEFIRST_INVALID_ARG;
                LOG_CODEFIRST_ERROR;
                break;
            }
            else if ((deviceHeader != NULL) &&
                (currentValueDeviceHeader != deviceHeader))
            {
                result = CODEFIRST_VALUES_FROM_DIFFERENT_DEVICES_ERROR;
                LOG_CODEFIRST_ERROR;
                break;
            }
            else if ((deviceHeader == NULL) &&
                ((transaction = Device_StartTransaction(currentValueDeviceHeader->DeviceHandle)) == NULL))
            {
                result = CODEFIRST_DEVICE_PUBLISH_FAILED;
                LOG_CODEFIRST_ERROR;
                break;
            }
            else
            {
                deviceHeader = currentValueDeviceHeader;

                if (value == ((unsigned char*)deviceHeader->data))
                {
                    /* we got a full device, send all its state data */
                    result = SendAllDeviceProperties(deviceHeader, transaction);
                    if (result != CODEFIRST_OK)
                    {
                        LOG_CODEFIRST_ERROR;
                        break;
                    }
                }
                else
                {
                    const REFLECTED_SOMETHING* propertyReflectedData;
                    const char* modelName;
                    STRING_HANDLE valuePath;

                    if ((valuePath = STRING_new()) == NULL)
                    {
                        result = CODEFIRST_ERROR;
                        LOG_CODEFIRST_ERROR;
                        break;
                    }
                    else
                    {
                        if ((modelName = Schema_GetModelName(deviceHeader->ModelHandle)) == NULL)
                        {
                            result = CODEFIRST_ERROR;
                            LOG_CODEFIRST_ERROR;
                            STRING_delete(valuePath);
                            break;
                        }
                        else if ((propertyReflectedData = FindValue(deviceHeader, value, modelName, 0, valuePath)) == NULL)
                        {
                            result = CODEFIRST_INVALID_ARG;
                            LOG_CODEFIRST_ERROR;
                            STRING_delete(valuePath);
                            break;
                        }
                        else
                        {
                            AGENT_DATA_TYPE agentDataType;

                            if (propertyReflectedData->what.property.Create_AGENT_DATA_TYPE_from_Ptr(value, &agentDataType) != AGENT_DATA_TYPES_OK)
                            {
                                result = CODEFIRST_AGENT_DATA_TYPE_ERROR;
                                LOG_CODEFIRST_ERROR;
                                STRING_delete(valuePath);
                                break;
                            }
                            else
                            {
                                if (Device_PublishTransacted(transaction, STRING_c_str(valuePath), &agentDataType) != DEVICE_OK)
                                {
                                    Destroy_AGENT_DATA_TYPE(&agentDataType);

                                    result = CODEFIRST_DEVICE_PUBLISH_FAILED;
                                    LOG_CODEFIRST_ERROR;
                                    STRING_delete(valuePath);
                                    break;
                                }
                                else
                                {
                                    STRING_delete(valuePath); /*anyway*/
                                }

                                Destroy_AGENT_DATA_TYPE(&agentDataType);
                            }
                        }
                    }
                }
            }
        }

        if (i < numProperties)
        {
            if (transaction != NULL)
            {
                (void)Device_CancelTransaction(transaction);
            }
        }
        else if (Device_EndTransaction(transaction, destination, destinationSize) != DEVICE_OK)
        {
            result = CODEFIRST_DEVICE_PUBLISH_FAILED;
            LOG_CODEFIRST_ERROR;
        }
        else
        {
            result = CODEFIRST_OK;
        }

        va_end(ap);

    }

    return result;
}

CODEFIRST_RESULT CodeFirst_SendAsyncReported(unsigned char** destination, size_t* destinationSize, size_t numReportedProperties, ...)
{
    CODEFIRST_RESULT result;
    if ((destination == NULL) || (destinationSize == NULL) || numReportedProperties == 0)
    {
        LogError("invalid argument unsigned char** destination=%p, size_t* destinationSize=%p, size_t numReportedProperties=%lu", destination, destinationSize, (unsigned long)numReportedProperties);
        result = CODEFIRST_INVALID_ARG;
    }
    else
    {
        (void)CodeFirst_Init_impl(NULL, false);/*lazy init*/

        DEVICE_HEADER_DATA* deviceHeader = NULL;
        size_t i;
        REPORTED_PROPERTIES_TRANSACTION_HANDLE transaction = NULL;
        va_list ap;
        result = CODEFIRST_ACTION_EXECUTION_ERROR; /*this initialization squelches a false warning about result not being initialized*/

        va_start(ap, numReportedProperties);

        for (i = 0; i < numReportedProperties; i++)
        {
            void* value = (void*)va_arg(ap, void*);
            if (value == NULL)
            {
                LogError("argument number %lu passed through variable arguments is NULL", (unsigned long)i);
                result = CODEFIRST_INVALID_ARG;
                break;
            }
            else
            {
                DEVICE_HEADER_DATA* currentValueDeviceHeader = FindDevice(value);
                if (currentValueDeviceHeader == NULL)
                {
                    result = CODEFIRST_INVALID_ARG;
                    LOG_CODEFIRST_ERROR;
                    break;
                }
                else if ((deviceHeader != NULL) &&
                    (currentValueDeviceHeader != deviceHeader))
                {
                    result = CODEFIRST_VALUES_FROM_DIFFERENT_DEVICES_ERROR;
                    LOG_CODEFIRST_ERROR;
                    break;
                }
                else if ((deviceHeader == NULL) &&
                    ((transaction = Device_CreateTransaction_ReportedProperties(currentValueDeviceHeader->DeviceHandle)) == NULL))
                {
                    result = CODEFIRST_DEVICE_PUBLISH_FAILED;
                    LOG_CODEFIRST_ERROR;
                    break;
                }
                else
                {
                    deviceHeader = currentValueDeviceHeader;
                    if (value == ((unsigned char*)deviceHeader->data))
                    {
                        result = SendAllDeviceReportedProperties(deviceHeader, transaction);
                        if (result != CODEFIRST_OK)
                        {
                            LOG_CODEFIRST_ERROR;
                            break;
                        }
                    }
                    else
                    {
                        const REFLECTED_SOMETHING* propertyReflectedData;
                        const char* modelName;

                        STRING_HANDLE valuePath;
                        if ((valuePath = STRING_new()) == NULL)
                        {
                            result = CODEFIRST_ERROR;
                            LOG_CODEFIRST_ERROR;
                            break;
                        }
                        else
                        {
                            modelName = Schema_GetModelName(deviceHeader->ModelHandle);

                            if ((propertyReflectedData = FindReportedProperty(deviceHeader, value, modelName, 0, valuePath)) == NULL)
                            {
                                result = CODEFIRST_INVALID_ARG;
                                LOG_CODEFIRST_ERROR;
                                STRING_delete(valuePath);
                                break;
                            }
                            else
                            {
                                AGENT_DATA_TYPE agentDataType;
                                if (propertyReflectedData->what.reportedProperty.Create_AGENT_DATA_TYPE_from_Ptr(value, &agentDataType) != AGENT_DATA_TYPES_OK)
                                {
                                    result = CODEFIRST_AGENT_DATA_TYPE_ERROR;
                                    LOG_CODEFIRST_ERROR;
                                    STRING_delete(valuePath);
                                    break;
                                }
                                else
                                {
                                    if (Device_PublishTransacted_ReportedProperty(transaction, STRING_c_str(valuePath), &agentDataType) != DEVICE_OK)
                                    {
                                        Destroy_AGENT_DATA_TYPE(&agentDataType);
                                        result = CODEFIRST_DEVICE_PUBLISH_FAILED;
                                        LOG_CODEFIRST_ERROR;
                                        STRING_delete(valuePath);
                                        break;
                                    }
                                    else
                                    {
                                        STRING_delete(valuePath);
                                    }
                                    Destroy_AGENT_DATA_TYPE(&agentDataType);
                                }
                            }
                        }
                    }
                }
            }
        }

        if (i < numReportedProperties)
        {
            if (transaction != NULL)
            {
                Device_DestroyTransaction_ReportedProperties(transaction);
            }
        }
        else
        {
            if (Device_CommitTransaction_ReportedProperties(transaction, destination, destinationSize) != DEVICE_OK)
            {
                result = CODEFIRST_DEVICE_PUBLISH_FAILED;
                LOG_CODEFIRST_ERROR;
            }
            else
            {
                result = CODEFIRST_OK;
            }

            Device_DestroyTransaction_ReportedProperties(transaction);
        }

        va_end(ap);
    }
    return result;
}

EXECUTE_COMMAND_RESULT CodeFirst_ExecuteCommand(void* device, const char* command)
{
    EXECUTE_COMMAND_RESULT result;
    if (
        (device == NULL) ||
        (command == NULL)
        )
    {
        result = EXECUTE_COMMAND_ERROR;
        LogError("invalid argument (NULL) passed to CodeFirst_ExecuteCommand void* device = %p, const char* command = %p", device, command);
    }
    else
    {
        DEVICE_HEADER_DATA* deviceHeader = FindDevice(device);
        if(deviceHeader == NULL)
        {
            result = EXECUTE_COMMAND_ERROR;
            LogError("unable to find the device given by address %p", device);
        }
        else
        {
            result = Device_ExecuteCommand(deviceHeader->DeviceHandle, command);
        }
    }
    return result;
}

METHODRETURN_HANDLE CodeFirst_ExecuteMethod(void* device, const char* methodName, const char* methodPayload)
{
    METHODRETURN_HANDLE result;
    if (
        (device == NULL) ||
        (methodName== NULL) /*methodPayload can be NULL*/
        )
    {
        result = NULL;
        LogError("invalid argument (NULL) passed to CodeFirst_ExecuteMethod void* device = %p, const char* methodName = %p", device, methodName);
    }
    else
    {
        DEVICE_HEADER_DATA* deviceHeader = FindDevice(device);
        if (deviceHeader == NULL)
        {
            result = NULL;
            LogError("unable to find the device given by address %p", device);
        }
        else
        {
            result = Device_ExecuteMethod(deviceHeader->DeviceHandle, methodName, methodPayload);
        }
    }
    return result;
}

CODEFIRST_RESULT CodeFirst_IngestDesiredProperties(void* device, const char* jsonPayload, bool parseDesiredNode)
{
    CODEFIRST_RESULT result;
    if (
        (device == NULL) ||
        (jsonPayload == NULL)
        )
    {
        LogError("invalid argument void* device=%p, const char* jsonPayload=%p", device, jsonPayload);
        result = CODEFIRST_INVALID_ARG;
    }
    else
    {
        DEVICE_HEADER_DATA* deviceHeader = FindDevice(device);
        if (deviceHeader == NULL)
        {
            LogError("unable to find a device having this memory address %p", device);
            result = CODEFIRST_ERROR;
        }
        else
        {
            if (Device_IngestDesiredProperties(device, deviceHeader->DeviceHandle, jsonPayload, parseDesiredNode) != DEVICE_OK)
            {
                LogError("failure in Device_IngestDesiredProperties");
                result = CODEFIRST_ERROR;
            }
            else
            {
                result = CODEFIRST_OK;
            }
        }
    }
    return result;
}


