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

from zope.interface import implements

from twisted.python import components

from nevow import tags
from nevow import inevow
from nevow import context
from nevow import util

import formless
from formless import webform
from formless import iformless
from formless import configurable

from nevow.test import test_flatstan

class Base(test_flatstan.Base):
    implements(iformless.IConfigurableFactory)

    synchronousLocateConfigurable = False

    def locateConfigurable(self, *args, **kw):
        r = iformless.IConfigurable(self.conf)
        if not self.synchronousLocateConfigurable:
            r = util.succeed(r)
        return r

    def setupContext(self, *args, **kw):
        ctx = test_flatstan.Base.setupContext(self, *args, **kw)
        return context.PageContext(tag=tags.html(), parent=ctx)

    def render(self, tag, setupContext=lambda c:c):
        return test_flatstan.Base.render(
            self, tag, setupContext=setupContext,
            wantDeferred=True)

    def renderForms(self, configurable, ctx=None, *args, **kwargs):
        self.conf = configurable
        if ctx is None:
            ctx = self.setupContext(False)
        ctx.remember(self, iformless.IConfigurableFactory)
        return self.render(
            webform.renderForms(*args, **kwargs),
            setupContext=lambda c: ctx)

    def postForm(self, ctx, obj, bindingName, args):
        self.conf = obj
        ctx.remember(self, iformless.IConfigurableFactory)

        def trapValidate(f):
            f.trap(formless.ValidateError)
            e = f.value
            errors = ctx.locate(iformless.IFormErrors)
            ## Set the overall error for this form
            errors.setError(bindingName, e.formErrorMessage)
            errors.updateErrors(bindingName, e.errors)
            ctx.locate(iformless.IFormDefaults).getAllDefaults(bindingName).update(e.partialForm)

        return util.maybeDeferred(self.locateConfigurable,obj).addCallback(lambda x: x.postForm(
            ctx, bindingName, args
            )).addErrback(trapValidate)


class Complete(Base):
    def test_configureProperty(self):
        class IStupid(formless.TypedInterface):
            foo = formless.String()

        class StupidThing(configurable.Configurable):
            implements(IStupid)

            def __init__(self):
                configurable.Configurable.__init__(self, None)
                self.foo = 'foo'

        dumb = StupidThing()

        def doasserts(val):
            self.assertSubstring('freeform_post!!foo', val)
            self.assertSubstring('foo', val)
            self.assertSubstring('type="text"', val)
            self.assertSubstring('<input name="change" type="submit"', val)
        return self.renderForms(dumb).addCallback(doasserts)


    def test_configureMethod(self):
        class IDumb(formless.TypedInterface):
            def foo(bar=formless.String()):
                return formless.String()
            foo = formless.autocallable(foo)

        class DumbThing(configurable.Configurable):
            implements(IDumb)

            def foo(self, bar):
                return "baz"

        stupid = DumbThing(1)

        def doasserts(val):
            self.assertSubstring('freeform_post!!foo', val)
            self.assertSubstring('foo', val)
            self.assertSubstring('bar', val)
        return self.renderForms(stupid).addCallback(doasserts)


