# License: MIT
# Copyright © 2022 Frequenz Energy-as-a-Service GmbH

"""Base exception classes.

# Exceptions

All exceptions generated by this library inherit from the
[`Error`][frequenz.channels.Error] exception.

Exceptions generated by channels inherit from the
[`ChannelError`][frequenz.channels.ChannelError] exception. When there is an attempt to
use a closed channel, a [`ChannelClosedError`][frequenz.channels.ChannelClosedError]
exception is raised.

# Causes

When a exception is caused by another exception, for example if the underlying channel
was closed while seding or receiving a message, the original exception will be
available as the cause of the exception:

```python show_lines="6:"
from frequenz.channels import Anycast, ChannelClosedError, SenderError

channel = Anycast[int](name="test-channel")
sender = channel.new_sender()

try:
    await sender.send(42)
except SenderError as error:
    match error.__cause__:
        case None:
            print("The message couldn't be sent for an known reason")
        case ChannelClosedError() as closed_error:
            print(f"The message couldn't be sent, channel closed: {closed_error}")
        case _ as unknown_error:
            print(f"The message couldn't be sent: {unknown_error}")
```

Tip:
    If you are using the async iteration interface for receivers, then you can
    access the cause of the
    [`ReceiverStoppedError`][frequenz.channels.ReceiverStoppedError] exception by
    explicitly calling [`receive()`][frequenz.channels.Receiver.receive] on the
    receiver after the iteration is done:

    ```python show_lines="6:"
    from frequenz.channels import Anycast, ChannelClosedError, ReceiverStoppedError

    channel = Anycast[int](name="test-channel")
    receiver = channel.new_receiver()

    async for message in receiver:
        print(message)
    try:
        await receiver.receive()
    except ReceiverStoppedError as error:
        print("The receiver was stopped")
        match error.__cause__:
            case None:
                print("The receiver was stopped without a known reason")
            case ChannelClosedError() as closed_error:
                print(f"The channel was closed with error: {closed_error}")
            case _ as unknown_error:
                print(f"The receiver was stopped due to an unknown error: {unknown_error}")
    ```
"""

from typing import Generic

from ._generic import ErroredChannelT_co


class Error(RuntimeError):
    """An error that originated in this library.

    This is useful if you want to catch all exceptions generated by this library.
    """

    def __init__(self, message: str):
        """Initialize this error.

        Args:
            message: The error message.
        """
        super().__init__(message)


class ChannelError(Error, Generic[ErroredChannelT_co]):
    """An error that originated in a channel.

    All exceptions generated by channels inherit from this exception.
    """

    def __init__(self, message: str, channel: ErroredChannelT_co):
        """Initialize this error.

        Args:
            message: The error message.
            channel: The channel where the error happened.
        """
        super().__init__(message)
        self.channel: ErroredChannelT_co = channel
        """The channel where the error happened."""


class ChannelClosedError(ChannelError[ErroredChannelT_co]):
    """A closed channel was used."""

    def __init__(self, channel: ErroredChannelT_co):
        """Initialize this error.

        Args:
            channel: The channel that was closed.
        """
        super().__init__(f"Channel {channel} was closed", channel)
