from PyQt4.QtCore import QObject, pyqtSignature


def extractSignature(signal):
    """Extract the signature from a signal"""
    return signal.split("(")[1].split(")")[0]


def isSignal(value):
    return type(value) == str and value[0] == '2'


class PyQtHelper(QObject):
    """Helper class to circumvent the current limitations of PyQt4.

    Supports SIGNAL to SIGNAL disconnection, as well as a disconnectAll method.

    """

    __connections = {}

    def connect(self, *args):
        """Connect a source and keep track of the connection.
        
        If the given slot is a SIGNAL, wrap it.
        """
        if isSignal(args[-1]):
            signal = args[-1]
            @pyqtSignature(extractSignature(args[1]))
            def wrapper(*args_):
                self.emit(signal, *args_)
            wrapper._originalSignal = signal
            args = list(args)
            args[-1] = wrapper
        QObject.connect(*args)
        PyQtHelper.__storeConnection(*args)

    @staticmethod
    def disconnect(*args):
        # Extract arguments for readability
        source = args[0]
        signal = args[1]
        if len(args) == 3:
            slot = args[2]
            # See if slot is a simple function or an instance method of source
            if hasattr(slot, "im_self"):
                target = slot.im_self
            else:
                target = None

            if isSignal(slot):
                # Disconnect a SIGNAL from a SIGNAL
                for i, slot_ in enumerate(
                        PyQtHelper.__connections[source][signal][target]):
                    if hasattr(slot_, "_originalSignal"):
                        if slot_._originalSignal == slot:
                            QObject.disconnect(source, signal, slot_)
                            del PyQtHelper.__connections[source][signal][target][i]
                            return
            else:
                # Disconnect a SIGNAL from an object
                QObject.disconnect(source, signal, slot)
        else:
            # Disconnecting a SIGNAL from a Qt SLOT
            target = args[2]
            slot = args[3]
            QObject.disconnect(source, signal, target, slot)

        for i, slot_ in enumerate(
                PyQtHelper.__connections[source][signal][target]):
            if slot_ == slot:
                del PyQtHelper.__connections[source][signal][target][i]
                break                

    @staticmethod
    def disconnectAll(source, targetFilter=-1):
        """Disconnect all signals from a source.
        
        If targetFilter is given, then only connections pointing to it are
        disconnected.
        """
        if targetFilter == -1:
            # Disconnect everything
            for signal, targets in \
                    PyQtHelper.__connections[source].iteritems():
                for target, slots in targets.iteritems():
                    for slot in slots:
                        if target is None or target == slot.im_self:
                            # Disconnecting a simple function or an instance 
                            # method
                            QObject.disconnect(source, signal, slot)
                        else:
                            # Disconnecting a Qt SLOT
                            QObject.disconnect(source, signal, target, slot)
            del PyQtHelper.__connections[source]
        else:
            # Disconnect selectively
            filteredSourceConnections = {}
            for signal, targets in \
                    PyQtHelper.__connections[source].iteritems():
                filteredSourceConnections[signal] = {}
                for target, slots in targets.iteritems():
                    if target == targetFilter:
                        for slot in slots:
                            if target is None or \
                                    target == slot.im_self:
                                # Disconnecting a simple function or an 
                                # instance method
                                QObject.disconnect(source, signal, slot)
                            else:
                                # Disconnecting a Qt SLOT
                                QObject.disconnect(source, signal, target, 
                                        slot)
                    else:
                        filteredSourceConnections[signal][target] = slots
            PyQtHelper.__connections[source] = filteredSourceConnections
                        

    @staticmethod
    def __storeConnection(*args):
        # For readability ...
        source = args[0]
        signal = args[1]
        if len(args) == 3:
            slot = args[2]
            # See if slot is a simple function or an instance method of source
            if hasattr(slot, "im_self"):
                target = slot.im_self
            else:
                target = None
        else:
            target = args[2]
            slot = args[3]

        # Create the source/signal dictionnaries if they dont exist
        if not PyQtHelper.__connections.has_key(source):
            PyQtHelper.__connections[source] = {}
            PyQtHelper.__connections[source][signal] = {}
        elif not PyQtHelper.__connections[source].has_key(signal):
            PyQtHelper.__connections[source][signal] = {}
        if not PyQtHelper.__connections[source][signal].has_key(target):
            PyQtHelper.__connections[source][signal][target] = []

        # And finally append the slot to the target
        PyQtHelper.__connections[source][signal][target].append(slot)
