#!/usr/bin/python
##
## license:BSD-3-Clause
## copyright-holders:Vas Crabb

from . import dbaccess
from . import htmltmpl

import cgi
import inspect
import json
import mimetypes
import os.path
import re
import sys
import wsgiref.simple_server
import wsgiref.util

if sys.version_info >= (3, ):
    import urllib.parse as urlparse
else:
    import urlparse


class HandlerBase(object):
    STATUS_MESSAGE = {
            400: 'Bad Request',
            401: 'Unauthorized',
            403: 'Forbidden',
            404: 'Not Found',
            405: 'Method Not Allowed',
            500: 'Internal Server Error',
            501: 'Not Implemented',
            502: 'Bad Gateway',
            503: 'Service Unavailable',
            504: 'Gateway Timeout',
            505: 'HTTP Version Not Supported' }

    def __init__(self, app, application_uri, environ, start_response, **kwargs):
        super(HandlerBase, self).__init__(**kwargs)
        self.app = app
        self.js_escape = app.js_escape
        self.application_uri = application_uri
        self.environ = environ
        self.start_response = start_response

    def error_page(self, code):
        yield htmltmpl.ERROR_PAGE.substitute(code=cgi.escape('%d' % code), message=cgi.escape(self.STATUS_MESSAGE[code])).encode('utf-8')


class ErrorPageHandler(HandlerBase):
    def __init__(self, code, app, application_uri, environ, start_response, **kwargs):
        super(ErrorPageHandler, self).__init__(app=app, application_uri=application_uri, environ=environ, start_response=start_response, **kwargs)
        self.code = code
        self.start_response('%d %s' % (self.code, self.STATUS_MESSAGE[code]), [('Content-type', 'text/html; charset=utf-8'), ('Cache-Control', 'public, max-age=3600')])

    def __iter__(self):
        return self.error_page(self.code)


class AssetHandler(HandlerBase):
    def __init__(self, directory, app, application_uri, environ, start_response, **kwargs):
        super(AssetHandler, self).__init__(app=app, application_uri=application_uri, environ=environ, start_response=start_response, **kwargs)
        self.directory = directory
        self.asset = wsgiref.util.shift_path_info(environ)

    def __iter__(self):
        if not self.asset:
            self.start_response('403 %s' % (self.STATUS_MESSAGE[403], ), [('Content-type', 'text/html; charset=utf-8'), ('Cache-Control', 'public, max-age=3600')])
            return self.error_page(403)
        elif self.environ['PATH_INFO']:
            self.start_response('404 %s' % (self.STATUS_MESSAGE[404], ), [('Content-type', 'text/html; charset=utf-8'), ('Cache-Control', 'public, max-age=3600')])
            return self.error_page(404)
        else:
            path = os.path.join(self.directory, self.asset)
            if not os.path.isfile(path):
                self.start_response('404 %s' % (self.STATUS_MESSAGE[404], ), [('Content-type', 'text/html; charset=utf-8'), ('Cache-Control', 'public, max-age=3600')])
                return self.error_page(404)
            elif self.environ['REQUEST_METHOD'] != 'GET':
                self.start_response('405 %s' % (self.STATUS_MESSAGE[405], ), [('Content-type', 'text/html; charset=utf-8'), ('Accept', 'GET, HEAD, OPTIONS'), ('Cache-Control', 'public, max-age=3600')])
                return self.error_page(405)
            else:
                try:
                    f = open(path, 'rb')
                    type, encoding = mimetypes.guess_type(path)
                    self.start_response('200 OK', [('Content-type', type or 'application/octet-stream'), ('Cache-Control', 'public, max-age=3600')])
                    return wsgiref.util.FileWrapper(f)
                except:
                    self.start_response('500 %s' % (self.STATUS_MESSAGE[500], ), [('Content-type', 'text/html; charset=utf-8'), ('Cache-Control', 'public, max-age=3600')])
                    return self.error_page(500)


class QueryPageHandler(HandlerBase):
    def __init__(self, app, application_uri, environ, start_response, **kwargs):
        super(QueryPageHandler, self).__init__(app=app, application_uri=application_uri, environ=environ, start_response=start_response, **kwargs)
        self.dbcurs = app.dbconn.cursor()

    def machine_href(self, shortname):
        return cgi.escape(urlparse.urljoin(self.application_uri, 'machine/%s' % (shortname, )), True)

    def sourcefile_href(self, sourcefile):
        return cgi.escape(urlparse.urljoin(self.application_uri, 'sourcefile/%s' % (sourcefile, )), True)


