# Source code of the Ubuntu-it website
# Copyright (C) 2015-2016  Pietro Albini <pietroalbini@ubuntu.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; witout even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

import datetime
import random
import string

from htmlmin import minify as html_minify
import gunicorn.app.base as baseapp


# check that the html_minify we imported is a real function
# (i.e., no the html_minify that comes from django-htmlmin)
try:
    assert callable(html_minify)
except AssertionError:
    raise ImportError("html_minify is not callable")


def random_key(length):
    """Generate a random key of a given length"""
    rng = random.SystemRandom()  # Use /dev/urandom
    space = string.digits + string.ascii_letters + string.punctuation
    result = ""

    for _ in range(length):
        result += rng.choice(space)

    return result


class GunicornInstance(baseapp.BaseApplication):
    """A gunicorn instance which runs the app we want"""

    def __init__(self, config_file=None, options=None):
        # Initialize the classs
        self.options = options if options is not None else {}
        self.config_file = config_file
        self.app = None

        super(GunicornInstance, self).__init__()

    def load_config(self):
        """Apply the configuration to this gunicorn instance"""
        config = self.options.copy()

        # First of all, load from the configuration file
        if self.config_file is not None:
            from_file = baseapp.Application.get_config_from_filename(
                            object(), self.config_file)
            config.update(from_file)

        for key, value in config.items():
            # Some cleanup
            if key not in self.cfg.settings or value is None:
                continue
            self.cfg.set(key.lower(), value)

    def load(self):
        """Load the application"""
        if self.app is None:
            raise RuntimeError("Application object not set")
        return self.app


class ReverseProxied:
    '''Wrap the application in this middleware and configure the
    front-end server to add these headers, to let you quietly bind
    this to a URL other than / and to an HTTP scheme that is
    different than what is used locally.

    Created by Peter Hansen and released in the public domain.

    In nginx:
    location /myprefix {
        proxy_pass http://192.168.0.1:5001;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Scheme $scheme;
        proxy_set_header X-Script-Name /myprefix;
        }

    :param app: the WSGI application
    '''
    def __init__(self, app):
        self.app = app

    def __call__(self, environ, start_response):
        script_name = environ.get('HTTP_X_SCRIPT_NAME', '')
        if script_name:
            environ['SCRIPT_NAME'] = script_name
            path_info = environ['PATH_INFO']
            if path_info.startswith(script_name):
                environ['PATH_INFO'] = path_info[len(script_name):]

        scheme = environ.get('HTTP_X_SCHEME', '')
        if scheme:
            environ['wsgi.url_scheme'] = scheme
        return self.app(environ, start_response)


def prepare_app(app):
    """Add some utilities to the app"""

    @app.after_request
    def after_request(response):
        if "text/html" in response.content_type:
            content = response.get_data(as_text=True)

            # Minify the HTML
            content = html_minify(content)

            # Generate a random string and append it as the render ID
            randstr = "".join(random.choice(string.ascii_letters) for _ in range(10))
            content = "<!--render:%s-->%s" % (randstr, content)

            response.set_data(content)

        return response

    @app.template_filter("format_timestamp")
    def format_timestamp(timestamp):
        return datetime.datetime.fromtimestamp(
            int(timestamp)
        ).strftime('%d/%m/%Y %H:%M:%S')
