

# chatola.py
# a simple chat engine

from twisted.internet.protocol import ClientCreator, Factory
from twisted.protocols.basic import LineReceiver
from twisted.python import failure
from twisted.cred import portal, checkers, credentials, error
from twisted.web import resource
from twisted.internet import reactor, defer

from nevow import renderer
from nevow.tags import *
from nevow import liveevil
from nevow import guard


class IMooCredentials(credentials.ICredentials): pass
class MooCredentials:
    __implements__ = IMooCredentials,

    def __init__(self, server, port, username, password):
        self.server = server
        self.port = port
        self.username = username
        self.password = password


class MooCredChecker:
    credentialInterfaces = (IMooCredentials, )

    def requestAvatarId(self, credentials):
        try:
            port = int(credentials.port)
        except:
            return failure.Failure(error.UnauthorizedLogin("Port should be an integer."))
        
        return ClientCreator(
            reactor, MooProtocol, credentials.username, credentials.password
        ).connectTCP(
            credentials.server,
            port
        ).addCallback(
            lambda proto: proto.finishedConnecting.addErrback(
                lambda reason: failure.Failure(
                    error.UnauthorizedLogin("Login failed: %s" % reason))))


class MooGuard(guard.SessionWrapper):
    def getCredentials(self, request):
        arg = lambda S: request.args.get(S)[0]
        return MooCredentials(
            arg('server'),
            arg('port'),
            arg('user'),
            arg('password')
        )


class MooseRealm:
    """A simple implementor of cred's IRealm.
    The only interface we support web, this gives us the LoggedIn page.
    """
    __implements__ = portal.IRealm

    def requestAvatar(self, avatarId, mind, *interfaces):
        if resource.IResource in interfaces:
            if avatarId is checkers.ANONYMOUS:
                return (
                    resource.IResource,
                    Anonymous(),
                    lambda: None)
            else:
                page = MooseClient(avatarId, mind)
                return resource.IResource, page, lambda: None

        raise NotImplementedError("Can't support that interface.")


class MooProtocol(LineReceiver):
    def __init__(self, username, password):
        self.username = username
        self.password = password
        self.buffer = []
        self.authenticated = False
        self.outputConduit = None
        self.finishedConnecting = defer.Deferred()

    def connectionLost(self, reason):
        if not self.authenticated:
            self.finishedConnecting.errback('Connection was dropped.')

    def lineReceived(self, line):
        if not self.authenticated:
            if line.startswith('The lag is'):
                self.transport.write("co %s %s\r\n" % (self.username, self.password))
            if line.startswith('*** Connected ***'):
                self.authenticated = True
                self.finishedConnecting.callback(self)
            elif line.startswith('Either that player does not exist, or has a different password'):
                self.finishedConnecting.errback('Username or password incorrect')
                self.transport.loseConnection()
        if self.outputConduit is None:
            self.buffer.append(line)
        else:
            self.outputConduit.write(line)

    def outputConnected(self, output):
        self.outputConduit = output
        for line in self.buffer:
            output.write(line)
        self.buffer = []


class Anonymous(renderer.Renderer):
    def render_login(self, context, data):
        return form(action=guard.LOGIN_AVATAR)[
            div[label["Server:"], input(type="text", name="server", value="pictwe.com")],
            div[label["Port:"], input(type="text", name="port", value="4242")],
            div[label["Username:"], input(type="text", name="user", value="fzzzy")],
            div[label["Password:"], input(type="password", name="password", value="")],
            input(type="submit", name="foo", value="bar")
        ]

    document = html[
    head[
        title["log in"]
    ], body[
        render_login
    ]
]


q = lambda it: '"%s"' % it


