#include "list.h"

#include <assert.h>
#include <stdint.h>
#include <stdio.h>

// As simple as it gets, just needs to be initialized correctly
// - list_init
// - list_is_empty
// - list_get_len
// - list_clear
void create_and_clear_list(void)
{
    List my_cool_list = list_init();

    // verify no data 
    assert(my_cool_list.head == NULL);
    assert(my_cool_list.tail == NULL);
    assert(list_get_len(&my_cool_list) == 0);
    assert(list_is_empty(&my_cool_list));

    list_clear(&my_cool_list);

    assert(list_is_empty(&my_cool_list));
}

// Push back one entry, make sure it looks as expected
// tests:
// - list_init
// - list_push_back
// - list_is_empty
// - list_get_len
// - list_get_at_idx
// - list_clear
void push_back_one_entry(void)
{
    List my_cool_list = list_init();

    // verify no data 
    assert(my_cool_list.head == NULL);
    assert(my_cool_list.tail == NULL);
    assert(list_get_len(&my_cool_list) == 0);
    assert(list_is_empty(&my_cool_list));

    int test_data = 1337;

    list_push_back(&my_cool_list, &test_data);

    // should have data and be the same head/tail
    assert(my_cool_list.head != NULL);
    assert(my_cool_list.tail != NULL);
    assert(my_cool_list.head == my_cool_list.tail);
    assert(list_get_len(&my_cool_list) == 1);

    // pointer should be the same
    assert(my_cool_list.head->data == &test_data);
    // and consequently the value should be the same
    assert(*(int*)(my_cool_list.head->data) == test_data);
    assert(list_get_at_idx(&my_cool_list, 0) == &test_data);
    assert(list_get_at_idx(&my_cool_list, 1) == NULL);
    assert(!list_is_empty(&my_cool_list));
    // clear the list
    list_clear(&my_cool_list);

    // verify no data 
    assert(my_cool_list.head == NULL);
    assert(my_cool_list.tail == NULL);
    assert(list_is_empty(&my_cool_list));
}

// Push front one entry, make sure it looks as expected
// - list_init
// - list_push_front
// - list_is_empty
// - list_get_len
// - list_get_at_idx
// - list_clear
void push_front_one_entry(void)
{
    List my_cool_list = list_init();

    // verify no data 
    assert(my_cool_list.head == NULL);
    assert(my_cool_list.tail == NULL);
    assert(list_get_len(&my_cool_list) == 0);
    assert(list_is_empty(&my_cool_list));

    int test_data = 1337;

    list_push_front(&my_cool_list, &test_data);

    // should have data and be the same head/tail
    assert(my_cool_list.head != NULL);
    assert(my_cool_list.tail != NULL);
    assert(my_cool_list.head == my_cool_list.tail);
    assert(list_get_len(&my_cool_list) == 1);
    assert(!list_is_empty(&my_cool_list));

    // pointer should be the same
    assert(my_cool_list.head->data == &test_data);
    // and consequently the value should be the same
    assert(*(int*)(my_cool_list.head->data) == test_data);
    assert(list_get_at_idx(&my_cool_list, 0) == &test_data);
    assert(list_get_at_idx(&my_cool_list, 1) == NULL);
    // clear the list
    list_clear(&my_cool_list);

    // verify no data 
    assert(my_cool_list.head == NULL);
    assert(my_cool_list.tail == NULL);
    assert(list_is_empty(&my_cool_list));
}

