class LinkedList_Test_Operations_On_List_with_1000_Random_Elements(unittest.TestCase):
    def setUp(self):
        self._linked_list = LinkedList()
        self._shadow_list = []
        for i in range(10):
            random_element = randint(0, 1000)
            self._linked_list.append(random_element)
            self._shadow_list.append(random_element)#will keep track of count and index 
    
    def test_count_index_remove_index_count_on_each_removal(self):
        """
        Check count, index before and after each predictable removal operation 
        """
        for i in self._shadow_list[:]:
            self.assertEqual(self._linked_list.length, len(self._shadow_list))
            
            self.assertEqual(self._linked_list.index(i), self._shadow_list.index(i))
            
            self._shadow_list.remove(i)
            self._linked_list.remove(i)
            
            try:
                self._shadow_list.index(i)
                self.assertEqual(self._linked_list.index(i), self._shadow_list.index(i))
            except ValueError:
                self.assertEqual(self._linked_list.index(i), -1)
            
            self.assertEqual(self._linked_list.length, len(self._shadow_list))
class LinkedList_Test_Insert_At_Index_In_Middle_With_4_Elements(unittest.TestCase):
    
    def setUp(self):
        self._linkedList = LinkedList()
        self._linkedList.append(1)
        self._linkedList.append(2)
        self._linkedList.append(4)
        self._linkedList.append(5)
        
    def test_insert_at_negative_index_of_empty_list(self):
        expected_length = self._linkedList.length + 1
        element_to_insert = 3
        insert_at_index = 2
        self._linkedList.insert_at(insert_at_index, element_to_insert)# list will become 1, 2, 3, 4, 5
        self.assertEqual(self._linkedList.length, expected_length, 'Linked list length did not tally after insert in the middle')
        self.assertEqual(self._linkedList.index(3), insert_at_index, 'index of new element is not where we insert it at!')
        
        #iterate and check
        expected_list = []
        for element in self._linkedList:
            expected_list.append(element)
        self.assertEqual(expected_list, [1, 2, 3, 4, 5], 'List was not same as expected after insert At index')
    
    def tearDown(self):
        self._linkedList = None
class LinkedList_Test_IndexOf_Elements_In_4_Element_List(unittest.TestCase):
    
    def setUp(self):
        self._linkedList = LinkedList()
        self._linkedList.append(1)
        self._linkedList.append(2)
        self._linkedList.append(3)
        self._linkedList.append(4)
    
    def test_index(self):
        for element in range(1, 5):
            index = self._linkedList.index(element)
            self.assertEqual(index + 1, element, 'index failed for element')

    def tearDown(self):
        self._linkedList = None    
class LinkedList_Test_Insert_At_Index_Larger_Than_Length_With_4_Elements(unittest.TestCase):
    
    def setUp(self):
        self._linkedList = LinkedList()
        self._linkedList.append(1)
        self._linkedList.append(2)
        self._linkedList.append(3)
        self._linkedList.append(4)
        
    def test_insert_at_negative_index_of_empty_list(self):
        expected_length = self._linkedList.length + 1
        element_to_insert = 20
        self._linkedList.insert_at(100, element_to_insert)
        self.assertEqual(self._linkedList.length, expected_length, 'Linked list length did not tally after insert at huge index')
        self.assertEqual(self._linkedList.tail, element_to_insert, 'Linked list tail must be same as new element inserted at huge index')
        self.assertEqual(self._linkedList.index(20), expected_length - 1, 'index of new element did not add up')
    
    def tearDown(self):
        self._linkedList = None
class LinkedList_Test_extend_With_Predictable_Elements(unittest.TestCase):
    '''
    Test the extend operation of singly_linked_list. 
    a) Create 2 lists. 
    b) Add the second list to the end of the first list. 
    c) Check the indices of the newly added elements.
    '''
    
    def setUp(self):
        self._linkedList = LinkedList()
        self._linkedList.append(1)
        self._linkedList.append(2)
        self._linkedList.append(3)
        self._linkedList.append(4)
        #create a second linkedlist with 5, 6, 7
        self._secondList = LinkedList()
        self._secondList.append(5)
        self._secondList.append(6)
        self._secondList.append(7)
        
    def test_extend_of_list(self):
        self.assertEqual(self._linkedList.length, 4, 'Original list before extend must be 4')
        count_before_extend = self._linkedList.length
        next_index_will_be = count_before_extend
        self._linkedList.extend(self._secondList)
        #check new length
        self.assertEqual(self._linkedList.length, count_before_extend + self._secondList.length, 'New length after extend must add up.')
        #check if the elements of shadow list are present
        for element in self._secondList:
            self.assertEqual(self._linkedList.index(element), next_index_will_be
                             , 'Indices of new elements after extend must add up')
            next_index_will_be += 1
    
    def tearDown(self):
        self._linkedList = None
        self._secondList = None
    #we add the following list (iterable) of elements
    print('Adding elements 2 ,3 , 4, 5, 6, 7, 8, 9, 10')
    elements = [2, 3, 4, 5, 6, 7, 8, 9, 10]
    link_list.extend(elements)

    #head
    print('Head of list is: %s' % str(link_list.head))

    #tail
    print('Tail of list is: %s' % str(link_list.tail))
    print('Print list by iterating')

    #Find an element's index
    element = 4
    element_index = link_list.index(element)
    if element_index == -1:
        print('Element %s was not found on list' % str(element))
    else:
        print('Element %s found at index %s' %
              (str(element), str(element_index)))

    #Lets delete 7
    print('Deleting 7')
    element_to_remove = 7
    link_list.remove(element_to_remove)

    #Try to find it.
    print('Trying to find %s' % str(element_to_remove))
    index_of_deleted = link_list.index(element_to_remove)
    print('Index of deleted element is %s' % str(index_of_deleted))