class MooseClient(renderer.HTMLRenderer):
    templateFile = "Moose.html"
    client = None # The client will be set to an object which we can use to send and recieve data from the web browser.

    def __init__(self, connection, client, *args, **kw):
        renderer.HTMLRenderer.__init__(self, *args, **kw)
        self.client = client
        client.addNotification(lambda output: connection.outputConnected(self))
        self.connection = connection

    def render_glue(self, context, data):
        return liveevil.glue

    def render_content(self, context, data):
        return context.tag[div(style="height: 100%")[xml("&nbsp;")]]

    def render_input(self, context, data):
        return form(
            style="margin: 1em;",
            onsubmit=liveevil.handler(self.onTextInput, "getInput();")
        )[
            input(type="text", id="inputline", style="width: 100%")
        ]

    def onTextInput(self, client, text):
        self.connection.transport.write(text+'\r\n')
        self.client.sendScript("writeContent('<div class=\\'input\\'> -&gt; %s</div>');" % text)

    def render_examine(self, context, data):
        return form(onsubmit=liveevil.handler(self.onExamine, "getValue('examineWhat')"))[
            div(id="examineOutput"),
            input(type="text", id="examineWhat", width="100%")
        ]

    def onExamine(self, client, examineWhat):
        self.doOutOfBand(self.examineResults, 'object_info', OBJ_=examineWhat)

    def examineResults(self, client, OBJ_, VERBS_, PROPS_, OBJ_NAME_):
        html = [
            strong[OBJ_NAME_], "(", em[OBJ_], ")", br,
            strong["Verbs"],
            ul[[
                li[
                    a(onclick=liveevil.handler(
                        self.onVerb, q(v), q(OBJ_[0]), q(OBJ_NAME_[0]))
                    )[v]
                ] for v in VERBS_
            ]],
            strong["Properties"],
            ul[[
                li[p] for p in PROPS_
            ]]
        ]
        print "HTML", html

        self.examineOutput(client, html)

    def examineOutput(self, client, html):
        client.sendScript("document.getElementById('examineOutput').innerHTML = '%s';" % self.flatten(client, html))

    def flatten(self, client, html):
        from nevow import iwoven
        from nevow import context
        ctx = context.WovenContext()
        ctx.remember(None, iwoven.IData)
        ctx.remember(client, liveevil.ILiveEvil)
        html = ''.join(renderer.flatten(renderer.serialize(html, ctx)))
        html = html.replace("'", "\\'").replace('"', '\\"')
        return html

    def onVerb(self, client, CODE_NAME_, OBJ_, OBJ_NAME_):
        self.verbName = CODE_NAME_
        self.objName = OBJ_NAME_
        self.doOutOfBand(self.examineVerb, 'list_code', CODE_NAME_=CODE_NAME_, OBJ_=OBJ_, CODE_TYPE_="VERB", OBJ_NAME_=OBJ_NAME_)

    def examineVerb(self, client, line=None, end=False, **kw):
        if not end:
            if line is None:
                self.curMetadata = kw
                self.curCode = []
            else:
                self.curCode.append(line)
        else:
            print "END", self.curMetadata, self.curCode
            md = self.curMetadata
            html = [
                strong[self.objName, ':', self.verbName],
                '(', em[md['VERB_DOBJ_'][0], ' ', md['VERB_PREP_'][0], ' ', md['VERB_IOBJ_'][0]], ') ', md['VERB_PERMS_']
            ]
            if md['VERB_CHANGEABLE_'][0] == '1':
                html.append( textarea['\\n'.join(self.curCode)] )
            else:
                html.append( [ pre[x] for x in self.curCode ] )

            client.sendScript("document.getElementById('examineOutput').innerHTML = '%s';" % self.flatten(client, html))

    def doOutOfBand(self, handler, command, **args):
        self.currentHandler = handler
        argstr = ' '.join([
            '%s: %s' % (key, value)
            for (key, value) in args.items()
        ])
        oob = '#$#MacMOOSE %s %s\r\n' % (command, argstr)
        print "OOB", oob
        self.connection.transport.write(oob)

    def write(self, text):
        """Write some text to the client browser.
        """
        if text.startswith('_&_'):
            print "IB", text
            result = text[4:]
            if result.startswith('CODE_LINE_: '):
                return self.currentHandler(self.client, result[len('CODE_LINE_: '):])
            elif result.startswith('CODE_END'):
                return self.currentHandler(self.client, end=True)
            pairs = result.split()
            keywords = {}
            while pairs:
                key = pairs.pop(0)[:-1]
                val = pairs.pop(0)
                ## Null can't possibly be in the strings that came across the wire, so we will use it
                ## to mark where the real forward slashes are supposed to go.
                val = val.replace(r'\\/', '\0').split('/')
                val = [x.replace('\0', '/') for x in val if x]
                keywords[key] = val
            self.currentHandler(self.client, **keywords)
        else:
            safe = text.replace("'", "\\'").replace('"', '\\"')
            self.client.sendScript("writeContent('<div class=\\'output\\'>%s</div>');" % safe)

