/* Copyright 2009 Palm, Inc.  All rights reserved. */

var detailSceneName = "detail";

ListAssistant = Class.create(App.Scene,{
	initialize: function($super, params){
		$super();
		if (params) {
			this.suppressPopScene = params.suppressPopScene
			this.focusWindow = params.focusWindow;
			this.mode = params.mode;
			this.exclusions = params.exclusions;
			this.crossPushMessage = params.message;
			this.crossAppCallback = params.callback;
		}
		this.delayedFetchBound = this.delayedFetch.bind(this);
		this.checkResponse = this.checkResponse.bind(this);
		this.handleCount = this.handleCount.bind(this);
		this.handleUpdateCount = this.handleUpdateCount.bind(this);
		
		this.useChunking = true;
		
		this.filterGeneration = 0;
		
	},
	
	transformListResults: function(data){
		if (!this.sortOrder) {
			Mojo.Log.error("No defined sort order");
		}
		
		for (var i = 0; i < data.list.length; i++) {
			var c = data.list[i];
			if (this.exclusions) {
				if (this.exclusions.indexOf(c.id) != -1) {
					c.exclude = "exclude";
				}
			}
			Contact.fixup(c);
//			c.firstName = (data.offset + i + 1) + ' ' + c.firstName;
			this.decorator.decorateListItem(c, this.filter, this.sortOrder);
		}
		
		this.refreshEmptyTemplate();
		
		// FIXME: workaround NOV-32527 by only showing the GAL div if the list len < 500
		this.emptyHolder = this.controller.get('emptyHolder');
		if (data.offset == 0 && this.hasEASAccount) {
			if (this.filter && this.filter.length > 0 && data.list.length < 500 && this.hasEASAccount) {
				this.listDiv.removeClassName("no-eas-account");
				this.gal.show();
			}
			else {
				this.listDiv.addClassName("no-eas-account");
				this.gal.hide();
			}
		}
		
		if (this.useChunking && data.list.length >= data.limit) {
			// TODO: Adil, please make this pretty!
			data.list.push({ formattedText: $L("Loading..."), listPic: "images/loading.png" });
			
			//Mojo.Log.info("ADDED LOADING DIV... o/l=" + data.offset + '/' + data.limit);
		}
		
	},
	
	processCount: function(listWidget, count, options) {
		// only use chunking for the initial load of the list.  after that, revert to normal list behavior.
		this.useChunking = false;
		
		Mojo.Log.info("NOW processing the list count");
		if (listWidget.mojo.getLength() != count) {
			Mojo.Log.info("calling list.setLength w/ count=", count);
			listWidget.mojo.setLength(count);
		} else {
			Mojo.Log.info("got count response but list length is already set to: ", count);
		}
		
		// @@ sometimes the filterlist clears the count LATE... so try again to set the count
		if (listWidget.mojo.setCount) {
			listWidget.mojo.setCount(count);
		}
		
		delete this.processCountSaved;
		if (options) {
			delete options.nextRequest;
		}
		
		Mojo.Log.info("chunking took: " + (Date.now() - this.startChunking) + "ms");
		
		// handle any updates that occurred during chunking NOW
		if (this.processUpdateCountSaved) {
			this.processUpdateCountSaved();
			delete this.processUpdateCountSaved;
		}
		
	},
	
	handleCount: function(listWidget, count) {
		Mojo.Log.info("got count, saving it for later processing");
		this.processCountSaved = this.processCount.bind(this, listWidget, count);
		this.listCount = count;
	},
	
	handleUpdateCount: function(handler) {
		
		if (this.useChunking) {
			Mojo.Log.info("got UPDATE count, saving it for later processing");
			this.processUpdateCountSaved = handler;
		} else {
			handler();
		}
		
	},
	
	checkResponse: function(handler, options, response) {
		// return false to abort processing of this response
		// return false and hold onto a reference to a given fn to pause processing response (call it later)
		// return true to process response immediately
		
		if (options.filterGeneration != this.filterGeneration) {
			Mojo.Log.info("not processing invalidated request: filtergen=" + options.filterGeneration + " curr filter=" + this.filterGeneration + " o/l=" + response.offset + '/' + response.limit);
			return false;
		}
		
		if (this.useChunking && this.scrolling) {
			Mojo.Log.info("PAUSING RESPONSE HANDLING...");
			this.savedHandler = handler;
			return false;
		}
		
		return true;
	},
	
	delayedFetch: function(filterGeneration, listWidget, offset, limit, iteration) {
		if (filterGeneration != this.filterGeneration) {
			//Mojo.Log.info("not processing invalidated request: filtergen=" + filterGeneration + " curr filter=" + this.filterGeneration + " o/l=" + offset + '/' + limit);
			return;
		}
		//Mojo.Log.info("delayed fetch, filtergen=" + filterGeneration + " offset=" + offset);
		
		if (this.scrolling) {
			
			if (!this.useChunking) {
				Mojo.Log.error("ERROR... scrolling in progress, but chunking is somehow off...");
			}
			
			this.savedFetch = this.delayedFetchBound.curry(filterGeneration, listWidget, offset, limit, iteration);
			return;
		}
		
		var chunkOffset = offset + (iteration * ListAssistant.chunkSize);
		var chunkSize = Math.min(ListAssistant.chunkSize, (offset + limit) - chunkOffset);
		
		if (typeof this.listCount != "undefined" && chunkOffset >= this.listCount || chunkOffset >= offset + limit) {
			Mojo.Log.info("STOPPING CHUNKED REQUESTS, reached the end of the list or chunking region. offset=" + chunkOffset + " size=" + this.listCount);
			
			// remove the last 'Loading' div, since we're done
			var loadingIndex = Math.min(this.listCount, chunkOffset);
			if (loadingIndex < listWidget.mojo.getLength()) {
				Mojo.Log.info("removing 'loading' item at offset=" + loadingIndex + " listlen=" + listWidget.mojo.getLength());
				listWidget.mojo.noticeRemovedItems(loadingIndex, 1);
			}
			
			if (this.processCountSaved) {
				this.processCountSaved();
			} else {
				// @@ this can happen sometimes when the FilterList decides to ask for a window of 0/300 with a given filter TWICE
				// @@ but there's no way to distinguish the dupe request and a refresh due to an async backend update.
				// so just do it anyway.
			}
			
			return;
		}
		
		//Mojo.Log.info("STARTING REQUEST FOR o/l=" + chunkOffset + "/" + chunkSize);
		
		var that = this;
		var options = {
			checkfn: this.checkResponse,
			filterGeneration: filterGeneration,
			handleCount: this.handleCount,
			nextRequest: function() {
				that.delayedFetchBound.delay(ListAssistant.chunkDelayInSeconds, filterGeneration, listWidget, offset, limit, iteration + 1);
			}
		};
		
		this.dataSource.fetchItems(listWidget, chunkOffset, chunkSize, options);
		
	},
	
	getItems: function(filter, listWidget, offset, limit){
	
		if (this.useChunking) {
			
			// just kick off the first chunk here.  after that one is done, it'll kick off the next one and so on until done with the window.
			this.startChunking = Date.now();
			this.delayedFetch(this.filterGeneration, listWidget, offset, limit, 0);
			
		} else {
			this.dataSource.fetchItems(listWidget, offset, limit);
		}
		
	},
	
	getDivider: function(item){
		if (!item.exclude) {
			return item.dividerText;
		}
	},
	
	formatPresence: function(presence){
		var formatted;
		switch (presence) {
			case IMName.BUSY:
				formatted = 'busy';
				break;
			case IMName.IDLE:
				formatted = 'idle';
				break;
			case IMName.ONLINE:
				formatted = 'available';
				break;
			case IMName.OFFLINE:
				formatted = 'offline';
				break;
		}
		return formatted;
	},
	
	setup: function(){
		if (this.controller.crossLaunchPush == true) {
			this.isCrossLaunch = true;
			this.mode = "picker";
		}
		
		if (this.mode == "picker") {
			//We have to do this, otherwise all over the app where we say popScenesTo("list") could drop us off here
			//Is this legit?
			this.controller.sceneName = "picker";
			
			if (this.crossPushMessage) {
				var headerDiv = Mojo.View.render({
					template: 'list/header',
					object: {
						message: this.crossPushMessage
					}
				});
				this.controller.get("header").update(headerDiv);
			}
			
			this.spinnerModel = { spinning : false };
			this.controller.setupWidget('list-saving-spinner',
				{ spinnerSize : 'large' }, this.spinnerModel);

		}
		this.sortOrderRequest = AppAssistant.contactsService.getSortOrder(this.controller, this.handleSortOrder.bind(this));
		
		this.transformListResults = this.transformListResults.bind(this);
		
		this.dataSource = new ActiveRecordListBridge(AppAssistant.contactsService.newList.curry(this.controller), AppAssistant.contactsService.count.curry(this.controller), this.transformListResults);
		this.dataSource.setHandleUpdateCount(this.handleUpdateCount);
		
		
		this.decorator = new ContactDecorator();
		if (this.mode != "picker") {
			this.controller.setupWidget(Mojo.Menu.commandMenu, undefined, {
				items: [{
					icon: 'new-contact',
					label: 'New',
					command: 'new'
				}]
			});
			
			// TODO: set up app menu using chain-of-command
			// the stage should have a default handler that activates and handles the prefs menu item
			// all else should be declared and handled by the scene
			var prefsItem = {
				label: $L("Preferences & Accounts"),
				command: Mojo.Menu.prefsCmd
			
			}
			this.controller.setupWidget(Mojo.Menu.appMenu, {
				omitDefaultItems: true
			}, {
				items: [Mojo.Menu.editItem,{
					label:$L("Send All to Car Kit"),
					command:"sendcontacts"
				}, prefsItem, {
					label: $LL('Help'),
					command: Mojo.Menu.helpCmd
				}]
			});
		}
		
		this.listAttrs = {
			itemTemplate: 'list/contact_entry',
			filterFunction: this.getItems.bind(this),
			formatters: {
				imAvailability: this.formatPresence
			},
			delay: 1,
			optimizedOptIn: true,
			lookahead: 50,
			renderLimit: 200,
			scrollThreshold: 4800,
			
			dividerTemplate: 'list/group_separator',
			dividerFunction: this.getDivider,
			emptyTemplate: 'list/empty'
		};
		
		this.listDiv = this.controller.get('contacts-list');
		
		this.controller.setupWidget('contacts-list', this.listAttrs, null);
		
		this.controller.listen(this.listDiv, Mojo.Event.filter, this.handleFilter.bindAsEventListener(this), true);
		this.controller.listen(this.listDiv, Mojo.Event.filterImmediate, this.handleFilterImmediate.bindAsEventListener(this));
		this.controller.listen(this.listDiv, Mojo.Event.listTap, this.handleListTap.bindAsEventListener(this));
		this.emptyHolder = this.controller.get('emptyHolder');

		if (this.mode != "picker") {
			AppAssistant.listResetCallback = this.reset.bind(this);
		}
		
		this.scrollStarting = this.scrollStarting.bind(this);
		this.moved = this.moved.bind(this);
		
		// ********* GAL setup *********
		
		this.gal = this.controller.get('LookupContainer');
		if (this.mode != "picker") {
			this.checkAccountListCallbackBound = this.checkAccountListCallback.bind(this);
			AppAssistant.accountsService.getAllAccountsForService(this.controller, AccountsService.CONTACTS, false, this.checkAccountListCallbackBound);
			
			this.lookups = this.controller.get('LookupResults');
			this.lookupsAttr = {
				itemTemplate: 'list/lookup-entry',
				items: [],
				//emptyTemplate: 'list/empty-gal',
				toggler: this.lookups
			};
			this.lookupsModel = {
				items: []
			}
			this.controller.setupWidget('LookupResults', this.lookupsAttr, this.lookupsModel);
			this.lookups.observe(Mojo.Event.listTap, this.viewLookedUpContact.bindAsEventListener(this));
			
			this.lookupsController = new GALHelper(this);
			
			this.controller.get("GalRow").observe(Mojo.Event.tap, this.handleGALStart.bindAsEventListener(this));
		}
		
		this.galSpinnerModel = {spinning: false};
    this.controller.setupWidget('galSpinner', {spinnerSize: 'small'}, this.galSpinnerModel);
	},
	
	enableSubscriptions:function(){
		this.dataSource.enableSubscriptions();
	},
	
	disableSubscriptions:function(){
		this.dataSource.disableSubscriptions();
	},
	
	maximizeSubscriptions: function(){
		this.dataSource.maximizeSubscriptions();
	},
	
	minimizeSubscriptions: function(){
		this.dataSource.minimizeSubscriptions();
	},
	
		
	hide:function(){
		this.disableSubscriptions();
	},
	
	show:function(){
		this.enableSubscriptions();
	},
	
	minimize:function(){
		this.disableSubscriptions();
	},
	
	maximize:function(){
		this.enableSubscriptions();
	},

	activate: function() {
		if(this.focusWindow){
			this.focusWindow = false;
			this.controller.stageController.activate();
		}
		this.controller.listen(this.controller.getSceneScroller(), Mojo.Event.scrollStarting, this.scrollStarting);
	},
	
	deactivate: function() {
		this.controller.stopListening(this.controller.getSceneScroller(), Mojo.Event.scrollStarting, this.scrollStarting);
	},
	
	reset: function(){
		delete this.sortOrder;
		AppAssistant.contactsService.getSortOrder(this.controller, this.handleSortOrder.bind(this));
		this.dataSource.doUpdate(true);
	},
	
	checkAccountListCallback: function(resp){
		this.hasEASAccount = resp.list.any(function(acct){
			return acct.domain == "eas";
		});
	},
	
	cleanup: function(){
		this.dataSource.cleanup();
		//Only clean up the list reset callback when you actually get rid of the list.  We were destroying this when you came back
		//from the people picker
		if (this.mode != "picker") {
			AppAssistant.listResetCallback = undefined;
		}
	},
	
	scrollStarting: function(event) {
		event.scroller.addListener(this);
		
		//Mojo.Log.info("STARTING SCROLL");
		
		this.scrolling = true;
		
	},
	
	moved: function(ended) {
		
		if (ended) {
			//Mojo.Log.info("STOPPED SCROLL");
			
			this.scrolling = false;
			// resume the fetching
			if (this.savedFetch) {
				this.savedFetch();
				delete this.savedFetch;
			}
			if (this.savedHandler) {
				this.savedHandler();
				delete this.savedHandler;
			}
		}
	},
	
	handleSortOrder: function(response){
		this.sortOrder = response.order;
		
		// FIXME: I don't think this works.  Can a list have its attributes dynamically like this?  --andy
		this.listAttrs.dividerTemplate = (this.sortOrder == PrefsConstants.companyFirstLast || this.sortOrder == PrefsConstants.companyLastFirst) ? 'list/multiline-separator' : 'list/group_separator';
	},
	
	handleCommand: function(event){
		if (event.type == Mojo.Event.command && event.command === 'new') {
			this.controller.stageController.pushScene('edit', {
				newContact: true,
				onSaveCallback: this.dataSource.doUpdate.bind(this.dataSource)
			});
		}else if (event.type == Mojo.Event.command && event.command === Mojo.Menu.prefsCmd) {
			this.controller.stageController.pushScene('prefs');
		}else if (event.type == Mojo.Event.command && event.command == "sendcontacts") {
			this.sendContacts();
		}
		if (event.type == Mojo.Event.commandEnable && event.command == Mojo.Menu.prefsCmd) {
			// funkay!  this is apparently how you declare you want to activate the prefs menu item
			event.stopPropagation();
		}
	},
	
	sendContacts:function(){
		
		var params = {"type":"bluetooth", "onSuccess":this.bluetoothResponse.bind(this)}; 
		ConnectionWidget.connect(params, this.controller.stageController);
		
	},
	
	bluetoothResponse:function(response){
		if (response == "BT-On" || response == "BT-StartingUp") {
			this.vcardRequest = AppAssistant.contactsService.makeVCardList(this.controller, {stripPhoneNumbers:true}, this.gotVCard.bind(this));
			var cancelFn = function(){
				this.vcardRequest.cancel();
				this.vcardRequest = null;
			}.bind(this)
			console.log("Setting up a dialog");
			this.sendContactsPopupAssistant = new SendContactsPopupAssistant(this.controller, cancelFn);
			this.controller.showDialog({
				template: 'list/send-contact-popup',
				assistant: this.sendContactsPopupAssistant,
				preventCancel:true
			});
		}
	},
	
	gotVCard:function(resp){
		var appArgs = {
			appId:"com.palm.app.bluetooth",
			name:"btopp"
		}
		var sceneArgs = {
			file:resp.file
		}
		
		this.sendContactsPopupAssistant.widget.mojo.close();
		
		this.controller.stageController.pushScene(appArgs, sceneArgs)
	},
	
	handleListTap: function(event){
		Mojo.Log.info("tapped on " + event.item.display + " at time=" + Date.now());
		if (this.mode != "picker") {
			this.controller.stageController.pushScene(detailSceneName, {
				personId: event.item.id,
				contact: event.item,
				onSaveCallback: this.dataSource.doUpdate.bind(this.dataSource)
			});
		}
		else {
			var returnParams = {
				personId: event.item.id,
				fullName: event.item.displayText,
				listPic: event.item.listPic,
				
			}
			if (typeof this.crossAppCallback == "function") {
				if(!this.suppressPopScene)
					this.controller.stageController.popScene();
				this.crossAppCallback(returnParams)
			}
			else {
				AppAssistant.contactsService.details(returnParams.personId, this.peoplePickerDetailCallback.bind(this, returnParams), this.controller, null, this.peoplePickerDetailCallbackFailure.bind(this))
			}
		}
	},
	
	peoplePickerDetailCallbackFailure:function(resp){
		this.controller.stageController.popScene(resp);
	},
	
	peoplePickerDetailCallback:function(oldParams, resp){
		oldParams.details = resp;
		this.controller.stageController.popScene(oldParams);
	},
	
	handleFilter: function(event){
		var filter = event.filterString
		Mojo.Log.info("CLIENT GOT FILTER: ", filter, " at time=", Date.now());
		if (this.filter != filter) {
			this.filter = filter;
			this.dataSource.setParam(filter);
			
			if (this.filter != this.immediateFilter) {
				Mojo.Log.info("resetting on this filter");
				this.useChunking = true;
				this.filterGeneration++;
				delete this.processCountSaved;
				delete this.listCount;
			}
			
		}
		if (this.mode != "picker") {
			this.lookupsController.reset();
		}
	},
	
	refreshEmptyTemplate: function(){
		if (this.filter && this.filter.length > 0) {
			if (!this.filtering) {
				this.listDiv.removeClassName('unfiltered');
				this.filtering = true;
			}
		}
		else {
			if (this.filtering) {
				this.listDiv.addClassName('unfiltered');
				this.filtering = false;
			}
		}
	},
	
	handleFilterImmediate: function(event){
		// if we are waiting for a service response, MUST CANCEL IMMEDIATELY UPON FURTHER INPUT
		// the pending data is stale by the time the user types another character.
		//this.dataSource.cleanup();
		
		if (event.filterString != this.filter) {
//			Mojo.Log.info("got USEFUL filter immediate event for str=", event.filterString, " at time=", Date.now());
			this.useChunking = true;
			this.filterGeneration++;
			delete this.processCountSaved;
			delete this.listCount;
			this.immediateFilter = event.filterString;
		} else {
//			Mojo.Log.info("ignoring SPURIOUS filter immediate event for str=", event.filterString, " at time=", Date.now());
		}
		
	},
	
	// ********* GAL functions *********
	
	//  handleHasEASAccount: function(response) {
	//    this.hasEASAccount = response.result;
	//    if (!this.hasEASAccount) {
	//      this.gal.hide();
	//    }
	//  },
	//  
	handleGALStart: function(event){
		this.lookupsController.toggle();
		if (this.lookupsController.isOpen()) {
			this.galSpinnerModel.spinning = true;
			this.controller.modelChanged(this.galSpinnerModel);
			this.controller.serviceRequest("palm://com.palm.mail", {
				method: "queryGAL",
				onSuccess: this.displayLookupResults.bind(this),
				parameters: {
					target: this.filter,
					offset: 0,
					limit: 100
				}
			});
		}
	},
	
	displayLookupResults: function(results){
		//Mojo.Log.info("Contacts Gal Lookup Here are the results + " + results.total + Object.toJSON(results));
		this.lookupsController.updateCount(results.total);
		this.galSpinnerModel.spinning = false;
		this.controller.modelChanged(this.galSpinnerModel);		
		var size = results.total;
		var titleObj = {
			size:size,
		}
		var title;
		if(size == 100){
			this.controller.get("GalTooBig").show();
		} else {
			this.controller.get("GalTooBig").hide();
		}
        title = Mojo.Format.formatChoice(size, $L("0#No results found|1#1 result found|100<##{size} results found|#Over 100 Results found"), titleObj); 
		this.controller.get("GALCountResults").innerHTML = title;
		this.lookupsModel.items = results.result.each(this.formatLookupResults);
		if (results.total == 0) {
			this.lookupsModel.items = [];
		}
		this.controller.modelChanged(this.lookupsModel);
		this.controller.modelChanged(this.lookupsAttr);
		
		// @@ turn it into hint text?
		//    this.resetFilter = true;
		//    this.filterField.setSelectionRange.bind(this.filterField, 0, 0).defer();
		//    this.filterField.addClassName("hinted");
	},
	
	formatLookupResults: function(result){
		if (result.firstName || result.lastName) {
			result.formattedText = result.firstName + " " + result.lastName;
		}
		else {
			if (result.addressList.length > 0) 
				result.formattedText = result.addressList[0].value;
		}
	},
	
	
	viewLookedUpContact: function(e){
		var c = this.lookupsController.makeContact(e.item);
		Pseudocard.pushPseudocard(this.controller.stageController, {
			contact: c
		});
	},
	
	showListSavingScrim:function(){
		if (this.mode == "picker") {
			this.controller.get("list-saving-div").show();
			this.spinnerModel.spinning = true;
			this.controller.modelChanged(this.spinnerModel)
		}	
	},
	
	showErrorMessage:function(error){
		this.controller.showAlertDialog({
				message:error,
				choices:[{label:$L("OK"), command:"ok"}],
				onChoose:function(){
					
				}.bind(this)
			});
	}
	
	
});

ListAssistant.chunkDelayInSeconds = 0.15;
ListAssistant.chunkSize = 20;

var SendContactsPopupAssistant = Class.create({
	initialize:function(controller, cancelFn){
		this.controller = controller;
		this.cancelFn = cancelFn;
		this.cancel = this.cancel.bind(this)
	},
	
	setup:function(widget){
		this.widget = widget;
		this.controller.setupWidget("send-contacts-spinner", {spinnerSize:Mojo.Widget.spinnerSmall, modelProperty:"spinning"}, {spinning:true})
		this.controller.setupWidget("CancelSendContacts", {}, {label:$L("Cancel")})
		this.controller.listen("CancelSendContacts", Mojo.Event.tap, this.cancel);
	},
	
	activate:function(){
		this.controller.get("send-contacts-spinner").mojo.start();
	},
	
	cancel:function(){
		this.cancelFn();
		this.widget.mojo.close();
	}
})
