class Node:
    """
    Define a Linked List Node
    """
    def __init__(self, nodeValue, nextNode=None):
        """
        Create A Node in the Linked List
        @param: Node self: this Node
        @param: object value: data of linked list node
        @param: Node next: next node pointed to by this Node
        @rtype: None
        """
        self.nodeValue, self.nextNode = nodeValue, nextNode

class Linkedlist:
    """
    A collection of Nodes
    """
    def __init__(self):
        """
        Create an empty linked list
        @param: Node head: first node of linked list
        @param: Node tail: last node of linked list
        @param: int size: int
        @return:
        @rtype:
        """
        self.head, self.tail, self.size = None, None, 0

    def append(self, value):
        """
        Insert a new Node with value after the last node (tail)
        @param: Linkedlist self: this Linkedlist
        @param object value: value of the new Node
        @rtype: None
        """
        newNode = Node(value)
        if self.head is None:
            # append to an empty linked list
            self.head = self.tail = newNode
        else:
            # self.tail better not be None
            assert self.tail, "Unexpected Error: No Tail!"
            self.tail.nextNode = newNode
            self.tail = newNode
        self.size += 1

    def delHead(self):
        """
        Delete the first node of the Linked List
        @param: Linkedlist self: this Linkedlist
        @rtype: None
        """
        headnode = self.head
        self.head = self.head.nextNode
        headnode.nextNode = None
        self.size -= 1

    def prepend(self, value):
        """
        Insert a Node at the beginning of the Linked List
        @param: Linkedlist self: this Linkedlist
        @param: object value: value of new Node to be added
        @rtype: None
        """
        newNode = Node(value)
        if self.head is None:
            "Linked list has no head!"
            raise Exception("Linked list has no head!")
        else:
            newNode.nextNode = self.head
            self.head = newNode
        self.size += 1

    def isFound(self, searchValue):
        """
        Search the Linked List for searchValue
        @param: Linkedlist self: this Linkedlist
        @param object searchValue: search for searchValue in Linked List
        @rtype: bool
        """
        myList = self.head
        while myList:
            if myList.nodeValue == searchValue:
                return True
            else:
                myList = myList.nextNode
        return False

    def insert(self, searchValue, insertValue):
        """
        Search Linked List for a value and insert a new Node after it
        @param Linkelist self: this Linkedlist
        @param: object searchValue: value to look for
        @param object insertValue: value to be inserted
        @rtype: None
        """
        myList = self.head
        while myList:
            if myList.nodeValue == searchValue:
                newNode = Node(insertValue)
                if self.tail == myList:
                    # last node
                    myList.nextNode = newNode
                    self.tail = newNode
                else:
                    foundNode = myList.nextNode
                    newNode.nextNode = foundNode
                    myList.nextNode = newNode
                self.size += 1
                return None
            else:
                myList = myList.nextNode

    def delNode(self, delValue):
        """
        Delete a node whose value is delValue (first node)
        @param: Linkedlist self: this Linkedlist
        @param object delValue: value of the Node to be deleted
        @rtype: None
        """
        myList = self.head
        prevNode = myList
        while myList:
            if myList.nodeValue == delValue:
                if myList == self.head:
                    headnode = self.head
                    self.head = self.head.nextNode
                elif myList == self.tail:
                    prevNode.nextNode = None
                    self.tail = prevNode
                else:
                    prevNode.nextNode = myList.nextNode

                self.size -= 1
                return
            else:
                prevNode = myList
                myList = myList.nextNode

    def __str__(self):
        """
        Return a user-friendly string representation of Node
        @rtype: str
        """
        valueList = ""
        linkList = self.head
        while linkList:
            valueList = valueList + str(linkList.nodeValue) + " -> "
            linkList = linkList.nextNode
        valueList = valueList + "/"
        return valueList


l1 = Linkedlist()
l1.append('foo')
l1.append('bar')
l1.append('new')
print(l1)
print(l1.size)
print("Found: ", l1.isFound('bar'))
l1.insert('bar', 'karaoke')
l1.prepend('to be deleted soon')
print(l1.size, "  <------->  ", l1)
l1.delHead()
print(l1)
print(l1.size)
l1.delHead()
l1.delNode('new')
print(l1.size)
print(l1)

