from enum import Enum as _Enum
from math import gcd as _gcd
from numbers import (Integral as _Integral,
                     Rational as _Rational)
from operator import mul as _mul
from sys import hash_info as _hash_info
from typing import (Tuple as _Tuple,
                    Union as _Union)


class _BaseEnum(_Enum):
    __module__ = 'rithm'

    def __repr__(self):
        return f'{type(self).__module__}.{type(self).__qualname__}.{self.name}'


class Endianness(_BaseEnum):
    BIG = 'big'
    LITTLE = 'little'


class TieBreaking(int, _BaseEnum):
    AWAY_FROM_ZERO = 0
    TO_EVEN = 1
    TO_ODD = 2
    TOWARD_ZERO = 3


@_Integral.register
class Int:
    @property
    def denominator(self):
        return _ONE

    @property
    def numerator(self):
        return self

    def bit_length(self):
        return Int(self._value.bit_length())

    def gcd(self, other):
        return Int(_gcd(self._value, other._value))

    def is_power_of_two(self):
        return self._value > 0 and not (self._value & (self._value - 1))

    def to_bytes(self, endianness):
        return self._value.to_bytes(_to_bytes_count(self._value),
                                    endianness.value,
                                    signed=True)

    @classmethod
    def from_bytes(cls, value, endianness):
        return cls(int.from_bytes(value, endianness.value,
                                  signed=True))

    __module__ = 'rithm'
    __slots__ = '_value',

    def __new__(cls, _value=0, _base=None):
        self = super().__new__(cls)
        self._value = (int(_value)
                       if _base is None
                       else int(_value, _base))
        return self

    def __abs__(self):
        return Int(abs(self._value))

    def __add__(self, other):
        return (Int(self._value + other._value)
                if isinstance(other, Int)
                else self.__radd__(other))

    def __and__(self, other):
        return (Int(self._value & other._value)
                if isinstance(other, Int)
                else self.__rand__(other))

    def __bool__(self):
        return bool(self._value)

    def __ceil__(self):
        return self

    def __divmod__(self, other):
        return (tuple(Int(value)
                      for value in divmod(self._value, other._value))
                if isinstance(other, Int)
                else (tuple(Int(value)
                            for value in divmod(self._value, other))
                      if isinstance(other, int)
                      else NotImplemented))

    def __eq__(self, other):
        return (self._value == other._value
                if isinstance(other, Int)
                else (self._value == other
                      if isinstance(other, int)
                      else NotImplemented))

    def __float__(self):
        return float(self._value)

    def __floor__(self):
        return self

    def __floordiv__(self, other):
        return (Int(self._value // other._value)
                if isinstance(other, Int)
                else (Int(self._value // other)
                      if isinstance(other, int)
                      else NotImplemented))

    def __ge__(self, other):
        return (self._value >= other._value
                if isinstance(other, Int)
                else (self._value >= other
                      if isinstance(other, int)
                      else NotImplemented))

    def __getstate__(self):
        return self._value

    def __gt__(self, other):
        return (self._value > other._value
                if isinstance(other, Int)
                else (self._value > other
                      if isinstance(other, int)
                      else NotImplemented))

    def __hash__(self):
        return hash(self._value)

    def __invert__(self):
        return Int(~self._value)

    def __index__(self):
        return self._value

    __int__ = __index__

    def __le__(self, other):
        return (self._value <= other._value
                if isinstance(other, Int)
                else (self._value <= other
                      if isinstance(other, int)
                      else NotImplemented))

    def __lshift__(self, other):
        return (Int(self._value << other._value)
                if isinstance(other, Int)
                else (Int(self._value << other)
                      if isinstance(other, int)
                      else NotImplemented))

    def __lt__(self, other):
        return (self._value < other._value
                if isinstance(other, Int)
                else (self._value < other
                      if isinstance(other, int)
                      else NotImplemented))

    def __mod__(self, other):
        return (Int(self._value % other._value)
                if isinstance(other, Int)
                else (Int(self._value % other)
                      if isinstance(other, int)
                      else NotImplemented))

    def __mul__(self, other):
        return (Int(self._value * other._value)
                if isinstance(other, Int)
                else self.__rmul__(other))

    def __neg__(self):
        return Int(-self._value)

    def __or__(self, other):
        return (Int(self._value | other._value)
                if isinstance(other, Int)
                else self.__ror__(other))

    def __pos__(self):
        return self

    def __pow__(self, exponent, divisor=None):
        return (((Int(self._value ** int(exponent))
                  if exponent >= _ZERO
                  else Fraction(_ONE, self) ** -exponent)
                 if divisor is None
                 else (Int(pow(self._value, int(exponent), int(divisor)))
                       if isinstance(divisor, (Int, int))
                       else NotImplemented))
                if isinstance(exponent, (Int, int))
                else NotImplemented)

    def __radd__(self, other):
        return (Int(self._value + other)
                if isinstance(other, int)
                else NotImplemented)

    def __rand__(self, other):
        return (Int(self._value & other)
                if isinstance(other, int)
                else NotImplemented)

    def __rdivmod__(self, other):
        return (tuple(Int(value) for value in divmod(other, self._value))
                if isinstance(other, int)
                else NotImplemented)

    def __repr__(self):
        return (f'{type(self).__module__}.{type(self).__qualname__}'
                f'({self._value})')

    def __rfloordiv__(self, other):
        return (Int(other // self._value)
                if isinstance(other, int)
                else NotImplemented)

    def __rlshift__(self, other):
        return (Int(other << self._value)
                if isinstance(other, int)
                else NotImplemented)

    def __rmod__(self, other):
        return (Int(other % self._value)
                if isinstance(other, int)
                else NotImplemented)

    def __rmul__(self, other):
        return (Int(self._value * other)
                if isinstance(other, int)
                else NotImplemented)

    def __ror__(self, other):
        return (Int(self._value | other)
                if isinstance(other, int)
                else NotImplemented)

    def __round__(self, digits=None):
        return Int(round(self._value, digits))

    def __rpow__(self, base, divisor=None):
        return (Int(base).__pow__(self)
                if isinstance(base, int)
                else NotImplemented)

    def __rrshift__(self, other):
        return (Int(other >> self._value)
                if isinstance(other, int)
                else NotImplemented)

    def __rshift__(self, other):
        return (Int(self._value >> other._value)
                if isinstance(other, Int)
                else (Int(self._value >> other)
                      if isinstance(other, int)
                      else NotImplemented))

    def __rsub__(self, other):
        return (Int(other - self._value)
                if isinstance(other, int)
                else NotImplemented)

    def __rtruediv__(self, other):
        return (Fraction(other, self)
                if isinstance(other, int)
                else NotImplemented)

    def __rxor__(self, other):
        return (Int(self._value ^ other)
                if isinstance(other, int)
                else NotImplemented)

    def __setstate__(self, state: int):
        self._value = state

    def __str__(self):
        return str(self._value)

    def __sub__(self, other):
        return (Int(self._value - other._value)
                if isinstance(other, Int)
                else (Int(self._value - other)
                      if isinstance(other, int)
                      else NotImplemented))

    def __truediv__(self, other):
        return (Fraction(self, other)
                if isinstance(other, (Int, int))
                else NotImplemented)

    def __trunc__(self):
        return self

    def __xor__(self, other):
        return (Int(self._value ^ other._value)
                if isinstance(other, Int)
                else self.__rxor__(other))


def _to_bytes_count(value: int) -> int:
    return (8 + (value + (value < 0)).bit_length()) // 8


_ONE = Int(1)
_ZERO = Int()
_HASH_INF = Int(_hash_info.inf)
_HASH_MODULUS = Int(_hash_info.modulus)


@_Rational.register
class Fraction:
    @property
    def denominator(self):
        return self._denominator

    @property
    def numerator(self):
        return self._numerator

    def round(self, tie_breaking):
        quotient, remainder = divmod(self.numerator, self.denominator)
        double_remainder = remainder * 2
        if double_remainder == self.denominator:
            if tie_breaking is TieBreaking.AWAY_FROM_ZERO:
                return quotient + _ONE if quotient >= 0 else quotient
            elif tie_breaking is TieBreaking.TO_EVEN:
                return quotient + _ONE if quotient % 2 else quotient
            elif tie_breaking is TieBreaking.TO_ODD:
                return quotient + _ONE if not quotient % 2 else quotient
            else:
                assert tie_breaking is TieBreaking.TOWARD_ZERO, (
                    tie_breaking
                )
                return quotient + _ONE if quotient < 0 else quotient
        else:
            return (quotient + _ONE
                    if double_remainder > self.denominator
                    else quotient)

    __module__ = 'rithm'
    __slots__ = '_denominator', '_numerator'

    def __new__(cls,
                _numerator=_ZERO,
                _denominator=None,
                *,
                _normalize=True):
        self = super().__new__(cls)
        if _denominator is None:
            if isinstance(_numerator, Fraction):
                numerator, denominator = (_numerator.numerator,
                                          _numerator.denominator)
            elif isinstance(_numerator, float):
                raw_numerator, raw_denominator = (
                    _numerator.as_integer_ratio()
                )
                numerator, denominator = (Int(raw_numerator),
                                          Int(raw_denominator))
            elif isinstance(_numerator, (Int, int)):
                numerator, denominator = Int(_numerator), _ONE
            elif isinstance(_numerator, _Rational):
                numerator, denominator = (_numerator.numerator,
                                          _numerator.denominator)
            else:
                raise TypeError('First argument should be of '
                                f'type {Int}, {int} or {float}, '
                                f'but found: {type(_numerator)}.')
        elif isinstance(_denominator, (Int, int)):
            if not isinstance(_numerator, (Int, int)):
                raise TypeError(f'Numerator should be '
                                f'of type {Int} or {int}, '
                                f'but found: {type(_numerator)}.')
            numerator, denominator = Int(_numerator), Int(_denominator)
        else:
            raise TypeError(f'Denominator should be '
                            f'of type {Int} or {int}, '
                            f'but found: {type(_denominator)}.')
        if not denominator:
            raise ZeroDivisionError('Denominator should not be zero.')
        if _normalize:
            numerator, denominator = _normalize_components_sign(
                    *_normalize_components_moduli(numerator, denominator)
            )
        self._numerator, self._denominator = numerator, denominator
        return self

    def __abs__(self):
        return Fraction(abs(self.numerator), self.denominator,
                        _normalize=False)

    def __add__(self, other):
        return (self._add_fraction(other)
                if isinstance(other, Fraction)
                else self.__radd__(other))

    def __bool__(self):
        return bool(self.numerator)

    def __ceil__(self):
        return -(-self.numerator // self.denominator)

    def __divmod__(self, divisor):
        return (_divmod_rationals(self, divisor)
                if isinstance(divisor, (Fraction, Int, int))
                else NotImplemented)

    def __eq__(self, other):
        return (self.numerator == other.numerator
                and self.denominator == other.denominator
                if isinstance(other, Fraction)
                else (self.denominator == _ONE
                      and self.numerator == other
                      if isinstance(other, (Int, int))
                      else NotImplemented))

    def __float__(self):
        return int(self.numerator) / int(self.denominator)

    def __floor__(self):
        return self.numerator // self.denominator

    def __floordiv__(self, divisor):
        return ((self.numerator * divisor.denominator)
                // (self.denominator * divisor.numerator)
                if isinstance(divisor, Fraction)
                else (self.numerator // (self.denominator * divisor)
                      if isinstance(divisor, (Int, int))
                      else NotImplemented))

    def __ge__(self, other):
        return (self.numerator * other.denominator
                >= other.numerator * self.denominator
                if isinstance(other, Fraction)
                else (self.numerator >= other * self.denominator
                      if isinstance(other, (Int, int))
                      else NotImplemented))

    def __getstate__(self):
        return self._numerator, self._denominator

    def __gt__(self, other):
        return (self.numerator * other.denominator
                > other.numerator * self.denominator
                if isinstance(other, Fraction)
                else (self.numerator > other * self.denominator
                      if isinstance(other, (Int, int))
                      else NotImplemented))

    def __hash__(self) -> int:
        inverted_denominator = pow(self._denominator, _HASH_MODULUS - 2,
                                   _HASH_MODULUS)
        result = (((abs(self._numerator) * inverted_denominator)
                   % _HASH_MODULUS)
                  if inverted_denominator
                  else _HASH_INF)
        result = result if self >= 0 else -result
        return -2 if result == -1 else int(result)

    def __le__(self, other):
        return (self.numerator * other.denominator
                <= other.numerator * self.denominator
                if isinstance(other, Fraction)
                else (self.numerator <= other * self.denominator
                      if isinstance(other, (Int, int))
                      else NotImplemented))

    def __lt__(self, other):
        return (self.numerator * other.denominator
                < other.numerator * self.denominator
                if isinstance(other, Fraction)
                else (self.numerator < other * self.denominator
                      if isinstance(other, (Int, int))
                      else NotImplemented))

    def __mod__(self, divisor):
        return (Fraction((self.numerator * divisor.denominator)
                         % (self.denominator * divisor.numerator),
                         self.denominator * divisor.denominator)
                if isinstance(divisor, Fraction)
                else (Fraction(self.numerator
                               % (self.denominator * divisor),
                               self.denominator)
                      if isinstance(divisor, (Int, int))
                      else NotImplemented))

    def __mul__(self, other):
        return (self._mul_by_fraction(other)
                if isinstance(other, Fraction)
                else self.__rmul__(other))

    def __neg__(self):
        return Fraction(-self.numerator, self.denominator,
                        _normalize=False)

    def __pos__(self):
        return self

    def __pow__(self, exponent, divisor=None):
        return ((Fraction(self.numerator ** exponent,
                          self.denominator ** exponent,
                          _normalize=False)
                 if exponent >= _ZERO
                 else
                 Fraction(
                         *_normalize_components_sign(
                                 self.denominator ** -exponent,
                                 self.numerator ** -exponent
                         ),
                         _normalize=False
                 ))
                if isinstance(exponent, (Int, int)) and divisor is None
                else NotImplemented)

    def __radd__(self, other):
        return (self._add_int(Int(other))
                if isinstance(other, (Int, int))
                else NotImplemented)

    def __rdivmod__(self, dividend):
        return (_divmod_rationals(dividend, self)
                if isinstance(dividend, (Int, int))
                else NotImplemented)

    def __repr__(self):
        return (f'{type(self).__module__}.{type(self).__qualname__}'
                f'({self.numerator!r}, {self.denominator!r})')

    def __rfloordiv__(self, dividend):
        return ((dividend * self.denominator) // self.numerator
                if isinstance(dividend, (Int, int))
                else NotImplemented)

    def __rmod__(self, dividend):
        return (Fraction((dividend * self.denominator) % self.numerator,
                         self.denominator)
                if isinstance(dividend, (Int, int))
                else NotImplemented)

    def __rmul__(self, other):
        return (self._mul_by_int(Int(other))
                if isinstance(other, (Int, int))
                else NotImplemented)

    def __round__(self, digits=None):
        if digits is None:
            return self.round(TieBreaking.TO_EVEN)
        else:
            shift = 10 ** abs(digits)
            return (Fraction((self * shift).round(TieBreaking.TO_EVEN),
                             shift)
                    if digits > 0
                    else Fraction((self / shift).round(TieBreaking.TO_EVEN)
                                  * shift))

    def __rsub__(self, subtrahend):
        return (
            Fraction(
                    *_normalize_components_moduli(
                            subtrahend * self.denominator - self.numerator,
                            self.denominator
                    ),
                    _normalize=False
            )
            if isinstance(subtrahend, (Int, int))
            else NotImplemented
        )

    def __setstate__(self, state):
        self._numerator, self._denominator = state

    def __str__(self):
        return (str(self.numerator)
                if self.denominator == _ONE
                else f'{self.numerator}/{self.denominator}')

    def __sub__(self, minuend):
        return (Fraction(
                *_normalize_components_moduli(
                        self.numerator * minuend.denominator
                        - minuend.numerator * self.denominator,
                        self.denominator * minuend.denominator
                ),
                _normalize=False)
                if isinstance(minuend, Fraction)
                else
                (Fraction(
                        *_normalize_components_moduli(
                                self.numerator - minuend * self.denominator,
                                self.denominator
                        ),
                        _normalize=False
                )
                 if isinstance(minuend, (Int, int))
                 else NotImplemented))

    def __rtruediv__(self, dividend):
        return (self._rtruediv_by_int(Int(dividend))
                if isinstance(dividend, (Int, int))
                else NotImplemented)

    def __truediv__(self, divisor):
        return (
            Fraction(
                    *_normalize_components_sign(
                            *map(_mul,
                                 _normalize_components_moduli(
                                         self.numerator, divisor.numerator
                                 ),
                                 _normalize_components_moduli(
                                         divisor.denominator,
                                         self.denominator
                                 ))
                    ),
                    _normalize=False
            )
            if isinstance(divisor, Fraction)
            else (self._truediv_by_int(Int(divisor))
                  if isinstance(divisor, (Int, int))
                  else NotImplemented)
        )

    def __trunc__(self):
        return (self.__ceil__()
                if self.numerator < _ZERO
                else self.__floor__())

    def _add_fraction(self, other: 'Fraction') -> 'Fraction':
        return Fraction(
                *_normalize_components_moduli(
                        self.numerator * other.denominator
                        + other.numerator * self.denominator,
                        self.denominator * other.denominator
                ),
                _normalize=False
        )

    def _add_int(self, other: Int) -> 'Fraction':
        return Fraction(
                *_normalize_components_moduli(self.numerator
                                              + other * self.denominator,
                                              self.denominator),
                _normalize=False
        )

    def _mul_by_fraction(self, other: 'Fraction') -> 'Fraction':
        numerator, other_denominator = _normalize_components_moduli(
                self.numerator, other.denominator)
        other_numerator, denominator = _normalize_components_moduli(
                other.numerator, self.denominator)
        return Fraction(numerator * other_numerator,
                        denominator * other_denominator,
                        _normalize=False)

    def _mul_by_int(self, other: Int) -> 'Fraction':
        other, denominator = _normalize_components_moduli(other,
                                                          self.denominator)
        return Fraction(self.numerator * other, denominator,
                        _normalize=False)

    def _rtruediv_by_int(self, dividend: Int) -> 'Fraction':
        dividend, numerator = _normalize_components_moduli(dividend,
                                                           self.numerator)
        return Fraction(
                *_normalize_components_sign(dividend * self.denominator,
                                            numerator),
                _normalize=False
        )

    def _truediv_by_int(self, divisor: Int) -> 'Fraction':
        numerator, divisor = _normalize_components_moduli(self.numerator,
                                                          divisor)
        return Fraction(
                *_normalize_components_sign(numerator,
                                            divisor * self.denominator),
                _normalize=False
        )


def _divmod_rationals(dividend: _Union[Fraction, Int, int],
                      divisor: _Union[Fraction, Int, int]
                      ) -> _Tuple[Int, Fraction]:
    quotient, remainder_numerator = divmod(
            dividend.numerator * divisor.denominator,
            dividend.denominator * divisor.numerator
    )
    assert isinstance(quotient, Int)
    return quotient, Fraction(remainder_numerator,
                              dividend.denominator * divisor.denominator)


def _normalize_components_moduli(numerator: Int, denominator: Int
                                 ) -> _Tuple[Int, Int]:
    gcd = numerator.gcd(denominator)
    return numerator // gcd, denominator // gcd


def _normalize_components_sign(numerator: Int, denominator: Int
                               ) -> _Tuple[Int, Int]:
    return ((-numerator, -denominator)
            if denominator < _ZERO
            else (numerator, denominator))
