/*

Copyright 2008-2011 Clipperz Srl

This file is part of Clipperz Community Edition.
Clipperz Community Edition is an online password manager.
For further information about its features and functionalities please
refer to http://www.clipperz.com.

* Clipperz Community Edition is free software: you can redistribute
  it and/or modify it under the terms of the GNU Affero General Public
  License as published by the Free Software Foundation, either version
  3 of the License, or (at your option) any later version.

* Clipperz Community Edition is distributed in the hope that it will
  be useful, but WITHOUT ANY WARRANTY; without even the implied
  warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
  See the GNU Affero General Public License for more details.

* You should have received a copy of the GNU Affero General Public
  License along with Clipperz Community Edition.  If not, see
  <http://www.gnu.org/licenses/>.

*/

//Clipperz.Async = MochiKit.Async;


if (typeof(Clipperz) == 'undefined') { Clipperz = {}; }
if (typeof(Clipperz.Async) == 'undefined') { Clipperz.Async = {}; }

Clipperz.Async.VERSION = "0.1";
Clipperz.Async.NAME = "Clipperz.Async";

Clipperz.Async.Deferred = function(aName, args) {
	args = args || {};

	Clipperz.Async.Deferred.superclass.constructor.call(this, args.canceller);

	this._args = args;
	this._name = aName || "Anonymous deferred";
	this._count = 0;
	this._shouldTrace = ((CLIPPERZ_DEFERRED_TRACING_ENABLED === true) || (args.trace === true));
	this._vars = null;

	return this;
}

//=============================================================================

