# -*- coding: utf-8 -*-
import re
import os
import datetime
import json
import operator
import urllib
import xml.sax.saxutils as saxutils
import uuid

import webapp2
import logging
from google.appengine.api import users

from pseudo_db import PseudoDB, DBUpdater, UpdateThumbs, Views, Sources, DBError
from common import Jinja, Shortener


class Redirection:
    def __init__(self, url):
        self.url = url


class Page:
    page_size = {
            'admin': 50,
            'gallery': 100,
            }

    _accepted_vars = ['order', 'sort_by', 'since', 'until', 'names', 'tags',
                      'source', 'hosts', 'limit', 'tags_op']
    # vars that go into links to other blog pages
    _clean_vars = ['order', 'sort_by', 'since', 'until', 'names', 'tags',
                   'source', 'hosts', 'limit', 'tags_op']

    _extra_hosts_regex = re.compile('&.*$')

    @staticmethod
    def get(page_number=None, tags=None, dates=None, source=None,
            page_type=None, params=None, _db=None, _autoplay_video=None,
            _update_url=None, _is_random=None):

        if params is None:
            params = {}

        # default to the gallery
        if not page_type:
            page_type = 'gallery'

        if not page_number:
            page_number = 0
        else:
            page_number = int(page_number)

        if _db:
            db = _db
        else:
            db = PseudoDB()

        # add no_video to watch urls if the user asked for it
        # (only affects the gallery)
        add_no_video = False
        for var in ['links', 'no_video']:
            if var in params:
                add_no_video = True
                del params[var]

        # autoplay videos if user requested it
        autoplay = False
        for var in ['autoplay', 'videos']:
            if var in params:
                autoplay = True
                del params[var]

        if _is_random:
            params['order'] = 'random'
            params['limit'] = '20'
            autoplay = True

        # set which video to autoplay, if available
        autoplay_video = None
        if _autoplay_video:
            autoplay = True
            autoplay_video = _autoplay_video

        new_location = None
        if _update_url:
            new_location = _update_url

        # prepare query parameters
        path_args = {'tags': tags, 'dates': dates, 'source': source}
        params = Page.merge_arguments(path_args, params)
        params = PseudoDB.get_filter_params(params, strict=False,
                                            accepted_vars=Page._accepted_vars)

        # select only one page
        page_size = Page.page_size[page_type]
        # if user set a limit, try to use that as page size
        if 'limit' in params and params['limit']:
            try:
                page_size = int(params['limit'])
            except ValueError:
                pass
        params['offset'] = page_size*page_number
        params['limit'] = page_size

        entries, has_more = db.query(**params)

        # build urls for links in the page
        page_links = Page.get_page_links(page_number, page_type, params, has_more)

        # fix source (add text if only url is available), except if this is the
        # admin page
        if page_type != 'admin':
            Page.fix_sources(entries)

        # set page title, when applicable
        page_title = None
        if params['tags']:
            page_title = ', '.join(params['tags']).replace('_', ' ')
        elif params['source'] and entries:
            page_title = entries[0]['source_text']
        elif params['hosts']:
            hosts_with_slashes = [u'{}/'.format(host) for host in params['hosts']]
            page_title = ', '.join(hosts_with_slashes)
        elif params['since'] or params['until']:
            dates = []
            for key in ['since', 'until']:
                if params[key]:
                    dates.append(u'{} {}'.format(key, params[key]))
            page_title = ', '.join(dates)

        # for admin page, show check all checkbox if the entries have been
        # filtered in any way
        show_check_all = params['_filtered']

        template_values = {
            'entries': entries,
            'tags': tags,
            'page_title': page_title,
            'page_links': page_links,
            'add_no_video': add_no_video,
            'autoplay': autoplay,
            'autoplay_video': autoplay_video,
            'new_location': new_location,
            'show_check_all': show_check_all,
        }

        if page_type == 'admin':
            template = 'blog/admin/list'
        else:
            template = 'blog/' + page_type

        return (template, template_values)

    @staticmethod
    def merge_arguments(path, params):
        '''Marge tags and dates specified via the path with those specified via
        parameters.

        In the case of dates, only the first date will be taken into
        consideration. In other words, we keep backward compatibility for
        /YYMMDD/ paths, but not for /YYMMDD,YYMMDD,...
        '''

        if 'tags' in path and path['tags']:
            if not ('tags' in params and params['tags']):
                params['tags'] = path['tags']
            else:
                # split both lists, add non duplicate value and rejoin
                tags = params['tags'].split(',')
                for tag in path['tags'].split(','):
                    if not tag in tags:
                        tags.append(tag)
                params['tags'] = ','.join(tags)

        if 'dates' in path and path['dates']:
            dates = path['dates'].split(',')
            date = dates[0]

            # generate since and to
            since = datetime.datetime.strptime(date, '%y%m%d')
            until = since + datetime.timedelta(days=1)

            # a date in path overrides any parameters which may have been passed
            params['since'] = str(since)
            params['until'] = str(until)

        if 'source' in path and path['source']:
            params['source'] = path['source']

        return params

    @staticmethod
    def _get_page_base_path(page_type):
        # since gallery is the default, there's no need to include it in
        # the link
        if page_type == 'gallery':
            return '/blog/'
        return '/blog/{}/'.format(page_type)

    @staticmethod
    def get_page_links(page_number, page_type, params, has_more):
        '''Builds urls for each page type, and also to the previous and the
        current page numbers.

        Notice that in the links to other types we porpusefully skip the page
        number. The reason for this is that pages have different sizes for
        different page types, so in order to include it we'd have to calculate
        the appropriate page number for each, and they still wouldn't match each
        other, so it's not worth the trouble.
        '''

        page_links = {}

        # default values
        default_values = {
                'order': 'descending',
                'sort_by': 'posted_at',
                'limit': Page.page_size[page_type],
                }

        # select only params that contain a useful value
        link_params = PseudoDB.get_clean_params(params, Page._clean_vars, default=default_values)
        path_params = ''
        if link_params:
            path_params = '?{}'.format(Page.urlencode(link_params))

        # create link for each page type
        for _type in ['admin', 'gallery', 'rss']:
            # don't include link to admin page for regular users
            if _type == 'admin' and (not users.is_current_user_admin()):
                continue

            # don't include a link to the current page
            if _type == page_type:
                page_links[_type] = None
            else:
                base_path = Page._get_page_base_path(_type)
                page_links[_type] = base_path + path_params

        # if we're in the gallery without any params, use /blog/page/ to help
        # robots efficiently index all videos
        if page_type == 'gallery' and not path_params:
            page_path = '/blog/page/{page}'
        else:
            base_path = Page._get_page_base_path(page_type)
            page_path = base_path + '{page}'

        # only show page links if there is more than one page total
        if page_number > 0 or has_more:
            page_links['prev_page'] = page_links['next_page'] = None
            page_links['top_page'] = page_path.format(page=0) + path_params
            if page_number > 0:
                page_links['prev_page'] = page_path.format(page=page_number-1) + path_params
            if has_more:
                page_links['next_page'] = page_path.format(page=page_number+1) + path_params

        return page_links

    @staticmethod
    def fix_sources(entries):
        '''Make sure sources have text'''
        for entry in entries:
            # make sure all sources with urls also have text
            if entry['source_url'] and (not entry['source_text']):
                entry['source_text'] = entry['source_url']

    @staticmethod
    def urlencode(params):
        params = {var: Jinja.fix_urllib_str(params[var]) for var in params}
        return urllib.urlencode(params)

    @staticmethod
    def _redirect_to_page_containing(entry, db):
        '''Redirects to blog page cointaining the video and autoplays the video.

        If video source contains more than one video, then the source page will
        be used, otherwise we will try to pick the most specific tag that
        contains more than one video (and if there is none, fallback to the
        source)
        '''

        shortlink = entry['source_shortlink']
        url_by_source = '/blog/{}'.format(shortlink)
        if len(db.sources.get_videos(shortlink)) > 1:
            return Page.get(source=shortlink, _db=db,
                        _autoplay_video=entry['name'], _update_url=url_by_source)

        # try to find the most specific tag (the one with the fewest videos, but
        # still more than one)
        #
        # note that tags that cover more than one page are unnacceptable because
        # the video might not be on the first page
        best_tag, best_tag_len = None, None
        if db.tags_idx:
            for tag in entry['tags']:
                tag_len = len(db.tags_idx[tag])
                if (tag_len > 1 and
                        tag_len < Page.page_size['gallery'] and
                        (tag_len < best_tag_len or best_tag_len is None)):
                    best_tag = tag
                    best_tag_len = tag_len

        if best_tag:
            logging.debug('source only has one video, using tag ‘{}’ instead'.format(best_tag))
            url_by_tag = '/blog/tag/{}'.format(best_tag)
            return Page.get(tags=best_tag, _db=db,
                        _autoplay_video=entry['name'], _update_url=url_by_tag)

        # no appropriate tag found, fallback to source
        return Page.get(source=shortlink, _db=db,
                    _autoplay_video=entry['name'], _update_url=url_by_source)

    @staticmethod
    def redirect(video=None, params=None):
        '''Check if video is in the blog and either redirect to new url scheme
        (/#ab/cde) or to an appropriate blog page containing the video.'''

        if not video:
            return Redirection('/')

        # check that video is a valid shortenable or shortened url
        shortened_url = None
        expanded_urls = Shortener.expand_urls(video)
        if expanded_urls:
            shortened_url = Shortener.shorten_urls(expanded_urls)

        if shortened_url:
            # drop extra hosts if they exist (i.e. ab/cde,fgh&ij/klm -> ab/cde,fgh)
            # we have to do this because the front page doesn't understand lists
            # with multiple hosts
            shortened_url = Page._extra_hosts_regex.sub('', shortened_url)

            # check if video is in the DB
            db = PseudoDB()

            entries, has_more = db.query(names=[shortened_url], expand=True)
            if entries:
                return Page._redirect_to_page_containing(entries[0], db)

            # if video is not in the blog, redirect to the reshortened url, so
            # we don't forward urls like /ab/cde.mp4&ab/cde.webm and so on
            return Redirection('/#{}'.format(shortened_url))

        # video not shortenable, just redirect as is
        return Redirection('/#{}'.format(video))


