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

function NotificationAssistant(controller){
	Mojo.Log.info("Initializing Messaging NotificationAssistant");
	this.controller = controller;
	this.Messaging = this.controller.Messaging;
	// a reference to this is passed to the dashboard assistant
	this.messageDashboardData = {}; 
	this.class0AlertData = {
		list: []
	};
	this.connectionErrorDashboardData = {
		list: []
	};
	this.sendErrorDashboardData = {
		list: [],
		singleMessageError: $L("Message failed to send. Try again."),
		multipleFailuresError: $L("Message to multiple recipients failed to send. Try again.")
	};
	
	this.subscribeToNotifications();
	
	this.mmsPartRequests = [];
	
	this.reminderDashboardData = {
		data: {}
	};
}

NotificationAssistant.prototype.subscribeToNotifications = function(){
	this.messageNotificationRequest = MessagingMojoService.registerForIncomingMessages({onSuccess: this.sendNotification.bind(this, this.Messaging.notificationTypes.newMessage)});
	this.sendErrorNotificationRequest = MessagingMojoService.registerForSendFailures(this.sendNotification.bind(this, this.Messaging.notificationTypes.sendFailure));
	this.errorNotificationRequest = MessagingMojoService.registerForConnectionFailures(this.sendNotification.bind(this, this.Messaging.notificationTypes.connectionFailure));
	this.reminderNotificationRequest = MessagingMojoService.registerForReminders(this.sendReminderNotification.bind(this));
	this.airplaneModeNotificationRequest = MessagingMojoService.registerForAirplaneModeNotifications(this.sendAirplaneModeNotification.bind(this));
};

NotificationAssistant.prototype.sendNotification = function(notificationType, resp){
	if (window.PalmSystem && !resp.returnValue) {
		try {
			if(notificationType == this.Messaging.notificationTypes.newMessage && resp.class0) {
				// If we encounter a class 0 message, use a popup notification
				this.sendClass0PopupAlert(resp);
				return;
			}
			
			resp.notificationType = notificationType;
			this.controller.sendToNotificationChain(resp);
		} catch (e) {
			Mojo.Log.logException(e, "NotificationAssistant.sendNotification")
		}
	}
};

/*
 * <ReminderCode>
 */
NotificationAssistant.prototype.sendReminderNotification = function(resp){
	if (resp.returnValue) {
		return;
	}
	
	// since CDMA tells us that the call is connected twice within a second...
	var reminderData = this.reminderDashboardData.data[resp.personId];
	if (reminderData && reminderData.reminderText === resp.reminderText) {
		return;
	}
	
	var stageName = "reminderStage" + resp.personId;

	var reminderNotificationText;
	
	if (resp.firstName || resp.lastName) {
		reminderNotificationText = new Template($L("Reminder for #{firstName} #{lastName}")).evaluate({
			firstName: resp.firstName,
			lastName:resp.lastName
		});	
		
		resp.displayText = "";
		
	} else {
		reminderNotificationText = new Template($L("Reminder for #{displayText}")).evaluate({
			displayText: resp.displayText
		});
		
		resp.firstName = "";
		resp.lastName = "";
	}
	
	this.reminderDashboardData.data[resp.personId] = resp;
	
	var bannerParams = {		
		icon: "images/personal-reminder-icon-small.png",
		messageText: reminderNotificationText,
		soundClass: "alerts"
	};
	
	var bannerLaunchParams =  {
		reminderPersonId: resp.personId,
		clearBanner: true		
	};

	this.controller.showBanner(bannerParams,bannerLaunchParams,'reminder');

	var dashboard = this.controller.getStageController(stageName);
	if (dashboard) {
		dashboard.delegateToSceneAssistant("update", true);
	} else {
		var func = function(stageController){
			stageController.pushScene('dashboardReminder', this.reminderDashboardData, resp.personId);
		}.bind(this);
		var params = {
			name: stageName,
			lightweight: true,
			icon: "images/personal-reminder-icon-small.png"
		};
		
		var createDashboard = function(){
			if(this.reminderDashboardData.data[resp.personId])
				this.controller.createStageWithCallback(params, func, Mojo.Controller.StageType.dashboard);
		}.bind(this);
		createDashboard.delay(5);
	}
};

