"""Time representation with nanosecond precision.

Instant provides precise time handling for discrete-event simulation.
Using nanoseconds internally avoids floating-point precision issues that
can cause non-deterministic behavior when comparing event times.

Special values:
- Instant.Epoch: Time zero (start of simulation)
- Instant.Infinity: Represents unbounded time (for auto-termination)
"""

from typing import Union


class Instant:
    """Immutable time value with nanosecond precision.

    Stores time as an integer number of nanoseconds to avoid floating-point
    errors. Supports arithmetic with other Instants or float seconds.

    Attributes:
        nanoseconds: The time value in nanoseconds.
    """
    def __init__(self, nanoseconds: int):
        self.nanoseconds = nanoseconds

    @classmethod
    def from_seconds(cls, seconds: int | float) -> "Instant":
        """Create an Instant from a seconds value.

        Args:
            seconds: Time in seconds (int or float).

        Returns:
            New Instant representing the given time.

        Raises:
            TypeError: If seconds is not int or float.
        """
        if isinstance(seconds, int):
            return cls(seconds * 1_000_000_000)

        if isinstance(seconds, float):
            return cls(int(seconds * 1_000_000_000))

        raise TypeError("seconds must be int or float")

    def to_seconds(self) -> float:
        """Convert this Instant to seconds as a float."""
        return float(self.nanoseconds) / 1_000_000_000

    def __add__(self, other: Union['Instant', int, float]):
        if isinstance(other, (int, float)):
            return Instant(self.nanoseconds + int(other * 1_000_000_000))
        elif isinstance(other, Instant):
            return Instant(self.nanoseconds + other.nanoseconds)
        return NotImplemented

    def __sub__(self, other: Union['Instant', int, float]):
        if isinstance(other, (int, float)):
            return Instant(self.nanoseconds - int(other * 1_000_000_000))
        elif isinstance(other, Instant):
            return Instant(self.nanoseconds - other.nanoseconds)
        return NotImplemented

    # Equality
    def __eq__(self, other):
        if not isinstance(other, Instant):
            return NotImplemented
        return self.nanoseconds == other.nanoseconds

    def __ne__(self, other):
        return not self.__eq__(other)

    # Less than
    def __lt__(self, other):
        if not isinstance(other, Instant):
            return NotImplemented
        return self.nanoseconds < other.nanoseconds

    def __le__(self, other):
        return self.__lt__(other) or self.__eq__(other)

    def __gt__(self, other):
        return not self.__le__(other)

    def __ge__(self, other):
        return not self.__lt__(other)

    def __repr__(self) -> str:
        """Return a human-readable ISO-like duration string with microsecond precision.
        
        Format: T{hours}:{minutes}:{seconds}.{microseconds}
        Examples: T00:00:01.500000, T01:23:45.678901
        """
        total_us = self.nanoseconds // 1_000  # Convert to microseconds
        
        us = total_us % 1_000_000
        total_seconds = total_us // 1_000_000
        seconds = total_seconds % 60
        total_minutes = total_seconds // 60
        minutes = total_minutes % 60
        hours = total_minutes // 60
        
        return f"T{hours:02d}:{minutes:02d}:{seconds:02d}.{us:06d}"


class _InfiniteInstant(Instant):
    """Singleton representing positive infinity for time comparisons.

    Used as the default end_time for auto-terminating simulations.
    Greater than all finite Instants. Arithmetic with infinity yields
    infinity (absorbing).
    """

    def __init__(self):
        super().__init__(float('inf'))

    def __add__(self, other: Union['Instant', int, float]):
        if isinstance(other, (int, float, Instant)):
            return self
        return NotImplemented

    def __sub__(self, other: Union['Instant', int, float]):
        if isinstance(other, Instant) and other.nanoseconds == float('inf'):
            return NotImplemented
        if isinstance(other, (int, float, Instant)):
            return self
        return NotImplemented

    def __eq__(self, other):
        if not isinstance(other, Instant):
            return NotImplemented
        return other.nanoseconds == float('inf')

    def __lt__(self, other):
        if not isinstance(other, Instant):
            return NotImplemented
        return False

    def to_seconds(self) -> float:
        return float('inf')

    def __repr__(self):
        return "Instant.Infinity"

# Singleton instances
Instant.Infinity = _InfiniteInstant()
Instant.Epoch = Instant(0)