// This is meant to be a full test of every function including the use of
// the ListItr iterator
// 
// Push back three entries. To cover the 3 states of nodes outside of single node
// lists. 3 states being, node at the front, node at the middle, and node at the end
//
// Afterwards remove one entry with `list_remove_at_idx` and then remove another
// with `list_remove_node`
//
// Lastly, push to the front the same 3 entries and verify the state of the list
// before detroying.
//
// - list_init
// - list_push_front
// - list_is_empty
// - list_get_len
// - list_get_at_idx
// - list_remove_at_idx
// - list_itr_create
// - list_itr_next
// - list_clear
// - list_itr_remove_node_current;
void push_back_three_remove_push_front_three_entries(void)
{
    List my_cool_list = list_init();

    // verify no data 
    assert(my_cool_list.head == NULL);
    assert(my_cool_list.tail == NULL);

    int test_data[3] = {0, 1, 2};

    list_push_back(&my_cool_list, &test_data[0]);

    // Make sure with one entry it looks right
    assert(my_cool_list.head == my_cool_list.tail);

    list_push_back(&my_cool_list, &test_data[1]);

    // Now with two it should be different
    assert(my_cool_list.head != my_cool_list.tail);

    list_push_back(&my_cool_list, &test_data[2]);

    // 3 entries?
    assert(list_get_len(&my_cool_list) == 3);

    // now use an iterator to examine each node
    int* data;

    ListItr list_itr = list_itr_create(&my_cool_list);

    int itr = 0;

    ListNode* prev_ln = NULL;

    while((data = list_itr_next(&list_itr)))
    {
        assert(data != NULL);
        assert(data == &test_data[itr]);
        ListNode* ln = list_itr.current_node;
        switch(itr)
        {
            case 0:
                assert(ln->prev == NULL);
                assert(ln->next != NULL);
                assert(ln->next->prev == ln);
                break;
            case 1:
                assert(ln->prev == prev_ln);
                assert(ln->prev != NULL);
                assert(ln->next != NULL);
                assert(ln->next->prev == ln);
                assert(ln->prev->next == ln);
                break;
            case 2:
                assert(ln->prev != NULL);
                assert(ln->next == NULL);
                assert(ln->prev->next == ln);
                assert(ln->prev == prev_ln);
                break;
            default:
                assert(false); // shouldn't get here
                break;
        }
        itr++;
        prev_ln = ln;
    }

    // remove the middle entry
    assert(list_remove_at_idx(&my_cool_list, 1));
    // can't remove entry 2 now (doesn't exist)
    assert(!list_remove_at_idx(&my_cool_list, 2));
    assert(list_get_len(&my_cool_list) == 2);

    list_itr = list_itr_create(&my_cool_list);
    data = NULL;
    itr = 0;
    prev_ln = NULL;
    while((data = list_itr_next(&list_itr)))
    {
        assert(data != NULL);
        ListNode* ln = list_itr.current_node;
        switch(itr)
        {
            case 0:
                assert(ln->prev == NULL);
                assert(ln->next != NULL);
                assert(ln->next->prev == ln);
                break;
            case 1:
                assert(ln->prev != NULL);
                assert(ln->next == NULL);
                assert(ln->prev->next == ln);
                assert(ln->prev == prev_ln);
                list_itr_remove_current_node(&list_itr);
                break;
            default:
                assert(false); // shouldn't get here
                break;
        }
        itr++;
        prev_ln = ln;
    }

    assert(list_get_len(&my_cool_list) == 1);
    assert(my_cool_list.head == my_cool_list.tail);

    // now push the same 3 pointers to the front of the list
    list_push_front(&my_cool_list, &test_data[0]);
    list_push_front(&my_cool_list, &test_data[1]);
    list_push_front(&my_cool_list, &test_data[2]);

    // now, the list should be in the order...
    // test_data[2] -> test_data[1] -> test_data[0] -> test_data[0]
    list_itr = list_itr_create(&my_cool_list);
    data = NULL;
    itr = 0;
    while((data = list_itr_next(&list_itr)))
    {
        assert(data != NULL);
        ListNode* ln = list_itr.current_node;

        switch(itr)
        {
            case 0:
                assert(ln->data == &test_data[2]);
                break;
            case 1:
                assert(ln->data == &test_data[1]);
                break;
            case 2:
                assert(ln->data == &test_data[0]);
                break;
            case 3:
                assert(ln->data == &test_data[0]);
                break;
            default:
                assert(false); // shouldn't get here
                break;
        }
        itr++;
    }

    assert(list_get_len(&my_cool_list) == 4);

    // clear the list
    list_clear(&my_cool_list);

    // verify no data 
    assert(my_cool_list.head == NULL);
    assert(my_cool_list.tail == NULL);
    assert(list_is_empty(&my_cool_list));
}

