#
# A Burlap client interface for Python.  The date and long types require
# Python 2.3 or later.
#
# The Burlap proxy is used as follows:
#
# proxy = Burlap("http://localhost:8080/buffalo/Hello")
#
# print proxy.hello()
#
import string, time
import urllib
#import xmllib
from types import *

__version__ = "0.1"


# --------------------------------------------------------------------
# Exceptions

class Error:
    # base class for client errors
    pass

class ProtocolError(Error):
    # Represents an HTTP protocol error
    def __init__(self, url, code, message, headers):
	self.url = url
	self.code = code
	self.message = message
	self.headers = headers

    def __repr__(self):
	return (
	    "<ProtocolError for %s: %s %s>" %
	    (self.url, self.code, self.message)
	    )

class Fault(Error):
    # Represents a fault from Burlap
    def __init__(self, code, message, **detail):
	self.code = code
	self.message = message

    def __repr__(self):
	return "<BurlapFault %s: %s>" % (self.code, self.message)

# --------------------------------------------------------------------
# Wrappers for Burlap data types non-standard in Python
#

#
# Boolean -- use the True or False constants
#
class Boolean:
    def __init__(self, value = 0):
	self.value = (value != 0)

    def _burlap_write(self, out):
	if self.value:
	    out.write('<boolean>')
	    out.write('1')
	    out.write('</boolean>')
	else:
	    out.write('<boolean>')
	    out.write('0')
	    out.write('</boolean>')

    def __repr__(self):
	if self.value:
	    return "<True at %x>" % id(self)
	else:
	    return "<False at %x>" % id(self)

    def __int__(self):
	return self.value

    def __nonzero__(self):
	return self.value

True, False = Boolean(1), Boolean(0)

#
# Date - wraps a time value in seconds
#
class Date:
    def __init__(self, value = 0):
	self.value = value

    def __repr__(self):
	return ("<Date %s at %x>" %
                (time.asctime(time.localtime(self.value)), id(self)))

    def _burlap_write(self, out):
	out.write("<date>")
	out.write(self.value)
	out.write("</date>")
#
# Binary - binary data
#

class Binary:
    def __init__(self, data=None):
	self.data = data

    def _burlap_write(self, out):
	out.write('<base64>')
	out.write(self.data)
	out.write('</base64>')

#
# BurlapWriter - writes Burlap data from Python objects
#
class BurlapWriter:
    dispatch = {}

    def write_call(self, method, params):
	self.refs = {}
	self.ref = 0
	self.__out = []
	self.write = write = self.__out.append

	write("<burlap:call>");
        #write(pack(">H", len(method)));
	write("<method>")
        write(method);
	write("</method>")
	for v in params:
	    self.write_object(v)
        write("</burlap:call>");
	result = string.join(self.__out, "")
	del self.__out, self.write, self.refs
	return result

    def write_object(self, value):
	try:
	    f = self.dispatch[type(value)]
	except KeyError:
	    raise TypeError, "cannot write %s objects" % type(value)
	else:
	    f(self, value)

    def write_int(self, value):
	self.write('<int>')
	self.write(str(value))
	self.write('</int>')
    dispatch[IntType] = write_int

    def write_long(self, value):
	self.write('<long>')
	self.write(str(value))
	self.write('</long>')
    dispatch[LongType] = write_long

    def write_double(self, value):
	self.write('<double>')
	self.write(str(value))
	self.write('</double>')
    dispatch[FloatType] = write_double

    def write_string(self, value):
	self.write('<string>')
	self.write(str(value))
	self.write('</string>')
    dispatch[StringType] = write_string

    def write_reference(self, value):
        # check for and write circular references
        # returns 1 if the object should be written, i.e. not a reference
	i = id(value)
	if self.refs.has_key(i):
	    self.write('<ref>')
	    self.write(self.refs[i])
	    self.write('</ref>')
	    return 0
	else:
	    self.refs[i] = self.ref
	    self.ref = self.ref + 1
	    return 1

    def write_list(self, value):
	if self.write_reference(value):
	    self.write("<list>");
	    self.write('<length>'+str(len(value))+'</length>')
	    for v in value:
		#print v
	        self.write_object(v)
	    self.write('</list>')
    dispatch[TupleType] = write_list
    dispatch[ListType] = write_list

    def write_map(self, value):
	if self.write_reference(value):
	    self.write("<map>")
	    for k, v in value.items():
	        #self.__write(k)
	        #self.__write('<string>'+v+'</string>')
		self.write_object(v)
		self.write_object(k)
	    self.write("</map>")
    dispatch[DictType] = write_map

    def write_instance(self, value):
	# check for special wrappers
	if hasattr(value, "_burlap_write"):
	    value._burlap_write(self)
    dispatch[InstanceType] = write_instance

from xml.parsers import expat
class Parser:
    def __init__(self):
        self._parser = expat.ParserCreate()
        self._parser.StartElementHandler = self.start
        #self._parser.EndElementHandler = self.end
        self._parser.CharacterDataHandler = self.data
	self.tags = []
	self.datas = []

    def feed(self, data):
        self._parser.Parse(data, 0)

    def close(self):
        self._parser.Parse("", 1) # end of data
        del self._parser # get rid of circular references

    def start(self, tag, attrs):
        #print "START", repr(tag), attrs
	self.tags.append(tag)

    def end(self, tag):
        print "END", repr(tag)

    def data(self, data):
        #print "DATA", repr(data)
	self.datas.append(data)
	