NotificationAssistant.prototype.clearReminderForPersonId = function(personId){
	if (this.reminderDashboardData.data[personId]) {
		delete this.reminderDashboardData.data[personId];
	}	
};
/*
 * </ReminderCode>
 */

NotificationAssistant.prototype.sendAirplaneModeNotification = function(){
	var stageController = this.controller.getStageController(MainStageName);
	if (stageController) {
		ConnectionWidget.connect({
			'type': 'voice'
		}, stageController);
	}
};

NotificationAssistant.prototype.considerForNotification = function(notificationData){
	if (!this.Messaging.messagingPrefs.enableNotification) {
		Mojo.Log.info("Notification Assistant - Consider for Notification - Notifications are disabled");	
		return;
	}
	
	// check if we should only play a sound (when you are already in a chat & a new message comes in)
	if(notificationData.playSoundOnly && this.Messaging.messagingPrefs.enableNotificationSound) {
		var notifParams = {};
		this.setNotificationSound(notifParams);
		this.controller.playSoundNotification(notifParams.soundClass, notifParams.soundFile);
		return; // don't display any visual notification
	}	
	
	if (Object.keys(notificationData).length > 0 && this.Messaging.messagingPrefs.enableNotification) {
		var notificationType = notificationData.notificationType;
		if (notificationType == this.Messaging.notificationTypes.newMessage) {
			// if the new message is an MMS, retrieve the part data
			if (notificationData.messageType == "MMS") {
				this.mmsPartRequests.push(MessagingMojoService.getMMSParts(null, notificationData.messageId, this.sendNewMessageNotificationAndUpdateDashboard.bind(this, notificationData)));
			} else {
				this.sendNewMessageNotificationAndUpdateDashboard(notificationData, null);
			}
		} else if (notificationType == this.Messaging.notificationTypes.connectionFailure) {
			this.sendErrorNotificationAndUpdateDashboard(notificationData);
		} else if (notificationType == this.Messaging.notificationTypes.sendFailure) {
			this.sendMessageErrorNotificationAndUpdateDashboard(notificationData);
		} else {
			Mojo.Log.info("Error: Unsupported notification type");
		}
	}
};

NotificationAssistant.prototype.sendClass0PopupAlert = function(notificationData) {
	var carrierName = this.Messaging.carrierSettings.qOperatorLongName;
	if( (carrierName) && (carrierName.length > 0) ) {
		notificationData.alertTitle = $L("Message from #{carrierName}").interpolate({
			carrierName: carrierName.escapeHTML()
		});
	} else {
		notificationData.alertTitle = $L("Message from carrier");
	}

	notificationData.carrierName = carrierName;
	this.class0AlertData.list.push(notificationData);
	this.renderClass0PopupAlert(true);
};

NotificationAssistant.prototype.renderClass0PopupAlert = function(playSound) {
	var class0Stage = this.controller.getStageController(Class0AlertStageName);

	if(class0Stage) {
		class0Stage.swapScene('class0Alert', this.class0AlertData);
		if (playSound){
			this.controller.playSoundNotification('alerts','');
		}
	} else {
		var soundClass = 'none';
		if(playSound) {
			soundClass = 'alerts';
		}		
		
		var pushClass0AlertScene = function(stageController) {
			stageController.pushScene('class0Alert', this.class0AlertData);
		}.bind(this);
		
		this.controller.createStageWithCallback({
			name: Class0AlertStageName,
			lightweight: true,
			height: Math.floor(Mojo.Environment.DeviceInfo.screenHeight * 0.50),
			soundclass: soundClass
		}, pushClass0AlertScene, 'popupalert');		
	}

Mojo.Log.info("class0AlertData %j", this.class0AlertData)
	var dashboard = this.controller.getStageController(DashboardClass0StageName);
	if (dashboard) {
		dashboard.delegateToSceneAssistant("update", true);
	} else {
		// Create a dashboard
		var f = function(stageController){
			stageController.pushScene('dashboardClass0', this.class0AlertData);
		}.bind(this);
		var params = {
			name: DashboardClass0StageName,
			lightweight: true
		};
		
		this.controller.createStageWithCallback(params, f, 'dashboard');
	}
};