// Test inserting at head, middle, and tail of list
// - list_init
// - list_push_back
// - list_insert
// - list_is_empty
// - list_get_len
// - list_itr_create
// - rev_list_itr_create
// - list_itr_next
// - list_clear
// - list_get_at_idx
void test_list_insertion(void)
{
    List my_cool_list = list_init();

    // verify no data 
    assert(my_cool_list.head == NULL);
    assert(my_cool_list.tail == NULL);
    assert(list_get_len(&my_cool_list) == 0);
    assert(list_is_empty(&my_cool_list));

    const int initial_list_size = 5;
    int test_data[initial_list_size];

    for(int i = 0; i < initial_list_size; i++)
    {
        test_data[i] = i;
        list_push_back(&my_cool_list, &test_data[i]);
    }

    assert(list_get_len(&my_cool_list) == initial_list_size);

    int head_val = 0xDEADBEEF;
    int middle_val = 0x1337;
    int tail_val = 0x10101010;
    int last_tail_val = 0x12345678;

    // insert at head of list
    list_insert(&my_cool_list, &head_val, 0);

    ListItr itr = list_itr_create(&my_cool_list);
    void* data;
    int test_data_itr = 0;

    int index = 0;

    while((data = list_itr_next(&itr)))
    {
        int data_itr = *(int*)data;
        int data_from_get_at_idx = *(int*)list_get_at_idx(&my_cool_list, index);

        if(index == 0)
        {
            assert(data_itr == head_val);
            assert(data_from_get_at_idx == head_val);
        }
        else
        {
            assert(data_itr == test_data[test_data_itr]);
            assert(data_from_get_at_idx == test_data[test_data_itr]);
            test_data_itr++;
        }
        index++;
    }

    assert(list_get_len(&my_cool_list) == initial_list_size + 1);
    
    // insert at tail of list, use large value of 100 to force it to append it
    // to the end of the list
    list_insert(&my_cool_list, &tail_val, 100);

    itr = list_itr_create(&my_cool_list);
    index = 0;
    test_data_itr = 0;

    while((data = list_itr_next(&itr)))
    {
        int data_itr = *(int*)data;
        int data_from_get_at_idx = *(int*)list_get_at_idx(&my_cool_list, index);

        if(index == 0)
        {
            assert(data_itr == head_val);
            assert(data_from_get_at_idx == head_val);
        }
        else if(index == initial_list_size + 1) // for head and tail insertion
        {
            assert(data_itr == tail_val);
            assert(data_from_get_at_idx == tail_val);
        }
        else
        {
            assert(data_itr == test_data[test_data_itr]);
            assert(data_from_get_at_idx == test_data[test_data_itr]);
            test_data_itr++;
        }
        index++;
    }

    assert(list_get_len(&my_cool_list) == initial_list_size + 2);
    
    // insert at "middle" of list
    int insert_loc = 2;
    test_data_itr = 0;
    list_insert(&my_cool_list, &middle_val, insert_loc);

    itr = list_itr_create(&my_cool_list);
    index = 0;

    while((data = list_itr_next(&itr)))
    {
        int data_itr = *(int*)data;
        int data_from_get_at_idx = *(int*)list_get_at_idx(&my_cool_list, index);

        if(index == 0)
        {
            assert(data_itr == head_val);
            assert(data_from_get_at_idx == head_val);
        }
        else if(index == insert_loc)
        {
            assert(data_itr == middle_val);
            assert(data_from_get_at_idx == middle_val);
        }
        else if(index == initial_list_size + 2) // for head+middle+tail
        {
            assert(data_itr == tail_val);
            assert(data_from_get_at_idx == tail_val);
        }
        else
        {
            assert(data_itr == test_data[test_data_itr]);
            assert(data_from_get_at_idx == test_data[test_data_itr]);
            test_data_itr++;
        }
        index++;
    }

    printf("\n");

    ListItr rev_itr = rev_list_itr_create(&my_cool_list);
    index = initial_list_size + 2;
    test_data_itr = initial_list_size - 1;
    while((data = list_itr_next(&rev_itr)))
    {
        int data_itr = *(int*)data;
        int data_from_get_at_idx = *(int*)list_get_at_idx(&my_cool_list, index);

        if(index == 0)
        {
            assert(data_itr == head_val);
            assert(data_from_get_at_idx == head_val);
        }
        else if(index == insert_loc)
        {
            assert(data_itr == middle_val);
            assert(data_from_get_at_idx == middle_val);
        }
        else if(index == initial_list_size + 2) // for head+middle+tail
        {
            assert(data_itr == tail_val);
            assert(data_from_get_at_idx == tail_val);
        }
        else
        {
            assert(data_itr == test_data[test_data_itr]);
            assert(data_from_get_at_idx == test_data[test_data_itr]);
            test_data_itr--;
        }
        index--;
    }

    assert(list_get_len(&my_cool_list) == initial_list_size + 3);

    // insert at the real end of the list, to make sure
    // it works in conjunction with the previous "out of range" insertion for the tail
    int tail_idx = list_get_len(&my_cool_list) - 1;
    test_data_itr = 0;
    list_insert(&my_cool_list, &last_tail_val, tail_idx);

    itr = list_itr_create(&my_cool_list);
    index = 0;

    while((data = list_itr_next(&itr)))
    {
        int data_itr = *(int*)data;
        int data_from_get_at_idx = *(int*)list_get_at_idx(&my_cool_list, index);

        if(index == 0)
        {
            assert(data_itr == head_val);
            assert(data_from_get_at_idx == head_val);
        }
        else if(index == insert_loc)
        {
            assert(data_itr == middle_val);
            assert(data_from_get_at_idx == middle_val);
        }
        else if(index == initial_list_size + 2) // for head+middle+tail
        {
            assert(data_itr == last_tail_val);
            assert(data_from_get_at_idx == last_tail_val);
        }
        else if(index == initial_list_size + 3) // for head+middle+tail+last_tail
        {
            assert(data_itr == tail_val);
            assert(data_from_get_at_idx == tail_val);
        }
        else
        {
            assert(data_itr == test_data[test_data_itr]);
            assert(data_from_get_at_idx == test_data[test_data_itr]);
            test_data_itr++;
        }
        index++;
    }

    assert(list_get_len(&my_cool_list) == initial_list_size + 4);

    // clear the list
    list_clear(&my_cool_list);

    // verify no data 
    assert(my_cool_list.head == NULL);
    assert(my_cool_list.tail == NULL);
    assert(list_is_empty(&my_cool_list));
}

