import os
import typing


class Xorshift:
    """Class of random vairables generator, uses `xorshift` alghoritm for random."""

    # Enable slots for more performance
    __slots__ = ["seed", "state", "mask"]

    # ==-----------------------------------------------------------------------------== #
    # Public methods                                                                    #
    # ==-----------------------------------------------------------------------------== #
    def __init__(self, seed: int | None = None):
        """Creates instance to generate random values, collections."""

        # Align mask
        self.mask = 0xFFFFFFFFFFFFFFFF

        # Save of initial seed
        self.seed = int(seed if seed is not None else int.from_bytes(os.urandom(8), "big")) & self.mask

        # Split state into two parts from seed
        self.state = [
            (self.seed ^ 0xA5A5A5A5A5A5A5A5) & self.mask,
            ((self.seed >> 1) ^ 0x5555555555555555 ^ 0x14057B7EF767814F) & self.mask
        ]

        # If state values is null
        if not self.state[0] and not self.state[1]:
            self.state = [1, 0x14057B7EF767814F]

    def randint(self, start: int, end: int) -> int:
        """Gets random integer value from [start, end] range."""

        # If start less than end
        if start > end:
            start, end = end, start

        # Random value generation
        if (offset := self.__get_next() % (span := end - start + 1)) < 0:
            offset += span

        # Return generated random integer value
        return start + offset

    def randfloat(self, start: float, end: float) -> float:
        """Gets random float value from [start, end) range."""

        # Start and end of range to generate random values
        start = min(start, end)
        end = max(start, end)

        # Generate and return random float value
        return start + (end - start) * ((self.__get_next() >> 11) / ((1 << 53) - 1.0))

    def sample(self, values: list[typing.Any], n: int | None = None) -> list[typing.Any]:
        """Gets `N` random values from source list and creates new one. If `N` is `None` samples the whole list."""

        # Get min-max avaiable `N` value
        n = max(0, min(len(values), n if n is not None else len(values)))

        # If sampling is not required
        if not n:
            return list()

        # Result list
        result = values[:]

        # Sample list values
        for index in range(len(values) - 1, len(values) - n - 1, -1):

            # Get random value
            random_value = self.__get_next() % (index + 1)

            # Shuffle values
            result[index], result[random_value] = result[random_value], result[index]

        # Return `N` last values in sampled list
        return result[-n:]

    def choice(self, values: typing.Iterable[typing.Any]) -> typing.Any:
        """Chooses random value from collection."""

        # If collection is empty
        if not values:
            raise Exception("Collection is empty, unable to chose random value")

        # Get and return random collection value
        return values[self.randint(0, len(values) - 1)]

    # ==-----------------------------------------------------------------------------== #
    # Private methods                                                                   #
    # ==-----------------------------------------------------------------------------== #
    def __get_next(self) -> int:
        """Генерирует следующее псеводо-случайное число."""

        # Извлечение текущего состояния
        state_0, state_1 = self.state

        # Генерация случайного числа
        result = (state_0 + state_1) & self.mask

        # Обновление состояния
        state_1 ^= state_0
        self.state[0] = (((state_0 << 24) | (state_0 >> 40)) ^ state_1 ^ (state_1 << 16)) & self.mask
        self.state[1] = ((state_1 << 37) | (state_1 >> 27)) & self.mask

        # Возврат результата
        return result
