#!/usr/bin/env python
# Tutorial code. If you use this in production, you are insane.

I = lambda x: x

import failure

class AlreadyCalledError(Exception):
	pass

def print_result(result): # test only
	print(result)

class Deferred(object):
	""" I represent a long-running operation that will eventually result in a value. 
	>>> d = Deferred()
	>>> d.addCallback(print_result) and None
	>>> d.callback(4711)
	4711
	>>> d = Deferred()
	>>> d.addCallback(lambda x: x + 1) and None
	>>> d.addCallback(print_result) and None
	>>> d.callback(4711)
	4712
	"""
	def __init__(self):
		self.callbacks = []
		self.errbacks = []
		self.called = False
		self.paused = 0
		self._runningCallbacks = False
	def addCallbacks(self, callback, errback=None, callbackArgs=None, callbackKeywords=None, errbackArgs=None, errbackKeywords=None):
		assert(hasattr(callback, "__call__"))
		assert(errback is None or hasattr(callback, "__call__"))
		self.callbacks.append(((callback, callbackArgs, callbackKeywords), (errback or I, errbackArgs, errbackKeywords)))
		if self.called:
			self._runCallbacks()
		return(self)
	def addCallback(self, callback, *args, **kwargs):
		return(self.addCallbacks(callback, None, args, kwargs))
	def addErrback(self, errback, *args, **kwargs):
		return(self.addCallbacks(I, errback, errbackArgs=args, errbackKeywords=kwargs))
	def addBoth(self, bothback, *args, **kwargs):
		return(self.addCallbacks(callback, callback, args, kwargs, args, kwargs))
	def chainDeferred(self, d):
		return(self.addCallbacks(d.callback, d.errback))
	def callback(self, result):
		assert(not isinstance(result, Deferred))
		self._startRunCallbacks(result)
	def errback(self, failure=None):
		if failure is None:
			failure = failure.Failure(fail)
		self._startRunCallbacks(failure)
	def _startRunCallbacks(self, result):
		if self.called:
			print >>sys.stderr, "whoops! Deferred has already been called."
			raise AlreadyCalledError()
		self.called = True
		self.result = result
		self._runCallbacks()
	def pause(self):
		self.paused += 1
	def unpause(self):
		self.paused -= 1
		if self.paused == 0 and self.called:
			self._runCallbacks()
	def _runCallbacks(self):
		if self.paused or self._runningCallbacks:
			return
		while self.callbacks:
			item = self.callbacks.pop(0)
			callback, args, kw = item[isinstance(self.result, failure.Failure)]
			args = args or ()
			kw = kw or {}
			try:
				self._runningCallbacks = True
				self.result = callback(self.result, *args, **kw)
				self._runningCallbacks = False
				if isinstance(self.result, Deferred):
					self.pause()
					self.result.addBoth(self._continue) # recurses
					break
			except:
				self.result = failure.Failure()
				self._runningCallbacks = False
		if self.result is not None:
			print("result is %r" % self.result)

if __name__ == "__main__":
	import doctest
	doctest.testmod()