// Test inserting at head, middle, and tail of list
// - list_init
// - list_push_back
// - list_swap
// - list_is_empty
// - list_get_len
// - list_itr_create
// - list_itr_next
// - list_clear
// - list_get_at_idx
void test_list_swap(void)
{
    List my_cool_list = list_init();

    // verify no data 
    assert(my_cool_list.head == NULL);
    assert(my_cool_list.tail == NULL);
    assert(list_get_len(&my_cool_list) == 0);
    assert(list_is_empty(&my_cool_list));

    const int initial_list_size = 5;
    int test_data[initial_list_size];

    for(int i = 0; i < initial_list_size; i++)
    {
        // 0 -> 1 -> 2 -> 3 -> 4
        test_data[i] = i;
        list_push_back(&my_cool_list, &test_data[i]);
    }

    assert(list_get_len(&my_cool_list) == initial_list_size);


    ListItr itr = list_itr_create(&my_cool_list);
    void* data;

    int index = 0;

    while((data = list_itr_next(&itr)))
    {
        // 0 -> 1 -> 2 -> 3 -> 4
        assert(*(int*)list_get_at_idx(&my_cool_list, index) == test_data[index]);
        assert(*(int*)data == test_data[index]);
        index++;
    }

    // swap nothing, out of range
    assert(!list_swap(&my_cool_list, 100, 100));

    itr = list_itr_create(&my_cool_list);
    index = 0;
    while((data = list_itr_next(&itr)))
    {
        // 0 -> 1 -> 2 -> 3 -> 4
        assert(*(int*)list_get_at_idx(&my_cool_list, index) == test_data[index]);
        assert(*(int*)data == test_data[index]);
        index++;
    }

    // swap nothing, in range
    assert(list_swap(&my_cool_list, 0, 0));

    itr = list_itr_create(&my_cool_list);
    index = 0;
    while((data = list_itr_next(&itr)))
    {
        // 0 -> 1 -> 2 -> 3 -> 4
        assert(*(int*)list_get_at_idx(&my_cool_list, index) == test_data[index]);
        assert(*(int*)data == test_data[index]);
        index++;
    }

    // swap head and "middle"
    int mid_idx = initial_list_size / 2;
    assert(list_swap(&my_cool_list, 0, mid_idx));

    itr = list_itr_create(&my_cool_list);
    index = 0;

    while((data = list_itr_next(&itr)))
    {
        int data_itr = *(int*)data;
        int data_from_get_at_idx = *(int*)list_get_at_idx(&my_cool_list, index);

        // 2 -> 1 -> 0 -> 3 -> 4
        if(index == 0)
        {
            assert(data_itr == test_data[mid_idx]);
            assert(data_from_get_at_idx == test_data[mid_idx]);
        }
        else if(index == mid_idx)
        {
            assert(data_itr == test_data[0]);
            assert(data_from_get_at_idx == test_data[0]);
        }
        else
        {
            assert(data_itr == test_data[index]);
            assert(data_from_get_at_idx == test_data[index]);
        }
        index++;
    }

    // swap tail and "middle"
    assert(list_swap(&my_cool_list, initial_list_size - 1, mid_idx));

    itr = list_itr_create(&my_cool_list);
    index = 0;

    while((data = list_itr_next(&itr)))
    {
        int data_itr = *(int*)data;
        int data_from_get_at_idx = *(int*)list_get_at_idx(&my_cool_list, index);

        // 2 -> 1 -> 4 -> 3 -> 0
        if(index == 0)
        {
            assert(data_itr == test_data[mid_idx]);
            assert(data_from_get_at_idx == test_data[mid_idx]);
        }
        else if(index == mid_idx)
        {
            assert(data_itr == test_data[initial_list_size - 1]);
            assert(data_from_get_at_idx == test_data[initial_list_size - 1]);
        }
        else if(index == initial_list_size - 1)
        {
            assert(data_itr == test_data[0]);
            assert(data_from_get_at_idx == test_data[0]);
        }
        else
        {
            assert(data_itr == test_data[index]);
            assert(data_from_get_at_idx == test_data[index]);
        }
        index++;
    }

    // clear the list
    list_clear(&my_cool_list);

    // verify no data 
    assert(my_cool_list.head == NULL);
    assert(my_cool_list.tail == NULL);
    assert(list_is_empty(&my_cool_list));
}


