#!/usr/bin/env python

import sys
import imp
import cPickle
from gmisclib import g_pipe
from gmisclib import load_mod
import traceback
from gmisclib import die

PROTOCOL = cPickle.HIGHEST_PROTOCOL

class RemoteException(Exception):
	def __init__(self, *s):
		Exception.__init__(self, *s)

def make_remote_exception(name, mod, args, trtext):
	return RemoteException("Remote Exception=", (mod, name),
					"args=", [str(q) for q in args],
					"traceback=", trtext
					)


def deal_with_arbitrary_exception():
	ty, ob, trb = sys.exc_info()
	tmp = tuple(traceback.format_tb(trb))
	del trb
	log( "DWAE, construcing remote exception" )
	return make_remote_exception(ty.__name__, ty.__module__,
					ob.args, tmp
					)


class io_grabber(object):
	"""This buffers text, transmitting it in units of lines.
	That's important at the other end if there are multiple
	connections, because fancyprint adds a prefix at the
	beginning of each write, assuming it is the beginning
	of each line."""

	def __init__(self):
		self.store = []

	def write(self, s):
		self.store.append( s )

	def writelines(self, x):
		self.store.extend( x )

	def flush(self):
		pass

	def close(self):
		pass

	def get_io(self):
		tmp = self.store
		self.store = []
		return tmp



class response(object):
	def __init__(self, ety, val, out, err):
		self.val = val
		self.out = out
		self.err = err
		self.ety = ety


_ffc = {}
def _find_func(mname, path, fname):
	key = (mname, path, fname)
	try:
		return _ffc[ key ]
	except KeyError:
		m = load_mod.load(mname, path)
		if isinstance(fname, tuple):
			f = m
			for fc in fname:
				f = getattr(f, fc)
		else:
			f = getattr(m, fname)
		_ffc[ key ] = f
	return f


class request(object):
	def __init__(self, path, mname, fname, a, kw):
		self.path = path
		self.mname = mname
		self.fname = fname
		self.a = a
		self.kw = kw


	def call(self):
		f = _find_func(self.mname, self.path, self.fname)
		ety = False
		try:
			v = f(*(self.a), **(self.kw))
		except:
			v = deal_with_arbitrary_exception()
			ety = True
		return response(ety, v, [], [])


def set_remote_path(pa):
	if pa is None:
		return
	pth = []
	imp.acquire_lock()
	for pc in pa:
		if pc is None:
			pth.extend( sys.path)
		else:
			pth.append(pc)
	sys.path = pth
	imp.release_lock()
	return pth


def echo(s):
	return s


class remote_fcn(object):
	def __init__(self, connection, pypath, mname, fname):
		self.connection = connection
		if pypath is None:
			self.pypath = pypath
		else:
			self.pypath = tuple(pypath)
		self.mname = mname
		assert isinstance(fname, str) or isinstance(fname, tuple)
		if isinstance(fname, str):
			self.fname = fname
		else:
			self.fname = tuple(fname)

	def __call__(self, *a, **kw):
		# print "remote_fcn.__call__"
		tmp = self.connection.call(self.pypath, self.mname, self.fname, a, kw)
		# print "remote_fcn.__call__: got response"
		sys.stdout.writelines(tmp.out)
		sys.stdout.flush()
		sys.stderr.writelines(tmp.err)
		sys.stderr.flush()
		if tmp.ety:
			# print "remote_fcn raised exception", tmp.val
			raise tmp.val
		# print "remote_fcn result", tmp.val
		return tmp.val


class connection(object):
	def __init__(self, execpath, arglist):
		self.closed = False
		self.wfd, self.rfd = g_pipe.popen2(execpath, arglist)
		
	def call(self, pypath, mname, fname, a, kw):
		# print 'connection.call'
		# self.wfd.writelines('q\n')
		cPickle.dump(request(pypath, mname, fname, a, kw), self.wfd, PROTOCOL)
		# print 'connection.dumped'
		self.wfd.flush()
		# print 'connection.flushed'
		# tmp = self.rfd.readline()
		# assert tmp=='z\n', "tmp=%s(%d)" % (tmp, len(tmp))
		tmp = cPickle.load(self.rfd)
		# print 'connection.got response'
		return tmp


	def close(self):
		if not self.closed:
			self.wfd.close()
			self.rfd.close()
			self.closed = True

	def __del__(self):
		self.close()

def log(*s):
	# sys.stderr.writelines(str(s))
	# sys.stderr.writelines('\n')
	pass


def server():
	log('Server starting')
	out = sys.stdout
	sys.stdout = sys.stderr
	while True:
		log('serverloop')
		try:
			# tmp = sys.stdin.readline()
			# if not tmp:
				# log("EOF on readline")
				# break
			# assert tmp=='q\n', "tmp=%s(%d)" % (tmp, len(tmp))
			rq = cPickle.load(sys.stdin)
			log('loaded')
		except EOFError:
			log("EOF error")
			break
		log('call')
		back = rq.call()
		log('return')
		# out.writelines('z\n')
		cPickle.dump(back, out, PROTOCOL)
		out.flush()
		log('loopend')


def test_rf2(i):
	return [8] * i

def test():
	c = connection(None, ['python', '-c', 'import gyropy; gyropy.g_remote_exec.server()'])
	f = remote_fcn(c, None, 'math', 'sqrt')
	g = remote_fcn(c, None, 'gyropy', ('g_remote_exec', 'test_rf2'))
	for i in range(1000):
		err = abs(f(i)**2 - i)
		if err > 0.001:
			die.die("Wrong answer")
	try:
		f(-1)
	except RemoteException:
		pass
	else:
		die.die("No exception!")
	f2 = remote_fcn(c, None, "gyropy", ("g_remote_exec", "set_remote_path"))
	if f2([None, "/tmp"])[-1] != "/tmp":
		die.die("Bad set remote path")
	for i in range(98, 1000, 3):
		if len(g(i)) != i:
			die.die("Wrong answer")
	c.close()



if __name__ == '__main__':
	if len(sys.argv)>1 and sys.argv[1] == '-test':
		for i in range(10):
			test()
		print 'OK'
		sys.exit(0)
	server()
