# -*- encoding: utf-8 -*-

import time, webbrowser, sys, datetime, cStringIO, xml.sax, xml.sax.saxutils, hashlib, time, binascii, unicodedata, urllib, urllib2, string, re, tempfile, os, shutil

path = os.path.join(os.path.dirname(os.path.abspath(__file__)), '..', 'lib')
sys.path.insert(0, path)

import flask, sqlalchemy, icalendar

import tables, orm, cfg, lang, rsa

app = flask.Flask('__main__')
app.secret_key = cfg.keys.cookie_secret

@app.template_filter('time')
def format_time(value, env):
    if env.lang == 'de':
        return value.strftime('%H:%M')
    return value.strftime('%I.%M %p')

weekdays = {'en': {'short': {0: 'Mon', 1: 'Tue', 2: 'Wed', 3: 'Thu', 4: 'Fri', 5: 'Sat', 6: 'Sun'},
                   'full': {0: 'Monday', 1: 'Tuesday', 2: 'Wednesday', 3: 'Thursday', 4: 'Friday', 5: 'Saturday', 6: 'Sunday'}},
            'de': {'short': {0: 'Mo', 1: 'Di', 2: 'Mi', 3: 'Do', 4: 'Fr', 5: 'Sa', 6: 'So'},
                   'full': {0: 'Montag', 1: 'Dienstag', 2: 'Mittwoch', 3: 'Donnerstag', 4: 'Freitag', 5: 'Samstag', 6: 'Sonntag'}}}

@app.template_filter('weekday')
def format_weekday(value, env, format='short'):
    return weekdays.get(env.lang, weekdays['en'])[format][value.weekday()]

@app.template_filter('date')
def format_date(value, env, format='full'):
    if env.lang == 'de':
        weekday = weekdays['de'][format][value.weekday()]
        month = {1: 'Januar', 2: 'Februar', 3: u'März', 4: 'April', 5: 'Mai', 6: 'Juni', 7: 'Juli', 8: 'August', 9: 'September', 10: 'Oktober', 11: 'November', 12: 'Dezember'}[value.month]
        return '%s, %d. %s %d' % (weekday, value.day, month, value.year)
    weekday = weekdays['en'][format][value.weekday()]
    month = {1: 'January', 2: 'February', 3: u'March', 4: 'April', 5: 'May', 6: 'June', 7: 'July', 8: 'August', 9: 'September', 10: 'October', 11: 'November', 12: 'December'}[value.month]
    return '%s, %s %d, %d' % (weekday, month, value.day, value.year)

@app.template_filter('session_order')
def sort_session_order(value):
    value.sort(key=lambda x: (x.session.begin, x.session.part.order, x.session.part_order))
    return value


def daterange(start_date, end_date):
    for n in range((end_date - start_date).days+1):
         yield start_date + datetime.timedelta(n)


def slow_hash(value):
    for i in range(cfg.keys.slow_hash_iterations):
        value = hashlib.sha1(value).digest()
    return binascii.hexlify(value)