#
# Parses the results from the server
#
class BurlapParser:
    def __init__(self, f):
	self._f = f
	self.read = self._f.readline()
	self._refs = []

    def parse_reply(self):
	#read = self.read
	#major = read(1)
	#minor = read(1)
	#ch = read(1)

        value = self.parse_object()

	return value
	#self.error() # actually a fault

    def parse_object(self):
	# parse an arbitrary object based on the type in the data
	return self.parse_object_code(self.read)

    def parse_object_code(self, code):
	# parse an object when the code is known
	read = self.read

	p = Parser()
	p.feed(read)
	p.close()

	if code == 'null':
	    return None

	elif p.tags[1] == 'boolean':
	    return p.datas[0]

	#elif code == 'F':
	#    return False

	elif p.tags[1] == 'int':
	    return p.datas[0]

	elif p.tags[1] == 'long':
	    return p.datas[0]

	elif p.tags[1] == 'double':
	    return p.datas[0]

	elif p.tags[1] == 'date':
	    ms = p.datas[0]

	    return ms

	elif p.tags[1] == 'string':
	    #return self.parse_string()
	    return p.datas[0]

	elif code == 'B':
	    return Binary(self.parse_string())

	elif p.tags[1] == 'list':
	    list = []
	    self._refs.append(list)
	    i = 2#skip length
	    while i<=len(p.datas):
		list.append(p.datas[i-1])
		i = i+1
	    return list

	elif p.tags[1] == 'map':
	    #self.parse_type() # skip type
	    map = {}
	    #self._refs.append(map)
	    i = 1
	    while i<=len(p.datas):
		#key = self.parse_object_code(ch)
		#value = self.parse_object()
		key = p.datas[i-1]
		value = p.datas[i]
		map[key] = value
		#ch = read(1)
		i = i+2
	    return map

	elif code == 'R':
	    return self._refs[unpack('>l', read(4))[0]]

	elif code == 'r':
	    self.parse_type()       # skip type
	    url = self.parse_type() # reads the url
	    return Burlap(url)

	else:
	    raise "UnknownObjectCode %d" % code

    def parse_type(self):
	f = self._f
	len = unpack('>cH', f.read(3))[1]
	return f.read(len)

    def error(self):
	raise "FOO"

#
# Encapsulates the method to be called
#
class _Method:
    def __init__(self, invoker, method):
	self._invoker = invoker
	self._method = method

    def __call__(self, *args):
	return self._invoker(self._method, args)

# --------------------------------------------------------------------
# Burlap is the main class.  A Burlap proxy is created with the URL
# and then called just as for a local method
#
# proxy = Burlap("http://localhost:8080/buffalo/Hello")
# print proxy.hello()
#
class Burlap:
    """Represents a remote object reachable by Burlap"""

    def __init__(self, url):
	# Creates a Burlap proxy object

	self._url = url

	# get the uri
	type, uri = urllib.splittype(url)
	if type != "http":
	    raise IOError, "unsupported Burlap protocol"

	self._host, self._uri = urllib.splithost(uri)

    def __invoke(self, method, params):
	# call a method on the remote server

	request = BurlapWriter().write_call(method, params)

	import httplib

	h = httplib.HTTP(self._host)
	h.putrequest("POST", self._uri)

	# required by HTTP/1.1
	h.putheader("Host", self._host)

	h.putheader("User-Agent", "PyBurlap.py/%s" % __version__)
	h.putheader("Content-Length", str(len(request)))

	h.endheaders()

	h.send(request)

	errcode, errmsg, headers = h.getreply()

	if errcode != 200:
	    raise ProtocolError(self._url, errcode, errmsg, headers)

	return self.parse_response(h.getfile())

    def parse_response(self, f):
	# read response from input file, and parse it

	parser = BurlapParser(f)
	value = parser.parse_reply()
	f.close()

	return value

    def _hessian_write(self, out):
	# marshals the proxy itself
	out.write("rt\x00\x00S")
	out.write(pack(">H", len(self._url)))
	out.write(self._url)

    def __repr__(self):
	return "<BurlapProxy %s>" % self._url

    __str__ = __repr__

    def __getattr__(self, name):
	# encapsulate the method call
	return _Method(self.__invoke, name)

#
# Testing code.
#
if __name__ == "__main__":
    proxy = Burlap("http://localhost:8080/buffalo/Hello")

    try:
	print proxy.hello("Breeze")
	print proxy.replyInt(10)
	print proxy.replyLong(10000)
	print proxy.replyDouble(1000000.90)
	print proxy.callIntStr(100,"Breeze")

	print proxy.replyList(['Breeze','QingFeng','Wind',100,300.68])#list

	print proxy.replyMap({'Breeze':1,'QingFeng':2,'Wind':3,100:4,300.68:5})#map->Python is dict

	print proxy.replyBoolean(True)

	#ISO_8609_DATETIME = '%Y%m%dT%H%M%SZ'
	print proxy.replyDate(Date('20050501T095231Z'))

	import base64
	print proxy.replyBase64(base64.encodestring('Breeze Base64'))
    except Error, v:
	print "ERROR", v

