#
# DBus connections
#
# Copyright (C) 2019  Red Hat, Inc.  All rights reserved.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
#
import os

from dasbus.connection import SystemMessageBus, MessageBus
from dasbus.constants import DBUS_STARTER_ADDRESS
from dasbus.error import ErrorMapper, get_error_decorator, AbstractErrorRule
from pyanaconda.anaconda_loggers import get_module_logger
from pyanaconda.core.constants import DBUS_ANACONDA_SESSION_ADDRESS, ANACONDA_BUS_ADDR_FILE
from pyanaconda.modules.common.errors import register_errors

log = get_module_logger(__name__)

__all__ = ["DBus", "SystemBus", "error_mapper", "dbus_error"]


class AnacondaMessageBus(MessageBus):
    """Representation of an Anaconda bus connection."""

    @property
    def address(self):
        """The bus address."""
        return self._find_bus_address()

    def _get_connection(self):
        """Get a connection to a bus at the specified address."""
        bus_address = self._find_bus_address()

        log.info("Connecting to the Anaconda bus at %s.", bus_address)
        return self._provider.get_addressed_bus_connection(bus_address)

    def _find_bus_address(self):
        """Get the address of the Anaconda bus."""
        if DBUS_ANACONDA_SESSION_ADDRESS in os.environ:
            return os.environ.get(DBUS_ANACONDA_SESSION_ADDRESS)

        if os.path.exists(ANACONDA_BUS_ADDR_FILE):
            with open(ANACONDA_BUS_ADDR_FILE, 'rt') as f:
                return f.read().strip()

        raise ConnectionError("Can't find Anaconda bus address!")


class DefaultMessageBus(AnacondaMessageBus):
    """Representation of a default bus connection."""

    def _find_bus_address(self):
        """Get the address of the default bus.

        Connect to the bus specified by the environmental variable
        DBUS_STARTER_ADDRESS. If it is not specified, connect to
        the Anaconda bus.
        """
        if DBUS_STARTER_ADDRESS in os.environ:
            return os.environ.get(DBUS_STARTER_ADDRESS)

        return super()._find_bus_address()


class AnacondaErrorMapper(ErrorMapper):
    """Map Anaconda exceptions to DBus errors."""

    def reset_rules(self):
        """Reset rules in the error mapper."""
        super().reset_rules()
        self.add_rule(DefaultNameErrorRule(
            "org.fedoraproject.Anaconda.Error"
        ))


class DefaultNameErrorRule(AbstractErrorRule):
    """Default rule for mapping an unknown exception to a DBus error name."""

    def __init__(self, default_name):
        """Create a new rule.

        :param default_name: a default name of a DBus error
        """
        self._default_name = default_name

    def match_type(self, exception_type):
        """Match every Python exception raised on the server side."""
        return True

    def get_name(self, exception_type):
        """Return a default error name for every matched exception."""
        return self._default_name

    def match_name(self, error_name):
        """Don't apply this rule on the client side."""
        return False

    def get_type(self, error_name):
        """There is no default error type in this rule."""
        return None


# System bus.
SystemBus = SystemMessageBus()

# The mapper of DBus errors.
error_mapper = AnacondaErrorMapper()

# The decorator for DBus errors.
dbus_error = get_error_decorator(error_mapper)

# Register all DBus errors.
register_errors()

# Default bus. Anaconda uses this connection.
DBus = DefaultMessageBus(
    error_mapper=error_mapper
)