class BuildingBlocksTest(Base):
    def test_1_renderTyped(self):
        binding = formless.Property('hello', formless.String(
            label="Hello",
            description="Hello, world."))

        ## Look up a renderer specific to the type of our binding, typedValue;
        renderer = iformless.ITypedRenderer(
            binding.typedValue, None)

        ## But render the binding itself with this renderer
        ## The binding has the ".name" attribute we need
        def later(val):
            self.assertSubstring('hello', val)
            self.assertSubstring('Hello', val)
            self.assertSubstring('Hello, world.', val)
            self.failIfSubstring('</form>', val)
            self.failIfSubstring('<input type="submit"', val)
        return self.render(tags.invisible(data=binding, render=renderer)).addCallback(later)

    test_1_renderTyped.todo = "Render binding"

    def test_2_renderPropertyBinding(self):
        binding = formless.Property('goodbye', formless.String(
            label="Goodbye",
            description="Goodbye cruel world"))

        # Look up an IBindingRenderer, which will render the form and the typed
        renderer = iformless.IBindingRenderer(binding)
        def later(val):
            self.assertSubstring('<form ', val)
            self.assertSubstring('<input name="change" type="submit"', val)
            self.assertSubstring('name="goodbye"', val)
            self.assertSubstring('Goodbye', val)
            self.assertSubstring('Goodbye cruel world', val)
        return self.render(tags.invisible(data=binding, render=renderer)).addCallback(later)

    def test_3_renderMethodBinding(self):
        binding = formless.MethodBinding('doit', formless.Method(
            returnValue=None,
            arguments=[formless.Argument('foo', formless.String(label="Foo"))],
            label="Do It",
            description="Do it to 'em all"))

        renderer = iformless.IBindingRenderer(binding)

        def later(val):
            self.assertSubstring('<form ', val)
            self.assertSubstring('Do It', val)
            self.assertSubstring("Do it to 'em all", val)
            self.assertSubstring("Foo", val)
            self.assertSubstring('name="foo"', val)
        return self.render(tags.invisible(data=binding, render=renderer)).addCallback(later)


class TestDefaults(Base):
    def test_1_renderWithDefaultValues(self):
        binding = formless.MethodBinding('haveFun', formless.Method(
            returnValue=None,
            arguments=[formless.Argument('funValue', formless.Integer(label="Fun Value", default=0))]
        ))

        def setupCtx(ctx):
            ctx.locate(iformless.IFormDefaults).setDefault('funValue', 15)
            return ctx

        renderer = iformless.IBindingRenderer(binding)
        def later(val):
            self.failIfSubstring('0', val)
            self.assertSubstring('15', val)
        return self.render(tags.invisible(data=binding, render=renderer), setupContext=setupCtx).addCallback(
            later)

    def test_2_renderWithObjectPropertyValues(self):
        class IDefaultProperty(formless.TypedInterface):
            default = formless.Integer(default=2)

        class Foo(configurable.Configurable):
            implements(IDefaultProperty)
            default = 54

        def later(val):
            self.failIfSubstring('2', val)
            self.assertSubstring('54', val)
        return self.renderForms(Foo(None)).addCallback(later)

    def test_3_renderWithAdapteeAttributeValues(self):
        class IDefaultProperty(formless.TypedInterface):
            default = formless.Integer(default=2)

        class Adaptee(object):
            default = 69

        class Bar(configurable.Configurable):
            implements(IDefaultProperty)

        def later(val):
            self.failIfSubstring('2', val)
            self.assertSubstring('69', val)
        return self.renderForms(Bar(Adaptee())).addCallback(later)

    def test_4_testBindingDefaults(self):
        class IBindingDefaults(formless.TypedInterface):
            def aMethod(foo=formless.String(default="The foo")):
                pass
            aMethod = formless.autocallable(aMethod)

            aProperty = formless.String(default="The property")

        class Implements(configurable.Configurable):
            implements(IBindingDefaults)

        def later(val):
            self.assertSubstring("The foo", val)
            self.assertSubstring("The property", val)
        return self.renderForms(Implements(None)).addCallback(later)

    def test_5_testDynamicDefaults(self):
        class IDynamicDefaults(formless.TypedInterface):
            def aMethod(foo=formless.String(default="NOTFOO")):
                pass
            def bMethod(foo=formless.String(default="NOTBAR")):
                pass
            aMethod = formless.autocallable(aMethod)
            bMethod = formless.autocallable(bMethod)

        class Implements(configurable.Configurable):
            implements(IDynamicDefaults)

        def later(val):
            self.assertSubstring("YESFOO", val)
            self.assertSubstring("YESBAR", val)
            self.assertNotSubstring("NOTFOO", val)
            self.assertNotSubstring("NOTBAR", val)

        return self.renderForms(Implements(None), bindingDefaults={
                'aMethod': {'foo': 'YESFOO'},
                'bMethod': {'foo': 'YESBAR'}}).addCallback(later)