class Env(object):

    app = app
    cfg = cfg

    @property
    def layout(self):
        if not hasattr(self, '_layout'):
            self._layout = flask.session.get('layout')
            if self._layout not in cfg.general.layouts:
                self._layout = cfg.general.layout
        return self._layout

    @property
    def lang(self):
        if not hasattr(self, '_lang'):
            self._lang = flask.session.get('lang')
            if self._lang not in cfg.general.languages:
                self._lang = cfg.general.language
        return self._lang

    @property
    def parts(self):
        if not hasattr(self, '_parts'):
            self._parts = self.db.query(orm.Part).order_by(orm.Part.order)
        return self._parts

    @property
    def days(self):
        if not hasattr(self, '_days'):
            first_session = self.db.query(orm.Session).order_by(orm.Session.begin).limit(1).one()
            last_session = self.db.query(orm.Session).order_by(sqlalchemy.desc(orm.Session.begin)).limit(1).one()
            self._days = list(daterange(first_session.begin.date(), last_session.begin.date()))
        return self._days

    @property
    def updates(self):
        if not hasattr(self, '_updates'):
            self._updates = self.db.query(orm.Update).order_by(sqlalchemy.desc(orm.Update.number)).all()
        return self._updates

    @property
    def state(self):
        if not hasattr(self, '_state'):
            state_id = flask.session.get('state_id')
            self.new_state = state_id is None
            if not self.new_state:
                self._state = self.db.query(orm.State).filter_by(id=state_id).first()
                if self._state is None:
                    self.new_state = True
            if self.new_state:
                self._state = orm.State()
        return self._state

    def save_state(self):
        if self.new_state:
            self.db.add(self._state)
            self.db.commit()
            flask.session['state_id'] = self._state.id
            self.new_state = False

    def lang_dict(self, **kwargs):
        return kwargs.get(self.lang or cfg.general.language)

    def lang_str(self, str):
        return lang.extract(cfg.general.languages, str, self.lang or cfg.general.language)

    @property
    def form_action(self):
        return flask.request.path.rsplit('/', 1)[1]

    def url_for(self, endpoint, **kwargs):
        return cfg.deploy.base_url + flask.url_for(endpoint, **kwargs)


class Route(object):

    def __init__(self, rule, **options):
        self.rule = rule
        self.options = options

    def __call__(self, func):
        def wrapper(**kwargs):
            env = Env()
            env.xhr = flask.request.args.get('xhr')
            lang = flask.request.args.get('lang')
            if lang in cfg.general.languages:
                flask.session['lang'] = lang
            layout = flask.request.args.get('layout')
            if layout in cfg.general.layouts:
                flask.session['layout'] = layout
            env.db = orm.DB()
            try:
                if kwargs.has_key('part'):
                    try:
                        env.part = env.db.query(orm.Part).filter_by(url=kwargs.pop('part')).one()
                    except sqlalchemy.orm.exc.NoResultFound:
                        flask.abort(404)
                if kwargs.has_key('session'):
                    try:
                        env.session = env.db.query(orm.Session).filter_by(url=kwargs.pop('session')).filter_by(part=env.part).one()
                    except sqlalchemy.orm.exc.NoResultFound:
                        flask.abort(404)
                if kwargs.has_key('paper'):
                    try:
                        env.paper = env.db.query(orm.Paper).filter_by(url=kwargs.pop('paper')).filter_by(session=env.session).one()
                    except sqlalchemy.orm.exc.NoResultFound:
                        flask.abort(404)
                if kwargs.has_key('day'):
                    try:
                        env.day = datetime.date(*map(int, kwargs.pop('day').split('-')))
                    except sqlalchemy.orm.exc.NoResultFound:
                        flask.abort(404)
                if kwargs:
                    raise RuntimeError('unprocessed arguments', kwargs)
                result = func(env)
            finally:
                try:
                    env.db.commit()
                finally:
                    env.db.close()
            response = flask.make_response(result)
            response.headers['Cache-Control'] = 'no-cache, must-revalidate'
            return response
        app.add_url_rule(cfg.deploy.url_prefix + self.rule, func.func_name, wrapper, **self.options)
        return wrapper


@Route('/', methods=['GET'])
def default(env):
    return flask.redirect(env.url_for('parts'))


@Route('/%s' % cfg.url.parts, methods=['GET'])
def parts(env):
    return flask.render_template('parts.html', env=env)


@Route('/%s/<part>' % cfg.url.part, methods=['GET'])
def part(env):
    return flask.render_template('part.html', env=env)


