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

EditAssistant = Class.create({
	initialize: function(params){
		this.uninitialized = true;
		this.focusWindow = params.focusWindow;
		this.linkedContacts = params.linkedContacts;
		this.contact = params.contact;
		this.personIdToSwapTo = params.personIdToSwapTo;
		this.person = params.person ||	{};
		this.updateDetailCallback = params.updateDetailCallback;
		this.detailSceneCallback = params.detailSceneCallback;
		if (this.contact) {
			this.contactWasPassedIn = true;
		}
		this.contact = params.contact;
		//TODO: Necessary?
		if (this.contact) {
			this.contact = new Contact(this.contact);
		}
		if (params.newContact) {
			this.isNewScene = true;
			this.contact = this.contact || new Contact();
			this.linkedContacts = [this.contact];
			this.contact.accountId = AppAssistant.defaultAccountId;
		}
		
		this.isFirstLinking = true;
		this.originalHandleCommand = MenuHandler.handleCommand;
		this.allowedKeyIdentifierChecker = new AllowedKeyIdentifierChecker(AllowedKeyIdentifierChecker.validPhoneNumberKeys);
	},
	
	parseMoreDetails: function(person){
		var moreDetails = [];
		for (var i = 0; i < this.moreDetailNames.length; i++) {
			var property = this.moreDetailNames[i];
			if (person[property]) {
				moreDetails.push({
					key: property,
					value: person[property]
				});
			}
		}
		return moreDetails;
	},
	moreDetailNames: ["nickname", "spouse", "children", "reminder"],
	
	renderContact: function(response, force){
		this.linkedContacts = response.list;
		if (this.linkedContacts.length < 2) {
			this.controller.sceneElement.addClassName("singleSource");
		}
		
		this.person = Object.extend(this.person, new Contact(response.person));
		
		if(this.person.ringtoneName){
			this.person.ringtoneDisplay = this.person.ringtoneName;
			this.person.ringtoneSet = "ringtone-set";
		} else {
			this.person.ringtoneDisplay = $L("Set a ringtone");
			this.person.ringtoneSet = "";
		}
		this.controller.get("RingtoneBox").update(Mojo.View.render({
			object: this.person,
			template: "edit/ringtones"
		}));
		
		
		var that = this;
		
		var notes = [];
		
		["emailAddresses", "phoneNumbers", "imNames", "urls", "addresses"].each(function(property){
			that.person[property] = $A();
		});
		response.list.each(function(linked){
			//Create a function used to set the contact as dirty.  Attach this to the contact points
			//so when we edit them we can easily tag the contact as dirty         
			var setDirty = function(c, andSave){
				c.dirty = true;
				if (andSave) {
					this.handleSave();
				}
			}.bind(that, linked);
			["emailAddresses", "phoneNumbers", "imNames", "urls", "addresses"].each(function(property){
				linked[property].each(function(contactPoint){
					if (property == "imNames") {
						Object.extend(contactPoint, new IMName(contactPoint.value));
					}
					if (property == "phoneNumbers") {
						Object.extend(contactPoint, new PhoneNumber(contactPoint.value));
					}
					contactPoint.accountDomain = linked.iconClass;
					contactPoint.setDirty = setDirty;
					if (linked.readOnly) {
						contactPoint.readOnly = "read-only-contact";
						contactPoint.disabled = true;
					}
					
					//This is a function used when we delete an item from a contact, or we move it to a different contact.  This allows us
					//To remove it from the contact without having to go back in and find out which contact to remove it from.
					contactPoint.removeFunction = function(contact, item){
						if(contact.accountDomain == "simphonebook"){
							AppAssistant.contactsService.doDelete(contact.id,null, this.controller, function(){
								this.controller.showAlertDialog({
									message:$L("There was an error removing the contact from the sim"),
									choices:[{label:$L("OK"), command:"ok"}]
								});
							}.bind(this));
							Utilities.removeFromArray(this.linkedContacts, contact);
							if(this.linkedContacts.length > 0){
								var contactToRender = this.decideWhichContactToStartWith(this.linkedContacts);
								this.renderSpecificContact(contactToRender);
							}
						}
						item.toDelete = true;
						contact.dirty = true;
					}.bind(that, linked, contactPoint);
				});
				that.person[property] = that.person[property].concat(linked[property]);
			});
			
			for (var i = 0; i < that.moreDetailNames.length; i++) {
				var property = that.moreDetailNames[i];
				var value;
				value = linked[property];
				if (value) {
					that.moreDetailsModel[property].value = value;
				}
			}
			if (linked.birthday) {
				if (linked.birthday != "0") {
					that.moreDetailsModel.birthday.value = linked.birthday;
					that.moreDetailsModel.birthday.birthdayString = Utilities.parseBirthday(linked.birthday);
					if(linked.readOnly){
						that.controller.get("birthdayRow").removeAttribute("x-mojo-tap-highlight");
						that.moreDetailsModel.birthday.birthdayReadOnly = true;
					}
				}
			}
			if (linked.readOnly) {
				if (linked.nickname) {
					that.moreDetailsModel.nickname.disabled = true;
				}
				if (linked.spouse) {
					that.moreDetailsModel.spouse.disabled = true;
				}
				if (linked.children) {
					that.moreDetailsModel.children.disabled = true;
				}
			}
			if (linked.notes) {
				notes.push(linked);
			}
		});
		if (notes.length === 0) {
			notes.push({
				notes: "",
				newNote: true
			});
		}
		this.notesModel.items = notes;
		
		this.moreDetailsModel.reminder.value = this.person.reminder;
		this.controller.modelChanged(this.notesModel);
		this.moreDetailNames.each(function(property){
			that.controller.modelChanged(that.moreDetailsModel[property]);
		});
		this.controller.modelChanged(this.moreDetailsModel.birthday);
		if (this.moreDetailsModel.birthday.birthdayString !== undefined) {
			this.controller.get("BirthdayValue").innerHTML = this.moreDetailsModel.birthday.birthdayString;
			if(this.moreDetailsModel.birthday.birthdayReadOnly){
				this.controller.get("birthdayRow").addClassName("disabled");
			}
		}
		
		this.renderContactPointList("phone", this.person.phoneNumbers);
		this.renderContactPointList("email", this.person.emailAddresses);
		var contactToRender = this.decideWhichContactToStartWith(this.linkedContacts);
		this.renderSpecificContact(contactToRender);
	},
	
	decideWhichContactToStartWith:function(linkedContacts){
		var len = linkedContacts.length;
		var hasName = null;
		var notReadOnly = null;
		for(var i = 0; i < len; i++){
			var c = linkedContacts[i];
			var name = c.getFullName();
			if(!c.readOnly && name && !name.blank()){
				return c;
			} else {
				if (!c.readOnly && !notReadOnly) {
					notReadOnly = c;
				}
				if(name && !name.blank() && !hasName){
					hasName = c;
				}
			} 
		}
		return hasName || notReadOnly || linkedContacts[0];
	},
	
	
	renderSpecificContact: function(contact){
		this.contact = contact;
		if (this.contact.readOnly) {
			this.contact.jobUneditable = true;
			this.contact.disabled = true;
			this.controller.get("nameDescription").addClassName("read-only-contact");
		}
		else {
			this.controller.get("nameDescription").removeClassName("read-only-contact");
		}
		if(this.contact.accountDomain == "simphonebook"){
			this.contact.jobUneditable = true;
		}
		this.nameSyncPicker.setStyle({"background":"url(" + this.contact.iconPath + ") center center no-repeat"});
		this.contact.freeformName = this.contact.getFullName();
		this.controller.setWidgetModel('edit_name', this.contact);
		
		this.controller.setWidgetModel('company-field', this.contact);
		this.controller.setWidgetModel('title-field', this.contact);
		this.rerenderHeader();
	},
	
	rerenderHeader: function(){
		var params = {
			pic: this.contact.pic || Contact.DEFAULT_DETAILS_AVATAR,
			disabled: this.contact.disabled
		};
		this.controller.get('edit-photo').update(Mojo.View.render({
			template: 'edit/photo',
			object: params
		}));
	},
	
	contactPointTypes: ["emailAddresses", "phoneNumbers", "imNames", "urls", "addresses"],
	renderContactPoints: function(){
		this.renderContactPointList("phone", this.person.phoneNumbers);
		this.renderContactPointList("email", this.person.emailAddresses);
		this.renderContactPointList("messaging", this.person.imNames);
		this.renderContactPointList("address", this.person.addresses);
		this.renderContactPointList("url", this.person.urls);
	},
	
	renderContactPointList: function(type, data){
		var listDiv = this.controller.get(type + "List");
		if (data.length > 0) {
			this.setupListLazily(listDiv);
		}
		var formattedData = null;
		formattedData = data.collect(ContactPointDecorator[type + "Formatter"]);
		var model = this[type + "Model"];
		model.items = formattedData;
		this.controller.modelChanged(model);
	},
	
	setupContact: function(){
		if (this.isNewScene) {
			this.controller.sceneElement.addClassName('newSource');
			
			this.renderSpecificContact(this.contact);
			//TODO Why is the notes stuff here
			var notes = [];
			notes.push({
				notes: "",
				newNote: true
			});
			this.notesModel.items = notes;
			this.controller.modelChanged(this.notesModel);
			if (this.contactWasPassedIn) {
				this.renderContact({
					person: {},
					list: [this.contact]
				});
			}
			else {
				var ringtoneName = this.person.ringtoneName || $L("Set a ringtone");
				this.controller.get("RingtoneBox").update(Mojo.View.render({
					object: {ringtoneDisplay:ringtoneName},
					template: "edit/ringtones"
				}));
				
			}
		}
		else {
			this.renderContact({
				person: this.person,
				list: this.linkedContacts
			});
		}
	},
	
	populateAccounts: function(resp){
		if (resp.list.length === 0) {
			return;
		}
		this.accounts = resp.list;
		var that = this;
		this.accounts.each(function(acct){
			if (that.isNewScene && (acct.accountId == that.contact.accountId)) {
				that.contact.accountDomain = acct.domain;
				that.contact.accountDisplayName = acct.accountDisplayName;
				that.nameSyncPicker.setStyle({"background":"url(" + that.contact.iconPath + ") center center no-repeat"});
			}
			if (acct.accountDisplayName == "Google") {
				acct.domain = "google";
			}
			
			acct.label = acct.accountDisplayName;
			
			if(acct.login){
				acct.label += " ("+acct.login+")";
			}
			
			acct.iconPath = Utilities.iconJSONToIconPath(acct.accountSpecificIcons);
			acct.command = "a," + acct.accountId + "," + acct.domain;
		});
		this.showPicker();
		if(this.aboutToActivateCallback){
			this.aboutToActivateCallback();
			delete this.aboutToActivateCallback;
		}
	},
	
	showPicker: function(){
		this.syncPicker = this.syncPicker || this.controller.get('sync-picker');
//		this.syncPicker.update(Mojo.View.render({
//			template: 'edit/new-contact-picker',
//			object: this.contact
//		}));
		this.syncPicker.observe(Mojo.Event.tap, this.popupContactChooser.bind(this));
		this.changeNewContactAccount("c," + this.contact.accountId);
	},
	
	handleDelete: function(){
		var deleteName = this.person.fullName ||
		$L({
			value: "Person",
			key: "default_value_for_deletion_when_no_name"
		});
		var choices;
		
		var deleteContactChoice = {
			label: $L('Delete All Profiles'),
			value: 'DELETE_ALL',
			type: 'negative'
		};
		var deleteOtherContactsChoice = {
			label: $L('Delete other profiles'),
			value: 'DELETE_OTHERS',
			type: 'color'
		};
		var cancelChoice = {
			label: $L('Cancel'),
			value: 'CANCEL'
		};
		choices = [];
		var hasReadOnlyContact = false;
		var hasWritableContact = false;
		var readOnlyType = "";
		this.linkedContacts.each(function(contact){
			if (contact.readOnly) {
				readOnlyType = contact.accountName;
				hasReadOnlyContact = true;
			} else {
				hasWritableContact = true;
			}
		});
		
		var messageTemplate;
		if (hasReadOnlyContact && hasWritableContact) {
			messageTemplate = new Template($L('#{type} profiles cannot be deleted.  Delete all other profiles?'));
			choices.push(deleteOtherContactsChoice);
			choices.push(cancelChoice);
		} else if (hasReadOnlyContact && !hasWritableContact) {
			messageTemplate = new Template($L('#{type} profiles can not be deleted'));
			choices.push(cancelChoice);
		} else {
			messageTemplate = new Template($L('Delete #{deleteName}?'));
			choices.push(deleteContactChoice);
			choices.push(cancelChoice);
		}
		var message = messageTemplate.evaluate({
			deleteName: deleteName,
			type:readOnlyType
		});
		this.controller.showAlertDialog({
			onChoose: function(value){
				if (value == 'DELETE_ALL' || value == 'DELETE_OTHERS') {
					var tempArray = [];
					this.linkedContacts.each(function(c){
						if (!c.readOnly) {
							AppAssistant.contactsService.doAsyncDelete(c.id);
						} else {
							tempArray.push(c);
						}
					});
					this.linkedContacts = tempArray;
					if (AppAssistant.listResetCallback) {
						AppAssistant.listResetCallback();
					}
					if (value == 'DELETE_ALL') {
						if(this.person.launcherId){
							new Mojo.Service.Request("palm://com.palm.applicationManager/removeLaunchPoint", {
								parameters: {
									launchPointId:this.person.launcherId,
									id:"com.palm.app.contacts"
								}
							});
						}
						this.deleting = true;
						this.controller.stageController.popScenesTo('list');
					} else {
						this.setupContact();
					}
				}
			}.bind(this),
			message: message,
			choices: choices
		});
	},
	
	
	setup: function(){
		//If we're launching straight to the edit scene, our contact objects probably aren't real contact model objects
		if(this.personIdToSwapTo){
			this.person = Object.extend(this.person, new Contact(this.person));
			this.linkedContacts.each(function(c){
				c = Object.extend(c, new Contact(c));
			});
		}
		this.editRules = EditRules[0];
		this.menuItems = [];
		this.setupAppMenu();
		this.nameSyncPicker = this.controller.get('NameSyncPicker');
		this.populateAccountsBound = this.populateAccounts.bind(this);
		if (this.isNewScene) {
			AppAssistant.accountsService.getAllAccountsForService(this.controller, AccountsService.CONTACTS, true, this.populateAccountsBound);
		}
		this.doneButtonObject = {
			label: $L('Done'),
			command: this.finished.bind(this)
		};
		this.commandMenuModel = [this.doneButtonObject];
		
		//If you've passed in a contact with no id change the edit button to a save button
		this.cmdMenuModel = {
			visible: true,
			items: this.commandMenuModel
		};
		
		this.controller.setupWidget(Mojo.Menu.commandMenu, undefined, this.cmdMenuModel);
		
		this.spinnerModel = { spinning : false };
		this.controller.setupWidget('scan-spinner',
				{ spinnerSize : 'large' }, this.spinnerModel);

		
		this.controller.get("ListsHolder").update(Mojo.View.render({
			template: 'edit/lists-fake',
			object: {}
		}));
		this.observeFakeLists();
		this.setupLists();
		
		
		
		var advancer = function(e){
			if (e.keyCode == 13 && e.target.value.blank()) {
				e.stop();
				if (e.type == "keyup") {
					Mojo.View.advanceFocus(this.controller.sceneElement);
				}
			}
		}.bind(this);
		this.controller.listen('addressList', 'keydown', advancer);
		this.controller.listen('NotesList', 'keydown', advancer);
		this.controller.listen('birthdayRow', 'keydown', advancer);
		this.controller.listen('spouseRow', 'keydown', advancer);
		this.controller.listen('childrenRow', 'keydown', advancer);
		this.controller.listen('nicknameRow', 'keydown', advancer);
		this.controller.listen('addressList', 'keyup', advancer);
		this.controller.listen('NotesList', 'keyup', advancer);
		this.moreDetailsModel = {};
		var that = this;
		this.moreDetailNames.each(function(property){
			that.moreDetailsModel[property] = {};
			that.controller.setupWidget(property + "Field", {
				enterSubmits: true,
				modelProperty: "value",
				multiline: true,
				//for text case, reminder should be sentence-style, everything else should be title-style
				textCase: ("reminder" === property) ? Mojo.Widget.steModeSentenceCase : Mojo.Widget.steModeTitleCase,
				//only autoreplace on the reminder field, of all these fields
				autoReplace: "reminder" === property
			}, that.moreDetailsModel[property]);
		});
		this.moreDetailsModel.birthday = {};
		this.controller.listen("reminderField", Mojo.Event.propertyChange, this.reminderChanged.bind(this));
		this.controller.listen("birthdayRow", Mojo.Event.propertyChange, this.birthdayChanged.bind(this));
		this.controller.listen("spouseRow", Mojo.Event.propertyChange, this.spouseChanged.bind(this));
		this.controller.listen("childrenRow", Mojo.Event.propertyChange, this.childrenChanged.bind(this));
		this.controller.listen("nicknameRow", Mojo.Event.propertyChange, this.nicknameChanged.bind(this));
		this.notesAttrs = {
			listTemplate: 'common/group-container-unlabeled',
			itemTemplate: 'edit/notes-edit'
		};
		
		this.controller.listen("birthdayRow", Mojo.Event.tap, this.chooseBirthday.bind(this));
		
		this.notesModel = {
			items: []
		};
		this.controller.setupWidget("NotesList", this.notesAttrs, this.notesModel);
		this.controller.setupWidget("notesTextField", {
			multiline: true,
			modelProperty: 'notes',
			textCase: Mojo.Widget.steModeSentenceCase
		});
		this.controller.listen("NotesList", Mojo.Event.propertyChange, this.notesChanged.bind(this));
		
		var nameChangeHandler = this.handleNameFieldChange.bind(this);
		
		this.nameModel = {
			modelProperty: "freeformName",
			hintText: $L("Name..."),
			multiline: true,
			enterSubmits: true,
			textCase: Mojo.Widget.steModeTitleCase,
			autoReplace: false
		};
		this.controller.setupWidget('edit_name', this.nameModel, this.contact);
		this.controller.listen('edit_name', 'keydown', this.observeNameKeyUp.bind(this));
		this.controller.listen('edit_name', Mojo.Event.propertyChange, this.nameChanged.bind(this));
		
		this.controller.setupWidget('title-field', {
			hintText: $L("Job Title..."),
			modelProperty: 'jobTitle',
			multiline: true,
			enterSubmits: true,
			disabledProperty:'jobUneditable',
			textCase: Mojo.Widget.steModeTitleCase,
			autoReplace: false
		}, {});
		this.controller.setupWidget('company-field', {
			hintText: $L("Company..."),
			modelProperty: 'companyName',
			multiline: true,
			enterSubmits: true,
			disabledProperty:'jobUneditable',
			textCase: Mojo.Widget.steModeTitleCase,
			autoReplace: false
		}, {});
		this.controller.listen('title-field', Mojo.Event.propertyChange, function(e){
			e.model.dirty = true;
		});
		this.controller.listen('company-field', Mojo.Event.propertyChange, function(e){
			e.model.dirty = true;
		});
		
		this.controller.listen("NameSyncPickerWrapper", Mojo.Event.tap, this.popupContactChooser.bind(this));
		this.controller.get("edit-photo").observe(Mojo.Event.tap, this.attachFilePicker.bind(this));
		this.controller.get("RingtoneBox").observe(Mojo.Event.tap, this.attachRingtonePicker.bind(this));
		this.renderLabels();
		this.setupContact();
		if (this.contact.readOnly) {
			this.controller.setInitialFocusedElement(null);
		}
		
	},
	
	observeNameKeyUp:function(e){
		if(this.contact.accountDomain == "simphonebook" && e.target.value.length >= 18 && e.keyCode != 8){
			e.stop();
		}
	},
	
	observeFakeLists: function(){
		this.listInitializer = this.setupListAndAdd.bind(this);
		
		this.controller.listen("phoneList", Mojo.Event.tap, this.listInitializer);
		this.controller.listen("messagingList", Mojo.Event.tap, this.listInitializer);
		this.controller.listen("emailList", Mojo.Event.tap, this.listInitializer);
		this.controller.listen("addressList", Mojo.Event.tap, this.listInitializer);
		this.controller.listen("urlList", Mojo.Event.tap, this.listInitializer);
	},
	
	setupListAndAdd: function(e){
		var div = e.target;
		if (!div.hasClassName("contactPointList")) {
			div = div.up(".contactPointList");
		}
		this.setupListLazily(div);
		Mojo.Event.send.bind(Mojo.Event, div, Mojo.Event.listAdd, {}).defer();
	},
	
	setupListLazily: function(div){
		this.controller.stopListening(div, Mojo.Event.tap, this.listInitializer);
		if (div.getAttribute("x-mojo-element") == "List") {
			return;
		}
		div.innerHTML = "";
		div.setAttribute("x-mojo-element", "List");
		this.controller.instantiateChildWidgets(div.parentNode);
		this.setupForwarderOnList(div);
		
	},
	listTypes: [{
		name: "phone",
		addItemLabel: $L("New Phone Number..."),
		modifierState: Mojo.Widget.numLock,
		modelProperty: "displayValue",
		multiline:true,
		enterSubmits:true,
		textCase: Mojo.Widget.steModeLowerCase
	}, {
		name: "messaging",
		addItemLabel: $L("New IM Address..."),
		modelProperty: "value",
		labelSelectorProperty: "serviceName",
		enterSubmits:true,
		textCase: Mojo.Widget.steModeLowerCase
	}, {
		name: "email",
		addItemLabel: $L("New Email Address..."),
		modelProperty: "displayValue",
		enterSubmits:true,
		textCase: Mojo.Widget.steModeLowerCase
	}, {
		name: "url",
		addItemLabel: $L("New URL..."),
		modelProperty: "url",
		enterSubmits:true,
		textCase: Mojo.Widget.steModeLowerCase
	}, {
		name: "address",
		addItemLabel: $L("New Address..."),
		modelProperty: "freeformAddress",
		multiline: true,
		textCase: Mojo.Widget.steModeTitleCase
	}],
	setupLists: function(){
		var type = undefined;
		for (var i = 0; i < this.listTypes.length; i++) {
			type = this.listTypes[i];
			
			//Set up the list widgets
			var listAttributes = {
				itemTemplate: "edit/" + type.name + "-edit",
				listTemplate: "common/group-container-unlabeled",
				addItemLabel: type.addItemLabel,
				swipeToDelete: true,
				preventDeleteProperty:"readOnly"
			};
			
			//this.phoneModel is the model for the phone list
			this[type.name + "Model"] = {
				items: [],
				choices:this.editRules[type.name + "PopupLabels"]
			};
			
			this.controller.setupWidget(type.name + "List", listAttributes, this[type.name + "Model"]);
			
			//Set up the text fields and labels in each list
			var textFieldAttributes = {
				multiline: type.multiline,
				modelProperty: type.modelProperty,
				autoReplace: false,
				textCase: type.textCase,
				modifierState: type.modifierState,
				limitResize: true,
				enterSubmits:type.enterSubmits
			};
			this.controller.setupWidget(type.name + "TextField", textFieldAttributes);
			
			this[type.name + "Labels"] = {
				modelProperty: "label",
				choices: []
			};
			var labels = {
				modelProperty: type.labelSelectorProperty || "label",
				choices: []
			};
			this[type.name + "Labels"] = labels;
			this.controller.setupWidget("label_selector_" + type.name, labels, labels);
		}
		
		var addressCB = function(address){
			address.working = false;
			
			this.controller.modelChanged(address);
			var listDiv = this.controller.get("addressList");
			//Have to defer the focus so that it happens when the textfield is visible
			var textfield = listDiv.mojo.focusItem.bind(listDiv.mojo, address).defer();
			if (!address.getOneLine().blank()) {
				listDiv.mojo.showAddItem(true);
			}
		}.bind(this);
		
		this.controller.listen('addressList', Mojo.Event.listTap, function(e, cb){
			if (e.originalEvent.target.hasClassName('drawer-opener')) {
				e.item.working = true;
				e.item.parseAddress(e.item.freeformAddress);
				this.controller.stageController.pushScene('address-edit', e.item, e.model, cb);
			}
		}.bindAsEventListener(this, addressCB));
		this.observeListForDetails();
	},
	
	setupAppMenu: function(){
		this.menuItems = [];
		//this.menuItems.push({label:$L("Set speed dial"), enabled:true, command: MenuHandler.setSpeedDial});
		this.menuItems.push({
			label: $L("Name Details"),
			enabled: true,
			command: "namedetails"
		});
		if (!this.isNewScene) {
			this.menuItems.push({
				label: $L("Delete"),
				command: MenuHandler.deleteContact
			});
		}
		MenuHandler.createAppMenu(this, this.menuItems);
		this.handleCommand = this._handleCommand;
	},
	
	ready: function(){
		//TODO need to find a better way to do this
		this.setUpAddButtonForwarders();
	},
	
	setUpAddButtonForwarders: function(){
		this.listDivs = this.listDivs || this.controller.select(".contactPointList");
		this.listDivs.each(this.setupForwarderOnList.bind(this));
	},
	
	setupForwarderOnList: function(div){
		var addItem = div.select('.add-item')[0];
		var hidden = new Element('input', {
			'class': 'inputShillForAdd',
			type: 'text'
		});
		hidden.setStyle({
			'margin-top': '-50px',
			'margin-right': '-50px',
			border: 0
		});
		if (!addItem) {
			return;
		}
		addItem.insert({
			top: hidden
		});
		hidden.observe('focus', function(){
			if (div.getAttribute("x-mojo-element") == "List") {
				Mojo.Event.send(div, Mojo.Event.listAdd, {});
			}
			else {
				this.setupListAndAdd({
					target: div
				});
			}
		}.bind(this));
	},
	
	
	
	observeListForDetails: function(){
		this.lists = this.lists || ['phoneList', 'messagingList', 'emailList', 'addressList', 'urlList'];
		for (var i = 0; i < this.lists.length; i++) {
			this.controller.listen(this.lists[i], Mojo.Event.listChange, this.handleListChange);
		}
		this.observeSmallList('phoneList', this.phoneModel, this.phoneLabels, this.makeNewPhone.bind(this));
		this.observeSmallList('messagingList', this.messagingModel, this.messagingLabels, this.makeNewIm.bind(this));
		this.observeSmallList('emailList', this.emailModel, this.emailLabels, this.makeNewEmail.bind(this));
		this.observeSmallList('addressList', this.addressModel, this.addressLabels, this.makeNewAddress.bind(this));
		this.observeSmallList('urlList', this.urlModel, this.urlLabels, this.makeNewURL.bind(this));
		var phoneList = this.controller.get("phoneList");
		this.controller.listen("phoneList", "keydown", this.handlePhoneKeyDownOrUp.bindAsEventListener(this, phoneList));
		this.controller.listen("phoneList", "keyup", this.handlePhoneKeyDownOrUp.bindAsEventListener(this, phoneList));
		this.controller.listen("phoneList", "cut", this.handlePhoneNumberCut.bindAsEventListener(this, phoneList));
		//this.controller.listen("phoneList", 'paste', this.deferredPhoneNumberHandlePaste.bindAsEventListener(this));
	},
	
	// This is a really weird hack where for some reason
	// when trying to perform a cut from the menu on a 
	// phoneList textfield, the textfield is not passed
	// any events associated with editing. When performing 
	// the cut from the menu the text is cut, however the 
	// model does not get updated, nor do any of the usual event
	// listeners get called. Because of this, when moving
	// focus to a new textfield or leaving edit mode, the 
	// cut is essentially undone and the text before the cut is 
	// restored.
	//
	// This bug occurs only when the cut is made from the menu. A
	// cut using the keyboard does not inherit this bug. 
	// By updating the model, blurring the field, and  if applicable
	// refocusing the field, the necessary steps to update the
	// model are acheived. (NOV-73962) 
	deferredPhoneNumberHandleCut: function(e, list) {
		var item = list.mojo.getItemByNode(e.target);
		item.value = e.target.value;
		item.setDirty();

		e.target.blur();
		if (item.value != "") {
			e.target.focus();
		}
	},
	
	handlePhoneNumberCut: function(event, list) {
		this.deferredPhoneNumberHandleCut.bind(this).defer(event, list);
	},
	//////////////////////////////////////////////////////////////////////////////////////////////////////////////
	
	handlePhoneNumberPaste : function(textField) {
		//textField.mojo.setValue(this.allowedKeyIdentifierChecker.removeInvalidCharsFromString(textField.value));
		textField.mojo.setText(this.allowedKeyIdentifierChecker.removeInvalidCharsFromString(textField.value));
	},
					
	deferredPhoneNumberHandlePaste : function(event) {
		// run this when the call stack becomes empty
		this.handlePhoneNumberPaste.bind(this).defer(event.target);
	},
	
	handlePhoneKeyDownOrUp:function(e, list){
		
		if (!this.allowedKeyIdentifierChecker.checkIfValidKeyIdentifier(e.keyIdentifier)){
			e.stop();
		}
	
		var item = list.mojo.getItemByNode(e.target);
		if(item.accountDomain == "sim" && e.target.value.length >= 40 && e.keyCode != 8){
			e.stop();
		}
		
	},
	
	stopEditing: function(){
		Mojo.Event.send(this.controller.sceneElement, Mojo.Event.commitChanges);
		//TODO PERF Can we drop this?
		this.controller.select("*:focus").each(function(el){
			el.blur();
		});
	},
	
	aboutToActivate:function(callback){
		if(this.isNewScene){
			this.aboutToActivateCallback = callback;
		} else {
			callback();
		}
	},
	
	activate: function(){
		if(this.aboutToActivateCallback){
			delete this.aboutToActivateCallback;
		}
		if(this.focusWindow){
			this.focusWindow = false;
			this.controller.stageController.activate();
		}
		if (this.uninitialized) {
			if (this.person.imNames) {
				this.renderContactPointList("messaging", this.person.imNames);
			}
			if(this.person.addresses){
				this.renderContactPointList("address", this.person.addresses);
			}
			if (this.person.urls) {
				this.renderContactPointList("url", this.person.urls);
			}
			this.loadDeferredWidgets();
			this.uninitialized = false;
		} 
	},
	
	loadDeferredWidgets: function(){
		this.controller.select("div[x-mojo-element-deferred]").each(function(div){
			div.setAttribute("x-mojo-element", div.getAttribute("x-mojo-element-deferred"));
		});
		this.controller.instantiateChildWidgets(this.controller.sceneElement);
	},
	
	cleanup: function(){
		if (!this.deleting) {
			this.handleSave();
		}
	},
	
	handleListChange: function(e){
		e.item.value = e.originalEvent.target.value;
		e.item.setDirty();
	},
	
	saveContact: function(c, afterSaveCallback){
		//Mojo.Log.info("ContactMulti Saving single contact %j", c);
		var that = this;
		if (c.dirty) {
				
			var saveCallback = this.afterSave.bind(this, afterSaveCallback, true);
			c.dirty = false;
			AppAssistant.contactsService.save(c, saveCallback, that.controller);
		}
	},
	
	saveFailed:function(c, resp){
		c.dirty = true;
		
	
			
	
		
		if (!this.showedFailureAlert) {
			var message;
			if(this.failedOnce){
				message = $L("This contact could not be saved.");
			} else {
				message = $L("There was an error saving this contact.  Please try again.");
			}
			
			this.controller.showAlertDialog({
				message:message,
				title:$L("Error"),
				onChoose:function(){
					if(this.failedOnce){
						this.controller.stageController.popScene();
					}
					this.failedOnce = true;
				}.bind(this),
				choices: [{label: $L("OK")}]
			});
			this.showedFailureAlert = true;
			this.hideSavingScrim();
		}
	},
	
	//This aftersaveCallback is used by the new scene, to push on the newly created detail
	//scene when the save is done
	handleSave: function(afterSaveCallback){			
		this.showSavingScrim();
		this.stopEditing();
		
		var didSaveAContact = false; //Need to know whether to call the savePersonDetails directly, instead
		//of after a callback to a contacts save
		this.numberOfSaves = 0; //Use a counter to tell when the last save call is calling back
		this.saveCalls = [];
		var that = this;
		this.showedFailureAlert = false;
		this.linkedContacts.each(function(c){
			if (c.dirty) {
				c.beingSaved = true;
				that.numberOfSaves++;
				didSaveAContact = true;
				c.dirty = false;
				var saveCallback = that.afterSave.bind(that, c, afterSaveCallback);
				var failure = that.saveFailed.bind(that, c);
				that.saveCalls.push(AppAssistant.contactsService.save(c, saveCallback, failure));
			}
		});
		
		if (didSaveAContact && AppAssistant.listResetCallback) {
			AppAssistant.listResetCallback();
		}
		
		//This is a problem when there is a new contact, and all you do is set a ringtone or set a picture.  
		//It won't save, but I think that's ok for now.
		if (!didSaveAContact) {
			this.savePersonDetails();
		}
		//Call this back so the detail scene gets instant updates instead of having to wait for a db notif
		//TODO Should we only call this if we saved something?
		if (this.detailSceneCallback) {
			this.detailSceneCallback({
				person: this.person,
				list: this.linkedContacts
			});
		}
	},
	
	savePersonDetails: function(){
		if (!this.person.id || !this.person.dirty) {
			return;
		}
		//TODO Make sure empty ringtones don't overwrite if you don't set a ringtone.
		AppAssistant.contactsService.setPersonDetails(this.controller, this.person.id, this.person);
	},
	
	saveContactPicture: function(contact){
		AppAssistant.contactsService.saveContactPicture(contact.id, contact.pic, contact.cropInfo);
	},
	
	
	afterSave: function(contact, afterSaveCallback, resp){
		contact.beingSaved = false;
		this.person.id = resp.personId;
		contact.id = resp.contactId;
		if (contact.pictureDirty) {
			this.saveContactPicture(contact);
		}
		this.numberOfSaves--;
		if (this.numberOfSaves > 0) {
			return;
		}
		this.failedAlready = false; 
		if (typeof afterSaveCallback == "function") {
			afterSaveCallback(resp);
		}
		this.savePersonDetails();
		if (this.updateDetailCallback) {
			this.updateDetailCallback(resp.personId);
		}
		
	},
	
	
	chooseBirthday: function(){
		if (this.moreDetailsModel.birthday.birthdayReadOnly) {
			return;
		}
		var contact = this.linkedContacts[0];
		var firstName = contact.firstName || "";
		var lastName = contact.lastName || "";
		
		this.controller.showDialog({
			template: 'edit/change-birthday',
			pic: this.person.pic,
			firstName: firstName,
			lastName: lastName,
			isClipped: this.person.isClipped,
			assistant: new BirthdayPickerAssistant(this.controller, this.birthdayChangedCallback.bind(this), this.moreDetailsModel.birthday)
		});
		
	},
	
	birthdayChangedCallback: function(birthday){
		if (!birthday) {
			this.moreDetailsModel.birthday.value = null;
			this.controller.get("BirthdayValue").innerHTML = "";
			this.saveMoreDetails("birthday", this.moreDetailsModel.birthday.value);
			return;
		}
		var dateString = Utilities.dateToLocalDate(birthday);
		
		this.moreDetailsModel.birthday.value = dateString;
		this.controller.get("BirthdayValue").innerHTML = Utilities.parseBirthday(dateString);
		this.saveMoreDetails("birthday", this.moreDetailsModel.birthday.value);
	},
	
	showSavingScrim:function(){
		this.controller.get("saving-div").show();
		this.spinnerModel.spinning = true;
		this.controller.modelChanged(this.spinnerModel);
	},
	
	hideSavingScrim:function(){
		this.controller.get("saving-div").hide();
		this.spinnerModel.spinning = false;
		this.controller.modelChanged(this.spinnerModel);
	},
	
	doSaveOnSim:function(number){
		if(!AppAssistant.simPhoneLength){
			this.simLengthRequest = new Mojo.Service.Request("palm://com.palm.telephony", {
				method:"simbookEntryMaxLengthQuery",
				parameters:{
					type:"adn"
				},
				onSuccess:function(resp){
					this.simLengthRequest = null;
					if(resp.extended){
						AppAssistant.simPhoneLength = resp.extended.number;
						return this.finalizeSaveToSim(number);
					} else {
						return this.saveSimFailure(resp);
					}
					
				}.bind(this),
				onFailure:function(resp){
					return this.saveSimFailure(resp);
				}.bind(this)
			});
		} else {
			return this.finalizeSaveToSim(number);
		}
	},
	
	finalizeSaveToSim:function(number){
		if(number.value.length > AppAssistant.simPhoneLength){
			return false;
		}
		return true;
	},
	
	saveSimFailure:function(resp){
		var message = $L("There was an error saving to the sim");
		if(resp.errorCode == "tel:109"){
			message = $L("The SIM card is full.");
		}
		Mojo.Controller.appController.showBanner(message, null, "contacts-banner");
		return false;
	},
	
	
	finished: function(){
		var primary = this.linkedContacts[0];
		if(primary.getFullName() || !primary.getFullName().blank()){
			this.person.fullName = primary.getFullName();
			this.person.workInfoLine = Contact.getWorkInfoLine(primary);
		} else {
			this.person.fullName = Contact.getWorkInfoLine(primary);
			delete this.person.workInfoLine;
			
		}
		
		var that = this;
		
		var simError = false;
		this.linkedContacts.each(function(c){
				simError = false;
				if (c.accountName == "SIM") {
					for (var i = 0; i < c.phoneNumbers.length; i++) {
						simError = that.doSaveOnSim(c.phoneNumbers[i]);
						simError = !simError;
						if (simError) {
							throw $break;
						}
					}
				}				
		});
		
		if (simError) {
			this.controller.showAlertDialog({
				title:$L("Unable to save"),
				message:$L("A number is too long to save to the SIM card. Try again with a different number."),
				choices:[{label:$L("OK")}]
			});
			return;
		}
		
		this.stopEditing();
		if (this.isNewScene) {
			if (!this.contact.dirty) {
				this.controller.stageController.popScene();
				return;
			}
			
			
			//Handle the new scenes save, maybe go back to detail scene, if no reason to save just go back
			var saveCallback = function(resp){
				this.controller.stageController.popScene();
				if (resp.personId) {
					this.controller.stageController.pushScene("detail", {
						personId: resp.personId
					});
				}
			}.bind(this);
			this.handleSave(saveCallback);
			return;
			
		}
		if(this.personIdToSwapTo){
			this.controller.stageController.swapScene("detail", {personId: this.personIdToSwapTo});
		} else {
			this.controller.stageController.popScene({update:true});
		}
		
	},
	
	_handleCommand: function(event){
		if (event.type == Mojo.Event.back) {
			this.finished();
			event.stop();
			return;
		}
		if (event.type == Mojo.Event.command) {
			if (event.command == "namedetails") {
				this.controller.stageController.pushScene("namedetails", this.controller, {
					contact: this.contact
				});
				return;
			}
		}
		this.originalHandleCommand(event);
	}
	
});

