# copied from PyPy's build tool
# this code is available under an MIT-style license, see the PyPy package
# for more details

import sys
import traceback
import time
from BaseHTTPServer import HTTPServer, BaseHTTPRequestHandler
from SocketServer import ThreadingMixIn
import socket

# some generic stuff to make working with BaseHTTPServer a bit nicer...

# XXX note that this has some overlap with pypy/translator/js/lib/server.py,
# and should perhaps at some point be merged... (the main reason why I started
# this instead of using server.py is because the latter is mostly geared
# towards some tricks (transparent AJAX), and doesn't utilize or abstract the
# HTTP support in a very clean way, hopefully I can find a model here to
# improve server.py)

# XXX this needs to be built using httplib.responses in Python > 2.5 :(
HTTP_STATUS_MESSAGES = {
    200: 'OK',
    204: 'No Content',
    301: 'Moved permanently',
    302: 'Found',
    304: 'Not modified',
    401: 'Unauthorized',
    403: 'Forbidden',
    404: 'Not found',
    500: 'Server error',
    501: 'Not implemented',
}

class HTTPError(Exception):
    """ raise on HTTP errors
    
        note that this is just a more convenient way to deal with errors, you
        can also just return a 404/500/whatever status with appropriate headers
        and content
    """
    def __init__(self, status, data=None):
        self.status = status
        self.message = HTTP_STATUS_MESSAGES[status]
        self.data = data

    def __str__(self):
        data = ''
        if self.data:
            data = ' (%s)' % (self.data,)
        return '<HTTPException %s "%s"%s>' % (self.status, self.message, data)

def get_nocache_headers():
    return {'Connection': 'close',
            'Pragma': 'no-cache',
            'Expires': 'Mon, 26 Jul 1997 05:00:00 GMT',
            'Last-Modified': time.strftime("%a, %d %b %Y %H:%M:%S GMT"),
            'Cache-Control': 'no-cache, must-revalidate',
            'Cache-Control': 'post-check=0, pre-check=0',
            }

class ObjectPublisherHandler(BaseHTTPRequestHandler):
    """ BaseHTTPRequestHandler that does object publishing
    """

    application = None # attach web root (Collection object) here!!
    bufsize = 1024
    
    def do_GET(self, send_body=True):
        """ perform a request """
        path, query = self.process_path(self.path)
        try:
            resource = self.find_resource(path, query)
            status, headers, data = resource(self, path, query)
        except HTTPError, e:
            status = e.status
            headers, data = self.process_http_error(e)
        except:
            exc, e, tb = sys.exc_info()
            tb_formatted = '\n'.join(traceback.format_tb(tb))
            status = 200
            data = 'An error has occurred: %s - %s\n\n%s' % (exc, e,
                                                             tb_formatted)
            headers = {'Content-Type': 'text/plain'}
        else:
            if not 'content-type' in [k.lower() for k in headers]:
                headers['Content-Type'] = 'text/html; charset=UTF-8'
        headers['Connection'] = 'close' # for now? :|
        self.response(status, headers, data, send_body)

    do_POST = do_GET

    def do_HEAD(self):
        return self.do_GET(False)

    def process_path(self, path):
        """ split the path in a path and a query part

            returns a tuple (path, query), where path is a string and
            query a dictionary containing the GET vars (URL decoded and such)
        """
        path = path.split('?')
        if len(path) > 2:
            raise ValueError('illegal path %s' % (path,))
        p = path[0]
        q = len(path) > 1 and path[1] or ''
        return p, q

    def find_resource(self, path, query):
        """ find the resource for a given path
        """
        if not path:
            raise HTTPError(301, '/')
        assert path.startswith('/')
        chunks = path.split('/')
        chunks.pop(0) # empty item
        return self.application.traverse(chunks, path)

    def process_http_error(self, e):
        """ create the response body and headers for errors
        """
        headers = {'Content-Type': 'text/plain',
                   }
        headers.update(get_nocache_headers())
        if e.status in [301, 302]:
            # XXX should we deal with GET vars here?
            headers['Location'] = e.data
            body = 'Redirecting to %s' % (e.data,)
        else:
            body = 'Error: %s (%s)' % (e.status, e.message)
        return headers, body
    
    def response(self, status, headers, body, send_body=True):
        """ generate the HTTP response and send it to the client
        """
        self.send_response(status)
        if (isinstance(body, str) and
                not 'content-length' in [k.lower() for k in headers]):
            headers['Content-Length'] = len(body)
        for keyword, value in headers.iteritems():
            self.send_header(keyword, value)
        self.end_headers()
        if not send_body:
            return
        if isinstance(body, str):
            self.wfile.write(body)
        elif hasattr(body, 'read'):
            while 1:
                data = body.read(self.bufsize)
                if data == '':
                    break
                try:
                    self.wfile.write(data)
                except socket.error, e:
                    print 'error writing to client:', e
                    break
            del body
        else:
            raise ValueError('body is not a plain string or file-like object')

class ThreadedHTTPServer(ThreadingMixIn, HTTPServer):
    pass

def run_server(address, handler, serverclass=ThreadedHTTPServer):
    """ run a BaseHTTPServer instance
    """
    server = serverclass(address, handler)
    server.serve_forever()


