import json
import random
import string

from twisted.internet import protocol, defer

from cyclone.web import RequestHandler, Application

from twisted.protocols import policies, basic
from twisted.web.http import Request

from oonib import log, randomStr


class SimpleHTTPChannel(basic.LineReceiver, policies.TimeoutMixin):
    """
    This is a simplified version of twisted.web.http.HTTPChannel to overcome
    header lowercase normalization. It does not actually implement the HTTP
    protocol, but only the subset of it that we need for testing.

    What this HTTP channel currently does is process the HTTP Request Line and
    the Request Headers and returns them in a JSON datastructure in the order
    we received them.

    The returned JSON dict looks like so:

    {
        'request_headers':
            [['User-Agent', 'IE6'], ['Content-Length', 200]]
        'request_line':
            'GET / HTTP/1.1'
    }
    """
    requestFactory = Request
    __first_line = 1
    __header = ''
    __content = None

    length = 0
    maxHeaders = 500
    maxHeaderLineLength = 16384
    requestLine = ''

    timeOut = 60 * 60 * 12

    def __init__(self):
        self.headers = []
        self.requests = []

    def connectionMade(self):
        self.setTimeout(self.timeOut)

    def lineReceived(self, line):
        if (len(self.__header) + len(line)) >= self.maxHeaderLineLength \
                and not self.__first_line:
            log.err("Maximum header length reached.")
            return self.transport.loseConnection()

        if self.__first_line:
            self.requestLine = line
            self.__first_line = 0
        elif line == '':
            # We have reached the end of the headers.
            if self.__header:
                self.headerReceived(self.__header)
            self.__header = ''
            self.allHeadersReceived()
            self.setRawMode()
        elif line[0] in ' \t':
            # This is to support header field value folding over multiple lines
            # as specified by rfc2616.
            self.__header += '\n'+line
        else:
            if self.__header:
                self.headerReceived(self.__header)
            self.__header = line

    def headerReceived(self, line):
        try:
            header, data = line.split(':', 1)
            self.headers.append((header, data.strip()))
        except:
            log.err("Got malformed HTTP Header request field")
            log.err("%s" % line)

        if len(self.headers) >= self.maxHeaders:
            log.err("Maximum number of headers received.")
            self.closeConnection()

    def allHeadersReceived(self):
        headers_dict = {}
        for k, v in self.headers:
            if k not in headers_dict:
                headers_dict[k] = []
            headers_dict[k].append(v)

        response = {
            'request_headers': self.headers,
            'request_line': self.requestLine,
            'headers_dict': headers_dict
        }
        json_response = json.dumps(response)
        self.transport.write('HTTP/1.1 200 OK\r\n\r\n')
        self.transport.write('%s' % json_response)

        self.closeConnection()

    def closeConnection(self):
        if self._TimeoutMixin__timeoutCall and \
                not self._TimeoutMixin__timeoutCall.called:
            self._TimeoutMixin__timeoutCall.cancel()
            self._TimeoutMixin__timeoutCall = None
        self.transport.loseConnection()


class HTTPReturnJSONHeadersHelper(protocol.ServerFactory):
    protocol = SimpleHTTPChannel

    def buildProtocol(self, addr):
        return self.protocol()


class HTTPTrapAll(RequestHandler):
    def _execute(self, transforms, *args, **kwargs):
        self._transforms = transforms
        defer.maybeDeferred(self.prepare).addCallbacks(
            self._execute_handler,
            lambda f: self._handle_request_exception(f.value),
            callbackArgs=(args, kwargs))

    def _execute_handler(self, r, args, kwargs):
        if not self._finished:
            args = [self.decode_argument(arg) for arg in args]
            kwargs = dict((k, self.decode_argument(v, name=k))
                          for (k, v) in kwargs.iteritems())

            # This is where we do the patching
            # XXX this is somewhat hackish
            d = defer.maybeDeferred(self.all, *args, **kwargs)
            d.addCallbacks(self._execute_success, self._execute_failure)
            self.notifyFinish().addCallback(self.on_connection_close)


class HTTPRandomPage(HTTPTrapAll):
    """
    This generates a random page of arbitrary length and containing the string
    selected by the user.
    /<length>/<keyword>
    XXX this is currently disabled as it is not of use to any test.
    """
    isLeaf = True

    def _gen_random_string(self, length):
        return ''.join(random.choice(string.letters) for x in range(length))

    def genRandomPage(self, length=100, keyword=None):
        data = self._gen_random_string(length/2)
        if keyword:
            data += keyword
        data += self._gen_random_string(length - length/2)
        data += '\n'
        return data

    def all(self, length, keyword):
        self.set_header('Content-Disposition', 'attachment; filename="%s.txt"' % randomStr(10))
        length = 100
        if length > 100000:
            length = 100000
        self.write(self.genRandomPage(length, keyword))

HTTPRandomPageHelper = Application([
    # XXX add regexps here
    (r"/(.*)/(.*)", HTTPRandomPage)
])