class MachineRpcHandlerBase(QueryPageHandler):
    def __init__(self, app, application_uri, environ, start_response, **kwargs):
        super(MachineRpcHandlerBase, self).__init__(app=app, application_uri=application_uri, environ=environ, start_response=start_response, **kwargs)
        self.shortname = wsgiref.util.shift_path_info(environ)

    def __iter__(self):
        if not self.shortname:
            self.start_response('403 %s' % (self.STATUS_MESSAGE[403], ), [('Content-type', 'text/html; charset=utf-8'), ('Cache-Control', 'public, max-age=3600')])
            return self.error_page(403)
        elif self.environ['PATH_INFO']:
            self.start_response('404 %s' % (self.STATUS_MESSAGE[404], ), [('Content-type', 'text/html; charset=utf-8'), ('Cache-Control', 'public, max-age=3600')])
            return self.error_page(404)
        else:
            machine = self.dbcurs.get_machine_id(self.shortname)
            if machine is None:
                self.start_response('404 %s' % (self.STATUS_MESSAGE[404], ), [('Content-type', 'text/html; charset=utf-8'), ('Cache-Control', 'public, max-age=3600')])
                return self.error_page(404)
            elif self.environ['REQUEST_METHOD'] != 'GET':
                self.start_response('405 %s' % (self.STATUS_MESSAGE[405], ), [('Content-type', 'text/html; charset=utf-8'), ('Accept', 'GET, HEAD, OPTIONS'), ('Cache-Control', 'public, max-age=3600')])
                return self.error_page(405)
            else:
                self.start_response('200 OK', [('Content-type', 'application/json; chearset=utf-8'), ('Cache-Control', 'public, max-age=3600')])
                return self.data_page(machine)