NotificationAssistant.prototype.updateClass0Dashboard = function(){
	var dashboard = this.controller.getStageController(DashboardClass0StageName);
	if (dashboard) {
		if (this.class0AlertData.list.length > 0 ) {
			dashboard.delegateToSceneAssistant("update");
		} else {
			Mojo.Log.info("NotificationAssistant.updateClass0Dashboard closeStage(DashboardClass0StageName)");
			// Need to defer closing of the dashboard because there is a timing issue with the alert dashboard 
			// still being up.
			var delayedClose = function(){
				this.controller.closeStage(DashboardClass0StageName);
			}.bind(this);
			delayedClose.defer();
		}
	}
};

NotificationAssistant.prototype.sendNewMessageNotificationAndUpdateDashboard = function(notificationData, mmsParts){
	var chatThreadId = notificationData.chatThreadId;

	// trash reference to the mmsParts request
	if(mmsParts && this.mmsPartRequests.length > 0) {
		this.mmsPartRequests.shift();
	}
	
	var mmsSummaryData = this.handleMMSParts(mmsParts);
	// Display notification for new message
	var name = MessagingUtils.generateNameString(notificationData);
	var message = (notificationData.messageText || notificationData.summary || mmsSummaryData.message || "")
	message = message.stripTags();
	
	
	var notificationText;
	
	if (message === "") {
		notificationText = name;
	}else{
		notificationText = $L("#{name}: #{message}").interpolate({
			name: name,
			message: message
		});
	}
	
	var bannerParams = {
		messageText: notificationText
	};
	this.setNotificationSound(bannerParams);
	var bannerLaunchParams =  {
		chatThreadId: chatThreadId,
		clearBanner: true		
	};
	
	this.controller.showBanner(bannerParams, bannerLaunchParams, 'chat');
	
	// Store the data so it can be used in the dashboard
	var data = {
		chatThreadId: chatThreadId,
		messageId: notificationData.messageId,
		messageType: notificationData.messageType,
		name: name,
		address: notificationData.address,
		message: message.escapeHTML(),
		mmsImage: mmsSummaryData.image
	};

	if (this.messageDashboardData[chatThreadId] === undefined) {
		this.messageDashboardData[chatThreadId] = { timestamp:Date.now(), list:[data] };
	} else {
		// handle replacement messages by removing the specified replacedID
		// the new message that just came in will be pushed to the top of the stack
		var replacedId = notificationData.replacedId;
		var messageList = this.messageDashboardData[chatThreadId].list;
		this.messageDashboardData[chatThreadId].timestamp = Date.now(); // update the timestamp
		if (replacedId && messageList) {
			for(var i=0; i<messageList.length; i++) {
				var message = messageList[i];
				if(message.messageId == replacedId) {
					messageList.splice(i,1);
				}
			}
		}
		
		messageList.push(data);
	}
	this.messageDashboardData.newest = chatThreadId;

	// Add this notification to the dashboard
	var dashboard = this.controller.getStageController(DashboardMessageStageName);
	
	if (dashboard) {
		dashboard.delegateToSceneAssistant("update", true);
	} else {
		// Create a dashboard
		var f = function(stageController){
			stageController.pushScene('dashboardMessage', this.messageDashboardData);
		}.bind(this);
		var params = {
			name: DashboardMessageStageName,
			lightweight: true
		};
		
		var createDashboard = function(){
			if (this.messageDashboardData.newest != undefined) {
				this.controller.createStageWithCallback(params, f, 'dashboard');
			}
			this.isNewMessageDashboardPending = false; 
		}.bind(this);
		// delay creating the dashboard window for the case where the banner is clicked on
		// to take you to the chat view.  This will likely result in the dashboard data
		// being cleared.  If the dashboard data is empty, we do not need to create the dashboard.		
		if (this.Messaging.DisplayState.isDisplayOn()) {
			if (!this.isNewMessageDashboardPending) {
				this.isNewMessageDashboardPending = true; 
				createDashboard.delay(5);
			}
		} else {
			createDashboard(); // if the screen is off, create the dashboard right away
		}
	}
};

