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

#ifdef __cplusplus
#include <cstdlib>
#else
#include <stdlib.h>
#endif

#include "azure_macro_utils/macro_utils.h"
#include "testrunnerswitcher.h"
#include "umock_c/umock_c.h"
#include "umock_c/umocktypes_charptr.h"
#include "umock_c/umocktypes_bool.h"
#include "umock_c/umock_c_negative_tests.h"

#define ENABLE_MOCKS
#include "azure_c_shared_utility/gballoc.h"
#include "azure_c_shared_utility/uniqueid.h"
#undef ENABLE_MOCKS

#include "azure_c_shared_utility/uuid.h"

static TEST_MUTEX_HANDLE g_testByTest;

MU_DEFINE_ENUM_STRINGS(UMOCK_C_ERROR_CODE, UMOCK_C_ERROR_CODE_VALUES)

static void on_umock_c_error(UMOCK_C_ERROR_CODE error_code)
{
    ASSERT_FAIL("umock_c reported error :%" PRI_MU_ENUM "", MU_ENUM_VALUE(UMOCK_C_ERROR_CODE, error_code));
}


#define UUID_OCTET_COUNT    16
#define UUID_STRING_LENGTH  36
#define UUID_STRING_SIZE    (UUID_STRING_LENGTH + 1)

static const UUID_T TEST_UUID = { 222, 193, 74, 152, 197, 252, 67, 14, 180, 227, 51, 193, 196, 52, 220, 175 };
static char* TEST_UUID_STRING = "dec14a98-c5fc-430e-b4e3-33c1c434dcaf";
static const char* INVALID_UUIDS[] =
{
    "d",
    "-",
    "dec14a98",
    "dec14a98-c5fc",
    "dec14a98-c5fc-430e-b4e3-",
    "dec14a98-c5fc-430e-b4e3-33c1c434dca",
    "dec14a98c5fc-430e-b4e3-33c1c434dcaf",
    "dec14a98c5fc-430e-b4e3-33c1c434dcaf1",
    "dec14a98-c5fc430e-b4e3-33c1c434dcaf",
    "dec14a98-c5fc-430eb4e3-33c1c434dcaf",
    "dec14a98-c5fc-430e-b4e333c1c434dcaf",
    "dec14a98c5fc430eb4e333c1c434dcaf",
    "dec14a98-c5fc-430e-b4e3-33c1c434dca-",
    "dec14a98-c5fc-430e-b-e3-33c1c434dcaf",
    "dec14a98-c5fc-43-e-b4e3-33c1c434dcaf",
    "dec14a98-c-fc-430e-b4e3-33c1c434dcaf",
    "dec14-98-c5fc-430e-b4e3-33c1c434dcaf",
    "-ec14a98-c5fc-430e-b4e3-33c1c434dcaf",
};

static UNIQUEID_RESULT mock_UniqueId_Generate_result;
static UNIQUEID_RESULT mock_UniqueId_Generate(char* uid, size_t bufferSize)
{
    (void)memcpy(uid, TEST_UUID_STRING, bufferSize);
    return mock_UniqueId_Generate_result;
}

static void initialize_variables()
{
    mock_UniqueId_Generate_result = UNIQUEID_OK;
}

static void register_global_mock_returns()
{
    REGISTER_GLOBAL_MOCK_FAIL_RETURN(malloc, NULL);
    REGISTER_GLOBAL_MOCK_FAIL_RETURN(UniqueId_Generate, UNIQUEID_ERROR);
}

static void register_global_function_hooks()
{
    REGISTER_GLOBAL_MOCK_HOOK(UniqueId_Generate, mock_UniqueId_Generate);
}

static void register_mock_aliases()
{
    REGISTER_UMOCK_ALIAS_TYPE(UNIQUEID_RESULT, int);
}


BEGIN_TEST_SUITE(uuid_unittests)

TEST_SUITE_INITIALIZE(suite_init)
{
    int result;

    result = umock_c_init(on_umock_c_error);
    ASSERT_ARE_EQUAL(int, 0, result);

    g_testByTest = TEST_MUTEX_CREATE();
    ASSERT_IS_NOT_NULL(g_testByTest);

    register_mock_aliases();
    register_global_mock_returns();
    register_global_function_hooks();
    initialize_variables();
}

