import pickle
import os
import socket
import sys
import commandsplus
import atexit
import logging
import errno
import select
import locale
import logging

#checks
import imp
imp.find_module("pydcop")

logger = logging.getLogger("pydcopwrapper")

def cleanup_slave(sockaddr):
	try: os.unlink(sockaddr)
	except: pass

class Call:
	def __init__(self,appname,objectname,method,*args):
		self.appname = appname
		self.objectname = objectname
		self.methodname = method
		self.arguments = args

_slave_killed = False # no concerns about this being a global var, since it exists only once per slave process



# def turntoqstring(aparm):
# 		if type(aparm) is str:
# 			n = unicode(aparm,locale.getpreferredencoding())
# 			
# 			return aparm.decode("utf-8").encode("iso-8859-1")
# 		if type(aparm) is unicode:
# 			return aparm.encode("iso-8859-1")
# 		if type(aparm) is tuple:
# 			return tuple([turnintoiso(a) for a in aparm])
# 		if type(aparm) is list: # falta manipuleo de dicts y otros iterables
# 			return [turnintoiso(a) for a in aparm]
# 		return aparm
# 	
# 	args = turnintoiso(args)
# 
# def turntostr(aparm)
# 		if type(aparm) is str: # FIXME: remove this kludge.  problema: si no hago esto, los caracteres no llegan bien.  osea llega doblemente codificado como utf8.
# 			return aparm.decode("utf-8").encode("iso-8859-1")
# 		if type(aparm) is unicode:
# 			return aparm.encode("iso-8859-1")
# 		if type(aparm) is tuple:
# 			return tuple([turnintoiso(a) for a in aparm])
# 		if type(aparm) is list: # falta manipuleo de dicts y otros iterables
# 			return [turnintoiso(a) for a in aparm]
# 		return aparm
	



def run_slave(s):
	def sighandler(signal,frame):
		global _slave_killed
		_slave_killed = True
	import signal
	signal.signal(signal.SIGINT,sighandler)
	signal.signal(signal.SIGTERM,sighandler)
	
	from time import sleep
	import pydcop
	max_linger = 60 # duration max one minute
	linger = 0
	while not _slave_killed:
# 		print "loop"; sys.stdout.flush()
		try:
			r,w,e = select.select([s],[],[],1.0)
		except select.error,e:
			if e.args[0] is errno.EINTR: continue
			else: raise
		if not r:
			linger = linger + 1
			if linger >= max_linger: break # lo juimo!
			continue
		linger = 0 # we reset the linger arg again
		conn,addr = s.accept()
# 		print "yea"; sys.stdout.flush()
		result = ""
		while True:
			received = conn.recv(2048)
			if not received: break
			result = result + received 
		unmarshaled = pickle.loads(result)
		if unmarshaled is None: returnvalue = None # a ping!!!
		else:
			try:
# 				print unmarshaled.appname,unmarshaled.objectname,unmarshaled.methodname,unmarshaled.arguments
				app = pydcop.DCOPApplication(unmarshaled.appname)
				obj = getattr(app,unmarshaled.objectname)
				method = getattr(obj,unmarshaled.methodname)
				args = unmarshaled.arguments
				def turnintoiso(args): #FUCKING DCOP, el problema es que yo le paso un estring (str) en UTF-8, y el man lo convierte internamente a quien sabe que chucha, y no funciona por eso.  Sospecho que la conversion interna la hace qstring y despues la pasa a latin1() y ahi recien la entrega al consumidor, lo que causa que no funcione correctamente.  Por lo menos este retorna strings UTF-8, asi que no tengo que mungear los valores de retorno
					# este kludge VA A FALLAR si alguno de los caracteres pasados en los string arguments no puede ser renderizado en iso-8859-1, so FIXME FIXME FIXME
					if type(args) in [tuple,list]: return [ turnintoiso(a) for a in args ]
					if type(args) is str: return unicode(args,"utf-8").encode("latin-1")
					if type(args) is unicode: return args.encode("latin-1")
					return args
					
				args = turnintoiso(args)