// ISSUES: what are the error types?  What is the max number / types of dashboards that we could have?
// 1. lost connection due to wan/wifi going down
// 2. logged out because of server error
// 3. kicked off for already being logged in (AIM pressing 1)
// 4. invalid password

// if the same error happens for multiple accounts, do we show the error for each account?

// possible implementation: create dashboard stages with the error code string as the name, if it
// already exists, then update it, else create it

NotificationAssistant.prototype.sendErrorNotificationAndUpdateDashboard = function(notificationData){
	var connectionErrorCode = notificationData.connectionErrorCode;
	var connectionType = notificationData.connectionType;
	var willAttemptToLogin = notificationData.willAttemptToLogin;
	var loggedOut = notificationData.loggedOut;
	
	var username = notificationData.username;
	var at = username.indexOf('@');
	if (at > 0) {
		notificationData.username = username.substring(0, at);
	}	
	
	var notificationText = new Template($L("#{username}: #{failureMessage}")).evaluate({
		username: notificationData.username,
		failureMessage: this.Messaging.Error.getLoginErrorFromCode(notificationData.connectionErrorCode)
	});	

	var bannerParams = {
		messageText: notificationText.escapeHTML()
	};
	if (this.Messaging.messagingPrefs.enableNotificationSound)
		bannerParams.soundClass = "alerts";	
	var bannerLaunchParams =  {
		clearBanner: true,
		clearErrorDashboard: true
	};

	this.controller.showBanner(bannerParams,bannerLaunchParams,'error');

	this.connectionErrorDashboardData.list.push(notificationData);
	// Add this notification to the dashboard
	var dashboard = this.controller.getStageController(DashboardErrorStageName);
	
	if (dashboard) {
		dashboard.delegateToSceneAssistant("update", true);
	} else {
		// Create a dashboard
		var f = function(stageController){
			stageController.pushScene('dashboardError', this.connectionErrorDashboardData);
		}.bind(this);
		var params = {
			name: DashboardErrorStageName,
			lightweight: true
		};
		
		this.controller.createStageWithCallback(params, f, 'dashboard');
	}
};

NotificationAssistant.prototype.sendMessageErrorNotificationAndUpdateDashboard = function(notificationData) {
	var hasMultipleFailures = notificationData.multipleFailures;
	var chatThreadId = notificationData.chatThreadId;
	var data = {
		chatThreadId: chatThreadId,
		multipleFailures: hasMultipleFailures
	};
	this.sendErrorDashboardData.list.push(data);
	
	var bannerParams = {
		messageText: this.sendErrorDashboardData.singleMessageError
	};
	// TODO: Should we play an error sound?
	//if (this.Messaging.messagingPrefs.enableNotificationSound)
		//bannerParams.soundClass = "alerts";	
	var bannerLaunchParams =  {
		clearBanner: true
	};
	
	if(hasMultipleFailures) {
		 bannerLaunchParams.launchListView = true;
		 bannerLaunchParams.forceListView = this.Messaging.Views.HISTORY;
		 bannerLaunchParams.clearSentMessageErrorDashboard = true;
	} else {
		bannerLaunchParams.chatThreadId = chatThreadId;
	}
	
	this.controller.showBanner(bannerParams,bannerLaunchParams,'sendError');

	// Add this notification to the dashboard
	var dashboard = this.controller.getStageController(DashboardSendErrorStageName);
	
	if (dashboard) {
		dashboard.delegateToSceneAssistant("update", true);
	} else {
		// Create a dashboard
		var f = function(stageController){
			stageController.pushScene('dashboardSendError', this.sendErrorDashboardData);
		}.bind(this);
		var params = {
			name: DashboardSendErrorStageName,
			lightweight: true
		};
		var createDashboard = function(){
			if (this.sendErrorDashboardData.list.length > 0) {
				this.controller.createStageWithCallback(params, f, 'dashboard');
			}
			this.isErrorDashboardPending = false;
		}.bind(this);
		
		if (this.Messaging.DisplayState.isDisplayOn()) {
			if (!this.isErrorDashboardPending) {
				this.isErrorDashboardPending = true;
				createDashboard.delay(5);
			}
		} else {
			createDashboard();
		}
	}	
};

