# Copyright (c) 2004 Divmod.
# See LICENSE for details.


from twisted.internet import defer

from nevow import context, inevow
from nevow import testutil
from nevow.flat import twist
from nevow.util import Deferred

from nevow import rend, loaders, tags



def deferit(data):
    return data.d


def deferdot(data):
    return data.d2


class RenderHelper(testutil.TestCase):
    def renderIt(self):
        req = testutil.FakeRequest()
        self.r.renderHTTP(context.PageContext(tag=self.r, parent=context.RequestContext(tag=req)))
        return req


class LaterRenderTest(RenderHelper):
    def setUp(self):
        self.d = Deferred()
        self.d2 = Deferred()
        self.r = rend.Page(
            docFactory=loaders.stan(
                tags.html(data=self)[
                    'Hello ', tags.invisible[tags.invisible[tags.invisible[tags.invisible[deferit]]]],
                    deferdot,
                    ]
                )
            )

    def test_deferredSupport(self):
        req = self.renderIt()
        self.assertEqual(req.v, '<html>Hello ')
        self.d.callback("world")
        self.assertEqual(req.v, '<html>Hello world')
        self.d2.callback(".")
        self.assertEqual(req.v, '<html>Hello world.</html>')


    def test_deferredSupport2(self):
        req = self.renderIt()
        self.assertEqual(req.v, '<html>Hello ')
        self.d2.callback(".")
        self.assertEqual(req.v, '<html>Hello ')
        self.d.callback("world")
        self.assertEqual(req.v, '<html>Hello world.</html>')

    def test_deferredSupport3(self):
        self.r.buffered = True
        req = self.renderIt()
        self.assertEqual(req.v, '')
        self.d.callback("world")
        self.assertEqual(req.v, '')
        self.d2.callback(".")
        self.assertEqual(req.v, '<html>Hello world.</html>')

    def test_renderNestedDeferredCallables(self):
        """
        Test flattening of a renderer which returns a Deferred which fires with
        a renderer which returns a Deferred.
        """
        def render_inner(ctx, data):
            return defer.succeed('')

        def render_outer(ctx, data):
            return defer.succeed(render_inner)

        ctx = context.WovenContext()
        ctx.remember(None, inevow.IData)

        out = []
        d = twist.deferflatten(render_outer, ctx, out.append)
        def flattened(ign):
            self.assertEqual(out, [''])
        d.addCallback(flattened)
        return d


    def test_renderNestedDeferredErrorHandling(self):
        """
        Test that flattening a renderer which returns a Deferred which fires
        with a renderer which raises an exception causes the outermost Deferred
        to errback.
        """
        class NestedException(Exception):
            pass

        def render_inner(ctx, data):
            raise NestedException()

        def render_outer(ctx, data):
            return defer.succeed(render_inner)

        ctx = context.WovenContext()
        ctx.remember(None, inevow.IData)

        out = []
        d = twist.deferflatten(render_outer, ctx, out.append)
        return self.assertFailure(d, NestedException)


class LaterDataTest(RenderHelper):
    def data_later(self, context, data):
        return self.d

    def data_later2(self, context, data):
        return self.d2

    def setUp(self):
        self.d = Deferred()
        self.d2 = Deferred()
        self.r = rend.Page(docFactory=loaders.stan(
            tags.html(data=self.data_later)[
                'Hello ', str, ' and '
                'goodbye ',str,
                tags.span(data=self.data_later2, render=str)]))

    def test_deferredSupport(self):
        req = self.renderIt()
        self.assertEqual(req.v, '')
        self.d.callback("world")
        self.assertEqual(req.v, '<html>Hello world and goodbye world')
        self.d2.callback(".")
        self.assertEqual(req.v, '<html>Hello world and goodbye world.</html>')


class SuperLaterDataTest(RenderHelper):
    def test_reusedDeferredSupport(self):
        """
        Two occurrences of a particular slot are each replaced with the
        result of the Deferred which is used to fill that slot.
        """
        doc = tags.html[
            tags.slot('foo'), tags.slot('foo')]
        doc.fillSlots('foo', defer.succeed(tags.span['Foo!!!']))
        self.r = rend.Page(docFactory=loaders.stan(doc))
        req = self.renderIt()
        self.assertEqual(req.v, '<html><span>Foo!!!</span><span>Foo!!!</span></html>')


    def test_rendererCalledOnce(self):
        """
        Make sure that if a Deferred fires with a render function that the
        render function is called only once.
        """
        calls = []
        def recorder(ctx, data):
            calls.append(None)
            return str(len(calls))
        doc = tags.html[tags.directive('renderer')]
        class RendererPage(rend.Page):
            docFactory = loaders.stan(doc)
            def render_renderer(self, ctx, data):
                return defer.succeed(recorder)
        self.r = RendererPage()
        req = self.renderIt()
        self.assertEqual(req.v, '<html>1</html>')