@Route('/%s/<part>/session/<session>' % cfg.url.part, methods=['GET', 'POST'])
def session(env):
    if flask.request.method == 'POST':
        choice = flask.request.form.get('session')
        state_session = env.session.states.get(env.state.id)
        if state_session:
            if choice == cfg.session.choices[0]:
                env.db.delete(state_session)
                for state_paper in env.db.query(orm.StatePaper).filter_by(state=env.state).join(orm.Paper).filter_by(session=env.session).all():
                    env.db.delete(state_paper)
            else:
                state_session.choice = choice
        else:
            if choice != cfg.session.choices[0]:
                orm.StateSession(choice, env.state, env.session)
                env.save_state()
    return flask.render_template('session.html', env=env, showSelection=flask.request.args.get('showSelection'))


@Route('/%s/<part>/session/<session>/%s/<paper>' % (cfg.url.part, cfg.url.paper), methods=['GET', 'POST'])
def paper(env):
    if flask.request.method == 'POST':
        choice = flask.request.form.get('paper')
        state_session = env.session.states.get(env.state.id)
        if not state_session and choice != cfg.paper.choices[0]:
            orm.StateSession(cfg.session.choices[1], env.state, env.session)
        state_paper = env.paper.states.get(env.state.id)
        if state_paper:
            if choice == cfg.paper.choices[0]:
                env.db.delete(state_paper)
            else:
                state_paper.choice = choice
        else:
            if choice != cfg.paper.choices[0]:
                orm.StatePaper(choice, env.state, env.paper)
                env.save_state()
    return flask.render_template('paper.html', env=env, showSelection=flask.request.args.get('showSelection'))


@Route('/days', methods=['GET'])
def days(env):
    return flask.render_template('days.html', env=env)


@Route('/day/<day>', methods=['GET'])
def day(env):
    env.sessions = env.db.query(orm.Session).filter(sqlalchemy.extract('day', orm.Session.begin) == env.day.day).order_by(orm.Session.begin).join(orm.Part).order_by(orm.Part.order).order_by(orm.Session.part_order).all()
    return flask.render_template('day.html', env=env)


def state2xml(state):
    f = cStringIO.StringIO()
    x = xml.sax.saxutils.XMLGenerator(f, 'utf-8')
    x.startDocument()
    x.startElement('scip', {'version': '1.0', 'year': cfg.scip.year, 'conf': cfg.scip.conf})
    x.characters('\n')
    for state_session in state.sessions:
        x.startElement('session', {'part': state_session.session.part.url,
                                   'session': state_session.session.url,
                                   'choice': state_session.choice})
        x.endElement('session')
        x.characters('\n')
    for state_paper in state.papers:
        x.startElement('paper', {'part': state_paper.paper.session.part.url,
                                 'session': state_paper.paper.session.url,
                                 'paper': state_paper.paper.url,
                                 'choice': state_paper.choice})
        x.endElement('paper')
        x.characters('\n')
    x.endElement(u'scip')
    x.characters('\n')
    x.endDocument()
    f.seek(0)
    return f


class LoadError(Exception):

    def __init__(self, **msg):
        self.msg = msg


def xml2state(f, db, state):

    class LoadHandler(xml.sax.handler.ContentHandler):

        def __init__(self):
            # make it more robust: don't load papers without session
            self.session_ids = set()

        def startElement(self, name, attrs):
            if name == 'scip':
                if attrs['version'] != '1.0':
                    raise LoadError(en='invalid file (version)', de=u'ungültige Datei (version)')
                if attrs['year'] != cfg.scip.year:
                    raise LoadError(en='invalid file (year)', de=u'ungültige Datei (year)')
                if attrs['conf'] != cfg.scip.conf:
                    raise LoadError(en='invalid file (conf)', de=u'ungültige Datei (conf)')
                db.query(orm.StateSession).filter_by(state=state).delete()
                db.query(orm.StatePaper).filter_by(state=state).delete()
            elif name == 'session':
                session = db.query(orm.Session).filter_by(url=attrs['session'])\
                                               .join('part').filter_by(url=attrs['part'])\
                                               .first()
                if session:
                    self.session_ids.add(session.id)
                    orm.StateSession(attrs['choice'], state, session)
            elif name == 'paper':
                paper = db.query(orm.Paper).filter_by(url=attrs['paper'])\
                                           .join('session').filter_by(url=attrs['session'])\
                                           .join('part', from_joinpoint=True).filter_by(url=attrs['part'])\
                                           .first()
                if paper and paper.session.id in self.session_ids:
                    orm.StatePaper(attrs['choice'], state, paper)
            else:
                raise LoadError(en='invalid file (tag)', de=u'ungültige Datei (tag)')

    if f.filename is None:
        raise LoadError(en='file missing', de=u'Datei fehlt')
    loadHandler = LoadHandler()
    try:
        xml.sax.parse(f, loadHandler)
    except xml.sax.SAXException:
        raise LoadError(en='invalid file (format)', de=u'ungültige Datei (format)')


