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

#include "azure_c_shared_utility/doublylinkedlist.h"
#include "testrunnerswitcher.h"

typedef struct simpleItem_tag
{
    unsigned char index;
    DLIST_ENTRY link;
} simpleItem,*pSimpleItem;

static simpleItem simp1 = { 1, { NULL, NULL } };
static simpleItem simp2 = { 2, { NULL, NULL } };
static simpleItem simp3 = { 3, { NULL, NULL } };
static simpleItem simp4 = { 4, { NULL, NULL } };
static simpleItem simp5 = { 5, { NULL, NULL } };

static TEST_MUTEX_HANDLE g_testByTest;

BEGIN_TEST_SUITE(doublylinkedlist_unittests)

TEST_SUITE_INITIALIZE(TestClassInitialize)
{
    g_testByTest = TEST_MUTEX_CREATE();
    ASSERT_IS_NOT_NULL(g_testByTest);
}

TEST_SUITE_CLEANUP(TestClassCleanup)
{
    TEST_MUTEX_DESTROY(g_testByTest);
}

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

TEST_FUNCTION_CLEANUP(TestMethodCleanup)
{
    TEST_MUTEX_RELEASE(g_testByTest);
}

    /* Tests_SRS_DLIST_06_005: [DList_InitializeListHead will initialize the Flink & Blink to the address of the DLIST_ENTRY.] */
    TEST_FUNCTION(DList_InitializeListHead_with_a_list_head_points_Flink_and_Blink_to_its_address)
    {
        // arrange
        DLIST_ENTRY head = { 0 };

        // act
        DList_InitializeListHead(&head);

        // assert
        ASSERT_ARE_EQUAL(void_ptr, &head, head.Flink);
        ASSERT_ARE_EQUAL(void_ptr, &head, head.Blink);
    }

    /* Tests_SRS_DLIST_06_003: [DList_IsListEmpty shall return a non-zero value if there are no DLIST_ENTRY's on this list other than the list head.] */
    TEST_FUNCTION(DList_IsListEmpty_with_a_list_head_only_returns_empty)
    {
        // arrange
        DLIST_ENTRY local = { 0 };
        int result;

        DList_InitializeListHead(&local);
        // act
        result = DList_IsListEmpty(&local);

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

    /* Tests_SRS_DLIST_06_004: [DList_IsListEmpty shall return 0 if there is one or more items in the list.]*/
    TEST_FUNCTION(DList_IsListEmpty_with_a_list_head_and_items_returns_empty)
    {
        // arrange
        DLIST_ENTRY head = { 0 };
        int result;

        DList_InitializeListHead(&head);
        DList_InsertTailList(&head, &(simp1.link));
        // act
        result = DList_IsListEmpty(&head);

        // assert
        ASSERT_ARE_EQUAL(int, 0, result);
    }

    /* Tests_SRS_DLIST_06_006: [DListInsertTailList shall place the DLIST_ENTRY at the end of the list defined by the listHead parameter.] */
    TEST_FUNCTION(DList_InsertTailList_with_a_list_head_inserts_1st_item_at_the_end_of_the_list)
    {
        // arrange
        DLIST_ENTRY head = { 0 };

        DList_InitializeListHead(&head);
        // act
        DList_InsertTailList(&head, &(simp1.link));

        // assert
        ASSERT_ARE_EQUAL(void_ptr, head.Flink, &(simp1.link));
        ASSERT_ARE_EQUAL(void_ptr, head.Blink, &(simp1.link));
    }

    /* Tests_SRS_DLIST_06_006: [DListInsertTailList shall place the DLIST_ENTRY at the end of the list defined by the listHead parameter.] */
    TEST_FUNCTION(DList_InsertTailList_with_a_list_head_inserts_2nd_item_at_the_end_of_the_list)
    {
        // arrange
        DLIST_ENTRY head = { 0 };

        DList_InitializeListHead(&head);
        DList_InsertTailList(&head, &(simp1.link));
        // act
        DList_InsertTailList(&head, &(simp2.link));

        // assert
        ASSERT_ARE_EQUAL(void_ptr, head.Flink, &(simp1.link));
        ASSERT_ARE_EQUAL(void_ptr, simp1.link.Flink, &(simp2.link));
        ASSERT_ARE_EQUAL(void_ptr, simp2.link.Flink, &head);
        ASSERT_ARE_EQUAL(void_ptr, head.Blink, &(simp2.link));
        ASSERT_ARE_EQUAL(void_ptr, simp1.link.Blink, &head);
        ASSERT_ARE_EQUAL(void_ptr, simp2.link.Blink, &(simp1.link));
    }

    /* Tests_SRS_DLIST_06_007: [DList_AppendTailList shall place the list defined by ListToAppend at the end of the list defined by the listHead parameter.] */
    TEST_FUNCTION(DList_AppendTailList_adds_listToAppend_after_listHead)
    {
        // arrange
        DLIST_ENTRY listHead = { 0 };
        PDLIST_ENTRY currentEntry;

        DList_InitializeListHead(&listHead);
        DList_InsertTailList(&listHead, &(simp1.link));

        DList_InitializeListHead(&simp2.link);
        DList_InsertTailList(&simp2.link, &simp4.link);
        DList_InsertTailList(&simp2.link, &simp5.link);
        // act
        DList_AppendTailList(&listHead, &simp2.link);

        // assert
        // Go forwards
        ASSERT_ARE_EQUAL(int, 0, DList_IsListEmpty(&listHead));
        currentEntry = listHead.Flink;
        ASSERT_ARE_EQUAL(void_ptr, currentEntry, &simp1.link);
        ASSERT_ARE_EQUAL(short, (short)1, containingRecord(currentEntry, simpleItem, link)->index);
        currentEntry = currentEntry->Flink;
        ASSERT_ARE_EQUAL(void_ptr, currentEntry, &simp2.link);
        ASSERT_ARE_EQUAL(short, (short)2, containingRecord(currentEntry, simpleItem, link)->index);
        currentEntry = currentEntry->Flink;
        ASSERT_ARE_EQUAL(void_ptr, currentEntry, &simp4.link);
        ASSERT_ARE_EQUAL(short, (short)4, containingRecord(currentEntry, simpleItem, link)->index);
        currentEntry = currentEntry->Flink;
        ASSERT_ARE_EQUAL(void_ptr, currentEntry, &simp5.link);
        ASSERT_ARE_EQUAL(short, (short)5, containingRecord(currentEntry, simpleItem, link)->index);
        currentEntry = currentEntry->Flink;
        ASSERT_ARE_EQUAL(void_ptr, currentEntry, &listHead);

        // Now back
        currentEntry = listHead.Blink;
        ASSERT_ARE_EQUAL(void_ptr, currentEntry, &simp5.link);
        ASSERT_ARE_EQUAL(short, (short)5, containingRecord(currentEntry, simpleItem, link)->index);
#ifdef _MSC_VER
#pragma warning(suppress: 6011) /* test code, should crash if this is truly NULL */
#endif
        currentEntry = currentEntry->Blink;
        ASSERT_ARE_EQUAL(void_ptr, currentEntry, &simp4.link);
        ASSERT_ARE_EQUAL(short, (short)4, containingRecord(currentEntry, simpleItem, link)->index);
        currentEntry = currentEntry->Blink;
        ASSERT_ARE_EQUAL(void_ptr, currentEntry, &simp2.link);
        ASSERT_ARE_EQUAL(short, (short)2, containingRecord(currentEntry, simpleItem, link)->index);
        currentEntry = currentEntry->Blink;
        ASSERT_ARE_EQUAL(void_ptr, currentEntry, &simp1.link);
        ASSERT_ARE_EQUAL(short, (short)1, containingRecord(currentEntry, simpleItem, link)->index);
        currentEntry = currentEntry->Blink;
        ASSERT_ARE_EQUAL(void_ptr, currentEntry, &listHead);
    }

    /* Tests_SRS_DLIST_06_010: [DList_RemoveEntryList shall return non-zero if the remaining list is empty.] */
    TEST_FUNCTION(DList_RemoveEntryList_with_head_only_in_list_shall_return_non_zero)
    {
        // arrange
        DLIST_ENTRY listHead = { 0 };
        int resultOfRemove;

        DList_InitializeListHead(&listHead);

        // act
        resultOfRemove = DList_RemoveEntryList(&listHead);

        // assert
        ASSERT_ARE_NOT_EQUAL(int, 0, resultOfRemove);
    }

    /* Tests_SRS_DLIST_06_008: [DList_RemoveEntryList shall remove a listEntry from whatever list it is properly part of.] */
    /* Tests_SRS_DLIST_06_010: [DList_RemoveEntryList shall return non-zero if the remaining list is empty.] */
    /* Tests_SRS_DLIST_06_009: [The remaining list is properly formed.] */
    TEST_FUNCTION(DList_RemoveEntryList_with_one_element_and_removing_that_one_element_shall_return_non_zero)
    {
        // arrange
        DLIST_ENTRY listHead = { 0 };
        int resultOfRemove;

        DList_InitializeListHead(&listHead);
        DList_InsertTailList(&listHead, &(simp1.link));

        // act
        resultOfRemove = DList_RemoveEntryList(&(simp1.link));

        // assert
        ASSERT_ARE_NOT_EQUAL(int, 0, resultOfRemove);
        ASSERT_ARE_EQUAL(void_ptr, &listHead, listHead.Blink);
        ASSERT_ARE_EQUAL(void_ptr, &listHead, listHead.Flink);
    }

    /* Tests_SRS_DLIST_06_008: [DList_RemoveEntryList shall remove a listEntry from whatever list it is properly part of.] */
    /* Tests_SRS_DLIST_06_010: [DList_RemoveEntryList shall return non-zero if the remaining list is empty.] */
    TEST_FUNCTION(DList_RemoveEntryList_with_one_element_and_removing_the_head_shall_return_non_zero)
    {
        // arrange
        DLIST_ENTRY listHead = { 0 };
        int resultOfRemove;

        DList_InitializeListHead(&listHead);
        DList_InsertTailList(&listHead, &(simp1.link));

        // act
        resultOfRemove = DList_RemoveEntryList(&listHead);

        // assert
        ASSERT_ARE_NOT_EQUAL(int, 0, resultOfRemove);
        ASSERT_ARE_EQUAL(void_ptr, &(simp1.link), simp1.link.Blink);
        ASSERT_ARE_EQUAL(void_ptr, &(simp1.link), simp1.link.Flink);
    }

    /* Tests_SRS_DLIST_06_008: [DList_RemoveEntryList shall remove a listEntry from whatever list it is properly part of.] */
    /* Tests_SRS_DLIST_06_009: [The remaining list is properly formed.] */
    /* Tests_SRS_DLIST_06_010: [DList_RemoveEntryList shall return non-zero if the remaining list is empty.] */
    /* Tests_SRS_DLIST_06_011: [DList_RemoveEntryList shall return zero if the remaining list is NOT empty.] */
    TEST_FUNCTION(DList_RemoveEntryList_with_three_elements_and_removing_the_first_return_zero)
    {
        // arrange
        DLIST_ENTRY listHead = { 0 };
        int resultOfRemove;

        DList_InitializeListHead(&listHead);
        DList_InsertTailList(&listHead, &(simp1.link));
        DList_InsertTailList(&listHead, &(simp2.link));
        DList_InsertTailList(&listHead, &(simp3.link));

        // act
        resultOfRemove = DList_RemoveEntryList(&(simp1.link));

        // assert
        ASSERT_ARE_EQUAL(int, 0, resultOfRemove);
        ASSERT_ARE_EQUAL(void_ptr, listHead.Flink, &(simp2.link));
        ASSERT_ARE_EQUAL(void_ptr, simp2.link.Flink, &(simp3.link));
        ASSERT_ARE_EQUAL(void_ptr, simp3.link.Flink, &listHead);
        ASSERT_ARE_EQUAL(void_ptr, listHead.Blink, &(simp3.link));
        ASSERT_ARE_EQUAL(void_ptr, simp3.link.Blink, &(simp2.link));
        ASSERT_ARE_EQUAL(void_ptr, simp2.link.Blink, &(listHead));
    }

    /* Tests_SRS_DLIST_06_008: [DList_RemoveEntryList shall remove a listEntry from whatever list it is properly part of.] */
    /* Tests_SRS_DLIST_06_009: [The remaining list is properly formed.] */
    /* Tests_SRS_DLIST_06_010: [DList_RemoveEntryList shall return non-zero if the remaining list is empty.] */
    /* Tests_SRS_DLIST_06_011: [DList_RemoveEntryList shall return zero if the remaining list is NOT empty.] */
    TEST_FUNCTION(DList_RemoveEntryList_with_three_elements_and_removing_the_last_return_zero)
    {
        // arrange
        DLIST_ENTRY listHead = { 0 };
        int resultOfRemove;

        DList_InitializeListHead(&listHead);
        DList_InsertTailList(&listHead, &(simp1.link));
        DList_InsertTailList(&listHead, &(simp2.link));
        DList_InsertTailList(&listHead, &(simp3.link));

        // act
        resultOfRemove = DList_RemoveEntryList(&(simp3.link));

        // assert
        ASSERT_ARE_EQUAL(int, 0, resultOfRemove);
        ASSERT_ARE_EQUAL(void_ptr, listHead.Flink, &(simp1.link));
        ASSERT_ARE_EQUAL(void_ptr, simp1.link.Flink, &(simp2.link));
        ASSERT_ARE_EQUAL(void_ptr, simp2.link.Flink, &listHead);
        ASSERT_ARE_EQUAL(void_ptr, listHead.Blink, &(simp2.link));
        ASSERT_ARE_EQUAL(void_ptr, simp2.link.Blink, &(simp1.link));
        ASSERT_ARE_EQUAL(void_ptr, simp1.link.Blink, &(listHead));
    }

    /* Tests_SRS_DLIST_06_008: [DList_RemoveEntryList shall remove a listEntry from whatever list it is properly part of.] */
    /* Tests_SRS_DLIST_06_009: [The remaining list is properly formed.] */
    /* Tests_SRS_DLIST_06_010: [DList_RemoveEntryList shall return non-zero if the remaining list is empty.] */
    /* Tests_SRS_DLIST_06_011: [DList_RemoveEntryList shall return zero if the remaining list is NOT empty.] */
    TEST_FUNCTION(DList_RemoveEntryList_with_three_elements_and_removing_the_middle_return_zero)
    {
        // arrange
        DLIST_ENTRY listHead = { 0 };
        int resultOfRemove;

        DList_InitializeListHead(&listHead);
        DList_InsertTailList(&listHead, &(simp1.link));
        DList_InsertTailList(&listHead, &(simp2.link));
        DList_InsertTailList(&listHead, &(simp3.link));

        // act
        resultOfRemove = DList_RemoveEntryList(&(simp2.link));

        // assert
        ASSERT_ARE_EQUAL(int, 0, resultOfRemove);
        ASSERT_ARE_EQUAL(void_ptr, listHead.Flink, &(simp1.link));
        ASSERT_ARE_EQUAL(void_ptr, simp1.link.Flink, &(simp3.link));
        ASSERT_ARE_EQUAL(void_ptr, simp3.link.Flink, &listHead);
        ASSERT_ARE_EQUAL(void_ptr, listHead.Blink, &(simp3.link));
        ASSERT_ARE_EQUAL(void_ptr, simp3.link.Blink, &(simp1.link));
        ASSERT_ARE_EQUAL(void_ptr, simp1.link.Blink, &(listHead));
    }

    /* Tests_SRS_DLIST_06_013: [DList_RemoveHeadList shall return listHead if that's the only item in the list.] */
    TEST_FUNCTION(DList_RemoveHeadList_with_only_head_shall_return_the_head)
    {
        // arrange
        DLIST_ENTRY head = { 0 };
        PDLIST_ENTRY returnedEntry;

        DList_InitializeListHead(&head);

        // act
        returnedEntry = DList_RemoveHeadList(&head);

        // assert
        ASSERT_ARE_EQUAL(void_ptr, &head, returnedEntry);
    }

    /* Tests_SRS_DLIST_06_012: [DList_RemoveHeadList removes the oldest entry from the list defined by the listHead parameter and returns a pointer to that entry.] */
    TEST_FUNCTION(DList_RemoveHeadList_with_one_entry_returns_entry)
    {
        // arrange
        DLIST_ENTRY listHead = { 0 };
        PDLIST_ENTRY returnedEntry;

        DList_InitializeListHead(&listHead);
        DList_InsertTailList(&listHead, &(simp1.link));

        // act
        returnedEntry = DList_RemoveHeadList(&listHead);

        // assert
        ASSERT_ARE_NOT_EQUAL(int, 0, DList_IsListEmpty(&listHead));
        ASSERT_ARE_EQUAL(void_ptr, &(simp1.link), returnedEntry);
    }

    /* Tests_SRS_DLIST_06_012: [DList_RemoveHeadList removes the oldest entry from the list defined by the listHead parameter and returns a pointer to that entry.] */
    TEST_FUNCTION(DList_RemoveHeadList_with_two_entries_returns_first_entry)
    {
        // arrange
        DLIST_ENTRY listHead = { 0 };
        PDLIST_ENTRY returnedEntry;

        DList_InitializeListHead(&listHead);
        DList_InsertTailList(&listHead, &(simp2.link));
        DList_InsertTailList(&listHead, &(simp1.link));

        // act
        returnedEntry = DList_RemoveHeadList(&listHead);

        // assert
        ASSERT_ARE_EQUAL(int, 0, DList_IsListEmpty(&listHead));
        ASSERT_ARE_EQUAL(void_ptr, &(simp2.link), returnedEntry);
    }

    /*Tests_SRS_DLIST_02_002: [DList_InsertHeadList inserts a singular entry in the list having as head listHead after "head".]*/
    TEST_FUNCTION(DList_InsertHeadList_with_empty_list_succeeds)
    {
        ///arrange
        DLIST_ENTRY listHead;
        DLIST_ENTRY toBeInserted;
        DList_InitializeListHead(&listHead);

        ///act
        DList_InsertHeadList(&listHead, &toBeInserted);

        ///assert
        ASSERT_ARE_EQUAL(void_ptr, listHead.Flink, &toBeInserted);
        ASSERT_ARE_EQUAL(void_ptr, listHead.Blink, &toBeInserted);
        ASSERT_ARE_EQUAL(void_ptr, toBeInserted.Flink , &listHead);
        ASSERT_ARE_EQUAL(void_ptr, toBeInserted.Blink, &listHead);
    }

    /*Tests_SRS_DLIST_02_002: [DList_InsertHeadList inserts a singular entry in the list having as head listHead after "head".]*/
    TEST_FUNCTION(DList_InsertHeadList_with_1_item_in_list_succeeds)
    {
        ///arrange
        DLIST_ENTRY listHead;
        DLIST_ENTRY existingInList;
        DLIST_ENTRY toBeInserted;
        DList_InitializeListHead(&listHead);
        DList_InsertTailList(&listHead, &existingInList); /*would be same as insertHead when it is the first item... */

        ///act
        DList_InsertHeadList(&listHead, &toBeInserted);

        ///assert
        ASSERT_ARE_EQUAL(void_ptr, listHead.Flink, &toBeInserted);
        ASSERT_ARE_EQUAL(void_ptr, listHead.Blink, &existingInList);
        ASSERT_ARE_EQUAL(void_ptr, existingInList.Flink, &listHead);
        ASSERT_ARE_EQUAL(void_ptr, existingInList.Blink, &toBeInserted);
        ASSERT_ARE_EQUAL(void_ptr, toBeInserted.Flink, &existingInList);
        ASSERT_ARE_EQUAL(void_ptr, toBeInserted.Blink, &listHead);

    }


END_TEST_SUITE(doublylinkedlist_unittests)