TEST_SUITE_CLEANUP(suite_cleanup)
{
    TEST_MUTEX_DESTROY(g_testByTest);
}

TEST_FUNCTION_INITIALIZE(TestMethodInitialize)
{
    if (TEST_MUTEX_ACQUIRE(g_testByTest) != 0)
    {
        ASSERT_FAIL("our mutex is ABANDONED. Failure in test framework");
    }
}

TEST_FUNCTION_CLEANUP(TestMethodCleanup)
{
    TEST_MUTEX_RELEASE(g_testByTest);
}

// Tests_SRS_UUID_09_001: [ If uuid is NULL, UUID_generate shall return a non-zero value ]
TEST_FUNCTION(UUID_generate_NULL_uuid)
{
    //Arrange
    int result;

    umock_c_reset_all_calls();

    //Act
    result = UUID_generate(NULL);

    //Assert
    ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls());
    ASSERT_ARE_NOT_EQUAL(int, 0, result);
}

// Tests_SRS_UUID_09_002: [ UUID_generate shall obtain an UUID string from UniqueId_Generate ]
// Tests_SRS_UUID_09_004: [ The UUID string shall be parsed into an UUID_T type (16 unsigned char array) and filled in uuid ]
// Tests_SRS_UUID_09_006: [ If no failures occur, UUID_generate shall return zero ]
TEST_FUNCTION(UUID_generate_succeed)
{
    //Arrange
    UUID_T uuid;
    int result;
    char uuid_string[UUID_STRING_SIZE];

    umock_c_reset_all_calls();
    STRICT_EXPECTED_CALL(malloc(UUID_STRING_SIZE))
        .SetReturn(uuid_string);
    STRICT_EXPECTED_CALL(UniqueId_Generate(uuid_string, UUID_STRING_SIZE));
    STRICT_EXPECTED_CALL(free(uuid_string));

    //Act
    result = UUID_generate(&uuid);

    //Assert
    ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls());
    ASSERT_ARE_EQUAL(int, 0, result);

    {
        int i;
        for (i = 0; i < UUID_OCTET_COUNT; i++)
        {
            ASSERT_ARE_EQUAL(int, TEST_UUID[i], uuid[i]);
        }
    }
}

// Tests_SRS_UUID_09_003: [ If the UUID string fails to be obtained, UUID_generate shall fail and return a non-zero value ]
// Tests_SRS_UUID_09_005: [ If uuid fails to be set, UUID_generate shall fail and return a non-zero value ]
TEST_FUNCTION(UUID_generate_failure_checks)
{
    //Arrange
    UUID_T uuid;
    int result;
    size_t i;
    char uuid_string[UUID_STRING_SIZE];

    ASSERT_ARE_EQUAL(int, 0, umock_c_negative_tests_init());

    umock_c_reset_all_calls();
    STRICT_EXPECTED_CALL(malloc(UUID_STRING_SIZE))
        .SetReturn(uuid_string);
    STRICT_EXPECTED_CALL(UniqueId_Generate(uuid_string, UUID_STRING_SIZE));
    STRICT_EXPECTED_CALL(free(uuid_string));
    umock_c_negative_tests_snapshot();

    for (i = 0; i < umock_c_negative_tests_call_count(); i++)
    {
        char temp_str[64];

        if (i == 2) continue;

        umock_c_negative_tests_reset();
        umock_c_negative_tests_fail_call(i);

        (void)sprintf(temp_str, "On failed call %lu", (unsigned long)i);

        // act
        result = UUID_generate(&uuid);

        // assert
        ASSERT_ARE_NOT_EQUAL(int, 0, result, temp_str);
    }

    umock_c_negative_tests_reset();
    umock_c_negative_tests_deinit();
}

// Tests_SRS_UUID_09_011: [ If uuid is NULL, UUID_to_string shall return a non-zero value ]
TEST_FUNCTION(UUID_to_string_NULL_uuid)
{
    //Arrange

    //Act
    char* result = UUID_to_string(NULL);

    //Assert
    ASSERT_IS_NULL(result);
}