class MachineHandler(QueryPageHandler):
    def __init__(self, app, application_uri, environ, start_response, **kwargs):
        super(MachineHandler, self).__init__(app=app, application_uri=application_uri, environ=environ, start_response=start_response, **kwargs)
        self.shortname = wsgiref.util.shift_path_info(environ)

    def __iter__(self):
        if not self.shortname:
            # could probably list machines here or something
            self.start_response('403 %s' % (self.STATUS_MESSAGE[403], ), [('Content-type', 'text/html; charset=utf-8'), ('Cache-Control', 'public, max-age=3600')])
            return self.error_page(403)
        elif self.environ['PATH_INFO']:
            self.start_response('404 %s' % (self.STATUS_MESSAGE[404], ), [('Content-type', 'text/html; charset=utf-8'), ('Cache-Control', 'public, max-age=3600')])
            return self.error_page(404)
        else:
            machine_info = self.dbcurs.get_machine_info(self.shortname).fetchone()
            if not machine_info:
                self.start_response('404 %s' % (self.STATUS_MESSAGE[404], ), [('Content-type', 'text/html; charset=utf-8'), ('Cache-Control', 'public, max-age=3600')])
                return self.error_page(404)
            elif self.environ['REQUEST_METHOD'] != 'GET':
                self.start_response('405 %s' % (self.STATUS_MESSAGE[405], ), [('Content-type', 'text/html; charset=utf-8'), ('Accept', 'GET, HEAD, OPTIONS'), ('Cache-Control', 'public, max-age=3600')])
                return self.error_page(405)
            else:
                self.start_response('200 OK', [('Content-type', 'text/html; chearset=utf-8'), ('Cache-Control', 'public, max-age=3600')])
                return self.machine_page(machine_info)

    def machine_page(self, machine_info):
        id = machine_info['id']
        description = machine_info['description']
        yield htmltmpl.MACHINE_PROLOGUE.substitute(
                app=self.js_escape(cgi.escape(self.application_uri, True)),
                assets=self.js_escape(cgi.escape(urlparse.urljoin(self.application_uri, 'static'), True)),
                sourcehref=self.sourcefile_href(machine_info['sourcefile']),
                description=cgi.escape(description),
                shortname=cgi.escape(self.shortname),
                isdevice=cgi.escape('Yes' if machine_info['isdevice'] else 'No'),
                runnable=cgi.escape('Yes' if machine_info['runnable'] else 'No'),
                sourcefile=cgi.escape(machine_info['sourcefile'])).encode('utf-8')
        if machine_info['year'] is not None:
            yield (
                    '    <tr><th>Year:</th><td>%s</td></tr>\n' \
                    '    <tr><th>Manufacturer:</th><td>%s</td></tr>\n' %
                    (cgi.escape(machine_info['year']), cgi.escape(machine_info['Manufacturer']))).encode('utf-8')
        if machine_info['cloneof'] is not None:
            parent = self.dbcurs.listfull(machine_info['cloneof']).fetchone()
            if parent:
                yield (
                        '    <tr><th>Parent Machine:</th><td><a href="%s">%s (%s)</a></td></tr>\n' %
                        (cgi.escape('%smachine/%s' % (self.application_uri, machine_info['cloneof']), True), cgi.escape(parent[1]), cgi.escape(machine_info['cloneof']))).encode('utf-8')
            else:
                yield (
                        '    <tr><th>Parent Machine:</th><td><a href="%s">%s</a></td></tr>\n' %
                        (cgi.escape('%smachine/%s' % (self.application_uri, machine_info['cloneof']), True), cgi.escape(machine_info['cloneof']))).encode('utf-8')
        if (machine_info['romof'] is not None) and (machine_info['romof'] != machine_info['cloneof']):
            parent = self.dbcurs.listfull(machine_info['romof']).fetchone()
            if parent:
                yield (
                        '    <tr><th>Parent ROM set:</th><td><a href="%s">%s (%s)</a></td></tr>\n' %
                        (cgi.escape('%smachine/%s' % (self.application_uri, machine_info['romof']), True), cgi.escape(parent[1]), cgi.escape(machine_info['romof']))).encode('utf-8')
            else:
                yield (
                        '    <tr><th>Parent Machine:</th><td><a href="%s">%s</a></td></tr>\n' %
                        (cgi.escape('%smachine/%s' % (self.application_uri, machine_info['romof']), True), cgi.escape(machine_info['romof']))).encode('utf-8')
        unemulated = []
        imperfect = []
        for feature, status, overall in self.dbcurs.get_feature_flags(id):
            if overall == 1:
                imperfect.append(feature)
            elif overall > 1:
                unemulated.append(feature)
        if (unemulated):
            unemulated.sort()
            yield(
                    ('    <tr><th>Unemulated Features:</th><td>%s' + (', %s' * (len(unemulated) - 1)) + '</td></tr>\n') %
                    tuple(unemulated)).encode('utf-8');
        if (imperfect):
            yield(
                    ('    <tr><th>Imperfect Features:</th><td>%s' + (', %s' * (len(imperfect) - 1)) + '</td></tr>\n') %
                    tuple(imperfect)).encode('utf-8');
        yield '</table>\n'.encode('utf-8')

        # allow system BIOS selection
        haveoptions = False
        for name, desc, isdef in self.dbcurs.get_biossets(id):
            if not haveoptions:
                haveoptions = True;
                yield htmltmpl.MACHINE_OPTIONS_HEADING.substitute().encode('utf-8')
                yield htmltmpl.MACHINE_BIOS_PROLOGUE.substitute().encode('utf-8')
            yield htmltmpl.MACHINE_BIOS_OPTION.substitute(
                    name=cgi.escape(name, True),
                    description=cgi.escape(desc),
                    isdefault=('yes' if isdef else 'no')).encode('utf-8')
        if haveoptions:
            yield '</select>\n<script>set_default_system_bios();</script>\n'.encode('utf-8')

        # allow RAM size selection
        first = True
        for name, size, isdef in self.dbcurs.get_ram_options(id):
            if first:
                if not haveoptions:
                    haveoptions = True;
                    yield htmltmpl.MACHINE_OPTIONS_HEADING.substitute().encode('utf-8')
                yield htmltmpl.MACHINE_RAM_PROLOGUE.substitute().encode('utf-8')
                first = False
            yield htmltmpl.MACHINE_RAM_OPTION.substitute(
                    name=cgi.escape(name, True),
                    size=cgi.escape('{:,}'.format(size)),
                    isdefault=('yes' if isdef else 'no')).encode('utf-8')
        if not first:
            yield '</select>\n<script>set_default_ram_option();</script>\n'.encode('utf-8')

        # placeholder for machine slots - populated by client-side JavaScript
        if self.dbcurs.count_slots(id):
            if not haveoptions:
                haveoptions = True
                yield htmltmpl.MACHINE_OPTIONS_HEADING.substitute().encode('utf-8')
            yield htmltmpl.MACHINE_SLOTS_PLACEHOLDER.substitute(
                    machine=self.js_escape(self.shortname)).encode('utf=8')

        # list devices referenced by this system/device
        first = True
        for name, desc, src in self.dbcurs.get_devices_referenced(id):
            if first:
                yield \
                        '<h2>Devices Referenced</h2>\n' \
                        '<table id="tbl-dev-refs">\n' \
                        '    <thead>\n' \
                        '        <tr><th>Short name</th><th>Description</th><th>Source file</th></tr>\n' \
                        '    </thead>\n' \
                        '    <tbody>\n'.encode('utf-8')
                first = False
            yield self.machine_row(name, desc, src)
        if not first:
            yield htmltmpl.SORTABLE_TABLE_EPILOGUE.substitute(id='tbl-dev-refs').encode('utf-8')

        # list slots where this device is an option
        first = True
        for name, desc, slot, opt, src in self.dbcurs.get_compatible_slots(id):
            if (first):
                yield \
                        '<h2>Compatible Slots</h2>\n' \
                        '<table id="tbl-comp-slots">\n' \
                        '    <thead>\n' \
                        '        <tr><th>Short name</th><th>Description</th><th>Slot</th><th>Choice</th><th>Source file</th></tr>\n' \
                        '    </thead>\n' \
                        '    <tbody>\n'.encode('utf-8')
                first = False
            yield htmltmpl.COMPATIBLE_SLOT_ROW.substitute(
                    machinehref=self.machine_href(name),
                    sourcehref=self.sourcefile_href(src),
                    shortname=cgi.escape(name),
                    description=cgi.escape(desc),
                    sourcefile=cgi.escape(src),
                    slot=cgi.escape(slot),
                    slotoption=cgi.escape(opt)).encode('utf-8')
        if not first:
            yield htmltmpl.SORTABLE_TABLE_EPILOGUE.substitute(id='tbl-comp-slots').encode('utf-8')

        # list systems/devices that reference this device
        first = True
        for name, desc, src in self.dbcurs.get_device_references(id):
            if first:
                yield \
                        '<h2>Referenced By</h2>\n' \
                        '<table id="tbl-ref-by">\n' \
                        '    <thead>\n' \
                        '        <tr><th>Short name</th><th>Description</th><th>Source file</th></tr>\n' \
                        '    </thead>\n' \
                        '    <tbody>\n'.encode('utf-8')
                first = False
            yield self.machine_row(name, desc, src)
        if not first:
            yield htmltmpl.SORTABLE_TABLE_EPILOGUE.substitute(id='tbl-ref-by').encode('utf-8')

        yield '</html>\n'.encode('utf-8')

    def machine_row(self, shortname, description, sourcefile):
        return (htmltmpl.MACHINE_ROW if description is not None else htmltmpl.EXCL_MACHINE_ROW).substitute(
                machinehref=self.machine_href(shortname),
                sourcehref=self.sourcefile_href(sourcefile),
                shortname=cgi.escape(shortname),
                description=cgi.escape(description or ''),
                sourcefile=cgi.escape(sourcefile or '')).encode('utf-8')


