<filename>uprate/rate.py
from __future__ import annotations

from typing import Union

__all__ = (
    "Rate",
    "Seconds",
    "Minutes",
    "Hours",
    "Days",
    "Weeks",
    "Months"
)

class RateGroup:
    __slots__ = ("_data",)

    _data: list[Rate]

    def __init__(self):
        self._data = []

    def __or__(self, other) -> RateGroup:
        if isinstance(other, Rate):
            self._data.append(other)
            return self
        elif isinstance(other, self.__class__):
            self._data = self._data + other._data
            return self
        return NotImplemented

    # __ror__ wouldn't be called
    # unless someone is using it wrong or
    # is dealing with uprate's internals
    __ror__ = __or__


class Rate:
    """A rate consisting of uses and time period

    Used to define rates for ratelimits
    with syntatical expressions.

    You should refrain from creating your own Rate objects
    and instead use the objects provided in the library.

    Attributes
    ----------
    uses: int
        Number of uses per time period
    period: float
        The time period of the rate in seconds
    """
    __slots__ = ("uses", "period")

    uses: int
    period: float

    def __init__(self, uses: int, period: float) -> None:
        self.uses = uses
        self.period = period

    def __call__(self, magnitude: float) -> Rate:
        return self.__class__(self.uses, self.period * magnitude)

    def __or__(self, other: Union[Rate, RateGroup]) -> RateGroup:
        if isinstance(other, RateGroup):
            return other | self
        elif isinstance(other, self.__class__):
            return RateGroup() | self | other
        return NotImplemented

    __ror__ = __or__

    def __rtruediv__(self, other: int) -> Rate:
        return self.__class__(other, self.period)

    def __mul__(self, other: float) -> Rate:
        # Let multipling a Rate increase the time period
        if isinstance(other, (float, int)):
            return self.__class__(self.uses, self.period * other)
        return NotImplemented

    __rmul__ = __mul__

    def __truediv__(self, other: float) -> Rate:
        # Let dividing a Rate decrease the time period
        if isinstance(other, (float, int)):
            return self.__class__(self.uses, self.period / other)
        return NotImplemented

    def __add__(self, other: Rate) -> Rate:
        if isinstance(other, self.__class__):
            if other.uses != 1 or self.uses != 1:
                raise ValueError("Cannot 'add' two rates which have uses other than one")
            return self.__class__(1, self.period + other.period)
        return NotImplemented

    __radd__ = __add__

    # object has other as object

    def __eq__(self, other) -> bool:
        if isinstance(other, self.__class__):
            return self.period == other.period and self.uses == other.uses
        return NotImplemented

    def __ne__(self, other) -> bool:
        if isinstance(other, self.__class__):
            return not (self == other)
        return False

    def __hash__(self) -> int:
        # This might be a bad idea
        return hash((self.uses, self.period))

    def __str__(self) -> str:
        return f"{self.uses}/{self.period}"


Seconds = Rate(1, 1)
"""A Rate of 1 use / 1 Second
Used in syntatical expression to define rate

Example
-------
.. code-block:: python

    assert 2 / Seconds(3) == 2 / (3 * Seconds)
    ...
    # 30 uses per 2 min 30 sec but also 2 uses per 2 seconds
    api_rate: uprate.rate.RateGroup = 2/Seconds(2) | 30/(2 * Minutes + 30 * Seconds)

"""

Minutes = 60 * Seconds
"""Rate of 1 use / 1 Minute"""

Hours = 60 * Minutes
"""Rate of 1 use / 1 Hour"""

Days = 24 * Hours
"""Rate of 1 use / 1 Day"""

Weeks = 7 * Days
"""Rate of 1 use / 1 Week"""

Months = 30 * Days
"""Rate of 1 use / 1 Month


Note
----
If you need larger time periods you can create them by

.. code-block:: python

    Year = Rate(1, 60 * 60 * 24 * 365)
    # Proceed to use like other unitary use rates
    rate = 5 / Year(2) # 5 uses per 2 years

"""
