"""httpd.py
"""

import sys
import time
import urllib
import socket
import traceback
import cStringIO
import SocketServer
import BaseHTTPServer

from eventlet import api


USE_ACCESS_LOG = True


class Request(object):
    _responsecode = 200
    _reason_phrase = None
    _request_started = False
    _chunked = False
    def __init__(self, protocol, method, path, headers):
        self.request_start_time = time.time()
        self.site = protocol.server.site
        self.protocol = protocol
        self._method = method
        if '?' in path:
            self._path, self._query = path.split('?', 1)
            self._query = self._query.replace('&amp;', '&')
        else:
            self._path, self._query = path, None
        self._incoming_headers = headers
        self._outgoing_headers = dict()

    def response(self, code, reason_phrase=None):
        """Change the response code. This will not be sent until some
        data is written; last call to this method wins. Default is
        200 if this is not called.
        """
        self._responsecode = code
        self._reason_phrase = reason_phrase
        self.protocol.set_response_code(self, code, reason_phrase)

    def is_okay(self):
        return 200 <= self._responsecode <= 299

    def begin_response(self, length="-"):
        """Begin the response, and return the initial response text
        """
        self._request_started = True
        request_time = time.time() - self.request_start_time

        code = self._responsecode
        proto = self.protocol

        if USE_ACCESS_LOG:
            proto.server.write_access_log_line(
                proto.client_address[0],
                time.strftime("%d/%b/%Y %H:%M:%S"),
                proto.requestline,
                code,
                length,
                request_time)

        if self._reason_phrase is not None:
            message = self._reason_phrase.split("\n")[0]
        elif code in proto.responses:
            message = proto.responses[code][0]
        else:
            message = ''
        if proto.request_version == 'HTTP/0.9':
            return []

        response_lines = proto.generate_status_line()

        if not self._outgoing_headers.has_key('connection'):
            con = self.get_header('connection')
            if con is None:
                con = 'close'
            self.set_header('connection', con)

        for key, value in self._outgoing_headers.items():
            key = '-'.join([x.capitalize() for x in key.split('-')])
            response_lines.append("%s: %s" % (key, value))

        response_lines.append("")
        return response_lines

    def write(self, data):
        """Write all the data of the response.
           Can be called just once.
        """
        if self._request_started:
            print "Request has already written a response:"
            traceback.print_stack()
            return

        data = self.convert(self, data)

        self._outgoing_headers['content-length'] = len(data)

        response_lines = self.begin_response(len(data))
        response_lines.append(data)
        self.protocol.wfile.write("\r\n".join(response_lines))
    push = write

    def write_chunked(self, data):
        """Write a chunk of the response.
           Can be called multiple times.
        """
        if not self._request_started:
            self._chunked = True
            self.set_header('transfer-coding', 'chunked')
            response_lines = self.begin_response()
        else:
            if not self._chunked:
                print "Request has already started without chunking:"
                traceback.print_stack()
                return
            response_lines = []

        data = self.convert(self, data)

        response_lines.append("%X" % len(data))
        response_lines.append(data)
        response_lines.append("")
        self.protocol.wfile.write("\r\n".join(response_lines))

    def convert(self, second_self, data):
        return data

    def method(self):
        return self._method.lower()

    def path(self):
        return self._path
    uri = path

    def get_headers(self):
        return self._incoming_headers

    def get_header(self, header_name, default=None):
        return self.get_headers().get(header_name.lower(), default)

    def get_query_pairs(self):
        if not hasattr(self, '_split_query'):
            if self._query is None:
                self._split_query = ()
            else:
                spl = self._query.split('&')
                spl = [x.split('=', 1) for x in spl if x]
                self._split_query = []
                for query in spl:
                    if len(query) == 1:
                        key = query[0]
                        value = ''
                    else:
                        key, value = query
                    self._split_query.append((urllib.unquote_plus(key), urllib.unquote_plus(value)))

        return self._split_query

    def get_queries_generator(self, name):
        """Generate all query parameters matching the given name.
        """
        for key, value in self.get_query_pairs():
            if key == name or not name:
                yield value

    get_queries = lambda self, name: list(self.get_queries_generator)

    def get_query(self, name, default=None):
        try:
            return self.get_queries_generator(name).next()
        except StopIteration:
            return default

    def get_arg_list(self, name):
        return self.get_field_storage().getlist(name)

    def get_arg(self, name, default=None):
        return self.get_field_storage().getfirst(name, default)

    def get_field_storage(self):
        if not hasattr(self, '_field_storage'):
            if self.method() == 'get':
                data = ''
            if self._query:
                data = self._query
                fl = cStringIO.StringIO(data)
            else:
                fl = self.protocol.rfile
            ## Allow our resource to provide the FieldStorage instance for
            ## customization purposes.
            headers = self.get_headers()
            environ = dict(
                REQUEST_METHOD='POST',
                QUERY_STRING=self._query or '')
            if (hasattr(self, 'resource') and
                hasattr(self.resource, 'getFieldStorage')):
                self._field_storage = self.resource.getFieldStorage(
                    self, fl, headers, environ)
            else:
                self._field_storage = cgi.FieldStorage(
                    fl, headers, environ=environ)

        return self._field_storage

    def set_header(self, key, value):
        if key.lower() == 'connection' and value.lower() == 'close':
            self.protocol.close_connection = 1
        self._outgoing_headers[key.lower()] = value
    __setitem__ = set_header

    def get_outgoing_header(self, key):
        return self._outgoing_headers[key.lower()]

    def has_outgoing_header(self, key):
        return self._outgoing_headers.has_key(key.lower())

    def socket(self):
        return self.protocol.socket

    def read_body(self):
        if hasattr(self, '_cached_body'):
            return self._cached_body
        length = self.get_header('content-length')
        if length:
            length = int(length)
        if length:
            self._cached_body = self.socket().read(length)
        else:
            self._cached_body = ''
        return self._cached_body

    def __repr__(self):
        return "<Request %s %s>" % (
            getattr(self, '_method'), getattr(self, '_path'))


