"""
XEP-0009 XMPP Remote Procedure Calls
"""
from __future__ import with_statement
from . import base
import logging
from xml.etree import cElementTree as ET
import copy
import time
import base64

def py2xml(*args):
	params = ET.Element("params")
	for x in args:
		param = ET.Element("param")
		param.append(_py2xml(x))
		params.append(param) #<params><param>...
	return params

def _py2xml(*args):
	for x in args:
		val = ET.Element("value")
		if type(x) is int:
			i4 = ET.Element("i4")
			i4.text = str(x)
			val.append(i4)
		if type(x) is bool:
			boolean = ET.Element("boolean")
			boolean.text = str(int(x))
			val.append(boolean)
		elif type(x) is str:
			string = ET.Element("string")
			string.text = x
			val.append(string)
		elif type(x) is float:
			double = ET.Element("double")
			double.text = str(x)
			val.append(double)
		elif type(x) is rpcbase64:
			b64 = ET.Element("Base64")
			b64.text = x.encoded()
			val.append(b64)
		elif type(x) is rpctime:
			iso = ET.Element("dateTime.iso8601")
			iso.text = str(x)
			val.append(iso)
		elif type(x) is list:
			array = ET.Element("array")
			data = ET.Element("data")
			for y in x:
				data.append(_py2xml(y))
			array.append(data)
			val.append(array)
		elif type(x) is dict:
			struct = ET.Element("struct")
			for y in x.keys():
				member = ET.Element("member")
				name = ET.Element("name")
				name.text = y
				member.append(name)
				member.append(_py2xml(x[y]))
				struct.append(member)
			val.append(struct)
		return val

def xml2py(params):
	vals = []
	for param in params.findall('param'):
		vals.append(_xml2py(param.find('value')))
	return vals

def _xml2py(value):
	if value.find('i4') is not None:
		return int(value.find('i4').text)
	if value.find('int') is not None:
		return int(value.find('int').text)
	if value.find('boolean') is not None:
		return bool(value.find('boolean').text)
	if value.find('string') is not None:
		return value.find('string').text
	if value.find('double') is not None:
		return float(value.find('double').text)
	if value.find('Base64') is not None:
		return rpcbase64(value.find('Base64').text)
	if value.find('dateTime.iso8601') is not None:
		return rpctime(value.find('dateTime.iso8601'))
	if value.find('struct') is not None:
		struct = {}
		for member in value.find('struct').findall('member'):
			struct[member.find('name').text] = _xml2py(member.find('value'))
		return struct
	if value.find('array') is not None:
		array = []
		for val in value.find('array').find('data').findall('value'):
			array.append(_xml2py(val))
		return array
	raise ValueError()

class rpcbase64(object):
	def __init__(self, data):
		#base 64 encoded string
		self.data = data

	def decode(self):
		return base64.decodestring(data)

	def __str__(self):
		return self.decode()

	def encoded(self):
		return self.data

class rpctime(object):
	def __init__(self,data=None):
		#assume string data is in iso format YYYYMMDDTHH:MM:SS
		if type(data) is str:
			self.timestamp = time.strptime(data,"%Y%m%dT%H:%M:%S")
		elif type(data) is time.struct_time:
			self.timestamp = data
		elif data is None:
			self.timestamp = time.gmtime()
		else:
			raise ValueError()

	def iso8601(self):
		#return a iso8601 string
		return time.strftime("%Y%m%dT%H:%M:%S",self.timestamp)

	def __str__(self):
		return self.iso8601()

class JabberRPCEntry(object):
	def __init__(self,call):
		self.call = call
		self.result = None
		self.error = None
		self.allow = {} #{'<jid>':['<resource1>',...],...}
		self.deny = {}

	def check_acl(self, jid, resource):
		#Check for deny
		if jid in self.deny.keys():
			if self.deny[jid] == None or resource in self.deny[jid]:
				return False
		#Check for allow
		if allow == None:
			return True
		if jid in self.allow.keys():
			if self.allow[jid] == None or resource in self.allow[jid]:
				return True
		return False

	def acl_allow(self, jid, resource):
		if jid == None:
			self.allow = None
		elif resource == None:
			self.allow[jid] = None
		elif jid in self.allow.keys():
			self.allow[jid].append(resource)
		else:
			self.allow[jid] = [resource]
		
	def acl_deny(self, jid, resource):
		if jid == None:
			self.deny = None
		elif resource == None:
			self.deny[jid] = None
		elif jid in self.deny.keys():
			self.deny[jid].append(resource)
		else:
			self.deny[jid] = [resource]

	def call_method(self, args):
		ret = self.call(*args)