Clipperz.Base.extend(Clipperz.Async.Deferred, MochiKit.Async.Deferred, {

	'name': function () {
		return this._name;
	},

	'args': function () {
		return this._args;
	},

	//-----------------------------------------------------------------------------

	'callback': function (aValue) {
		if (this._shouldTrace) {
		//	Clipperz.log("CALLBACK " + this._name, aValue);
			console.log("CALLBACK " + this._name, aValue);
		}

		if (this.chained == false) {
			var message;
			
			message = "ERROR [" + this._name + "]";
			this.addErrback(function(aResult) {
				if (! (aResult instanceof MochiKit.Async.CancelledError)) {
					Clipperz.log(message, aResult);
				}
				return aResult;
			});

			if (this._shouldTrace) {
				var resultMessage;

				resultMessage = "RESULT   " + this._name + " <==";
//				this.addCallback(function(aResult) {
				Clipperz.Async.Deferred.superclass.addCallback.call(this, function(aResult) {
				//	Clipperz.log(resultMessage, aResult);
					console.log(resultMessage, aResult);

					return aResult;
				});
			}
		}

		if (CLIPPERZ_DEFERRED_CALL_LOGGING_ENABLED === true) {
			Clipperz.log("callback " + this._name, this);
		}

		return Clipperz.Async.Deferred.superclass.callback.apply(this, arguments);
	},

	//-----------------------------------------------------------------------------

	'addCallback': function () {
		var message;
		
		if (this._shouldTrace) {
			this._count ++;
			message = "[" + this._count + "] " + this._name + " ";
//			this.addBoth(function(aResult) {Clipperz.log(message + "-->", aResult); return aResult;});
			this.addCallbacks(
			//	function(aResult) {Clipperz.log("-OK- " + message + "-->"/*, aResult*/); return aResult;},
				function(aResult) {console.log("-OK- " + message + "-->"/*, aResult*/); return aResult;},
			//	function(aResult) {Clipperz.log("FAIL " + message + "-->"/*, aResult*/); return aResult;}
				function(aResult) {console.log("FAIL " + message + "-->"/*, aResult*/); return aResult;}
			);
		}

		Clipperz.Async.Deferred.superclass.addCallback.apply(this, arguments);

		if (this._shouldTrace) {
//			this.addBoth(function(aResult) {Clipperz.log(message + "<--", aResult); return aResult;});
			this.addCallbacks(
			//	function(aResult) {Clipperz.log("-OK- " + message + "<--", aResult); return aResult;},
				function(aResult) {console.log("-OK- " + message + "<--", aResult); return aResult;},
			//	function(aResult) {Clipperz.log("FAIL " + message + "<--", aResult); return aResult;}
				function(aResult) {console.log("FAIL " + message + "<--", aResult); return aResult;}
			);
		}
	},

	//=============================================================================

	'addCallbackPass': function() {
		var passFunction;

		passFunction = MochiKit.Base.partial.apply(null, arguments);

		this.addCallback(function() {
			var result;

			result = arguments[arguments.length -1];
			passFunction();

			return result;
		});
	},

	//-----------------------------------------------------------------------------

	'addErrbackPass': function() {
		var passFunction;

		passFunction = MochiKit.Base.partial.apply(null, arguments);

		this.addErrback(function() {
			var result;

			result = arguments[arguments.length -1];
			passFunction();

			return result;
		});
	},

	//-----------------------------------------------------------------------------

	'addBothPass': function() {
		var passFunction;

		passFunction = MochiKit.Base.partial.apply(null, arguments);

		this.addBoth(function() {
			var result;

			result = arguments[arguments.length -1];
			passFunction();

			return result;
		});
	},

	//-----------------------------------------------------------------------------

	'addIf': function (aThenBlock, anElseBlock) {
		this.addCallback(MochiKit.Base.bind(function (aValue) {
			var deferredResult;

			if (!MochiKit.Base.isUndefinedOrNull(aValue) && aValue) {
				deferredResult = Clipperz.Async.callbacks(this._name + " <then>", aThenBlock, null, aValue);
			} else {
				deferredResult = Clipperz.Async.callbacks(this._name + " <else>", anElseBlock, null, aValue);
			}
			
			return deferredResult;
		}))
	},

	//-----------------------------------------------------------------------------

	'addMethod': function () {
		this.addCallback(MochiKit.Base.method.apply(this, arguments));
	},

	//-----------------------------------------------------------------------------

	'addMethodcaller': function () {
		this.addCallback(MochiKit.Base.methodcaller.apply(this, arguments));
	},

	//=============================================================================

	'addLog': function (aLog) {
		if (CLIPPERZ_DEFERRED_LOGGING_ENABLED) {
			this.addBothPass(function(res) {Clipperz.log(aLog + " ", res);});
//			this.addBothPass(function(res) {console.log(aLog + " ", res);});
		}
	},

	//=============================================================================
	
	'acquireLock': function (aLock) {
//		this.addCallback(function (aResult) {
//			return Clipperz.Async.callbacks("Clipperz.Async.acquireLock", [
//				MochiKit.Base.method(aLock, 'acquire'),
//				MochiKit.Base.partial(MochiKit.Async.succeed, aResult)
//			], {trace:false});
//		});

		this.addCallback(MochiKit.Base.method(aLock, 'acquire'));
	},

	'releaseLock': function (aLock) {
//		this.addCallback(function (aResult) {
//			return Clipperz.Async.callbacks("Clipperz.Async.release <ok>", [
//				MochiKit.Base.method(aLock, 'release'),
//				MochiKit.Base.partial(MochiKit.Async.succeed, aResult)
//			], {trace:false});
//		});
//		this.addErrback(function (aResult) {
///console.log("releaseLock.addErrback:", aResult);
//			return Clipperz.Async.callbacks("Clipperz.Async.release <fail>", [
//				MochiKit.Base.method(aLock, 'release'),
//				MochiKit.Base.partial(MochiKit.Async.fail, aResult)
//			], {trace:false});
//		});
		
//		this.addBothPass(MochiKit.Base.method(aLock, 'release'));
		this.addCallbackPass(MochiKit.Base.method(aLock, 'release'));
		this.addErrback(function (anError) {
			aLock.release();

			return anError;
		});
	},

	//=============================================================================

	'collectResults': function (someRequests) {
		this.addCallback(Clipperz.Async.collectResults(this._name + " <collect results>", someRequests, this._args));
	},

	'addCallbackList': function (aRequestList) {
		this.addCallback(Clipperz.Async.callbacks, this._name + " <callback list>", aRequestList, this._args);
	},

	//=============================================================================

	'vars': function () {
		if (this._vars == null) {
			this._vars = {}
		}
		
		return this._vars;
	},

	'setValue': function (aKey) {
		this.addCallback(MochiKit.Base.bind(function (aValue) {
			this.vars()[aKey] = aValue;
			return aValue;
		}, this));
	},
	
	'getValue': function (aKey) {
		this.addCallback(MochiKit.Base.bind(function () {
			return this.vars()[aKey];
		}, this));
	},

	//=============================================================================
	
	__syntaxFix__: "syntax fix"
});