AllowedKeyIdentifierChecker = Class.create({
	initialize: function(arrayOfAllowedKeyIdentifiers){
		this.allowedKeyIdentifiers = arrayOfAllowedKeyIdentifiers || [];
	},
	
	
	
	checkIfValidKeyIdentifier: function(keyIdentifier) {
		for (var index = 0, len = this.allowedKeyIdentifiers.length; index < len; ++index) {
  			var allowedKeyIdentifier = this.allowedKeyIdentifiers[index];
				
			if (allowedKeyIdentifier.length == 1 && keyIdentifier.charAt(0) == "U") {
				allowedKeyIdentifier = this.getCharsKeyIdentifierValue(allowedKeyIdentifier);
			}
				
			if (allowedKeyIdentifier == keyIdentifier){
				return true;
			}
				
		}
		return false;
	},
	
	getCharsKeyIdentifierValue : function(charValue){
		var toReturn;
		var charCode = charValue.charCodeAt(0);
		var prefix = "U+";
					
		if (charCode <= 15){
			prefix += "000";
		}
		else if(charCode <= 255){
			prefix += "00";
		}
		else if(charCode <= 4095){
			prefix += "0";
		}
					
		toReturn = prefix + charCode.toString(16);
		
		return toReturn;
	},
	
	removeInvalidCharsFromString :function(stringToRemoveChars) {
		var toReturn = "";
		for (var i = 0, length = stringToRemoveChars.length; i < length; i++){
			var charToCheck = stringToRemoveChars.charAt(i);
			
			// Not the most performant way to do this but lets see if it becomes an issue
			// the workaround takes up more memory. 
			// XXX: bug?  len is reused here from above...
			for (var index = 0, len = this.allowedKeyIdentifiers.length; index < len; ++index) {
				var allowedKeyIdentifier = this.allowedKeyIdentifiers[index];
				if (allowedKeyIdentifier.length == 1) {
					if (allowedKeyIdentifier == charToCheck){
						toReturn += charToCheck;
					}
				}
			}
			
		}
		
		return toReturn;
	}
	
});

// Valid keys for when entering a phone number
AllowedKeyIdentifierChecker.validPhoneNumberKeys = [
		"U+0008",			// backspace
		"0",			// 0
		"1",			// 1
		"2",			// 2
		"3",			// 3
		"4",			// 4
		"5",			// 5
		"6",			// 6
		"7",			// 7
		"8",			// 8
		"9",			// 9
		"U+002B",			// + 
		"-",			// -
		"p",			// p
		"P",			// P
		"w",			// w
		"W",			// W
		"t",			// t
		"T",			// T
		" ",			// space
		".",			// .
		"#",			// #
		"U+002A", 			// *
		"Shift",			// Shift
		"Alt",				// Alt
		"Left",				// Shift + left swipe
		"Right",			// Shift + right swipe
		"Meta",				// Touch gesture area
		"Back"				// Back swipe
];