// Tests_SRS_UUID_09_012: [ UUID_to_string shall allocate a valid UUID string (uuid_string) as per RFC 4122 ]
// Tests_SRS_UUID_09_014: [ Each character in uuid shall be written in the respective positions of uuid_string as a 2-digit HEX value ]
// Tests_SRS_UUID_09_016: [ If no failures occur, UUID_to_string shall return uuid_string ]
TEST_FUNCTION(UUID_to_string_succeed)
{
    //Arrange
    char* result;
    char buffer[UUID_STRING_SIZE];

    umock_c_reset_all_calls();
    STRICT_EXPECTED_CALL(malloc(UUID_STRING_SIZE * sizeof(char)))
        .SetReturn(buffer);

    //Act
    result = UUID_to_string(&TEST_UUID);

    //Assert
    ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls());
    ASSERT_ARE_EQUAL(char_ptr, TEST_UUID_STRING, result);
}

// Tests_SRS_UUID_09_013: [ If uuid_string fails to be allocated, UUID_to_string shall return NULL ]
// Tests_SRS_UUID_09_015: [ If uuid_string fails to be set, UUID_to_string shall return NULL ]
TEST_FUNCTION(UUID_to_string_failure_checks)
{
    //Arrange
    char* result;
    char buffer[UUID_STRING_SIZE];
    size_t i;

    ASSERT_ARE_EQUAL(int, 0, umock_c_negative_tests_init());

    umock_c_reset_all_calls();
    STRICT_EXPECTED_CALL(malloc(UUID_STRING_SIZE * sizeof(char)))
        .SetReturn(buffer);

    umock_c_negative_tests_snapshot();

    for (i = 0; i < umock_c_negative_tests_call_count(); i++)
    {
        char temp_str[64];

        umock_c_negative_tests_reset();
        umock_c_negative_tests_fail_call(i);

        (void)sprintf(temp_str, "On failed call %lu", (unsigned long)i);

        // act
        result = UUID_to_string(&TEST_UUID);

        // assert
        ASSERT_IS_NULL(result, temp_str);
    }

    umock_c_negative_tests_reset();
    umock_c_negative_tests_deinit();
}

// Tests_SRS_UUID_09_007: [ If uuid_string or uuid are NULL, UUID_from_string shall return a non-zero value ]
TEST_FUNCTION(UUID_from_string_NULL_uuid_string)
{
    //Arrange
    int result;
    UUID_T uuid;

    umock_c_reset_all_calls();

    //Act
    result = UUID_from_string(NULL, &uuid);

    //Assert
    ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls());
    ASSERT_ARE_NOT_EQUAL(int, 0, result);
}

// Tests_SRS_UUID_09_007: [ If uuid_string or uuid are NULL, UUID_from_string shall return a non-zero value ]
TEST_FUNCTION(UUID_from_string_NULL_uuid)
{
    //Arrange
    int result;

    umock_c_reset_all_calls();

    //Act
    result = UUID_from_string(TEST_UUID_STRING, NULL);

    //Assert
    ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls());
    ASSERT_ARE_NOT_EQUAL(int, 0, result);
}

// Tests_SRS_UUID_09_008: [ Each pair of digits in uuid_string, excluding dashes, shall be read as a single HEX value and saved on the respective position in uuid ]
// Tests_SRS_UUID_09_010: [ If no failures occur, UUID_from_string shall return zero ]
TEST_FUNCTION(UUID_from_string_succeed)
{
    //Arrange
    int result;
    UUID_T uuid;

    umock_c_reset_all_calls();

    //Act
    result = UUID_from_string(TEST_UUID_STRING, &uuid);

    //Assert
    ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls());
    ASSERT_ARE_EQUAL(int, 0, result);

    {
        int i;
        for (i = 0; i < UUID_OCTET_COUNT; i++)
        {
            ASSERT_ARE_EQUAL(int, TEST_UUID[i], uuid[i]);
        }
    }
}

TEST_FUNCTION(UUID_from_string_invalid_fails)
{
    //Arrange
    int result;
    UUID_T uuid;

    umock_c_reset_all_calls();

    for (size_t i = 0; i < sizeof(INVALID_UUIDS)/sizeof(INVALID_UUIDS[0]); i++)
    {
        //Act
        result = UUID_from_string(INVALID_UUIDS[i], &uuid);

        //Assert
        ASSERT_ARE_NOT_EQUAL(int, 0, result);
    }
}

// Tests_SRS_UUID_09_009: [ If uuid fails to be generated, UUID_from_string shall return a non-zero value ]
// To be implemented once sscanf mock is implemented.

END_TEST_SUITE(uuid_unittests)
