/* A simple layer between the List widget and an AR Mojo Service that supports:
 * - separate list and count service calls
 * - list reset on service call single param change (e.g. filter)
 * - list reset on "updated: true" message from backend
 * - subscribe reset on list reset
 * - optional list item munger callback
 
Copyright 2009 Palm, Inc.  All rights reserved.

*/
ActiveRecordListBridge = Class.create({

	// listFn should be pre-scoped and accept the following params in order: (serviceParam, callback, subscriberId, offset, limit)
	// countFn should be pre-scoped and accept the following params in order: (serviceParam, callback)
	// mungeFn should be pre-scoped and accept the following param: {list:(list item window), offset, limit}
	// initialServiceParam (optional) sets the initial value passed to listFn and countFn
	initialize: function(listFn, countFn, mungeFn, initialServiceParam) {
		this.baseListFn = listFn;
		this.baseCountFn = countFn;
		this.mungeFn = mungeFn;
		
		this.setParam(initialServiceParam);
		this.setDoCount(true);
		
		this._handleResponseBound = this._handleResponse.bind(this);
		this._handleResponseWrapperBound = this._handleResponseWrapper.bind(this);
		
		this._handleCount = this._handleCountResponse.bind(this, false);
		this._handleUpdateCount = this._handleCountResponse.bind(this, true);
		
		this.fetchInitialItems = this.fetchInitialItems.bind(this);
		this.fetchItems = this.fetchItems.bind(this);
		
		this.timings = [];
		
		this.optionsQueue = [];
		
		this.throttler = new Throttler(this.makeRequest.bind(this), 5000, 30000, 5000);
		this.throttler.enable();
		
	},
	
	fetchInitialItems: function(listWidget, offset, limit, options) {
		// don't subscribe for the initial delayed-load windows
		
		//Mojo.Log.info("list requesting INITIAL items w/ offset/limit:", offset, "/", limit);
		
		this.listWidget = listWidget;
		
		var responseHandler = this._handleResponseBound.curry(listWidget, options);
		
		var subscriberId = this.subscriberId;
		
		if (subscriberId) {
			// subscribe for the first window, but for the rest of the initial chunks,
			// do one-off requests.
			subscriberId = -1;
		}
		
		this.listFn(responseHandler, subscriberId, this.resetFlag, offset, limit);
		this.resetFlag = undefined;
		this.timings.push(Date.now());
	},
	
	// Use this as the list items callback
	fetchItems: function(listWidget, offset, limit, options) {
		Mojo.Log.info("list requesting items w/ offset/limit:", offset, "/", limit);
		this.listWidget = listWidget;
		
		var responseHandler;
		
		if (!this.request) {
				responseHandler = this._handleResponseWrapperBound.curry(listWidget);
		}
		
		this.optionsQueue.push(options);
		
		var newRequest = this.listFn(responseHandler, this.subscriberId, this.resetFlag, offset, limit);
		this.resetFlag = undefined;
		if (!this.request) {
			// this is the first request, keep the request around to receive responses
			this.request = newRequest;
		}
		this.timings.push(Date.now());
	},
	
	// set up a new service param to use (if any)
	setParam: function(serviceParam) {
		Mojo.Log.info("THIS IS THE PARAM TO PASS TO SERVICE + \"", serviceParam, "\"");
		this.listFn = this.baseListFn.curry(serviceParam);
		this.countFn = this.baseCountFn.curry(serviceParam);
		this._reset();
	},
	
	setDoCount: function(doCount) {
		this.doCount = doCount;
	},
	
	setHandleUpdateCount: function(handler) {
		this.updateCountHandler = handler;
	},
	
	immediateUpdate:function(){
		this.makeRequest();
		this.notifySentRequest();
	},
	
//	setThrottlerWrapper: function(wrapper){
//		this._throttleResponseWrapper = wrapper;	
//	},
	
	minimizeSubscriptions:function(){
		this.throttler.minimize();
	},
	
	maximizeSubscriptions:function(){
		this.throttler.maximize();
	},
	
	disableSubscriptions:function(){
		this.throttler.disable();
	},
	
	enableSubscriptions:function(){
		this.throttler.enable();
	},
	
	
	cleanup: function() {
		if (this.request) {
			Mojo.Log.info("CANCELLING LIST QUERY");
			this.request.cancel();
			this.request = undefined;
		}
		if (this.sentCountRequest) {
			Mojo.Log.info("CANCELLING COUNT QUERY");
			this.sentCountRequest.cancel();
			this.sentCountRequest = undefined;
		}
		this.timings = [];
		this.optionsQueue = [];
		
		this.subscriberId = undefined;
		this.updateInProgress = false;
		this.setDoCount(true);
		
	},
	
	doUpdate: function() {
		this.sentCountRequest = this.countFn(this._handleUpdateCount.curry(this.listWidget, null));
		this.timings.push(Date.now());
	},
	
	_reset: function() {
		//this.cleanup();
		//this.subscriberId = undefined;
		// set the reset flag so the next list request goes out with reset=true to invalidate backend cache
		Mojo.Log.info("AR BRIDGE: set reset=true");
		this.resetFlag = true;
		this.updateInProgress = false;
		this.setDoCount(true);
		delete this.sentCountRequest;
		
		// @@ Notify list to reset here or in app code?  Need access to controller to reset scroller and call 'modelChanged()'...
	},
	
	_processUpdateCount: function(listWidget, count) {
		// @@ This is the only piece of bridge code that is needed by the new 'sortKey'-based AR backend.
		// @@
		// When the list data is updated, the update may have affected the sortKey sentinels kept by the backend to speed up queries.
		//  But we currently don't know if this is the case, so for now, send reset=true on the next request to re-initialize the backend.
		this._reset();
		
		Mojo.Log.info("**** INVALIDATING LIST CACHE AND SETTING LIST COUNT TO: ", count);
		listWidget.mojo.setLengthAndInvalidate(count);
	},
	
	_handleCountResponse: function(asPartOfUpdate, listWidget, options, response) {
		Mojo.assert(response !== undefined);
		Mojo.assert(response.count !== undefined);
		
		var begin = this.timings.shift();
		Mojo.Log.info("*** count ROUND TRIP TIME = ", (Date.now() - begin), " for count=", response.count);
		
		// check if the count is different-- ignore if count is the same AND it's not an update response
		if (asPartOfUpdate) {
			
			if (this.updateCountHandler) {
				this.updateCountHandler(this._processUpdateCount.bind(this, listWidget, response.count));
			} else {
				// @@ old style simple handling...
				// does not play nice with chunking loads
				this._processUpdateCount(listWidget, response.count);
			}
			
		} else {
			
			if (listWidget.mojo.setCount) {
				listWidget.mojo.setCount(response.count);
			}
			
			if (options && options.handleCount) {
				options.handleCount(listWidget, response.count);
			}
			
		}
		
	},
	
	_doUpdate: function(listWidget) {
		Mojo.Log.warn("contacts app: actually updating list NOW");
		// this fn is delayed and first needs to re-evaluate whether it should still run
		
		if (!this.updateInProgress) {
			// doesn't need to run any more... maybe someone called this.setParam()?
			return;
		}
		
		// unset the 'in progress' flag so any updates from the db after this exact moment get processed normally.
		// otherwise, the update may be lost if the flag is set later than now.
		this.updateInProgress = false;
		
		this.doUpdate();
	},
	
	_handleResponseWrapper: function(listWidget, response) {
		Mojo.assert(response !== undefined);
		//if(response === undefined ) return console.log("//I shouldn't be calling this since i dont' know why it happenede : 10:58");
		if (response.updated) {
			this.throttler.request();
		} else if(response.list){
			this.throttler.notifyRequestFulfilled();
			var options = this.optionsQueue.shift();
			this._handleResponse(listWidget, options, response);
		}
		

	},
	
	makeRequest:function(listWidget){
		Mojo.Log.warn("contacts app: received updated=true from list query");
		if (!this.updateInProgress) { 
			this.updateInProgress = true;
				
			Mojo.Log.info("[[[[[[[[[[[[[[[[[ Update Received: starting deferred update handler");
			this._doUpdate(listWidget);
		}
	},
	
	_handleResponse: function(listWidget, options, response) {
		//Mojo.Log.info("*************************** WHOLE MESSAGE: " + Object.toJSON(response));
		var setLengthToWindowSize = false;
		var windowSize;
		
		Mojo.assert(response.list !== undefined);
		Mojo.assert(response.offset !== undefined);
		Mojo.assert(response.limit !== undefined);
		
		if (options && options.checkfn) {
			var go = options.checkfn(this._handleResponseBound.curry(listWidget, options, response), options, response);
			if (!go) {
				return;
			}
		}
		
		var begin = this.timings.shift();
		Mojo.Log.info("*** list ROUND TRIP TIME = ", (Date.now() - begin), " for offset/limit/actual", response.offset, "/", response.limit, "/", response.list.length);
		
		if (!this.subscriberId && response.subscriberId) {
			this.subscriberId = response.subscriberId;
		}
		
		if (!this.sentCountRequest) {
			if (response.list.length < response.limit) {
				Mojo.Log.info("window size < asked for limit, will set initial count = window size");
				setLengthToWindowSize = true;
				windowSize = response.list.length;
			}
			else if (this.doCount) {
				// the total list length may be greater than or equal to this window size, need to ask for the full count
				Mojo.Log.info("window size >= asked for limit, issuing service query to find count");
				this.sentCountRequest = this.countFn(this._handleCount.curry(listWidget, options));
				this.timings.push(Date.now());
			} else {
				// have to make the count badge do something reasonable here
				if (listWidget.mojo.setCount) {
					listWidget.mojo.setCount('...');
				}
			}
		}
		
		if (this.mungeFn) {
			//var begin = new Date().getTime();
			this.mungeFn(response);
			//var end = new Date().getTime();
			//			Mojo.Log.info("MUNGER TOOK " + (end - begin) + "ms");
		}
		
		//Mojo.Log.info("LIST UPDATE at offset=", response.offset, " items=", response.list.pluck('sortKey').toJSON());
		listWidget.mojo.noticeUpdatedItems(response.offset, response.list);
		//Mojo.Log.info("LIST UPDATED at offset=" + response.offset);
		
		if (setLengthToWindowSize) {
			this.timings.push(Date.now());
			this._handleCount(listWidget, options, { count: windowSize });
			this.sentCountRequest = { cancel: function(){} };
		}
		
		if (options && options.nextRequest) {
			options.nextRequest();
		}
		
	}
	
});
