

# chatola.py
# a simple chat engine


from twisted.cred import portal, checkers
from nevow import renderer
from nevow import tags
from nevow import liveevil
from nevow import inevow


def callJS(func, *args):
    ## Move this into liveevil.
    args = ["'%s'" % a.replace("'", "\\'").replace('"', '\\"') for a in args]
    return "%s(%s);" % (func, ','.join(args))


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

    def __init__(self):
        self.clients = []

    def requestAvatar(self, avatarId, client, *interfaces):
        for iface in interfaces:
            if iface is inevow.IResource:
                if avatarId is checkers.ANONYMOUS:
                    client.userId = "Anonymous %s" % (len(self.clients) + 1, )
                    self.broadcast(client, "%s has joined." % client.userId)
                    # The mind when using nevow is a LiveEvil instance
                    # which represents the client side browser. We can
                    # use it to broadcast messages to viewing browsers.
                    self.clients.append(client)
                    # When constructing the page the user will see, we pass the realm
                    # as the data to the page. When the user enters some text to broadcast,
                    # this page can then notify the realm which broadcasts the message to
                    # all other clients.
                    page = Chatola(self, client)
                    ## We are returning an IResource, resc, and when the user logs out we
                    ## wish to remove them from our client list.
                    return inevow.IResource, page, lambda: (
                        self.broadcast(client, "%s has left." % client.userId), self.clients.remove(client))

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

    def broadcast(self, user, message):
        """Broadcast a message to every client which is connected to this realm.
        """
        script = callJS("writeContent", "<div>%s: %s</div>" % (user.userId, message))
        for C in self.clients:
            C.sendScript(script)
            if C is user:
                C.sendScript("focusInput();")


class Chatola(renderer.HTMLRenderer):
    templateFile = "Chatola.html"
    def __init__(self, realm, client):
        self.realm = realm
        self.client = client
        renderer.Renderer.__init__(self)

    def render_content(self, context, data):
        """When a user enters the chat we'll render a list of the people that are currently in it
        """
        return context.tag[
            "Userlist: ",
            [(user.userId, " ") for user in self.realm.clients]
        ]

    def render_input(self, context, data):
        """Render an input form; a text box and a submit button. On the form, we add an onsubmit
        event handler; we pass a python callable wrapped by the liveevil.handler function which will
        be called on the server when the client side onsubmit handler is called. liveevil.handler
        automatically returns false in  the browser handler so the normal page submit does not
        occur. Instead, the second argument to handler, a string, is evaluated as javascript in the
        browser context and the result is sent to the server-side onSubmit method.
        """
        return tags.form(
            onsubmit=liveevil.handler(self.onSubmit, "getValue('inputline');")
        )[
            tags.input(type="text", id="inputline"), 
            tags.input(type="submit", value="say")
        ]

    def onSubmit(self, client, text):
        """When the onSubmit method is called on the server in response to the client-side
        onsubmit javascript handler, pass the text which was in the user's input box to the
        realm's broadcast method. Broadcast will format the message with the user's nick
        and send javascript to every connected client (including this user) which will append
        this message to the "content" div so all users can see the new chat text.
        """
        self.realm.broadcast(client, text)

    def render_nick(self, context, data):
        return tags.form(
            onsubmit=liveevil.handler(self.onNickChange, "getValue('nick');")
        )[
            tags.input(type="text", id="nick", value=self.client.userId),
            tags.input(type="submit", value="change nick"),
            tags.span(id="nickChangeNotification")
        ]

    def onNickChange(self, client, newval):
        self.realm.broadcast(client, "%s is now known as %s." % (client.userId, newval))
        client.userId = newval
        client.sendScript(callJS('nickChanged', newval))

    def render_glue(self, context, data):
        """Insert the liveevil glue necessary for the input and output conduits to be present,
        and thus serverToClient and clientToServer events to be passed.
        """
        return liveevil.glue