@Route('/select', methods=['GET', 'POST'])
def select(env):
    if flask.request.method == 'POST':
        if flask.request.form.get('delete'):
            env.db.query(orm.StateSession).filter_by(state=env.state).delete()
            env.db.query(orm.StatePaper).filter_by(state=env.state).delete()
            # make it robust to unsaved states (like at reload of the delete page)
            env.save_state()
            env.db.delete(env.state)
            flask.session.pop('state_id')
        elif flask.request.form.get('export'):
            format = flask.request.form.get('format')
            if format in ['regular', 'notes']:
                if not cfg.deploy.pdflatex:
                    return flask.render_template('nolatex.html', env=env)
                dir = tempfile.mkdtemp()
                f = open(os.path.join(dir, 'sciplite.tex'), 'w')
                f.write('\\documentclass[%s]{%s}\n' % (format, env.cfg.format.latex_class))
                f.write('\\begin{document}\n')
                f.write('\\scBookLanguage{%s}\n' % env.lang)
                f.write('\\begin{scSessions}\n')
                for state_session in sort_session_order(env.state.sessions):
                    f.write('\\scSessionPart{%s}\n' % state_session.session.part.name)
                    f.write('\\scSessionKey{%s}\n' % state_session.session.name)
                    f.write('\\scSessionTitle{%s}\n' % state_session.session.title)
                    f.write('\\scSessionDay{%s}\n' % format_weekday(state_session.session.begin, env, 'full'))
                    f.write('\\scSessionBeginTime{%s}\n' % format_time(state_session.session.begin, env))
                    f.write('\\scSessionEndTime{%s}\n' % format_time(state_session.session.end, env))
                    f.write('\\scSessionLocation{%s}\n' % state_session.session.location)
                    f.write('\\scSessionFormat{%s}\n' % state_session.choice)
                    f.write('\\begin{scSession}\n')
                    for paper in state_session.session.papers:
                        f.write('\\begin{scAbstract}\n')
                        state_paper = paper.states.get(env.state.id)
                        if state_paper:
                            f.write('\\scAbstractFormat{%s}\n' % state_paper.choice)
                        else:
                            f.write('\\scAbstractFormat{%s}\n' % state_session.choice)
                        f.write(paper.latex)
                        f.write('\\end{scAbstract}\n')
                    f.write('\\end{scSession}\n')
                f.write('\\end{scSessions}\n')
                f.write('\\end{document}\n')
                f.close()
                shutil.copyfile(os.path.join(os.path.dirname(os.path.abspath(__file__)), 'static', '%s.cls' % env.cfg.format.latex_class),
                                os.path.join(dir, '%s.cls' % env.cfg.format.latex_class))
                os.system('cd "%s";echo x|pdflatex sciplite' % dir)
                try:
                    pdf = open(os.path.join(dir, 'sciplite.pdf'), 'rb')
                    f = cStringIO.StringIO()
                    shutil.copyfileobj(pdf, f)
                    pdf.close()
                except IOError:
                    return flask.render_template('latexfailed.html', env=env)
                shutil.rmtree(dir, True)
                f.seek(0)
                return flask.send_file(f, mimetype='application/pdf', as_attachment=True,
                                       attachment_filename='%s_%s_%s.pdf' % (env.lang_str(cfg.general.pdffilename), cfg.scip.year, cfg.scip.conf))
            elif format == 'ics':
                now = datetime.datetime.now()
                cal = icalendar.Calendar()
                cal.add('prodid', '-//%s//%s//' % (cfg.scip.title, cfg.scip.url.replace('http://', '')))
                cal.add('version', '2.0')
                papers = dict()
                for state_session in env.state.sessions:
                    if state_session.choice != cfg.session.choices[1]:
                        for paper in state_session.session.papers:
                            papers[paper] = cfg.format.ics_priority[state_session.choice]
                for state_paper in env.state.papers:
                    papers[state_paper.paper] = cfg.format.ics_priority[state_paper.choice]
                for paper, priority in papers.items():
                    event = icalendar.Event()
                    event.add('summary', '%s: %s - %s ' % (paper.name, flask.Markup(paper.html_link).striptags(), flask.Markup(paper.html_extra).striptags()))
                    event.add('description', flask.Markup(paper.html.replace('</p>', '</p>###LINEBREAK###')).striptags().replace('###LINEBREAK###', '\n').strip())
                    event.add('location', paper.session.location)
                    event.add('dtstart', paper.begin)
                    event.add('dtend', paper.end)
                    event.add('dtstamp', now)
                    event['uid'] = '%s@%s' % (paper.name.replace(' ', '_'), cfg.scip.url.replace('http://', ''))
                    event.add('priority', priority)
                    cal.add_component(event)
                f = cStringIO.StringIO()
                f.write(cal.as_string())
                f.seek(0)
                return flask.send_file(f, mimetype='text/calendar', as_attachment=True,
                                       attachment_filename='%s_%s_%s.ics' % (env.lang_str(cfg.general.icsfilename), cfg.scip.year, cfg.scip.conf))
            else:
                flask.abort(400)
        elif flask.request.form.get('store_file'):
            return flask.send_file(state2xml(env.state), mimetype='application/xml', as_attachment=True,
                                   attachment_filename='%s_%s_%s.xml' % (env.lang_str(cfg.general.selectionfilename), cfg.scip.year, cfg.scip.conf))
        elif flask.request.form.get('restore_file'):
            try:
                xml2state(flask.request.files.get('file'), env.db, env.state)
            except LoadError, e:
                env.restore_file_error = e.msg
            else:
                if env.state.sessions:
                    env.save_state()
        elif flask.request.form.get('store_server') or flask.request.form.get('restore_server') or flask.request.form.get('delete_server'):
            login = flask.request.form.get('login')
            passwd = flask.request.form.get('passwd')
            if not login or not passwd:
                env.any_server_error = dict(en='user name or password is missing', de='Benutzername oder Passwort fehlt')
            else:
                passwd_hash = slow_hash(chr(0).join([login.encode('utf-8'), cfg.keys.save_secret, passwd.encode('utf-8')]))
                save = env.db.query(orm.Save).filter_by(login=login).filter_by(passwd_hash=passwd_hash).first()
                if flask.request.form.get('store_server'):
                    if save:
                        env.db.query(orm.SaveSession).filter_by(save=save).delete()
                        env.db.query(orm.SavePaper).filter_by(save=save).delete()
                        env.db.delete(save)
                    save = orm.Save(login, passwd_hash)
                    for state_session in env.state.sessions:
                        orm.SaveSession(state_session.choice, save, state_session.session)
                    for state_paper in env.state.papers:
                        orm.SavePaper(state_paper.choice, save, state_paper.paper)
                    env.db.add(save)
                else:
                    if not save:
                        env.any_server_error = dict(en='no saved data found', de='keine gespeicherten Daten gefunden')
                    elif flask.request.form.get('restore_server'):
                        env.db.query(orm.StateSession).filter_by(state=env.state).delete()
                        env.db.query(orm.StatePaper).filter_by(state=env.state).delete()
                        env.save_state()
                        for save_session in save.sessions:
                            orm.StateSession(save_session.choice, env.state, save_session.session)
                        for save_paper in save.papers:
                            orm.StatePaper(save_paper.choice, env.state, save_paper.paper)
                    elif flask.request.form.get('delete_server'):
                        env.db.query(orm.SaveSession).filter_by(save=save).delete()
                        env.db.query(orm.SavePaper).filter_by(save=save).delete()
                        env.db.delete(save)
                    else:
                        flask.abort(500)
        else:
            flask.abort(400)
    return flask.render_template('selection.html', env=env)