# 				args = [ unicode(a,locale.getpreferredencoding()) for a in args ]
				returnvalue = method(*args)
				# por lo menos los return values si vienen tal y como debe ser, osea si es un dbquery vienen byte por byte como estaban grabados en la DB.  ¿Por que esa huevada no puede simplemente usar unicode objects?
			except Exception,e:
				returnvalue = e
		marshaled = pickle.dumps(returnvalue)
		conn.sendall(marshaled)
		conn.close()
	print "finishing up"

def start_slave():
	addr = os.tempnam()
	pid = os.fork()
	if pid: sys.exit(0) # soy el papa
	else: #soy el hijo
		try:
			s = socket.socket(socket.AF_UNIX,socket.SOCK_STREAM)
			s.bind(addr)
			s.listen(10) # 10 is the backlog, handily over anything short of extreme
			print addr # no me borren
			print os.getpid() # no me borren
			sys.stdout.flush()
			os.close(0)
			os.close(1)
			os.close(2)
			os.umask(077)
			logfile = file("/dev/null","w")
# 			logfile = file("/home/rudd-o/tmp/logfile","a+b")
			sys.stdout = logfile #pass logfile to write to the logfile above
			sys.stderr = logfile #pass logfile to write to the logfile above
			run_slave(s)
		finally:
			cleanup_slave(addr)

class SlaveError(Exception): pass
class SlaveGone(SlaveError): pass
class SlaveDeaf(SlaveError): pass
class SlaveDiedDuringCommunication(SlaveError): pass
class SlaveDoesNotStart(SlaveError): pass

class Slave:
	def __init__(self):
		# FIXME ver si __file__ siempre es correcto incluso cuando hago chdir()
		command = "python %s --spawn-slave" % commandsplus.shell_escape(__file__)
		status,output,error = commandsplus.getstatusoutputerror(["python",__file__,"--spawn-slave"])
		sockaddr,slave_pid = output.splitlines()[0:2]
		self.sockaddr = sockaddr.strip()
		self.slave_pid = int(slave_pid.strip())
		try:
			self.call(None) # a ping!!!!
		except SlaveError, e:
			raise SlaveDoesNotStart, "The DCOP slave could not be started"
		
	def __del__(self):
		try: os.kill(self.slave_pid,15)
		except: pass
		try: cleanup_slave(self.sockaddr)
		except: pass
		
	def call(self,obj):
		s = socket.socket(socket.AF_UNIX,socket.SOCK_STREAM)
		marshaled = pickle.dumps(obj)
		try:
			s.connect(self.sockaddr)
			s.sendall(marshaled)
			s.shutdown(1)
			result = ""
			while True:
				received = s.recv(2048)
				if not received: break
				result = result + received 
		except socket.error,e:
			if e.args[0] == errno.ENOENT: raise SlaveGone, "The DCOP slave is gone" # no such file or directory
			if e.args[0] == 111: raise SlaveDeaf, "The DCOP slave does not respond" # conn refused
			if e.args[0] == 104: raise SlaveDiedDuringCommunication, "The DCOP slave died during communication" # reset by peer
			else: raise
		unmarshaled = pickle.loads(result)
# 		print "UNMARSHALED:",unmarshaled
		if isinstance(unmarshaled,Exception): raise unmarshaled
		return unmarshaled

class Master:
	def __init__(self):
		self.slave = Slave()
		
	def call(self,appname,objectname,method,*args):
		callobj = Call(appname,objectname,method,*args)
		try: return self.slave.call(callobj)
		except (SlaveGone,SlaveDeaf),e:
			# we redo the slave
			self.slave = Slave()
			return self.slave.call(callobj)
		

def test():
	master = Master()
	import time
# 	time.sleep(1)
	for a in range(100):
 		print commandsplus.getstatusoutputerror("dcop amarok player path")
# 		print master.call("amarok","player","path")
# 	time.sleep(10)
# 	print master.call("amarok","player","path")

if __name__ == "__main__":
	if len(sys.argv) > 1 and sys.argv[1] == "--spawn-slave": start_slave()
	else: test()