class xep_0009(base.base_plugin):

	def plugin_init(self):
		self.xep = '0009'
		self.description = 'Jabber-RPC'
		self.xmpp.add_handler("<iq type='set'><query xmlns='jabber:iq:rpc' /></iq>", 
                                      self._callMethod, name='Jabber RPC Call')
		self.xmpp.add_handler("<iq type='result'><query xmlns='jabber:iq:rpc' /></iq>", 
                                      self._callResult, name='Jabber RPC Result')
		self.xmpp.add_handler("<iq type='error'><query xmlns='jabber:iq:rpc' /></iq>", 
                                      self._callError, name='Jabber RPC Error')
		self.entries = {}
		self.activeCalls = []

	def post_init(self):
		base.base_plugin.post_init(self)
		self.xmpp.plugin['xep_0030'].add_feature('jabber:iq:rpc')
		self.xmpp.plugin['xep_0030'].add_identity('automatition','rpc')

	def register_call(self, method, name=None):
		#@returns an string that can be used in acl commands.
		with self.lock:
			if name is None:
				self.entries[method.__name__] = JabberRPCEntry(method)
				return method.__name__
			else:
				self.entries[name] = JabberRPCEntry(method)
				return name

	def acl_allow(self, entry, jid=None, resource=None):
		#allow the method entry to be called by the given jid and resource.
		#if jid is None it will allow any jid/resource.
		#if resource is None it will allow any resource belonging to the jid.
		with self.lock:
			if self.entries[entry]:
				self.entries[entry].acl_allow(jid,resource)
			else:
				raise ValueError()
	
	def acl_deny(self, entry, jid=None, resource=None):
		#Note: by default all requests are denied unless allowed with acl_allow.
		#If you deny an entry it will not be allowed regardless of acl_allow
		with self.lock:
			if self.entries[entry]:
				self.entries[entry].acl_deny(jid,resource)
			else:
				raise ValueError()
	
	def unregister_call(self, entry):
		#removes the registered call
		with self.lock:
			if self.entries[entry]:
				del self.entries[entry]
			else:
				raise ValueError()

	def makeMethodCallQuery(self,pmethod,params):
		query = self.xmpp.makeIqQuery(iq,"jabber:iq:rpc")
		methodCall = ET.Element('methodCall')
		methodName = ET.Element('methodName')
		methodName.text = pmethod
		methodCall.append(methodName)
		methodCall.append(params)
		query.append(methodCall)
		return query
 
	def makeIqMethodCall(self,pto,pmethod,params):
		iq = self.xmpp.makeIqSet()
		iq.set('to',pto)
		iq.append(self.makeMethodCallQuery(pmethod,params))
		return iq
	
	def makeIqMethodResponse(self,pto,pid,params):
		iq = self.xmpp.makeIqResult(pid)
		iq.set('to',pto)
		query = self.xmpp.makeIqQuery(iq,"jabber:iq:rpc")
		methodResponse = ET.Element('methodResponse')
		methodResponse.append(params)
		query.append(methodResponse)
		return iq

	def makeIqMethodError(self,pto,id,pmethod,params,condition):
		iq = self.xmpp.makeIqError(id)
		iq.set('to',pto)
		iq.append(self.makeMethodCallQuery(pmethod,params))
		iq.append(self.xmpp['xep_0086'].makeError(condition))
		return iq
	
		

	def call_remote(self, pto, pmethod, *args):
		#calls a remote method. Returns the id of the Iq.
		pass

	def _callMethod(self,xml):
		pass

	def _callResult(self,xml):
		pass

	def _callError(self,xml):
		pass