/*

Copyright 2008-2015 Clipperz Srl

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

* Clipperz 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 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. 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);
		}

		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);
					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) {Clipperz.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) {Clipperz.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);});
		}
	},

	//=============================================================================
	
	'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) {
///Clipperz.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;

//Clipperz.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) {
//	Clipperz.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) { Clipperz.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;