"""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
from httplib import replyprog
import __main__
import select
import regex
import StringIO
import socket
import sys


# Search for blank line following HTTP headers
endofheaders = regex.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 MyHTTP(httplib.HTTP):

    def putrequest(self, request, selector):
	self.selector = selector
	httplib.HTTP.putrequest(self, request, selector)

    def getreply(self, file):
	self.file = file
	line = self.file.readline()
	if self.debuglevel > 0: print 'reply:', `line`
	if replyprog.match(line) < 0:
	    # 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 = {}
	    c_type, c_encoding = __main__.app.guess_type(self.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 = replyprog.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.sock:
	    try:
		self.sock.close()
	    except socket.error:
		# What can you do? :-)
		pass
	self.file = None
	self.sock = None


class http_access:

    def __init__(self, resturl, method, params, data=None):
	self.app = __main__.app
	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):
	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)
	grailversion = __main__.__version__
	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)
	for key, value in params.items():
	    if key[:1] != '.':
		self.h.putheader(key, value)
	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.sock
	try:
	    if not select.select([sock.fileno()], [], [], 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
	i = endofheaders.search(self.readahead)
	if i >= 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.fileno()], [], [], 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.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.sock.fileno()


# To test this, use ProtocolAPI.test()
