# Copyright (C) 2022-2023 Exaloop Inc. <https://exaloop.io>

@tuple
class range:
    start: int
    stop: int
    step: int

    # Magic methods

    def __new__(start: int, stop: int, step: int) -> range:
        if step == 0:
            raise ValueError("range() step argument must not be zero")
        return (start, stop, step)

    def __new__(start: int, stop: int) -> range:
        return (start, stop, 1)

    def __new__(stop: int) -> range:
        return (0, stop, 1)

    def _get(self, idx: int) -> int:
        return self.start + (idx * self.step)

    def __getitem__(self, idx: int) -> int:
        n = self.__len__()
        if idx < 0:
            idx += n
        if idx < 0 or idx >= n:
            raise IndexError("range object index out of range")
        return self._get(idx)

    def __getitem__(self, s: Slice) -> range:
        if s.start is None and s.stop is None and s.step is None:
            return self
        else:
            start, stop, step, length = s.adjust_indices(self.__len__())
            substep = self.step * step
            substart = self._get(start)
            substop = self._get(stop)
            return range(substart, substop, substep)

    def __contains__(self, idx: int) -> bool:
        start, stop, step = self.start, self.stop, self.step
        if (step > 0 and not (start <= idx < stop)) or (
            step < 0 and not (stop < idx <= start)
        ):
            return False
        return (idx - start) % step == 0

    def _index(self, n: int) -> int:
        return (n - self.start) // self.step

    def index(self, n: int) -> int:
        if n in self:
            return self._index(n)
        else:
            raise ValueError(str(n) + " is not in range")

    def count(self, n: int) -> int:
        return int(n in self)

    def __iter__(self) -> Generator[int]:
        start, stop, step = self.start, self.stop, self.step
        i = start
        if step > 0:
            while i < stop:
                yield i
                i += step
        else:
            while i > stop:
                yield i
                i += step

    def __len__(self) -> int:
        start, stop, step = self.start, self.stop, self.step
        if step > 0 and start < stop:
            return 1 + (stop - 1 - start) // step
        elif step < 0 and start > stop:
            return 1 + (start - 1 - stop) // (-step)
        else:
            return 0

    def __bool__(self) -> bool:
        return self.__len__() > 0

    def __reversed__(self) -> Generator[int]:
        start, stop, step = self.start, self.stop, self.step
        n = self.__len__()
        return range(start + (n - 1) * step, start - step, -step).__iter__()

    def __repr__(self) -> str:
        if self.step == 1:
            return f"range({self.start}, {self.stop})"
        else:
            return f"range({self.start}, {self.stop}, {self.step})"

@overload
def staticrange(start: Static[int], stop: Static[int], step: Static[int] = 1):
    return range(start, stop, step)

@overload
def staticrange(stop: Static[int]):
    return range(0, stop, 1)