//#############################################################################

Clipperz.Async.DeferredSynchronizer = function(aName, someMethods) {
	this._name = aName || "Anonymous deferred Synchronizer";
	this._methods  = someMethods;

	this._numberOfMethodsDone	= 0;
	this._methodResults = [];

	this._result = new Clipperz.Async.Deferred("Clipperz.Async.DeferredSynchronizer # " + this.name(), {trace:false});
	this._result.addMethod(this, 'methodResults');
	this._result.addCallback(function(someResults) {
		var	cancels;
		var errors;
		var result;

		cancels = MochiKit.Base.filter(function(aResult) { return (aResult instanceof MochiKit.Async.CancelledError)}, someResults);
		
		if (cancels.length == 0) {
			errors = MochiKit.Base.filter(function(aResult) { return (aResult instanceof Error)}, someResults);
		
			if (errors.length == 0) {
//				result = MochiKit.Async.succeed(someResults);
				result = someResults;
			} else {
				result = MochiKit.Async.fail(someResults);
			}
		} else {
			result = MochiKit.Async.fail(cancels[0]);
		}

		return result;
	}/*, this._methodResults */);
	
	return this;
}

MochiKit.Base.update(Clipperz.Async.DeferredSynchronizer.prototype, {

	//-----------------------------------------------------------------------------

	'name': function() {
		return this._name;
	},

	//-----------------------------------------------------------------------------

	'methods': function() {
		return this._methods;
	},
	
	'methodResults': function() {
		return this._methodResults;
	},

	//-----------------------------------------------------------------------------

	'result': function() {
		return this._result;
	},

	//-----------------------------------------------------------------------------

	'numberOfMethodsDone':function() {
		return this._numberOfMethodsDone;
	},

	'incrementNumberOfMethodsDone': function() {
		this._numberOfMethodsDone ++;
	},

	//-----------------------------------------------------------------------------
	
	'run': function(args, aValue) {
		var deferredResults;
		var i, c;
		
		deferredResults = [];
		args = args || {};

		c = this.methods().length;
		for (i=0; i<c; i++) {
			var deferredResult;
			var	methodCalls;
			var ii, cc;

//console.log("TYPEOF", typeof(this.methods()[i]));
			if (typeof(this.methods()[i]) == 'function') {
				methodCalls = [ this.methods()[i] ];
			} else {
				methodCalls = this.methods()[i];
			}
			
			cc = methodCalls.length;
			deferredResult = new Clipperz.Async.Deferred("Clipperz.Async.DeferredSynchronizer.run => " + this.name() + "[" + i + "]", args);
			for (ii=0; ii<cc; ii++) {
				deferredResult.addCallback(methodCalls[ii]);
			}
			deferredResult.addBoth(MochiKit.Base.method(this, 'handleMethodCallDone', i));

			deferredResults.push(deferredResult);
		}
		
		for (i=0; i<c; i++) {
			deferredResults[i].callback(aValue);
		}
		
		return this.result();
	},
	
	//-----------------------------------------------------------------------------

	'handleMethodCallDone': function(anIndexValue, aResult) {
		this.incrementNumberOfMethodsDone();
		this.methodResults()[anIndexValue] = aResult;
		
		if (this.numberOfMethodsDone() < this.methods().length) {
			//	nothing to do here other than possibly log something
		} else if (this.numberOfMethodsDone() == this.methods().length) {
			this.result().callback();
		} else if (this.numberOfMethodsDone() > this.methods().length) {
			alert("Clipperz.Async.Deferred.handleMethodCallDone -> WTF!");
			//	WTF!!! :(
		}
		
	},

	//-----------------------------------------------------------------------------

	__syntaxFix__: "syntax fix"
});

//#############################################################################