class TestNonConfigurableSubclass(Base):
    def test_1_testSimple(self):
        class ISimpleTypedInterface(formless.TypedInterface):
            anInt = formless.Integer()
            def aMethod(aString = formless.String()):
                return None
            aMethod = formless.autocallable(aMethod)

        class ANonConfigurable(object): # Not subclassing Configurable
            implements(ISimpleTypedInterface) # But implements a TypedInterface

        def later(val):
            self.assertSubstring('anInt', val)
            self.assertSubstring('aMethod', val)

        return self.renderForms(ANonConfigurable()).addCallback(later)



class TestPostAForm(Base):
    def test_1_failAndSucceed(self):
        class IAPasswordMethod(formless.TypedInterface):
            def password(pword = formless.Password(), integer=formless.Integer()):
                pass
            password = formless.autocallable(password)

        class APasswordImplementation(object):
            implements(IAPasswordMethod)
            matched = False
            def password(self, pword, integer):
                self.matched = True
                return "password matched"

        theObj = APasswordImplementation()
        ctx = self.setupContext()

        D = self.postForm(ctx, theObj, "password", {"pword": ["these passwords"], "pword____2": ["don't match"], 'integer': ['Not integer']})
        def after(result):
            self.assertEquals(theObj.matched, False)
            def later(val):
                self.assertSubstring("Passwords do not match. Please reenter.", val)
                self.assertSubstring('value="Not integer"', val)
            return self.renderForms(theObj, ctx).addCallback(later)
        return D.addCallback(after)

    def test_2_propertyFailed(self):
        class IAProperty(formless.TypedInterface):
            prop = formless.Integer()

        class Impl(object):
            implements(IAProperty)
            prop = 5

        theObj = Impl()
        ctx = self.setupContext()
        D = self.postForm(ctx, theObj, 'prop', {'prop': ['bad']})
        def after(result):
            def later(val):
                self.assertSubstring('value="bad"', val)
            return self.renderForms(theObj, ctx).addCallback(later)
        return D.addCallback(after)


class TestRenderPropertyGroup(Base):
    def test_1_propertyGroup(self):
        class Outer(formless.TypedInterface):
            class Inner(formless.TypedInterface):
                one = formless.Integer()
                two = formless.Integer()

                def buckleMyShoe():
                    pass
                buckleMyShoe = formless.autocallable(buckleMyShoe)

                def buriedAlive():
                    pass
                buriedAlive = formless.autocallable(buriedAlive)

        class Implementation(object):
            implements(Outer)
            one = 1
            two = 2
            buckled = False
            buried = False
            def buckleMyShoe(self):
                self.buckled = True
            def buriedAlive(self):
                self.buried = True

        impl = Implementation()
        ctx = self.setupContext()

        def later(val):
            D = self.postForm(ctx, impl, "Inner", {'one': ['Not an integer'], 'two': ['22']})

            def after(result):

                self.assertEquals(impl.one, 1)
                self.assertEquals(impl.two, 2)
                self.assertEquals(impl.buckled, False)
                self.assertEquals(impl.buried, False)

                def evenlater(moreval):
                    self.assertSubstring("is not an integer", moreval)
                    # TODO: Get default values for property groups displaying properly.
                    #self.assertSubstring('value="Not an integer"', moreval)
                    DD = self.postForm(ctx, impl, "Inner", {'one': ['11'], 'two': ['22']})
                    def afterafter(ign):
                        self.assertEquals(impl.one, 11)
                        self.assertEquals(impl.two, 22)
                        self.assertEquals(impl.buckled, True)
                        self.assertEquals(impl.buried, True)
                    return DD.addCallback(afterafter)
                return self.renderForms(impl, ctx).addCallback(evenlater)
            return D.addCallback(after)
        return self.renderForms(impl).addCallback(later)

