# region PROBLEM
'''
<> LL: Get
Implement the get method for the LinkedList class.

The get method takes an integer index and returns a pointer to the node at
that index in the linked list. If the index is out of bounds (< 0 or >= length),
return None.

Requirements:
    - Handle out-of-bounds indices by returning None.
    - Start at the head and traverse using each node's next attribute.
    - Stop traversal once the target index is reached and return that node.
    - Return None if the index is out of bounds.
'''

class Node:
    def __init__(self, value):
        self.value = value
        self.next = None


class LinkedList:
    def __init__(self, value):
        new_node = Node(value)
        self.head = new_node
        self.tail = new_node
        self.length = 1

    def print_list(self):
        temp = self.head
        while temp is not None:
            print(temp.value)
            temp = temp.next

    def append(self, value):
        new_node = Node(value)
        if self.length == 0:
            self.head = new_node
            self.tail = new_node
        else:
            self.tail.next = new_node
            self.tail = new_node
        self.length += 1
        return True

    def pop(self):
        if self.length == 0:
            return None
        temp = self.head
        pre = self.head
        while temp.next:
            pre = temp
            temp = temp.next
        self.tail = pre
        self.tail.next = None
        self.length -= 1
        if self.length == 0:
            self.head = None
            self.tail = None
        return temp

    def prepend(self, value):
        new_node = Node(value)
        if self.length == 0:
            self.head = new_node
            self.tail = new_node
        else:
            new_node.next = self.head
            self.head = new_node
        self.length += 1
        return True

    def pop_first(self):
        if self.length == 0:
            return None
        temp = self.head
        self.head = self.head.next
        temp.next = None
        self.length -= 1
        if self.length == 0:
            self.tail = None
        return temp
# endregion


# region SOLUTION

def get(self, target_index):
    if target_index < 0 or target_index >= self.length:
        return None
    
    current_index = 0
    current_node = self.head
    
    while current_index < target_index:
        current_index += 1
        current_node = current_node.next
        
    return current_node

LinkedList.get = get

# endregion


# region TESTS

def test_get_first_index():
    ll = LinkedList(0)
    ll.append(1)
    ll.append(2)
    node = ll.get(0)
    assert node is ll.head
    assert node.value == 0


def test_get_middle_index():
    ll = LinkedList(0)
    ll.append(1)
    ll.append(2)
    ll.append(3)
    node = ll.get(2)
    assert node.value == 2


def test_get_last_index():
    ll = LinkedList(0)
    ll.append(1)
    ll.append(2)
    ll.append(3)
    node = ll.get(3)
    assert node is ll.tail
    assert node.value == 3


def test_get_negative_index_returns_none():
    ll = LinkedList(0)
    ll.append(1)
    assert ll.get(-1) is None


def test_get_index_equal_to_length_returns_none():
    ll = LinkedList(0)
    ll.append(1)
    assert ll.get(2) is None


def test_get_index_beyond_length_returns_none():
    ll = LinkedList(0)
    ll.append(1)
    assert ll.get(99) is None


def test_get_on_empty_list_returns_none():
    ll = LinkedList(1)
    ll.head = None
    ll.tail = None
    ll.length = 0
    assert ll.get(0) is None


if __name__ == "__main__":
    import pytest

    raise SystemExit(pytest.main([__file__, "-v"]))

# endregion
