/**
 * Email scenes need to inform the notification assistant how to filter the new email notifications
 * coming in from the service. This is done with either
 * + notificationAssistant.clear(accountid, folderid, messageid) - this can be done once in the setup method
 * + notificationAssistant.setIgnoreNewEmails(ignore) - this needs to be done in the activate and deactivate methods
 
Copyright 2009 Palm, Inc.  All rights reserved.

*/
var NotificationAssistant = Class.create({
	
	initialize : function(appController) {
		Mojo.Log.info("Initializing NotificationAssistant");
		this.appController = appController;
		this.subscriptionFailureCount = 0;
		this.subscribeToServices();
		this.previousNewMailTimestamp = 0;

		// This keeps track of what account, folder, and message is being viewed to prevent
		// sending broadcast and dashboard updates when the user would see it because of the
		// scene he's in.
		this.filter = {};
		this.newMailDeferralTimeout = 7;
  	},

	windowBlurFocusHandler: function(event) {
		Mojo.Log.info("Email Stage event type", event.type, "in phase", event.eventPhase)
		// Only interested in the activate/deactivate events at the document object
		if (event.type === Mojo.Event.activate && event.eventPhase === Event.AT_TARGET) {
			var stgController = this.appController.getStageController("email");
			if (this.filter.account || this.filter.folder || this.filter.message || this.neverDisplay) {
				this.clear(this.filter.account, this.filter.folder, this.filter.message);
			}
		}
  	},

	/**
	 * Tell the notification assistant to ignore *all* new email notifications.
	 * @param {Object} ignore - true means ignore, false means process new emails normally
	 */
	setIgnoreNewEmails: function(ignore) {
		this.neverDisplay = ignore;
	},

	setIgnoreClears: function(ignore) {
		this.ignoreClears = ignore;
	},

	/*
	 * The notificationInfo contains an array of folders with new email to show in the dashboard.
	 * [{
		"hasNew": true,
		"count": 5, // the number of new emails in this folder
		"notify": 1,
		"playSound": 1,
		"summary": "hi there!",
		"accountDomain": "eas",
		"use": "INBOX",
		"from": "Sexy Sarah",
		"message": 65970697671577,
		"folder": 59373627900922,
		"accountName": "Exchange",
		"account": 12094627905572,
		"login": "Palm (ed.ballot@palm.com)"
	 * }]
	 */
	notificationInfo: [],


	/*
	 * Invoked when the MailService receives a new email (among other notification types)
	 */
	handleNotification: function(resp) {
		// received a notification which means subscription is ok so reset failure count
		this.subscriptionFailureCount = 0;
		if (resp.type === undefined && resp.returnValue === true) {
			return;
		}

		switch (resp.type) {
			case "new":
				this.handleNewEmailNotification(resp);
				break;
			case "general":
				this.appController.showBanner(resp.message, {}, NotificationAssistant.kBannerCategory);
				break;
			case "sendError":
				this.handleSendErrorNotification(resp);
				break;

			case "securityPolicyError":
				this.handleSecurityPolicyError(resp);
				break;

			case "SyncLoginException":
				ErrorAnalyzer.handleLoginErrorNotification(resp);
				this.handleErrorNotification(resp);
				break;

			case "SSLHandshakeException":
				resp.errorTitle = resp.accountName;
				resp.errorMessage = $L("SSL certificate error");
			
				// display SSL error banner notification
				this.handleNotification({ type:"general",
					message: $L("SSL certificate error: #{accountName}").interpolate(resp) });
			
				// display SSL error dashboard (icon) notification
				resp.commandFunc = function() {
					var sr = new Mojo.Service.Request('palm://com.palm.applicationManager', {
								method: 'open',
								parameters: {
									id: 'com.palm.app.email',
									params: { certificateError: true, reason: resp.reason }
								}
							});
				};
				this.handleErrorNotification(resp);
				
				break;

			default:
				Mojo.Log.warn("WARNING: unknown email notification: ", resp.type);
		}
	},

	handleErrorNotification: function(details) {
		details.stageName = NotificationAssistant.kErrorDashboardStageName + details.accountDomain;
		var f = function(stageController) {
			stageController.pushScene('error-dashboard', details);
		};
		var stageController = this.appController.getStageProxy(details.stageName);
		if (stageController) {
			stageController.delegateToSceneAssistant("updateDashboard", details);
		} else {
			var params = {
				name: details.stageName,
				lightweight: true,
				htmlFileName: 'notifications'
			};
			this.appController.createStageWithCallback(params, f, 'dashboard');
		}
	},

	/*
	 * notification contains the following properties:
	 *  type: "securityPolicyError"
	 *  account: account ID
	 *  accountName: account name
	 *  username: account username
	 */
	handleSecurityPolicyError: function(resp) {
		Mojo.Log.error("handleSecurityPolicyError", resp.accountName)
		var message = $L("Security policy error: \"#{accountName}\"").interpolate(resp);
		resp.securityPolicyError = true;
		this.appController.showBanner(message, resp, NotificationAssistant.kBannerCategory);

		resp.errorTitle = message;
		resp.errorMessage = $L("Tap for details");
		resp.commandFunc = function() {
			var sr = new Mojo.Service.Request('palm://com.palm.applicationManager', {
		                    method: 'open',
		                    parameters: {
								id: 'com.palm.app.email',
								params: resp
							}
		                });
		};
		this.handleErrorNotification(resp);
	},

	/*
	 * notification contains the following properties:
	 *  type: "sendError"
	 *  reason: text from service layer
	 *  subject: subject of the email
	 *  account: account ID
	 *  folder: folder ID
	 *  message: message ID
	 *  accountDomain: the account domain (used for display the account type icon)
	 */
	handleSendErrorNotification: function(resp) {
		var title = undefined;
		var message = undefined;
		if (resp.reason === NotificationAssistant.kSendErrMailboxFull) {
			title = $L("Unable To Send");
			message = $L("Your mailbox is full. Delete email to clear space.");
		} else {
			title = $L("Email Application Alert");	
			message = $L("Error sending \"#{summary}\"").interpolate({summary: resp.summary.escapeHTML()});
		} 
		var folderInfo = { 
			'domain': resp.accountDomain,
			'accountId': resp.account,
			'folderId': resp.folder
		};
		this.appController.showBanner(message, folderInfo, NotificationAssistant.kBannerCategory);

		resp.errorTitle = title;
		resp.errorMessage = message;
		resp.folderInfo = folderInfo;
		var f = function(stageController) {
			stageController.pushScene('senderror-dashboard', resp);
		};
		var sendErrorStageController = this.appController.getStageProxy(NotificationAssistant.kSendErrDashboardStageName+resp.accountDomain);
		if(sendErrorStageController) {
			sendErrorStageController.delegateToSceneAssistant("updateDashboard",resp);
		}
		else {
			var params = {
				name: NotificationAssistant.kSendErrDashboardStageName+resp.accountDomain,
				lightweight: true,
				htmlFileName: 'notifications'
			};
			this.appController.createStageWithCallback(params, f, 'dashboard');
		}
		
	},

	handleNewEmailNotification: function(resp) {
		if (resp === undefined || resp.list === undefined) {
			Mojo.Log.error("Received invalid new email notification", Object.toJSON(resp));
			return;
		}

		// If the list is empty, all emails have been read so make sure the
		// new mail dashboard is closed
		if (resp.list.length === 0) {
			this.dismissEmailDashboards();
		} else {
			var notifications = this.notificationInfo;
			this.notificationInfo = [];
			var playSoundParams = [];

			var folderObj;
			var listIndex;
			for (listIndex = 0; listIndex < resp.list.length; ++listIndex) {
				folderObj = resp.list[listIndex];
				if (this.shouldDoNotification(folderObj)) {
					// Add to the UI model only if notify is set and the count shows there's items to add
					if (folderObj.notify === true && folderObj.count > 0) {
						this.notificationInfo.push(folderObj)
					}
					
					if (folderObj.hasNew === true) {
						var soundParams = this.getSoundNotificationClass(folderObj);
						if (soundParams) 
							playSoundParams.push(soundParams);
					}
				}
			}
			
			if (playSoundParams.length > 0) {
				this.playNotificationSounds(playSoundParams);
			}
			
			// Any old notifications that don't exist in the most updated notification
			// list should have their dashboard dismissed, if all inboxes is set
			// to not shown.
			var clearDashboardList = [];
			if (!this.allInboxesOn) {
				for (listIndex = 0; listIndex < notifications.length; ++listIndex) {
					var checkIndex;
					for (checkIndex = 0; checkIndex < this.notificationInfo.length; ++checkIndex) {
						if (notifications[listIndex].folder === this.notificationInfo[checkIndex].folder) {
							break;
						}
					}
					if (checkIndex === this.notificationInfo.length) {
						clearDashboardList.push(notifications[listIndex]);
					}
				}
			}

			// This needs to happen after the audio/vibrate accounts are removed from
			// this.notificationInfo to prevent displaying them in the dashboard.
			if (this.notificationInfo.length > 0) {
				this.showNewEmailDashboard();
				if (clearDashboardList.length > 0) 
					this.closeEmailDashboards(clearDashboardList);
			} else {
				this.dismissEmailDashboards();
			}
		}
	},

	shouldDoNotification: function(folderObj) {
		if (!folderObj || folderObj.account === undefined || folderObj.folder === undefined || folderObj.message === undefined) {
			Mojo.Log.error("Received invalid new email notification", Object.toJSON(folderObj));
			return false;
		}

		// If the email account has no notifications, then just return. 
		if (folderObj.notify === false) {
			Mojo.Log.info("handleNewEmailNotificationFolder doing nothing for no-notification account");
			return false;
		}

		// Check to see whether to update the new email status
		var doUpdate = true;
		var stgController = this.appController.getStageController("email");
		var focused = (stgController && stgController.isActiveAndHasScenes());
		// If the display is off then always do the notification
		if (this.displayOff === true) {
			doUpdate = true;
		} else if (focused) {
			if (this.neverDisplay || !EmailAppDepot.getDepot().isFirstUseComplete()) {
				doUpdate = false;
				if (!this.allInboxesOn) 
					// clear all email notifications by setting folder id to be -1
					this.setFolderViewedTimestamp(-1, new Date().getTime());;
			} else if (this.filter.message) {
				// user is viewing a message so they aren't yet in the list view
				// therefore show the new email notification.
				doUpdate = true;
			} else if (this.filter.folder) {
				doUpdate = (this.filter.folder != folderObj.folder);
				if (!doUpdate && !this.allInboxesOn) 
					// if all Inboxes if off, clear the notification so that the
					// pending notification will show up when the user leaves 
					// this folder.
					this.clear(this.filter.account, this.filter.folder);
			} else if (this.filter.account) {
				doUpdate = (this.filter.account != folderObj.account);
				if (!doUpdate && !this.allInboxesOn) 
					// if all Inboxes if off, clear the notification so that the
					// pending notification will show up when the user leaves 
					// this account.
					this.clear(this.filter.account);
			} else {
				// If nothing is defined, then do the update unless the 'acccounts' scene is on top
				if (stgController) {
					if (stgController.isActiveAndHasScenes()) {
						var sceneName = stgController.topScene().sceneName;
						if (sceneName === 'accounts' || sceneName === 'firstuse') {
							doUpdate = false;
						}
					}
				}
			}
		}

		Mojo.Log.info("email notification doUpdate=", doUpdate, ", focused=", focused, ", filter=", $H(this.filter).inspect());
		return doUpdate;
	},
	
	getSoundNotificationClass: function(folder) {
		if (folder.playSound == AccountpreferencesAssistant.kNotifyTypeMute)
			return undefined;
			
		var params = {};
		if (folder.playSound == AccountpreferencesAssistant.kNotifyTypeSystem) {
			params.soundClass = "notification";
			params.soundFile = undefined;
		} else if (folder.playSound == AccountpreferencesAssistant.kNotifyTypeRingtone) {
			params.soundClass = "alerts";
			params.soundFile = folder.ringtonePath;
		} else if (folder.playSound == AccountpreferencesAssistant.kNotifyTypeVibrate) {
			params.soundClass = "vibrate";
			params.soundFile = undefined;
		}
		
		return params; 
	},

	playNotificationSounds: function(soundParams) {
		var func = function(param) {
			Mojo.Log.info("NotificationAssistant: playing sound notification=%j", param);
			if (param.soundClass == "vibrate") 
				PalmSystem.playSoundNotification(param.soundClass);
			 else 
				this.appController.playSoundNotification(param.soundClass, param.soundFile);
		}.bind(this);
		
		soundParams.each(func);
	},

	showNewEmailDashboard: function() {
		if (this.allInboxesOn) {
			this.showNewAccountEmailDashboard(NotificationAssistant.kNewEmailDashboardStageName, this.notificationInfo);
		}
		else {
			var func = function(notification) {
				var stageName = NotificationAssistant.kNewEmailDashboardStageName + notification.account;
				var notifications = [notification];
				
				this.showNewAccountEmailDashboard(stageName, notifications);
			}.bind(this);
			
			// each account has its own new email notification dashboard if All Inbexes is off
			this.notificationInfo.each(func);
		}
	},
	
	showNewAccountEmailDashboard: function(stageName, notifications) {
		/* Reuse the email dashboard stage if it already exists. */
		var dashboard = this.appController.getStageProxy(stageName);
		if (dashboard) {
			dashboard.delegateToSceneAssistant("update", notifications);
		}
		else {
			var params = {
				name: stageName,
				lightweight: true,
				htmlFileName: 'notifications'
			};
			var notificationAssistant = this;
			var f = function(stageController){
				stageController.pushScene('email-dashboard', notificationAssistant, notifications);
			};
			this.appController.createStageWithCallback(params, f, 'dashboard');
		}
	},
	
	dismissEmailDashboards: function() {
		if (this.allInboxesOn) {
			this.closeEmailDashboard();
		} else {
			this.closeEmailDashboards(this.notificationInfo);
		}
		
		// reset notification info once all email dashboards are cleared
		this.resetNotifications();
	},
	
	closeEmailDashboards: function(notifications) {
		var func = function(notification) {
			this.closeEmailDashboard(notification);
		}.bind(this);
		
		notifications.each(func);
	},
	
	closeEmailDashboard: function(notification) {
		var stageName = NotificationAssistant.kNewEmailDashboardStageName;
		if (notification) {
			stageName += notification.account;
		}
		var dashboard = this.appController.getStageProxy(stageName);
		if (dashboard) {
			this.appController.closeStage(stageName);
		}
	},
	
	updateEmailDashboard: function(newList, clearList) {
		if (this.allInboxesOn) {
			var stageName = NotificationAssistant.kNewEmailDashboardStageName;
			var dashboard = this.appController.getStageProxy(stageName);
			if (dashboard) {
				if (newList.length > 0) {
					dashboard.delegateToSceneAssistant("update", newList);
				}
				else {
					this.closeEmailDashboard();
				}
			}
		}
		else {
			if (newList.length > 0) {
				var updateFunc = function(notification) {
					var stageName = NotificationAssistant.kNewEmailDashboardStageName + notification.account;
					var dashboard = this.appController.getStageProxy(stageName);
					
					if (dashboard) {
						if (notification.count > 0) {
							dashboard.delegateToSceneAssistant("update", [notification]);
						}
						else {
							this.closeEmailDashboard(notification);
						}
					}
				}.bind(this);
				
				newList.each(updateFunc);
			}
			
			this.closeEmailDashboards(clearList);
		}
	},
	
	toggleEmailNotification: function(on) {
		if (this.allInboxesOn === on) 
			return;
			
		if (this.notificationInfo && this.notificationInfo.length > 0) {
			var closeFunc = function(stageName) {
				var dashboard = this.appController.getStageProxy(stageName);
				if (dashboard) {
					// Set 'ignoreClear' true in email dashboard so that closing
					// the dashboard won't clear the pending notifications.  Changing
					// All Inboxes on/off should not eliminate the current pending
					// notifications.
					dashboard.delegateToSceneAssistant('setCleanupParams', {'ignoreClear': true, 'allInboxesOn': this.allInboxesOn});
					this.appController.closeStage(stageName);
				}
			}.bind(this);
			
			// Re-create new email notification dashboard since All Inboxes 
			// preference is changed.  
			if (on) {
				for (var i = 0; i < this.notificationInfo.length; i++) {
					var stageName = NotificationAssistant.kNewEmailDashboardStageName + this.notificationInfo[i].account;
					closeFunc(stageName);
				}
			}
			else {
				closeFunc(NotificationAssistant.kNewEmailDashboardStageName);
			}
			
			this.allInboxesOn = on;
			this.showNewEmailDashboard();
		}
		else {
			this.allInboxesOn = on;
		}
	},

	getFoldersList: function(accountId) {
		var folders = [];
		var listIndex;
		for (listIndex = 0; listIndex < this.notificationInfo.length; ++listIndex) {
			if (this.notificationInfo[listIndex].account == accountId) {
				folders.push(this.notificationInfo[listIndex].folder);
			}
		}
		
		return folders;
	},

	clearAccount: function(accountId) {
		var newList = [];
		var clearList = [];
		var listIndex;
		for (listIndex = 0; listIndex < this.notificationInfo.length; ++listIndex) {
			if (this.notificationInfo[listIndex].account != accountId) {
				newList.push(this.notificationInfo[listIndex]);
			} else {
				clearList.push(this.notificationInfo[listIndex]);
			}
		}

		this.notificationInfo = newList;
		this.updateEmailDashboard(newList, clearList);
	},

	setFilter: function(accountId, folderId, messageId) {
		Mojo.Log.info("NotificationAssistant.setFilter(a=" + accountId + ", f=" + folderId + ", m=" + messageId + ")");
		this.filter.account = accountId;
		this.filter.folder = folderId;
		this.filter.message = messageId;
	},

	/**
	 * Clear certain emails from the notification data structure and continue to filter new emails. The filter is
	 * determined by which of the optional parameters is specified. If account and folder id is specified, then
	 * emails to that folder will not be shown in the banner and dashboard notifcation. If account, folder, and
	 * message id are all specified, then just that message will be removed from the notification data structure.
	 * If only account is specified, then all emails to that account will be filtered.
	 * @param {Object} accountId - the account to filter out
	 * @param {Object} folderId - the folder to filter out
	 * @param {Object} messageId - the specific message to filter out
	 */
	clear: function(accountId, folderId, messageId) {
		var newList = [];
		var clearList = [];
		var listIndex;

		if (this.ignoreClears) {
			return;
		}

		this.setFilter(accountId, folderId, messageId);

		this.setFolderViewedTimestamp(folderId, new Date().getTime());		

		// If there's no notifications, then just return
		if (this.notificationInfo.length === 0) {
			return;
		}
	
		try {
			if (messageId) {
				for (listIndex = 0; listIndex < this.notificationInfo.length; ++listIndex) {
					if (this.notificationInfo[listIndex].message == messageId) {
						clearList.push(this.notificationInfo[listIndex]);
					} else {
						newList.push(this.notificationInfo[listIndex]);
					}
				}
			} else if (folderId) {
				for (listIndex = 0; listIndex < this.notificationInfo.length; ++listIndex) {
					if (this.notificationInfo[listIndex].folder == folderId) {
						clearList.push(this.notificationInfo[listIndex]);
					} else {
						newList.push(this.notificationInfo[listIndex]);
					}
				}
			} else if (accountId) {
				for (listIndex = 0; listIndex < this.notificationInfo.length; ++listIndex) {
					if (this.notificationInfo[listIndex].account == accountId) {
						clearList.push(this.notificationInfo[listIndex]);
					} else {
						newList.push(this.notificationInfo[listIndex]);
					}
				}
			}
			
			if (!accountId && !folderId && !messageId) {
				clearList = this.notificationInfo;
			}
			this.notificationInfo = newList;
			this.updateEmailDashboard(newList, clearList);
		} 
		catch (e) {
			Mojo.Log.logException(e, "NotificationAssistant.clear");
			Mojo.Log.error("Clearing all notifications. Culprit a=" + accountId + ", f=" + folderId + ", m=" + messageId);
			this.dismissEmailDashboards();
		}
	},

	resetNotifications: function() {
		// No new emails so clean up everything...
		// Ensure the notificationInfo is reset to starting point
		this.notificationInfo = [];
	},

	subscriptionFailure: function(err) {
		if (this.retryTimeout) {
			clearTimeout(this.retryTimeout);
		}

		// Try relaunching the mail service 3 times before giving up
		this.subscriptionFailureCount++;
		if (this.subscriptionFailureCount < 3) {
			Mojo.Log.error("notifications subscriptionFailureCount=", this.subscriptionFailureCount, " detail=", $H(err).inspect());
			if (this.subscriptionFailureCount === 1) {
				Mojo.Log.error("MailService or VM stopped at ", Mojo.Format.formatDate(new Date(), {time:'short'}));
			}	

			this.notificationsSubscription.cancel();
			this.displaySubscription.cancel();
			this.subscribeToServices();			
		} else {
			// Since this subscription is not associated with a scene, it will never be manually restarted by the user.
			// Therefore it will retry the subscription after a few minutes.
			Mojo.Log.error("This Is Bad: notifications cannot maintain registerForNotifications subscription. I'll retry later. " + $H(err).inspect());
			this.retryTimeout = function() {
				this.retryTimeout = null;
				this.subscriptionFailureCount = 0;
				this.subscriptionFailure({errCode:-1, errText:"subscription failure retry"});
			}.bind(this).delay(180); // retry in 3 minutes
		}
	},
	
	displayUpdate: function(data) {
		if(data.event) {
			// Note, specifically checking Off and On so we ignore displayInactive,
			// displayActive and displayDimmed
			if (data.event == 'displayOff') {
				this.displayOff = true;
				this.newMailDeferralTimeout = 0.5;
			} else if (data.event == 'displayOn') {
				this.displayOff = false;
				this.newMailDeferralTimeout = 7;
			}

			Mojo.Log.info("displayUpdate displayOff", this.displayOff, "event", data.event);
		}
	},

	setFolderViewedTimestamp: function(folder, timestamp) {
		Mojo.Log.info("setFolderViewedTimestamp folder=%s, timestamp=%s", folder, timestamp);
		var x = new Mojo.Service.Request(Email.identifier, {
			method: 'setFolderViewedTimestamp',
			parameters: {'folder': folder, 'timestamp': timestamp},
			onSuccess: Mojo.doNothing,
			onFailure: Mojo.doNothing
		});
	},

	subscribeToServices: function() {
		this.notificationsSubscription = new Mojo.Service.Request(Email.identifier, {
			method: 'registerForNotifications',
			parameters: {'subscribe': true},
			onSuccess: this.handleNotification.bind(this),
			onFailure: this.subscriptionFailure.bind(this)
		});

		this.displaySubscription = new Mojo.Service.Request('palm://com.palm.display/control', {
			method: 'status',
			parameters: {'subscribe': true},
			onSuccess: this.displayUpdate.bind(this)
		});
		
		this.allInboxesSubscription = Folder.subscribeAllInboxesFolder(function(resp) {
			this.toggleEmailNotification(resp.list[0].flags == 1);
		}.bind(this));
	}

});

NotificationAssistant.closeAccountDashboardErrors = function(accountDetails) {
	// just close for the accountDomain for now. Will handle for both incoming and outgoing server errors 
	// Errors still persisting will reopen themselves.
	if (accountDetails.accountDomain) {
		Mojo.Controller.getAppController().closeStage(NotificationAssistant.kErrorDashboardStageName + accountDetails.accountDomain); 
	}
};

NotificationAssistant.kBannerCategory = 'email';
NotificationAssistant.kNewEmailDashboardStageName = 'email-dashboard';
NotificationAssistant.kErrorDashboardStageName = 'error-dashboard';
NotificationAssistant.kSendErrDashboardStageName = 'senderr-dashboard';

NotificationAssistant.kSendErrMailboxFull = "Send error: insufficient space";  //error message from mail service