class SourceFileHandler(QueryPageHandler):
    def __init__(self, app, application_uri, environ, start_response, **kwargs):
        super(SourceFileHandler, self).__init__(app=app, application_uri=application_uri, environ=environ, start_response=start_response, **kwargs)

    def __iter__(self):
        self.filename = self.environ['PATH_INFO']
        if self.filename and (self.filename[0] == '/'):
            self.filename = self.filename[1:]
        if not self.filename:
            if self.environ['REQUEST_METHOD'] != 'GET':
                self.start_response('405 %s' % (self.STATUS_MESSAGE[405], ), [('Content-type', 'text/html; charset=utf-8'), ('Accept', 'GET, HEAD, OPTIONS'), ('Cache-Control', 'public, max-age=3600')])
                return self.error_page(405)
            else:
                self.start_response('200 OK', [('Content-type', 'text/html; chearset=utf-8'), ('Cache-Control', 'public, max-age=3600')])
                return self.sourcefile_listing_page(None)
        else:
            id = self.dbcurs.get_sourcefile_id(self.filename)
            if id is None:
                if ('*' not in self.filename) and ('?' not in self.filename) and ('?' not in self.filename):
                    self.filename += '*' if self.filename[-1] == '/' else '/*'
                    if not self.dbcurs.count_sourcefiles(self.filename):
                        self.start_response('404 %s' % (self.STATUS_MESSAGE[404], ), [('Content-type', 'text/html; charset=utf-8'), ('Cache-Control', 'public, max-age=3600')])
                        return self.error_page(404)
                    elif self.environ['REQUEST_METHOD'] != 'GET':
                        self.start_response('405 %s' % (self.STATUS_MESSAGE[405], ), [('Content-type', 'text/html; charset=utf-8'), ('Accept', 'GET, HEAD, OPTIONS'), ('Cache-Control', 'public, max-age=3600')])
                        return self.error_page(405)
                    else:
                        self.start_response('200 OK', [('Content-type', 'text/html; chearset=utf-8'), ('Cache-Control', 'public, max-age=3600')])
                        return self.sourcefile_listing_page(self.filename)
                else:
                    self.start_response('404 %s' % (self.STATUS_MESSAGE[404], ), [('Content-type', 'text/html; charset=utf-8'), ('Cache-Control', 'public, max-age=3600')])
                    return self.error_page(404)
            elif self.environ['REQUEST_METHOD'] != 'GET':
                self.start_response('405 %s' % (self.STATUS_MESSAGE[405], ), [('Content-type', 'text/html; charset=utf-8'), ('Accept', 'GET, HEAD, OPTIONS'), ('Cache-Control', 'public, max-age=3600')])
                return self.error_page(405)
            else:
                self.start_response('200 OK', [('Content-type', 'text/html; chearset=utf-8'), ('Cache-Control', 'public, max-age=3600')])
                return self.sourcefile_page(id)

    def sourcefile_listing_page(self, pattern):
        if not pattern:
            title = heading = 'All Source Files'
        else:
            heading = self.linked_title(pattern)
            title = 'Source Files: ' + cgi.escape(pattern)
        yield htmltmpl.SOURCEFILE_LIST_PROLOGUE.substitute(
                assets=cgi.escape(urlparse.urljoin(self.application_uri, 'static'), True),
                title=title,
                heading=heading).encode('utf-8')
        for filename, machines in self.dbcurs.get_sourcefiles(pattern):
            yield htmltmpl.SOURCEFILE_LIST_ROW.substitute(
                    sourcefile=self.linked_title(filename, True),
                    machines=cgi.escape('%d' % machines)).encode('utf-8')
        yield '    </tbody>\n</table>\n<script>make_table_sortable(document.getElementById("tbl-sourcefiles"));</script>\n</body>\n</html>\n'.encode('utf-8')

    def sourcefile_page(self, id):
        yield htmltmpl.SOURCEFILE_PROLOGUE.substitute(
                assets=cgi.escape(urlparse.urljoin(self.application_uri, 'static'), True),
                filename=cgi.escape(self.filename),
                title=self.linked_title(self.filename)).encode('utf-8')

        first = True
        for machine_info in self.dbcurs.get_sourcefile_machines(id):
            if first:
                yield \
                        '<table id="tbl-machines">\n' \
                        '    <thead>\n' \
                        '        <tr>\n' \
                        '            <th>Short name</th>\n' \
                        '            <th>Description</th>\n' \
                        '            <th>Year</th>\n' \
                        '            <th>Manufacturer</th>\n' \
                        '            <th>Runnable</th>\n' \
                        '            <th>Parent</th>\n' \
                        '        </tr>\n' \
                        '    </thead>\n' \
                        '    <tbody>\n'.encode('utf-8')
                first = False
            yield self.machine_row(machine_info)
        if first:
            yield '<p>No machines found.</p>\n'.encode('utf-8')
        else:
            yield '    </tbody>\n</table>\n<script>make_table_sortable(document.getElementById("tbl-machines"));</script>\n'.encode('utf-8')

        yield '</body>\n</html>\n'.encode('utf-8')

    def linked_title(self, filename, linkfinal=False):
        parts = filename.split('/')
        final = parts[-1]
        del parts[-1]
        uri = urlparse.urljoin(self.application_uri, 'sourcefile')
        title = ''
        for part in parts:
            uri = urlparse.urljoin(uri + '/', part)
            title += '<a href="{0}">{1}</a>/'.format(cgi.escape(uri, True), cgi.escape(part))
        if linkfinal:
            uri = urlparse.urljoin(uri + '/', final)
            return title + '<a href="{0}">{1}</a>'.format(cgi.escape(uri, True), cgi.escape(final))
        else:
            return title + final

    def machine_row(self, machine_info):
        return (htmltmpl.SOURCEFILE_ROW_PARENT if machine_info['cloneof'] is None else htmltmpl.SOURCEFILE_ROW_CLONE).substitute(
                machinehref=self.machine_href(machine_info['shortname']),
                parenthref=self.machine_href(machine_info['cloneof'] or '__invalid'),
                shortname=cgi.escape(machine_info['shortname']),
                description=cgi.escape(machine_info['description']),
                year=cgi.escape(machine_info['year'] or ''),
                manufacturer=cgi.escape(machine_info['manufacturer'] or ''),
                runnable=cgi.escape('Yes' if machine_info['runnable'] else 'No'),
                parent=cgi.escape(machine_info['cloneof'] or '')).encode('utf-8')


