from empire.core import *


T = TypeVar('T')


class Stack(Generic[T]):
    def __init__(self, data: Union[List[T], Set[T], Tuple[T]] = None, max_size: Union[int, None] = None):
        self._stack = []
        self._max_size: Union[int, None] = max_size

        if data:
            if type(data) in [list, set, tuple]:
                for item in data:
                    self._stack.append(item)

                if self._max_size is not None and len(self._stack) > self._max_size:
                    self._stack = self._stack[0:self._max_size-1]
            else:
                raise TypeError('Invalid type for data "{}". Must be one of List, Set, Tuple'.format(type(data)))

    def push(self, data: T) -> bool:
        if self._max_size is None or len(self._stack) < self._max_size:
            self._stack.insert(0, data)
            return True
        else:
            return False

    def pop(self, raise_on_empty: bool = True) -> Union[T, None]:
        if raise_on_empty or len(self._stack) > 0:
            return self._stack.pop()
        else:
            return None

    def last(self, raise_on_empty: bool = True) -> Union[T, None]:
        if raise_on_empty or len(self._stack) > 0:
            return self._stack[-1]
        else:
            return None

    # def get(self, index: int) -> T:
    #     return self._stack[index]

    def insert_at_end(self, data: Any) -> bool:
        if self._max_size is None or len(self._stack) < self._max_size:
            self._stack.append(data)
            return True
        else:
            return False

    def clear(self):
        self._stack = []

    @property
    def is_empty(self) -> bool:
        return self.__len__() == 0

    def __getitem__(self, index: int) -> T:
        if 0 > index >= len(self._stack):
            raise IndexError('Provided index is out of bounds: 0 > {} >= {}'.format(index, len(self._stack)))
        else:
            return self._stack[index]

    def __setitem__(self, index: int, value: T):
        if 0 > index >= len(self._stack):
            raise IndexError('Provided index is out of bounds: 0 > {} >= {}'.format(index, len(self._stack)))
        else:
            self._stack[index] = value

    def __delitem__(self, index: int):
        if 0 > index >= len(self._stack):
            raise IndexError('Provided index is out of bounds: 0 > {} >= {}'.format(index, len(self._stack)))
        else:
            self._stack.pop(index)

    def __contains__(self, item: Any) -> bool:
        for self_item in self._stack:
            if self_item == item:
                return True

        return False


    def __len__(self) -> int:
        return len(self._stack)

    def __iter__(self):
        return iter(self._stack)

    def __next__(self):
        return next(self._stack)
