import asynchat
import asyncore
import cStringIO
import socket, os, string
import sys

from whatwhat.producers          import scanning_producer
from cherrypy._cpwsgi   import wsgiApp


class SCGIServer(asyncore.dispatcher):
    def __init__(self, application, prefix, host='127.0.0.1', port=None, socket_file=None):
        asyncore.dispatcher.__init__(self)
        self.port = port
        self.socket_file = socket_file
        if self.port:
            self.create_socket(socket.AF_INET, socket.SOCK_STREAM) 
            self.set_reuse_addr()
            self.bind((host,port))
        else:
            try:
                os.unlink(self.socket_file)
            except:
                pass
            self.create_socket(socket.AF_UNIX, socket.SOCK_STREAM)
            self.set_reuse_addr()
            self.bind(self.socket_file)
        
        self.listen(40)
        self.application = application
        self.prefix = prefix
        print 'SCGI/WSGI Server started'
    
    #needed for unix sockets/asyncore
    def handle_write(self):
        pass         
    
    #needed for unix sockets/asyncore
    def handle_connect(self):
        pass
    
    def handle_accept(self):
        sock_addr = self.accept()
        if sock_addr:
            sock, addr = sock_addr
        else:
            return # Failed
        SCGIHandler(sock, self.application, self.prefix)


class SCGIHandler(asynchat.async_chat):
    def __init__(self, sock, application, prefix):
        asynchat.async_chat.__init__(self, sock)
        self.set_terminator(':')
        self.env = None
        self.application = application
        self.prefix = prefix
        self.buffer = []
        self.largeFileThreshold = 1 << 18

    def collect_incoming_data(self, data):
        self.buffer.append(data)

    def found_terminator(self):
        """
        Handle an individual connection.
        """
        
        data = ''.join(self.buffer)
        self.buffer = []
        length = 0
        if self.get_terminator() == ':':
            self.set_terminator(int(data) + 1)
            return
        elif not self.env:
            self.env = self.read_env(data)
            length = int(self.env['CONTENT_LENGTH'])
            if length:
                self.set_terminator(length)
                return
            data = ''

        if length > self.largeFileThreshold:
            input = TemporaryFile('w+b')
        else:
            input = cStringIO.StringIO()

        input.write(data)
        input.seek(0,0)
        environ = self.env
        environ['wsgi.input']           = input 
        environ['wsgi.errors']          = sys.stderr
        environ['wsgi.version']         = (1,0)
        environ['wsgi.multithread']     = False
        environ['wsgi.multiprocess']    = True
        environ['wsgi.run_once']        = False
        
        if environ.get('HTTPS','off') in ('on','1'):
            environ['wsgi.url_scheme'] = 'https'
        else:
            environ['wsgi.url_scheme'] = 'http'
        
        path = environ['REQUEST_URI']
        
        environ['SCRIPT_NAME'] = self.prefix
        environ['PATH_INFO'] = path.split('?')[0]
        
        headers_set = []
        headers_sent = []
        results = []
        
        def write(data):
            results.append(data)
        
        def start_response(status,response_headers,exc_info=None):
            if exc_info:
                try:
                    if headers_sent:
                        # Re-raise original exception if headers sent
                        raise exc_info[0], exc_info[1], exc_info[2]
                finally:
                    exc_info = None        
            elif headers_set:
                raise AssertionError("Headers already set!")
            
            headers_set[:] = [status,response_headers]
            return write
        
        result = self.application(environ, start_response)
        
        output = cStringIO.StringIO()
        try:
            for data in result:
                results.append(data)
            if not headers_set:
                # Error -- the app never called start_response
                status = '500 Server Error'
                response_headers = [('Content-Type', '%s' % 'text/html')]
                results = ["start_response never called"]
            else:
                status, response_headers = headers_sent[:] = headers_set
            
            output.write('Status: %s\r\n' % status)
              
            for header in response_headers:
                output.write('%s: %s\r\n' % header)
            output.write('\r\n')
             
            for data in results:
                output.write(data)
                 
            self.push_with_producer(scanning_producer(output.getvalue()))   
        
        finally:
            if hasattr(result,'close'):
                result.close()
        
        output.close()    
        input.close()
        self.close_when_done()
        self.env = None
        self.set_terminator(':')


    def read_env(self, data):
        headers = data.split('\0')
        headers = headers[:-1] # Skip ending comma
        assert len(headers)%2 == 0, 'malformed scgi headers'
        env = {}
        for i in xrange(0, len(headers), 2):
            env[headers[i]] = headers[i+1]
        return env
