# -*- test-case-name: twisted.test.test_factories,twisted.internet.test.test_protocol -*-
# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.

"""
Standard implementations of Twisted protocol-related interfaces.

Start here if you are looking to write a new protocol implementation for
Twisted.  The Protocol class contains some introductory material.
"""

from __future__ import division, absolute_import

import random
from zope.interface import implementer

from twisted.python import log, failure, components
from twisted.internet import interfaces, error, defer


@implementer(interfaces.IProtocolFactory, interfaces.ILoggingContext)
class Factory:
    """
    This is a factory which produces protocols.

    By default, buildProtocol will create a protocol of the class given in
    self.protocol.
    """

    # put a subclass of Protocol here:
    protocol = None

    numPorts = 0
    noisy = True

    @classmethod
    def forProtocol(cls, protocol, *args, **kwargs):
        """
        Create a factory for the given protocol.

        It sets the C{protocol} attribute and returns the constructed factory
        instance.

        @param protocol: A L{Protocol} subclass

        @param args: Positional arguments for the factory.

        @param kwargs: Keyword arguments for the factory.

        @return: A L{Factory} instance wired up to C{protocol}.
        """
        factory = cls(*args, **kwargs)
        factory.protocol = protocol
        return factory


    def logPrefix(self):
        """
        Describe this factory for log messages.
        """
        return self.__class__.__name__


    def doStart(self):
        """Make sure startFactory is called.

        Users should not call this function themselves!
        """
        if not self.numPorts:
            if self.noisy:
                log.msg("Starting factory %r" % self)
            self.startFactory()
        self.numPorts = self.numPorts + 1

    def doStop(self):
        """Make sure stopFactory is called.

        Users should not call this function themselves!
        """
        if self.numPorts == 0:
            # this shouldn't happen, but does sometimes and this is better
            # than blowing up in assert as we did previously.
            return
        self.numPorts = self.numPorts - 1
        if not self.numPorts:
            if self.noisy:
                log.msg("Stopping factory %r" % self)
            self.stopFactory()

    def startFactory(self):
        """This will be called before I begin listening on a Port or Connector.

        It will only be called once, even if the factory is connected
        to multiple ports.

        This can be used to perform 'unserialization' tasks that
        are best put off until things are actually running, such
        as connecting to a database, opening files, etcetera.
        """

    def stopFactory(self):
        """This will be called before I stop listening on all Ports/Connectors.

        This can be overridden to perform 'shutdown' tasks such as disconnecting
        database connections, closing files, etc.

        It will be called, for example, before an application shuts down,
        if it was connected to a port. User code should not call this function
        directly.
        """

    def buildProtocol(self, addr):
        """Create an instance of a subclass of Protocol.

        The returned instance will handle input on an incoming server
        connection, and an attribute \"factory\" pointing to the creating
        factory.

        Override this method to alter how Protocol instances get created.

        @param addr: an object implementing L{twisted.internet.interfaces.IAddress}
        """
        p = self.protocol()
        p.factory = self
        return p


class ClientFactory(Factory):
    """A Protocol factory for clients.

    This can be used together with the various connectXXX methods in
    reactors.
    """

    def startedConnecting(self, connector):
        """Called when a connection has been started.

        You can call connector.stopConnecting() to stop the connection attempt.

        @param connector: a Connector object.
        """

    def clientConnectionFailed(self, connector, reason):
        """Called when a connection has failed to connect.

        It may be useful to call connector.connect() - this will reconnect.

        @type reason: L{twisted.python.failure.Failure}
        """

    def clientConnectionLost(self, connector, reason):
        """Called when an established connection is lost.

        It may be useful to call connector.connect() - this will reconnect.

        @type reason: L{twisted.python.failure.Failure}
        """


