"""Messages for talking to the DBus daemon itself

Generated by jeepney.bindgen and modified by hand.
"""
from .low_level import Message, MessageType, HeaderFields
from .wrappers import MessageGenerator, new_method_call

__all__ = [
    'DBusNameFlags',
    'DBus',
    'message_bus',
    'Monitoring',
    'Stats',
    'MatchRule',
]

class DBusNameFlags:
    allow_replacement = 1
    replace_existing = 2
    do_not_queue = 4

class DBus(MessageGenerator):
    """Messages to talk to the message bus
    """
    interface = 'org.freedesktop.DBus'

    def __init__(self, object_path='/org/freedesktop/DBus',
                 bus_name='org.freedesktop.DBus'):
        super().__init__(object_path=object_path, bus_name=bus_name)

    def Hello(self):
        return new_method_call(self, 'Hello')

    def RequestName(self, name, flags=0):
        return new_method_call(self, 'RequestName', 'su', (name, flags))

    def ReleaseName(self, name):
        return new_method_call(self, 'ReleaseName', 's', (name,))

    def StartServiceByName(self, name):
        return new_method_call(self, 'StartServiceByName', 'su',
                               (name, 0))

    def UpdateActivationEnvironment(self, env):
        return new_method_call(self, 'UpdateActivationEnvironment', 'a{ss}',
                               (env,))

    def NameHasOwner(self, name):
        return new_method_call(self, 'NameHasOwner', 's', (name,))

    def ListNames(self):
        return new_method_call(self, 'ListNames')

    def ListActivatableNames(self):
        return new_method_call(self, 'ListActivatableNames')

    def AddMatch(self, rule):
        """*rule* can be a str or a :class:`MatchRule` instance"""
        if isinstance(rule, MatchRule):
            rule = rule.serialise()
        return new_method_call(self, 'AddMatch', 's', (rule,))

    def RemoveMatch(self, rule):
        if isinstance(rule, MatchRule):
            rule = rule.serialise()
        return new_method_call(self, 'RemoveMatch', 's', (rule,))

    def GetNameOwner(self, name):
        return new_method_call(self, 'GetNameOwner', 's', (name,))

    def ListQueuedOwners(self, name):
        return new_method_call(self, 'ListQueuedOwners', 's', (name,))

    def GetConnectionUnixUser(self, name):
        return new_method_call(self, 'GetConnectionUnixUser', 's', (name,))

    def GetConnectionUnixProcessID(self, name):
        return new_method_call(self, 'GetConnectionUnixProcessID', 's', (name,))

    def GetAdtAuditSessionData(self, name):
        return new_method_call(self, 'GetAdtAuditSessionData', 's', (name,))

    def GetConnectionSELinuxSecurityContext(self, name):
        return new_method_call(self, 'GetConnectionSELinuxSecurityContext', 's',
                               (name,))

    def ReloadConfig(self):
        return new_method_call(self, 'ReloadConfig')

    def GetId(self):
        return new_method_call(self, 'GetId')

    def GetConnectionCredentials(self, name):
        return new_method_call(self, 'GetConnectionCredentials', 's', (name,))

message_bus = DBus()

class Monitoring(MessageGenerator):
    interface = 'org.freedesktop.DBus.Monitoring'

    def __init__(self, object_path='/org/freedesktop/DBus',
                 bus_name='org.freedesktop.DBus'):
        super().__init__(object_path=object_path, bus_name=bus_name)

    def BecomeMonitor(self, rules):
        """Convert this connection to a monitor connection (advanced)"""
        return new_method_call(self, 'BecomeMonitor', 'asu', (rules, 0))

class Stats(MessageGenerator):
    interface = 'org.freedesktop.DBus.Debug.Stats'

    def __init__(self, object_path='/org/freedesktop/DBus',
                 bus_name='org.freedesktop.DBus'):
        super().__init__(object_path=object_path, bus_name=bus_name)

    def GetStats(self):
        return new_method_call(self, 'GetStats')

    def GetConnectionStats(self, arg0):
        return new_method_call(self, 'GetConnectionStats', 's',
                               (arg0,))

    def GetAllMatchRules(self):
        return new_method_call(self, 'GetAllMatchRules')


class MatchRule:
    """Construct a match rule to subscribe to DBus messages.
    
    e.g.::
    
        mr = MatchRule(
            interface='org.freedesktop.DBus',
            member='NameOwnerChanged',
            type='signal'
        )
        msg = message_bus.AddMatch(mr)
        # Send this message to subscribe to the signal
    """
    def __init__(self, *, type=None, sender=None, interface=None, member=None,
                 path=None, path_namespace=None, destination=None,
                 eavesdrop=False):
        if isinstance(type, str):
            type = MessageType[type]
        self.message_type = type
        fields = {
            'sender': sender,
            'interface': interface,
            'member': member,
            'path': path,
            'destination': destination,
        }
        self.header_fields = {
            k: v for (k, v) in fields.items() if (v is not None)
        }
        self.path_namespace = path_namespace
        self.eavesdrop = eavesdrop
        self.arg_conditions = {}

    def add_arg_condition(self, argno: int, value: str, kind='string'):
        """Add a condition for a particular argument
        
        argno: int, 0-63
        kind: 'string', 'path', 'namespace'
        """
        if kind not in {'string', 'path', 'namespace'}:
            raise ValueError("kind={!r}".format(kind))
        if kind == 'namespace' and argno != 0:
            raise ValueError("argno must be 0 for kind='namespace'")
        self.arg_conditions[argno] = (value, kind)

    def serialise(self) -> str:
        """Convert to a string to use in an AddMatch call to the message bus"""
        pairs = list(self.header_fields.items())

        if self.message_type:
            pairs.append(('type', self.message_type.name))

        if self.path_namespace:
            pairs.append(('path_namespace', self.path_namespace))

        if self.eavesdrop:
            pairs.append(('eavesdrop', 'true'))

        for argno, (val, kind) in self.arg_conditions.items():
            if kind == 'string':
                kind = ''
            pairs.append((f'arg{argno}{kind}', val))

        # Quoting rules: single quotes ('') needed if the value contains a comma.
        # A literal ' can only be represented outside single quotes, by
        # backslash-escaping it. No escaping inside the quotes.
        # The simplest way to handle this is to use '' around every value, and
        # use '\'' (end quote, escaped ', restart quote) for literal ' .
        return ','.join(
            "{}='{}'".format(k, v.replace("'", r"'\''")) for (k, v) in pairs
        )

    def matches(self, msg: Message) -> bool:
        """Returns True if msg matches this rule"""
        h = msg.header
        if (self.message_type is not None) and h.message_type != self.message_type:
            return False

        for field, expected in self.header_fields.items():
            if h.fields.get(HeaderFields[field], None) != expected:
                return False

        if self.path_namespace is not None:
            path = h.fields.get(HeaderFields.path, '\0')
            path_ns = self.path_namespace.rstrip('/')
            if not ((path == path_ns) or path.startswith(path_ns + '/')):
                return False

        for argno, (expected, kind) in self.arg_conditions.items():
            if argno >= len(msg.body):
                return False
            arg = msg.body[argno]
            if not isinstance(arg, str):
                return False
            if kind == 'string':
                if arg != expected:
                    return False
            elif kind == 'path':
                if not (
                    (arg == expected)
                    or (expected.endswith('/') and arg.startswith(expected))
                    or (arg.endswith('/') and expected.startswith(arg))
                ):
                    return False
            elif kind == 'namespace':
                if not (
                    (arg == expected)
                    or arg.startswith(expected + '.')
                ):
                    return False

        return True
