"""Common plugins for Louie."""

from louie import dispatcher
from louie import error


def install_plugin(plugin):
    cls = plugin.__class__
    for p in dispatcher.plugins:
        if p.__class__ is cls:
            raise error.PluginTypeError(
                'Plugin of type {0!r} already installed.'.format(cls))
    dispatcher.plugins.append(plugin)

def remove_plugin(plugin):
    dispatcher.plugins.remove(plugin)


class Plugin(object):
    """Base class for Louie plugins.

    Plugins are used to extend or alter the behavior of Louie
    in a uniform way without having to modify the Louie code
    itself.
    """

    def is_live(self, receiver):
        """Return True if the receiver is still live.

        Only called for receivers who have already been determined to
        be live by default Louie semantics.
        """
        return True

    def wrap_receiver(self, receiver):
        """Return a callable that passes arguments to the receiver.

        Useful when you want to change the behavior of all receivers.
        """
        return receiver


class QtWidgetPlugin(Plugin):
    """A Plugin for Louie that knows how to handle Qt widgets
    when using PyQt built with SIP 4 or higher.

    Weak references are not useful when dealing with QWidget
    instances, because even after a QWidget is closed and destroyed,
    only the C++ object is destroyed.  The Python 'shell' object
    remains, but raises a RuntimeError when an attempt is made to call
    an underlying QWidget method.

    This plugin alleviates this behavior, and if a QWidget instance is
    found that is just an empty shell, it prevents Louie from
    dispatching to any methods on those objects.
    """

    def __init__(self):
        try:
            import qt
        except ImportError:
            self.is_live = self._is_live_no_qt
        else:
            self.qt = qt

    def is_live(self, receiver):
        """If receiver is a method on a QWidget, only return True if
        it hasn't been destroyed."""
        if (hasattr(receiver, 'im_self') and
            isinstance(receiver.im_self, self.qt.QWidget)
            ):
            try:
                receiver.im_self.x()
            except RuntimeError:
                return False
        return True

    def _is_live_no_qt(self, receiver):
        return True


class TwistedDispatchPlugin(Plugin):
    """Plugin for Louie that wraps all receivers in callables
    that return Twisted Deferred objects.

    When the wrapped receiver is called, it adds a call to the actual
    receiver to the reactor event loop, and returns a Deferred that is
    called back with the result.
    """

    def __init__(self):
        # Don't import reactor ourselves, but make access to it
        # easier.
        from twisted import internet
        from twisted.internet.defer import Deferred
        self._internet = internet
        self._Deferred = Deferred

    def wrap_receiver(self, receiver):
        def wrapper(*args, **kw):
            d = self._Deferred()
            def called(dummy):
                return receiver(*args, **kw)
            d.addCallback(called)
            self._internet.reactor.callLater(0, d.callback, None)
            return d
        return wrapper

