"""@package docstring
B-ASIC Port Module.
TODO: More info.
"""

from abc import ABC, abstractmethod
from typing import NewType, Optional, List

from b_asic.signal import Signal
from b_asic.operation import Operation

PortId = NewType("PortId", int)


class Port(ABC):
    """Abstract port class.
    TODO: More info.
    """

    _port_id: PortId
    _operation: Operation

    def __init__(self, port_id: PortId, operation: Operation):
        self._port_id = port_id
        self._operation = operation

    @property
    def identifier(self) -> PortId:
        """Get the unique identifier."""
        return self._port_id

    @property
    def operation(self) -> Operation:
        """Get the connected operation."""
        return self._operation

    @property
    @abstractmethod
    def signals(self) -> List[Signal]:
        """Get a list of all connected signals."""
        raise NotImplementedError

    @abstractmethod
    def signal(self, i: int = 0) -> Signal:
        """Get the connected signal at index i."""
        raise NotImplementedError

    @abstractmethod
    def signal_count(self) -> int:
        """Get the number of connected signals."""
        raise NotImplementedError

    @abstractmethod
    def connect(self, signal: Signal) -> None:
        """Connect a signal."""
        raise NotImplementedError

    @abstractmethod
    def disconnect(self, i: int = 0) -> None:
        """Disconnect a signal."""
        raise NotImplementedError


class InputPort(Port):
    """Input port.
    TODO: More info.
    """
    _source_signal: Optional[Signal]

    def __init__(self, port_id: PortId, operation: Operation):
        super().__init__(port_id, operation)
        self._source_signal = None

    @property
    def signals(self) -> List[Signal]:
        return [] if self._source_signal is None else [self._source_signal]

    def signal(self, i: int = 0) -> Signal:
        assert 0 <= i < self.signal_count() # TODO: Error message.
        assert self._source_signal is not None # TODO: Error message.
        return self._source_signal

    def signal_count(self) -> int:
        return 0 if self._source_signal is None else 1

    def connect(self, signal: Signal) -> None:
        self._source_signal = signal
        signal.destination = self

    def disconnect(self, i: int = 0) -> None:
        assert 0 <= i < self.signal_count() # TODO: Error message.
        self._source_signal.disconnect_source()
        self._source_signal = None

    # TODO: More stuff.


class OutputPort(Port):
    """Output port.
    TODO: More info.
    """

    _destination_signals: List[Signal]

    def __init__(self, port_id: PortId, operation: Operation):
        super().__init__(port_id, operation)
        self._destination_signals = []

    @property
    def signals(self) -> List[Signal]:
        return self._destination_signals.copy()

    def signal(self, i: int = 0) -> Signal:
        assert 0 <= i < self.signal_count() # TODO: Error message.
        return self._destination_signals[i]

    def signal_count(self) -> int:
        return len(self._destination_signals)

    def connect(self, signal: Signal) -> None:
        assert signal not in self._destination_signals # TODO: Error message.
        self._destination_signals.append(signal)
        signal.source = self

    def disconnect(self, i: int = 0) -> None:
        assert 0 <= i < self.signal_count() # TODO: Error message.
        del self._destination_signals[i]

    # TODO: More stuff.