#include <string.h> /* for memcmp */

#include "list.h"
#include "unity.h"

// I rather dislike keeping line numbers updated, so I made my own macro to ditch the line number
#define MY_RUN_TEST(func) RUN_TEST(func, 0)

static list_admin* mylist = NULL;
typedef struct
{
    int i;
    int j;
} test_struct;

// compare function that returns 0 if test_struct.i matches, it ignores .j
static int compare_test_func(const void* p1, const void* p2, size_t size)
{
    size = size; // to prevent warnings
    const test_struct* data1 = (const test_struct*)p1;
    const test_struct* data2 = (const test_struct*)p2;
    return data1->i - data2->i;
}
// check n items in list against verify_data
// IMPORTANT: this function must check using list_get_element,
//            else the test for that function is no longer useful
static void check_n_list_elements_ok(list_admin* admin, int nr_items, const test_struct* verify_data)
{
    for(int i = 0; i < nr_items; i++)
    {
        test_struct* ptr = list_get_element(admin, i);
        TEST_ASSERT_NOT_NULL(ptr);
        TEST_ASSERT_EQUAL(0, memcmp(verify_data+i, ptr, sizeof(*ptr)));
    }
}


void setUp(void)
{
    // This is run before EACH test
    mylist = construct_list(sizeof(test_struct));
    TEST_ASSERT_NOT_NULL(mylist);
}

void tearDown(void)
{
    // This is run after EACH test
    destruct_list(&mylist);
    TEST_ASSERT_NULL(mylist);
}

static void test_ConstructList(void)
{
    TEST_ASSERT_NULL(mylist->head);
    TEST_ASSERT_EQUAL(sizeof(test_struct), mylist->element_size);
    TEST_ASSERT_EQUAL(0, mylist->nr_elements);
}

static void test_NrElements_parameters(void)
{
    TEST_ASSERT_EQUAL(-1, list_get_nr_elements(NULL));
}

static void test_AddData_parameters(void)
{
    TEST_ASSERT_EQUAL(-1, list_add_tail(mylist, NULL));
    TEST_ASSERT_EQUAL(-1, list_add_tail(NULL, mylist));
}

static void test_AddData(void)
{
    test_struct data = {3, 4};
    TEST_ASSERT_EQUAL(0, list_add_tail(mylist, &data));

    TEST_ASSERT_NOT_NULL(mylist->head);
    TEST_ASSERT_EQUAL(1, list_get_nr_elements(mylist));
    list_head* head = mylist->head;
    test_struct* element = (test_struct*)(head+1);
    TEST_ASSERT_EQUAL(data.i, element->i);
    TEST_ASSERT_EQUAL(data.j, element->j);
}

static void test_Add2PiecesOfData(void)
{
    test_struct data1 = {8, 9};
    test_struct data2 = {-3, -4};
    TEST_ASSERT_EQUAL(0, list_add_tail(mylist, &data1));
    TEST_ASSERT_EQUAL(0, list_add_tail(mylist, &data2));

    TEST_ASSERT_NOT_NULL(mylist->head);
    TEST_ASSERT_NOT_NULL(mylist->head->next);
    TEST_ASSERT_EQUAL(2, list_get_nr_elements(mylist));
    list_head* head = mylist->head->next;
    test_struct* element = (test_struct*)(head+1);
    TEST_ASSERT_EQUAL(data2.i, element->i);
    TEST_ASSERT_EQUAL(data2.j, element->j);
}

static void test_GetElement_parameters(void)
{
    TEST_ASSERT_NULL(list_get_element(NULL, 0));
}

static void test_GetElement(void)
{
    const test_struct data[] = {{8, 9}, {-3, -4}, {21, 56}, {0, 0}};
    const int nr_elements = sizeof(data)/sizeof(data[0]);

    for(int i = 0; i < nr_elements; i++)
    {
        TEST_ASSERT_EQUAL(0, list_add_tail(mylist, &(data[i])));
    }

    check_n_list_elements_ok(mylist, nr_elements, data); // checks using list_get_element

    TEST_ASSERT_NULL(list_get_element(mylist, nr_elements));
    TEST_ASSERT_NULL(list_get_element(mylist, -1));
}