MochiKit.Base.update(Clipperz.Async, {

	'callbacks': function (aName, someFunctions, someArguments, aCallbackValue) {
		var deferredResult;
		var i, c;
		
		deferredResult = new Clipperz.Async.Deferred(aName, someArguments);
		c = someFunctions.length;
		for (i=0; i<c; i++) {
			deferredResult.addCallback(someFunctions[i]);
		}
		deferredResult.callback(aCallbackValue);

		return deferredResult;
	},

	//-------------------------------------------------------------------------

	'forkAndJoin': function (aName, someMethods, args) {
		return MochiKit.Base.partial(function (aName, someMethods, args, aValue) {
			var synchronizer;
			var	result;
		
			args = args || {};
			synchronizer = new Clipperz.Async.DeferredSynchronizer(aName, someMethods);
			result = synchronizer.run(args, aValue);
		
			return result;
		}, aName, someMethods, args);
	},
	
	//-------------------------------------------------------------------------

	'collectResults': function(aName, someRequests, args) {
		return MochiKit.Base.partial(function(aName, someRequests, args, aValue) {
			var deferredResult;
			var requestKeys;
			var methods;

			requestKeys = MochiKit.Base.keys(someRequests);
			methods = MochiKit.Base.values(someRequests);

			deferredResult = new Clipperz.Async.Deferred(aName, args);
			deferredResult.addCallback(Clipperz.Async.forkAndJoin(aName + " [inner forkAndJoin]", methods, args));
			deferredResult.addBoth(function(someResults) {
				var returnFunction;
				var results;
				var i,c;
				var result;

				if (someResults instanceof MochiKit.Async.CancelledError) {
					returnFunction = MochiKit.Async.fail;
					result = someResults;
				} else {
					if (someResults instanceof Error) {
						returnFunction = MochiKit.Async.fail;
						results = someResults['message'];
					} else {
						returnFunction = MochiKit.Async.succeed;
						results = someResults;
					}

					result = {};
			
					c = requestKeys.length;
					for (i=0; i<c; i++) {
						result[requestKeys[i]] = results[i];
					}
				} 

				return returnFunction.call(null, result);
			});
			deferredResult.callback(aValue);
		
			return deferredResult;
		}, aName, someRequests, args);
	},

	//-------------------------------------------------------------------------

	'collectAll': function (someDeferredObjects) {
		var deferredResult;
		
		deferredResult = new MochiKit.Async.DeferredList(someDeferredObjects, false, false, false);
		deferredResult.addCallback(function (aResultList) {
			return MochiKit.Base.map(function (aResult) {
				if (aResult[0]) {
					return aResult[1];
			} else {
					throw aResult[1];
					}
			}, aResultList);
		});
		
		return deferredResult;
	},

	//-------------------------------------------------------------------------

	'setItem': function (anObject, aKey, aValue) {
		anObject[aKey] = aValue;
			
		return anObject;
	},

	'setItemOnObject': function (aKey, aValue, anObject) {
		anObject[aKey] = aValue;
			
		return anObject;
	},

	'setDeferredItemOnObject': function (aKey, aDeferredFunction, anObject) {
		return Clipperz.Async.callbacks("Clipperz.Async.setDeferredItemOnObject", [
			aDeferredFunction,
			MochiKit.Base.partial(Clipperz.Async.setItem, anObject, aKey)
		], {trace:false}, anObject);
	},

	//-------------------------------------------------------------------------

	'deferredIf': function (aName, aThenBlock, anElseBlock) {
		return function (aValue) {
			var deferredResult;

			if (!MochiKit.Base.isUndefinedOrNull(aValue) && aValue) {
				deferredResult = Clipperz.Async.callbacks(aName + " <then>", aThenBlock, null, aValue);
			} else {
				deferredResult = Clipperz.Async.callbacks(aName + " <else>", anElseBlock, null, aValue);
			}
			
			return deferredResult;
		}
	},

	//-------------------------------------------------------------------------

	'log': function(aMessage, aResult) {
		if (CLIPPERZ_DEFERRED_LOGGING_ENABLED) {
			Clipperz.log(aMessage + " ", aResult);
		}
		
		return aResult;
	},
	
	//=========================================================================

	'deferredCompare': function (aComparator, aDeferred, bDeferred) {
		var deferredResult;

		deferredResult = new Clipperz.Async.Deferred("Clipperz.Async.deferredCompare", {trace:false});
		deferredResult.addCallback(Clipperz.Async.collectAll, [aDeferred, bDeferred]);
		deferredResult.addCallback(function (someResults) {
			var result;

			if (aComparator(someResults[0], someResults[1]) > 0) {
				result = MochiKit.Async.succeed();
			} else {
				result = MochiKit.Async.fail();
			};

			return result;
		});
		deferredResult.callback();
		
		return deferredResult;
	},

	//-------------------------------------------------------------------------

	'insertIntoSortedArray': function (anObject, aDeferredComparator, aSortedResult) {
		var deferredResult;
		var i, c;

		if (aSortedResult.length == 0) {
			deferredResult = MochiKit.Async.succeed([anObject]);
		} else {
			deferredResult = new Clipperz.Async.Deferred("Clipperz.Async.insertIntoSortedArray", {trace:false});
			c = aSortedResult.length + 1;
			for (i=0; i<c; i++) {
				deferredResult.addCallback(function (aDeferredComparator, aObject, bObject, aContext) {
					var innerDeferredResult;
					
					innerDeferredResult = new Clipperz.Async.Deferred("Clipperz.Async.insertIntoSortedArray <inner compare>", {trace:false});
					innerDeferredResult.addCallback(aDeferredComparator, aObject, bObject);
					innerDeferredResult.addErrback(MochiKit.Async.fail, aContext);
					innerDeferredResult.callback();
					
					return innerDeferredResult;
				}, aDeferredComparator, anObject, aSortedResult[i], i);
			}
			deferredResult.addMethod(aSortedResult, 'push', anObject);
			deferredResult.addErrback(function (anError) {
				aSortedResult.splice(anError.message, 0, anObject);
			})
			deferredResult.addBoth(MochiKit.Async.succeed, aSortedResult);
			deferredResult.callback();
		}
		
		return deferredResult;
	},
	
	//-------------------------------------------------------------------------

	'deferredSort': function (aDeferredComparator, someObjects) {
		var deferredResult;
		var i, c;
				
		deferredResult = new Clipperz.Async.Deferred("Clipperz.Async.deferredSort", {trace:false});
		c = someObjects.length;
		for (i=0; i<c; i++) {
			deferredResult.addCallback(Clipperz.Async.insertIntoSortedArray, someObjects[i], aDeferredComparator);
			if ((i % 50) == 0) {
//	console.log("######### sort wait ##########");
				deferredResult.addCallback(MochiKit.Async.wait, 0.5);
			}
		}		
		deferredResult.callback([]);

		return deferredResult;
	},

	//=========================================================================

	'deferredFilter': function (aFunction, someObjects) {
		var	deferredResult;
		var	i, c;

		deferredResult = new Clipperz.Async.Deferred("Clipperz.Async.deferredFilter", {trace:false});
		c = someObjects.length;
		for (i=0; i<c; i++) {
			deferredResult.addCallback(function (aFunction, anObject, anIndex, aResult) {
				var innerDeferredResult;
				
				innerDeferredResult = new Clipperz.Async.Deferred("Clipperz.Async.deferredFilter <inner - " + anIndex + ">", {trace:false});
				innerDeferredResult.addCallback(aFunction, anObject);
				innerDeferredResult.addCallback(function (aFilterResult) {
					if (aFilterResult) {
						aResult.push(anObject);
					};
				});
				innerDeferredResult.addBoth(MochiKit.Async.succeed, aResult);
				innerDeferredResult.callback();

				return innerDeferredResult;
			}, aFunction, someObjects[i], i);
		}
		deferredResult.callback([]);
		
		return deferredResult;
	},
	
	'forEach': function (aFunction) {
		return MochiKit.Base.partial(function (aFunction, anIterable) {
			MochiKit.Iter.forEach(anIterable, aFunction);
		}, aFunction);
	},

	//=========================================================================

	'or': function (someValues) {
		return Clipperz.Async.callbacks("Clipperz.Async.or", [
			MochiKit.Base.values,
			MochiKit.Base.flattenArguments,
//function (aValue) { console.log("Record.hasAnyCleanTextData - flatten", aValue); return aValue; },
			function(someInnerValues) {
				return MochiKit.Iter.some(someInnerValues, MochiKit.Base.operator.identity);
			}
		], {trace:false}, someValues);
	},

	//=========================================================================

	'clearResult': function () {},
	
	//=========================================================================
	__syntaxFix__: "syntax fix"
});


//#############################################################################

CLIPPERZ_DEFERRED_LOGGING_ENABLED = true;
CLIPPERZ_DEFERRED_TRACING_ENABLED = false;
CLIPPERZ_DEFERRED_CALL_LOGGING_ENABLED = false;
