"""Provisional HTTP interface using the new protocol API.
 
XXX This was hacked together in an hour si I would have something to
test ProtocolAPI.py.  Especially the way it uses knowledge about the
internals of httplib.HTTP is disgusting (but then, so would editing
the source of httplib.py be :-).
 
XXX Main deficiencies:
 
- poll*() always returns ready
- should read the headers more carefully (no blocking)
- (could even *write* the headers more carefully)
- should poll the connection making part too
 
"""
 
 
import string
import httplib
from urllib import splithost
import mimetools
from Assert import Assert
import grailutil
import select
import Reader
import re
import StringIO
import socket
import sys
from __main__ import GRAILVERSION
 
 
httplib.HTTP_VERSIONS_ACCEPTED = 'HTTP/1\\.[0-9.]+'
replypat = httplib.HTTP_VERSIONS_ACCEPTED + '[ \\t]+([0-9][0-9][0-9])(.*)'
replyprog = re.compile(replypat)
 
httplib.replypat = replypat
httplib.replyprog = replyprog
 
 
# Search for blank line following HTTP headers
endofheaders = re.compile('\\n[ \\t]*\\r?\\n')
 
 
# Stages
# there are now five stages
WAIT = 'wait'  # waiting for a socket
META = 'meta'
DATA = 'data'
DONE = 'done'
CLOS = 'closed'
 
class MyHTTPConnection(httplib.HTTPConnection):
 
    def putrequest(self, request, selector):
        self.selector = selector
        httplib.HTTPConnection.putrequest(self, request, selector)
 
 
class MyHTTP(httplib.HTTP):
 
    _connection_class = MyHTTPConnection
 
    def __init__(self, host='', port=None, strict=None):
        "Provide a default host, since the superclass requires one."
 
        # some joker passed 0 explicitly, meaning default port
        if port == 0:
            port = None
 
        # Note that we may pass an empty string as the host; this will throw
        # an error when we attempt to connect. Presumably, the client code
        # will call connect before then, with a proper host.
        self._setup(MyHTTPConnection(host, port, strict))
 
 
    def getreply(self, file):
        self.file = file
        line = self.file.readline()
        if self.debuglevel > 0: print 'reply:', `line`
        m = replyprog.match(line)
        if m is None:
            # Not an HTTP/1.0 response.  Fall back to HTTP/0.9.
            # Push the data back into the file.
            self.file.seek(-len(line), 1)
            self.headers = {}
            app = grailutil.get_grailapp()
            c_type, c_encoding = app.guess_type(self._conn.selector)
            if c_encoding:
                self.headers['content-encoding'] = c_encoding
            # HTTP/0.9 sends HTML by default
            self.headers['content-type'] = c_type or "text/html"
            return 200, "OK", self.headers
        errcode, errmsg = m.group(1, 2)
        errcode = string.atoi(errcode)
        errmsg = string.strip(errmsg)
        self.headers = mimetools.Message(self.file, 0)
        return errcode, errmsg, self.headers
 
    def close(self):
        if self.file:
            self.file.close()
        if self._conn.sock:
            try:
                self._conn.sock.close()
            except socket.error:
                # What can you do? :-)
                pass
        self.file = None
        self._conn.sock = None
 
 
class http_access:
 
    def __init__(self, resturl, method, params, data=None):
        self.app = grailutil.get_grailapp()
        self.args = (resturl, method, params, data)
        self.state = WAIT
        self.h = None
        self.reader_callback = None
        self.app.sq.request_socket(self, self.open)
 
    def register_reader(self, reader_callback, ignore):
        if self.state == WAIT:
            self.reader_callback = reader_callback
        else:
            # we've been waitin' fer ya
            reader_callback()
 
    def open(self):
        Assert(self.state == WAIT)
        resturl, method, params, data = self.args
        if data:
            Assert(method=="POST")
        else:
            Assert(method in ("GET", "POST"))
        if type(resturl) == type(()):
            host, selector = resturl    # For proxy interface
        else:
            host, selector = splithost(resturl)
        if not host:
            raise IOError, "no host specified in URL"
        i = string.find(host, '@')
        if i >= 0:
            user_passwd, host = host[:i], host[i+1:]
        else:
            user_passwd = None
        if user_passwd:
            import base64
            auth = string.strip(base64.encodestring(user_passwd))
        else:
            auth = None
        self.h = MyHTTP(host)
        self.h.putrequest(method, selector)
        self.h.putheader('User-agent', GRAILVERSION)
        if auth:
            self.h.putheader('Authorization', 'Basic %s' % auth)
        if not params.has_key('host'):
            self.h.putheader('Host', host)
        if not params.has_key('accept-encoding'):
            encodings = Reader.get_content_encodings()
            if encodings:
                encodings.sort()
                self.h.putheader(
                    'Accept-Encoding', string.join(encodings, ", "))
        for key, value in params.items():
            if key[:1] != '.':
                self.h.putheader(key, value)
        self.h.putheader('Accept', '*/*')
        self.h.endheaders()
        if data:
            self.h.send(data)
        self.readahead = ""
        self.state = META
        self.line1seen = 0
        if self.reader_callback:
            self.reader_callback()
 
    def close(self):
        if self.h:
            self.h.close()
        if self.state != CLOS:
            self.app.sq.return_socket(self)
            self.state = CLOS
        self.h = None
 
    def pollmeta(self, timeout=0):
        Assert(self.state == META)
 
        sock = self.h._conn.sock
        try:
            if not select.select([sock], [], [], timeout)[0]:
                return "waiting for server response", 0
        except select.error, msg:
            raise IOError, msg, sys.exc_traceback
        try:
            new = sock.recv(1024)
        except socket.error, msg:
            raise IOError, msg, sys.exc_traceback
        if not new:
            return "EOF in server response", 1
        self.readahead = self.readahead + new
        if '\n' not in new:
            return "receiving server response", 0
        if not self.line1seen:
            i = string.find(self.readahead, '\n')
            if i < 0:
                return "receiving server response", 0
            self.line1seen = 1
            line = self.readahead[:i+1]
            if replyprog.match(line) < 0:
                return "received non-HTTP/1.0 server response", 1
        m = endofheaders.search(self.readahead)
        if m and m.start() >= 0:
            return "received server response", 1
        return "receiving server response", 0
 
    def getmeta(self):
        Assert(self.state == META)
        if not self.readahead:
            x, y = self.pollmeta(None)
            while not y:
                x, y = self.pollmeta(None)
        file = StringIO.StringIO(self.readahead)
        errcode, errmsg, headers = self.h.getreply(file)
        self.state = DATA
        self.readahead = file.read()
        return errcode, errmsg, headers
 
    def polldata(self):
        Assert(self.state == DATA)
        if self.readahead:
            return "processing readahead data", 1
        return ("waiting for data",
                len(select.select([self], [], [], 0)[0]))
 
    def getdata(self, maxbytes):
        Assert(self.state == DATA)
        if self.readahead:
            data = self.readahead[:maxbytes]
            self.readahead = self.readahead[maxbytes:]
            return data
        try:
            data = self.h._conn.sock.recv(maxbytes)
        except socket.error, msg:
            raise IOError, msg, sys.exc_traceback
        if not data:
            self.state = DONE
            # self.close()
        return data
 
    def fileno(self):
        return self.h._conn.sock.fileno()
 
 
# To test this, use ProtocolAPI.test()