#!/usr/bin/python

# Authors:
#   Jason Gerard DeRose <jderose@redhat.com>
#
# wehjit: A Python web-widget library
# Copyright (C) 2009  Red Hat
#
# This file is part of `wehjit`.
#
# `wehjit` is free software: you can redistribute it and/or modify it under the
# terms of the GNU General Public License as published by the Free Software
# Foundation, either version 3 of the License, or (at your option) any later
# version.
#
# `wehjit` is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
# A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along with
# `wehjit`.  If not, see <http://www.gnu.org/licenses/>.

"""
An interactive tour of the `wehjit` library.
"""

import sys
import os
from os import path
import optparse


# Check for dependencies:
error = """
%(eclass)s: %(emsg)s
Error: %(script)s requires the Python `%(package)s` package
To install `%(package)s`, see %(url)s
"""

script = path.basename(sys.argv[0])

requirements = [
    ('paste', 'http://pythonpaste.org/'),
    ('genshi', 'http://genshi.edgewall.org/'),
    ('pygments', 'http://pygments.org/'),
    ('assetslib', 'http://jderose.fedorapeople.org/assets/'),
]

fail = False
for (package, url) in requirements:
    try:
        __import__(package)
    except ImportError, e:
        fail = True
        print error % dict(package=package, url=url, script=script,
            eclass=e.__class__.__name__, emsg=str(e)
        )
if fail:
    sys.exit(1)


# On with the show!
import paste.httpserver
from paste.urlmap import URLMap
import paste.gzipper
from assetslib import Assets
from assetslib.wsgi import AssetsApp
import wehjit
from wehjit.util import read_input
from wehjit.compat import json


# Parse some command line options
parser = optparse.OptionParser(
	version='%%prog %s' % wehjit.__version__,
)
parser.add_option('--out',
    help='Directory to render static files (default=static)',
    default='static',
)
parser.add_option('--static',
    help='Render demo to static .xhtml files',
    default=False,
    action='store_true',
)
parser.add_option('--html',
    help='Serialize to HTML instead of XHTML',
    default=False,
    action='store_true',
)
parser.add_option('--dev',
    help='Run in development mode (requires FireBug)',
    default=True,
    action='store_false',
    dest='prod',
)
parser.add_option('--host',
    help='Listen on address HOST (default=127.0.0.1)',
    default='127.0.0.1',
)
parser.add_option('--port',
    help='Listen on PORT (default=8080)',
    default=8080,
    type='int',
)

(options, args) = parser.parse_args()
kw = dict()
if options.html:
    kw['serializer'] = 'html'
if options.static:
    kw['static'] = True

wehjit.init_builtins()
widgets = wehjit.Collection('widget-demo')
widgets.register_builtins()
if options.static:
    if path.lexists(options.out):
        print 'Error: output directory already exists:'
        print '    %r' % options.out
        print 'Not rendering static .xhtml files'
        sys.exit(1)
    app = wehjit.Application('', widgets=widgets, prod=options.prod, **kw)
else:
    app = wehjit.Application('/', widgets=widgets, prod=options.prod, **kw)


# Populate the Application with pages:
example_pages = []
examples = tuple(wehjit.iter_page_examples())
for (name, module) in examples:
    # Create the page
    page = module.create_page(app)
    if page is None:
        continue
    example_pages.append(page)
    if name == 'e0_welcome':
        page.id = ''
    else:
        page.id = name
    page.title = module.title

    if getattr(page, 'menuset', None) is None:
        continue

    # Create the source code page:
    fname = name + '.py'
    src = path.join(path.dirname(path.abspath(module.__file__)), fname)
    assert path.isfile(src)
    code = app.new('PageApp',
        title=fname,
        id=':'.join([name, 'code']),
    )
    code.menuset.add(
        app.new('MenuItem', label='< Back', href=page.url)
    )
    code.actions.add(
        app.new('HighlighterThemeChanger')
    )
    code.view.add(
        app.new('Highlighter',
            lexer='python',
            code=open(src, 'r').read(),
        )
    )

    # Add a link to the code page:
    page.menuset.add(
        app.new('MenuItem',
            label='Source Code',
            href=code.url,
            title='Click to see source code for this example',
        )
    )