class TestRenderMethod(Base):

    def testDefault(self):

        class IFoo(formless.TypedInterface):
            def foo(abc=formless.String()):
                pass
            foo = formless.autocallable(foo)

        class Impl:
            implements(IFoo)

        def later(val):
            self.assertSubstring('value="Foo"', val)
            self.assertSubstring('name="abc"', val)
        return self.renderForms(Impl(), bindingNames=['foo']).addCallback(later)


    def testActionLabel(self):

        class IFoo(formless.TypedInterface):
            def foo(abc=formless.String()):
                pass
            foo = formless.autocallable(foo, action='FooFooFoo')

        class Impl:
            implements(IFoo)

        def later(val):
            self.assertSubstring('value="FooFooFoo"', val)
            self.assertSubstring('name="abc"', val)
        return self.renderForms(Impl(), bindingNames=['foo']).addCallback(later)

    def testOneSigMultiCallables(self):

        class IFoo(formless.TypedInterface):
            def sig(abc=formless.String()):
                pass
            foo = formless.autocallable(sig)
            bar = formless.autocallable(sig, action='FooFooFOo')

        class Impl:
            implements(IFoo)

        def later1(val):
            self.assertSubstring('value="Foo"', val)
            def later2(val):
                self.assertSubstring('value="FooFooFoo"', val)
            return self.renderForms(Impl(), bindingNames=['bar']).addCallback(later2)
        return self.renderForms(Impl(), bindingNames=['foo']).addCallback(later1)
    testOneSigMultiCallables.todo = 'autocallable should not set attributes directly on the callable'


class TestCustomTyped(Base):
    def test_typedCoerceWithBinding(self):
        class MyTyped(formless.Typed):
            passed = False
            wasBoundTo = None
            def coerce(self, val, boundTo):
                self.passed = True
                self.wasBoundTo = boundTo
                return "passed"

        typedinst = MyTyped()

        class IMyInterface(formless.TypedInterface):
            def theFunc(test=typedinst):
                pass
            theFunc = formless.autocallable(theFunc)

        class Implementation(object):
            implements(IMyInterface)
            called = False
            def theFunc(self, test):
                self.called = True

        inst = Implementation()
        ctx = self.setupContext()
        D = self.postForm(ctx, inst, 'theFunc', {'test': ['a test value']})
        def after(result):
            self.assertEquals(typedinst.passed, True)
            self.assertEquals(typedinst.wasBoundTo, inst)
            self.assertEquals(inst.called, True)
        return D.addCallback(after)


class TestUneditableProperties(Base):
    def test_uneditable(self):
        class Uneditable(formless.TypedInterface):
            aProp = formless.String(description="the description", immutable=True)

        class Impl(object):
            implements(Uneditable)

            aProp = property(lambda self: "HELLO")

        inst = Impl()

        def later(val):
            self.assertSubstring('HELLO', val)
            self.failIfSubstring('type="text"', val)
        return self.renderForms(inst).addCallback(later)


class TestAfterValidation(Base):
    """Test post-validation rendering"""

    def test_property(self):
        """Test that, when validation fails, the value just entered is redisplayed"""

        class IThing(formless.TypedInterface):
            foo = formless.Integer()

        class Thing:
            implements(IThing)
            foo = 1

        inst = Thing()
        ctx = self.setupContext()
        D = self.postForm(ctx, inst, 'foo', {'foo': ['abc']})
        def after(result):
            def later(val):
                def morelater(noval):
                    self.assertSubstring('value="abc"', val)
                return self.renderForms(inst, ctx).addCallback(morelater)
            return self.renderForms(inst)
        return D.addCallback(after)


class TestHandAndStatus(Base):
    """Test that the method result is available as the hand, and that
    a reasonable status message string is available"""
    def test_hand(self):
        """Test that the hand and status message are available before redirecting the post
        """
        returnResult = object()
        class IMethod(formless.TypedInterface):
            def foo(): pass
            foo = formless.autocallable(foo)

        class Method(object):
            implements(IMethod)
            def foo(self):
                return returnResult

        inst = Method()
        ctx = self.setupContext()
        D = self.postForm(ctx, inst, 'foo', {})
        def after(result):
            self.assertEquals(ctx.locate(inevow.IHand), returnResult)
            self.assertEquals(ctx.locate(inevow.IStatusMessage), "'foo' success.")
        return D.addCallback(after)

    def test_handFactory(self):
        """Test that the hand and status message are available after redirecting the post
        """
        returnResult = object()
        status = 'horray'
        def setupRequest(r):
            r.args['_nevow_carryover_'] = ['abc']
            from nevow import rend
            c = components.Componentized()
            c.setComponent(inevow.IHand, returnResult)
            c.setComponent(inevow.IStatusMessage, status)
            rend._CARRYOVER['abc'] = c
            return r
        ctx = self.setupContext(setupRequest=setupRequest)

        self.assertEquals(ctx.locate(inevow.IHand), returnResult)
        self.assertEquals(ctx.locate(inevow.IStatusMessage), status)


