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

var ComposeAssistant = Class.create({
	
	MIN_BODY_PIXELS: 100, // Display at least this many pixels of the body element when the scene is first activated.
	
	initialize: function(email, stageName, composeReadyFunc) {
		this.email = email || new Email();
		
		// Unescape the summary text so it looks proper in the text input fields
		if (this.email.summary) {
			this.email.summary = this.email.summary.unescapeHTML();
		}
 
		// Model used for selecting the account that the email should be sent from. We don't 
		// yet have real data so set dummy choices. When the real data comes in, we'll call
		// updateFromModel()
		this.senderAccountModel = {
			account: email.account,
			choices: [
				{label: "", value: "", accountDomain: ""}
			]
		};

		this.draftIsDirty = false;
		this.forwardIsDirty = false;
		this.stageName = stageName;
		this.showingRecipients = false; // CC and BCC are initially hidden
		this.handleToggleRecipientsHandler = this.handleToggleRecipients.bindAsEventListener(this);
		this.boundHandleDeleteAttachment = this.handleDeleteAttachment.bindAsEventListener(this);
		this.boundSenderAccountChanged = this.senderAccountChanged.bind(this);
		this.recipientAdded = this.recipientAdded.bind(this);
		Mojo.Log.info("initialize compose, stageName: ", this.stageName);
		
		this.composeReadyFunc = composeReadyFunc;
		
		this.boundAttachmentTapped = this.handleAttachmentTapped.bind(this);
		
		// a hash table that contains the recipients in the TO field.  The recipients
		// in this hash table are the ones that we have analyzed and send reminder
		// notification if the recipients contact has a reminder.
		this.toRecipientReminders = {};
		this.attachmentDLProgress = {
			lastUpdate:0,
			progress:{},
			subs:{},
			completed:{}, // used to keep track of completed downloads for EAS
			clearSubscription: function(id) {
				try {
					this.subs[id].cancel();
					delete (this.subs[id]);
					delete (this.progress[id]);
				} catch (e) {
					Mojo.Log.logException(e, "clearSubscription");
				}
			}
		};
	},
	
	downloadsInProgress: function() {
		if (!this.attachmentDLProgress || !this.attachmentDLProgress.subs) {
			return false;
		}
		// check for currently downloading attachments
		if ($H(this.attachmentDLProgress.subs).keys().length > 0) {
			return true;
		}
		
		// check for paused/pending attachments
		if (this.email.attachments && this.email.attachments.length > 0  && this.attachmentsToDownload) {
			var attachments = this.email.attachments;
			var completed = this.attachmentDLProgress.completed;
			var attach = null;
			for (var i = 0, attLength = attachments.length; i < attLength; ++i) {
				if (!completed[attachments[i].id]) {
					return true;
				}
			} 
		}
		return false;
	},
  
	activate: function() {
		var scroller, distanceOffscreen;
		
  		if (this.firstTime === undefined) {
			this.firstTime = false;

			// The CC and BCC must begin life shown, so here we determine if they should be hidden.
			// Show if they have contacts in them, otherwise hide. 
			if (this.parsedRecips[EmailRecipient.roleBcc] && this.parsedRecips[EmailRecipient.roleBcc].length > 0) {
				this.showingRecipients = true;
				this.ccElement.show();
				this.bccElement.show();
				this.controller.showWidgetContainer(this.ccElement);
				this.controller.showWidgetContainer(this.bccElement);
			}

			if (this.parsedRecips[EmailRecipient.roleCc] && this.parsedRecips[EmailRecipient.roleCc].length > 0) {
				this.ccElement.show();
				this.controller.showWidgetContainer(this.ccElement);
			}
			
			this.toElement.observe(Mojo.Event.tap, this.handleToggleRecipientsHandler);
			
			// If the recipient list is very long, then the body field is not initially visible.
			// Detect this case, and auto-scroll it to be visible.
			scroller = this.controller.getSceneScroller();
			distanceOffscreen = this.controller.get('msg_rte').viewportOffset().top - 
											(scroller.mojo.scrollerSize().height - this.MIN_BODY_PIXELS);
			if(distanceOffscreen > 0) {
				
				// The addressing widget scrolls itself into view AFTER activate() is called, and this 
				// screws up our own scroll adjustment, so we unfortunately need to delay our scrolling until
				// after the addr widget has gone.  This call to scrollTo doesn't need to be bound, since
				// exposed widget methods are already bound to the widget.
				scroller.mojo.scrollTo.defer(undefined, scroller.mojo.getScrollPosition().top - distanceOffscreen, true);
			}
			
		}
		if (this.lastFocused) { 
			this.lastFocused.focus(); 
		}
		
		// Call the compose ready function when we're activated.
		// This should re-enable the reply/compose/forward/etc. menu commands in whatever scene opened us.
		if(this.composeReadyFunc) {
			this.composeReadyFunc();
			this.composeReadyFunc = undefined; // but only call it once!
		}
		
	},

	setup: function() {
		this.mainElement = this.controller.get('main');
		
		var senderAccountAttributes = {
					modelProperty: 'account', // this will be used for the element's name
					label: $L("From")
				};
		this.controller.setupWidget('email-composeview-from', senderAccountAttributes, this.senderAccountModel );

		var content = Mojo.View.render({
				object: this.email,
				template: 'compose/compose_area'
			});

		this.mainElement.innerHTML = content;
		this.toElement = this.lastFocused = this.controller.get('toContactsPicker'); // to is autofocused on load
		this.ccElement = this.controller.get('ccContactsPicker');
		this.bccElement = this.controller.get('bccContactsPicker');
		this.subjectElement = this.controller.get('emailSubject');
		this.bodyElement = this.controller.get('msg_rte');
		this.forwardElement = this.controller.get('forwarded_msg_body');

		this.focusTracker = function(selectedElem) {
			if (selectedElem) {
				this.lastFocused = selectedElem;
			}
		}.bind(this);
		
		this.focusListeners = new Array(
			// Storing FocusListeners in order to properly stopListening in cleanup
			Mojo.Event.listenForFocusChanges(this.subjectElement, this.focusTracker),
			Mojo.Event.listenForFocusChanges(this.toElement, this.focusTracker), 
			Mojo.Event.listenForFocusChanges(this.ccElement, this.focusTracker),
			Mojo.Event.listenForFocusChanges(this.bccElement, this.focusTracker),
			Mojo.Event.listenForFocusChanges(this.bodyElement, this.focusTracker)
		);
		
		this.cmdMenuModel = {
				visible: true,
				menuClass: 'palm-white',
				items: [
					{label:$L('Attach'), icon:'attach', command:'attach'},
					{label:$L('Send'), icon:'send', command:'send'}
				]};

		this.appMenuSaveIndex = 2;
		this.priorityFlagMenuItem = { label:ComposeAssistant.kAppMenuHighPriority, shortcut:'j', command:'priority'};
		this.appMenuModel = {
				visible:true,
				items: [
					this.priorityFlagMenuItem,
					{label:$L('Discard Message'), shortcut:'d', command:'cancel'},
					{label:$L('Save as Draft'), shortcut:'s', command:'save', disabledProperty:true }
				]};
		this.controller.setupWidget(Mojo.Menu.appMenu, {richTextEditMenu: true}, this.appMenuModel);
		
		// Typing anything will mark the email as dirty. Except the escape key and the swipe area
		// b/c minimizing with the swipe-back gesture sends escape, and swipe sends 231.
		this.setDirtyHandler = function(e) { if (e.keyCode !== Mojo.Char.escape && e.keyCode !== 231) { this.setDirty(true); }}.bind(this);
		this.setDirtyForwardHandler = function(e) {
				if (e.keyCode !== Mojo.Char.escape && e.keyCode !== 231) { 
					this.forwardIsDirty = true;
					if (this.email.attachments.length > 0 && !this.attachmentDownloadStarted) {
						this.downloadForwardAttachments();
					} 
				}
			}.bind(this);
		// use keydown to properly register backspaces
		Event.observe(this.subjectElement, 'keydown', this.setDirtyHandler);
		Event.observe(this.bodyElement, 'keydown', this.setDirtyHandler);
		Event.observe(this.forwardElement, 'keydown', this.setDirtyForwardHandler, true);

		this.controller.setupWidget(Mojo.Menu.commandMenu, undefined, this.cmdMenuModel);			

		// Add address picker
		this.parsedRecips = EmailRecipient.separateRecipients(this.email.recipients);
		var toLabel = $L('To');
		this.toContactsPicker = {
			"property" : 'to$A',	
			"includeEmails": true,
			"showGAL": true,
			"labelText": "<div id='toLabel'>"+toLabel+"</div>",
			"hintText": $L("Name or email address"),
			"recipients": this.parsedRecips[EmailRecipient.roleTo],
			"actionableLabel": true
		};
		this.controller.setupWidget('toContactsPicker', undefined, this.toContactsPicker);
		this.controller.listen('toContactsPicker', Mojo.Event.addressingRecipientAdded, this.recipientAdded);

		// setup and store (b)cc models
		this.ccContactsPicker = {
			"property" : 'cc$A',	
			"includeEmails": true,
			"showGAL": true,
			"labelText": $L("Cc"),
			"hintText": $L("Name or email address"),
			"recipients": this.parsedRecips[EmailRecipient.roleCc]
		};
		
		this.bccContactsPicker = {
			"property" : 'bcc$A',	
			"includeEmails": true,
			"showGAL": true,
			"labelText": $L("Bcc"),
			"hintText": $L("Name or email address"),
			"recipients": this.parsedRecips[EmailRecipient.roleBcc]
		};
		
		if (this.ccContactsPicker.recipients.length > 0 || this.ccContactsPicker.recipients.length) {
			this.setupCCWidgets();
		} 
		
		var subjectWidgetAttrs = {
			textFieldName : 'summary',  
			hintText: $L('Enter Subject...'),
			modelProperty: 'summary',
			acceptBack: true
		};
		this.controller.setupWidget('emailSubject', subjectWidgetAttrs, this.email);
		
		// need to determine type of account before handling attachments
		var accounts = EmailAppDepot.getDepot().accounts;
		if (accounts && accounts.count > 0) {
			this.renderListOfUserAccount(accounts);
		} else {
			Mojo.Log.error("Compose scene using EmailAccount.getAccounts because depot isn't set");
			EmailAccount.getAccounts(this.controller, this.renderListOfUserAccount.bind(this), 
									 function(err) { Mojo.Log.error("Compose getAccounts:", $H(err).inspect()); });
		}

		// Need to perform some fixup of the attachments before setting up the attachments-list
		var fixupAttachment = this.fixupAttachment;
		this.email.attachments.each( function(attachment) { fixupAttachment(attachment); });
		this.email.inlineAttachments.each( function(attachment) { fixupAttachment(attachment); });

		this.attachmentModel = { items: this.email.attachments };
		
		this.attachmentsListElem = this.controller.get('compose-attachments-list');
		this.attachmentsListElem.observe(Mojo.Event.tap, this.boundAttachmentTapped);

		// Most likely there are no attachments so initially hide the div
		if (this.email.attachments == null || this.email.attachments.length == 0) {
			this.attachmentsListElem.hide(); 
		} else {
			// cannot lazy load. Setup now.
			this.setupAttachmentWidget();
			if (this.email.action === ComposeAssistant.kForwardAction && !this.isEasAccount()) {
				this.downloadForwardAttachments();
			}
		} 

		// Setup the richtext editors
		this.controller.setupWidget('msg_rte', {} , {});
		// only setup forward rte if actually a forward
		if (this.email.action === ComposeAssistant.kForwardAction) {
			// make into an rte
			this.setupForwardRTE();
		}
		
		this.signatureGuard = function(e) { 
			// reset formatting on signature clear
			if (this.bodyElement.innerHTML.match(ComposeAssistant.kEmptySignatureRegex)) {
				this.controller.get('signature').remove();
				// focus and set cursor to the end of the text
				this.bodyElement.focus();
				var sel = this.controller.window.getSelection();
				sel.setPosition(this.bodyElement, this.bodyElement.textContent.length);
			}
		}.bind(this);
		
		Event.observe(this.bodyElement, 'keydown', this.signatureGuard, true);

		this.controller.get('email-composeview-from').observe(Mojo.Event.propertyChange, this.boundSenderAccountChanged);

		if (this.email.replyMessage) {
			this.controller.get('replyMessage').value = this.email.replyMessage;		
		}
			
		if (this.email.priority && Email.getPriorityClass(this.email.priority) === Email.HIGH_PRIORITY_CLASS) {
			// set priority for drafted emails
			this.setPriority(true);
		} else {
			// Initial priority is normal. Note, this needs to be set after the appMenu items are
			// created since it modifies them.
			this.setPriority(false);
		}
		
		// Delayed a little, since we want the header part of render as quickly as possible
		ComposeAssistant.onLoad.defer(this.controller, this.email);
	},
	
	downloadForwardAttachments: function() {
		var attachments = this.email.attachments;
		if (!attachments) {
			return;
		}
		this.attachmentsToDownload = true;
		var attachment = null;
		for (var idx = 0, length = attachments.length; idx < length; ++idx) {
			attachment = attachments[idx];
			// if attachment has not been downloaded already
			if (attachment.download === 'display' || !attachment.uri) {
				this.controller.get('download_icon_' + attachment.id).show();
				this.controller.get('progress_' + attachment.id).addClassName('attachment-downloadable');
				this.startAttachmentDownload(attachment.id);
				this.attachmentDownloadStarted = true;
			} else {
				this.attachmentDLProgress.completed[attachment.id] = true;
				this.handleAttachmentDetails(attachment);
			}
		}
	},
	
	showDownloadAlert: function() {
		this.doSend = false;
		this.controller.showAlertDialog({
			onChoose: function(value){ 
							this.doSend = value;
							if (this.doSend === true) {
								this.send();
							}
			  			}.bind(this),
			title: $L("Downloading Attachments"),
			message: $L("Sending now will only send the attachments that have finished down- loading. All other attachments will not be sent."),
			choices: [
				 {label:$L('Send Without All Attachments'), value: true, type:'alert'},  
        		 {label:$L("Cancel"), value: false, type:'alert'},
			]
		});
	},
	
	cleanup: function() {
		// Save a draft if necessary
		this.saveDraft();

		this.toElement.stopObserving(Mojo.Event.tap, this.handleToggleRecipientsHandler);
		this.controller.stopListening('toContactsPicker', Mojo.Event.addressingRecipientAdded, this.recipientAdded);
		this.controller.get('email-composeview-from').stopObserving(Mojo.Event.propertyChange, this.boundSenderAccountChanged);
		
		this.attachmentsListElem.stopObserving(Mojo.Event.listDelete, this.boundHandleDeleteAttachment);
		Event.stopObserving(this.subjectElement, 'keydown', this.setDirtyHandler);
		Event.stopObserving(this.bodyElement, 'keydown', this.setDirtyHandler);
		Event.stopObserving(this.bodyElement, 'keydown', this.signatureGuard);
		Event.stopObserving(this.forwardElement, 'keydown', this.setDirtyForwardHandler);
		
		while (this.focusListeners.length > 0) {
			this.focusListeners.pop().stopListening();
		}
		this.attachmentsListElem.stopObserving(Mojo.Event.tap, this.boundAttachmentTapped);
	},

	orientationChanged: function(orientation) {
		if (orientation === "left" || orientation === "right") {
			this.controller.sceneElement.addClassName('landscape');
		} else {
			this.controller.sceneElement.removeClassName('landscape');
		}
	},
	
	setupAttachmentWidget: function () {
		AppAssistant.lazySetupWidget(this.controller, 'compose-attachments-list', 'List',
							{ itemTemplate: 'compose/compose_attachments', swipeToDelete:true },
							this.attachmentModel);
		this.controller.instantiateChildWidgets(this.attachmentsListElem);
		this.attachmentsListElem.observe(Mojo.Event.listDelete, this.boundHandleDeleteAttachment);							
		this.attWidgetRendered = true;
	},
	
	startAttachmentDownload: function(id) {
		Mojo.Log.info("start attachment download", id);
		var progressbar = this.controller.get('progress_' + id);
		if (progressbar.visible()) {
			Mojo.Log.info("ignoring tap because attachment is already downloading");
		} else {
			this.attachmentDLProgress.subs[id] = Message.loadAttachment(id, this.attachmentDownloadProgress.bind(this), this.attachmentError.bind(this, id));
			// This makes the progressbar show up so the user gets immediate feedback. 
			this.controller.get('progress_' + id).show(); // ensures the progress bar is shown
			// Set the initial progress to 0%
			this.attachmentUpdateProgressbar(id, 0);
		}
	},
	
	handleAttachmentDetails: function(attachment) {
		Mojo.Log.info("handleAttachmentDetails ", attachment.id);

		// Update the uri for this attachment now that we got one.
		this.email.attachments.each(function(a) {
			if (a.id == attachment.id) {
				a.uri = attachment.uri;
				$break;
			}
		});

		// For some reason the id needs to be a string so using toString().
		var elem = this.controller.get((attachment.id).toString());
		var newUri = attachment.uri;
		elem.writeAttribute('x-uri', newUri);
		elem.writeAttribute('x-mimetype', attachment.mimeType);
		
		this.controller.get('download_icon_' + attachment.id).hide(); // remove the download arrow from the icon
		this.controller.get('progress_' + attachment.id).hide(); // ensures the progress bar is no longer shown
	},

	
	attachmentDownloadProgress: function(info) {
		// If the object doesn't contain a 'id' property, it isn't valid download progress
		if (info.id) {
			if (info.status == Message.ATTACHMENT_LOAD_COMPLETED_EVENT) {
				Mojo.Log.info("attachmentDownloadProgress complete for id:", info.id);
				this.attachmentDLProgress.clearSubscription(info.id);
				this.attachmentDLProgress.completed[info.id] = true;
				this.attachmentUpdateProgressbar(info.id, 100);
				
				// remove class to prevent subsequent download requests
				this.controller.get('progress_' + info.id).removeClassName('attachment-downloadable');

				// Final step to fully download is to get details of this attachment, including the URI.
				Message.getAttachmentDetails(this.controller, info.id, this.handleAttachmentDetails.bind(this), this.attachmentError.bind(this, info.id));
			}
			else if (info.status == Message.ATTACHMENT_LOAD_PROGRESS_EVENT) {
				// The transports can be rather aggressive about sending progress notifications so
				// defend against that by only updating the UI periodically
				var now = Date.now();
				if (now < this.attachmentDLProgress.lastUpdate + 200) {
					this.attachmentDLProgress.progress[info.id] = info.progress;
				} else {
					this.attachmentDLProgress.lastUpdate = now;
					var that = this;
					if (info.progress !== undefined) {
						this.attachmentDLProgress.progress[info.id] = info.progress;
					}
					var progressObj = this.attachmentDLProgress.progress;
					Object.keys(progressObj).each(function(id) {
						var progress = progressObj[id];
						Mojo.Log.info("attachmentDownloadProgress id: ", id, ", progress: ", progress);
						that.attachmentUpdateProgressbar(id, progress);
					});
				}
			}
		}
	},
	
	attachmentUpdateProgressbar: function(id, percent) {
		var progressGroup = this.controller.get('progress_' + id);
		if (progressGroup) {
			var totalWidth = 2.48; // = 248 / 100%
			var progressWidth = Math.round(totalWidth * percent);
			progressGroup.down("div.download-progress").setStyle({width:progressWidth+10+"px"});
			var backgrndWidth = Math.round(totalWidth * (100 - percent));
			progressGroup.down("div.download-background").setStyle({width:backgrndWidth+"px"});
		} else {
			Mojo.Log.error("Attachment ID ", id, " is invalid.");
		}
	},
	
	attachmentError: function(id, err) {
		Mojo.Log.error("handleError ", err.errorText);
		this.stopAttachmentDownload(id);
		var that = this;
		var errorText;
		if (err.errorText && err.errorText.length > 0) {
			errorText = $L("Error downloading file. Server reports: #{errorText}").interpolate(err).escapeHTML();
		} else {
			errorText = $L("Error while downloading file.");
		}

		this.controller.showAlertDialog({
			onChoose: function(value) {},
			title: $L("Unable To Download File"),
			message: errorText,
			choices: [ {label:$L('OK'), value:'dismiss', type:'alert'} ]
		});
	},

	stopAttachmentDownload: function(id) {
		Mojo.Log.info("stop attachment download", id);
		this.attachmentDLProgress.clearSubscription(id);
		Message.cancelLoadAttachment(this.controller, id);
		
		// This makes the progressbar show up so the user gets immediate feedback. 
		this.controller.get('progress_' + id).hide();
		// Set the initial progress to 0%
		this.attachmentUpdateProgressbar(id, 0);
	},
	
	setupCCWidgets: function () {
		AppAssistant.lazySetupWidget(this.controller, 'ccContactsPicker', 'AddressingWidget',
							undefined,
							this.ccContactsPicker);
		AppAssistant.lazySetupWidget(this.controller, 'bccContactsPicker', 'AddressingWidget',
							undefined,
							this.bccContactsPicker);
		this.ccWidgetSRendered = true;
	},
	
	setupForwardRTE: function () {
		AppAssistant.lazySetupWidget(this.controller, 'forwarded_msg_body', 'RichTextEdit', {},{});
	},

	handleDeleteAttachment: function(event) {
		// shorthand vars
		var attachments = this.email.attachments;
		var subs = this.attachmentDLProgress.subs;
		var completed = this.attachmentDLProgress.completed;
		
		// Remove the specified attachment
		this.email.attachments.splice(event.index, 1);
		if (this.email.attachments.length === 0) {
			this.attachmentsListElem.hide();
		}
		
		if (this.email.action === ComposeAssistant.kForwardAction) {
			this.forwardIsDirty = true; 
			if  (!this.isEasAccount()) {
				return;
			}
			
			var attach = null;
			for (var i = 0, numAttachs = this.email.attachments.length; i < numAttachs; ++i) {
				attach = attachments[i];
				if (!subs[attach.id] && !completed[attach.id]) {
					this.attachmentsToDownload = true;
					// need class name to download
					this.controller.get('progress_' + attach.id).addClassName('attachment-downloadable');
					this.controller.get('download_icon_' + attach.id).show();
					this.startAttachmentDownload(attach.id);
				}
			}
		}
	},
	
	handleAttachmentTapped: function(event) {
		var targetRow = this.controller.get(event.target);
		var listDiv = targetRow;
		var tempDiv = null;
		if (!listDiv.hasClassName('attachment-downloadable')) {
			tempDiv = listDiv.up('div.attachment-downloadable');
			if (tempDiv) {
				listDiv = tempDiv;
			} else {
				// try from parent palm-row
				try {
					listDiv = listDiv.up('div.attachment-info').down('div.attachment-downloadable');
				} catch (e) {
					Mojo.Log.error("Unable to grab div. error: %s", e);
					listDiv = null;
				}
			}
		}

		if (listDiv) {
			var id = listDiv.id.replace('progress_', "");
			var attachmentUri = listDiv.getAttribute('x-uri');
			var attachmentMimeType = listDiv.getAttribute('x-mimetype');
			if (event.target.className === "download-cancel") {
				this.stopAttachmentDownload(id);
			} else {
				// Stop the timeout
				if (this.hideTimeout) {
					clearTimeout(this.hideTimeout);
					this.hideTimeout = null;
				}
				this.startAttachmentDownload(id);
			}
		}
	},

	/**
	 * handle a menu command.
	 */
	handleCommand: function(event) {
		if (event.type == Mojo.Event.command) {
			switch (event.command) {
				case 'attach':
					this.handleAttach();
					break;

				case 'save':
					this.draftIsDirty = true; // set to true to make sure the save occurs
					this.saveDraft();
					break;

				case 'send':
					this.send();
					break;

				case 'cancel':
					this.cancelCompose();
					break;
						
				case 'priority':
					// Toggle the priority
					this.setPriority(!this.priority);
					break;

				case 'forecolor':
					MenuController.handleTextColorMenu(this);
					break;
			}
		}
		else if (event.type == Mojo.Event.commandEnable) {
			if (event.command === 'forecolor') {
				var focusNode = this.controller.window.document.querySelector(':focus');
				if(!(focusNode && Mojo.View.isRichTextField(focusNode))) {
					event.preventDefault();
				}
				event.stopPropagation();
			}
		}
	},

	setPriority: function(priority) {
		this.priority = priority;
		if (priority) {
			this.controller.get('priority').value = '1';
			this.priorityFlagMenuItem.label = ComposeAssistant.kAppMenuNormalPriority;
			this.controller.get('emailSubjectField').addClassName(Email.HIGH_PRIORITY_CLASS);
		} else {
			this.controller.get('priority').value = '0';
			this.priorityFlagMenuItem.label = ComposeAssistant.kAppMenuHighPriority;
			this.controller.get('emailSubjectField').removeClassName(Email.HIGH_PRIORITY_CLASS);
		}
	},

	fixupAttachment: function(file) {
		// The filepicker sets the 'fullPath' property, but MailService wants 'uri'
		if (!file.uri && !file.id) {
			if (file.fullPath) {
				file.uri = file.fullPath;
			} else {
				Mojo.Log.warn("WARNING: email attachment had no id nor uri (or fullPath) property file");
				Mojo.Log.logProperties(file);
				return;
			}
		}

		// If displayeName is not set, make it because we need it for display
		if (!file.displayName) {
			var startOfDisplayname = file.uri.lastIndexOf('/') + 1;
			if (startOfDisplayname > 0) {
				file.displayName = file.uri.substring(startOfDisplayname);
			} else {
				file.displayName = file.uri;
			}
		}

		// Escape any apostrophe chars in the file path otherwise the json gets messed up.
		if (file.uri) {
			file.uri = file.uri.replace(/([\'\"])/g, "\\$1");
		}

		// If iconType is not set, determine it and 'extension' it because we need it for display
		if (!file.iconType) {
			Attachments.processFileObject(file);
		}
		
		// MailService wants mimeType
		file.mimeType = Attachments.mimeTypeTable[file.extension];
		if (!file.mimeType || !file.mimeType.startsWith("image")) {
			file.displayImage = "display:none";
		}
	},

	/**
	 * the callback for the FilePicker
	 */
	onFilePickerSelect: function(file) {
		this.fixupAttachment(file);
		this.email.attachments.push(file);
		this.setDirty(true);
		// add the new file to the attachments list
		this.attachmentsListElem.show(); // In case the element is hidden
		this.attachmentsListElem.mojo.noticeAddedItems(this.email.attachments.length, [file]);
	},

	/**
	 * use this to attach files in the compose view
	 */  
	handleAttach: function() {
		var params = {
			//_mockData: true,
			actionType: 'attach',
            defaultKind: 'image',
			onSelect: this.onFilePickerSelect.bind(this)
		};
		Mojo.FilePicker.pickFile(params, this.controller.stageController);
		// lazy setup as needed
		if (!this.attWidgetRendered) {
			this.setupAttachmentWidget();
		}	
	},

	/**
	 * Show/hide the CC and BCC contacts pickers
	 */  
	handleToggleRecipients: function(event) {
		// Manually toggling because the CC list may or may not have started out hidden and they
		// need to be shown/hidden together.
		if (event.target.id !== 'toLabel' && !event.target.querySelector('div#toLabel')) {
			return;
		}
		
		if (!this.ccWidgetSRendered) {
			// could do this in the else block below
			this.setupCCWidgets();
		}
		
		if (this.showingRecipients) {
			this.showingRecipients = false;
			this.ccElement.hide();
			this.bccElement.hide();
			this.controller.hideWidgetContainer(this.ccElement);
			this.controller.hideWidgetContainer(this.bccElement);
		} else {
			this.showingRecipients = true;
			this.ccElement.show();
			this.bccElement.show();
			this.controller.showWidgetContainer(this.ccElement);
			this.controller.showWidgetContainer(this.bccElement);
		}
	},

	renderListOfUserAccount: function(response) {
		var email = this.email;
		var model = this.senderAccountModel;
		model.choices = [];
		var thisAccountLogin;
		// If the account was specified, use it instead of the login
		if (!model.account && email.login) {
			thisAccountLogin = email.login.toLowerCase();
		}
		var modelAccountValid = false;
		response.list.each( function(account) {
			if (account.originalLogin) {
				var choiceEntry = new Object();
				var sigToUse = this.resolveSignature(account);
				choiceEntry.label = account.originalLogin;
				choiceEntry.value = account.id;
				choiceEntry.signature = sigToUse;
				choiceEntry.accountDomain = account.accountDomain;
				model.choices.push(choiceEntry);
				//Mojo.Log.info('user account=%j', account);
				// If replying to an email, use that account otherwise look for the 
				// the default account.
				if (thisAccountLogin && thisAccountLogin == account.originalLogin.toLowerCase()) {
					email.signature = sigToUse;
					modelAccountValid = true;
					model.account = account.id;
					// The service sends up defaultAccountFlag of either "0" or "1".
					// It could change, though so make JS convert it to true/false
					this.originalAccountDomain = account.accountDomain;
				} else if (!model.account && account.defaultAccountFlag == true) {
					modelAccountValid = true;
					model.account = account.id;
					email.signature = sigToUse;
				} else if (model.account && model.account == account.id) {
					modelAccountValid = true;
					email.signature = sigToUse;
				} 
			}
		}.bind(this));

		if (modelAccountValid === false) {
			Mojo.Log.error("No valid account found, using first account", response.list[0].id);
			model.account = response.list[0].id;
			email.signature = this.resolveSignature(response.list[0]);
		}

		// If there is only one choice don't show the From field
		if (model.choices.length > 1) {
			this.controller.get('email-composeview-from-wrapper').show();
		}
        this.controller.modelChanged(model);
		
		var sigElement = this.controller.get('signature');
		if (sigElement) {
			sigElement.innerHTML = email.signature;
		}
	},

	senderAccountChanged: function(event) {
		var sigElement = this.controller.get('signature');
		if (!sigElement) {
			Mojo.Log.error("Compose view could not find 'signature' element");
			return;
		}

		var email = this.email;
		event.model.choices.each( function(account) {
			if (account.value == event.value) {
				email.signature = this.resolveSignature(account);
				sigElement.innerHTML = email.signature;
				$break;
			};
		}.bind(this));
	},
	
	resolveSignature: function(account) {
		if (!account) {
			return ServiceStrings.stringsNeededByService.defaultSignature;
		}
		return (account.signature === "") ? ServiceStrings.stringsNeededByService.defaultSignature : account.signature; 
	},

	closeStage: function() {
		var emailStageController = Mojo.Controller.appController.getStageController(this.stageName);
		if (emailStageController) {
			Mojo.Controller.getAppController().closeStage(this.stageName); // remove ourself
		} else {
			Mojo.Log.error("WARNING: using window.close because stage not found. name=", this.stageName);
			this.controller.window.close();
		}
	},

	deletePreviousDraft: function() {
		// if the email has an account and a timestamp property then it was restored from drafts,
		// so delete the draft.
		var email = this.email;
		if (email.id > 0 && email.account !== undefined && email.timeStamp !== undefined) {
			Mojo.Log.info("deletePreviousDraft id=", email.id);
			Message.setDeleted(email.id, true);
			email.account = undefined;
			email.timestamp = undefined;
		}
	},

	saveDraft: function() {
		if (this.draftIsDirty === true) {
			Mojo.Log.info("ComposeAssistant.saveDraft");
			this.controller.commitChanges();
			// Final check: make sure the subject and message body contain something.
			// since message body contains html tags, check length of 5 or more
			var form = this.controller.get('composeMessage');
			var subject = form.summary;
			var theMessageBody = this.bodyElement.innerHTML;
			var forwardedBody = this.forwardElement.innerHTML;
			
			if (theMessageBody && forwardedBody && forwardedBody !== "") {
				theMessageBody += ComposeAssistant.kForwardDraftDelimeter + forwardedBody;
			}
			
			if (!subject.value.blank() || theMessageBody.length > 5) {
				// Need to replace the local file location back to original 'cid:' string
				theMessageBody = ComposeAssistant.replaceURIs(theMessageBody, this.email);
				// Copy the html from the richtext editor to the textarea
				this.controller.get('msg').value = theMessageBody;
				var assistant = Mojo.Controller.getAppController().assistant;

				// This is how to add a JSON object to the value property of an input. Doing this because
				// it is the best way to safely add the attachment object into the dom so serializeMojo
				// can create the email data object with an array of attachments.
				if (this.email.attachments && this.email.attachments.length > 0) {
					var attachmentDivs = this.controller.select(".attachment-details-storage");
					var attachmentIndex;
					for (attachmentIndex = 0; attachmentIndex < attachmentDivs.length; ++attachmentIndex) {
						attachmentDivs[attachmentIndex].value = Object.toJSON(this.email.attachments[attachmentIndex]);
					}
				}

				//get the form this button is a part of/ its called
				var successFunction = function() {
					// The file is no longer dirty, so disable the "save" command menu
					this.setDirty(false);
					var message;
					if (params.summary && !this.downloadsInProgress()) {
						message = $L("Saved \"#{summary}\"").interpolate(params);
					} else if (this.downloadsInProgress()){
						message = $L("Saved email. #{discardSummary}").interpolate({discardSummary: this.getDiscardDownloadSummary()});
					} else {
						message = $L("Saved email.");
					}
					
					assistant.notificationAssistant.handleNotification({ type:"general", message:message.escapeHTML() });
					// Delete the draft that this is based on, if any
					this.deletePreviousDraft();
					assistant.saveRequest = undefined; // remove reference to the request so it can be gc'ed 
				}.bind(this);
				
				//get all the items we need from the compose screen
				var params = Mojo.View.serializeMojo(form, true); //to a hash for params to email service
				// include original inline attachments so that mail service can add them
				// to the email to be sent
				if (!params.parts$A)
					params.parts$A = [];
				var addAttchFunc = function(attachment) {
					params.parts$A.push(attachment);
				};
				this.email.inlineAttachments.each(function(a) { addAttchFunc(a); } );
				
				assistant.saveRequest = this.email.saveMessage(params, successFunction, this.failResponseHandler.bind(this));
			}
		}
	},
	
	isEasAccount: function() {
		var accountDomain = 0;
		this.senderAccountModel.choices.each( function(account) {
			if (account.value == this.senderAccountModel.account) {
				accountDomain = account.accountDomain;
				throw $break;
			}
		}.bind(this));

		Mojo.Log.info('ComposeAssistant.send(): accountDomain=%s, this.originalAccountDomain=%s', accountDomain, this.originalAccountDomain);
		return (this.originalAccountDomain === "eas" || accountDomain === "eas");
	},

	send: function() {
		// Prevent multiple sends if the user taps the send button multiple times
		if (this.sending === true) {
			return;
		}
		
		if (this.downloadsInProgress() && !this.doSend) {
			this.showDownloadAlert();
			return;
		}
		
		this.sending = true;

		Mojo.Log.info("sending email");
		this.controller.commitChanges();
		var originalText = "";
		var forwardEdited = false;
		if (this.email.action === ComposeAssistant.kForwardAction) {
			if (!this.forwardIsDirty) {
				if (!this.isEasAccount()) {				
					// only the original email domain and domain that is used for sending 
					// the forwarded email are EAS should not include original text.  All
					// other cases should include the original forwareded text.
					originalText = this.email.originalText;
				}
			}
			else {
				forwardEdited = true;
				originalText = this.forwardElement.innerHTML;
			}
		}

		// This is how to add a JSON object to the value property of an input. Doing this because
		// it is the best way to safely add the attachment object into the dom so serializeMojo
		// can create the email data object with an array of attachments.
		if (this.email.attachments && this.email.attachments.length > 0) {
			var attachmentDivs = this.controller.select(".attachment-details-storage");
			var attachmentIndex;
			for (attachmentIndex = 0; attachmentIndex < attachmentDivs.length; ++attachmentIndex) {
				attachmentDivs[attachmentIndex].value = Object.toJSON(this.email.attachments[attachmentIndex]);
			}
		}

		// Copy the html from the richtext editor to the textarea
		var theMessageBody = this.controller.get('msg_rte').innerHTML;
		var msgTextField = this.controller.get('msg');
		msgTextField.value = theMessageBody + originalText;
		// Need to replace the local file location back to original 'cid:' string
		msgTextField.value = ComposeAssistant.replaceURIs(msgTextField.value, this.email);
	
		//get the form this button is a part of and paramertize them 
		var form = this.controller.get('composeMessage');
		var params = Mojo.View.serializeMojo(form, true); //to a hash for params to email service
		
		if (forwardEdited) {
			// add flag so service can see
			params.forwardEdited = true;
		}
		
		// include original inline attachments so that mail service can add them
		// to the email to be sent
		if (!params.parts$A)
			params.parts$A = [];
		var addAttchFunc = function(attachment) {
			params.parts$A.push(attachment);
		};
		this.email.inlineAttachments.each(function(a) { addAttchFunc(a); } );
		
		try {

			//get the form this button is a part of/ its called
			var successFunction = function() {
				// Just sent the email, so clear the dirty flag to prevent any further save-to-draft functionality.
				this.setDirty(false);
				// Delete the draft that this is based on, if any
				this.deletePreviousDraft();

				Mojo.Log.info("send complete, closeStage: ", this.stageName);
				this.closeStage();
			}.bind(this);
			this.email.sendMessage(this.controller, params, successFunction, this.failResponseHandler.bind(this));
			var notificationAssistant = Mojo.Controller.getAppController().assistant.notificationAssistant;
			var message;
			if (params.summary && !this.downloadsInProgress()) {
				message = $L("Sending \"#{summary}\"").interpolate(params);
			} else if (this.downloadsInProgress()) {
				message = $L("Sending email. #{discardSummary}").interpolate({discardSummary: this.getDiscardDownloadSummary()});
			} else {
				message = $L("Sending email");
			}
			
			notificationAssistant.handleNotification({ type:"general", message:message });
		} catch (e) {
			this.sending = false;
			// sendMessage() can throw an exception if there is something wrong with the email,
			// such as no recipients. Display those errors here.
			this.controller.showAlertDialog({
				onChoose: Mojo.doNothing,
				title: $L("Error"),
				message: $L(e.message),
				choices: [
					{label:$L('Done'), value:'dismiss', type:'alert'}
				]
			});
		}
	},
	
	getDiscardDownloadSummary: function() {
		return $L("#{numDiscard} attachment(s) discarded").interpolate({numDiscard: $H(this.attachmentDLProgress.subs).keys().length});
	},

	failResponseHandler: function(response) {
		this.sending = false;
		this.controller.showAlertDialog({
			onChoose: Mojo.doNothing,
			title: $L("Error"),
			message: $L(response.errorText), // The text from the service is stored in ServiceStrings.js so it can be localized
			choices: [
				{label:$L('Done'), value:'dismiss', type:'alert'}
			]
		});
	},

	cancelCompose: function() {
		// First make sure the dirty flag is cleared so we don't inadvertently save 
		this.setDirty(false);
		Mojo.Log.info("cancelCompose, closeStage: ", this.stageName);
		this.closeStage();
	},

	// Set or clear the dirty flag & update the menus
	setDirty: function(dirty) {
		// Only change if the dirty flag is different
		if (dirty != this.draftIsDirty) {
			Mojo.Log.info("ComposeAssistant.setDirty ", dirty);
			this.draftIsDirty = dirty;
			this.appMenuModel.items[this.appMenuSaveIndex].disabled = !dirty;
			if (this.controller) {
				this.controller.modelChanged(this.appMenuModel);
			}
		}
	}, 
	
//<Reminder Info>
	recipientAdded: function(event) {
		var form = this.controller.get('composeMessage');
		var params = Mojo.View.serializeMojo(form, true); //to a hash for params to compose area
		if (this.contactReminder === undefined) {
 			this.contactReminder = new ContactReminder();
		}
		var contactReminderFunc = this.contactReminder.displayReminder.bind(this.contactReminder);
		
		//Mojo.Log.info('Got recipient contacts=%j', params);
		params.to$A.each(function(recip) {
			Mojo.Log.info('contact from the list=%j', recip);
			EmailRecipient.getDetails(this.controller, recip.value, contactReminderFunc);
		}.bind(this));
		
	},
});


ComposeAssistant.onLoad = function(controller, email) {
	// We use this onLoad to add the text body into the email after it loads
	// so that emails that contain images don't slow down the initial display
	// of the scene.
	// Strip out all scripts since they can do dangerous things
	var bodyText = email.text;
	// Replace body text's CID values for image source with locally saved image
	// file location. 
	bodyText = ComposeAssistant.replaceCIDs(bodyText, email);
		
	// this block handles resolution of drafts of forwarded messages.
	// only really needed here, so not breaking out into its own method
	var forwardStartIndex = bodyText.indexOf(ComposeAssistant.kForwardDraftDelimeter);
	if (forwardStartIndex >= 0) {
		var textBlocks = bodyText.split(ComposeAssistant.kForwardDraftDelimeter);
		var forwardText = "";
		if (textBlocks.length > 1) {
			bodyText = textBlocks[0];
			email.originalText = textBlocks[1];
		} else if (textBlocks.length === 1) {
			if (forwardStartIndex === 0) {
				bodyText = "";
				email.originalText = textBlocks[0];
			} else {
				bodyText = textBlocks[0];
				email.originalText = "";
			}
		}
		if (email.originalText !== "") {
			email.action = "forward";
		}
	}
	
	var originalText = email.originalText;
	
	// Reduce bad scripts, styles, and margin settings by removing the html up to (and including) the body tag.
	var splitIndex = originalText.indexOf("<body");
	if (splitIndex > 0) {
		splitIndex = originalText.indexOf('>', splitIndex);
		if (splitIndex > 0) {
			splitIndex++;
			Mojo.Log.info("Slicing header starting at", splitIndex);
			originalText = originalText.slice(splitIndex);
		}
	}

	// Truncate to 100K if the message is too long. 
	// cutting it off conservatively).
	var maxBodyLength = 100000;
	if (originalText.length > maxBodyLength) {
		Mojo.Log.warn("WARNING: original email body is too long, size=", originalText.length);
		originalText = originalText.substring(0, maxBodyLength)
	}

	// Finally remove any remaining scripts to avoid any evilness they may bring
	originalText = originalText.stripScripts();
	// Replace original text's CID values for image source with locally saved image
	// file location. 
	originalText = ComposeAssistant.replaceCIDs(originalText, email);

	var editor = controller.get('msg_rte');
	if (email.action === ComposeAssistant.kForwardAction) {
		controller.get('forwarded_msg_body').innerHTML = originalText;
	} else {
		bodyText += originalText;
		editor.setStyle({'min-height':'320px', 'font-size': '18px'});
	}
	editor.innerHTML = bodyText;
	
	if (email.highlightBody) {
		editor.focus();
//		var sel = controller.window.getSelection();
//		sel.selectAllChildren(editor);
	} else if (email.action == "reply" || email.action == "replyAll") {
		var sel = controller.window.getSelection();
		sel.setPosition(editor, 0);
	}
	
	var sigElement = controller.get('signature');
	if (sigElement && email.signature) {
		sigElement.innerHTML = email.signature;
	}	
};

ComposeAssistant.replaceCIDs = function(originalText, email) {
	var resultingText = originalText;
	var replaceCIDFunc = function(inlineAttachment) {
		var uri = inlineAttachment.uri;
		var contentId = inlineAttachment.contentId;
		var pattern = 'cid:' + contentId;
		resultingText = resultingText.replace(new RegExp(pattern, 'gi'), uri);
	};
	
	email.inlineAttachments.each( function(ia) { replaceCIDFunc(ia); } );
	return resultingText;
};

ComposeAssistant.replaceURIs = function(originalText, email) {
	var resultingText = originalText;
	var replaceURIFunc = function(inlineAttachment) {
		var uri = inlineAttachment.uri;
		var cid = 'cid:' + inlineAttachment.contentId;
		resultingText = resultingText.replace(new RegExp(uri, 'gi'), cid);
	};
	
	email.inlineAttachments.each( function(ia) { replaceURIFunc(ia); } );
	return resultingText;
};

ComposeAssistant.kForwardAction = 'forward';
ComposeAssistant.kAppMenuHighPriority = $L('Set as High Priority');
ComposeAssistant.kAppMenuNormalPriority = $L('Set as Normal Priority');
ComposeAssistant.kEmptySignatureRegex = /<span id="signature"><div style="font-family: arial, sans-serif; font-size: 12px;color: #999999;">[<br>]*<\/div>[<br>]*<\/span>/
ComposeAssistant.kForwardDraftDelimeter = "<span id='FORWARD_DRAFT_TEXT' class='display:none'></span>"; 