class Json:
    '''API for retrieving video information from the DB'''

    @staticmethod
    def get(params=None):
        # set filter parameters based on variables in the request
        # do this now so we can fail quickly before retrieving any entries
        try:
            params = PseudoDB.get_filter_params(params)
        except ValueError as e:
            return_dict = {'success': False, 'error': unicode(e), 'entries': None}
            return json.dumps(return_dict, indent=2)

        db = PseudoDB()
        entries, has_more = db.query(**params)

        return_dict = {'success': True, 'error': None, 'entries': entries}
        return json.dumps(return_dict, indent=2)

    @staticmethod
    def post(db_action=None, params=None):
        view_count = 0
        if db_action == 'count_view':
            if not ('name' in params and params['name']):
                webapp2.abort(404)

            view_count = Views().increment_video_views(params['name'])

            if not view_count:
                return_dict = {'success': False, 'error': 'video not found: {}'.format(params['name'])}
                return json.dumps(return_dict, indent=2)
        else:
            webapp2.abort(404)

        return_dict = {'success': True, 'error': None, 'views': view_count}
        return json.dumps(return_dict, indent=2)


class Admin:
    '''Pages for administering the DB'''
    _aliases = {
            'add' : 'add_entries',
            'post' : 'add_entries',
            }

    # note that the page for editing entries is handled by Page, not Admin
    _pages = [
            'add_entries',
            'replace_entries',
            ]

    # pages that shouldn't normally be available in production
    _development_only_pages = [
            'replace_entries',
            ]

    _admin_url = '/blog/admin'

    @staticmethod
    def get(page=None, params=None):
        # replace aliases with the original page
        if page in Admin._aliases:
            page = Admin._aliases[page]

        if not page in Admin._pages:
            webapp2.abort(404)

        # disable restricted pages in production server
        # comment this out if there's a need to use a restricted page
        if Admin.is_page_restricted(page):
            webapp2.abort(403)

        return ('blog/admin/{}'.format(page), {})

    @staticmethod
    def post(db_action=None, params=None):
        if db_action == 'add_entries':
            return AddEntries.post(params)
        elif db_action == 'update_entries':
            return UpdateEntries.post(params)
        elif db_action == 'update_thumbs':
            return UpdateThumbs.post(params)
        elif db_action == 'replace_entries':
            return ReplaceEntries.post(params)
        else:
            webapp2.abort(404)

    @staticmethod
    def db_fixer(db_action=None, params=None):
        '''Low level functions for debugging and fixing the DB'''
        # disable in production
        # comment this out whenever there's a need
        if not os.environ['SERVER_SOFTWARE'].startswith('Development'):
            webapp2.abort(403)

        if db_action == 'dump':
            return json.dumps(PseudoDB.dump(), indent=2)
        elif db_action == 'low_level_rename':
            return json.dumps(Admin.low_level_rename(params), indent=2)
        elif db_action == 'replace_host':
            return json.dumps(Admin.replace_host(params), indent=2)
        else:
            webapp2.abort(404)

    @staticmethod
    def low_level_rename(params=None):
        if not (params and 'old_name' in params and 'new_name' in params):
            return {'success': False,
                    'error': 'Missing argument "old_name" or "new_name"'}

        db_updater = DBUpdater()
        return db_updater.low_level_rename(params['old_name'], params['new_name'])

    @staticmethod
    def replace_host(params=None):
        if not (params and 'old_name' in params and 'new_name' in params):
            return {'success': False,
                    'error': 'Missing argument "old_name" or "new_name"'}

        db_updater = DBUpdater()
        return db_updater.replace_host(params['old_name'], params['new_name'])

    @staticmethod
    def get_args(params, args):
        '''Prepares arguments for DBUpdater'''

        for key in args:
            if key in params:
                # split lists
                if key in ['videos', 'tags', 'new_names']:
                    # no need to strip() here since split() will already get rid
                    # of any whitespace
                    args[key] = params[key].split()
                else:
                    args[key] = params[key].strip()

        return args

    @staticmethod
    def is_page_restricted(page):
        '''Checks if page is restricted in this environment.'''
        # if in production
        if not os.environ['SERVER_SOFTWARE'].startswith('Development'):

            if page in Admin._development_only_pages:
                return True

        return False


