"""
    This is a class describing a sorted frozen set. This is a collection implementation for a set of ordered elements that establish specific protocols for iteration, information access, element identification.

"""
# ============================================================================
# STANDARD  IMPORTS
# ============================================================================

from collections.abc import Sequence, Set
from itertools import chain
from bisect import bisect_left


# ============================================================================
# CLASS AND DEFINITIONS
# ============================================================================


class SortedFrozenSet(Sequence, Set):
    """
        This is a collection that provides a set of properties to create a sorted frozen set.

        In particular

        Args:
            Sequence (Sequence): Inherits from the `Sequence` collection object.
            Set (Set): Inherits from the `Set` collection object.
    """

    def __init__(self, items=None):
        self._items = tuple(
            sorted(
                set(items) if (items is not None) else set(),
                key=lambda x: x.vehid,
            )
        )

    def __contains__(self, item):
        try:
            self.index(item)
            return True
        except ValueError:
            return False

    def __len__(self):
        return len(self._items)

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

    def __getitem__(self, index):
        result = self._items[index]
        return SortedFrozenSet(result) if isinstance(index, slice) else result

    def __repr__(self):
        return "{type}({arg})".format(
            type=type(self).__name__,
            arg=(
                "[{}]".format(", ".join(map(repr, self._items)))
                if self._items
                else ""
            ),
        )

    def __eq__(self, rhs):
        if not isinstance(rhs, type(self)):
            return NotImplemented
        return self._items == rhs._items

    def __hash__(self):
        return hash((type(self), self._items))

    def __add__(self, rhs):
        if not isinstance(rhs, type(self)):
            return NotImplemented
        return SortedFrozenSet(chain(self._items, rhs._items))

    def __mul__(self, rhs):
        return self if rhs > 0 else SortedFrozenSet()

    def __rmul__(self, lhs):
        return self * lhs

    def count(self, item):
        return int(item in self)

    def index(self, item):
        index = bisect_left(self._items, item)
        if (index != len(self._items)) and self._items[index] == item:
            return index
        raise ValueError(f"{item!r} not found")

    def issubset(self, iterable):
        return self <= SortedFrozenSet(iterable)

    def issuperset(self, iterable):
        return self >= SortedFrozenSet(iterable)

    def intersection(self, iterable):
        return self & SortedFrozenSet(iterable)

    def union(self, iterable):
        return self | SortedFrozenSet(iterable)

    def symmetric_difference(self, iterable):
        return self ^ SortedFrozenSet(iterable)

    def difference(self, iterable):
        return self - SortedFrozenSet(iterable)