class HttpProtocol(BaseHTTPServer.BaseHTTPRequestHandler):
    protocol_version = "HTTP/1.0"
    def __init__(self, request, client_address, server):
        self.socket = self.request = self.rfile = self.wfile = request
        self.client_address = client_address
        self.server = server
        self._code = 200
        self._message = 'OK'

    def set_response_code(self, request, code, message):
        self._code = code
        self._message = message

    def generate_status_line(self):
        return [
            "%s %d %s" % (
                self.protocol_version, self._code, self._message)]

    def handle(self):
        self.close_connection = 0

        while not self.close_connection:
            self.raw_requestline = self.rfile.readline()
        
            if not self.raw_requestline:
                self.close_connection = 1
                return
            if not self.parse_request():
                return
            request = Request(self, self.command, self.path, self.headers)
            request.set_header('Server', self.version_string())
            request.set_header('Date', self.date_time_string())
            
            try:
                self.server.site.handle_request(request)
            except socket.error, e:
                # Broken pipe, connection reset by peer
                if e[0] in (32, 54):
                    #print "Remote host closed connection before response could be sent"
                    pass
                else:
                    raise
            except Exception, e:
                request.response(500)
                request.write('Internal Server Error')
                self.socket.close()
                raise
        self.socket.close()


class Server(BaseHTTPServer.HTTPServer):
    def __init__(self, socket, address, site, log, args, kw):
        self.socket = socket
        self.address = address
        self.site = site
        if log:
            self.log = log
            log.write = log.info
        else:
            self.log = self
        self.arguments = args
        self.__dict__.update(kw)

    def write(self, something):
        sys.stdout.write('%s\n' % (something, ))

    def log_message(self, message):
        self.log.write(message)

    def log_exception(self, type, value, tb):
        print ''.join(traceback.format_exception(type, value, tb))

    def write_access_log_line(self, *args):
        """Write a line to the access.log. Arguments:
        client_address, date_time, requestline, code, size, request_time
        """
        self.log.write(
            '%s - - [%s] "%s" %s %s %s' % args)


def server(sock, site, log=None, *args, **kw):
    serv = Server(sock, sock.getsockname(), site, log, args, kw)
    try:
        print "httpd starting up on", sock.getsockname()
        while True:
            try:
                new_sock, address = sock.accept()
                proto = HttpProtocol(new_sock, address, serv)
                api.spawn(proto.handle)
            except KeyboardInterrupt:
                api.get_hub().remove_descriptor(sock.fileno())
                print "httpd exiting"
                break
    finally:
        try:
            sock.close()
        except socket.error:
            pass