@Route('/search', methods=['GET', 'POST'])
def search(env):
    limit = 10
    env.query = flask.request.args.get('query', '')
    env.page = int(flask.request.args.get('page', '1'))
    env.pages = []
    env.qs_query = urllib.urlencode([('query', env.query.encode('utf-8'))])
    env.qs = flask.request.environ.get('QUERY_STRING', '')
    query = env.db.query(orm.Paper)
    chars = []
    word = type = env.search = env.error = part = None
    for c in env.query[:1000].replace('Ae', 'A').replace('Oe', 'O').replace('Ue', 'U').replace('ae', 'a').replace('oe', 'o').replace('ue', 'u').replace(u'ß', 'ss') + ' ':
        try:
            n = unicodedata.name(c)
        except ValueError:
            pass
        else:
            if type == 'location' and c in '0123456789:.-_':
                chars.append(c)
            elif type == 'time' and c in '0123456789:,-':
                chars.append(c)
            elif type == 'limit' and c in '0123456789':
                chars.append(c)
            elif type == 'part':
                if c in 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-':
                    chars.append(c)
                else:
                    word = ''.join(chars)
            elif n.startswith('LATIN CAPITAL LETTER') or n.startswith('LATIN SMALL LETTER'):
                l = n.split()
                if len(l) >= 4 and l[3] in string.ascii_letters:
                    chars.append(l[3].lower())
                else:
                    word = ''.join(chars)
            else:
                word = ''.join(chars)
        if word is not None and not word and type:
            env.error = dict(en='invalid string after %s' % type, de=u'ungültige Zeichenkette nach %s' % type)
            break
        if word:
            if c == ':' and not type and word in ['authors', 'presenter', 'title', 'session', 'location', 'time', 'type', 'limit', 'part']:
                type = word
                word = None
            else:
                if type == 'limit':
                    try:
                        limit = int(word)
                    except ValueError:
                        env.error = dict(en='invalid number of results per page', de=u'ungültige Anzahl von Resultaten pro Seite')
                        break
                    if limit < 1:
                        env.error = dict(en='invalid number of results per page', de=u'ungültige Anzahl von Resultaten pro Seite')
                        break
                else:
                    env.search = True
                if type == None:
                    query = query.filter(orm.Paper.search.like(u'%%%s%%' % word))
                elif type == 'authors':
                    query = query.filter(orm.Paper.search_authors.like(u'%%%s%%' % word))
                elif type == 'presenter':
                    query = query.filter(orm.Paper.search_presenter.like(u'%%%s%%' % word))
                elif type == 'title':
                    query = query.filter(orm.Paper.search_title.like(u'%%%s%%' % word))
                elif type == 'session':
                    query = query.filter(orm.Paper.search_session.like(u'%%%s%%' % word))
                elif type == 'location':
                    query = query.filter(orm.Paper.search_location.like(u'%%%s%%' % word.replace('_', ' ')))
                elif type == 'type':
                    if word == 'oral':
                        query = query.filter(orm.Paper.type != 'Poster')
                    elif word == 'poster':
                        query = query.filter(orm.Paper.type == 'Poster')
                    else:
                        env.error = dict(en='invalid contribution type', de=u'ungültiger Beitragstyp')
                        break
                elif type == 'time':
                    try:
                        from_date, to_date = word.replace('-', '/', 2).split('-', 1)
                        if from_date.find(',') == -1:
                            from_date += ',0:00'
                        if to_date.find(',') == -1:
                            to_date += ',23:59'
                        from_date = datetime.datetime.strptime(from_date, '%Y/%m/%d,%H:%M')
                        to_date = datetime.datetime.strptime(to_date, '%Y-%m-%d,%H:%M')
                    except ValueError:
                        env.error = dict(en='invalid time range', de=u'ungültiger Zeitraum')
                        break
                    if from_date > to_date:
                        env.error = dict(en='negative time range', de=u'negativer Zeitraum')
                    query = query.filter(orm.Paper.begin <= to_date).filter(orm.Paper.end > from_date)
                elif type == 'part':
                    query = query.filter(orm.Paper.part == word)
                    part = True
                elif type != 'limit':
                    flask.abort(500)
                word = type = None
            chars = []
    if env.search and not env.error:
        if not part:
            query = query.filter(~orm.Paper.joint)
        query = query.order_by(orm.Paper.id)
        env.papers = query.offset(limit*(env.page-1)).limit(limit).all()
        if flask.request.method == 'POST' and flask.request.form.get('paper'):
            sessions = set()
            for paper in env.papers:
                choice = flask.request.form.get('paper')
                state_paper = paper.states.get(env.state.id)
                if state_paper:
                    if choice == cfg.paper.choices[0]:
                        env.db.delete(state_paper)
                        continue
                    state_paper.choice = choice
                else:
                    if choice == cfg.paper.choices[0]:
                        continue
                    orm.StatePaper(choice, env.state, paper)
                sessions.add(paper.session)
                env.save_state()
            for session in sessions:
                state_session = session.states.get(env.state.id)
                if not state_session and choice != cfg.paper.choices[0]:
                    orm.StateSession(cfg.session.choices[1], env.state, session)
        env.count = query.count()
        if env.count:
            lastpage = 1+(env.count-1)//limit
            if env.page <= 5:
                env.pages = list(range(1, env.page+1))
            else:
                env.pages = [1, None, env.page-2, env.page-1, env.page]
            if lastpage - env.page <= 4:
                env.pages.extend([i for i in range(env.page+1, lastpage+1)])
            else:
                env.pages.extend([env.page+1, env.page+2, None, lastpage])

    return flask.render_template('search.html', env=env)