class BiosRpcHandler(MachineRpcHandlerBase):
    def data_page(self, machine):
        result = { }
        for name, description, isdefault in self.dbcurs.get_biossets(machine):
            result[name] = { 'description': description, 'isdefault': True if isdefault else False }
        yield json.dumps(result).encode('utf-8')


class FlagsRpcHandler(MachineRpcHandlerBase):
    def data_page(self, machine):
        result = { 'features': { } }
        for feature, status, overall in self.dbcurs.get_feature_flags(machine):
            detail = { }
            if status == 1:
                detail['status'] = 'imperfect'
            elif status > 1:
                detail['status'] = 'unemulated'
            if overall == 1:
                detail['overall'] = 'imperfect'
            elif overall > 1:
                detail['overall'] = 'unemulated'
            result['features'][feature] = detail
        yield json.dumps(result).encode('utf-8')


class SlotsRpcHandler(MachineRpcHandlerBase):
    def data_page(self, machine):
        result = { 'defaults': { }, 'slots': { } }

        # get defaults and slot options
        for slot, default in self.dbcurs.get_slot_defaults(machine):
            result['defaults'][slot] = default
        prev = None
        for slot, option, shortname, description in self.dbcurs.get_slot_options(machine):
            if slot != prev:
                if slot in result['slots']:
                    options = result['slots'][slot]
                else:
                    options = { }
                    result['slots'][slot] = options
                prev = slot
            options[option] = { 'device': shortname, 'description': description }

        # remove slots that come from default cards in other slots
        for slot in tuple(result['slots'].keys()):
            slot += ':'
            for candidate in tuple(result['slots'].keys()):
                if candidate.startswith(slot):
                    del result['slots'][candidate]

        yield json.dumps(result).encode('utf-8')