# Build the 'Examples' menu on each example page:
for page in example_pages:
    if getattr(page, 'menu', None) is None:
        continue
    page.menu.label = 'Examples'
    for p in example_pages:
        page.menu.add(
            app.new('MenuItem', label=p.title, href=p.url)
        )

# Finalize the Application:
app.render_assets()
app.finalize()


class RPCError(StandardError):
    pass

class GridBackend(object):
    """
    An example JSON-RPC app used by the e4_grid demo.
    """

    def __init__(self):
        shells = ('/bin/bash', '/bin/dash', '/bin/sh')
        self._users = dict(
            (
                'flast%02d' % i,
                dict(
                    uid='flast%02d' % i,
                    givenname='First%02d' % i,
                    sn='Last%02d' % i,
                    loginshell=shells[i % len(shells)],
                )
            )
            for i in xrange(50)
        )

    def __call__(self, environ, start_response):
        start_response(
            '200 OK',
            [('Content-Type', 'application/json; charset=UTF-8')]
        )
        data = json.loads(read_input(environ))
        print 'JSON-RPC request:\n%r\n' % (data,)
        method = data.get('method', '').strip('_')
        params = data.get('params', [])
        _id = data.get('id')
        controller = getattr(self, method, None)
        if callable(controller):
            if len(params) == 0:
                (args, options) = ([], {})
            elif len(params) == 1:
                (args, options) = (params[0], {})
            else:
                (args, options) = params[:2]
            error = None
            result = None
            try:
                result = controller(args, options)
            except RPCError as e:
                error=str(e)
            response = dict(
                result=result,
                error=error,
                id=_id,
            )
        else:
            response = dict(
                result=None,
                error='no such method: %r' % method,
                id=_id,
            )
        return json.dumps(response)

    def search(self, args, options):
        s = (args[0].lower() if args else '')
        if s:
            entries = list(filter(
                lambda u: any(s in a.lower() for a in u.itervalues()),
                self._users.itervalues()
            ))
        else:
            entries = list(self._users.itervalues())
        msg = ['%d users matched', '%d user matched'][len(entries) == 1]
        return dict(
            result=entries,
            count=len(entries),
            summary=msg % len(entries),
            truncated=False
        )

    def create(self, args, options):
        uid = options['uid']
        if uid in self._users:
            raise RPCError('User "%s" already exists' % uid)
        entry = dict(
            (k, options.get(k)) for k in ['uid', 'sn', 'givenname', 'loginshell']
        )
        self._users[uid] = entry
        return dict(
            result=entry,
            value=uid,
            summary='Created user "%s"' % uid,
        )

    def retrieve(self, args, options):
        uid = args[0]
        if uid not in self._users:
            raise RPCError('No such user "%s"' % uid)
        return dict(
            result=self._users[uid],
            value=uid,
        )

    def update(self, args, options):
        uid = args[0]
        if uid not in self._users:
            raise RPCError('No such user "%s"' % uid)
        self._users[uid].update(options)
        return dict(
            result=self._users[uid],
            value=uid,
            summary='Updated user "%s"' % uid,
        )

    def delete(self, args, options):
        uid = args[0]
        if uid not in self._users:
            raise RPCError('No such user "%s"' % uid)
        del self._users[uid]
        return dict(
            result=True,
            value=uid,
            summary='Deleted user "%s"' % uid,
        )





if options.static:
    app.render_static(options.out)
else:
    assetsapp = AssetsApp(app.assets)

    urlmap = URLMap()
    urlmap[app.url] = app
    urlmap[assetsapp.url] = assetsapp
    urlmap['/jsonrpc'] = GridBackend()

    # Start the paste WSGI server:
    paste.httpserver.serve(
        paste.gzipper.middleware(urlmap),
        host=options.host,
        port=options.port,
    )