class _InstanceFactory(ClientFactory):
    """
    Factory used by ClientCreator.

    @ivar deferred: The L{Deferred} which represents this connection attempt and
        which will be fired when it succeeds or fails.

    @ivar pending: After a connection attempt succeeds or fails, a delayed call
        which will fire the L{Deferred} representing this connection attempt.
    """

    noisy = False
    pending = None

    def __init__(self, reactor, instance, deferred):
        self.reactor = reactor
        self.instance = instance
        self.deferred = deferred


    def __repr__(self):
        return "<ClientCreator factory: %r>" % (self.instance, )


    def buildProtocol(self, addr):
        """
        Return the pre-constructed protocol instance and arrange to fire the
        waiting L{Deferred} to indicate success establishing the connection.
        """
        self.pending = self.reactor.callLater(
            0, self.fire, self.deferred.callback, self.instance)
        self.deferred = None
        return self.instance


    def clientConnectionFailed(self, connector, reason):
        """
        Arrange to fire the waiting L{Deferred} with the given failure to
        indicate the connection could not be established.
        """
        self.pending = self.reactor.callLater(
            0, self.fire, self.deferred.errback, reason)
        self.deferred = None


    def fire(self, func, value):
        """
        Clear C{self.pending} to avoid a reference cycle and then invoke func
        with the value.
        """
        self.pending = None
        func(value)



class ClientCreator:
    """
    Client connections that do not require a factory.

    The various connect* methods create a protocol instance using the given
    protocol class and arguments, and connect it, returning a Deferred of the
    resulting protocol instance.

    Useful for cases when we don't really need a factory.  Mainly this
    is when there is no shared state between protocol instances, and no need
    to reconnect.

    The C{connectTCP}, C{connectUNIX}, and C{connectSSL} methods each return a
    L{Deferred} which will fire with an instance of the protocol class passed to
    L{ClientCreator.__init__}.  These Deferred can be cancelled to abort the
    connection attempt (in a very unlikely case, cancelling the Deferred may not
    prevent the protocol from being instantiated and connected to a transport;
    if this happens, it will be disconnected immediately afterwards and the
    Deferred will still errback with L{CancelledError}).
    """

    def __init__(self, reactor, protocolClass, *args, **kwargs):
        self.reactor = reactor
        self.protocolClass = protocolClass
        self.args = args
        self.kwargs = kwargs


    def _connect(self, method, *args, **kwargs):
        """
        Initiate a connection attempt.

        @param method: A callable which will actually start the connection
            attempt.  For example, C{reactor.connectTCP}.

        @param *args: Positional arguments to pass to C{method}, excluding the
            factory.

        @param **kwargs: Keyword arguments to pass to C{method}.

        @return: A L{Deferred} which fires with an instance of the protocol
            class passed to this L{ClientCreator}'s initializer or fails if the
            connection cannot be set up for some reason.
        """
        def cancelConnect(deferred):
            connector.disconnect()
            if f.pending is not None:
                f.pending.cancel()
        d = defer.Deferred(cancelConnect)
        f = _InstanceFactory(
            self.reactor, self.protocolClass(*self.args, **self.kwargs), d)
        connector = method(factory=f, *args, **kwargs)
        return d


    def connectTCP(self, host, port, timeout=30, bindAddress=None):
        """
        Connect to a TCP server.

        The parameters are all the same as to L{IReactorTCP.connectTCP} except
        that the factory parameter is omitted.

        @return: A L{Deferred} which fires with an instance of the protocol
            class passed to this L{ClientCreator}'s initializer or fails if the
            connection cannot be set up for some reason.
        """
        return self._connect(
            self.reactor.connectTCP, host, port, timeout=timeout,
            bindAddress=bindAddress)


    def connectUNIX(self, address, timeout=30, checkPID=False):
        """
        Connect to a Unix socket.

        The parameters are all the same as to L{IReactorUNIX.connectUNIX} except
        that the factory parameter is omitted.

        @return: A L{Deferred} which fires with an instance of the protocol
            class passed to this L{ClientCreator}'s initializer or fails if the
            connection cannot be set up for some reason.
        """
        return self._connect(
            self.reactor.connectUNIX, address, timeout=timeout,
            checkPID=checkPID)


    def connectSSL(self, host, port, contextFactory, timeout=30, bindAddress=None):
        """
        Connect to an SSL server.

        The parameters are all the same as to L{IReactorSSL.connectSSL} except
        that the factory parameter is omitted.

        @return: A L{Deferred} which fires with an instance of the protocol
            class passed to this L{ClientCreator}'s initializer or fails if the
            connection cannot be set up for some reason.
        """
        return self._connect(
            self.reactor.connectSSL, host, port,
            contextFactory=contextFactory, timeout=timeout,
            bindAddress=bindAddress)



class ReconnectingClientFactory(ClientFactory):
    """