static void test_GetLast_parameters(void)
{
    TEST_ASSERT_NULL(list_get_last(NULL));
}

static void test_GetLast(void)
{
    const test_struct data[] = {{8, 9}, {-3, -4}, {21, 56}, {0, 0}};
    const int nr_elements = sizeof(data)/sizeof(data[0]);

    TEST_ASSERT_NULL(list_get_last(mylist));
    for(int i = 0; i < nr_elements; i++)
    {
        TEST_ASSERT_EQUAL(0, list_add_tail(mylist, &(data[i])));
        test_struct* ptr = list_get_last(mylist);
        TEST_ASSERT_NOT_NULL(ptr);
        TEST_ASSERT_EQUAL(0, memcmp(&(data[i]), ptr, sizeof(*ptr)));
    }
}

static void test_IndexOf_parameters(void)
{
    TEST_ASSERT_EQUAL(-1, list_index_of(mylist, NULL));
    TEST_ASSERT_EQUAL(-1, list_index_of(NULL, mylist));
}

static void test_IndexOf(void)
{
    const test_struct real_data[] = {{8, 9}, {-3, -4}, {21, 56}};
    const int nr_real_elements = sizeof(real_data)/sizeof(real_data[0]);
    const test_struct false_data[] = {{-3, 9}, {8, 56}, {21, -4}, {0, 0}};
    const int nr_false_elements = sizeof(false_data)/sizeof(false_data[0]);
    for(int i = 0; i < nr_real_elements; i++)
    {
        TEST_ASSERT_EQUAL(0, list_add_tail(mylist, &(real_data[i])));
    }

    for(int i = 0; i < nr_real_elements; i++)
    {
        TEST_ASSERT_EQUAL(i, list_index_of(mylist, &(real_data[i])));
    }
    for(int i = 0; i < nr_false_elements; i++)
    {
        TEST_ASSERT_EQUAL(-1, list_index_of(mylist, &(false_data[i])));
    }
}

static void test_IndexOfSpecial_parameters(void)
{
    TEST_ASSERT_EQUAL(-1, list_index_of_special(mylist, NULL, compare_test_func));
    TEST_ASSERT_EQUAL(-1, list_index_of_special(NULL, mylist, compare_test_func));
    TEST_ASSERT_EQUAL(-1, list_index_of_special(mylist, mylist, NULL));
}

static void test_IndexOfSpecial(void)
{
    const test_struct data[] = {{8, 9}, {-3, -4}, {21, 56}}; // data in list
    const test_struct real_data[] = {{8, -4}, {-3, 56}, {21, 9}}; // data to search for (i equals)
    const int nr_real_elements = sizeof(real_data)/sizeof(real_data[0]);
    const test_struct false_data[] = {{-2, 9}, {9, -4}, {22, 56}, {0, 0}}; // data that doesn't exist
    const int nr_false_elements = sizeof(false_data)/sizeof(false_data[0]);

    // first make sure our compare function works
    for(int i = 0; i < nr_real_elements; i++)
    {
        TEST_ASSERT_EQUAL(0, compare_test_func(&(data[i]), &(real_data[i]), 0));
        for (int j = 0; j < nr_false_elements; j++)
        {
            TEST_ASSERT_NOT_EQUAL(0, compare_test_func(&(data[i]), &(false_data[j]), 0))
        }
    }

    for(int i = 0; i < nr_real_elements; i++)
    {
        TEST_ASSERT_EQUAL(0, list_add_tail(mylist, &(data[i])));
    }

    for(int i = 0; i < nr_real_elements; i++)
    {
        TEST_ASSERT_EQUAL(i, list_index_of_special(mylist, &(real_data[i]), compare_test_func));
    }
    for(int i = 0; i < nr_false_elements; i++)
    {
        TEST_ASSERT_EQUAL(-1, list_index_of_special(mylist, &(false_data[i]), compare_test_func));
    }
}