@Route('/advanced_search', methods=['GET', 'POST'])
def advanced_search(env):
    if flask.request.method == 'POST':
        query = []
        for key in ['full', 'authors', 'presenter', 'title', 'session', 'type', 'limit', 'part']:
            value = flask.request.form.get(key)
            if value:
                for word in value.split():
                    if key == 'full':
                        query.append(word)
                    else:
                        query.append('%s:%s' % (key, word))
        location = flask.request.form.get('location', '').strip().replace(' ', '_')
        if location:
            query.append('location:%s' % location)
        from_date = re.sub('[^-0-9]', '', flask.request.form.get('from_date', ''))
        to_date = re.sub('[^-0-9]', '', flask.request.form.get('to_date', ''))
        if from_date and to_date:
            from_time = re.sub('[^:0-9]', '', flask.request.form.get('from_time', ''))
            if from_time:
                from_date += ',' + from_time
            to_time = re.sub('[^:0-9]', '', flask.request.form.get('to_time', ''))
            if to_time:
                to_date += ',' + to_time
            query.append('time:%s-%s' % (from_date, to_date))
        query = ' '.join(query)
        qs = urllib.urlencode([('query', query.encode('utf-8')), ('submit', env.lang_dict(en='Search', de='Suchen'))])
        return flask.redirect('%s?%s' % (env.url_for('search'), qs))
    return flask.render_template('advanced_search.html', env=env)