class ReplaceEntries:
    '''Updates DB replacing all entries'''

    @staticmethod
    def post(params):
        if not 'entries' in params:
            return_dict = {'success': False, 'error': 'missing required key "entries"'}
            return json.dumps(return_dict, indent=2)

        entries_json = params['entries']

        db_updater = DBUpdater()

        try:
            entries = json.loads(entries_json)

            if isinstance(entries, dict):
                entries = entries['entries']
            if not isinstance(entries, list):
                raise ValueError('Expected a list of entries')

            db_updater.replace_entries(entries=entries)
        except (DBError, ValueError, KeyError) as e:
            return_dict = {'success': False, 'error': unicode(e)}
            return json.dumps(return_dict, indent=2)

        return_dict = {'success': True, 'error': None}
        return json.dumps(return_dict, indent=2)


class AddEntries:
    '''Updates DB adding new videos'''

    _args = {
            'videos': None,
            'source_url': None,
            'source_text': None,
            'tags': None,
            'posted_at': None,
            }

    @staticmethod
    def post(params):
        args = Admin.get_args(params, AddEntries._args)

        # add entries
        db_updater = DBUpdater()

        try:
            added = db_updater.add_entries(**args)
        except DBError as e:
            return_dict = {'success': False, 'error': unicode(e)}
            return json.dumps(return_dict, indent=2)

        # build admin page url for the new entries
        added_str = Jinja.fix_urllib_str(PseudoDB.names_list_separator.join(added))
        admin_page_args = { 'names': added_str }
        admin_url = '{}?{}'.format(Admin._admin_url, urllib.urlencode(admin_page_args))

        return_dict = {'success': True, 'error': None, 'admin_url': admin_url}
        return json.dumps(return_dict, indent=2)