static void test_DeleteItem_parameters(void)
{
    TEST_ASSERT_EQUAL(-1, list_delete_item(NULL, 0));
    TEST_ASSERT_EQUAL(-1, list_delete_item(mylist, -1));
}

static void test_DeleteItem(void)
{
    const test_struct data[] = {{8, 9}, {-3, -4}, {21, 56}, {0, 0}, {12, 12345}};
    int nr_elements = sizeof(data)/sizeof(data[0]);
    const test_struct data_noFirst[] = {{-3, -4}, {21, 56}, {0, 0}, {12, 12345}};
    const test_struct data_noLast[] = {{-3, -4}, {21, 56}, {0, 0}};
    const test_struct data_noMiddle[] = {{-3, -4}, {0, 0}};

    TEST_ASSERT_EQUAL(-1, list_delete_item(mylist, 0));
    for(int i = 0; i < nr_elements; i++)
    {
        TEST_ASSERT_EQUAL(0, list_add_tail(mylist, &(data[i])));
    }

    TEST_ASSERT_EQUAL(0, list_delete_item(mylist, 0));
    nr_elements--;
    TEST_ASSERT_EQUAL(nr_elements, list_get_nr_elements(mylist));

    check_n_list_elements_ok(mylist, nr_elements, data_noFirst);

    TEST_ASSERT_EQUAL(0, list_delete_item(mylist, nr_elements-1));
    nr_elements--;
    TEST_ASSERT_EQUAL(nr_elements, list_get_nr_elements(mylist));

    check_n_list_elements_ok(mylist, nr_elements, data_noLast);

    TEST_ASSERT_EQUAL(0, list_delete_item(mylist, 1));
    nr_elements--;
    TEST_ASSERT_EQUAL(nr_elements, list_get_nr_elements(mylist));

    check_n_list_elements_ok(mylist, nr_elements, data_noMiddle);
}

static void test_DeleteLast_parameters(void)
{
    TEST_ASSERT_EQUAL(-1, list_delete_last(NULL));
}

static void test_DeleteLast(void)
{
    const test_struct data[] = {{8, 9}, {-3, -4}, {21, 56}, {0, 0}, {12, 12345}};
    int nr_elements = sizeof(data)/sizeof(data[0]);

    TEST_ASSERT_EQUAL(-1, list_delete_last(mylist));
    for(int i = 0; i < nr_elements; i++)
    {
        TEST_ASSERT_EQUAL(0, list_add_tail(mylist, &(data[i])));
    }

    for (int i = nr_elements-1; i >= 0; i--)
    {
        TEST_ASSERT_EQUAL(0, list_delete_last(mylist));
        TEST_ASSERT_EQUAL(i, list_get_nr_elements(mylist));
        check_n_list_elements_ok(mylist, i, data);
    }

    TEST_ASSERT_NULL(mylist->head);
    TEST_ASSERT_EQUAL(0, list_get_nr_elements(mylist));
}

int main(void)
{
    UnityBegin();

    MY_RUN_TEST(test_ConstructList);
    MY_RUN_TEST(test_NrElements_parameters);
    MY_RUN_TEST(test_AddData_parameters);
    MY_RUN_TEST(test_AddData);
    MY_RUN_TEST(test_Add2PiecesOfData);
    MY_RUN_TEST(test_GetElement_parameters);
    MY_RUN_TEST(test_GetElement);
    MY_RUN_TEST(test_GetLast_parameters);
    MY_RUN_TEST(test_GetLast);
    MY_RUN_TEST(test_IndexOf_parameters);
    MY_RUN_TEST(test_IndexOf);
    MY_RUN_TEST(test_IndexOfSpecial_parameters);
    MY_RUN_TEST(test_IndexOfSpecial);
    MY_RUN_TEST(test_DeleteItem_parameters);
    MY_RUN_TEST(test_DeleteItem);
    MY_RUN_TEST(test_DeleteLast_parameters);
    MY_RUN_TEST(test_DeleteLast);
    // MY_RUN_TEST();
    // MY_RUN_TEST();
    // MY_RUN_TEST();
    // MY_RUN_TEST();

    return UnityEnd();
}