/**
 * When an account successfully goes online, clear any current/queued notifications
 * @param {Object} accountId
 */
NotificationAssistant.prototype.clearErrorNotificationsForAccount = function(accountId){
	// clear current notifications
	var newData = this.clearErrorNotificationsForAccountHelper(accountId,this.connectionErrorDashboardData.list);
	
	// if anything was changed, update the dashboard
	var dashboard = this.controller.getStageController(DashboardErrorStageName);
	if(newData.length != this.connectionErrorDashboardData.list.length) {
		this.connectionErrorDashboardData.list = newData;
		if (dashboard) 
			dashboard.delegateToSceneAssistant("update");		
	}
	if (this.connectionErrorDashboardData.list.length == 0 && dashboard) {
		this.controller.closeStage(DashboardErrorStageName);
	}		
};

NotificationAssistant.prototype.clearErrorNotificationsForAccountHelper = function(accountId,arr){
	var newArr = [];
	// clear queued notfications
	for (var i = 0; i < arr.length; i++) {
		var notification = arr[i];
		var notificationAccountId = notification.accountId;
		if(!notificationAccountId || notificationAccountId != accountId) {
			newArr.push(notification);
		}
	}
	return newArr;
};

NotificationAssistant.prototype.handleMMSParts = function(mmsParts){
	// if no parts are found this empty object will be returned
	var result = {
		image: "",
		message: ""
	}
	// This object is used to:
	//   1. keep count of each type of object
	//   2. provide a mapping for the display name for each attachment
	//      in the singular and plural cases
	var tmpData = {
		picture: {
			count: 0,
			str: $L("1##{partCount} new picture|##{partCount} new pictures")
		},
		audio: {
			count: 0,
			str: $L("1##{partCount} new audio file|##{partCount} new audio files")
		},
		video: {
			count: 0,
			str: $L("1##{partCount} new video|##{partCount} new videos")
		},
		vcard: {
			count: 0,
			str: $L("1##{partCount} new vCard|##{partCount} new vCards")
		},
		vcal: {
			count: 0,
			str: $L("1##{partCount} new vCal|##{partCount} new vCals")
		}
	};
	
	// iterate through mms parts, keep track attachment type count
	// and keep assigning an image.  The last attachment will be
	// the image that we display.
	if (mmsParts && mmsParts.list) {
		mmsParts.list.each(function(part) {
			var mimeType = part.mimeType.toLowerCase();
			
			if (mimeType.substring(0, 5) == 'image') {
				result.image = MessagingUtils.systemPathToLocalURL(part.uri);
				tmpData.picture.count++;
			} else if (mimeType.substring(0, 5) == 'audio') {
				result.image = "images/mms-icon-audio.png";
				tmpData.audio.count++;
			} else if (mimeType.substring(0, 5) == 'video') {
				result.image = "images/mms-icon-video-new.png";
				tmpData.video.count++;
			} else if (mimeType == 'text/x-vcalendar') {
				result.image = "images/mms-icon-vcal.png";
				tmpData.vcal.count++;
			} else if (mimeType == 'text/x-vcard') {
				result.image = "images/mms-icon-vcard-new.png";
				tmpData.vcard.count++;
			}
		});
		// build MMS summary message
		var message = "";
		var delim = "";
		var key;
		for (key in tmpData) {
			var part = tmpData[key];
			
			if (part.count > 0) {
				message += delim + Mojo.Format.formatChoice(part.count, part.str, { partCount: part.count });
			}
			
			if (message.length > 0 && delim.length == 0) 
				delim = $L(", ");
		};
		
		result.message = message;
	}
	
	return result;
};

