#!/usr/bin/env python

import hashlib
import re

from google.appengine.ext import ndb


"""
This file contains utilities for working with "aliased urls." Think of aliased
urls as being like short urls from bit.ly, but without the HTTP redirect.
Instead, accessing an aliased url will directly return the response created by
calling the original url's handler with the original url.

The reason why this facility exists at all is that App Engine has trouble with
urls longer than ~2000 characters in length. When encountering a very long url
it will throw a HTTP 414 "Request URI too long" error. To work around this, one
can create an aliased url for the long url and use that instead. App Engine
won't choke, and the long url's handler will be called with the original path,
query string, et cetera.
"""


class AliasedUrl(ndb.Model):
    _use_memcache = False
    PATH_REGEX = re.compile("^/a/(.*)$")
    long_url = ndb.TextProperty(indexed=False)

    @staticmethod
    def from_long_url(long_url):
        """Generate and return a short url that aliases a long url.

        The short url is also the entity's key. There is potential for an
        MD5 collision here but in practice the probability of that happening
        is extremely small since we don't anticipate creating a lot of these
        entities.
        """
        url_hash = hashlib.md5(long_url).hexdigest()
        short_url = "/a/%s" % url_hash
        AliasedUrl(id=short_url, long_url=long_url).put()
        return short_url

    @staticmethod
    def to_long_url(short_url):
        """Look up and return the canonical long url for a short url.

        Returns None if the provided short_url doesn't exist.
        """
        entity = AliasedUrl.get_by_id(short_url)
        return entity.long_url if entity else None


class AliasedUrlMiddleware(object):
    """Middleware that expands aliased urls.

    This is accomplished without issuing a HTTP redirect by rewriting the
    PATH_INFO environment variable, which is defined in the WSGI specification
    as the incoming request's path (example: "/foo/bar"). By rewriting
    PATH_INFO at the middleware level, we are presenting the app with the
    original long url. webapp2 then dispatches the request to the correct
    handler and returns that handler's response.

    When a request arrives at a RequestHandler by way of an aliased url, the
    handler can access information about the "real" url through the
    self.request object's interface: http://docs.webob.org/en/latest/reference.html#method-url
    """

    def __init__(self, app):
        self.app = app

    def __call__(self, environ, start_response):
        """Look for and expand short urls in incoming requests.

        Rewrites the PATH_INFO environment variable with the canonical url
        if one exists for this short url.
        """
        current_url = environ["PATH_INFO"]
        if AliasedUrl.PATH_REGEX.match(current_url):
            long_url = AliasedUrl.to_long_url(current_url)
            if long_url:
                environ["PATH_INFO"] = long_url
        return self.app(environ, start_response)
