from __future__ import annotations

import numpy


# 1nm snap (0.001 µm)
GRID_SNAP_UM = 0.001


def snap_nm(value: float) -> float:
    """
    Snap a coordinate to the nearest 1nm (0.001 um).
    """
    return round(value * 1000) / 1000


class Port:
    """
    A port defined by (x, y, orientation) in micrometers.
    """
    __slots__ = ('x', 'y', 'orientation')

    def __init__(
            self,
            x: float,
            y: float,
            orientation: float,
            ) -> None:
        self.x = snap_nm(x)
        self.y = snap_nm(y)
        self.orientation = float(orientation % 360)

    def __repr__(self) -> str:
        return f'Port(x={self.x}, y={self.y}, orientation={self.orientation})'

    def __eq__(self, other: object) -> bool:
        if not isinstance(other, Port):
            return False
        return (self.x == other.x and
                self.y == other.y and
                self.orientation == other.orientation)

    def __hash__(self) -> int:
        return hash((self.x, self.y, self.orientation))


def translate_port(port: Port, dx: float, dy: float) -> Port:
    """
    Translate a port by (dx, dy).
    """
    return Port(port.x + dx, port.y + dy, port.orientation)


def rotate_port(port: Port, angle: float, origin: tuple[float, float] = (0, 0)) -> Port:
    """
    Rotate a port by a multiple of 90 degrees around an origin.
    """
    ox, oy = origin
    px, py = port.x, port.y

    rad = numpy.radians(angle)
    qx = snap_nm(ox + numpy.cos(rad) * (px - ox) - numpy.sin(rad) * (py - oy))
    qy = snap_nm(oy + numpy.sin(rad) * (px - ox) + numpy.cos(rad) * (py - oy))

    return Port(qx, qy, port.orientation + angle)
