# nghttp2 - HTTP/2.0 C Library

# Copyright (c) 2013 Tatsuhiro Tsujikawa

# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:

# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.

# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
import io
import sys
from urllib.parse import urlparse

import nghttp2

def _dance_decode(b):
    # TODO faster than looping through and mod-128'ing all unicode points?
    return b.decode('utf-8').encode('latin1').decode('latin1')

class WSGIContainer(nghttp2.BaseRequestHandler):

    _BASE_ENVIRON = {
        'wsgi.version': (1,0),
        'wsgi.url_scheme': 'http', # FIXME
        'wsgi.multithread': True, # TODO I think?
        'wsgi.multiprocess': False, # TODO no idea
        'wsgi.run_once': True, # TODO now I'm just guessing
        'wsgi.errors': sys.stderr, # TODO will work for testing - is this even used by any frameworks?
    }

    def __init__(self, app, *args, **kwargs):
        super(WSGIContainer, self).__init__(*args, **kwargs)
        self.app = app
        self.chunks = []

    def on_data(self, chunk):
        self.chunks.append(chunk)

    def on_request_done(self):
        environ = WSGIContainer._BASE_ENVIRON.copy()
        parsed = urlparse(self.path)

        environ['wsgi.input'] = io.BytesIO(b''.join(self.chunks))

        for name, value in self.headers:
            mangled_name = b'HTTP_' + name.replace(b'-', b'_').upper()
            environ[_dance_decode(mangled_name)] = _dance_decode(value)

        environ.update(dict(
            REQUEST_METHOD=_dance_decode(self.method),
            # TODO SCRIPT_NAME? like APPLICATION_ROOT in Flask...
            PATH_INFO=_dance_decode(parsed.path),
            QUERY_STRING=_dance_decode(parsed.query),
            CONTENT_TYPE=environ.get('HTTP_CONTENT_TYPE', ''),
            CONTENT_LENGTH=environ.get('HTTP_CONTENT_LENGTH', ''),
            SERVER_NAME=_dance_decode(self.host),
            SERVER_PORT='', # FIXME probably requires changes in nghttp2
            SERVER_PROTOCOL='HTTP/2.0',
        ))

        response_status = [None]
        response_headers = [None]
        response_chunks = []

        def start_response(status, headers, exc_info=None):
            if response_status[0] is not None:
                raise AssertionError('Response already started')
            exc_info = None # avoid dangling circular ref - TODO is this necessary? borrowed from snippet in WSGI spec

            response_status[0] = status
            response_headers[0] = headers
            # TODO handle exc_info

            return lambda chunk: response_chunks.append(chunk)

        # TODO technically, this breaks the WSGI spec by buffering the status,
        # headers, and body until all are completely output from the app before
        # writing the response, but it looks like nghttp2 doesn't support any
        # other way for now

        # TODO disallow yielding/returning before start_response is called
        response_chunks.extend(self.app(environ, start_response))
        response_body = b''.join(response_chunks)

        # TODO automatically set content-length if not provided
        self.send_response(
            status=response_status[0],
            headers=response_headers[0],
            body=response_body,
        )

def wsgi_app(app):
    return lambda *args, **kwargs: WSGIContainer(app, *args, **kwargs)


if __name__ == '__main__':
    import ssl
    from werkzeug.testapp import test_app

    ssl_ctx = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
    ssl_ctx.options = ssl.OP_ALL | ssl.OP_NO_SSLv2
    ssl_ctx.load_cert_chain('server.crt', 'server.key')

    server = nghttp2.HTTP2Server(('127.0.0.1', 8443), wsgi_app(test_app),
                                 ssl=ssl_ctx)
    server.serve_forever()