class UpdateEntries:
    '''Updates DB modifying entries'''

    _args = {
            'db_action': None,
            'videos': None,
            'source_url': None,
            'source_text': None,
            'tags': None,
            'new_names': None,
            }

    @staticmethod
    def post(params):
        args = Admin.get_args(params, UpdateEntries._args)

        # in the case of renaming, check if we have the correct amount of names,
        # so we can fail before pulling anything from the db if not
        if args['db_action'] == 'rename_videos' and (len(args['videos']) != len(args['new_names'])):
            error_str = '{} videos selected, but {} new names provided'.format(
                    len(args['videos']),
                    len(args['new_names'])
                    )
            return_dict = {'success': False, 'error': error_str}
            return json.dumps(return_dict, indent=2)

        # update entries
        db_updater = DBUpdater()

        try:
            db_updater.update_entries(**args)
        except DBError as e:
            return_dict = {'success': False, 'error': unicode(e)}
            return json.dumps(return_dict, indent=2)

        return_dict = {'success': True, 'error': None}
        return json.dumps(return_dict, indent=2)


class Logger:
    '''Logs requests. Meant for debugging UpdateThumbs.'''

    @staticmethod
    def get(params):
        logging.debug('logger: got arguments: {}'.format(params))


class Tag:
    '''List of all tags in DB'''

    @staticmethod
    def get(params=None):
        db = PseudoDB()
        tags_list = []
        # take the opportunity to purge empty tags, even though this shouldn't
        # normally be happening
        tags_to_purge = []

        for tag_name in db.tags_idx:
            if len(db.tags_idx[tag_name]) > 0:
                tags_list.append((tag_name, len(db.tags_idx[tag_name])))
            else:
                tags_to_purge.append(tag_name)

        if tags_to_purge:
            logging.debug('purging empty tags: {}'.format(tags_to_purge))
            for tag_name in tags_to_purge:
                del db.tags_idx[tag_name]
            db.store_tags_index()

        # sort by name, then number of videos (i.e. group primarily by number of videos, then by
        # name)
        # reverse both times so that in the end names will be ascending and number of videos
        # descending
        tags_list = sorted(tags_list, key=operator.itemgetter(0))
        tags_list.reverse()
        tags_list = sorted(tags_list, key=operator.itemgetter(1))
        tags_list.reverse()

        template_values = {
                'tag_pairs' : tags_list,
                }
        return ('blog/tags', template_values)

