"""An alternative Quixote demo.  This version is contained in a single module
and does not use PTL.  The easiest way to run this demo is to use the
simple HTTP server included with Quixote.  For example:

    $ server/simple_server.py --factory quixote.demo.altdemo.create_publisher

The server listens on localhost:8080 by default.  Debug and error output
will be sent to the terminal.

If you have installed durus, you can run the same demo, except with
persistent sessions stored in a durus database, by running:

    $ server/simple_server.py --factory quixote.demo.altdemo.create_durus_publisher

"""

from quixote import get_user, get_session, get_session_manager, get_field
from quixote.directory import Directory
from quixote.html import href, htmltext
from quixote.publish import Publisher
from quixote.session import Session, SessionManager
from quixote.util import dump_request

def format_page(title, content):
    request = htmltext(
        '<div style="font-size: smaller;background:#eee">'
        '<h1>Request:</h1>%s</div>') % dump_request()
    return htmltext(
        '<html><head><title>%(title)s</title>'
        '<style type="text/css">\n'
        'body { border: thick solid green; padding: 2em; }\n'
        'h1 { font-size: larger; }\n'
        'th { background: #aaa; text-align:left; font-size: smaller; }\n'
        'td { background: #ccc; font-size: smaller; }\n'
        '</style>'
        '</head><body>%(content)s%(request)s</body></html>') % locals()

def format_request():
    return format_page('Request', dump_request())

def format_link_list(targets):
    return htmltext('<ul>%s</ul>') % htmltext('').join([
        htmltext('<li>%s</li>') % href(target, target) for target in targets])

class RootDirectory(Directory):

    _q_exports = ['', 'login', 'logout']

    def _q_index(self):
        content = htmltext('')
        if not get_user():
            content += htmltext('<p>%s</p>' % href('login', 'login'))
        else:
            content += htmltext(
                '<p>Hello, %s.</p>') % get_user()
            content += htmltext('<p>%s</p>' % href('logout', 'logout'))
        sessions = get_session_manager().items()
        if sessions:
            sessions.sort()
            content += htmltext('<table><tr>'
                                '<th></th>'
                                '<th>Session</th>'
                                '<th>User</th>'
                                '<th>Number of Requests</th>'
                                '</tr>')
            this_session = get_session()
            for index, (id, session) in enumerate(sessions):
                if session is this_session:
                    formatted_id = htmltext(
                        '<span style="font-weight:bold">%s</span>' % id)
                else:
                    formatted_id = id
                content += htmltext(
                    '<tr><td>%s</td><td>%s</td><td>%s</td><td>%d</td>' % (
                    index,
                    formatted_id,
                    session.user or htmltext("<em>None</em>"),
                    session.num_requests))
            content += htmltext('</table>')
        return format_page("Quixote Session Management Demo", content)

    def login(self):
        content = htmltext('')
        if get_field("name"):
            session = get_session()
            session.set_user(get_field("name")) # This is the important part.
            content += htmltext(
                '<p>Welcome, %s!  Thank you for logging in.</p>') % get_user()
            content += href("..", "go back")
        else:
            content += htmltext(
                '<p>Please enter your name here:</p>\n'
                '<form method="POST" action="login">'
                '<input name="name" />'
                '<input type="submit" />'
                '</form>')
        return format_page("Quixote Session Demo: Login", content)

    def logout(self):
        if get_user():
            content = htmltext('<p>Goodbye, %s.</p>') % get_user()
        else:
            content = htmltext('<p>That would be redundant.</p>')
        content += href("..", "start over")
        get_session_manager().expire_session() # This is the important part.
        return format_page("Quixote Session Demo: Logout", content)


class DemoSession(Session):

    def __init__(self, id):
        Session.__init__(self, id)
        self.num_requests = 0

    def start_request(self):
        """
        This is called from the main object publishing loop whenever
        we start processing a new request.  Obviously, this is a good
        place to track the number of requests made.  (If we were
        interested in the number of *successful* requests made, then
        we could override finish_request(), which is called by
        the publisher at the end of each successful request.)
        """
        Session.start_request(self)
        self.num_requests += 1

    def has_info(self):
        """
        Overriding has_info() is essential but non-obvious.  The
        session manager uses has_info() to know if it should hang on
        to a session object or not: if a session is "dirty", then it
        must be saved.  This prevents saving sessions that don't need
        to be saved, which is especially important as a defensive
        measure against clients that don't handle cookies: without it,
        we might create and store a new session object for every
        request made by such clients.  With has_info(), we create the
        new session object every time, but throw it away unsaved as
        soon as the request is complete.

        (Of course, if you write your session class such that
        has_info() always returns true after a request has been
        processed, you're back to the original problem -- and in fact,
        this class *has* been written that way, because num_requests
        is incremented on every request, which makes has_info() return
        true, which makes SessionManager always store the session
        object.  In a real application, think carefully before putting
        data in a session object that causes has_info() to return
        true.)
        """
        return (self.num_requests > 0) or Session.has_info(self)

    is_dirty = has_info


def create_publisher():
    return Publisher(RootDirectory(),
                     session_manager=SessionManager(session_class=DemoSession),
                     display_exceptions='plain')

try:
    # If durus is installed, define a create_durus_publisher() that
    # uses a durus database to store persistent sessions.
    import os, tempfile
    from durus.persistent import Persistent
    from durus.persistent_dict import PersistentDict
    from durus.file_storage import FileStorage
    from durus.connection import Connection
    connection = None # set in create_durus_publisher()

    class PersistentSession(DemoSession, Persistent):
        pass

    class PersistentSessionManager(SessionManager, Persistent):
        def __init__(self):
            sessions = PersistentDict()
            SessionManager.__init__(self,
                                    session_class=PersistentSession,
                                    session_mapping=sessions)
        def forget_changes(self, session):
            print 'abort changes', get_session()
            connection.abort()

        def commit_changes(self, session):
            print 'commit changes', get_session()
            connection.commit()

    def create_durus_publisher():
        global connection
        filename = os.path.join(tempfile.gettempdir(), 'quixote-demo.durus')
        print 'Opening %r as a Durus database.' % filename
        connection = Connection(FileStorage(filename))
        root = connection.get_root()
        session_manager = root.get('session_manager', None)
        if session_manager is None:
            session_manager = PersistentSessionManager()
            connection.get_root()['session_manager'] = session_manager
            connection.commit()
        return Publisher(RootDirectory(),
                         session_manager=session_manager,
                         display_exceptions='plain')
except ImportError:
    pass # durus not installed.
