from linked_list import LinkedList
from linked_list import CircularList


if __name__ == '__main__':

	list1 = LinkedList([1, 2, 3])
	list1.add_back(4)
	print(list1)
	list1.remove_front()
	print(list1)
	print(list1.contains(2))

	list2 = LinkedList()
	list2.add_front('A')
	list2.add_front('B')
	list2.add_front('C')
	print(list2)

	list3 = CircularList([1, 2, 3, 3, 4, 5])
	print(list3)
	list3.circularListReverse()
	print(list3)

class LinkedListTest(unittest.TestCase):
    def setUp(self) -> None:
        self.ll = LinkedList()

    # Creates a new linked list with the letters A through G inclusive. 7 elements total. Referred to as 'standard
    # fill' in comments.
    def fill_linked_list_with_data(self):
        self.ll = LinkedList(['A', 'B', 'C', 'D', 'E', 'F', 'G'])

    # Inserts the letter 'A' at index 0 in an empty list, checks to see if it is at the front of the list.
    def test_add_link_before_empty_list(self):
        self.ll.add_link_before("A", 0)
        self.assertEqual(self.ll.get_front(), "A")

    # Inserts A at index 0, B at index 1 and C at index 2. Asserts A is at the front and C is at the back.
    # test_add_link_before: Changed indexes to match assignment 3 guidelines example, does not allow for creating a
    # new node past the list's length
    def test_add_link_before(self):
        self.ll.add_link_before("A", 0)
        self.ll.add_link_before("B", 0)
        self.ll.add_link_before("C", 1)
        self.assertEqual(self.ll.get_front(), "B")
        self.assertEqual(self.ll.get_back(), "A")

    # Tries to insert Z at index 26 in an empty list, verifies an IndexError is raised.
    def test_add_link_before_out_of_bounds(self):
        with self.assertRaises(IndexError):
            self.ll.add_link_before('Z', 26)

    # Tries to insert the '@' symbol at index -2, verifies an IndexError is raised.
    def test_add_link_before_negative_index(self):
        with self.assertRaises(IndexError):
            self.ll.add_link_before("@", -2)

    # Inserts H, G and F at index 0. Verifies F is before G and G is before H.
    def test_add_link_before_multiple_times_same_index_empty_list(self):
        self.ll.add_link_before("H", 0)
        self.ll.add_link_before("G", 0)
        self.ll.add_link_before("F", 0)
        self.assertEqual(self.ll.__str__(), '[F -> G -> H]')

    # Tries to remove a link in an empty list, verifies an IndexError is raised.
    def test_remove_link_empty_list(self):
        with self.assertRaises(IndexError):
            self.ll.remove_link(1)

    # Tries to remove a link at a negative index, verifies an IndexError is raised.
    def test_remove_link_negative_index(self):
        with self.assertRaises(IndexError):
            self.ll.remove_link(-93)

    # Adds A, B and C to list respectively. Removes B. Verifies A is first, C is last and B is gone.
    def test_remove_link(self):
        self.ll.add_back("A")
        self.ll.add_back("B")
        self.ll.add_back("C")
        self.ll.remove_link(1)
        self.assertEqual("A", self.ll.get_front())
        self.assertEqual("C", self.ll.get_back())
        self.assertFalse(self.ll.contains("B"))

    # Adds A then B and C to front, respectively, verifies that C is at the front.
    def test_add_front(self):
        self.ll.add_front("A")
        self.ll.add_front("B")
        self.ll.add_front("C")
        self.assertEqual(self.ll.get_front(), "C")

    # Adds X, Y then Z to the back. Verifies that Z is at the back.
    def test_add_back(self):
        self.ll.add_back("X")
        self.ll.add_back("Y")
        self.ll.add_back("Z")
        self.assertEqual("Z", self.ll.get_back())

    # Using the standard fill (A through G), verifies that get_front() returns A and doesn't remove it.
    def test_get_front(self):
        self.fill_linked_list_with_data()
        self.assertEqual(self.ll.get_front(), "A")
        self.assertEqual(self.ll.get_front(), "A")

    # Using the standard fill, verifies that get_back() returns G and doesn't remove it.
    def test_get_back(self):
        self.fill_linked_list_with_data()
        self.assertEqual(self.ll.get_back(), "G")
        self.assertEqual(self.ll.get_back(), "G")

    # Using standard fill, removes the front (A) and verifies that B is now the front.
    def test_remove_front(self):
        self.fill_linked_list_with_data()
        self.ll.remove_front()
        self.assertEqual(self.ll.get_front(), "B")

    # Using standard fill, removes back (G) and verifies that F is now the back.
    def test_remove_back(self):
        self.fill_linked_list_with_data()
        self.ll.remove_back()
        self.assertEqual(self.ll.get_back(), "F")

    # Verifies a list with data is not empty.
    def test_is_empty_on_non_empty_list(self):
        self.fill_linked_list_with_data()
        self.assertFalse(self.ll.is_empty())

    # Verifies a list without data is empty.
    def test_is_empty_on_empty_list(self):
        self.assertTrue(self.ll.is_empty())

    # Verifies a list that had data, and is now empty, is empty.
    def test_is_empty_on_empty_list_that_was_previously_populated(self):
        self.fill_linked_list_with_data()
        for i in range(7):
            self.ll.remove_front()
        self.assertTrue(self.ll.is_empty())

    # Verifies a value not in list causes contains() to return False.
    def test_contains_value_not_in_list(self):
        self.fill_linked_list_with_data()
        self.assertFalse(self.ll.contains("X"))

    # Verifies a value in the list causes contains() to return True.
    def test_contains_value_in_list(self):
        self.fill_linked_list_with_data()
        self.assertTrue(self.ll.contains("C"))

    # Verifies that when a value is in the list more than once, contains(value) will still return True.
    def test_contains_duplicate_values_in_list(self):
        self.fill_linked_list_with_data()
        self.ll.add_back("A")
        self.assertTrue(self.ll.contains("A"))

    # Verifies that contains(value) reports False, when the value removed is the first value in the list.
    def test_contains_after_remove_first_item(self):
        self.fill_linked_list_with_data()
        self.ll.remove('A')
        self.assertFalse(self.ll.contains('A'))

    # Verifies that contains(value) reports False, when the value removed is the last value in the list.
    def test_contains_after_remove_last_item(self):
        self.fill_linked_list_with_data()
        self.ll.remove('G')
        self.assertFalse(self.ll.contains('G'))

    # Verifies that contains(value) reports False, when the value removed (by index) is the first value in the list.
    def test_contains_after_remove_link(self):
        self.fill_linked_list_with_data()
        self.ll.remove_link(0)
        self.assertFalse(self.ll.contains('A'))

    # Verifies that contains(value) reports False, when the value removed (by index) is the last value in the list.
    def test_contains_after_remove_link_end_of_list(self):
        self.fill_linked_list_with_data()
        self.ll.remove_link(6)
        self.assertFalse(self.ll.contains('G'))

    # Verify None is returned by get_front() when a value is removed from an empty list.
    def test_remove_empty_list(self):
        self.ll.remove("Z")
        self.assertEqual(self.ll.get_front(), None)

    # Verify when remove(value) works as expected.
    def test_remove_value_in_list(self):
        self.fill_linked_list_with_data()
        self.ll.remove("D")
        for i in range(3):
            self.ll.remove_front()
        self.assertEqual(self.ll.get_front(), "E")

    # Verify a populated list doesn't change when a value is removed which does not exist in the list.
    def test_remove_value_not_in_list(self):
        self.fill_linked_list_with_data()
        self.ll.remove('X')
        self.assertEqual(self.ll.__str__(),
                         '[A -> B -> C -> D -> E -> F -> G]')