class SourcesList:
    '''List of all sources in DB'''
    @staticmethod
    def get(params=None):
        db = PseudoDB()
        videos_dict = PseudoDB.list2ordered_dict(db.videos)

        sources = Sources()
        shortlinks = sources.get_all_shortlinks()

        sources_list = []
        for shortlink in shortlinks:
            names = sources.get_videos(shortlink)
            # should never happen
            if not names:
                console.log('got empty shortlink: {}'.format(shortlink))

            entry = videos_dict[names[0]]

            source_dict = {
                    'shortlink': shortlink,
                    'text': entry['source_text'],
                    'url' : entry['source_url'],
                    'num_videos': len(names),
                    }
            sources_list.append(source_dict)

        # sort by text, then number of videos (i.e. group primarily by number of
        # videos, then by text)
        # reverse both times so that in the end text will be ascending and
        # number of videos descending
        sources_list = sorted(sources_list, key=operator.itemgetter('text'))
        sources_list.reverse()
        sources_list = sorted(sources_list, key=operator.itemgetter('num_videos'))
        sources_list.reverse()

        template_values = {
                'sources' : sources_list,
                }
        return ('blog/sources', template_values)


class RSS:
    '''RSS feeds'''

    _accepted_vars = ['order', 'since', 'until', 'offset', 'limit', 'names',
                      'tags', 'source', 'hosts', 'tags_op']

    _blog_url = 'https://loopvid.appspot.com/blog'

    default_size = 50

    @staticmethod
    def get(obj, params=None):

        db = PseudoDB()

        default_params = {'limit': RSS.default_size}
        params = PseudoDB.get_filter_params(params, defaults=default_params,
                strict=False, accepted_vars=RSS._accepted_vars)

        # expand entries because we want the thumbnail
        params['expand'] = True

        entries, has_more = db.query(**params)

        Page.fix_sources(entries)

        # generate RSS fields for each entry
        for entry in entries:
            values = {
                'blog_url': RSS._blog_url,
                'entry': entry,
                }
            description = Jinja.render(obj, 'blog/rss/description', values)
            entry['description'] = saxutils.escape(description)

            entry['guid'] = RSS.guid(entry['watch_url'])
            entry['rfc822_date'] = RSS.rfc822_date(entry['posted_at'])

        values = {
                'blog_url': RSS._blog_url,
                'entries': entries,
                }

        return ('blog/rss/feed', values)

    @staticmethod
    def rfc822_date(date_str):
        '''Convert post time to RFC822 format'''
        date = datetime.datetime.strptime(date_str, '%Y-%m-%d %H:%M:%S.%f')
        return date.strftime('%a, %d %b %Y %H:%M:%S GMT')

    @staticmethod
    def guid(url):
        '''Generate GUID based on an entry's url'''
        # uuid suffers from the same unicode issues as urllib
        url = Jinja.fix_urllib_str(url)

        guid = uuid.uuid5(uuid.NAMESPACE_URL, url)
        return unicode(guid)


