# -*- coding: utf-8 -*-
# Moovida - Home multimedia server
# Copyright (C) 2006-2009 Fluendo Embedded S.L. (www.fluendo.com).
# All rights reserved.
#
# This file is available under one of two license agreements.
#
# This file is licensed under the GPL version 3.
# See "LICENSE.GPL" in the root of this distribution including a special
# exception to use Moovida with Fluendo's plugins.
#
# The GPL part of Moovida is also available under a commercial licensing
# agreement from Fluendo.
# See "LICENSE.Moovida" in the root directory of this distribution package
# for details on that license.
#
# Author: Olivier Tilloy <olivier@fluendo.com>

"""
Unit tests for the L{elisa.core.utils.internet} module.
"""

from twisted.trial.unittest import TestCase
from twisted.internet import reactor
from twisted.web2 import iweb, server, responsecode
from twisted.web2.http import StatusResponse
from twisted.web2.channel.http import HTTPFactory

from zope.interface import implements

from elisa.core.utils import internet, defer
from elisa.core.utils.cancellable_defer import CancelledError

import time, random, string, os


TEST_HOST = 'localhost'
TEST_HTTP_PORT = 4242
TEST_HTTPS_PORT = 4243
TEST_SERVER_TITLE = 'Moovida test HTTP Server'


class TestServerDelayedResource(object):
    implements(iweb.IResource)

    """
    Resource that waits a given delay before answering.
    """

    def __init__(self, delay):
        # The delay before answering, in seconds
        self._delay = delay

    def renderHTTP(self, request):
        time.sleep(self._delay)
        contents = ''.join([random.choice(string.printable)
                            for i in xrange(random.randint(100, 1000))])
        response = StatusResponse(responsecode.OK, contents, TEST_SERVER_TITLE)
        return response


class TestServerRootResource(object):
    implements(iweb.IResource)

    """
    Root resource served by the test server ('/').
    This resource is responsible for addressing the other resources (its
    children) when they are requested.
    """

    def __init__(self):
        # Children resources for our test server
        self.my_children = {'': self,
                            'delayed_1': TestServerDelayedResource(1)}

    def locateChild(self, request, segments):
        res = self.my_children.get(segments[0])
        if res is self:
            return (self, server.StopTraversal)
        elif res is None:
            return (None, segments)
        else:
            return (res, segments[1:])

    def renderHTTP(self, request):
        response = StatusResponse(responsecode.OK, "Server's root resource: /",
                                  TEST_SERVER_TITLE)
        return response


class TestHttpServerRequest(server.Request):
    site = server.Site(TestServerRootResource())


class SetupServerMixin(object):

    scheme = 'http'
    host = TEST_HOST
    port = 0

    def setUpClass(self):
        # Start listening
        self.factory = HTTPFactory(TestHttpServerRequest)
        self._port = self.listen()

    def listen(self):
        raise NotImplementedError()

    def tearDownClass(self):
        # Stop listening
        return defer.maybeDeferred(self._port.stopListening)


class SetupHTTPServerMixin(SetupServerMixin):

    def listen(self):
        self.port = TEST_HTTP_PORT
        return reactor.listenTCP(self.port, self.factory)


class SetupHTTPSServerMixin(SetupServerMixin):

    def _generate_certificate(self):
        base = os.path.abspath(self.mktemp())
        org = 'fluendo.moovida'
        org_unit = '%s:server' % org
        from twisted.test.test_ssl import generateCertificateFiles
        generateCertificateFiles(base, org, org_unit)
        key = os.extsep.join((base, 'key'))
        cert = os.extsep.join((base, 'cert'))
        return key, cert

    def listen(self):
        self.scheme = 'https'
        self.port = TEST_HTTPS_PORT
        key, cert = self._generate_certificate()
        from twisted.internet import ssl
        return reactor.listenSSL(self.port, self.factory,
                                 ssl.DefaultOpenSSLContextFactory(key, cert))


class TestCancellableGetPageMixin(object):

    def test_get_page(self):
        uri = '%s://%s:%s/' % (self.scheme, self.host, self.port)
        dfr = internet.get_page(uri)
        return dfr

    def test_get_page_cancel_immediate(self):
        uri = '%s://%s:%s/' % (self.scheme, self.host, self.port)
        dfr = internet.get_page(uri)

        def got(result):
            self.fail()

        def error(failure, wait_for_cleanup_dfr):
            failure.trap(CancelledError)
            # Give the name resolver some time to perform its cleanup,
            # otherwise this test may error out with an "unclean reactor"
            # failure.
            reactor.callLater(0.1, wait_for_cleanup_dfr.callback, None)

        wait_for_cleanup_dfr = defer.Deferred()
        dfr.addCallbacks(got, error, errbackArgs=(wait_for_cleanup_dfr,))
        dfr.cancel()
        return wait_for_cleanup_dfr

    def test_get_page_cancel_delayed(self):
        uri = '%s://%s:%s/delayed_1' % (self.scheme, self.host, self.port)
        dfr = internet.get_page(uri)

        def got(result):
            self.fail()

        def error(failure):
            failure.trap(CancelledError)

        dfr.addCallbacks(got, error)
        reactor.callLater(0.1, dfr.cancel)
        return dfr


class TestCancellableGetPageHTTP(TestCase, TestCancellableGetPageMixin,
                                 SetupHTTPServerMixin):
    pass


class TestCancellableGetPageHTTPS(TestCase, TestCancellableGetPageMixin,
                                  SetupHTTPSServerMixin):
    pass


class TestCancellableGetPageErrors(TestCase):

    def test_unknown_scheme(self):
        uri = 'crap://%s/' % TEST_HOST
        dfr = internet.get_page(uri)
        self.failUnlessFailure(dfr, ValueError)
        return dfr