class TestCharsetDetectionSupport(Base):

    def test_property(self):

        class ITest(formless.TypedInterface):
            foo = formless.String()

        class Impl:
            implements(ITest)

        impl = Impl()
        ctx = self.setupContext()
        def later(val):
            self.assertIn('<input name="_charset_" type="hidden" />', val)
            self.assertIn('accept-charset="utf-8"', val)
        return self.renderForms(impl, ctx).addCallback(later)


    def test_group(self):

        class ITest(formless.TypedInterface):
            class Group(formless.TypedInterface):
                foo = formless.String()

        class Impl:
            implements(ITest)

        impl = Impl()
        ctx = self.setupContext()
        def later(val):
            self.assertIn('<input name="_charset_" type="hidden" />', val)
            self.assertIn('accept-charset="utf-8"', val)
        return self.renderForms(impl, ctx).addCallback(later)


    def test_method(self):

        class ITest(formless.TypedInterface):
            def foo(foo = formless.String()):
                pass
            foo = formless.autocallable(foo)

        class Impl:
            implements(ITest)

        impl = Impl()
        ctx = self.setupContext()
        def later(val):
            self.assertIn('<input name="_charset_" type="hidden" />', val)
            self.assertIn('accept-charset="utf-8"', val)
        return self.renderForms(impl, ctx).addCallback(later)


class TestUnicode(Base):

    def test_property(self):

        class IThing(formless.TypedInterface):
            aString = formless.String(unicode=True)

        class Impl(object):
            implements(IThing)
            aString = None

        inst = Impl()
        ctx = self.setupContext()
        D = self.postForm(ctx, inst, 'aString', {'aString':['\xc2\xa3']})
        return D.addCallback(lambda result: self.assertEquals(inst.aString, u'\xa3'))

class TestChoice(Base):
    """Test various behaviors of submitting values to a Choice Typed.
    """

    def test_reject_missing(self):
        # Ensure that if a Choice is not specified, the form is not submitted.

        self.called = []

        class IFormyThing(formless.TypedInterface):
            def choiceyFunc(arg = formless.Choice(["one", "two"], required=True)):
                pass
            choiceyFunc = formless.autocallable(choiceyFunc)

        class Impl(object):
            implements(IFormyThing)

            def choiceyFunc(innerSelf, arg):
                self.called.append(arg)

        inst = Impl()
        ctx = self.setupContext()
        D = self.postForm(ctx, inst, 'choiceyFunc', {})
        return D.addCallback(lambda result: self.assertEquals(self.called, []))


class mg(Base):

    def test_leakyForms(self):

        class ITest(formless.TypedInterface):
            """Test that a property value on one form does not 'leak' into
            a property of the same name on another form.
            """
            foo = formless.String()

            def meth(foo = formless.String()):
                pass
            meth = formless.autocallable(meth)

        class Impl:
            implements(ITest)
            foo = 'fooFOOfoo'

        impl = Impl()
        ctx = self.setupContext()
        def later(val):
            self.assertEquals(val.count('fooFOOfoo'), 1)
        return self.renderForms(impl, ctx)


# What the *hell* is this?!?

#DeferredTestCases = type(Base)(
#    'DeferredTestCases',
#    tuple([v for v in locals().values()
#     if isinstance(v, type(Base)) and issubclass(v, Base)]),
#    {'synchronousLocateConfigurable': True})