class MiniMawsApp(object):
    JS_ESCAPE = re.compile('([\"\'\\\\])')
    RPC_SERVICES = {
            'bios':     BiosRpcHandler,
            'flags':    FlagsRpcHandler,
            'slots':    SlotsRpcHandler }

    def __init__(self, dbfile, **kwargs):
        super(MiniMawsApp, self).__init__(**kwargs)
        self.dbconn = dbaccess.QueryConnection(dbfile)
        self.assetsdir = os.path.join(os.path.dirname(inspect.getfile(self.__class__)), 'assets')
        if not mimetypes.inited:
            mimetypes.init()

    def __call__(self, environ, start_response):
        application_uri = wsgiref.util.application_uri(environ)
        module = wsgiref.util.shift_path_info(environ)
        if module == 'machine':
            return MachineHandler(self, application_uri, environ, start_response)
        elif module == 'sourcefile':
            return SourceFileHandler(self, application_uri, environ, start_response)
        elif module == 'static':
            return AssetHandler(self.assetsdir, self, application_uri, environ, start_response)
        elif module == 'rpc':
            service = wsgiref.util.shift_path_info(environ)
            if not service:
                return ErrorPageHandler(403, self, application_uri, environ, start_response)
            elif service in self.RPC_SERVICES:
                return self.RPC_SERVICES[service](self, application_uri, environ, start_response)
            else:
                return ErrorPageHandler(404, self, application_uri, environ, start_response)
        elif not module:
            return ErrorPageHandler(403, self, application_uri, environ, start_response)
        else:
            return ErrorPageHandler(404, self, application_uri, environ, start_response)

    def js_escape(self, str):
        return self.JS_ESCAPE.sub('\\\\\\1', str).replace('\0', '\\0')


def run_server(options):
    application = MiniMawsApp(options.database)
    server = wsgiref.simple_server.make_server(options.host, options.port, application)
    try:
        server.serve_forever()
    except KeyboardInterrupt:
        pass