@Route('/updates', methods=['GET', 'POST'])
def updates(env):

    def str2date(str):
        return datetime.datetime.strptime(str, '%d.%m.%Y %H:%M')

    class UpdateHandler(xml.sax.handler.ContentHandler):

        def __init__(self, db, number):
            self.db = db
            self.number = number
            self.buffer = None

        def startElement(self, name, attrs):
            if name == 'update':
                self.update = orm.Update(self.number, str2date(attrs['released']))
                self.db.add(self.update)
            elif name == 'modify_paper':
                paper = self.db.query(orm.Paper).filter_by(name=attrs['name']).one()
                orm.UpdateItem(attrs['description'], paper, self.update)
                self.modify = paper
            elif name == 'modify_session':
                self.modify = self.db.query(orm.Session).filter_by(name=attrs['name']).one()
            elif name == 'insert_paper':
                session = self.db.query(orm.Session).filter_by(name=attrs['session']).one()
                session.insert_paper(int(attrs['pos']), orm.Paper(attrs['url'], attrs['name'], *([None]*15)))
            else:
                self.buffer = []

        def characters(self, content):
            if self.buffer is not None:
                self.buffer.append(content)

        def endElement(self, name):
            if name not in ['update', 'modify_paper', 'modify_session', 'insert_paper']:
                value = ''.join(self.buffer)
                if name in ['begin', 'end']:
                    value = str2date(value)
                setattr(self.modify, name, value)
                self.buffer = None


    env.new = -1
    if flask.request.method == 'POST':
        try:
            count = int(urllib2.urlopen(cfg.updates.base_url + '/count').read(100))
            new = count - len(env.updates)
            for i in range(len(env.updates)+1, count+1):
                content = urllib2.urlopen(cfg.updates.base_url + '/fetch/%i/content' % i).read(1000000)
                signature = urllib2.urlopen(cfg.updates.base_url + '/fetch/%i/signature' % i).read(10000)
                if rsa.hash(content) != rsa.decrypt(int(signature)):
                    env.error = dict(en='update error (signature mismatch)', de='Aktualisierung fehlgeschlagen (Signaturfehler)')
                    break
                updateHandler = UpdateHandler(env.db, i)
                xml.sax.parseString(content, updateHandler)
            del env._updates # invalidate the cache
            env.new = new
        except urllib2.URLError:
            env.error = dict(en='communication failure with the update server', de='Kommunikationsfehler mit dem Aktualisierungsserver')
    return flask.render_template('updates.html', env=env)


