# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.

"""
Tests for Perspective Broker module.

TODO: update protocol level tests to use new connection API, leaving
only specific tests for old API.
"""

# issue1195 TODOs: replace pump.pump() with something involving Deferreds.
# Clean up warning suppression.

from __future__ import absolute_import, division

import sys, os, time, gc, weakref
from collections import deque

from io import BytesIO as StringIO
from zope.interface import implementer, Interface

from twisted.trial import unittest
from twisted.spread import pb, util, publish, jelly
from twisted.internet import protocol, main, reactor, address
from twisted.internet.error import ConnectionRefusedError
from twisted.internet.defer import Deferred, gatherResults, succeed
from twisted.protocols.policies import WrappingFactory
from twisted.python import failure, log
from twisted.python.compat import iterbytes, xrange, _PY3
from twisted.cred.error import UnauthorizedLogin, UnhandledCredentials
from twisted.cred import portal, checkers, credentials

from twisted.test.proto_helpers import _FakeConnector



class Dummy(pb.Viewable):
    def view_doNothing(self, user):
        if isinstance(user, DummyPerspective):
            return 'hello world!'
        else:
            return 'goodbye, cruel world!'


class DummyPerspective(pb.Avatar):
    """
    An L{IPerspective} avatar which will be used in some tests.
    """
    def perspective_getDummyViewPoint(self):
        return Dummy()



@implementer(portal.IRealm)
class DummyRealm(object):
    def requestAvatar(self, avatarId, mind, *interfaces):
        for iface in interfaces:
            if iface is pb.IPerspective:
                return iface, DummyPerspective(avatarId), lambda: None


class IOPump:
    """
    Utility to pump data between clients and servers for protocol testing.

    Perhaps this is a utility worthy of being in protocol.py?
    """
    def __init__(self, client, server, clientIO, serverIO):
        self.client = client
        self.server = server
        self.clientIO = clientIO
        self.serverIO = serverIO


    def flush(self):
        """
        Pump until there is no more input or output or until L{stop} is called.
        This does not run any timers, so don't use it with any code that calls
        reactor.callLater.
        """
        # failsafe timeout
        self._stop = False
        timeout = time.time() + 5
        while not self._stop and self.pump():
            if time.time() > timeout:
                return


    def stop(self):
        """
        Stop a running L{flush} operation, even if data remains to be
        transferred.
        """
        self._stop = True


    def pump(self):
        """
        Move data back and forth.

        Returns whether any data was moved.
        """
        self.clientIO.seek(0)
        self.serverIO.seek(0)
        cData = self.clientIO.read()
        sData = self.serverIO.read()
        self.clientIO.seek(0)
        self.serverIO.seek(0)
        self.clientIO.truncate()
        self.serverIO.truncate()
        self.client.transport._checkProducer()
        self.server.transport._checkProducer()
        for byte in iterbytes(cData):
            self.server.dataReceived(byte)
        for byte in iterbytes(sData):
            self.client.dataReceived(byte)
        if cData or sData:
            return 1
        else:
            return 0



def connectServerAndClient(test, clientFactory, serverFactory):
    """
    Create a server and a client and connect the two with an
    L{IOPump}.

    @param test: the test case where the client and server will be
        used.
    @type test: L{twisted.trial.unittest.TestCase}

    @param clientFactory: The factory that creates the client object.
    @type clientFactory: L{twisted.spread.pb.PBClientFactory}

    @param serverFactory: The factory that creates the server object.
    @type serverFactory: L{twisted.spread.pb.PBServerFactory}

    @return: a 3-tuple of (client, server, pump)