#
# Copyright 2003 by Object Craft P/L, Melbourne, Australia.
#
# LICENCE - see LICENCE file distributed with this software for details.
#
# AUTHOR(S)
#       Matt Goodall <matt AT pollenation DOT net>

import BaseHTTPServer
import cgi
import mimetypes
from cStringIO import StringIO
from urlparse import urlsplit
import os.path
import shutil
import time
import stat

from albatross.cgiapp import RequestFields

RFC822_FORMAT = '%a, %d %b %Y %H:%M:%S +0000'

class Request(RequestFields):

    """BaseHTTPServer request adaptor"""

    def __init__(self, req):

        self._req = req
        self._status = 200;
        self.headers_out = {}
        self.data = StringIO()

        fields = self.make_fields()
        RequestFields.__init__(self, fields)

    def make_fields(self):
        '''
        Make a cgi.FieldStorage from the request.

        This feels like a complete hack but it makes good use of the standard
        cgi module ... and it works! Basically, we're pretending to be a
        CGI by providing an "environment" dictionary with everything the
        cgi.FieldStorage needs (I simply read the code to know what's used).
        '''
        
        environ = {}
        environ['REQUEST_METHOD'] = self._req.command
        environ['QUERY_STRING'] = urlsplit(self._req.requestline.split()[1])[3]

        self._req.headers.setdefault(
            'content-type',
            'application/x-www-form-urlencoded')

        field_storage = cgi.FieldStorage(
            fp=self._req.rfile,
            headers=self._req.headers,
            environ=environ)

        return field_storage
        
    def get_header(self, name):
        return self._req.headers.get(name)
        
    def write_header(self, name, value):
        self.headers_out[name] = value

    def end_headers(self):
        pass

    def get_uri(self):
        return self._req.path

    def get_path_info(self):
        return self._req.path

    def get_servername(self):
        server_name = self._req.server.server_name
        port = self._req.server.server_port
        return '%s:%d' % (server_name, port)

    def write_content(self, data):
        self.data.write(data)

    def redirect(self, loc):
        self.write_header('Location', loc)
        self.write_header('Status', '301 Moved Permanently')
        self._status = 301
        self.end_headers()

    def set_status(self, status):
        self._status = status

    def status(self):
        return self._status


class RequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):

    """
    Handle HTTP request, routing them through to the Albatross
    application
    """

    def do_GET(self):
        # Try the static resources first
        if self.server.static_resources:
            for uri_path, fs_path in self.server.static_resources.items():
                if self.path.startswith(uri_path):
                    path = self.path.split(uri_path)[1]
                    self.send_file('%s%s' % (fs_path, path))
                    return
        self.process_request()

    def do_POST(self):
        self.process_request()

    def process_request(self):
        '''Process a request to the Albatross app'''
        self.log_request()
        req = Request(self)
        self.server.app.run(req)
        self.send_response(req.status())
        for name, value in req.headers_out.items():
            self.send_header(name, value)
        self.end_headers()
        if req.status() == 200:
            self.wfile.write(req.data.getvalue())

    def send_file(self, path):
        '''Send a file directly from the filesystem'''
        
        self.log_request()

        # Stat the file
        try:
            stats = os.stat(path)
        except OSError, e:
            # 404 - file not found
            self.send_error(404)
            self.end_headers()
            return

        # Get the last modified date in RFC822 form
        last_modified = time.gmtime(stats[stat.ST_MTIME])
        last_modified = time.strftime(RFC822_FORMAT, last_modified)

        # See if a 304 (not modified) is enough
        ts = self.headers.get('If-Modified-Since')
        if ts and ts == last_modified:
            self.send_response(304)
            self.end_headers()
            return
        
        # Need to send the whole file
        f = None
        try:
            try:
                f = file(path, 'rb')
                self.send_response(200)
                self.send_header('Content-Type', mimetypes.guess_type(path)[0])
                self.send_header('Content-Length', os.path.getsize(path))
                self.send_header('Last-Modified', last_modified)
                self.end_headers()
                shutil.copyfileobj(f, self.wfile)
            except IOError, e:
                self.send_error(500)
                self.end_headers()
        finally:
            if f:
                f.close()


class HTTPServer(BaseHTTPServer.HTTPServer):

    """
    Simple, standalone HTTP server for Albatross applications.
    
    HTTPServer is a simple web server dedicated to processing requests and
    mapping that request through to Albatross. It can also serve static
    resources such as images, stylesheets etc directly from the filesystem.
    """

    def __init__(self, app, port, static_resources=None):
        '''
        Create an HTTP server for the Albatross application, app,
        and listen for requests on the specified port.

        static_resources is an optional dictionary that maps uri paths to
        a corresponding filesystem path. Static resources are served directly
        from the filesystem without involving the Albatross application.
        '''
        BaseHTTPServer.HTTPServer.__init__( self, ('', port), RequestHandler)
        self.app = app
        self.static_resources = static_resources