NotificationAssistant.prototype.clearNotificationsForChat = function(chatThreadId){	
	var i;
	// New Message Dashboard
	var hasChange = (this.messageDashboardData[chatThreadId] !== undefined);
	delete (this.messageDashboardData[chatThreadId]);
	if (this.messageDashboardData.newest == chatThreadId) {
		this.messageDashboardData.newest = undefined;
	}
	var dashboard = this.controller.getStageProxy(DashboardMessageStageName);
	if (dashboard) {
		var keys = Object.keys(this.messageDashboardData);
		if (keys.length > 1) {
			// Need to update messageDashboardData.newest to reference the next newest chat thread
			var newestTimestamp = 0;
			var newestChat = 0;
			var k;
			for (i=0; i<keys.length; i++) {
				k = keys[i];
				if (k != "newest") {
					if (this.messageDashboardData[k].timestamp > newestTimestamp) {
						newestTimestamp = this.messageDashboardData[k].timestamp;
						newestChat = k;
					}
				}
			}
			this.messageDashboardData.newest = newestChat;
			dashboard.delegateToSceneAssistant("update");
		} else {
			// only the 'newest' prop left so close the dashboard since no unviewed chats 
			dashboard.delegateToSceneAssistant("closeDashboard");
		}
	}

	// Error Dashboard
	hasChange = false;
	var newData = [];
	for (i = 0; i < this.sendErrorDashboardData.list.length; i++) {
		var msg = this.sendErrorDashboardData.list[i];

		if (msg.chatThreadId != chatThreadId) {
			newData.push(msg);
		} else {
			hasChange = true;
		}
	}
	dashboard = this.controller.getStageProxy(DashboardSendErrorStageName);
	
	if (hasChange) {
		this.sendErrorDashboardData.list = newData;
		
		// if the notification data is empty, don't both to update the dashboard since we will
		// be closing the dashboard
		if (dashboard && this.sendErrorDashboardData.list.length > 0) {
			// if the dashboards ever implement update methods with different names,
			// then this should be part of the notificationTypes object definition above
			dashboard.delegateToSceneAssistant("update");
		}
	}
	
	if (this.sendErrorDashboardData.list.length == 0 && dashboard) {
		dashboard.delegateToSceneAssistant("closeDashboard");
	}
};

NotificationAssistant.prototype.resetNewMessages = function(){
	this.messageDashboardData = {};
};

NotificationAssistant.prototype.clearMessageDashboard = function(){
	var dashboard = this.controller.getStageProxy(DashboardMessageStageName);
	this.messageDashboardData = {};
	if (dashboard) {
		dashboard.delegateToSceneAssistant("closeDashboard");
	}
};

NotificationAssistant.prototype.clearSendErrorDashboard = function(){
	var dashboard = this.controller.getStageProxy(DashboardSendErrorStageName);
	if (dashboard) {
		this.sendErrorDashboardData.list.clear();
		dashboard.delegateToSceneAssistant("closeDashboard");
	}	
};

NotificationAssistant.prototype.clearErrorDashboard = function(){
	var dashboard = this.controller.getStageController(DashboardErrorStageName);
	
	if (dashboard) {
		this.controller.closeStage(DashboardErrorStageName);
	}
};

// TODO: Consider removing this
NotificationAssistant.prototype.clearNotifications = function(){
	
	if (PalmSystem) {
		try {
			PalmSystem.clearBannerMessages();
		} catch (e) {
			//Mojo.Log.info("clearNotifications: ",e);
		}
	}
};

NotificationAssistant.prototype.setNotificationSound = function(bannerParams){
	switch (this.Messaging.messagingPrefs.enableNotificationSound) {
		case 1: // standard alert
			bannerParams.soundClass = "notifications";
			bannerParams.soundFile = undefined;
			break;
		case 2: // ringtone
			bannerParams.soundClass = "notifications";
			bannerParams.soundFile = this.Messaging.messagingPrefs.notificationRingtonePath;
			break;
		case 3: // vibrate
			bannerParams.soundClass = "vibrate";
			bannerParams.soundFile = undefined;
			break;
		default: // mute
			bannerParams.soundClass = "none";
			bannerParams.soundFile = undefined;
			break;
	}
	
	Mojo.Log.info("setNotificationSound result=%j", bannerParams);
};


