#!/usr/bin/env python3

from typing import Any, Union


class Animal:
    def __init__(self, name: str) -> None:
        self.name = name

    def set_order(self, order: int) -> None:
        self.order = order

    def peek_order(self) -> int:
        return self.order

    def __str__(self) -> str:
        return f"{self.name}"


class Node:
    def __init__(self, data: Any):
        self.data = data
        self.next_node = None


class LinkedList:
    def __init__(self) -> None:
        self.head = None
        self.tail = None

    def __str__(self) -> str:
        current = self.head
        string = f""
        while current.next_node is not None:
            string += f"{current.data} -> "
            current = current.next_node
        return string + "END"

    def is_empty(self) -> bool:
        if self.head is None:
            return True
        else:
            return False

    def insert(self, item: Any) -> None:
        if self.is_empty():
            self.head = Node(item)
            self.tail = self.head
        else:
            new_node = Node(item)
            self.tail.next_node = new_node
            self.tail = self.tail.next_node

    def remove(self) -> Any:
        if self.head is None:
            raise ("Empty LinkedList!")
        else:
            data = self.head.data
            self.head = self.head.next_node
            return data

    def peak(self):
        return self.head.data


class Dog(Animal):
    def __init__(self, name: str):
        super().__init__(name)


class Cat(Animal):
    def __init__(self, name: str):
        super().__init__(name)


class AnimalQueue:
    def __init__(self) -> None:
        self.dogs = LinkedList()
        self.cats = LinkedList()
        self.order = 0

    def enqueue(self, animal: Union[Dog, Cat]) -> None:
        if not isinstance(animal, (Dog, Cat)):
            raise Exception("Expected Dog or Cat!")
        else:
            animal.set_order(self.order)
            self.order += 1
            if isinstance(animal, Dog):
                self.dogs.insert(animal)
            elif isinstance(animal, Cat):
                self.cats.insert(animal)

    def dequeAny(self) -> Union[Dog, Cat]:
        if self.dogs.is_empty():
            return self.dequeCat()
        elif self.cats.is_empty():
            return self.dequeDog()

        if self.dogs.head.data.peek_order() > self.cats.head.data.peek_order():
            return self.dequeCat()
        else:
            return self.dequeDog()

    def print_cats(self) -> str:
        string = ""
        cat = self.cats.head
        while cat is not None:
            string += f"{cat.data.name} {cat.data.peek_order()} | "
            cat = cat.next_node
        return string

    def dequeDog(self) -> Dog:
        return self.dogs.remove()

    def dequeCat(self) -> Cat:
        return self.cats.remove()


def main():
    q = AnimalQueue()
    dogs = [Dog("d1"), Dog("d2"), Dog("d3")]
    cats = [Cat("c1"), Cat("c2"), Cat("c3")]
    both = []
    while cats != []:
        both.append(cats.pop())
        both.append(dogs.pop())
    [q.enqueue(animal) for animal in both]

    string = ""
    for anim in both:
        string += f"{anim.name} {anim.order} | "
    print(string)
    # print(q.print_cats())
    get = q.dequeDog()
    print(get.order,get.name)
    get = q.dequeAny()
    print(get.order,get.name)


if __name__ == "__main__":
    main()

[]