class Hosts:
    @staticmethod
    def get(host=None, params=None):
        # most hosts aren't posted to the blog, so we shouldn't confuse the user
        # redirecting him to a blank page
        if not host in DBUpdater.allowed_hosts:
            webapp2.abort(404)

        params['hosts'] = host
        return Page.get(params=params)


class Router(webapp2.RequestHandler):
    '''Routes requests to the appropriate handler classes.

    This is a proper-regex-capable replacement for webapp2's terrible routing.
    '''

    _double_slash_regex = re.compile('/{2,}')

    _handlers = [
            # blog page

            # notice /blog/list will redirect to /blog (i.e. the gallery) to
            # maintain compatibility with old links

            # source
            # note that source must come first because sources with only numbers
            # look like page numbers
            (re.compile(r'^/blog(?:/(?:gallery|videos|list))?/(?P<source>[0-9A-Fa-f]{8})/?$'), Page.get),

            # tags before dates
            (re.compile(r'^/blog(?:/(?:gallery|videos|list))?(?:/tag/(?P<tags>[^/]+))?(?:/(?P<dates>\d{6}(?:,\d{6})*))?(?:/(?P<page_number>\d+))?/?$'), Page.get),
            # dates before tags
            (re.compile(r'^/blog(?:/(?:gallery|videos|list))?(?:/(?P<dates>\d{6}(?:,\d{6})*))?(?:/tag/(?P<tags>[^/]+))?(?:/(?P<page_number>\d+))?/?$'), Page.get),

            # dedicated page number url
            (re.compile(r'^/blog/page/(?P<page_number>\d+)/?$'), Page.get),
            # dedicated random page
            (re.compile(r'^/blog/(?P<_is_random>random)/$'), Page.get),

            # admin
            (re.compile(r'^/blog/(?P<page_type>admin)(?:/list)?(?:/(?P<page_number>\d+))?/?$'), Page.get),
            (re.compile(r'^/blog/admin/(?P<page>[^/]+)/?$'), Admin.get),
            (re.compile(r'^/blog/(?P<page>post)/?$'), Admin.get),
            (re.compile(r'^/blog/json/admin/(?P<db_action>(?:replace|add|update)_entries|update_thumbs)/?$'), Admin.post),
            (re.compile(r'^/blog/json/admin/db_fixer/(?P<db_action>dump|low_level_rename|replace_host)/?$'), Admin.db_fixer),

            # tags
            (re.compile(r'^/blog/tags?/?$'), Tag.get),
            # sources
            (re.compile(r'^/blog/sources/?$'), SourcesList.get),
            # json
            (re.compile(r'^/blog/json/?$'), Json.get),
            (re.compile(r'^/blog/json/(?P<db_action>count_view)/?$'), Json.post),
            # rss
            (re.compile(r'^/blog/rss/?$'), RSS.get),
            # logger
            (re.compile(r'^/blog/log/?$'), Logger.get),
            # redirect
            (re.compile(r'^/(?:blog/)?(?P<host>[a-z][a-z0-9])/?$'), Hosts.get),
            (re.compile(r'^/(?P<video>[a-z][a-z0-9]/.+)$'), Page.redirect),
            ]

    # handlers that return something other than html (for setting Content-Type
    # header)
    _handler_output = {
            Json.get: 'application/json',
            Json.post: 'application/json',
            Admin.post: 'application/json',
            Admin.db_fixer: 'application/json',
            RSS.get: 'text/xml',
            }

    _aliases = {
            'from': 'since',
            'to': 'until',
            'sources': 'source',
            }

    def get(self, path=''):
        self.route(path=path)

    def post(self, path=''):
        self.route(path=path)

    def route(self, path=''):
        '''Routes request to appropriate handler class.'''
        # get rid of double slashes
        path = Router._double_slash_regex.sub('/', path)

        # try all regexes
        handler = None
        for regex, regex_handler in Router._handlers:
            match = regex.match(path)
            if match:
                handler = regex_handler
                break
        if not (match and handler):
            webapp2.abort(404)

        args = match.groupdict()
        args['params'] = self.get_params()

        pargs = []
        # RSS needs the object in order to render templates with Jinja
        if handler == RSS.get:
            pargs = [self]

        # call handler, write output to response
        response = handler(*pargs, **args)

        if isinstance(response, Redirection):
            self.redirect(response.url)

        if handler in Router._handler_output:
            self.response.headers['Content-Type'] = Router._handler_output[handler]

        # if response is a pair (template, values), render it before writing,
        # otherwise write as is
        if isinstance(response, tuple):
            self.response.write(Jinja.render(self, response[0], response[1]))
        elif response is not None:
            self.response.write(response)
        # if response is None, just return

    def get_params(self):
        '''Returns dict with all parameters set by the user.'''
        #return {var: self.request.get(var) for var in self.request.arguments()}
        params = {}
        for var in self.request.arguments():
            value = self.request.get(var)

            # replace aliases with the original key
            if var in Router._aliases:
                var = Router._aliases[var]

            params[var] = value

        return params


routes = [webapp2.Route(r'<path:.*>', handler=Router)]
# only show debug info in development environment
debug = os.environ['SERVER_SOFTWARE'].startswith('Development')
application = webapp2.WSGIApplication(routes, debug=debug)