@Route('/help')
def help(env):
    return flask.render_template('help.html', env=env)


@Route('/quit', methods=['GET', 'POST'])
def quit(env):
    if flask.request.method == 'GET':
        return flask.render_template('quit.html', env=env)
    if not env.app.can_quit:
        return flask.abort(400)
    sys.stderr.write('Application quits, thanks for using sciplite.\n')
    sys.exit()


if __name__ == '__main__':
    copyright = open(os.path.join(os.path.dirname(os.path.abspath(__file__)), 'COPYRIGHT.txt')).read()
    print '\n%s\n%s%s\n' % ('-'*78, copyright, '-'*78)
    tables.metadata.create_all(tables.engine)
    if cfg.deploy.start_client:
        start_msg = open(os.path.join(os.path.dirname(os.path.abspath(__file__)), 'templates', 'start.html')).read()
        start_page = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'start.html')
        f = open(start_page, 'w')
        f.write(start_msg.replace('{{ base_url }}', cfg.deploy.base_url).replace('{{ copyright }}', copyright.replace('<', '&lt;').replace('>', '&gt;')))
        f.close()
        webbrowser.open(start_page)
    app.can_quit = not cfg.deploy.debug and cfg.deploy.passthrough_errors
    app.run(host=cfg.deploy.host, port=cfg.deploy.port,
            debug=cfg.deploy.debug, passthrough_errors=cfg.deploy.passthrough_errors)
