"""HTTP Messages

This modules contains classes for representing HTTP responses and requests.
"""

import re

import encodings
import regexes as r
import weblogging as logging

reasondict = {
    100 : 'Continue',
    101 : 'Switching Protocols',
    200 : 'OK',
    201 : 'Created',
    202 : 'Accepted',
    203 : 'Non-Authoritative Information',
    204 : 'No Content',
    205 : 'Reset Content',
    206 : 'Partial Content',
    300 : 'Multiple Choices',
    301 : 'Moved Permanently',
    302 : 'Found',
    303 : 'See Other',
    304 : 'Not Modified',
    305 : 'Use Proxy',
    307 : 'Temporary Redirect',
    400 : 'Bad Request',
    401 : 'Unauthorized',
    402 : 'Payment Required',
    403 : 'Forbidden',
    404 : 'Not Found',
    405 : 'Method Not Allowed',
    406 : 'Not Acceptable',
    407 : 'Proxy Authentication Required',
    408 : 'Request Time-out',
    409 : 'Conflict',
    410 : 'Gone',
    411 : 'Length Required',
    412 : 'Precondition Failed',
    413 : 'Request Entity Too Large',
    414 : 'Request-URI Too Large',
    415 : 'Unsupported Media Type',
    416 : 'Requested range not satisfiable',
    417 : 'Expectation Failed',
    500 : 'Internal Server Error',
    501 : 'Not Implemented',
    502 : 'Bad Gateway',
    503 : 'Service Unavailable',
    504 : 'Gateway Time-out',
    505 : 'HTTP Version not supported',
}


class Message(object):
    """Class that stores an HTTP Message"""

    def __init__(self):
        """Initialize the Message"""
        self.version = "HTTP/1.1"
        self.body = ""
        self.headers = dict()
        
    def set_header(self, name, value):
        """Add a header and its value
        
        Args:
            name (str): name of header
            value (str): value of header
        """
        self.headers[name] = str(value)
        
    def get_header(self, name):
        """Get the value of a header
        
        Args:
            name (str): name of header

        Returns:
            str: value of header, empty if header does not exist
        """
        if name in self.headers:
            return self.headers[name]
        else:
            return None
    
    def parse_headers(self, msg):
        for name, value in re.findall(r.MessageHeader, msg):
            self.set_header(name, value.strip())
            logging.debug("%s: %s" % (name, value.strip()))

    def startline(self):
        return ''

    def __str__(self):
        """Convert the Message to a string
        
        Returns:
            str: representation the can be sent over socket
        """
        msg = ''
        msg += '%s\r\n' % self.startline()
        msg += '\r\n'.join([k + ": " + v for k, v in self.headers.iteritems()])
        msg += '\r\n\r\n' + self.body
        return msg

    def __eq__(self, other):
        return self.__dict__ == other.__dict__


class Request(Message):
    """Class that stores an HTTP request"""

    def __init__(self):
        """Initialize the Request"""
        super(Request, self).__init__()
        self.method = ""
        self.uri = ""

    def parse(self, msg):
        [reqline, rest] = msg.split('\r\n', 1)
        reqline = re.match(r.RequestLine, reqline + '\r\n')
        self.method, self.uri, self.version = reqline.groups()

        [headers, body] = rest.split('\r\n\r\n', 1)
        self.parse_headers(headers)
        self.body = body

    def encodings(self):
        requested = self.get_header('Accept-Encoding')
        if requested == None:
            return encodings.all

        encs = []
        requested = re.split(r.EncodingSplit, requested)
        for value in requested:
            try:
                match = re.match(r.AcceptEncodingValue, value)
                if match == None:
                    return [encodings.IDENTITY]
                enc, q = match.groups()
                # Unclear what should happen when some qvalues are omitted
                q = q or '0.001'
                if enc == '*':
                    for enc in encodings.all:
                        encs.append((encodings.get(enc), float(q)))
                else:
                    encs.append((encodings.get(enc), float(q)))
            except encodings.UnknownEncodingError:
                pass

        rejected = [e[0] for e in encs if e[1] == 0]
        accepted = [e for e in encs if e[1] != 0]
        
        if not encodings.IDENTITY in rejected + [a[0] for a in accepted]:
            accepted.append((encodings.IDENTITY, 0.001))

        accepted.sort(key=lambda x:x[1], reverse=True)
        return [a[0] for a in accepted]
        
    def startline(self):
        return "%s %s %s" % (self.method, self.uri, self.version)
        
    def __str__(self):
        """Convert the Request to a string

        Returns:
            str: representation the can be sent over socket
        """
        return super(Request, self).__str__()
        

class Response(Message):
    """Class that stores an HTTP Response"""

    def __init__(self):
        """Initialize the Response"""
        super(Response, self).__init__()
        self.set_header('Server', 'WebPy')

    def parse(self, msg):
        [respline, rest] = msg.split('\r\n', 1)
        respline = re.match(r.StatusLine, respline + '\r\n')
        self.version = respline.group(1)
        self.code = int(respline.group(2))
        
        [headers, body] = rest.split('\r\n\r\n', 1)
        self.parse_headers(headers)
        self.body = body

    def startline(self):
        return "%s %d %s" % (self.version, self.code, reasondict[self.code])

    def set_content_length(self):
        self.set_header('Content-Length', len(self.body))

    def decompress(self):
        self.body = encodings.decode(
                self.get_header('Content-Encoding'), self.body)
    
    def __str__(self):
        """Convert the Response to a string

        Returns:
            str: representation the can be sent over socket
        """
        return super(Response, self).__str__()