""" A Queue using a Linked List like structure """
from typing import Any, Optional


class Node:
    def __init__(self, data: Any, next: Optional["Node"] = None):
        self.data: Any = data
        self.next: Optional["Node"] = next


class LinkedQueue:
    """
    Linked List Queue implementing put (to end of queue),
    get (from front of queue) and is_empty

    >>> queue = LinkedQueue()
    >>> queue.is_empty()
    True
    >>> queue.put(5)
    >>> queue.put(9)
    >>> queue.put('python')
    >>> queue.is_empty();
    False
    >>> queue.get()
    5
    >>> queue.put('algorithms')
    >>> queue.get()
    9
    >>> queue.get()
    'python'
    >>> queue.get()
    'algorithms'
    >>> queue.is_empty()
    True
    >>> queue.get()
    Traceback (most recent call last):
        ...
    IndexError: get from empty queue
    """

    def __init__(self) -> None:
        self.front: Optional[Node] = None
        self.rear: Optional[Node] = None

    def is_empty(self) -> bool:
        """ returns boolean describing if queue is empty """
        return self.front is None

    def put(self, item: Any) -> None:
        """ append item to rear of queue """
        node: Node = Node(item)
        if self.is_empty():
            # the queue contains just the single element
            self.front = node
            self.rear = node
        else:
            # not empty, so we add it to the rear of the queue
            assert isinstance(self.rear, Node)
            self.rear.next = node
            self.rear = node

    def get(self) -> Any:
        """ returns and removes item at front of queue """
        if self.is_empty():
            raise IndexError("get from empty queue")
        else:
            # "remove" element by having front point to the next one
            assert isinstance(self.front, Node)
            node: Node = self.front
            self.front = node.next
            if self.front is None:
                self.rear = None

            return node.data