// Test the "list_remove_data" function
// - list_init
// - list_is_empty
// - list_get_len
// - list_clear
void test_remove_data(void)
{
    // this value MUST be 5
    const int initial_list_size = 5;
    const int midpoint = initial_list_size / 2;
    List my_cool_list = list_init();

    // verify no data 
    assert(my_cool_list.head == NULL);
    assert(my_cool_list.tail == NULL);
    assert(list_get_len(&my_cool_list) == 0);
    assert(list_is_empty(&my_cool_list));

    int test_data[initial_list_size];

    for(int i = 0; i < initial_list_size; i++)
    {
        // 0 -> 1 -> 2 -> 3 -> 4
        test_data[i] = i;
        list_push_back(&my_cool_list, &test_data[i]);
    }

    assert(list_get_len(&my_cool_list) == initial_list_size);

    // remove from the front
    assert(list_remove_data(&my_cool_list, &test_data[0]));

    assert(my_cool_list.head->data == &test_data[1]);
    assert(list_get_len(&my_cool_list) == initial_list_size - 1);

    // remove from the back
    assert(list_remove_data(&my_cool_list, &test_data[initial_list_size - 1]));

    assert(my_cool_list.head->data == &test_data[1]);
    assert(my_cool_list.tail->data == &test_data[initial_list_size - 2]);
    assert(list_get_len(&my_cool_list) == initial_list_size - 2);

    // remove from the middle
    assert(list_remove_data(&my_cool_list, &test_data[midpoint]));

    assert(my_cool_list.head->data == &test_data[1]);
    assert(my_cool_list.tail->data == &test_data[initial_list_size - 2]);
    assert(list_get_len(&my_cool_list) == initial_list_size - 3);

    // fail on removal of pointer not in list
    assert(!list_remove_data(&my_cool_list, NULL));

    assert(my_cool_list.head->data == &test_data[1]);
    assert(my_cool_list.tail->data == &test_data[initial_list_size - 2]);
    assert(list_get_len(&my_cool_list) == initial_list_size - 3);

    // remove last two elements, make sure list can be emptied
    assert(!list_is_empty(&my_cool_list));
    assert(list_remove_data(&my_cool_list, &test_data[1]));
    assert(!list_is_empty(&my_cool_list));
    assert(list_remove_data(&my_cool_list, &test_data[3]));
    assert(list_is_empty(&my_cool_list));

    list_clear(&my_cool_list);

    assert(list_is_empty(&my_cool_list));
}

int main(void)
{
    printf("Testing List Create and Clear.\n");
    create_and_clear_list();

    printf("Testing List Push Back.\n");
    push_back_one_entry();

    printf("Testing List Push Front.\n");
    push_front_one_entry();

    printf("Testing List Complete Exercise.\n");
    push_back_three_remove_push_front_three_entries();

    printf("Testing List Insertion.\n");
    test_list_insertion();

    printf("Testing List Swap.\n");
    test_list_swap();

    printf("Testing List Remove Data.\n");
    test_remove_data();

    printf("-------------------------------------------------------------------------------\n");
    printf("List Tests Passed :)\n");
    printf("-------------------------------------------------------------------------------\n");

    return 0;
}
