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

var AppAssistant = Class.create({
	
	_name: 'findapps',
	
	initialize: function()
	{
		Mojo.Log.info("AppAssistant::initialize");
	    Weave.Services.ConnectionManager.monitor();
	},
	
	setup: function()
	{
		Mojo.Log.info("AppAssistant::setup");
		
		var self = this;		
		Weave.System.Activator.addInterface('main',
		{
			_defaultStage: "default",
			
			init: function(target)
			{
				var me = this;
				Weave.System.Activator.open(this._defaultStage, "DefaultStageAssistant", function(stage)
				{
					me.start(stage, me._defaultStage);
				});
			},
			
			start: function(stage, stageName)
			{
				Mojo.Log.info("AppAssistant(main) start");
				if (!TermsOfUse.isAccepted())
				{
					stage.controller.pushScene('terms', function(accepted)
					{
						if (accepted) 
						{
							stage._gotoMain();
						}
						else 
						{
							Weave.System.Activator.close(stageName);
						}
					});
				}
				else
				{
					stage._gotoMain();
				}
				//stage.controller.activate();
				
				// is this needed?
				// Delay starting up to try to let the card come up a bit faster
				/*(function()
				{
					stage._gotoMain();
					stage.controller.activate();
				}).delay(1);
				*/
			},
		});
		
		Weave.System.Activator.addInterface('myapps',
		{
			_defaultStage: "default",
			
			init: function(target)
			{
				var me = this;
				Weave.System.Activator.open(this._defaultStage, "DefaultStageAssistant", function(stage)
				{
					me.start(stage, me._defaultStage);
				});
			},
			
			start: function(stage, stageName)
			{
				Mojo.Log.info("AppAssistant(myapps) start");
				
				var top = stage.controller.topScene();
				if (!top || top.sceneName != "myapps") 
				{
					stage.controller.pushScene('myapps');	
				}
				//stage.controller.activate();
			},	
		});
		
		Weave.System.Activator.addInterface('target',
		{
			_defaultStage: "default",
			_webDistributedStage: "webDistributed",
			
			// perform any initialization determine which stage to open
			init: function(target)
			{
				Mojo.Log.info("AppAssistant(target).init target %s", target);
				var ids = self._findAppAndPackageId(target);
				
				// call server to find out the type of this application
				var appDetails = new AppDetails(ids.applicationId, ids.packageId);
				appDetails.attach(this);
				appDetails.getDetailsFromServer();
			},
			
			updateDetails: function(app)
			{
				var stageName = this._defaultStage;
				var stageAssistantName = "DefaultStageAssistant";
				
				var programType = app.getProgramType();
				Mojo.Log.info("AppAssistant(target).updateDetails programType %s", programType);
				
				// figure out which stage to push
				if (programType && (programType == "W" || programType == "B"))
				{
					stageName = this._webDistributedStage + (new Date()).getTime();
					stageAssistantName = "WebDistributedStageAssistant"
				}
				
				var me = this;				
				Weave.System.Activator.open(stageName, stageAssistantName, function(stage)
				{
					me.target(app, stage, stageName);
				})
			},
			
			target: function(app, stage, stageName)
			{
				Mojo.Log.info("AppAssistant(target).target id:%j", app._packageid);
				app.detach(this);
				
				if (!TermsOfUse.isAccepted())
				{
					stage.controller.pushScene('terms', function(accepted)
					{
						if (accepted) 
						{
							stage.controller.swapScene('details', null, null, app);
						}
						else 
						{
							Weave.System.Activator.close(stageName);
						}
					});
				}
				else
				{
					stage.controller.pushScene('details', null, null, app);
				}
				//stage.controller.activate();
			}
		});
	},
	
	cleanup: function()
	{
		Mojo.Log.info("AppAssistant.cleanup");
		if (Catalog && Catalog.AppDownloadMngr)
			Catalog.AppDownloadMngr.cleanup();
			
		Weave.Services.ConnectionManager.cleanup();
	},
	
	handleLaunch: function(params)
    {
		Mojo.Log.info("AppAssistant::handleLaunch params *%j*", params);
		if (!params || params == "")
		{
			params = {main: ""};
		}
		
		Weave.System.Activator.run(params);
    },
	
	_findAppAndPackageId: function(target)
	{
		Mojo.Log.info("AppAssistant::_findAppAndPackageId target", target);
		var matches = /[?&]packageid=([^&]*)(?:&applicationid=(\d*)){0,1}/.exec(target);
		return matches ? { packageId: matches[1], applicationId: matches[2] } : {};
	},
});

var DefaultStageAssistant = Class.create(
{	
	setup: function()
	{
		// Default AppMenu
		var self = this;
		var menu = new Weave.Utilities.AppMenu()
			.addEdit()
			.addItem($L('Preferences & Accounts'), 
				function()
				{
					self.controller.pushScene("prefs");
				},
				function()
				{
					return myProfile.enableAcc;
				})
			.addHelp('http://help.palm.com/app_catalog/index.html');
		Weave.Utilities.AppMenu.setDefault(menu);
		
		Weave.Services.ApplicationServer.getAppCatUserFlags(function(status, response, token)
		{
			//myProfile.country = token.country;
			myProfile.carrier = token.carrier.toLowerCase();
			myProfile.email = token.email;
			if (status ) 
			{
				Mojo.Log.info("GetAppCatUserFlags: status:%j, response:%j, token:%j", status, response, token);
				if(response.enablePaymentSetup)
					myProfile.enableAcc = response.enablePaymentSetup;
				else
					menu._items.splice(1, 1);
			}
			else 
			{
				//In case error at server, check for mcc to activate account--only for US
				Mojo.Log.info("Falling back to mcc..");
				Weave.Services.DeviceProfile.getCarrierIdentification(function(status, response)
				{
					if (status && ((response.mcc >= 310 && response.mcc <= 316) || PalmSystem.version.match("desktop"))) {
						myProfile.enableAcc = true;
					}
					else {
						// No nice way to do this - remove the prefs menu item
						menu._items.splice(1, 1);
					}
				});
			}
		});
	},
	
	_gotoMain: function()
	{
		var mainSceneExists = false;
		var sceneStack = this.controller.getScenes();
		if (sceneStack) 
		{
			for (var index = sceneStack.size()-1; index >= 0; index--) 
			{
				var scene = sceneStack[index];
				if (scene.sceneName == "main") 
				{
					mainSceneExists = true;
					break;
				}
			}
		}
			
		// If we're on the main screen, stay there, otherwise pop all the scenes to main
		var top = this.controller.topScene();
		if (top && top.sceneName != "main") 
		{
			this.controller.popScenesTo("main");
		}
		
		if (!mainSceneExists)
		{
			this.controller.pushScene("main");
		}
	}
});

var WebDistributedStageAssistant = Class.create(
{	
	setup: function()
	{
		// Default AppMenu
		var self = this;
		var menu = new Weave.Utilities.AppMenu()
			.addEdit()
			.addItem($L('Preferences & Accounts'), 
				function()
				{
					self.controller.pushScene("prefs");
				},
				function()
				{
					return myProfile.enableAcc;
				})
			.addHelp('http://help.palm.com/app_catalog/index.html');
		Weave.Utilities.AppMenu.setDefault(menu);
		Weave.Services.ApplicationServer.getAppCatUserFlags(function(status, response, token)
		{
			//myProfile.country = token.country;
			myProfile.carrier = token.carrier.toLowerCase();
			myProfile.email = token.email;
			if (status ) {
				if(response.enablePaymentSetup)
					myProfile.enableAcc = response.enablePaymentSetup;
				else
					menu._items.splice(1, 1);
			}
			else 
			{
				Weave.Services.DeviceProfile.getCarrierIdentification(function(status, response)
				{
					if (status && ((response.mcc >= 310 && response.mcc <= 316) || PalmSystem.version.match("desktop"))) {
						myProfile.enableAcc = true;
					}
					else {
						// No nice way to do this - remove the prefs menu item
						menu._items.splice(1, 1);
					}
				});
			}
		});
	},
	
	/**********************************************
    * Global data
    ***********************************************/
	
	embargoedCounrtyList: undefined,
});
/* Copyright 2009 Palm, Inc.  All rights reserved. */

var MainAssistant = Class.create({
	
	initialize : function() 
	{
		Mojo.Log.info("MainAssistant.initialize");
		/*
		var curr = new Date();
		console.log("@@@@@@@@@@@@@@@@@@@ init diff: " + (curr.getTime() - timer.getTime()));
		console.log("@@@@@@@@@@@@@@@@@@@ init total: " + (curr.getTime() - total.getTime()));
		timer = new Date();
		*/
	},

	setup: function() 
	{
		/*
		var curr = new Date();
		console.log("@@@@@@@@@@@@@@@@@@@ setup diff: " + (curr.getTime() - timer.getTime()));
		console.log("@@@@@@@@@@@@@@@@@@@ setup total: " + (curr.getTime() - total.getTime()));
		timer = new Date();
		*/
		Mojo.Log.info("MainAssistant.setup");
		
		this._gotoApp = this._gotoApp.bindAsEventListener(this);
		this._gotoTag = this._gotoTag.bindAsEventListener(this);
		this._gotoTagCloud = this._gotoTagCloud.bindAsEventListener(this);
		this._gotoTopRated = this._gotoTopRated.bindAsEventListener(this);
		this._gotoMostRecent = this._gotoMostRecent.bindAsEventListener(this);
		this._gotoFreeApps = this._gotoFreeApps.bindAsEventListener(this);
		this._gotoSearch = this._gotoSearch.bindAsEventListener(this);
		this._searchKey = this._searchKey.bindAsEventListener(this);
		this._gotoExploreTags = this._gotoExploreTags.bindAsEventListener(this);
		this._focusSearch = this._focusSearch.bindAsEventListener(this);
		
		this._searchModel = { value: '' };
	    this.controller.setupWidget(
			'in-fa-ma-search-text', 
			{
				hintText: $L('Search...'),
				focus: false,
				enterSubmits: true,
				multiline: false,
				modifierState: Mojo.Widget.steModeSentenceCase,
				focusMode: Mojo.Widget.focusInsertMode,
				requiresEnterKey: true,
				changeOnKeyPress: true,
			}, 
			this._searchModel
		);
		
		// Set up the attributes and model for the popular apps list widget
		this.controller.setupWidget(
			'dv-fa-ma-popularappslist',
			{
				itemTemplate: 'main/main-app',
				itemsCallback: this._featuredItemsCallback.bind(this),
				formatters: {
					dummy: this._formatFeaturedItem.bind(this),
				},
				onItemRendered: this._renderFeaturedItem.bind(this),
			},
			{}
		);
	
		// scrolling for palm apps list
		this.controller.setupWidget(
			'dv-fa-ma-popularappslist-scroller', 
			{}, 
			{
				mode: "horizontal"
			}
		);
		
		// Set up a command menu
		this.controller.setupWidget(Mojo.Menu.commandMenu, 
		{
			menuClass: 'no-fade', 
		}, {
			items: [
				{},
				{},
				{
	                label: $L('My Apps'),
	                command: 'myapps',
					icon: 'bag'
	            },
			],
		});
		           
		// Two things need to complete to remove the shim
		//this._shimcount = 0;
		
		// get notified when installed applications change
		Catalog.AppDownloadMngr.attach(this);
		
		// Menus
		Weave.Utilities.AppMenu.useDefault(this);
	},
	
	ready: function()
	{
		/*
		var curr = new Date();
		console.log("@@@@@@@@@@@@@@@@@@@ ready diff: " + (curr.getTime() - timer.getTime()));
		console.log("@@@@@@@@@@@@@@@@@@@ ready total: " + (curr.getTime() - total.getTime()));
		timer = new Date();
		*/
		// Look for a credit card in the background to save time later
		
		
		
	},
	
	cleanup: function() 
	{
		Catalog.AppDownloadMngr.detach(this);
		Mojo.Log.info("MainAssistant.cleanup");
	},

	activate: function() 
	{
		/*
		var curr = new Date();
		console.log("@@@@@@@@@@@@@@@@@@@ activate diff: " + (curr.getTime() - timer.getTime()));
		console.log("@@@@@@@@@@@@@@@@@@@ activate total: " + (curr.getTime() - total.getTime()));
		timer = new Date();
		*/
		
		Mojo.Log.info("MainAssistant.activate");
		
		this.controller.get('in-fa-ma-search-text').addEventListener(Mojo.Event.propertyChange, this._searchKey);
		this.controller.get('dv-fa-ma-popularappslist').addEventListener(Mojo.Event.listTap, this._gotoApp);

		this.controller.get('dv-fa-ma-tag-explore').addEventListener(Mojo.Event.tap, this._gotoExploreTags);
		this.controller.get('dv-fa-ma-tag-mostrecent').addEventListener(Mojo.Event.tap, this._gotoMostRecent);
		this.controller.get('dv-fa-ma-tag-toprated').addEventListener(Mojo.Event.tap, this._gotoTopRated);
		this.controller.get('search-button').addEventListener(Mojo.Event.tap, this._gotoSearch);
		
		this._searchModel.value = '';
		this.controller.modelChanged(this._searchModel);
		this.controller.get('search-button').style.visibility = 'hidden'; // Because changing the model doesnt fire any propertyChange events
		
		this.controller.document.addEventListener(Mojo.Event.tap, this._focusSearch);
		this.controller.get('in-fa-ma-search-text').mojo.focus();
	},

	deactivate: function() 
	{
		Mojo.Log.info("MainAssistant.deactivate");
		
		this.controller.get('in-fa-ma-search-text').removeEventListener(Mojo.Event.propertyChange, this._gotoSearch);
		this.controller.get('dv-fa-ma-popularappslist').removeEventListener(Mojo.Event.listTap, this._gotoApp);
		this.controller.get('dv-fa-ma-tag-explore').removeEventListener(Mojo.Event.tap, this._gotoExploreTags);
		this.controller.get('dv-fa-ma-tag-mostrecent').removeEventListener(Mojo.Event.tap, this._gotoMostRecent);
		this.controller.get('dv-fa-ma-tag-toprated').removeEventListener(Mojo.Event.tap, this._gotoTopRated);
		this.controller.get('search-button').removeEventListener('mouseup', this._gotoSearch);
		this.controller.document.removeEventListener(Mojo.Event.tap, this._focusSearch);
	},
	
	_featuredItemsCallback: function(widget, offset, count)
	{
		var self = this;
		Weave.Services.ApplicationServer.getFeaturedApplications(function(status, apps, papps, releaseStatus, country)
		{
			self.controller.get('popularapps-loader').style.display = "none";
			self.controller.get('dv-fa-ma-popularappslist-scroller').style.display = 'block';
			/*
			if (--self._shimcount == 0) 
			{
				Mojo.Log.info("turning off scrim");
				self.controller.get('splash-scrim').style.display = 'none';
			}
			*/
			
			if (status) 
			{
				myProfile.activationCountry = country
				Mojo.Log.info("Main-Assistant: Activation country %s", myProfile.activationCountry);
				
				// show beta banner if needed
				var disclaimerDiv = self.controller.get('palm-disclaimer');
				if (releaseStatus == "final" && disclaimerDiv.hasClassName("beta"))
				{
					disclaimerDiv.removeClassName("beta");
				}
				else if (releaseStatus == "beta" && !disclaimerDiv.hasClassName("beta"))
				{
					disclaimerDiv.addClassName("beta");
				}
		
				self.controller.get('dv-fa-ma-popularappslist').mojo.noticeUpdatedItems(0, papps);
			}
			else
			{
				// Error
				Utilities.Errors.displayError(apps);
			}
			
			if(myProfile.isEmbargoed === undefined){
			Weave.Services.PaymentServer.getEmbargoedEmailExtensions(function(status, response)
			{
				
				Mojo.Log.info("getEmbargoedCountryList %j myprofileEmail:%s", response, myProfile.email);
				if (status) {
					AppAssistant.embargoedList = response.OutGetEmbargoedEmailExtensions.embargoedEmailExtensions;
					if(myProfile.email)
					{
						var ext = myProfile.email.substring(myProfile.email.lastIndexOf(".") + 1); 
						myProfile.isEmbargoed = AppAssistant.embargoedList.indexOf(ext) != -1;
						Mojo.Log.info("Main Assistant isEmbargoed%s", myProfile.isEmbargoed);
					} 
				}
			});
			if (myProfile.validCC === undefined)
			{
				Weave.Services.PaymentServer.verifyPaymentSetup(function(status, response)
				{
					if (status && response.OutGetCCPaymentInfos.ccPaymentInfos.length > 0)
					{
						myProfile.validCC = response;
					}
				});
			}
		}
		});
	},
	
	_formatFeaturedItem: function(dummy, model)
	{
		model.formattedAverageRating = ('' + (Math.round(model.averageRating * 2) / 2)).replace(/\./, '');
		if (this._getApplicationInstalledState(model) == 'update')
		{
			model.formattedUpdateAvailable = 'has-update';
		}
		else
		{
			model.formattedUpdateAvailable = '';
		}
	},
	
	_renderFeaturedItem: function(widget, model, node)
	{
		new LazyLoadImage(model.appIcon, node.querySelector('.app-picture'));
	},

	_gotoApp: function(event) 
	{
		this.controller.stageController.pushScene("details", event.item.id, event.item.publicApplicationId);
	},
	
	_gotoExploreTags: function(event)
	{
		this.controller.stageController.pushScene("browse", undefined, false); 
	},
	
	_gotoTag: function(event)
	{
		this.controller.stageController.pushScene("search", 'tag', event.item.name);
	},
	
	_gotoTagCloud: function(event)
	{
		this.controller.stageController.pushScene("tagcloud");
	},
	
	_gotoTopRated: function(event)
	{
		this.controller.stageController.pushScene("search", "sort", "by_rating");
	},
	
	_gotoMostRecent: function(event)
	{
		this.controller.stageController.pushScene("search", "sort", "by_date");
	},
	
	_gotoFreeApps: function(event)
	{
		this.controller.stageController.pushScene("search", "sort", "PRICE_DESC");
	},

	_gotoSearch: function(event) 
	{
		this.controller.stageController.pushScene("search", "query", this.controller.get('in-fa-ma-search-text').mojo.getValue());
	},
	
	_searchKey: function(event)
	{
		if (event.originalEvent.type == 'keyup' && event.originalEvent.keyCode == Mojo.Char.enter) 
		{
			this.controller.stageController.pushScene("search", "query", event.value);
		}
		else if (event.value == '')
		{
			this.controller.get('search-button').style.visibility = 'hidden';
		}
		else
		{
			this.controller.get('search-button').style.visibility = 'visible';
		}
	},
	
	// observer method for notifications from AppDownloadMngr
	// called when apps installed on the system change 
	updateInstalledApps: function()
	{
		Mojo.Log.info("MainAssistant.updateInstalledApps");
		var mojo = this.controller.get('dv-fa-ma-popularappslist').mojo;
		if (mojo) 
		{
			var range = mojo.getLoadedItemRange();
			mojo.noticeUpdatedItems(range.offset, mojo.getItems(range.offset, range.limit));
		}
	},
	
	_getApplicationInstalledState: function(summary)
	{
		var app = Catalog.AppDownloadMngr.getInstalledApp(summary.publicApplicationId);
		if (app)
		{
			Mojo.Log.info("versions: installed %s download %s", app.installedVersion, summary.appVersion);
			if (Utilities.VersionCheck.compare(app.installedVersion, summary.appVersion) != -1)
			{
				return 'installed';
			}
			else
			{
				return 'update';
			}
		}
		return 'notinstalled';
	},
	
	handleCommand: function(event)
	{
		if (event.type == Mojo.Event.command && event.command == 'myapps') 
		{
			this.controller.stageController.pushScene("myapps"); 
		}
	},
	
	_focusSearch: function(event)
	{
		this.controller.get('in-fa-ma-search-text').mojo.focus.defer();
	}
})
/*
	Small controller class used to get password for the account
*/
var PasswordAssistant = Class.create(
{
	initialize: function(sceneAssistant, params)
	{
		this.sceneAssistant = sceneAssistant;
		this.controller = sceneAssistant.controller;	
		this._loginAttemptCount = 0;
		this._params = params;
		this._params.sceneAssistant = sceneAssistant;
		// Customer Service URL will be localized 
		this._custserviceurl = $L("http://www.palm.com/us/support/mobile/webos/contact.html");
	},
	
	setup : function(widget) 
	{
		this.widget = widget;
		
		this.acctPasswordAttr = {
			hintText: $L("enter password"),
			modelProperty: 'original',
			autoFocus: true,
			maxLength: 20,
			changeOnKeyPress: true,
			requiresEnterKey: true,
			focusMode: Mojo.Widget.focusSelectMode
		};
		this.acctPasswordModel = {
			'original' : ''
		};

		this.controller.setupWidget('acctPassword', this.acctPasswordAttr, this.acctPasswordModel);
		this.controller.get('acctPassword').observe(Mojo.Event.propertyChange, this._passwordChanged.bind(this));
		
		this.buttonAttr = {
			disabledProperty: 'disabled'
		};
		
		this.buttonModel = {
			disabled: true,
			buttonLabel : $L('Continue'),
			buttonClass: 'palm-button'
		};
		
		this.controller.setupWidget('submitPassword', this.buttonAttr, this.buttonModel);
		Mojo.listen(this.controller.get('submitPassword'), Mojo.Event.tap, this._isUserValid.bindAsEventListener(this));
		
		this.forgotButtonAttr = {
			disabledProperty: 'disabled'
		};
		
		this.forgotButtonModel = {
			disabled: false,
			buttonLabel : $L('Forgot password'),
			buttonClass: 'palm-button'
		};
		
		this.controller.setupWidget('forgotPasswordButton', this.forgotButtonAttr, this.forgotButtonModel);
		this.controller.listen('forgotPasswordButton', Mojo.Event.tap, this._forgotPassword.bindAsEventListener(this));
				
		this._getAccountToken();
		this.passwordField = this.controller.get("acctPassword");
	},
	
	activate: function() 
	{
		this.passwordField.mojo.focus();
	},
	
	_passwordChanged: function(event) 
	{
		// Enable/disable based on length of password
		if(this.acctPasswordModel.original.length > 0) 
	   	{
		  	this.buttonModel.disabled = false;
	   	} 
	   	else 
	   	{
	   		this.buttonModel.disabled = true;
		}
		this.controller.modelChanged(this.buttonModel);

		// If the password field has focus and Enter is pressed then simulate tapping on "next"
		if (Mojo.Char.isEnterKey(event.originalEvent.keyCode)) 
		{
			// If the submit button is enabled then create the account
			if (this.buttonModel.disabled == false) 
			{
				this.passwordField.mojo.blur();
				this._isUserValid();
				Event.stop(event);
			} 
			else 
			{
				this.passwordField.mojo.focus();
			}
		}
	},
	
	_getAccountToken: function() 
	{
		Mojo.Log.info("------------------- Get account token ------------------");

		Weave.Services.AccountServices.getAccountToken(function(status, accountToken, accountAlias, accountState) 
		{
			if (status) 
			{
				if (accountAlias != undefined) 
				{
					myProfile.email = accountAlias;
					myProfile.state = accountState;
				} 
				else 
				{
					//TODO Handle missing account token case
					Mojo.Log.error("PasswordAssistant._getAccountToken: accountAlias == undefined: No account token");	
				}
			} else {
				Mojo.Log.error("No account token");	
			}
		});
	},
	
	_displayErrors: function(badpwdlen, badpwd)
	{
		this.controller.get('passwordLengthError').style.display = (badpwdlen ? null : "none");
		this.controller.get('passwordError').style.display = (badpwd ? null : "none");
	},

	_isUserValid: function() 
	{
		this._loginAttemptCount++;
		myProfile.password = this.acctPasswordModel.original;
		
		if ((myProfile.password.length < 6) || (myProfile.password.length > 20)) 
		{
			this._displayErrors(true, false);
			this.passwordField.mojo.focus.defer();
		}
		else if (!Weave.Services.ConnectionManager.isOnline()) 
		{
			this._displayErrors(false, false);
			//Weave.Services.ConnectionManager.showConnectionError();
		}
		else 
		{
			this.buttonModel.disabled = true;
			this.controller.modelChanged(this.buttonModel);
			
			var self = this;
			Weave.Services.DeviceProfile.getDeviceId(function(status, devid)
			{
				if (status && devid) 
				{
					Weave.Services.AccountServices.isUserValid(myProfile.email, myProfile.password.replace(/ /g,""), devid, function(status, response)
					{
						if (status) 
						{
							Mojo.Log.info("Login attempt: " + self._loginAttemptCount);
							
							if (response.isValid == true) 
							{
								self._displayErrors(false, false);
								//This is the temp token used for preferences
								myProfile.idToken = response.idToken;
								self.widget.mojo.close();
								if (response.passwordResetFlag) 
								{
									self.controller.showDialog(
									{
										template: 'payment-setup/reset-password-dialog',
										assistant: new ResetPasswordAssistant(self, self._params)
									});
								}
								else 
								{
									Preferences.setLoginTime();
									self._params.onComplete(
									{
										passwordValid: true
									});
								}
							}
							else 
							{
								// If we have no security question, fetch it
								if (myProfile.questionId == -2) 
								{
									Weave.Services.AccountServices.getAccountSecurityQuestions(myProfile.email, Mojo.Locale.current, function(status, response)
									{
										if (status) 
										{
											if (response.id !== undefined) 
											{
												Mojo.Log.info("Got security question", response.id);
												myProfile.questionId = response.id;
												myProfile.questionText = response.question;
											}
											else 
											{
												myProfile.questionId = -1;
											}
										}
										else 
										{
											Mojo.Log.error("Error in getting account security question = %o", $H(response));
											myProfile.questionId = -1;
										}
										
										self._loginError();
									});
								}
								else 
								{
									self._loginError();
								}
							}
						}
						else 
						{
							if (response.errorCode && response.errorCode === "CONNECTION_ERROR") 
							{
								this.widget.mojo.close();
								//Weave.Services.ConnectionManager.showConnectionError();
							}
						}
					});
				}
				else 
				{
					Mojo.Log.error("Could not get device id %o", $H(response));
				}
			});
		}
	},
	
	/*
	 * Display login error and allow retries.
	 */
	_loginError: function () 
	{
		if (this._loginAttemptCount >= 3) 
		{
			if (myProfile.questionId >= 0) 
			{
				// Forgot password
				this._forgotPassword();
			}
			else 
			{
				Mojo.Log.info("sending password reset");
				var self = this;
				Weave.Services.AccountServices.requestPasswordResetEmail(myProfile.email, function(status, response)
				{
					if (status)
					{
						Mojo.Log.info("------------ resetEmailSuccess -----------------%o", $H(response));
						self.controller.showAlertDialog(
						{
						    onChoose: function(value) 
							{
								self.widget.mojo.close();
								self._params.onComplete({});
							},
						    title: $L("Password reset"),
						    message: $L('Follow the instructions we sent to <b>#{email}</b> to reset your password or <a href="#{url}">contact customer service.</a>').interpolate({email:myProfile.email, url:self._custserviceurl}),
							allowHTMLMessage: true,
						    choices:
							[
					        	{label: $L('Done'), value:'done', type:'color'}    
						    ]
					 	});	
					}
					else
					{
						Mojo.Log.error("could not send password reset email %o", $H(response));
					}
				});
			}
		}
		else 
		{
			Mojo.Log.info("Login attempt: ", this._loginAttemptCount);
			
			if (myProfile.questionId >= 0) 
			{
				this.controller.get('forgotPassword').show();
			}
			this._displayErrors(false, true);
			this.passwordField.mojo.focus();
		}
	},
	
	_forgotPassword: function()
	{
		this.widget.mojo.close();
		this.controller.showDialog(
		{
			template: 'payment-setup/security-question-dialog',
			assistant: new ForgotPasswordAssistant(this, this._params)
		});
	},
});
var ForgotPasswordAssistant = Class.create({
	
	initialize: function(sceneAssistant, params) {
		this.sceneAssistant = sceneAssistant;
		this.controller = sceneAssistant.controller;
		this.attemptCount = 1;
		this._params = params;
	},
	
	setup : function(widget) 
	{
		this.widget = widget;
		this._clearErrorMessages();
		
		this.controller.get("questionText").update(myProfile.questionText);
						
		this.responseAttributes = {
			modelProperty: 'response',
			multiline: false,
			maxLength: 50, 
			textReplacement: false,
			focusMode: Mojo.Widget.focusInsertMode,
			changeOnKeyPress: true,
			requiresEnterKey: true
		};
		this.responseModel = {
			'response' : '',
			disabled: false
		};
		
		this.controller.setupWidget('response', this.responseAttributes, this.responseModel);
		this.controller.get('response').observe(Mojo.Event.propertyChange, this.responseTextChanged.bind(this));
		
		this.buttonAttr = {
			disabledProperty: 'disabled'
		};
		
		this.buttonModel = {
			disabled: true,
			buttonLabel : $L('Done'),
			buttonClass: 'palm-button'
		};
		
		this.controller.setupWidget('submitAnswer', this.buttonAttr, this.buttonModel);
		this.controller.listen('submitAnswer', Mojo.Event.tap, this.submitAnswer.bindAsEventListener(this));
	},
	
	activate: function() 
	{
		this.controller.get("response").mojo.focus();
	},
	
	toggleDisabled: function() 
	{
		
       if(this.responseModel.response.length > 0) {
		  	this.buttonModel.disabled = false;
			this.controller.modelChanged(this.buttonModel);
	   } else {
	   		this.buttonModel.disabled = true;
			this.controller.modelChanged(this.buttonModel);
	   }
    },
	
	responseTextChanged: function(event) 
	{
	 	this.responseModel.response = event.value;
		this.controller.modelChanged(this.responseModel, this);
		this.toggleDisabled();
		if (event && Mojo.Char.isEnterKey(event.originalEvent.keyCode)) {
			// If the submit button is enabled then submit answer
			if (this.buttonModel.disabled == false) {
				this.submitAnswer();
				Event.stop(event);
			} 
		}
	},
	
	_clearErrorMessages: function()
	{
		this.controller.get("noResponseMessage").hide();
		this.controller.get("wrongResponse").hide();
	},
	
	submitAnswer: function() 
	{
		this.response = this.responseModel.response;
		this._clearErrorMessages();
		if(this.response == "" || this.response == undefined) 
		{
			this.controller.get('noResponseMessage').show();
			return;
		}

		if (Weave.Services.ConnectionManager.isOnline() === false) {
			//Weave.Services.ConnectionManager.showConnectionError();
			return;
		}

		this.buttonModel.disabled = true;
		this.controller.modelChanged(this.buttonModel);
		this.attemptCount++;
		
		var self = this;
		Weave.Services.AccountServices.authenticateAccountFromSecurityQuestion(myProfile.email, myProfile.questionId, this.response, function(status, response)
		{
			if (status)
			{
				Mojo.Log.info("authenticate successful: %o", $H(response));
				if(response.returnValue == true) 
				{
					myProfile.idToken = response.idToken;
					self.widget.mojo.close();		
					self.controller.showDialog(
					{
						template: 'payment-setup/reset-password-dialog',
						assistant: new ResetPasswordAssistant(self, self._params)
					});	
				}
			}
			else
			{
				self._clearErrorMessages();
				Mojo.Log.error("changePassword error = %o", $H(response));
				if (self.attemptCount <= 3) 
				{
					self.controller.get('wrongResponse').show();
					self.controller.get('response').mojo.focus();
				} 
				else 
				{
					Weave.Services.AccountServices.requestPasswordResetEmail(myProfile.email, function(status, response)
					{
						if (status)
						{
							self.widget.mojo.close();		
							self.controller.showDialog(
							{
								template: 'payment-setup/reset-email-dialog',
								assistant: new ResetEmailAssistant(self)
							});	
						}
						else
						{
							self.widget.mojo.close();
							Mojo.Controller.errorDialog($L("We could not send an email to reset your password. Visit palm.com/support for more help."));
						}
					});
				}	
			}
		});
	},

});var ResetPasswordAssistant = Class.create(
{
	initialize: function(sceneAssistant, params)
	{
		this.sceneAssistant = sceneAssistant;
		this.controller = sceneAssistant.controller;
		this._params = params;
	},
	
	setup : function(widget) 
	{
		this.widget = widget;
		this.clearErrorMessages();
		
		this.acctPasswordAttr = {
			hintText: $L("enter password"),
			modelProperty: 'original',
			autoFocus: true,
			maxLength: 20,
			enterSubmits: true,
			focusMode:Mojo.Widget.focusSelectMode
		};
		this.acctPasswordModel = {
			'original' : ''
		};

		this.controller.setupWidget('newPassword', this.acctPasswordAttr, this.acctPasswordModel);
				
		this.verifyPasswordAttr = {
			hintText: $L("confirm password"),
			modelProperty: 'verify',
			autoFocus: false,
			maxLength: 20,
			className: ' ',
			changeOnKeyPress: true,
			requiresEnterKey: true,
			focusMode:Mojo.Widget.focusSelectMode
		};
		this.verifyPasswordModel = {
			'verify' : ''
		};
		
		this.controller.setupWidget('confirmPassword', this.verifyPasswordAttr, this.verifyPasswordModel);	
		
		this.controller.get('newPassword').observe(Mojo.Event.propertyChange, this.passwordChanged.bind(this));
		this.controller.get('confirmPassword').observe(Mojo.Event.propertyChange, this.verifyPasswordChanged.bind(this));
		
		this.buttonAttr = {
			disabledProperty: 'disabled'
		};
		
		this.buttonModel = {
			disabled: true,
			buttonLabel : $L('Done'),
			buttonClass: 'palm-button'
		};
		
		this.controller.setupWidget('submitResetPassword', this.buttonAttr, this.buttonModel);
		this.controller.listen('submitResetPassword', Mojo.Event.tap, this.resetPassword.bindAsEventListener(this));
	},
	
	clearErrorMessages: function() 
	{
		this.controller.get('mismatchMessage').hide();
		this.controller.get('noPasswordMessage').hide();
		this.controller.get('noConfirmMessage').hide();
		this.controller.get('systemErrorMessage').hide();
		this.controller.get('passwordLengthError').hide();
	},
	
	
	toggleDisabled: function() 
	{
       	if (this.acctPasswordModel.original.length > 0 && this.verifyPasswordModel.verify.length > 0) 
		{
		  	this.buttonModel.disabled = false;
			this.controller.modelChanged(this.buttonModel);
	   	} 
	   	else 
	   	{
	   		this.buttonModel.disabled = true;
			this.controller.modelChanged(this.buttonModel);
	   	}
    },
	
	passwordChanged: function (event) 
	{
		this.toggleDisabled();
	},
	
	verifyPasswordChanged: function (event) 
	{
		this.toggleDisabled();
		// If the password field has focus and Enter is pressed then simulate tapping on "next"
		if (event && Mojo.Char.isEnterKey(event.originalEvent.keyCode)) 
		{
			// If the submit button is enabled then change password
			if (this.buttonModel.disabled == false) 
			{
				if (this.resetPassword() === true)
				{
					this.controller.get('newPassword').mojo.focus.defer();
				}
				Event.stop(event);
			} 
		}
	},
	
	resetPassword: function()
	{
		this.newPassword = this.acctPasswordModel.original;
		this.confirmPassword = this.verifyPasswordModel.verify;
		this.clearErrorMessages();
		
		this.controller.get('newPassword').mojo.focus.defer();
		
		if (this.newPassword == "" || this.newPassword == undefined) 
		{
			this.controller.get('noPasswordMessage').show();
			return true;
		}
		else if ((this.newPassword.length < 6) || (this.newPassword.length > 20)) 
		{
			this.controller.get('passwordLengthError').show();
			return true;
		}
		else if (this.confirmPassword == "" || this.confirmPassword == undefined) 
		{
			this.controller.get('noConfirmMessage').show();
			return true;
		}
		else if (this.newPassword != this.confirmPassword) 
		{
			this.controller.get('mismatchMessage').show();
			return true;
		}
		else if (Weave.Services.ConnectionManager.isOnline() === false) 
		{
			//Weave.Services.ConnectionManager.showConnectionError();
		}
		else 
		{
			Mojo.Log.info("AUTHING ACCOUNT", myProfile.email, myProfile.password, this.newPassword);
			
			this.buttonModel.disabled = true;
			this.controller.modelChanged(this.buttonModel);
			
			var self = this;
			Weave.Services.AccountServices.changePassword(this.newPassword.replace(/ /g, ''), myProfile.questionId, undefined, myProfile.idToken, true, function(status, response)
			{
				if (status && response.returnValue) 
				{
					Mojo.Log.info("changePassword successful: %o", $H(response));
					myProfile.password = self.newPassword;
					Preferences.setLoginTime();
					self._params.onComplete(
					{
						passwordValid: true,
						passwordChanged: true,
					});
				}
				else 
				{
					Mojo.Log.error("changePassword error = %o", $H(response));
					if (Weave.Services.ConnectionManager.isOnline() === false || response.errorText === "No response") 
					{
						Mojo.Log.info("show connection error ---------------");
						self.widget.mojo.close();
						//Weave.Services.ConnectionManager.showConnectionError();
					}
					else 
					{
						self.controller.get('systemErrorMessage').show();
					}
				}
			});
		}
	},
});
var ResetEmailAssistant = Class.create({
	
	initialize: function(sceneAssistant) {
		this.sceneAssistant = sceneAssistant;
		this.controller = sceneAssistant.controller;
	},
	
	setup : function(widget) {
		this.widget = widget;
		this.controller.get("emailAddress").update(myProfile.email);
		this.controller.listen("doneResetEmail", Mojo.Event.tap, this.doneResetEmail.bindAsEventListener(this));
	},
		
	doneResetEmail: function(event) {
		this.widget.mojo.close();
	}
	
});
/* Copyright 2009 Palm, Inc.  All rights reserved. */

/*
 * Current user profile.
 */
var myProfile =
{
	email: "",
	password: "",
	firstName: "",
	lastName: "",
	questionId: -2,
	response: "",
	securityQuestions: [],
};

var Preferences =
{
	_timeout: 4 * 60 * 60 * 1000, // 4 hours
	
	getPaymentLogin: function()
	{
		var cookie = (new Mojo.Model.Cookie("com.palm.app.findapps.paymentPref")).get() || {};
		Mojo.Log.info("getPaymentLogin %j", cookie);
		return cookie.paymentPref || "timeout"; 
	},
	
	setPaymentLogin: function(val)
	{
		var cookieJar = new Mojo.Model.Cookie("com.palm.app.findapps.paymentPref");
		var cookie = cookieJar.get() || {};
		Mojo.Log.info("setPaymentLogin %j %s", cookie, val);
		cookie.paymentPref = val;
		cookieJar.put(cookie);
	},
	
	setLoginTime: function()
	{
		var cookieJar = new Mojo.Model.Cookie("com.palm.app.findapps.paymentPref");
		var cookie = cookieJar.get() || {};
		Mojo.Log.info("setLoginTime %j", cookie);
		cookie.loginTime = new Date().getTime();
		cookieJar.put(cookie);
	},
	
	isLoginTimedOut: function()
	{
		var cookie = (new Mojo.Model.Cookie("com.palm.app.findapps.paymentPref")).get() || { loginTime: 0 };
		var now = new Date().getTime();
		Mojo.Log.info("now %d then %d timeout %d diff %d", now, cookie.loginTime, this._timeout, now - cookie.loginTime);
		if (now - cookie.loginTime > this._timeout || cookie.paymentPref == "every")
		{
			return true;
		}
		else
		{
			return false;
		}
	},
};
var TermsOfUse =
{
	_TOSDate: "7th Aug 2009",
	
	isAccepted: function()
	{
		var cookie = (new Mojo.Model.Cookie("com.palm.app.findapps.terms")).get() || {};
		return cookie.termsOfUseAccepted == this._TOSDate;
	},
	
	setAccepted: function()
	{
		var cookieJar = new Mojo.Model.Cookie("com.palm.app.findapps.terms");
		var cookie = cookieJar.get() || {};
		cookie.termsOfUseAccepted = this._TOSDate;
		cookieJar.put(cookie);
	},
};/* Copyright 2009 Palm, Inc.  All rights reserved. */

var Spinner = Class.create({
	
	initialize: function(sceneAssistant, id, start, size, scrim)
	{
		this._controller = sceneAssistant.controller;
		this._model =
		{
			spinning: false
		};
		this._controller.setupWidget(id,
			{
				spinnerSize: size ? size : 'small'
			},
			this._model
		);
		this._scrim = this._controller.get(scrim ? scrim : id + "Cont");
		this._state = 'stopped';
		if (start)
		{
			this.start();
		}
	},
	
	start: function()
	{
		if (this._scrim)
		{
			this._scrim.show();
		}
		this._fire('start');
	},
	
	stop: function()
	{
		this._fire('stop');
	},
	
	_fire: function(event)
	{
		Mojo.Log.info('Spinner event', event, 'state', this._state);
		var self = this;
		switch (this._state)
		{
			case 'stopped':
				switch (event)
				{
					case 'start':
						this._state = 'starting';
						this._timer = setTimeout(function() { self._fire('starting2sec'); }, 2000);
						break;
						
					case 'stop':
					case 'starting1sec':
					case 'running1sec':
						break;
				}
				break;
				
			case 'starting':
				switch (event)
				{
					case 'starting2sec':
						this._state = 'runningquick';
						this._model.spinning = true;
						this._controller.modelChanged(self._model);
						this._timer = setTimeout(function() { self._fire('running1sec'); }, 1000);
						break;
						
					case 'stop':
						clearTimeout(this._timer);
						this._state = 'stopped';
						if (this._scrim)
						{
							this._scrim.hide();
						}
						break;
						
					case 'start':
					case 'running1sec':
						break;
				}
				break;
				
			case 'runningquick':
				switch (event)
				{
					case 'running1sec':
						this._state = 'running';
						break;
						
					case 'stop':
						this._state = 'stopping';
						break;
						
					case 'start':
					case 'starting2sec':
						break;
				}
				break;
				
			case 'running':
				switch (event)
				{
					case 'stop':
						this._state = 'stopped';
						this._model.spinning = false;
						this._controller.modelChanged(this._model);
						if (this._scrim)
						{
							this._scrim.hide();
						}
						break;
						
					case 'start':
					case 'starting2sec':
					case 'running1sec':
						break;
				}
				break;
				
			case 'stopping':
				switch (event)
				{
					case 'running1sec':
						this._state = 'stopped';
						this._model.spinning = false;
						this._controller.modelChanged(this._model);
						if (this._scrim)
						{
							this._scrim.hide();
						}
						break;
						
					case 'start':
						this._state = 'running';
						break;
						
					case 'stop':
					case 'starting2sec':
						break;
				}
				break;
		}
	},
	
});
/* Copyright 2009 Palm, Inc.  All rights reserved. */

var Utilities = Utilities || {};


Utilities.CommonErrors = 
{
	_PMTGroupErrors:
	{
		PMT_0: { dialog: true, title: $L("Payment Type"), message: $L("You can only pay with a credit card. Update your account information in Preferences & Accounts and try again. #{errCode}") , choices: [{ label: $L("OK"), value: true, type: 'dismiss' }]},
		PMT_1: { dialog: true, title: $L("Payment Failed"), message: $L("We cannot process your payment. Contact your financial institution. #{errCode}"), choices: [{ label: $L("OK"), value: true, type: 'dismiss' }] },
		PMT_2: { dialog: true, title: $L("Payment Failed"), message: $L("Update the payment information in your account and try again. #{errCode}"), choices: [{ label: $L("OK"), value: true, type: 'dismiss' }] },
		PMT_3: { dialog: true, title: $L("Payment Failed"), message: $L("CyberSource refused your payment. #{errCode}") , choices: [{ label: $L("OK"), value: true, type: 'dismiss' }]},
		PMT_4: { dialog: true, title: $L("Payment Failed"), message: $L("You are not permitted to purchase items in the App Catalog. #{errCode}") , choices: [{ label: $L("OK"), value: true, type: 'dismiss' }]},
		PMT_5: { dialog: true, title: $L("Transaction Error"), message: $L("The credit card you are using may be fraudulent. Enter a different credit card in Preferences & Accounts and try again. #{errCode}") , choices: [{ label: $L("OK"), value: true, type: 'dismiss' }]},
		PMT_6: { dialog: true, title: $L("Can't Purchase"), message: $L("The United States Government prohibits Palm from allowing you to purchase applications.") , choices: [{label: $L("OK"), value: "quit", type: 'primary'}, {label: $L("Help"), value: "help", type: 'secondary'}]},
		PMT_7: { dialog: true, title: $L("Invalid Address"), message: $L("The address you entered cannot be found. Verify that the address you entered is correct, and is in the Billing Country you have chosen.") , choices: [{label: $L("OK"), value: "ok", type: 'dismiss'}]},

	},
}

Utilities.Errors = 
{
	// This is an entry point to showing error scene
	_displayErrorPage: function(error)
	{
		var stageController = Weave.System.Activator.getActiveStageController(true);
		
		Mojo.Log.error("Errors._displayErrorPage", error);
		if (error == "invalidtoken")
		{
			// If we receive a bad authentication token we must inform the system and popup a dialog (not pop to an error screen)
			// Always felt the error screen was better.
			Weave.Services.AccountServices.notifyAuthenticationFailure(function()
			{
				if (!stageController) return;
				stageController.topScene().showAlertDialog(
				{
					onChoose: function() {},
					title: $L('No Palm Profile'),
					message: $L('You need an active Palm profile to use App Catalog.'),
					choices: 
					[
						{label: $L("OK"), value: true, type: 'dismiss'},
					]
				});
			});
		}
		else
		{
			if (!stageController) return;
			stageController.swapScene("error", error);
		}
	},
	
	_displayIncompatibleErrorPage: function(error)
	{
		Mojo.Log.error("Errors._displayNonFatalErrorPage", error);
		var stageController = Weave.System.Activator.getActiveStageController(true);
		if (!stageController) return;
		
		// This code assusmes that details scene is the one at the top of the stack
		// and that error came from requesting current app details. With current implementation
		// that is always true
		stageController.swapScene("incompatible", error);
	},
	
	_displayErrorDialog: function(error, callback)
	{
		var stageController = Weave.System.Activator.getActiveStageController();
		if (!stageController) return;
		
		stageController.topScene().showAlertDialog(
		{
			onChoose: callback,
			title: error.title,
			message: error.formattedMessage ? error.formattedMessage : error.message,
			choices: error.choices
		});
	},
	
	// displayError: function(errorCode, defaultTitle, defaultMessage, callback)
	displayError: function(errorCode, interpolateArgs, defaultError, defaultTitle, defaultMessage, callback)
	{
		// for payment failures don't show full error pages
		// just go with default error dialog
		if (errorCode == "failure" && defaultError && defaultError.indexOf('PMT_'))
		{
			errorCode = defaultError;
		}		
		
		Mojo.Log.error("Errors.displayError errorCode %s, interpolateArgs %j, defaultError %s, defaultMessage %s", errorCode, interpolateArgs, defaultError, defaultMessage);
		var error = this._dialogErrors[errorCode] || this._dialogErrors[defaultError];
		
		if (!error)
		{
			// if PMT error default to catch_all message 
			if (errorCode.toString().indexOf("PMT") >= 0)
				error = this._dialogErrors[PMT_catchAll];
			else
				error = {dialog: true, title: defaultTitle || $L("Unknown Error"), message: defaultMessage || errorCode};
		}
		
		if (error.page) 
		{
			this._displayErrorPage(errorCode);
		}
		else if (error.incompatible_page)
		{
			this._displayIncompatibleErrorPage(errorCode);
		}
		else if (error.dialog)
		{
			if (interpolateArgs) 
			{
				var msg = error.message;
				error.formattedMessage = msg.interpolate(interpolateArgs);
			}
			
			this._displayErrorDialog(error, callback || function(){});
		}
	},
	
	_dialogErrors:
	{
		// terminal errors
		offline: {page: true},
		invalidtoken: {page: true},
		failure: {page: true},
		badformat: {page: true},
		timeout: {page: true},
		jsonexception: {page: true},
		appunavailable: {page: true},
		dplfailed: {page: true},
		
		// incompatible app errors
		DISC0025: {incompatible_page: true},
		DISC0124: {incompatible_page: true},
		DISC0125: {incompatible_page: true},
		DISC0201: {incompatible_page: true},
		DISC0202: {incompatible_page: true},
		DISC0203: {incompatible_page: true},
		
		// payment errors
		PMT_catchAll: {dialog: true, title: $L("Unexpected problem"), message: $L("App Catalog could not complete the last action you performed. Try again later. #{errCode}"), choices: [{ label: $L("OK"), value: true, type: 'dismiss'}]},
		PMT_addCC_default: {dialog: true, title: $L("Couldn't Add Credit Card"), message: $L("A problem occurred when adding your credit card information. Try again later. #{errCode}"), choices: [{ label: $L("OK"), value: true, type: 'dismiss'}]},
		PMT_modifyCC_default: {dialog: true, title: $L("Couldn't Update"), message: $L("The credit card information could not be updated. Try again later. #{errCode}"), choices: [{ label: $L("OK"), value: true, type: 'dismiss'}]},
		PMT_removeCC_default: {dialog: true, title: $L("Couldn't Remove"), message: $L("The credit card was not removed from your account. Try again later. #{errCode}"), choices: [{ label: $L("OK"), value: true, type: 'dismiss'}]},
		PMT_purchase_default: {dialog: true, title: $L("Couldn't Purchase"), message: $L("The item could not be purchased. Try again later. #{errCode}"), choices: [{ label: $L("OK"), value: true, type: 'dismiss'}]},
		PMT_cant_download: Utilities.CommonErrors._PMTGroupErrors.PMT_6,
		PMT_cant_download_encrypted:  {dialog: true, title: $L("Can't Download"), message: $L("The United States Government prohibits Palm from allowing you to download this application."), choices: [{ label: $L("OK"), value: true, type: 'dismiss'}]},
		PMT_cant_purchase: Utilities.CommonErrors._PMTGroupErrors.PMT_6,			
		PMT02000: { dialog: true, title: $L("Data Entry"), message: $L("A required field does not exist or is empty"), choices: [{ label: $L("OK"), value: true, type: 'dismiss' }] },
		PMT02002: { dialog: true, title: $L("Data Entry"), message: $L("This card type is not supported"), choices: [{ label: $L("OK"), value: true, type: 'dismiss' }] },
		PMT02003: { dialog: true, title: $L("Data Entry"), message: $L("This order type is not supported"), choices: [{ label: $L("OK"), value: true, type: 'dismiss' }] },
		PMT02005: { dialog: true, title: $L("Data Entry"), message: $L("The State code is incorrect. Try again."), choices: [{ label: $L("OK"), value: true, type: 'dismiss' }] },
		PMT02006: { dialog: true, title: $L("Data Entry"), message: $L("The Country code is incorrect. Try again."), choices: [{ label: $L("OK"), value: true, type: 'dismiss' }] },
		PMT02008: { dialog: true, title: $L("Data Entry"), message: $L("The Currency code is incorrect. Try again."), choices: [{ label: $L("OK"), value: true, type: 'dismiss' }] },
		PMT02010: { dialog: true, title: $L("Data Entry"), message: $L("You must enter valid information in every field."), choices: [{ label: $L("OK"), value: true, type: 'dismiss' }] },
		PMT02011: { dialog: true, title: $L("Data Entry"), message: $L("You must enter a number in the Credit Card field."), choices: [{ label: $L("OK"), value: true, type: 'dismiss' }] },
		PMT02012: { dialog: true, title: $L("Data Entry"), message: $L("You must enter a number in the Payment Info ID field."), choices: [{ label: $L("OK"), value: true, type: 'dismiss' }] },
		PMT02013: { dialog: true, title: $L("Data Entry"), message: $L("You must enter a number in the Quantity field."), choices: [{ label: $L("OK"), value: true, type: 'dismiss' }] },
		PMT02014: { dialog: true, title: $L("Data Entry"), message: $L("The date in the Expiration Date field must be in the format mmyyyy"), choices: [{ label: $L("OK"), value: true, type: 'dismiss' }] },
		PMT02015: { dialog: true, title: $L("Data Entry"), message: $L("The value in the Item Unit Price field is not valid. Enter a number instead."), choices: [{ label: $L("OK"), value: true, type: 'dismiss' }] },
		PMT02016: { dialog: true, title: $L("Data Entry"), message: $L("The date in the Order Date field must be in the format yyyyMMddHHmmss"), choices: [{ label: $L("OK"), value: true, type: 'dismiss' }] },
		PMT02017: { dialog: true, title: $L("Data Entry"), message: $L("Update the Expiration Date information in your account and try again."), choices: [{ label: $L("OK"), value: true, type: 'dismiss' }] },
		PMT02018: { dialog: true, title: $L("Data Entry"), message: $L("The Zip code is incorrect. Try again."), choices: [{ label: $L("OK"), value: true, type: 'dismiss' }] },
		
		
		PMT03000: { dialog: true, title: $L("Transaction Error"), message: $L("Update your credit card information in Preferences & Accounts and try again."), choices: [{ label: $L("OK"), value: true, type: 'dismiss' }] },
		PMT03001: Utilities.CommonErrors._PMTGroupErrors.PMT_0,
		PMT03002: { dialog: true, title: $L("Payment Failed"), message: $L("We cannot perform financial transactions in your country."), choices: [{ label: $L("OK"), value: true, type: 'dismiss' }] },
		PMT03003: Utilities.CommonErrors._PMTGroupErrors.PMT_0,
		PMT03006: { dialog: true, title: $L("Payment Failed"), message: $L("The last transaction failed. Update the payment information in your account and try again."), choices: [{ label: $L("OK"), value: true, type: 'dismiss' }] },
		PMT03007: { dialog: true, title: $L("Couldn't Update"), message: $L("Credit card information can only be updated after all pending purchases have cleared. Try again after you received receipts for all recent purchases."), choices: [{ label: $L("OK"), value: true, type: 'dismiss' }] },
		
		PMT04000: { dialog: true, title: $L("Payment Failed"), message: $L("Update the Expiration Date information in your account and try again."), choices: [{ label: $L("OK"), value: true, type: 'dismiss' }] },
		PMT04001: Utilities.CommonErrors._PMTGroupErrors.PMT_1,
		PMT04002: Utilities.CommonErrors._PMTGroupErrors.PMT_1,
		PMT04004: Utilities.CommonErrors._PMTGroupErrors.PMT_1,
		PMT04005: Utilities.CommonErrors._PMTGroupErrors.PMT_2,
		PMT04006: Utilities.CommonErrors._PMTGroupErrors.PMT_1,
		PMT04007: { dialog: true, title: $L("Payment Failed"), message: $L("Update the security number and try again."), choices: [{ label: $L("OK"), value: true, type: 'dismiss' }] },
		PMT04008: Utilities.CommonErrors._PMTGroupErrors.PMT_1,
		PMT04009: Utilities.CommonErrors._PMTGroupErrors.PMT_1,
		// Per Thieu, PMT04010 is only returned as "data entry" and is not associated with the payment
		PMT04010: { dialog: true, title: $L("Data Entry"), message: $L("Update the payment information in your account and try again. #{errCode}"), choices: [{ label: $L("OK"), value: true, type: 'dismiss' }]},
		PMT04011: Utilities.CommonErrors._PMTGroupErrors.PMT_1,
		PMT04012: Utilities.CommonErrors._PMTGroupErrors.PMT_1,
		PMT04013: Utilities.CommonErrors._PMTGroupErrors.PMT_3,
		PMT04014: Utilities.CommonErrors._PMTGroupErrors.PMT_3,
		PMT04015: Utilities.CommonErrors._PMTGroupErrors.PMT_4,
		PMT04016: Utilities.CommonErrors._PMTGroupErrors.PMT_2,
		PMT04017: { dialog: true, title: $L("Payment Failed"), message: $L("There is no payment information associated with your Palm Profile. Tap Preferences in the App Menu to set it up.") , choices: [{ label: $L("OK"), value: true, type: 'dismiss' }]},
		
		PMT04018: Utilities.CommonErrors._PMTGroupErrors.PMT_5,
		PMT04019: Utilities.CommonErrors._PMTGroupErrors.PMT_5,
		PMT04020: Utilities.CommonErrors._PMTGroupErrors.PMT_5,
		PMT04021: { dialog: true, title: $L("Transaction Error"), message: $L("A transaction problem has occurred. Update your credit card information in Preferences & Accounts and try again.") , choices: [{ label: $L("OK"), value: true, type: 'dismiss' }]},
		
		PMT04200: { dialog: true, title: $L("Payment Failed"), message: $L("Update your credit card address information and try again."), choices: [{ label: $L("OK"), value: true, type: 'dismiss' }] },
		PMT04400: Utilities.CommonErrors._PMTGroupErrors.PMT_3,
		PMT04401: Utilities.CommonErrors._PMTGroupErrors.PMT_3,
		PMT04402: Utilities.CommonErrors._PMTGroupErrors.PMT_4,
		PMT04403: Utilities.CommonErrors._PMTGroupErrors.PMT_2,
		PMT04404: Utilities.CommonErrors._PMTGroupErrors.PMT_1,
		PMT04405: Utilities.CommonErrors._PMTGroupErrors.PMT_2,
		PMT04406: Utilities.CommonErrors._PMTGroupErrors.PMT_1,
		PMT04407: Utilities.CommonErrors._PMTGroupErrors.PMT_1,
		PMT04408: Utilities.CommonErrors._PMTGroupErrors.PMT_1,
		PMT04409: Utilities.CommonErrors._PMTGroupErrors.PMT_1,
		PMT04410: Utilities.CommonErrors._PMTGroupErrors.PMT_2,
		PMT04411: Utilities.CommonErrors._PMTGroupErrors.PMT_1,
		PMT04412: Utilities.CommonErrors._PMTGroupErrors.PMT_1,
		PMT04413: Utilities.CommonErrors._PMTGroupErrors.PMT_2,
		PMT04414: Utilities.CommonErrors._PMTGroupErrors.PMT_1,
		PMT04415: { dialog: true, title: $L("Transaction Error"), message: $L("There is a problem with the credit card. Enter a different credit card in Preferences & Accounts and try again. PMT04415") , choices: [{ label: $L("OK"), value: true, type: 'dismiss' }]},
		PMT04416: { dialog: true, title: $L("Transaction Error"), message: $L("There may be a problem with the credit card. Enter a different credit card in Preferences & Accounts and try again. PMT04416") , choices: [{ label: $L("OK"), value: true, type: 'dismiss' }]},
		PMT04417: { dialog: true, title: $L("Transaction Error"), message: $L("There is a problem with the credit card. Enter a different credit card in Preferences & Accounts and try again. PMT04417") , choices: [{ label: $L("OK"), value: true, type: 'dismiss' }]},
		
		//DAV failures
	    PMT04600: Utilities.CommonErrors._PMTGroupErrors.PMT_7,
        PMT04601: Utilities.CommonErrors._PMTGroupErrors.PMT_7,
        PMT04602: Utilities.CommonErrors._PMTGroupErrors.PMT_7,
        PMT04603: Utilities.CommonErrors._PMTGroupErrors.PMT_7,
        PMT04604: Utilities.CommonErrors._PMTGroupErrors.PMT_7,
        PMT04605: Utilities.CommonErrors._PMTGroupErrors.PMT_7,
        PMT04606: Utilities.CommonErrors._PMTGroupErrors.PMT_7,
        PMT04607: Utilities.CommonErrors._PMTGroupErrors.PMT_7,
        PMT04608: Utilities.CommonErrors._PMTGroupErrors.PMT_7,
        PMT04609: Utilities.CommonErrors._PMTGroupErrors.PMT_7,
        PMT04610: Utilities.CommonErrors._PMTGroupErrors.PMT_7,
        PMT04611: Utilities.CommonErrors._PMTGroupErrors.PMT_7,
        PMT04612: Utilities.CommonErrors._PMTGroupErrors.PMT_7,
		
		//DPL failure
		PMT04800: Utilities.CommonErrors._PMTGroupErrors.PMT_6,
		
		//Mod10 failure
		PMT02019: { dialog: true, title: $L("Card validation"), message: $L("Please verify that the credit card number and payment type are set correctly."), choices: [{ label: $L("OK"), value: true, type: 'dismiss' }] },
		
		PMT51004: Utilities.CommonErrors._PMTGroupErrors.PMT_1,
		PMT51005: Utilities.CommonErrors._PMTGroupErrors.PMT_1,
		
		LOCL0001: { dialog: true, title: $L("Data Entry"), message: $L("The address is incomplete. Verify that you’ve entered your complete address."), choices: [{ label: $L("OK"), value: true, type: 'dismiss' }] },
		LOCL0002: { dialog: true, title: $L("Data Entry"), message: $L("The payment information is incomplete. Verify that you’ve entered your payment information completely.") , choices: [{ label: $L("OK"), value: true, type: 'dismiss' }]},
		LOCL0003: { dialog: true, title: $L("Data Entry"), message: $L("Please only enter numbers (0-9) in the account number."), choices: [{ label: $L("OK"), value: true, type: 'dismiss' }] },
		LOCL0004: { dialog: true, title: $L("Data Entry"), message: $L("Choose a card type."), choices: [{ label: $L("OK"), value: true, type: 'dismiss' }] },
		LOCL0005: { dialog: true, title: $L("Data Entry"), message: $L("You must enter a phone number in the Phone Number field."), choices: [{ label: $L("OK"), value: true, type: 'dismiss' }] },
		LOCL0006: { dialog: true, title: $L("Data Entry"), message: $L("Verify your billing information. It is incomplete."), choices: [{ label: $L("OK"), value: true, type: 'dismiss' }] },
		LOCL0007: { dialog: true, title: $L("Data Entry"), message: $L("Verify your account information. It is incomplete or incorrect."), choices: [{ label: $L("OK"), value: true, type: 'dismiss' }] },
		LOC02020: { dialog: true, title: $L("Data Entry"), message: $L("The first name is too long (must be less than 60 characters). Try again."), choices: [{ label: $L("OK"), value: true, type: 'dismiss' }] },
		LOC02021: { dialog: true, title: $L("Data Entry"), message: $L("The last name is too long (must be less than 60 characters). Try again."), choices: [{ label: $L("OK"), value: true, type: 'dismiss' }] },
		LOC02018: { dialog: true, title: $L("Data Entry"), message: $L("The Zip code is incorrect. Try again."), choices: [{ label: $L("OK"), value: true, type: 'dismiss' }] },
		LOC02019: { dialog: true, title: $L("Data Entry"), message: $L("The Postal code is incorrect. Try again."), choices: [{ label: $L("OK"), value: true, type: 'dismiss' }] },
		// ValidateInstallSpace errors
		//
		// only one - "catch all" case for one and multiple apps case
		"validate_space_default_single": {dialog: true, title: $L("Can’t Install"), message: $L("This app requires #{installSize}. You must delete some apps or files before you can install it. Click Help for more information.") , choices:[{label: $L("Help"), value: "help", type: 'primary'}, {label: $L("OK"), value: "ok", type: 'secondary'}]},
		"validate_space_default_mult": {dialog: true, title: $L("Can’t Install"), message: $L("#{installSize} is required to install everything. You can install apps one at a time, or delete apps or files to make room. Click Help for more information.") , choices:[{label: $L("Help"), value: "help", type: 'primary'}, {label: $L("OK"), value: "ok", type: 'secondary'}]},
		
		
		// install errors
		//
		"install_default": {dialog: true, title: $L("Couldn’t Install"), message: $L("There was a problem installing the application"), choices:[{label: $L("Try Again"), value: "retry", type: 'primary'}, {label: $L("Don't Install"), value: "cancel", type: 'secondary'}]},
		"FAILED_NOT_ENOUGH_TEMP_SPACE": {dialog: true, title: $L("Couldn’t Install"), message: $L("This app requires #{installSize}. You must delete some apps or files before you can install it. Click Help for more information.") , choices:[{label: $L("Help"), value: "help", type: 'primary'}, {label: $L("OK"), value: "cancel", type: 'secondary'}]},
		"FAILED_NOT_ENOUGH_INSTALL_SPACE": {dialog: true, title: $L("Couldn’t Install"), message: $L("This app requires #{installSize}. You must delete some apps or files before you can install it. Click Help for more information.") , choices:[{label: $L("Help"), value: "help", type: 'primary'}, {label: $L("OK"), value: "cancel", type: 'secondary'}]},
		
		// below errors fall into default case
		// FAILED_PACKAGEFILE_NOT_FOUND, FAILED_PACKAGEFILE_CORRUPT, FAILED_CREATE_TMP, FAILED_VERIFY, FAILED_IPKG_INSTALL
	
	
		// Download errors that can be returned when response.completed == false
		// contains a lot of HTTP errors as well like 404
		//
		"download_default": {dialog: true, title: $L("Download Error"), message: $L("There was a problem downloading the application."), choices:[{label: $L("Try Again"), value: "retry", type: 'primary'}, {label: $L("Don't Download"), value: "cancel", type: 'secondary'}]},
		"-3": {dialog: true, title: $L("Download Error"), message: $L("The download file is missing or damaged."), choices:[{label: $L("Try Again"), value: "retry", type: 'primary'}, {label: $L("Don't Download"), value: "cancel", type: 'secondary'}]},
		"-2": {dialog: true, title: $L("Download Error"), message: $L("The download file is missing or damaged."), choices:[{label: $L("Try Again"), value: "retry", type: 'primary'}, {label: $L("Don't Download"), value: "cancel", type: 'secondary'}]},
	},
};
/* Copyright 2009 Palm, Inc.  All rights reserved. */

var Utilities = Utilities || {};

Utilities.UTCDate = new (Class.create({
	
	parse: function(date)
	{
		var d = new Date();
		d.setUTCFullYear(date.substring(0, 4) - 0, date.substring(5, 7) - 1, date.substring(8, 10) - 0);
		d.setUTCHours(date.substring(11, 13));
		d.setUTCMinutes(date.substring(14, 16));
		return d;
	},
	
	shortDate: function(d)
	{
		return "" + d.getUTCFullYear() + (d.getUTCMonth() + 1).toPaddedString(2) + d.getUTCDate().toPaddedString(2) + d.getUTCHours().toPaddedString(2) + d.getUTCMinutes().toPaddedString(2) + d.getUTCSeconds().toPaddedString(2);
	},
	
}))();
/* Copyright 2009 Palm, Inc.  All rights reserved. */

var Utilities = Utilities || {};

Utilities.VersionCheck = 
{
	/*
	 * This matches the Java version on the server.
	 * Return 0 is equal, NEG if s1 < s2, POS if s1 > s2
	 */
	compare: function(s1, s2)
	{
		if (s1 == null && s2 == null)
		{
			return 0;
		}
		else if (s1 == null) 
		{
			return -1;
		}
		else if (s2 == null)
		{
			return 1;
		}
		
		var a1 = (''+s1).split(/[^a-zA-Z0-9]+/);
		var a2 = (''+s2).split(/[^a-zA-Z0-9]+/);
		var max = Math.min(a1.length, a2.length);
		
		for (var ii = 0; ii <= max; ii++) 
		{
			if (ii == a1.length)
			{
				return (ii == a2.length ? 0 : -1);
			}
			else if (ii == a2.length)
			{
				return 1;
			}
			var i1 = this._parseInt(a1[ii]);
			var i2 = this._parseInt(a2[ii]);
			if (i1 != i2)
			{
				var r = i1 - i2;
				return r < 0 ? -1 : r > 0 ? 1 : 0;
			}
			i1 = a1[ii].toLowerCase();
			i2 = a2[ii].toLowerCase();
			if (i1 < i2)
			{
				return -1;
			}
			else if (i1 > i2)
			{
				return 1;
			}
		}
		return 0;
	},
	
	_parseInt: function(str)
	{
		if (str.search(/[^\D]/))
		{
			return null;
		}
		else
		{
			return parseInt(str, 10);
		}
	}
};
/* Copyright 2009 Palm, Inc.  All rights reserved. */

var LazyLoadImage = Class.create(
{
	initialize: function(url, target)
	{
		var img = document.createElement('img');
		img.onload = function()
		{
			this.onload = undefined; // Remove the listener (in case of GC broken-ness)
			target.style.backgroundImage = 'url(' + this.src + ')';
		};
		img.src = url;
	}
});
var Utilities = Utilities || {};

Utilities.Common = {
	
	handleCommand: function(event, app)
	{
 		if (event.type == Mojo.Event.command && event.command == 'email') 
 		{
 			var url = this._generateAppURL(app);
			if (url)
			{
				var params = 
				{
					summary: $L("Check out this webOS app"),
					text: this._generateEmailMessageText(app, url)
				}
				Weave.Services.ApplicationManager.launchApplication("com.palm.app.email", params);
			}
 		}
 		else if(event.type == Mojo.Event.command && event.command == 'text')
 		{
 			var url = this._generateAppURL(app);
			if (url)
			{
				var params = {
					messageText: this._generateSMSMessageText(app, url),
				}
				Weave.Services.ApplicationManager.launchApplication("com.palm.app.messaging", params)
			}
 		}
 	},
	
	_generateAppURL: function(app) 
	{
		var details = app.getDetails();
 		if (details && details.publicApplicationId && details.id) 
		{
			return "http://developer.palm.com/appredirect/?packageid=" + details.publicApplicationId + "&applicationid=" + details.id;
 		}
 		else 
		{
 			return null;
 		}
 	},
	
	_generateEmailMessageText: function(app, url) 
	{
		var details = app.getDetails();
		var shareData = {appURL: url};
		if (details && details.title)
		{
 			shareData.title = details.title;
		} 
		else
		{
 			shareData.title = url;
 		}
		return Mojo.View.render({object:shareData, template:'comments/share-template'});
	},
	
	_generateSMSMessageText: function(app, url)
	{
		var appTitle = url;
		var details = app.getDetails();
		if (details && details.title)
		{
 			appTitle = details.title;
		}
		
		return $L("Check out #{title}: #{url}").interpolate({title: appTitle, url: url});
	},
	
	doEmbargoCheck:function(edit, callback)
	{
		Mojo.Log.info("_doEmbargoCheck, profile address%s , embargoed", myProfile.email, myProfile.isEmbargoed);
		if (myProfile.isEmbargoed !== undefined) {
			this._handleEmbargoAcc(edit, callback);
		}
		else {
			var ext = myProfile.email.substring(myProfile.email.lastIndexOf(".") + 1);
			
			if (AppAssistant.embargoedList) {
				myProfile.isEmbargoed = AppAssistant.embargoedList.indexOf(ext) != -1;
				Mojo.Log.info("_doEmbargoCheck, isEmbargoed%s", myProfile.isEmbargoed);
				this._handleEmbargoAcc(edit, callback);
			}
			else {
				
				var self = this;
				Weave.Services.PaymentServer.getEmbargoedEmailExtensions(function(status, response){
					Mojo.Log.info("getEmbargoedCountryList %j", response);
					if (status) {
						AppAssistant.embargoedList = response.OutGetEmbargoedEmailExtensions.embargoedEmailExtensions;
						myProfile.isEmbargoed = AppAssistant.embargoedList.indexOf(ext) != -1;
						self._handleEmbargoAcc(edit, callback);
					}
					else {
						var err = response.errorCode ? response.errorCode : response;
						Utilities.Errors.displayError(err, {
							errCode: err
						}, "PMT_catchAll_default");
					}
				});
			}
		}
	},
	
	_handleEmbargoAcc: function(edit, callback)
	{
		var self = this;
		if (!edit && myProfile.isEmbargoed) 
		{
			Mojo.Log.info("PrefsAssistant, Email address Embargoed %s**",myProfile.email);
			Utilities.Errors.displayError("PMT_cant_download", {}, "PMT_cant_download", null, null, function(value)
			{
				if (value == 'help') 
				{
					Weave.Services.ConnectionManager.getStatus(function(online)
					{
						Weave.Services.ApplicationManager.openApplication('com.palm.app.help', 
						{
							target: online ? 'http://help.palm.com/app_catalog/appcatalog_download_error.html' : 'no-network'
						});
					});
				
				}
			});
		}
		else 
		{
			Mojo.Log.info("not embargoed***");
			callback(edit);
		}	
	},
	
	capWords: function(word){
   		var words = word.split(" "); 
   		for (var i=0 ; i < words.length ; i++){ 
      		words[i] = words[i].capitalize(); 
   		} 
   		return words.join(" "); 
	}
};/* Copyright 2009 Palm, Inc.  All rights reserved. */

if (window == window.top) 
{
	var Weave = Weave || {};
	
	(function()
	{
		function load(config)
		{
			for (var prefix in config) 
			{
				if (prefix) 
				{
					Weave[prefix.charAt(0).toUpperCase() + prefix.substring(1)] = {};
				}
			}
		}
		
		load(
		{
			system:		['activator'],
			services:	['services', 'accountservices', 'applicationinstaller', 'applicationmanager', 'catalogserver', 'applicationserver', 'browser', 'connectionmanager', 
						 'deviceprofile', 'appinstallservice', 'systemmanager', 'systemproperties', 'paymentserver'],
			utilities:	['regexp', 'appmenu'],
			download:	['downloadstates', 'appdetails', 'appdownload', 'appdownloadmanager'],
		});
		
	})();
}/**
 * The Activator manage the dispatch of incoming launch commands.
 * 'Interfaces' (literals of functions) are attached to the Activator
 * together with the interface name and the target stage.  Then a command
 * is 'run', the format of the arguments is used to locate the relevant interface,
 * launch the stage if necessary, and dispatch the contains arguments to the
 * methods named in the parameters.  The command format is:
 *   { interfacename: { methodname: { ... arguments .... } }
 * If no parameters are present, then the 'main' interface is called.  If the
 * stage is created, it calls the 'start' method, if not it calls the 'restart'
 * method.
 
Copyright 2009 Palm, Inc.  All rights reserved.

*/
Weave.System.Activator = 
{	
	_inames: {},
	
	addInterface: function(name, iface)
	{
		this._inames[name] = iface;
	},
	
	run: function(params)
	{
		// there is only one param
		var self = this;
		for (var k in params) 
		{
			// this runs only once
			iface = this._inames[k];
			if (iface)
			{
				iface.init(params[k]);
			}
		}
	},
	
	open: function(stageName, stageAssistantName, callback)
	{
		Mojo.Log.info("Activator.open stage ", stageName, stageAssistantName);
		if (stageName == null) 
		{
			callback && callback(null);
		}
		else 
		{
			var stage = Mojo.Controller.appController.getStageController(stageName);
			if (stage) 
			{
				callback && callback(stage.assistant);
				//bring stage to focus if existing stage
				stage.activate();
			}
			else 
			{
				var self = this;
				Mojo.Controller.appController.createStageWithCallback(
				{
					lightweight: true,
					name: stageName,
					assistantName: stageAssistantName,
					//htmlFileName: 'index-default'
				}, 
				function(stage)
				{
					Mojo.Log.info("Activator.open stageAssistant: %s callback ", stage.assistant.stageName);	
					callback && callback(stage.assistant);
				});
			}
		}
	},
	
	close: function(name)
	{
		Mojo.Controller.appController.closeStage(name);
	},
	
	// returns active stage controller that is not dashboard
	getActiveStageController: function(tryDefault)
	{
		var stage = Mojo.Controller.appController.getActiveStageController();
		if (!stage && tryDefault)
		{
			stage = Mojo.Controller.appController.getStageController("default");
		}
		
		return stage;
	}
};
/**
 * Services provides a basic garbage-collector safe wrapper round the standard service 
 * requests.
 
Copyright 2009 Palm, Inc.  All rights reserved.

*/
Object.extend(Weave.Services,
{	
	_pending: {},
	_next: 1,
	
	request: function(target, args, success, failure)
	{
		var id = this._next++;
		var pending = this._pending;
		args.onSuccess = function(response)
		{
			delete pending[id];
			success && success(response);
		};
		args.onFailure = function(response)
		{
			delete pending[id];
			failure && failure(response);
		};
		pending[id] = new Mojo.Service.Request(target, args);
		return pending[id];
	},
	
	subscriptionRequest: function(target, args, success, failure)
	{
		var id = this._next++;
		var pending = this._pending;
		args.onSuccess = function(response)
		{
			success && success(response);
		};
		args.onFailure = function(response)
		{
			failure && failure(response);
		};
		args.parameters = Object.extend(args.parameters || {}, {subscribe: true});
		
		pending[id] = new Mojo.Service.Request(target, args);
		return pending[id];
	},
	
	
	// called on application cleanup to delete any
	// pending requests
	cleanup: function()
	{
		
	}
	
});
/* Copyright 2009 Palm, Inc.  All rights reserved. */

Weave.Services.AccountServices = 
{
	_target: 'palm://com.palm.accountservices',
	
	getServerUrl: function(callback)
	{
		Weave.Services.request(this._target,
		{
			method: 'getServerUrl',
			parameters: {},
		},
		function(response)
		{
			callback(true, response.serverUrl);
		},
		function()
		{
			callback(false);
		});
	},
	
	getPaymentServerUrl: function(callback)
	{
		/*
		 * e.g. http://148.92.248.80:8080/palmcsext/services/deviceJ/getPreferences
		 * Request : {"InPreferences":{"preferenceKey":"APPLICATIONS,PAYMENT"}}
		 * Response: {"OutParameterInfo":{"parameterInfos":{"category":"SETTINGS","key":"PAYMENT_URL","value":"http:\/\/148.192.248.80:8080\/palmcspmtext\/services\/paymentJ\/"},"size":1}}
		 */
		Weave.Services.request(this._target,
		{
			method: 'getPreferences',
			parameters: 
			{
				appName: "PAYMENT",
			},
		},
		function(response)
		{
			callback(true, response.parameterInfos.value);
		},
		function()
		{
			callback(false);
		});
	},
	
	getAccountToken: function(callback)
	{
		Mojo.Log.info("**** Trying to get Account Token");
		Weave.Services.request(this._target,
		{
			method: 'getAccountToken',
			parameters: {},
		},
		function(response)
		{
			Mojo.Log.info("**** Got account token");
			callback(true, response.token, response.accountAlias, response.accountState);
		},
		function()
		{
			callback(false);
		});
	},
	
	notifyAuthenticationFailure: function(callback)
	{
		// This request gets no rely - so send it and invoke the callback immediately
		Weave.Services.request(this._target,
		{
			method: 'notifyAuthenticationFailure',
			parameters: {
				trustedApp: true,
			},
		});
		callback(true);
	},
	
	notifyUninstalledApplication: function(name, version, callback)
	{
		Weave.Services.request(this._target,
		{
			method: 'notifyUninstalledApplication',
			parameters: {
				name: name,
				version: version,
			},
		},
		function()
		{
			callback(true);
		},
		function()
		{
			callback(false);
		});
	},
	
	getAccountInfo: function(callback) 
	{
		// Possible to use the Weave.Services.request instead of a serviceRequest?
		Mojo.Log.info("** Getting account info");
		Weave.Services.request(this._target, 	
		{
			method: 'getAccountInfo',
			parameters:{}
		},
		function(response)
		{
			callback(true, response);
		},
		function()
		{
			callback(false);
		});
	},
	
	updateAccountInfo: function(myProfile, callback) 
	{
		Weave.Services.request(this._target, 
		{
			method: 'updateAccountInfo',
			parameters: 
			{
				'firstName': myProfile.firstName, 
				'lastName':myProfile.lastName,
				'password':myProfile.password, 
				'email':myProfile.email,
				'languageCode':myProfile.languageCode,
				'countryCode': myProfile.countryCode 
			}
		},
		function(response)
		{
			callback(true, response);
		},
		function()
		{
			callback(false);
		});
	},
	
	isUserValid: function(email, password, deviceId, callback) 
	{
		Weave.Services.request(this._target,
		{
			method:"isUserValid",
			parameters: 
			{
				'email':email, 
				'password':password, 
				"deviceId":deviceId
			}
		},
		function(response)
		{
			callback(true, response);
		},
		function()
		{
			callback(false);
		});
	},
	
	getLocale: function(callback) 
	{
		Weave.Services.request('palm://com.palm.systemservice', 
		{
			method: 'getPreferences',
			parameters: 
			{
				"keys":["locale"]
			},
		},
		function(response)
		{
			callback(true, response);
		},
		function()
		{
			callback(false);
		});
	},
	
	getAllSecurityQuestions: function(userLocale, callback) 
	{
		Weave.Services.request(this._target,
		{
			method:"getAllSecurityQuestions",
			parameters: {subscribe: false, locale:userLocale}
		},
		function(response)
		{
			callback(true, response);
		},
		function()
		{
			callback(false);
		});
	},
	
	getAccountSecurityQuestions: function(email, locale, callback) 
	{
		Weave.Services.request(this._target,
		{
			method:"getAccountSecurityQuestion",
			parameters: {"email": email, "locale":locale}
		},
		function(response)
		{
			callback(true, response);
		},
		function()
		{
			callback(false);
		});		
	},
	
	resendVerificationEmail: function(callback) 
	{
		Weave.Services.request(this._target,
		{
			method:"requestResendVerificationEmail",
			parameters: {}
		},
		function(response)
		{
			callback(true, response);
		},
		function()
		{
			callback(false);
		});
	},
	
	requestPasswordResetEmail: function(emailAddress, callback)
	{
		Weave.Services.request(this._target,
		{
			method:"requestPasswordResetEmail",
			parameters: 
			{
				"email": emailAddress, "subscribe": false
			}
		},
		function(response)
		{
			callback(true, response);
		},
		function()
		{
			callback(false);
		});
	},
	
	authenticateAccountFromSecurityQuestion: function(email, questionId, response, callback) 
	{
		Weave.Services.request(this._target,
		{
			method:"authenticateAccountFromSecurityQuestion",
			parameters: {'email':email, 'questionId':questionId, 'response':response}
		},
		function(response)
		{
			callback(true, response);
		},
		function()
		{
			callback(false);
		});
	},
	
	changeEmail: function(email, callback) 
	{
		Weave.Services.request(this._target, 
		{
			method: 'changeEmail',
			parameters: {"email": email}
		},
		function(response)
		{
			callback(true, response);
		},
		function()
		{
			callback(false);
		});
	},
	
	changePassword: function(newPassword, questionId, answer, idToken, isResetPassword, callback) 
	{
		Weave.Services.request(this._target,
		{
			method:"changePassword",
  			parameters: {"newPassword": newPassword, "questionId":questionId, "answer":answer, "idToken":idToken, "isResetPassword":isResetPassword}
		},
		function(response)
		{
			callback(true, response);
		},
		function()
		{
			callback(false);
		});
	},
	
	authenticateAccount: function(email, password, callback) 
	{
		Weave.Services.request(this._target,
		{
			method:"authenticateAccount",
 			parameters: {'email':email, 'password':password, 'application':'ASClient'}
		},
		function(response)
		{
			callback(true, response);
		},
		function()
		{
			callback(false);
		});			
	},
	isEmailAvailable: function(email, callback) 
	{		
		Weave.Services.request(this._target,
		{
			method:"isEmailAvailable",
 			parameters: {'email':email}
		},
		function(response)
		{
			Mojo.Log.info("response:" + Object.toJSON(response));
			callback(true, response);
		}
		);			
	}
};

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

Weave.Services.ApplicationInstaller = 
{
	_target: 'palm://com.palm.appinstaller',
	
	validateInstall: function(appid, size, uncomprSize, callback)
	{
		uncomprSize = (uncomprSize && uncomprSize != 0) ? Math.ceil(uncomprSize/1024): undefined;
		size = Math.ceil(size/1024);
		Mojo.Log.info("Weave.Services.ApplicationInstaller.validateInstall appid %s, size %s, uncomprSize %d", appid,size,uncomprSize );
		var self = this;
		this._pendingreq = Weave.Services.request(self._target, 
		{
			method: 'queryInstallCapacity',
			parameters: 
			{
				appId: appid,
				size: size,
				uncompressedSize: uncomprSize,
			},
		},
		function(response)
		{
			if (response.result == 0 || response.result == 4  || PalmSystem.version.match("desktop")) 
			{
				callback(true);
			}
			else
			{
				Mojo.Log.error("Weave.Services.ApplicationInstaller.validateInstall failed %j", response);
				callback(false, response);
			}
		},
		function(response)
		{
			Mojo.Log.error("Weave.Services.ApplicationInstaller.validateInstall failed %j", response);
			callback(false);
		});
	},
	
};/* Copyright 2009 Palm, Inc.  All rights reserved. */

Weave.Services.ApplicationManager =
{	
	_target: 'palm://com.palm.applicationManager',
	
	getInstalledApplications: function(callback)
	{
		Weave.Services.request(this._target, 
		{
			method: 'listApps',
			parameters: {},
		},
		function(response)
		{
			//Mojo.Log.info("GET INSTALLED APPLICATIONS %j", response);
			var apps = response.apps;
			callback(true, !apps ? [] : Object.isArray(apps) ? apps : [ apps ]);
		},
		function()
		{
			callback(false);
		});
	},
	
	openApplication: function(name, params)
	{
		Weave.Services.request(this._target, 
		{
			method: 'open',
			parameters: 
			{
				id: name,
				params: params
			}
		});
	},

	launchApplication: function(name, params)
	{
		Weave.Services.request(this._target, 
		{
			method: 'launch',
			parameters: 
			{
				id: name,
				params: params
			}
		});
	},
	
	launchPointChanges: function(callback)
	{
		Weave.Services.subscriptionRequest(this._target, 
		{
			method: 'launchPointChanges',
		},
		function(response)
		{
			callback(true, response);
		},
		function()
		{
			callback(false);
		});
	},
	
	/*
	getSizeOfApps: function(apps, callback)
	{
		Weave.Services.request(this._target, 
		{
			method: 'getSizeOfApps',
			parameters: 
			{
				appIds : apps
			}
		},
		function(response)
		{
			var total = 0;
			for (var x in response)
			{
				if (x != "subscribed" && x != "returnValue")
				{
					Mojo.Log.error("response X: ", response[x]);
					total += response[x];
				}
			}
			callback(total);
		},
		function()
		{
			Mojo.Log.info("getSizeOfApps failed");
		});
	},
	*/
};
/* Copyright 2009 Palm, Inc.  All rights reserved. */

Weave.Services.CatalogServer = Class.create(
{
	_defaultRetries: 3,
	_defaultCallTimeout: 20.0,
	
	$whenReadyServerUrl: function(callback)
	{
		if (this._serverUrl) 
		{
			callback();
		}
		else 
		{
			// We need the server url and the carrier info
			var self = this;
			Weave.Services.AccountServices.getServerUrl(function(status, url)
			{
				self._serverUrl = status ? url : 'error:///';
				callback();
			});
		}
	},
	
	$whenReadyServerUrlCarrier: function(callback)
	{
		if (this._serverUrl && this._carrier) 
		{
			callback();
		}
		else 
		{
			// We need the server url and the carrier info
			var self = this;
			this.$whenReadyServerUrl(function()
			{
				self._carrier && callback();
			});
			this._carrier || Weave.Services.Preferences.SystemProperties.getCarrier(function(status, carrier)
			{
				self._carrier = status ? carrier : 'ROW';
				self._serverUrl && callback();
			});
		}
	},
	
	_whenReadySecurityToken: function(callback)
	{
		var self = this;
		var calledCallback = undefined;
		function ready()
		{		
			return (self._token != undefined && self._deviceid != undefined && self._email != undefined && self._carrier != undefined) ;
		};
		if (ready())
		{
			calledCallback = true;
			callback();
		}
		else 
		{
			this._token || Weave.Services.AccountServices.getAccountToken(function(status, token, email)
			{
				Mojo.Log.info("Token:", status, token);
				self._token = status ? token : 'ERROR';
				self._email = status ? email : 'ERROR';
				if (ready() && !calledCallback) 
				{
					calledCallback = true;
					callback();
				}
			});
			this._deviceid || Weave.Services.DeviceProfile.getDeviceId(function(status, id)
			{
				Mojo.Log.info("DeviceId:", id);
				self._deviceid = status ? id : 'ERROR';
				if (ready() && !calledCallback) 
				{
					calledCallback = true;
					callback();
				}
			});
			this._carrier || Weave.Services.Preferences.SystemProperties.getCarrier(function(status, carrier)
			{
				Mojo.Log.info("Carrier:", carrier);
				self._carrier = status ? carrier : 'ROW';
				if (ready() && !calledCallback) 
				{
					calledCallback = true;
					callback();
				}
			});
		}
	},
	
	getSecurityToken: function(callback)
	{
		var self = this;
		this._whenReadySecurityToken(function()
		{
			callback(
				{
					token: self._token,
					deviceId: self._deviceid,
					email: self._email,
					carrier: self._carrier,
				}
			);
		});
	},
	
	invalidateSecurityToken: function()
	{
		delete this._token;
	},
	
	_callServer: function(path, body, callback, retries)
	{
		//Mojo.Log.info("_callServer %s %s %j", this._serverUrl, path, body);
		retries = (retries == undefined ? this._defaultRetries : retries);
		var self = this;
		this.$whenReadyServerUrl(function()
		{
			Mojo.Log.info("ServerURL ready");
			var timeout = 0;
			var id = Weave.Services.ConnectionManager.waitForOffline(function()
			{
				if (!timeout++) 
				{
					Mojo.Log.info("Offline");
					callback(false, 'offline'); // do not localize
				}
			});
			if (id) 
			{
				Mojo.Log.info("Ajax.Request");
				new Ajax.Request(self._serverUrl + path, 
				{
					method: 'POST',
					contentType: 'application/json',
					postBody: Object.toJSON(body),
					evalJSON: 'force',
					onSuccess: function(response)
					{
						if (!timeout++) 
						{
							//Mojo.Log.info("onSuccess %j", response);
							response = response.responseJSON;
							if (!response) 
							{
								callback(true); // Empty replies are okay
							}
							else 
							{
								var exception = response.JSONException;
								if (exception) 
								{
									Mojo.Log.error("CatalogServer._callServer %j", exception);
									callback(false, 'jsonexception', exception);
								}
								else 
								{
									callback(true, response);
								}
							}
						}
					},
					onFailure: function(response)
					{
						if (!timeout++) 
						{
							Mojo.Log.info("onFailure %j", response);
							if (retries < 1) 
							{
								if (response.responseJSON && response.responseJSON.JSONException) 
								{
									callback(false, 'jsonexception', response.responseJSON.JSONException);
								}
								else 
								{
									callback(false, 'failure', response.status); // do not localize
								}
							}
							else 
							{
								self._callServer(path, body, callback, retries - 1);
							}
						}
					},
					on0: function(response)
					{
						// If we fail to connect to the network for some reason, then we get a status == 0 which would normally
						// go into onSuccess.  We define a specific error handler here to avoid this.
						// When we fail and can no longer retry, we either report offline or failure depending on the current
						// connection manager status.
						if (!timeout++) 
						{
							Mojo.Log.info("on0 %j", response);
							if (retries < 1) 
							{
								Weave.Services.ConnectionManager.getStatus(function(online)
								{
									callback(false, online ? 'failure' : 'offline', response.status); // do not localize
								});
							}
							else 
							{
								self._callServer(path, body, callback, retries - 1);
							}
						}
					}
				});
				(function()
				{
					if (!timeout++) 
					{
						Mojo.Log.info("Timeout");
						if (retries < 1) 
						{
							callback(false, 'timeout'); // do not localize
						}
						else 
						{
							self._callServer(path, body, callback, retries - 1);
						}
					}
					Weave.Services.ConnectionManager.cancelWait(id);
				}).delay(self._defaultCallTimeout);
			}
		});	
	},
});
/* Copyright 2009 Palm, Inc.  All rights reserved. */

Weave.Services.ApplicationServer = new (Class.create(Weave.Services.CatalogServer,
{
	$whenReadyServerUrl: function(callback)
	{
		if (this._serverUrl) 
		{
			callback();
		}
		else 
		{
			// We need the server url and the carrier info
			var self = this;
			Weave.Services.AccountServices.getServerUrl(function(status, url)
			{
				self._serverUrl = status ? url : 'error:///';
				callback();
			});
		}
	},
	
	getFeaturedApplications: function(callback)
	{
		var self = this;
		this.getSecurityToken(function(token)
		{
			self.$whenReadyServerUrlCarrier(function()
			{
				var features =
				{
					InGetFeaturedAppsV2:
					{
						accountTokenInfo: token,
						carrier: self._carrier,
					}
				};
				self._callServer('featuredApps_ext2', features, function(status, response)
				{
					if (status) 
					{
						if (response.FeaturedApps) 
						{
							callback(true, response.FeaturedApps.palmFeaturedAppList.appSummary, response.FeaturedApps.carrierFeaturedAppList.appSummary, response.FeaturedApps.releaseStatus, response.FeaturedApps.country);
						}
						else 
						{
							callback(false, 'badformat'); // do not localize
						}
					}
					else 
					{
						callback.apply(null, arguments);
					}
				});
			});
		});
	},
	
	getAppCatUserFlags: function(callback)
	{
		var self = this;
		this.getSecurityToken(function(token)
		{
			self.$whenReadyServerUrl(function()
			{
				var inGetAppCatUserFlags =
				{
					InGetAppCatUserFlags:
					{
						accountTokenInfo: token
					}
				};
				self._callServer('getAppCatUserFlags', inGetAppCatUserFlags, function(status, response)
				{
					if (status) 
					{
						if (response.OutGetAppCatUserFlags) 
						{
							callback(true, response.OutGetAppCatUserFlags, token);
						}
						else 
						{
							callback(false, 'badformat', token); // do not localize
						}
					}
					else 
					{
						callback(false, response, token);
					}
				});
			});
		});
	},

	getCategories: function(category, callback)
	{
		var self = this;
		this.$whenReadyServerUrlCarrier(function()
		{
			var params =
			{
				InGetCategoryList:
				{
					locale: Mojo.Locale.current,
					categoryId: category || "",
				}
			};
			self._callServer('categoryList', params, function(status, response)
			{
				Mojo.Log.info("getCategories", status, response);
				if (status)
				{
					if (response.OutGetCategoryList == '' || response.OutGetCategoryList.categoryList == '')
					{
						callback(true, [], 0);
					}
					else if (response.OutGetCategoryList && response.OutGetCategoryList.categoryList && response.OutGetCategoryList.categoryList.categoryItems)
					{
						var items = response.OutGetCategoryList.categoryList.categoryItems;
						callback(true, !items ? [] : Object.isArray(items) ? items : [ items ]);
					}
					else
					{
						callback(false, 'badformat'); // do not localize
					}
				}
				else
				{
					callback(false, response);
				}
			});
		});
	},
	
	getApplicationDetails: function(appId, packageId, locale, callback)
	{
		var self = this;
		this.getSecurityToken(function(token)
		{
			var details =
			{
				InGetAppDetailV2:
				{
					accountTokenInfo: token,
					appId: appId,
					packageId: packageId,
					locale: locale,
				}
			};
			self._callServer('appDetail_ext2', details, function(status, response, extra)
			{
				if (status)
				{
					if (response.OutGetAppDetailV2) 
					{
						callback(true, response.OutGetAppDetailV2.appDetail, response.OutGetAppDetailV2.userRating, response.OutGetAppDetailV2.country);
					}
					else
					{
						callback(false, 'badformat'); // do not localize
					}
				}
				else
				{		
					if (response == 'jsonexception' && extra.errorCodes == 'DISC0120')
					{
						callback(false, 'appunavailable', extra.message); // do not localize
					}
					else if (response == 'jsonexception' && self._isAppIncompatibleError(extra.errorCodes))
					{
						// app is not compatible with the device
						callback(false, extra.errorCodes); 
					}
					else if (response == 'jsonexception' && self._isInvalidTokenError(extra.errorCodes))
					{
						callback(false, 'invalidtoken', extra.message); // do not localize
					}
					else
					{
						callback.apply(null, arguments);
					}
				}
			});
		});
	},
	
	getTags: function(limit, order, callback)
	{
		var get = 
		{
			InGetTags:
			{
				criterion: (order == 'popularity' ? 'APP_COUNT' : 'TAG_NAME'),
				limit: limit
			}
		}
		this._callServer('getTags', get, function(status, response)
		{
			if (status)
			{
				if (response.OutGetTagList) 
				{
					callback(true, response.OutGetTagList.tagList.tagItems);
				}
				else
				{
					callback(false, 'badformat'); // do not localize
				}
			}
			else
			{
				callback.apply(null, arguments);
			}
		});
	},
	
	searchForApplications: function(query, categoryid, start, count, sort, locale, callback)
	{
		Mojo.Log.info("*********************************Entered seach for applications***********************");
		var self = this;
		this.getSecurityToken(function(token)
			{
				var q = [];
				if (query)
				{
					var words = query.toLowerCase().split(' ');
					for (var i = 0; i < words.length; i++)
					{
						q.push(words[i] + '*');
					}
				}
				q = q.join(' && ');
				Mojo.Log.info("********* token *****" + token);
				var search =
				{
					InGetAppListV2:
					{
						tagName: '',
						queryStr: q,
						categoryid: categoryid,
						startPosition: start,
						count: count,
						sort: sort,
						locale: locale,
						accountTokenInfo: token
					}
				};
				Mojo.Log.info('****************************** app List called with token***' + token);
				self._callServer('appList_ext2', search, function(status, response)
				{
					if (status)
					{
						if (response.OutGetAppList) 
						{
							var apps = response.OutGetAppList.appList.appSummary;
							callback(true, !apps ? [] : apps.constructor != Array ? [ apps ] : apps, response.OutGetAppList.appList.totalCount, response.OutGetAppList.country);
						}
						else
						{
							callback(false, 'badformat'); // do not localize
						}
					}
					else
					{
						callback.apply(null, arguments);
					}
				});
			});
	},
	
	getUserComments: function(appid, offset, count, callback)
	{
		var get =
		{
			InGetUserRatings:
			{
				appId: appid,
				startPosition: offset,
				count: count,
			}
		};
		this._callServer('getUserRatings', get, function(status, response)
		{
			if (status)
			{
				if (response.UserRatingList) 
				{
					var ratings = response.UserRatingList.ratings;
					ratings = (!ratings ? [] : ratings.constructor != Array ? [ ratings ] : ratings);
					callback(true, ratings, ratings.length);
				}
				else if (response.UserRatingList == '')
				{
					callback(true, [], 0);
				}
				else
				{
					callback(false, 'badformat'); // do not localize
				}
			}
			else
			{
				callback.apply(null, arguments);
			}
		});
	},
	
	addUserComment: function(appid, comment, score, locale, name, anonymous, inappropriate, callback)
	{
		var self = this;
		this.getSecurityToken(function(token)
		{
			var rating = 
			{
				InAddUserRating: 
				{
					userRatingItem: 
					{
						score: Math.round(score), // Server requires score to be an interger
						comment: comment,
						accountId: name || "",
						locale: locale,
						isAnonymous: anonymous,
						isInappropriate: inappropriate,
						appId: appid,
					},
					appEntryId: appid,
					accountTokenInfo: token,
				}
			};
			self._callServer('addUserRating', rating, function(status, response, extra)
			{
				callback.apply(null, arguments);
			});
		});
	},
	
	getMyComment: function(appid, callback)
	{
		var self = this;
		this.getSecurityToken(function(token)
		{
			var comment =
			{
				InGetMyRating:
				{
					accountTokenInfo: token,
					appEntryId: appid
				}
			};
			self._callServer('getMyRating', comment, function(status, response, extra)
			{
				if (status)
				{
					if (response && response.UserRating)
					{
						callback(true, response.UserRating);
					}
					else
					{
						callback(true, null);
					}
				}
				else
				{
					if (response == 'jsonexception' && self._isInvalidTokenError(extra.errorCodes))
					{
						callback(false, 'invalidtoken', extra.message); // do not localize
					}
					else
					{
						callback.apply(null, arguments);
					}
				}
			});
		});
	},
	
	getAppListForUpdates: function(packageNames, callback) 
	{
		Mojo.Log.info("Weave.Services.ApplicationServer.getAppListForUpdates sending %s", packageNames);
		var updates = 
		{
			InGetUpdatableApps: 
			{
				packageIds: packageNames
			}
		};
		this._callServer('getListOfUpdatableApps', updates, function(status, response, extra)
		{
			if (status) 
			{
				Mojo.Log.info("%j", response);
				if (response.OutUpdateInfoList == "") 
				{
					callback(true, []);
				}
				else if(response.OutUpdateInfoList) 
				{
					var updates = response.OutUpdateInfoList.appSummaryForUpdates;
					callback(true, !updates ? [] : Object.isArray(updates) ? updates : [ updates ]);
				} 
				else 
				{
					callback(false, 'badformat');
				}
			}
			else
			{
				callback.apply(null, arguments);
			}
		});
	},
	
	_isInvalidTokenError: function(err)
	{
		if (err == 'DISC0049' || err == 'DISC0050' || err == 'DISC0051')
		{
			Mojo.Log.error("ApplicationServer._isInvalidTokenError TRUE, error:%s", err);
			return true;
		}
		return false;
	},
	
	_isAppIncompatibleError: function(err)
	{
		if (err == 'DISC0025' || err == 'DISC0124' || err == 'DISC0125'
			|| err == 'DISC0201' || err == 'DISC0202' || err == 'DISC0203')
		{
			Mojo.Log.error("ApplicationServer.isAppIncompatibleError TRUE, error:%s", err);
			return true;
		}
		return false;
	},	
}));
/* Copyright 2009 Palm, Inc.  All rights reserved. */

Weave.Services.ApplicationManager.Browser =
{
	openPage: function(url)
	{
		Weave.Services.ApplicationManager.openApplication('com.palm.app.browser', { target: url });
	},
	
};/* Copyright 2009 Palm, Inc.  All rights reserved. */

Weave.Services.ConnectionManager =
{
	_target: 'palm://com.palm.connectionmanager',
	_online: undefined,
	_1x: undefined,
	_waiting: [],
	_started: false,
	
	_startup: function(callback)
	{
		this._started = true;
		if (PalmSystem.version.match("desktop")) 
		{
			// Simulator is always online
			this._stateChanged(true);
			callback && callback(this._online);
			callback = null;
		}
		else 
		{
			var self = this;
			
			// register for entering msm notifications
		 	this._MSMrequest = Weave.Services.request('palm://com.palm.bus/signal',
			{
				method: 'addmatch',
				parameters:
				{
					"category" : "/storaged",
					"method" : "MSMProgress",
					"subscribe" : true
				}
			},
			function(response)
			{
				self._MSMnotification(response);
			},
			function(response)
			{
				Mojo.Log.error("ConnectionManager._startup MSM subscription request failed %j", response);
				self._MSMnotification(response);
			});
	
			this._getstatusreq = Weave.Services.request(this._target, 
			{
				method: 'getstatus',
				parameters: 
				{
					subscribe: true
				},
			},
			function(response)
			{
				Mojo.Log.info ("ConnectionManager.connectionStatusNotification %j", response);
				self._1x = response.wan.network == "1x" && response.wifi.state != "connected";
				self._stateChanged((response.isInternetConnectionAvailable == true ? true : false));
				callback && callback(self._online);
				// callback = null;
			},
			function()
			{
				self._stateChanged(false);
				callback && callback(self._online);
				// callback = null;
			});
		}
	},
	
	_MSMnotification: function(response) 
	{	
		if (!response) return;
	
		Mojo.Log.info ("ConnectionManager._MSMnotification %j", response);
		if (response.stage == 'attempting') 
		{
			Mojo.Log.info ("ConnectionManager._MSMnotification: in MSM");
			this._MSMmodeActive = true;
			ConnectionWidget.cancel();
		}
	},

	_stateChanged: function(online)
	{
		if (this._online != online)
		{
			this._online = online;
			var waiting = this._waiting;
			this._waiting = [];
			for (var i = 0; i < waiting.length; i++) 
			{
				var wait = waiting[i];
				if (wait.online === undefined || wait.online === online) 
				{
					waiting[i].callback(online);
				}
				else
				{
					this._waiting.push(wait);
				}
			}
		}
		
		if (online == false)
		{
			this.showConnectionError();
		}
		else 
		{
			// if internet is available we are certainly not in MSM mode
			this._MSMmodeActive = false;
			ConnectionWidget.cancel();
		}
	},
	
	/*
	 * Enable monitoring of network status.
	 */
	monitor: function()
	{
		if (!this._started) 
		{
			this._startup();
		}
	},
	
	getStatus: function(callback)
	{
		if (this._online === undefined) 
		{
			if (!this._started) 
			{
				this._startup(callback);
			}
			else
			{
				this._waiting.push({ online: undefined, callback: callback });
			}
		}
		else 
		{
			callback(this._online);
		}
	},
	
	isOnline: function()
	{
		if (this._online === undefined)
		{
			throw new Error("ConnectionManager is not monitoring status");
		}
		else
		{
			return this._online;
		}
	},
	
	isOn1x: function()
	{
		return this._1x;
	},
	
	waitForOnline: function(callback)
	{
		if (this._online === true)
		{
			callback();
			return undefined;
		}
		else
		{
			var wait = { online: true, callback: callback };
			this._waiting.push(wait);
			return wait;
		}
	},
	
	waitForOffline: function(callback)
	{
		if (this._online === false)
		{
			callback();
			return undefined;
		}
		else
		{
			var wait = { online: false, callback: callback };
			this._waiting.push(wait);
			return wait;
		}
	},
	
	cancelWait: function(wait)
	{
		var waiting = this._waiting;
		for (var i = 0; i < waiting.length; i++)
		{
			if (waiting[i] == wait)
			{
				waiting.splice(i, 1);
				break;
			}
		}
	},
	
	showConnectionError: function() 
	{
		// don't show connection widget if we are in MSM mode
		if (this._MSMmodeActive) return;
		
		var stage = Weave.System.Activator.getActiveStageController();
		if (!stage) return;
		
		if (!stage.isActiveAndHasScenes()) return;
			
		Mojo.Log.info("ConnectionManager.showConnectionError");
		var params = 
		{
			type: "data",
			onSuccess: function(response){}
		};
		ConnectionWidget.connect(params, stage);
	},
	
	cleanup: function()
	{
		// close connection's widget stage if active
		ConnectionWidget.cancel();
		delete this._MSMrequest;
		delete this._getstatusreq;
	}
	
};/* Copyright 2009 Palm, Inc.  All rights reserved. */

Weave.Services.DeviceProfile = 
{
	_target: 'palm://com.palm.deviceprofile',
	
	getDeviceId: function(callback)
	{
		Weave.Services.request(this._target,
		{
			method: 'getDeviceId',
			parameters: {},
		},
		function(response)
		{
			callback(true, response.deviceId);
		},
		function()
		{
			callback(false);
		});
	},
	
	getCarrierIdentification: function(callback)
	{
		Weave.Services.request('palm://com.palm.data.carriernetworksettings/',
		{
			method: 'getCurrentCarrierIdentification',
			parameters: {},
		},
		function(response)
		{
			callback(true, response);
		},
		function()
		{
			callback(false);
		});
	}
};/* Copyright 2009 Palm, Inc.  All rights reserved. */

Weave.Services.AppInstallService = 
{
	_target: 'palm://com.palm.appInstallService',
	
	install: function(app, callback)
	{
		Weave.Services.AccountServices.getAccountToken(function(status, token)
		{
			Weave.Services.DeviceProfile.getDeviceId(function(status, id)
			{
				var params = 
				{
					catalogId: app.id,
					id: app.publicApplicationId,
					title: app.title,
					version: app.serverVersion,
					vendor: app.vendor,
					vendorUrl: app.vendorUrl,
					iconUrl: app.iconUrl,
					ipkUrl: app.packageUrl,
					authToken: token || 0,
					deviceId: id || 0,
				};
				Mojo.Log.info("Weave.Services.AppInstallService.install payload %j", params);
				var request = Weave.Services.request(Weave.Services.AppInstallService._target, 
				{
					method: 'install',
					parameters: params
				},
				function(response)
				{
					callback(true);
				},
				function(response)
				{
					Mojo.Log.error("Weave.Services.AppInstallService.install failed %s, %j", app.publicApplicationId, response);
					callback(false, response);
				});
			});
		});
	},
	
	status: function(callback)
	{
		Mojo.Log.info("Weave.Services.AppInstallService.status");
		var self = this;
		var request = Weave.Services.subscriptionRequest(this._target, 
		{
			method: 'status',
			parameters: {},
		},
		function(response)
		{
			Mojo.Log.info("Weave.Services.AppInstallService.status response %j", response);
			if (response.status && response.status.apps)
			{
				callback(true, false, response.status.apps);
			}
			else if (response.id)
			{
				callback(true, true, [response]);
			}
		},
		function(response)
		{
			Mojo.Log.error("Weave.Services.AppInstallService.status failed %j", response);
		});
	},
	
	/*
	retryInstall: function(publicApplicationId)
	{
		var self = this;
		var request = Weave.Services.request(this._target, 
		{
			method: 'retryInstall',
			parameters: {"id": publicApplicationId},
		},
		function(response){},
		function(response)
		{
			Mojo.Log.error("Weave.Services.AppInstallService.retryInstall failed %s, %j", publicApplicationId, response);
		});
	},
	*/
	
	pause: function(publicApplicationId, callback)
	{
		var self = this;
		var request = Weave.Services.request(this._target, 
		{
			method: 'pause',
			parameters: {"id": publicApplicationId},
		},
		function(response)
		{
			callback(true);
		},
		function(response)
		{
			Mojo.Log.error("Weave.Services.AppInstallService.pause failed %s, %j", publicApplicationId, response);
			callback(false, response);
		});
	},
	
	resume: function(publicApplicationId, callback)
	{
		var self = this;
		var request = Weave.Services.request(this._target, 
		{
			method: 'resume',
			parameters: {"id": publicApplicationId},
		},
		function(response)
		{
			callback(true);
		},
		function(response)
		{
			Mojo.Log.error("Weave.Services.AppInstallService.resume failed %s, %j", publicApplicationId, response);
			callback(false, response);
		});
	},
	
	cancel: function(publicApplicationId, callback)
	{
		var self = this;
		var request = Weave.Services.request(this._target, 
		{
			method: 'cancel',
			parameters: {"id": publicApplicationId},
		},
		function(response)
		{ 
			callback(true); 
		},
		function(response)
		{
			Mojo.Log.error("Weave.Services.AppInstallService.cancel failed %s, %j", publicApplicationId, response);
			callback(false, response); 
		});
	},
	
	remove: function(publicApplicationId, callback)
	{
		var self = this;
		var request = Weave.Services.request(this._target, 
		{
			method: 'remove',
			parameters: {"id": publicApplicationId},
		},
		function(response)
		{
			callback(true);
		},
		function(response)
		{
			callback(false, response); 
			Mojo.Log.error("Weave.Services.AppInstallService.remove failed %s, %j", publicApplicationId, response);
		});
	},
};/* Copyright 2009 Palm, Inc.  All rights reserved. */

Weave.Services.SystemManager =
{
	_target: 'palm://com.palm.systemmanager',
	_unlockcount: 0,
	
	_startup: function()
	{
		if (PalmSystem.version.match("desktop")) 
		{
			// Simulator is always unlocked
		}
		else 
		{
			var self = this;
			var first = true;
			this._getstatusreq = Weave.Services.request(this._target, 
			{
				method: 'getLockStatus',
				parameters: 
				{
					subscribe: true
				},
			},
			function(response)
			{
				if (!first) 
				{
					self._unlockcount++;
				}
				first = false;
			});
		}
	},
	
	hasScreenLocked: function(state, callback)
	{
		if (!this._getstatusreq)
		{
			this._startup(undefined, false);
		}
		if (state._count == undefined || state._count == this._unlockcount)
		{
			state.locked = false;
		}
		else
		{
			state.locked = true;
		}
		state._count = this._unlockcount;
		callback(true, state);
	},
	
};/* Copyright 2009 Palm, Inc.  All rights reserved. */

Weave.Services.Preferences = Weave.Services.Preferences || {};
Weave.Services.Preferences.SystemProperties = 
{
	_target: 'palm://com.palm.preferences/systemProperties',
	
	getCarrier: function(callback)
	{
		Weave.Services.request(this._target,
		{
			method: 'Get',
			parameters: { key: 'com.palm.properties.DMCARRIER' },
		},
		function(response)
		{
			callback(true, response['com.palm.properties.DMCARRIER']);
		},
		function()
		{
			callback(false);
		});
	}
};/* Copyright 2009 Palm, Inc.  All rights reserved. */

Weave.Services.PaymentServer = new (Class.create(Weave.Services.CatalogServer,
{    
    $whenReadyServerUrl: function(callback)
	{	
		if (this._serverUrl) 
		{
			callback();
		}
		else 
		{
			// We need the payment server url and the carrier info
			var self = this;
			Weave.Services.AccountServices.getPaymentServerUrl(function(status, url)
			{
				self._serverUrl = status ? url : 'error:///';
				callback();
			});
		}
    },

    getCCTypes: function(binCountry, callback)
	{
        var self = this;
        this.getSecurityToken(function(token)
		{
            var ingetCCTypes = 
			{
                InGetCCTypes: 
				{
                    authToken: token.token,
                    accountAlias: token.email,
                    deviceId: token.deviceId,
                    billToCountry: binCountry
                }
            };
            self._callServer('getCCTypes', ingetCCTypes, function(status, response, extra)
			{
				if (status) 
				{
					callback(true, response);
				}
				else if (response == "jsonexception")
				{
					callback(false, extra);
				}
				else
				{
					callback(false, { errorCode: response });
				}
            });
        });
        
    },
    
    setInvoiceEmail: function(emailAdd, callback)
    {
        var self = this;
        this.getSecurityToken(function(token)
        {
            var insetUserInfo = 
            {
                InSetUserInfo: 
                {
                    authToken: token.token,
                    accountAlias: token.email,
                    deviceId: token.deviceId,
                    invoiceEmail: emailAdd
                }
            };
            self._callServer('setUserInfo', insetUserInfo, function(status, response, extra)
            {
                if (status)
                {
                    callback(true, response);
                }
                else if (response == "jsonexception")
                {
                   	callback(false, extra);
                }
                else 
                {
                    callback(false, {
                        errorCode: response
                     });
               	}
            });
        });
        
    },
    
    getBillToCountries: function(callback)
    {
        var self = this;
        this.getSecurityToken(function(token)
        {
            var ingetBillToCountries = 
            {
                InGetBillToCountries: 
                {
                    authToken: token.token,
                    accountAlias: token.email,
                    deviceId: token.deviceId
                }
            };
           
            self._callServer('getBillToCountries', ingetBillToCountries, function(status, response, extra)
            {
            	if (status) 
            	{
            		callback(true, response);
            	}
            	else if (response == "jsonexception")
            	{
            		callback(false, extra);
            	}
            	else
            	{
            		callback(false, { errorCode: response });
            	}
             });
        });
        
    },
    
    verifyPaymentSetup: function(callback)
	{
        var self = this;
        this.getSecurityToken(function(token)
		{
            var inGetCCPaymentInfos = 
			{
                InGetCCPaymentInfos: 
				{
                    authToken: token.token,
                    accountAlias: token.email,
                    deviceId: token.deviceId
                }
            };
            
            self._callServer('getCCPaymentInfos', inGetCCPaymentInfos, function(status, response, extra)
            {
                if (status) 
                {
                    if (!response.OutGetCCPaymentInfos.invoiceEmail) 
                        response.OutGetCCPaymentInfos.invoiceEmail = token.email;
                    callback(true, response);
                }
                else if (response == "jsonexception") 
                {
                    callback(false, extra);
                }
                else 
                {
                    callback(false,{
                            errorCode: response
                        });
                }
            });
        });
    },
	
    addAccount: function(address, ccInfo, callback)
	{
        var self = this;
		
        this.getSecurityToken(function(token)
		{
            //ccInfo.email = token.email;
            var accountInfo = 
			{
                InAddCCPaymentInfo: 
				{
                    authToken: token.token,
                    accountAlias: token.email,
                    deviceId: token.deviceId,
                    billTo: address,
                    creditCard: ccInfo
                }
            };
 
            self._callServer('addCCPaymentInfo', accountInfo, function(status, response, extra)
			{
				if (status) 
				{
					if (response.OutAddCCPaymentInfo) 
					{
						callback(true,response);
					} 
					else 
					{
						callback(false,'badresponse');
					}
				} 
				else if (response == "jsonexception")
				{
					callback(false, extra);
				}
				else
				{
					callback(false, { errorCode: response });
				}
            });
        });
		
	},
	
	updateAccount: function(address, ccInfo, callback)
	{
        var self = this;
		
        this.getSecurityToken(function(token)
		{
            ccInfo.email = token.email;
            var accountInfo = 
			{
                InUpdateCCPaymentInfo: 
				{
                    authToken: token.token,
                    accountAlias: token.email,
                    deviceId: token.deviceId,
					paymentInfoId : ccInfo.paymentInfoId,
                    billTo: address,
                    creditCard: ccInfo
                }
            };
			
            self._callServer('updateCCPaymentInfo', accountInfo, function(status, response, extra)
			{
				if (status) 
				{
					if (response.OutUpdateCCPaymentInfo) 
					{
						callback(true,response);
					} 
					else 
					{
						callback(false,'badresponse');
					}
				} 
				else if (response == "jsonexception")
				{
					callback(false, extra);
				}
				else
				{
					callback(false, { errorCode: response });
				}
            });
            
        });
    },
	
	removeAccount: function(ccInfo, callback)
	{
        var self = this;
        this.getSecurityToken(function(token)
		{
            ccInfo.email = token.email;
            var accountInfo = 
			{
                InRemoveCCPaymentInfo: 
				{
                    authToken: token.token,
                    accountAlias: token.email,
                    deviceId: token.deviceId,
					paymentInfoId : ccInfo.paymentInfoId
                }
            };
            
            self._callServer('removeCCPaymentInfo', accountInfo, function(status, response, extra)
			{
				if (status) 
				{
					if (response.OutRemoveCCPaymentInfo) 
					{
						callback(true, response);
					} 
					else 
					{
						callback(false,'badresponse');
					}
				} 
				else if (response == "jsonexception")
				{
					callback(false, extra);
				}
				else
				{
					callback(false, { errorCode: response });
				}
            });
            
        });
    },

	capturePayment: function(orderObj, callback)
	{
        var self = this;
        this.getSecurityToken(function(token)
		{
            var orderInfo = 
			{
                InCapturePayment: 
				{
                    authToken: token.token,
                    accountAlias: token.email,
                    deviceId: token.deviceId,
					order: orderObj
                }
            };
            
            self._callServer('capturePayment', orderInfo, function(status, response, extra)
			{
				if (status) 
				{
					if (response.OutCapturePayment) 
					{
						callback(true, response);
					} 
					else 
					{
						callback(false, 'badresponse');
					}
				}
				else if (response == "jsonexception")
				{
					callback(false, extra);
				}
				else
				{
					callback(false, { errorCode: response });
				}
            });
        });
    },
	
	getEmbargoedEmailExtensions: function(callback)
	{
        var self = this;
        this.getSecurityToken(function(token)
		{
            var inGetEmbargoedEmailExtensions = 
			{
                InGetEmbargoedEmailExtensions: 
				{
                    authToken: token.token,
                    accountAlias: token.email,
                    deviceId: token.deviceId
                }
            };
			
			self._callServer('getEmbargoedEmailExtensions', inGetEmbargoedEmailExtensions, function(status, response, extra)
			{
				if (status) 
				{
					if (response.OutGetEmbargoedEmailExtensions) 
					{	
						//callback(true,{"OutGetEmbargoedEmailExtensions":{"embargoedEmailExtensions":["cu", "ir", "kp", "sd", "sy"]}});
						callback(true, response);
					} 
					else 
					{
						callback(false, 'badresponse');
					}
				}
				else if (response == "jsonexception")
				{
					callback(false, extra);
				}
				else
				{
					callback(false, { errorCode: response });
				}
            });
        });
    },
}));
/* Copyright 2009 Palm, Inc.  All rights reserved. */

Weave.Utilities.RegExp = 
{
	escape: function(str)
	{
		return str.replace(/[-[\]{}()*+?.\\^$|,#\s]/g, "\\$&");
	}
};
/* Copyright 2009 Palm, Inc.  All rights reserved. */

Weave.Utilities.AppMenu = Class.create(
{
	initialize: function(scene, inherit)
	{
		this._scene = scene;
		this._items = inherit ? inherit._items : [];
		if (scene) 
		{
			this._oldHandleCommand = scene.handleCommand;
			scene.handleCommand = this._handleCommand.bind(this);
			if (scene.controller.setupWidget) 
			{
				scene.controller.setupWidget(Mojo.Menu.appMenu, 
				{
					omitDefaultItems: true,
				}, 
				{
					items: this._items
				});
			}
		}
	},
	
	addItem: function(label, callback, enabled)
	{
		this._items.push({ label: label, command: 'cmd' + this._items.length + 1, checkEnabled: (enabled ? true : false), _callback: callback, _enabled: enabled });
		return this;
	},
	
	addEdit: function()
	{
		this._items.push(Mojo.Menu.editItem);
		return this;
	},
	
	addHelp: function(url, enabled)
	{
		if (url) 
		{
			this._items.push(Object.extend(
			{
				_callback: function()
				{
					Weave.Services.ConnectionManager.getStatus(function(online)
					{
						Weave.Services.ApplicationManager.openApplication('com.palm.app.help', 
						{
							target: online ? url : 'no-network'
						});
					});
				},
				_enabled: enabled ? enabled : function(event)
				{
					event.stopPropagation();
					return true;
				}
			}, Mojo.Menu.helpItem));
		}
		else
		{
			this._items.push(Mojo.Menu.helpItem);
		}
		return this;
	},
	
	addPreferences: function(callback, enabled)
	{
		if (callback) 
		{
			this._items.push(Object.extend(
			{
				_callback: callback,
				_enabled: enabled ? enabled : function(event)
				{
					event.stopPropagation();
					return true;
				}
			}, Mojo.Menu.prefsItem));
		}
		else
		{
			this._items.push(Mojo.menu.prefsItem);
		}
		return this;
	},
	
	_handleCommand: function(event)
	{
		if (event.type == Mojo.Event.commandEnable)
		{
			for (var i = 0; i < this._items.length; i++)
			{
				if (this._items[i].command == event.command)
				{
					if (this._items[i]._enabled && !this._items[i]._enabled(event)) 
					{
						event.stopPropagation();
						event.preventDefault();
					}
					break;
				}
			}
		}
		else if (event.type == Mojo.Event.command)
		{
			for (var i = 0; i < this._items.length; i++)
			{
				if (this._items[i].command == event.command)
				{
					this._items[i]._callback && this._items[i]._callback(event);
					break;
				}
			}
		}
		this._oldHandleCommand && this._oldHandleCommand.call(this._scene, event);
	},
	
});

Object.extend(Weave.Utilities.AppMenu,
{
	setDefault: function(menu)
	{
		Weave.Utilities.AppMenu.defaultMenu = menu;
	},
	
	useDefault: function(scene)
	{
		new Weave.Utilities.AppMenu(scene, Weave.Utilities.AppMenu.defaultMenu);
	}
});
/* Catalog.appStates
 * 
 * Global objects that represent all possible states for
 * a catalog application's download process.
 * 
 * State and Flyweight patterns combined.
 * That is, State objects are flyweight.
 * 
 * All per-download information like application id, price, location etc
 * is saved in an instance of appdownload object. State objects are shared
 * between all appdownload instances. They contain no extrinsic state.
 * 
 * Client (e.g. details scene) obtains an instance of appdownload object 
 * from the appdownloadmanager. This will be either an existing download or 
 * new download object. Client then makes calls on this download object for example:
 * 
 * appdownload->install
 * 
 * appdownload object delegates the call to it's current state passing 
 * itself as an argument:
 * 
 * appdownload::install 
 * {
 * 		this->_currentState->install(this);
 * }
 * 
 * 
 * Client subscribes as a listener on appdownload object.
 * When state of the download changes, appdownload object notifies 
 * all its current listeners which can then update their UI from the 
 * download object model.
 * 
 * 
 * State interface:
 * 
 * 
 * 
	// initializes app's internal data based on the current state
	// for example, progress pill model and css classes used in MyApps scene 
	// are changed with every state change 
	init(app)
	
	// updates internal data from app details
	// returned from the server. For example: if app is currently in
	// installed state, refreshing data from the server might cause it
	// to switch to "installed update available" state if server's version is 
	// higher 
	updateFromServer(app)
	
	// updates internal state from app details
	// returned from palm://com.palm.applicationManager/listApps. 
	// For example: if app was in "dummy" state this will cause it
	// to switch to "installed" state 
	updateFromInstalledAppsList(app)
	
	// called by details scene when user taps the progress pill.
	// Each state defines this method and depending on the current state 
	// of the object different thing will happen. If app is installed, 
	// it will be launched, if it's downloading it will be paused etc 
	defaultAction(app)
	
	// called by myapps scene when user taps the list item icon.
	// Each state defines this method and depending on the current state 
	// of the object different thing will happen. if app is downloading 
	// it will be paused etc 
	myAppsDefaultAction(app)
	
	// canceles download
	// only some states define this method (e.g "download progress")
	cancelDownload: function(app)
	
	// uninstalls the application
	// only some states define this method, (e.g "installed" state)
	uninstall: function(app)
	
	// resets the state of the object usually on error
	// it will forget about the current error and return the object back to
	// "download", "purchased" or "installed" state
	_reset: function(app)
	
	// resets the state of the object upon uninstalled notification
	_remove: function(app)
	
	// installs the application
	install: function(app)
	
	// returns true if download object in the current state should
	// be saved to myapps list maintained by appdownloadmanager.
	// appdownloadmanager listens for state changes on all existing
	// download objects and decides which ones should be saved to myApps list.
	// The general rule is: any downloads in progress + installed applications
	saveToMyApps: function()
	
	// returns true if download object in the current state should
	// be removed from myapps list maintained by appdownloadmanager.
	// appdownloadmanager listens for state changes on all existing
	// download objects and decides which ones should be saved/removed to/from myApps list.
	// The general rule is: any downloads in progress + installed applications
	// if user decides to give up on a download (due to an error for example)
	// app will be reset back to "download" state and removed from myApps list
	removeFromMyApps: function()
	
	// returns current state's string identifier
	toString: function()
	
	
*/


var Catalog 		= Catalog || {}; 
Catalog.appStates 	= Catalog.appStates || new Array();


// common functions, can be called from multiple states
Catalog.appStatesCommon = {
						
	_1xCarrierDialog: 
	{
		"sprint": {title: $L("No 3G Data Network"), message: $L("You will be unable to receive phone calls while the application is downloading."), choices: [{label: $L("Cancel"), value: "cancel", type: 'secondary'}, { label: $L("Download"), value: "download", type: 'primary'}]},
		"default": {title: $L("No 3G Data Network"), message: $L("Because no high speed network connection is available, it will take much longer to download this application. You can download the application later for free."), choices: [{label: $L("Download Later"), value: "cancel", type: 'secondary'}, { label: $L("Download Now"), value: "download", type: 'primary'}]},
	},
		
	_verifyPaymentSetup: function(callback)
	{
		if (myProfile.validCC !== undefined)
		{
			callback(true, myProfile.validCC);
		}
		else
		{
			// another state while checking payment setup?
			// but that step is not very critical, so what if we check it twice
			Weave.Services.PaymentServer.verifyPaymentSetup(function(status, response)
			{
				if (status) 
				{
					myProfile.validCC = response;
				}
				callback(status, response);
			});
		}
	},
	
	_makePurchase: function(app, response, callback) 
	{
		if (!Weave.Services.ConnectionManager.isOnline()) 
		{
			callback(false);
			return;	
		}
		
		var stage = Weave.System.Activator.getActiveStageController();
		if (stage && stage.topScene()) 
		{
			stage.topScene().showAlertDialog(
			{
				allowHTMLMessage: true,
				message: $L('Purchase #{title}?<br/><br/>Your credit card will be charged.').interpolate({title: app.title}),
				choices:
				[
					{ label: $L("Purchase"), value: "ok", type: 'affirmative' }, 
					{ label: $L("Cancel"), value: "cancel", type: 'dismiss'}
				],
				onChoose: function(value)
				{
					if (value == "ok") 
					{
						Mojo.Log.info("Catalog.appStatesCommon._makePurchase: purchasing %s, %s", app.title, app.id)
						// User has validate the purchase - we make it now.
						var d = app;
						var orderObj =
						{
							paymentInfoId: response.OutGetCCPaymentInfos.ccPaymentInfos[0].paymentInfoId,
							items:
							[{
								type: d.priceType,
								quantity: "1",
								sku: d.sku
							}],
						};
						
						app.setState("purchasing");
						Mojo.Log.info("Order Object: %j", orderObj.items[0]);
						Weave.Services.PaymentServer.capturePayment(orderObj, function(status, response)
						{
							// testing
							// status = false; 
							// response.errorCode = "PMT04004";
							
							if (status)
							{
								// Payment successful - download
								Mojo.Log.info("Catalog.appStatesCommon._makePurchase: purchased");
								app.setState("purchased", {version: app.serverVersion, transitional: true});
								callback(true);
							}
							else
							{
								Mojo.Log.error("Catalog.appStatesCommon._makePurchase: purchase failed %j", response);
								var err = response.errorCode ? response.errorCode : response;
								app.setState("purchase_failed", {errorCode: err});
							}
						});
					}
					else 
					{
						callback(false);
					}
				},
			});
		}
		else
		{
			callback(false);	
		}
	},
	
	// set only the current state of this function, "validating_cc"
	// in case of error it's up to the caller to restore previous state
	_handleVerifyPayment: function(app, valid, callback)
	{	
		Catalog.appStatesCommon._verifyPaymentSetup(function(status, response) 
		{
			if (status) 
			{
				if (response.OutGetCCPaymentInfos.ccPaymentInfos.length > 0) 
				{	
					// check if user needs to enter password
					if (!valid) 
					{	
						var stage = Weave.System.Activator.getActiveStageController();
						if (stage && stage.topScene()) 
						{
							stage.topScene().showDialog(
							{
								template: 'payment-setup/password-dialog',
								assistant: new PasswordAssistant(stage.topScene().assistant, 
								{
									appid: app.id, 
									onComplete: function(info)
									{
										Mojo.Log.info("Catalog.appStatesCommon._handleVerifyPayment password valid: ", info.passwordValid);
										if (info.passwordValid)
										{
											Catalog.appStatesCommon._makePurchase(app, response, callback);
										}
										else
										{
											callback(false);
										}
									}
								})
							});
						}
						else
						{
							callback(false);
						}
					}
					else 
					{
						Catalog.appStatesCommon._makePurchase(app, response, callback);
					}
				} 
				else 
				{
					// forward to set up credit card account
					Mojo.Log.info("Catalog.appStatesCommon._handleVerifyPayment no payment set up");
					var stage = Weave.System.Activator.getActiveStageController();
					if (stage) 
					{
						stage.pushScene("payment-setup", app.id, function(ret)
						{
							Mojo.Log.info("Catalog.appStatesCommon._handleVerifyPayment payment setup callback ret %j", ret);
							if (ret.accountValid) 
							{
								Catalog.appStatesCommon._handleVerifyPayment(app, true, callback);
							}
							else 
							{
								callback(false);
							}
						});
					}
					else 
					{
						callback(false);
					}
				}
			} 
			else 
			{
				callback(false);
				Mojo.Log.error("Error retrieving payment setup");
				Utilities.Errors.displayError(response.errorCode, {errCode: response.errorCode}, "PMT_catchAll_default");
			}
		});				
    },
	
	_validateRegion: function(app) 
	{
		Mojo.Log.info("Catalog.appStatesCommon._validateRegion uscarrier: ", myProfile.uscarrier)
		if (app.price > 0 && !myProfile.uscarrier)
		{
			var stage = Weave.System.Activator.getActiveStageController();
			if (stage && stage.topScene()) 
			{
				stage.topScene().showAlertDialog({
					title: $L('Sorry, Application Unavailable'),
					message: $L('This application is not available in your region.'),
					choices: [{
						label: $L("OK"),
						value: true,
						type: 'primary'
					}, ]
				});
			}
			return false;
		}
		return true;
	},
	
	_handleEmbargoAcc: function(app ,callback){
		if(myProfile.isEmbargoed)
		{
			if (app.price > 0)
			{
				Utilities.Errors.displayError("PMT_cant_purchase", {}, null, null, null, function(value)
				{
	                if (value == 'help') {
	                    Weave.Services.ConnectionManager.getStatus(function(online){
	                        Weave.Services.ApplicationManager.openApplication('com.palm.app.help', {
	                            target: online ? 'http://help.palm.com/app_catalog/appcatalog_download_error.html' : 'no-network'
	                        });
	                    });
	                    
	                }
	            });
			}
			else
				Utilities.Errors.displayError("PMT_cant_download_encrypted");
			callback(false);
		}
		else
			callback(true);
	},
	
	_checkNotEmbargoed: function(app, callback)
	{
		if (app.price > 0 || app.isEncrypted) {
			if (myProfile.isEmbargoed !== undefined) {
				this._handleEmbargoAcc(app, callback);
			}
			else {
				var ext = myProfile.email.substring(myProfile.email.lastIndexOf(".") + 1);
				
				if (AppAssistant.embargoedList) {
					myProfile.isEmbargoed = AppAssistant.embargoedList.indexOf(ext) != -1;
					this._handleEmbargoAcc(app, callback);
				}
				else {
					var self = this;
					Weave.Services.PaymentServer.getEmbargoedEmailExtensions(function(status, response){
						Mojo.Log.info("getEmbargoedCountryList %j", response);
						if (status) {
							AppAssistant.embargoedList = response.OutGetEmbargoedEmailExtensions.embargoedEmailExtensions;
							myProfile.isEmbargoed = AppAssistant.embargoedList.indexOf(ext) != -1;
							self._handleEmbargoAcc(app, callback);
						}
						else {
							var err = response.errorCode ? response.errorCode : response;
							Utilities.Errors.displayError(err, {
								errCode: err
							}, "PMT_catchAll_default");
						}
					});
				}
			}
		}
		else
			callback(true);
	},
	
	_handleLocationServices: function(app, callback)
	{
		Mojo.Log.info("Catalog.appStatesCommon._handleLocationServices app.islocationbased", app.islocationbased);
		if (app.islocationbased)
		{
			var stage = Weave.System.Activator.getActiveStageController();
			if (stage && stage.topScene()) 
			{
				stage.topScene().showAlertDialog({
					onChoose: function(value) {
						if (value == "continue") {
							callback(true);
						}
						else {
							callback(false);
						}
					},
					title: $L('Location Services'),
					message: $L('This application will request your current location for some functions.'),
					choices: [{
						label: $L("Don't Download"),
						value: "cancel",
						type: 'dismiss'
					}, {
						label: $L("Continue"),
						value: "continue",
						type: 'affirmative'
					}]
				});
			}
			else
			{
				callback(false);
			}
		}
		else
		{
			callback(true);
		}
	},
	
	validateInstallSpace: function(app, showError, callback)
	{
		Mojo.Log.info("Catalog.appStatesCommon.validateInstallSpace");
		
		Weave.Services.ApplicationInstaller.validateInstall(app.publicApplicationId, app.packageSize, app.installSize, function(status, response)
		{
			if (status == true)
			{
				callback(true);
			}
			else
			{
				Mojo.assert(response && response.spaceNeededInKB, "Catalog.appStatesCommon.validateInstallSpace validateInstall failed but spaceNeededInKB is not defined");

				var totalInstallSize;
				if (response && response.spaceNeededInKB) 
				{
					totalInstallSize = parseInt(response.spaceNeededInKB);
					totalInstallSize = totalInstallSize >= 1024 ? Math.ceil((totalInstallSize/1024)*100)/100 + "M" : totalInstallSize + "K";
				}
				else 
				{
					totalInstallSize = $L("unknown MB");
				}
			
				Utilities.Errors.displayError("dummy", {installSize: totalInstallSize}, showError, null, null, function(value)
				{
                	if (value == 'help')
                    {
                        Weave.Services.ConnectionManager.getStatus(function(online)
                        {
                            Weave.Services.ApplicationManager.openApplication('com.palm.app.help',
                            {
                                target: online ? 'http://help.palm.com/basics/manage_applications/basics_delete_app_from_launcher.html' : 'no-network'
                            });
                        });
                    }
	        	});
				
				callback(false);
			}
		});
	},
	
	validateDownloadConnection: function(callback)
	{
		if (Weave.Services.ConnectionManager.isOn1x()) 
		{
			Mojo.Log.info("Catalog.appStatesCommon.validateDownloadConnection connection is 1x");
			if (Catalog.AppDownloadMngr.canAllow1xDownload())
			{
				// user already decided to allow 1x download
				Mojo.Log.info("Catalog.appStatesCommon.validateDownloadConnection canAllow1xDownload == true")
				callback(true);
			}
			else 
			{
				Mojo.Log.info("Catalog.appStatesCommon.validateDownloadConnection prompt user for 1x permission")
				var stage = Weave.System.Activator.getActiveStageController();
				if (stage && stage.topScene()) 
				{
					// get correct dialog based on carrier
					var dialog = Catalog.appStatesCommon._1xCarrierDialog[myProfile.carrier];
					if (!dialog) 
						dialog = Catalog.appStatesCommon._1xCarrierDialog["default"];
						
					stage.topScene().showAlertDialog(
					{
						onChoose: function(value) 
						{
							if (value == "cancel") 
							{
								callback(false);
							}
							else if (value == "download") 
							{
								Catalog.AppDownloadMngr.allow1xDownload(true, function(status)
								{
									Mojo.Log.info("Catalog.appStatesCommon.validateDownloadConnection allow1xDownload returned status %s", status)
									callback(status);	
								});
							}
						},
						title: dialog.title,
						message: dialog.message,
						choices: dialog.choices
					});
				}
				else 
				{
					callback(false);
				}
			}
		}
		else if (Weave.Services.ConnectionManager.isOnline())
		{
			callback(true);
		}
		else 
		{
			callback(false);
		}
	},
	
	_install: function(app)
	{
		Catalog.appStatesCommon.validateDownloadConnection(function(status)
		{ 
			var oldState = app.stateToString();
			if (status)
			{
				app.setState("fake progress");
				
				Mojo.Log.info("Catalog.appStatesCommon._install: ", app.publicApplicationId);
				Weave.Services.AppInstallService.install(app, function(status, response)
				{
					if (!status)
					{
						Mojo.Log.error("Catalog.appStatesCommon._install failed %s: %j, returing to state %s", app.publicApplicationId, response, oldState);
						// if we are still waiting revert to old state
						if (app.stateToString() == "fake progress")
							app.setState(oldState);
					}
				});
			}
			/*
			else 
			{
				// force refresh
				if (app.stateToString() == oldState)
					app.setState(oldState);
			}
			*/
		});
	},
	
	_pause: function(app)
	{
		var oldState = app.stateToString();
		app.setState("pausing");
				
		Mojo.Log.info("Catalog.appStatesCommon._pause: ", app.publicApplicationId);
		Weave.Services.AppInstallService.pause(app.publicApplicationId, function(status, response)
		{
			if (!status)
			{
				Mojo.Log.error("Catalog.appStatesCommon._pause failed %s, %j, returing to state %s", app.publicApplicationId, response, oldState);
				// if we are still waiting revert to old state
				if (app.stateToString() == "pausing")
					app.setState(oldState);
			}
		});
	},
	
	_resume: function(app)
	{
		Catalog.appStatesCommon.validateDownloadConnection(function(status)
		{
			if (status)
			{
				var oldState = app.stateToString();
				app.setState("resuming");
				
				Mojo.Log.info("Catalog.appStatesCommon._resume: ", app.publicApplicationId);
				Weave.Services.AppInstallService.resume(app.publicApplicationId, function(status, response)
				{
					if (!status)
					{
						Mojo.Log.error("Catalog.appStatesCommon._resume failed %s, %j, returing to state %s", app.publicApplicationId, response, oldState);
						// if we are still waiting revert to old state
						if (app.stateToString() == "resuming")
							app.setState(oldState);
					}
				});
			}
		});
	},
	
	_cancel: function(app)
	{
		var oldState = app.stateToString();
		app.setState("canceling");
		
		Mojo.Log.info("Catalog.appStatesCommon._cancel: ", app.publicApplicationId);
		Weave.Services.AppInstallService.cancel(app.publicApplicationId, function(status, response)
		{
			if (!status)
			{
				Mojo.Log.error("Catalog.appStatesCommon._cancel failed %s, %j, returing to state %s", app.publicApplicationId, response, oldState);
				// if we are still waiting revert to old state
				if (app.stateToString() == "canceling")
					app.setState(oldState);
			}
		});	
	},
	
	_uninstall: function(app)
	{
		var oldState = app.stateToString();
		app.setState("removing");
		
		Mojo.Log.info("Catalog.appStatesCommon._uninstall: ", app.publicApplicationId);
		Weave.Services.AppInstallService.remove(app.publicApplicationId, function(status, response)
		{
			if (!status)
			{
				Mojo.Log.error("Catalog.appStatesCommon._uninstall failed %s, %j, returing to state %s", app.publicApplicationId, response, oldState);
				// if we are still waiting revert to old state
				if (app.stateToString() == "removing")
					app.setState(oldState);
			}
		});
	},
	
	_reset: function(app)
	{
		if (app.installedVersion)
		{
			// we were installing an update
			if (app.serverVersion && Utilities.VersionCheck.compare(app.installedVersion, app.serverVersion) == -1) 
			{
				app.setState("installed update available");
			}
			else 
			{
				app.setState("installed");
			}
		}
		else if (app.price && app.price > 0) 
		{
			app.setState("purchased");
		}
		else
		{
			app.setState("download");
		}
	},
	
	_remove: function(app)
	{
		app.installedVersion = null;
		app.icon = null;
		
		if (app.price && app.price > 0) 
		{
			app.setState("purchased");
		}
		else
		{
			app.setState("download");
		}
	},
};

// use dummy until we find out the correct state
Catalog.appStates["dummy"] = {
	
	init: function(app, args) 
	{
		app._progressPillModel.titleRight = undefined;
		app._progressPillModel.icon = 'none';
		app._progressPillModel.image = undefined;
		app._progressPillModel.value = undefined;
		app._progressPillModel.title = $L('Getting data...');
		
		app.disabledClass = null;
		app.updateClass = null;
		app.activeClass = null; 
		app.resumeClass = null;
		app.pauseClass = null;
		app.warningClass = null;
	},
	
	updateFromServer: function(app) 
	{	
		this._update(app);
	},
	
	updateFromInstalledAppsList: function(app)
	{
		this._update(app);
	},
	
	_update: function(app)
	{
		var state = "download";
		
		if (app.purchasedVersion)
		{
			state = 'purchased';
		}
		
		if (app.installedVersion) 
		{
			state = 'installed';
			if (app.serverVersion && Utilities.VersionCheck.compare(app.installedVersion, app.serverVersion) == -1) 
			{
				state = "installed update available";
			}
		}
		
		app.setState(state);
	},
	
	toString: function()
	{
		return "dummy";
	}
};

/*
 * Transitional states
 * 
 * object is in a transitional state while we
 * are waiting for a response from appInstallService. While object is in
 * these states user can't perform almost any action (except deleting an app)
 * transitional states: pausing, deleting, resuming, canceling, fake progress
 *  
 */
Catalog.appStates["fake progress"] = {
	
	init: function(app) 
	{
		app._progressPillModel.titleRight = undefined;
		app._progressPillModel.icon = 'action-icon';
		app._progressPillModel.image = 'images/download-icon.png';
		app._progressPillModel.title = $L('Downloading...');
		app._progressPillModel.value = 0;
		
		app.disabledClass = "disabled";
		app.updateClass = null;
		app.activeClass = "active"; 
		app.resumeClass = null;
		app.pauseClass = null;
		app.warningClass = null;
	},

	_reset: function(app)
	{
		Catalog.appStatesCommon._reset(app);
	},
	
	_remove: function(app)
	{
		Catalog.appStatesCommon._remove(app);
	},
	
	saveToMyApps: function()
	{
		return true;
	},

	toString: function()
	{
		return "fake progress";
	}
};

Catalog.appStates["pausing"] = {
	
	init: function(app) 
	{
		app._progressPillModel.titleRight = undefined;
		app._progressPillModel.icon = 'none';
		app._progressPillModel.image = undefined;
		app._progressPillModel.title = $L('Pausing...');
		
		app.disabledClass = "disabled";
	},
	
	// after we send pause request we are in 
	// "pausing..." state, at that point we ignore additional 
	// progress updates that come before the final "paused" state
	_allowTransition: function(newState)
	{
		if (newState == "download progress")
			return false;
		
		return true;
	},
	
	_reset: function(app)
	{
		Catalog.appStatesCommon._reset(app);
	},
	
	_remove: function(app)
	{
		Catalog.appStatesCommon._remove(app);
	},
	
	saveToMyApps: function()
	{
		return true;
	},

	toString: function()
	{
		return "pausing";
	}
};

Catalog.appStates["resuming"] = {
	
	init: function(app) 
	{
		app._progressPillModel.titleRight = undefined;
		app._progressPillModel.icon = 'none';
		app._progressPillModel.image = undefined;
		app._progressPillModel.title = $L('Resuming...');
		
		app.disabledClass = "disabled";
	},
	
	_reset: function(app)
	{
		Catalog.appStatesCommon._reset(app);
	},
	
	_remove: function(app)
	{
		Catalog.appStatesCommon._remove(app);
	},
	
	saveToMyApps: function()
	{
		return true;
	},

	toString: function()
	{
		return "resuming";
	}
};

Catalog.appStates["removing"] = {
	
	init: function(app) 
	{
		app._progressPillModel.titleRight = undefined;
		app._progressPillModel.icon = 'none';
		app._progressPillModel.image = undefined;
		app._progressPillModel.title = $L('Deleting...');
		app._progressPillModel.value = 1;
		
		app.disabledClass = "disabled";
		app.updateClass = null;
		app.activeClass = null; 
		app.resumeClass = null;
		app.pauseClass = null;
		app.warningClass = null;
	},
	
	// after we send remove request we are in 
	// "removing..." state, at that point we ignore additional 
	// progress updates from appInstallService 
	// that come before the final "removed" state
	_allowTransition: function(newState)
	{
		if (newState == "download progress")
			return false;
		
		return true;
	},
	
	_reset: function(app)
	{
		Catalog.appStatesCommon._reset(app);
	},
	
	_remove: function(app)
	{
		Catalog.appStatesCommon._remove(app);
	},
	
	saveToMyApps: function()
	{
		return true;
	},

	toString: function()
	{
		return "removing";
	}
};

Catalog.appStates["canceling"] = {
	
	init: function(app) 
	{
		app._progressPillModel.titleRight = undefined;
		app._progressPillModel.icon = 'none';
		app._progressPillModel.image = undefined;
		app._progressPillModel.title = $L('Canceling...');
		
		app.disabledClass = "disabled";
	},
	
	// after we send cancel request we are in 
	// "canceling..." state, at that point we ignore additional 
	// progress updates that come before the final "canceled" state
	_allowTransition: function(newState)
	{
		if (newState == "download progress")
			return false;
		
		return true;
	},
	
	_reset: function(app)
	{
		Catalog.appStatesCommon._reset(app);
	},
	
	_remove: function(app)
	{
		Catalog.appStatesCommon._remove(app);
	},
	
	saveToMyApps: function()
	{
		return true;
	},

	toString: function()
	{
		return "canceling";
	}
};



// default / starting state for all apps
// once we obtain app details from the server
Catalog.appStates["download"] = {
	
	init: function(app, args) 
	{
		app._progressPillModel.titleRight = undefined;
		app._progressPillModel.icon	= 'download-app-icon';
		app._progressPillModel.image = undefined;
		app._progressPillModel.value = undefined;
		app._progressPillModel.title = $L('Download for #{price}').interpolate({price:app.getFormattedPrice()});
		
		app.progress = 0;
		app.disabledClass = null;
		app.updateClass = null;
		app.activeClass = null; 
		app.resumeClass = null;
		app.pauseClass = null;
		app.warningClass = null;
	},
	
	updateFromInstalledAppsList: function(app)
	{
		if (app.installedVersion) 
		{
			if (app.serverVersion && Utilities.VersionCheck.compare(app.installedVersion, app.serverVersion) == -1) 
			{
				app.setState("installed update available");
			}
			else 
			{
				app.setState("installed");	
			}
		}
	},
	
	updateFromServer: function(app) 
	{	
		var state = "download";
		
		if (app.purchasedVersion)
		{
			state = 'purchased';
		}
		
		if (app.installedVersion) 
		{
			state = 'installed';
			if (Utilities.VersionCheck.compare(app.installedVersion, app.serverVersion) == -1) 
			{
				state = "installed update available";
			}
		}
		
		app.setState(state);
	},
	
	removeFromMyApps: function()
	{
		return true;
	}, 
	
	defaultAction: function(app) 
	{
		app.install();
	},
	
	install: function(app) 
	{
		Catalog.appStatesCommon._checkNotEmbargoed(app, function(status)
		{
			if(status)
			{
				Catalog.appStatesCommon._handleLocationServices(app, function(status)
				{
					if (status) 
					{
						Catalog.appStatesCommon.validateInstallSpace(app, "validate_space_default_single", function(status)
						{
							if (status) 
							{
								Catalog.appStatesCommon.validateDownloadConnection(function(status)
								{
									if (status)
									{ 
										if (app.price > 0) 
										{
											Catalog.appStatesCommon._handleVerifyPayment(app, !Preferences.isLoginTimedOut(), function(status)
											{
												Mojo.Log.info("Catalog.appStates[download].download _handleVerifyPayment returned");
												if (status) 
												{
													Mojo.Log.info("Catalog.appStates[download].download _handleVerifyPayment status=true download app");
													Catalog.appStatesCommon._install(app);
												}
												else 
												{
													// reset back in case of error
													app.setState("download");
												}
											});
										}
										else 
										{
											Catalog.appStatesCommon._install(app);
										}
									}
									else 
									{
										app.setState("download");
									}
								});
							}
							else 
							{
								app.setState("download");
							}
						});
					}
					else 
					{
						app.setState("download");
					}
				});
			}
			else 
			{
				app.setState("download");
			}
		});
		
	},
	
	toString: function()
	{
		return "download";
	}
};

// Entered when download manager send the first 
// valid progress amount and active until download is active
// that is, we receive progress updates
Catalog.appStates["download progress"] = {
	
	init: function(app, args) 
	{
		app._progressPillModel.titleRight = undefined;
		app._progressPillModel.icon = 'action-icon';
		app._progressPillModel.image = 'images/download-icon.png';
		app._progressPillModel.title = $L('Downloading...');
		app._progressPillModel.value = app.progress/100;
		
		app.disabledClass = null;
		app.updateClass = null;
		app.activeClass = "active"; 
		app.resumeClass = null;
		app.pauseClass = "show";
		app.warningClass = null;
	},
	
	updateFromServer: function(app) 
	{	
		// force a refresh
		app.setState("download progress");
	},
	
	updateFromInstalledAppsList: function(app)
	{
		// force a refresh
		app.setState("download progress");	
	},
	
	defaultAction: function(app) 
	{
		this.pauseDownload(app);
	},
	
	myAppsDefaultAction: function(app)
	{
		this.pauseDownload(app);
	},
	
	pauseDownload: function(app)
	{
		Catalog.appStatesCommon._pause(app);	
	},
	
	cancelDownload: function(app)
	{
		Catalog.appStatesCommon._cancel(app);
	},
	
	uninstall: function(app)
	{
		Catalog.appStatesCommon._uninstall(app);
	},
	
	_reset: function(app)
	{
		Catalog.appStatesCommon._reset(app);
	},
	
	_remove: function(app)
	{
		Catalog.appStatesCommon._remove(app);
	},
	
	saveToMyApps: function()
	{
		return true;
	},
	
	toString: function()
	{
		return "download progress";
	}
};

Catalog.appStates["paused"] = {
	
	init: function(app, args) 
	{
		app._progressPillModel.titleRight = undefined;
		app._progressPillModel.icon = 'none';
		app._progressPillModel.value = app.progress/100;
		app._progressPillModel.image = undefined;
		app._progressPillModel.title = $L('Download paused');
		
		app.disabledClass = null;
		app.updateClass = null;
		app.activeClass = "active"; 
		app.resumeClass = "show";
		app.pauseClass = null;
		app.warningClass = null;
	},
	
	updateFromServer: function(app) 
	{	
		// force a refresh
		app.setState("paused");
	},
	
	updateFromInstalledAppsList: function(app)
	{
		// force a refresh
		app.setState("paused");	
	},
	
	myAppsDefaultAction: function(app)
	{
		app.resumeDownload();
	},
	
	defaultAction: function(app) 
	{
		app.resumeDownload();
	},
	
	resumeDownload: function(app)
	{
		Catalog.appStatesCommon._resume(app);
	},
	
	cancelPausedDownload: function(app)
	{
		Mojo.Log.info("Catalog.appStates.paused.cancelPausedDownload")
		Catalog.appStatesCommon._cancel(app);
	},
	
	uninstall: function(app)
	{
		Catalog.appStatesCommon._uninstall(app);
	},
	
	_reset: function(app)
	{
		Catalog.appStatesCommon._reset(app);
	},
	
	_remove: function(app)
	{
		Catalog.appStatesCommon._remove(app);
	},
	
	saveToMyApps: function()
	{
		return true;
	},

	toString: function()
	{
		return "paused";
	}
};

Catalog.appStates["download failed"] = {
	
	init: function(app) 
	{
		Mojo.Log.error("Catalog.download failed.init error: ", app.errorCode);
		app._progressPillModel.titleRight = undefined;
		app._progressPillModel.icon = 'failed-app-icon';
		app._progressPillModel.image = undefined;
		app._progressPillModel.value = undefined;
		app._progressPillModel.title = $L('Download failed');
		app.progress = 0;
		app.disabledClass = null;
		app.updateClass = null;
		app.activeClass = "warning"; 
		app.resumeClass = null;
		app.pauseClass = null;
		app.warningClass = "show";
	},
	
	updateFromServer: function(app) 
	{
		// force a refresh
		app.setState("download failed");
	},
	
	updateFromInstalledAppsList: function(app)
	{
		// force a refresh
		app.setState("download failed");	
	},
	
	defaultAction: function(app) 
	{
		this._defaultAction(app);
	},
	
	myAppsDefaultAction: function(app)
	{
		this._defaultAction(app);
	},
	
	_defaultAction: function(app) 
	{
		Utilities.Errors.displayError(app.errorCode, {errCode: app.errorCode}, "download_default", null, null, 
		function(value)
		{
			if (value == "retry") 
			{
				app.install();
			}
			else if (value == "cancel") 
			{
				Catalog.appStatesCommon._cancel(app);
			}
		});
	},
	
	cancelDownload: function(app)
	{
		Catalog.appStatesCommon._cancel(app);
	},
	
	uninstall: function(app)
	{
		Catalog.appStatesCommon._uninstall(app);
	},
	
	_reset: function(app)
	{
		Catalog.appStatesCommon._reset(app);
	},
	
	_remove: function(app)
	{
		Catalog.appStatesCommon._remove(app);
	},
	
	install: function(app)
	{
		var oldState = app.stateToString();
		app.setState("fake progress");
		
		Weave.Services.ApplicationServer.getApplicationDetails(app.id, app.publicApplicationId, Mojo.Locale.getCurrentLocale(), 
		function(status, details)
		{
			if (status) 
			{
				Mojo.Log.info("DownloadStates.download failed.getDetailsFromServer details %j", details);
				app.updateFromServer(details);
				
				Catalog.appStatesCommon.validateInstallSpace(app, "validate_space_default_single", function(status)
				{
					if (status) 
					{
						Catalog.appStatesCommon.validateDownloadConnection(function(status)
						{ 
							if (status)
							{
								Mojo.Log.info("DownloadStates.download failed install: ", app.publicApplicationId);
								Weave.Services.AppInstallService.install(app, function(status, response)
								{
									if (!status)
									{
										Mojo.Log.error("DownloadStates.download failed: install failed %s: %j", app.publicApplicationId, response);
										if (app.stateToString() == "fake progress")
											app.setState(oldState);
									}
								});
							}
							else
							{
								app.setState(oldState);
							}
						});
					}
					else
					{
						app.setState(oldState);
					}
				});
			}
			else 
			{
				Mojo.Log.error("DownloadStates.download failed.getDetailsFromServer failed to get details from the server error: ", details);
				app.setState(oldState);
				Utilities.Errors.displayError(details);
			}
		});
	},
	
	saveToMyApps: function()
	{
		return true;
	},
	
	toString: function()
	{
		return "download failed";
	}
};

Catalog.appStates["purchasing"] = {
	
	init: function(app, args) 
	{
		app._progressPillModel.titleRight = undefined;
		app._progressPillModel.icon = 'none';
		app._progressPillModel.image = undefined;
		app._progressPillModel.value = undefined;
		app._progressPillModel.title = $L('Purchasing...');
	},
	
	toString: function()
	{
		return "purchasing";
	}
};

Catalog.appStates["purchased"] = {
	
	init: function(app, args) 
	{
		if (!args || (args && !args.transitional)) 
		{
			app._progressPillModel.titleRight = undefined;
			app._progressPillModel.icon = 'download-app-icon';
			app._progressPillModel.image = undefined;
			app._progressPillModel.value = undefined;
			app._progressPillModel.title = $L('Download for free');
		}
		
		app.progress = 0;
		
		if (args && args.version)
			app.purchasedVersion = args.version;
	},
	
	updateFromServer: function(app) 
	{	
	},
	
	updateFromInstalledAppsList: function(app)
	{
		if (app.installedVersion) 
		{
			if (app.serverVersion && Utilities.VersionCheck.compare(app.installedVersion, app.serverVersion) == -1) 
			{
				app.setState("installed update available");
			}
			else 
			{
				app.setState('installed');	
			}
		}
	},
	
	removeFromMyApps: function()
	{
		return true;
	}, 
	
	defaultAction: function(app) 
	{
		app.install();
	},
	
	install: function(app)
	{
		Catalog.appStatesCommon.validateInstallSpace(app, "validate_space_default_single", function(status)
		{
			if (status) 
				Catalog.appStatesCommon._install(app);
		});
	},
	
	toString: function()
	{
		return "purchased";
	}
};

Catalog.appStates["purchase_failed"] = {
	
	init: function(app, args) 
	{
		Mojo.Log.error("Catalog.purchase_failed.init %j", args);
		app._progressPillModel.titleRight = undefined;
		app._progressPillModel.icon = 'failed-app-icon';
		app._progressPillModel.image = undefined;
		app._progressPillModel.value = undefined;
		app._progressPillModel.title = $L('Purchase failed');
		
		if (args) 
		{
			app.errorCode = args.errorCode.toString();
			Mojo.Log.error("Catalog.appStates[purchase_failed].init error: %s", app.errorCode);
		}
	},
	
	updateFromServer: function(app) 
	{	
	},
	
	
	defaultAction: function(app) 
	{
		var self = this;
		Utilities.Errors.displayError(app.errorCode, {errCode: app.errorCode}, "PMT_purchase_default", null, null, function(value)
		{
			if (value) 
				self._reset(app);
		});
	},
	
	_reset: function(app)
	{
		app.setState("download");
	},
	
	_remove: function(app)
	{
		app.setState("download");
	},
	
	toString: function()
	{
		return "purchase_failed";
	}
};


Catalog.appStates["installing"] = {
	
	init: function(app, args) 
	{
		app._progressPillModel.titleRight = undefined;
		app._progressPillModel.icon = 'none';
		app._progressPillModel.image = undefined;
		app._progressPillModel.value = 1;
		app._progressPillModel.title = $L('Installing...');
		app.progress = 100;
		
		app.disabledClass = null;
		app.updateClass = null;
		app.activeClass = "active"; 
		app.resumeClass = null;
		app.pauseClass = null;
		app.warningClass = null;
	},
	
	updateFromInstalledAppsList: function(app)
	{
		// force a refresh
		app.setState("installing");
	},
	
	_reset: function(app)
	{
		Catalog.appStatesCommon._reset(app);
	},
	
	_remove: function(app)
	{
		Catalog.appStatesCommon._remove(app);
	},
	
	saveToMyApps: function()
	{
		return true;
	},
	
	toString: function()
	{
		return "installing";
	}
};

Catalog.appStates["installed"] = {
	
	init: function(app, args) 
	{
		app._progressPillModel.titleRight = undefined;
		app._progressPillModel.icon = 'launch-app-icon';
		app._progressPillModel.image = undefined;
		app._progressPillModel.value = undefined;
		app._progressPillModel.title = $L('Tap to launch');	
		Mojo.Log.info("Catalog.appStates[installed].init installedVersion=%s", app.installedVersion);
		
		app.disabledClass = null;
		app.updateClass = null;
		app.activeClass = null; 
		app.resumeClass = null;
		app.pauseClass = null;
		app.warningClass = null;
	},
	
	updateFromServer: function(app) 
	{
		this._update(app);
	},

	updateFromServerUpdatesList: function(app) 
	{
		this._update(app);
	},
	
	_update: function(app)
	{
		Mojo.assert(app.installedVersion, "ERROR: app.installedVersion undefined when app is in installed state");
		// See if update is available
		if (app.serverVersion && Utilities.VersionCheck.compare(app.installedVersion, app.serverVersion) == -1) 
		{
			app.setState("installed update available");
		}
		else
		{
			// force a refresh
			app.setState("installed");
		}
	},
	
	saveToMyApps: function()
	{
		return true;
	},

	defaultAction: function(app) 
	{
		app.launch();
	},
	
	launch: function(app)
	{
		Weave.Services.ApplicationManager.openApplication(app.publicApplicationId);
	},
	
	uninstall: function(app)
	{
		Catalog.appStatesCommon._uninstall(app);
	},
	
	_reset: function(app)
	{
		Catalog.appStatesCommon._reset(app);
	},
	
	_remove: function(app)
	{
		Catalog.appStatesCommon._remove(app);
	},
	
	toString: function()
	{
		return "installed";
	}
};

Catalog.appStates["installed update available"] = {	
	
	init: function(app, args) 
	{
		app._progressPillModel.titleRight = undefined;
		app._progressPillModel.icon = 'update-app-icon';
		app._progressPillModel.image = undefined;
		app._progressPillModel.value = undefined;
		app._progressPillModel.title = $L('Update Available');
		
		app.disabledClass = null;
		app.updateClass = "update";
		app.activeClass = null; 
		app.resumeClass = null;
		app.pauseClass = null;
		app.warningClass = null;
	},
	
	saveToMyApps: function()
	{
		return true;
	},
	
	defaultAction: function(app) 
	{
		app.install();
	},
	
	myAppsDefaultAction: function(app)
	{
		app.install();
	},
	
	install: function(app) 
	{
		Catalog.appStatesCommon.validateInstallSpace(app, "validate_space_default_single", function(status)
		{
			if (status) 
			{
				Catalog.appStatesCommon._install(app);
			}
			else 
			{
				app.setState("installed update available");
			}
		});
	},
	
	// called when updates are installed in a bulk
	// all checks (install capacity, network..) are done
	// once for the whole batch
	installUpdate: function(app) 
	{
		var oldState = app.stateToString();
		app.setState("fake progress");
				
		Mojo.Log.info("Catalog.installed update available.installUpdate: ", app.publicApplicationId);
		Weave.Services.AppInstallService.install(app, function(status, response)
		{
			if (!status)
			{
				Mojo.Log.error("Catalog.installed update available.installUpdate failed %s: %j, returing to state %s", app.publicApplicationId, response, oldState);
				if (app.stateToString() == "fake progress")
					app.setState(oldState);
			}
		});
	},
	
	updateFromInstalledAppsList: function(app)
	{
		this._update(app);
	},
	
	updateFromServerUpdatesList: function(app) 
	{
		this._update(app);
	},
	
	_update: function(app)
	{
		Mojo.assert(app.installedVersion, "ERROR: app.installedVersion undefined when app is in installed state");
		if (app.serverVersion && Utilities.VersionCheck.compare(app.installedVersion, app.serverVersion) == -1) 
		{
			// force a refresh
			app.setState("installed update available");
		}
		else 
		{
			app.setState("installed");
		}
	},
	
	uninstall: function(app)
	{
		Catalog.appStatesCommon._uninstall(app);
	},
	
	_reset: function(app)
	{
		Catalog.appStatesCommon._reset(app);
	},
	
	_remove: function(app)
	{
		Catalog.appStatesCommon._remove(app);
	},
	
	toString: function()
	{
		return "installed update available";
	}
};

Catalog.appStates["install failed"] = {

	init: function(app) 
	{
		app._progressPillModel.titleRight = undefined;
		app._progressPillModel.icon = 'failed-app-icon';
		app._progressPillModel.image = undefined;
		app._progressPillModel.value = undefined;
		app._progressPillModel.title = $L('Install failed');
		Mojo.Log.error("Catalog.appStates[install failed].init error: %s", app.errorCode);
		
		app.disabledClass = null;
		app.updateClass = null;
		app.activeClass = "warning"; 
		app.resumeClass = null;
		app.pauseClass = null;
		app.warningClass = "show";
	},
	
	updateFromServer: function(app) 
	{	
		// do nothing, let user retry to install
	},
	
	updateFromInstalledAppsList: function(app)
	{
		// force refresh
		app.setState("install failed");
	},
	
	defaultAction: function(app) 
	{
		this._defaultAction(app);
	},
	
	myAppsDefaultAction: function(app)
	{
		this._defaultAction(app);	
	},
	
	_defaultAction: function(app)
	{
		// TODO Add retryInstall back once NOV-83315 is implemented and there
		// is an "intelligent" retry, for now always download from scratch
		var totalInstallSize;
		if (app.installSize) 
		{
			totalInstallSize = (app.installSize / (1024 * 1024)) + 1;
			totalInstallSize = (Math.ceil(totalInstallSize * 100) / 100) + "M";
		}
		else 
		{
			totalInstallSize = $L("unknown MB");
		}
		
		Utilities.Errors.displayError(app.errorCode, {installSize: totalInstallSize}, "install_default", null, null, function(value)
		{
			if (value == "retry") 
			{
				app.install();
			}
			else if (value == "cancel") 
			{
				Catalog.appStatesCommon._cancel(app);
			}
			else if (value == "help") 
			{
				Weave.Services.ConnectionManager.getStatus(function(online)
                {
                    Weave.Services.ApplicationManager.openApplication('com.palm.app.help',
                    {
                        target: online ? 'http://help.palm.com/basics/manage_applications/basics_delete_app_from_launcher.html' : 'no-network'
                    });
                });
			}
		});
	},
	
	cancelDownload: function(app)
	{
		Catalog.appStatesCommon._cancel(app);
	},
	
	uninstall: function(app)
	{
		Catalog.appStatesCommon._uninstall(app);
	},
	
	_reset: function(app)
	{
		Catalog.appStatesCommon._reset(app);
	},
	
	_remove: function(app)
	{
		Catalog.appStatesCommon._remove(app);
	},
	
	install: function(app)
	{
		var oldState = app.stateToString();
		app.setState("fake progress");
		
		// refetch data from the server
		Weave.Services.ApplicationServer.getApplicationDetails(app.id, app.publicApplicationId, Mojo.Locale.getCurrentLocale(), 
		function(status, details)
		{
			if (status) 
			{
				Mojo.Log.info("DownloadStates.install failed.getDetailsFromServer details %j", details);
				app.updateFromServer(details);
				
				Catalog.appStatesCommon.validateInstallSpace(app, "validate_space_default_single", function(status)
				{
					if (status) 
					{
						Catalog.appStatesCommon.validateDownloadConnection(function(status)
						{ 
							if (status)
							{
								Mojo.Log.info("DownloadStates.install failed install: ", app.publicApplicationId);
								Weave.Services.AppInstallService.install(app, function(status, response)
								{
									if (!status)
									{
										Mojo.Log.error("DownloadStates.install failed: install failed %s: %j", app.publicApplicationId, response);
										if (app.stateToString() == "fake progress")
											app.setState(oldState);
									}
								});
							}
							else
							{
								app.setState(oldState);
							}
						});
					}
					else
					{
						app.setState(oldState);
					}
				});
			}
			else 
			{
				Mojo.Log.error("DownloadStates.install failed.getDetailsFromServer failed to get details from the server error: ", details);
				app.setState(oldState);
				Utilities.Errors.displayError(details);
			}
		});
	},
	
	saveToMyApps: function()
	{
		return true;
	},
	
	toString: function()
	{
		return "install failed";
	}
};

/* AppDetails
 * 
 * Stores details for a single application from the app store
 * 
 * Observer interface:
 * 
 * // called when this application's details change
 * updateDetails()
 * 
 */
var AppDetails = Class.create({
	
	initialize: function(appid, packageid) 
	{
		this._observers = new Array();
		// TLE these are no longer private
		this._appid = appid;
		this._packageid = packageid;
		this._details = null;
		this._error = null;
		this._myRating = null;
	},
	
	getDetailsFromServer: function() 
	{
		var self = this;
		// testing
		//this._appid = 2008954;
		//this._packageid = "myfreekingapp";
		Weave.Services.ApplicationServer.getApplicationDetails(this._appid, this._packageid, 
			Mojo.Locale.getCurrentLocale(), function(status, details, myrating, country)
			{
				//status = false;
				if (status) 
				{
					myProfile.activationCountry = country;
					Mojo.Log.info("AppDetails.getDetailsFromServer details %j, myrating %j, activation country %s", details, myrating, myProfile.activationCountry);
					self._setError(null);
					self._setDetails(details);
					self.getMyCommentFromServer();
				}
				else 
				{
					Mojo.Log.error("AppDetails.getDetailsFromServer failed to get details from the server error: ", details);
					self._setError(details);
					self._setMyRating(null);
					self._setDetails(null);
					Utilities.Errors.displayError(details);
				}
			});
	},
	
	getMyCommentFromServer: function() 
	{
		if (!this._details) return;
		
		var self = this;
		Weave.Services.ApplicationServer.getMyComment(this._details.id, function(status, review)
		{
			if (status && review)
			{
				self._setMyRating(review);
			}
			// Ignore server errors here
		});
	},
	
	_setDetails: function(details) 
	{
		// TLE maybe don't set it here
		if (details) 
		{
			this._appid = details.id;
			this._packageid = details.publicApplicationId;
		}
		
		this._details = details;
		
		// Go through the list of observers and notify them of the change
		// At that point observers can call getDetails() to get the changes
		// notify observers
		for (var i = 0; i < this._observers.length; i++) 
		{
			if (this._observers[i].updateDetails) 
			{
				this._observers[i].updateDetails(this);
			}
		}
	},
	
	getDetails: function() 
	{
		return this._details;
	},
	
	_setMyRating: function(rating) 
	{
		this._myRating = rating ? rating : null;
		
		// Go through the list of observers and notify them of the change
		for (var i = 0; i < this._observers.length; i++) 
		{
			if (this._observers[i].updateMyRating) 
			{
				this._observers[i].updateMyRating();
			}
		}
	},
	
	getMyRating: function() 
	{
		return this._myRating;
	},
	
	_setError: function(error) 
	{
		this._error = error;
	},
	
	getError: function() 
	{
		return this._error;
	},
	
	getProgramType: function() 
	{
		return this._details != null ? this._details.programType : null;
	},
	
	// adds observer to the list of observers
	attach: function(observer) 
	{
		this._observers.push(observer);
	},
	
	// removes observer from the list
	detach: function(observer) 
	{
		for (var i = 0; i < this._observers.length; i++)
		{
			if (this._observers[i] === observer)
			{
				this._observers.splice(i, 1);
			}
		}
	},
});

/*
 * AppDownload
 * 
 * represents a single app download. Each time details scene is pushed an instance of
 * AppDownload object is created (or an existing one is returned from myApps hash
 * maintained by the AppDownloadManager). Details scene listens for state changes
 * on this object to update the UI.
 * The most important property of AppDownload is _state which is always set to one
 * of the states defined in downloadstates.js (Catalog.appStates).
 * Behavior of the download object is entirely driven by the current state.
 * AppDownload defers almost all functionlaity to its current state object.
 * 
 * For example if details scene calls appDownload->cancelDownload()
 * the call will map to appDownload->_state->cancelDownload()
 * In some states one can not cancel a download, thus only some states 
 * define this method. Calling cancelDownload() while _state == "installed"
 * will do nothing since Catalog.appStates["installed"] doesn't define cancelDownload().
 * 
 */


var AppDownload = Class.create({
	
	_defaultState: "dummy",
	
	initialize: function() 
	{
		this._observers 		= new Array();
		this._progressPillModel = {};
		this._setState(this._defaultState);
	},
	
	/*
	 *  Update functions
	 *  
	 *  update application object from different sources
	 *  Each source names properties differently, we
	 *  need to "equalize" them before updating object
	 */
	
	// called when application details are pulled
	// from the server to update state. Mostly used to
	// figure out if software update is available
	updateFromServer: function(appDetails) 
	{
		var details = 
		{
			id: appDetails.id,
			publicApplicationId: appDetails.publicApplicationId,
			title: appDetails.title,
			iconUrl: appDetails.appIcon,
			purchasedVersion: appDetails.purchasedVersion,
			serverVersion: appDetails.version,
			packageSize: appDetails.appSize, 
			installSize: appDetails.installSize,
			isEncrypted: appDetails.isEncrypted,
			currency: appDetails.currency,
			priceType: appDetails.priceType, 
			price: appDetails.price,
			sku: appDetails.sku,
			paymentCategory: appDetails.paymentCategory,
			// install wants vendor and purchase vendorid
			vendor: appDetails.creator,
			vendorid: appDetails.vendorid,
			vendorUrl: appDetails.homeURL,
			vendorCity: appDetails.vendorCity,
			vendorRegion: appDetails.vendorRegion,
			vendorCountry: appDetails.vendorCountry,
			islocationbased: appDetails.islocationbased,
			packageUrl: appDetails.appLocation,
		}
		
		Object.extend(this, details); 
		
		Mojo.Log.info("AppDownload.updateFromServer updated: details %j", details);
		// delegate to state objects
		if (this._state.updateFromServer)
			this._state.updateFromServer(this);
	},
	
	// called from download manager when installed queue is created
	// at startup
	updateFromInstalledAppsList: function(appDetails)
	{
		var details = 
		{
			publicApplicationId: appDetails.id,
			title: appDetails.title,
			installedVersion: appDetails.version,
			icon: appDetails.icon ? appDetails.icon : this.icon,
		}
				
		Object.extend(this, details);
		Mojo.Log.info("AppDownload.updateFromInstalledAppsList updated: %s", this.toString());
		
		if (this._state.updateFromInstalledAppsList)
			this._state.updateFromInstalledAppsList(this); 
	},
	
	// called when list of updatable apps is retrived 
	// from the server (My apps scene calls it)
	updateFromServerUpdatesList: function(appDetails)
	{
		var details = 
		{
			id: appDetails.id,
			serverVersion: appDetails.appVersion,
			packageUrl: appDetails.packageUrl,			
			vendor: appDetails.vendor,
			vendorUrl: appDetails.homeURL,
			iconUrl: appDetails.appIcon,
			packageSize: appDetails.appSize, 
			installSize: appDetails.installSize, 
		}
		
		Object.extend(this, details); 
		Mojo.Log.info("AppDownload.updateFromServerUpdatesList updated %s, from details: %j", this.toString(), details);
		
		// delegate to state objects
		if (this._state.updateFromServerUpdatesList)
			this._state.updateFromServerUpdatesList(this);
	},
	
	updateFromInstallNotification: function(appDetails)
	{
		if (appDetails.change == "added" ||
			(appDetails.change == "updated" && appDetails.version && 
			this.installedVersion != appDetails.version)) 
		{
			var details = 
			{
				installedVersion: appDetails.version,
				icon: appDetails.icon
			}
			Object.extend(this, details);
			this.setState("installed");
			Mojo.Log.info("AppDownload.updateFromInstallNotification changeDetails: %j, app: %s", appDetails, this.toString());
		}
		else if (appDetails.change == "removed")
		{
			if (this._state._remove)
				this._state._remove(this);
			Mojo.Log.info("AppDownload.updateFromInstallNotification removed: %s", this.toString());
		}
	},
	
	/*
	  	"icon download current"
		"icon download complete"
		"icon download paused" - will also have "progress" 100
		"ipk download current" - will also have "progress" : 0-100
		"ipk download complete"
		"ipk download paused"  - will also have "progress" : 0-100
		
		"installing"
		"installed"
		"removing"
		"removed"
		"canceled"
		"download failed" - will also have "errorCode" : int and "reason" : string
		"install failed" - - will also have "errorCode" : int and "reason" : string
		"remove failed" - - will also have "errorCode" : int and "reason" : string
		"unknown"
	 * 
	 * 
	 * 
	 */
	updateFromStatus: function(id, appDetails)
	{
		var newState = appDetails.state;
		Mojo.Log.info("AppDownload.updateFromStatus newState: %s", newState);
		
		// removed & installed notifications are handled in updateFromInstallNotification
		// nothing to do for removing & unknown
		if (newState == "removing" || 
			newState == "unknown" || 
			newState == "removed" || 
			newState == "installed")
		{
			return;
		}
		
		// translate state
		if (newState == "icon download complete" ||
			newState == "icon download current")
		{
			appDetails.progress = 0;
			newState = "download progress";
		}				
		
		if (newState == "ipk download complete")
		{
			appDetails.progress = 100;
		}
	
		// translate state
		if (newState == "ipk download current" ||
			newState == "ipk download complete")
		{
			newState = "download progress";
		}
		else if (newState == "icon download paused" ||
				newState == "ipk download paused") 
		{
			newState = "paused";
		}
		
		// fix up error code
		if (newState == "install failed")
		{
			this.errorCode = appDetails.reason;
		}
		else if (newState == "download failed")
		{
			this.errorCode = appDetails.errorCode.toString();
		}
		else 
		{
			this.errorCode = appDetails.errorCode;
		}
		
		var details = 
		{
			publicApplicationId: id,
			title: appDetails.title,
			progress: appDetails.progress,
			serverVersion: appDetails.version,
			vendor: appDetails.vendor,
			vendorUrl: appDetails.vendorUrl,
			icon: this.icon ? this.icon : appDetails.icon,
		}
		Object.extend(this, details);
		Mojo.Log.info("AppDownload.updateFromStatus updated: %s, from details %j", this.toString(), details);
		
		if (newState == "canceled" || newState == "remove failed") 
		{
			if (this._state._reset)
				this._state._reset(this);
		}
		else 
		{
			if (this._state._allowTransition && !this._state._allowTransition(newState)) 
			{
				Mojo.Log.info("AppDownload.updateFromStatus disallow transition from %s to %s", this.stateToString(), newState);
				return;
			}
				
			Mojo.Log.info("AppDownload.updateFromStatus setting to state: %s", newState);
			this.setState(newState);
		}
	},
	
	// called from state objects when state changes
	setState: function(state, args) 
	{
		Mojo.Log.info("AppDownload.setState %s", state);
		this._setState(state, args);
		
		// notify observers
		for (var i = 0; i < this._observers.length; i++) 
		{
			if (this._observers[i].updateDownloadState) 
				this._observers[i].updateDownloadState(this);
		}
	},
	
	_setState: function(state, args) 
	{
		this._state = Catalog.appStates[state];
		this._state.init(this, args);
	},
	
	// returns progress pill model, called by observers
	// to update the UI in response to state changes
	getProgressPillModel: function()
	{
		Mojo.Log.info("AppDownload.getProgressPillModel %j", this._progressPillModel);
		return this._progressPillModel;
	},
	
	// returns formated price for this app
	getFormattedPrice: function() 
	{
		// NOTE: If app.price is a string, then the server *must* have localized it
		return parseFloat(this.price) != this.price ? this.price : 
			this.price === 0 ? $L("free") : Mojo.Format.formatCurrency(this.price, { fractionDigits: 2, countryCode: myProfile.activationCountry});
		//return parseFloat(this.price) != this.price ? this.price : 
		//	this.price === 0 ? $L("free") : this.currency + " " +Mojo.Format.formatNumber(this.price, { fractionDigits: 2});
	},
	
	// adds observer to the list of observers
	attach: function(observer) 
	{
		this._observers.push(observer);
	},
	
	// removes observer from the list
	detach: function(observer) 
	{
		Mojo.Log.info("AppDownload.detach");
		for (var i = 0; i < this._observers.length; i++)
		{
			if (this._observers[i] === observer)
			{
				this._observers.splice(i, 1);
				Mojo.Log.info("AppDownload.detach removed observer, left %d", this._observers.length);
			}
		}
	},
	
	// defer all actions to state objects, that is 
	// if action is defined for the current state 
	install: function()
	{
		if (this._state.install) 
			this._state.install(this);
	},
	
	// called when updates are installed in a bulk from MyApps scene
	// all checks (install capacity, network..) are done
	// once for the whole batch and are skipped at this time
	installUpdate: function()
	{
		Mojo.Log.info("AppDownload.installUpdate ", this.publicApplicationId);
		if (this._state.installUpdate) 
			this._state.installUpdate(this);
	},
	
	// used by myApps scene to distinguish 
	// apps that are ready to be updated
	canInstallUpdate: function()
	{
		if (this._state.installUpdate) 
			return true;
		else
			return false;
	},
	
	cancelDownload: function()
	{
		if (this._state.cancelDownload) 
			this._state.cancelDownload(this);
	},
	
	pauseDownload: function()
	{
		if (this._state.pauseDownload) 
			this._state.pauseDownload(this);
	},
	
	cancelPausedDownload: function()
	{
		if (this._state.cancelPausedDownload) 
			this._state.cancelPausedDownload(this);
	},
	
	resumeDownload: function()
	{
		if (this._state.resumeDownload) 
			this._state.resumeDownload(this);
	},
	
	launch: function()
	{
		if (this._state.launch) 
			this._state.launch(this);
	},
	
	uninstall: function()
	{
		if (this._state.uninstall) 
			this._state.uninstall(this);
	},
	
	defaultAction: function()
	{
		if (this._state.defaultAction)
			this._state.defaultAction(this);
	},
	
	// called from MyApps scene to perform 
	// default action based on the current state
	myAppsDefaultAction: function()
	{
		if (this._state.myAppsDefaultAction)
			this._state.myAppsDefaultAction(this);
	},
	
	isInstalled: function()
	{
		var app = Catalog.AppDownloadMngr.getInstalledApp(this.publicApplicationId);
		if (app) 
			return true;
		
		return false;
	},
	
	stateToString: function()
	{
		return this._state.toString();
	},
	
	// for debugging purposes
	toString: function()
	{
		return "id:" + this.id + ", publicApplicationId:" + this.publicApplicationId + ", state:" + this.stateToString() + ", title:" + this.title
		+ ", serverVersion:" + this.serverVersion + ", installedVersion:" + this.installedVersion + ", purchasedVersion:" + this.purchasedVersion
		+ ", errorCode:" + this.errorCode + ", icon:" + this.icon + ", updateClass:" + this.updateClass + ", activeClass:" + this.activeClass +
		", resumeClass:" + this.resumeClass + ", pauseClass:" + this.pauseClass + " warningClass:" + this.warningClass +
		", packageSize:" + this.packageSize + " installSize: " + this.installSize;
	},
	
	// returns true if app should be saved to the global
	// downloads queue based on the current state
	saveToMyApps: function()
	{
		if (this._state.saveToMyApps)
			return this._state.saveToMyApps();
		
		return false;
	},
	
	// returns true if app should be removed from the global
	// downloads queue based on the current state
	removeFromMyApps: function()
	{
		Mojo.Log.info("AppDownload.removeFromMyApps");
		if (this._state.removeFromMyApps)
			return this._state.removeFromMyApps();
		
		return false;
	},
});


/* AppDownloadManager
 * 
 * Keeps a queue of active downloads. On launch, it gets active downloads 
 * from appInstallerService/status method and initializes the queue. It then queries 
 * the system for the list of installed apps and adds them to myApps as well.
 * When client request the download object (e.g details scene)
 * dm will either return an existing one, or create a new one.
 * DM is listening for state changes on all download objects, in case that
 * download becomes active and needs to be stored in the myApps queue. 
 * MyApps are installations in progress, failed installations and installed apps.
 * Other downloads are stored in _activeQueue until user initiates the install
 * at which point appDownload object is moved to myApps queue and appears 
 * in the list on MyApps scene.
 * 
 * Sample flow:
 * 
 * User opens a details scene of some new app "com.company.myapp". 
 * AppDownloadManager creates a new AppDownload object and stores it in _activeQueue hash.
 * AppDownloadManager & current details scene both subscribe as listeners on this object 
 * to be notified of state changes. When user taps "download app" install is initiated 
 * and state of the object starts changing with updates from appInstallService/status method
 * (_appInstallServiceStatusCB). When state changes AppDownloadManager is notified through 
 * updateDownloadState() observer method and it then decides if AppDownload should be added
 * to myApps queue. If state became "download progress" for example, app is added to myApps hash.
 * It's similar with details scene. When state of AppDownload changes details scene's 
 * updateDownloadState method is called to update the UI (progress pill). 
 * 
 * 
 * Observers on AppDownloadManager can subscribe to be notified of these changes:
 * 
 * 1) changes to installed applications list
 * current observers: main scene, search scene...
 * observer needs to implement 
 * 
 * updateInstalledApps(AppDownloadManager_instance, updateReason);
 * 
 * 
 * 2) changes to the myApps queue, sent when app is added/deleted/updated from the queue
 * current observers: myApps scene.
 * observer needs to implement  
 * 
 * updateMyApps(updateReason, app);
 * 
 * updateReason: one of: APP_ADDED, APP_DELETED, APP_UPDATED
 * MYAPPS_ALL - sent when myApps queue is populated 
 *             (from appInstallService/status + installed app from /listApps)
 * 
 * app: instance of AppDownload that changed
 * 
 */

var AppDownloadManager = Class.create({
	
	APP_ADDED: 		0,
	APP_DELETED: 	1,
	APP_UPDATED:	2,
	MYAPPS_ALL:		3,
	
	initialize: function() 
	{
		this._myApps = new Object();
		this._activeQueue = new Object();
		this._observers = new Array();
		this._allow1xDownload = false;
		// two things need to happen before myApps list is ready:
		// get all apps in progress and all installed apps
		this._myAppsListIsReady = 2;
	
		this._appInstallerService = new Mojo.Service.Request('palm://com.palm.bus/signal', {
			method: 'registerServerStatus',
			parameters: {
				'serviceName':'com.palm.appInstallService'
			},
			onSuccess: this._appInstallerServiceSignalCB.bind(this)
		});
		
		this._appManagerService = new Mojo.Service.Request('palm://com.palm.bus/signal', {
			method: 'registerServerStatus',
			parameters: {
				'serviceName':'com.palm.applicationManager'
			},
			onSuccess: this._appManagerServiceSignalCB.bind(this)
		});
		
		this._downloadManagerService = new Mojo.Service.Request('palm://com.palm.bus/signal', {
			method: 'registerServerStatus',
			parameters: {
				'serviceName':'com.palm.downloadmanager'
			},
			onSuccess: this._appDownloadServiceSignalCB.bind(this)
		});
	},
	
	start: function()
	{
		Mojo.Log.info("AppDownloadManager.start");
		
		this._myApps = new Object();
		this._myAppsListIsReady = 2;
		
		// get installed apps and subscribe for status
		this._getInstalledApps();
		Weave.Services.AppInstallService.status(this._appInstallServiceStatusCB.bind(this));
	},
	
	_appInstallerServiceSignalCB: function(response)
	{
		if (response.connected == true)
		{
			Mojo.Log.info("AppDownloadManager._appInstallerServiceSignal is back on the bus, starting");
			this.start();
		}
		else if (response.connected == false)
		{
			Mojo.Log.error("AppDownloadManager._appInstallerServiceSignal is gone from the bus");
		}
	},
	
	_appManagerServiceSignalCB: function(response)
	{
		if (response.connected == true)
		{
			// subscribe for app install/uninstall notifications
			var self = this;
			var cb = function(status, response) {
				Mojo.Log.info("AppDownloadManager.launchPointChanges callback %j", response);
				self._updateInstalledQueue(response);
			};
			Weave.Services.ApplicationManager.launchPointChanges(cb);
		}
		else if (response.connected == false)
		{
			Mojo.Log.error("AppDownloadManager._appManagerServiceSignalCB is gone from the bus");
		}
	},
	
	_appDownloadServiceSignalCB: function(response)
	{
		if (response.connected == true)
		{
			// if we need to allow 1x, reissue the command
			if (this._allow1xDownload)
				this.allow1xDownload(true);
		}
		else if (response.connected == false)
		{
			Mojo.Log.error("AppDownloadManager._appDownloadServiceSignalCB is gone from the bus");
		}
	},
	
	_getInstalledApps: function()
	{
		var self = this;
		Weave.Services.ApplicationManager.getInstalledApplications(function(status, apps)
		{
			var apps = status ? apps : [];			
			for (var i = 0; i < apps.length; i++)
			{
				if (apps[i].userInstalled)
				{
					Mojo.Log.info("AppDownloadManager._getInstalledApps installed app: %j", apps[i]);
					// update app from this payload
					// this might change the state of the app which will 
					// cause it to be added to _myApps
					var app = self.getAppDownload(apps[i].id);
					app.updateFromInstalledAppsList(apps[i]);
				}
			}

			self._sendMyAppsListIsReady();
			
			Mojo.Log.info("AppDownloadManager._getInstalledApps sending updateInstalledApps");
			for (var i = 0; i < self._observers.length; i++) 
			{
				if (self._observers[i].updateInstalledApps) 
					self._observers[i].updateInstalledApps();
			}
		});
	},
	
	// returns true if myApps list has been populated
	// from both sources: /status call - apps in progress
	// and /listApps call - installed applications
	myAppsListIsReady: function()
	{
		return (this._myAppsListIsReady == 0);
	},
	
	// Send MYAPPS_ALL notification when myApps list is populated
	// two things need to happen before myApps list is ready:
	// 1) get all apps in progress
	// 2) get all installed apps
	_sendMyAppsListIsReady: function()
	{
		this._myAppsListIsReady--;
		
		if (this._myAppsListIsReady == 0)
		{
			for (var i = 0; i < this._observers.length; i++) 
			{
				if (this._observers[i].updateMyApps) 
					this._observers[i].updateMyApps(null, this.MYAPPS_ALL);
			}
		}
	},
	
	// return app download with specified id
	// will attach listener to returned app object
	// called from details scene assistant
	getAppDownload: function(appId, listener) 
	{
		// queues have priority, we first return app from downloadQ if present and so on
		var app = this._myApps[appId];
		if (!app) app = this._activeQueue[appId];
		if (!app)
		{
			Mojo.Log.info("AppDownloadManager.getAppDownload  creating new with id %s", appId);
			// create a new one and add ourselfs as listener
			// we listen for state changes so that we can add it 
			// to the downloads queue if download starts 
			app = new AppDownload();
			app.attach(this);
			this._activeQueue[appId] = app;
		}
		
		if (listener) 
			app.attach(listener);
		
		return app;
	},
	
	// releases appDownload obj. If we are the last listener
	// and this app is in active queue, delete it from the queue
	// since it is no longer needed
	releaseAppDownload: function(appId, listener)
	{
		var app = this._myApps[appId];
		if (!app) app = this._activeQueue[appId];
		
		app.detach(listener);
		
		if (app == this._activeQueue[appId] && app._observers.length == 1)
		{
			Mojo.Log.info("AppDownloadManager.releaseAppDownload deleting from activeQueue %s", appId);
			delete this._activeQueue[appId];	
		}
	},
	
	_updateInstalledQueue: function(updateReason)
	{
		Mojo.Log.info("AppDownloadManager._updateInstalledQueue updateReason: %j", updateReason);
		
		// if app was deleted notify the server
		if (updateReason && (updateReason.change == "added" || 
			updateReason.change == "removed" || updateReason.change == "updated"))
		{
			var app = this._myApps[updateReason.id];
			if (app)
			{
				app.updateFromInstallNotification(updateReason);
			
				var self = this;
				var f = function()
				{
					Mojo.Log.info("AppDownloadManager._updateInstalledQueue sending updateInstalledApps");
					for (var i = 0; i < self._observers.length; i++) 
					{
						if (self._observers[i].updateInstalledApps) 
							self._observers[i].updateInstalledApps();
					}
				}
				// delay until myApps queue is updated
				f.delay(2);
			}
		}
	},
	
	// returns installed app if found or null
	getInstalledApp: function(id) 
	{
		var app = this._myApps[id];
		if (app && app.installedVersion)
		{
			return app;
		}
		return null;
	},
	
	getMyApps: function()
	{
		return this._myApps;
	},
	
	// adds observer to the list
	attach: function(observer) 
	{
		//Mojo.Log.info("AppDownloadManager.attach");
		this._observers.push(observer);
	},
	
	// removes observer from the list
	detach: function(observer) 
	{
		Mojo.Log.info("AppDownloadManager.detach");
		for (var i = 0; i < this._observers.length; i++)
		{
			if (this._observers[i] === observer)
			{
				this._observers.splice(i, 1);
				Mojo.Log.info("AppDownloadManager.detach removed observer, left %d", this._observers.length);
			}
		}
	},
	
	
	// Observer method for app download state changes
	// Here we decide if this app should be added/removed from
	// the myApps queue. If app is in the queue and has changed state
	// we send the "update" event APP_UPDATED.
	updateDownloadState: function(app) 
	{
		Mojo.Log.info("AppDownloadManager.updateDownloadState ", app.publicApplicationId, app.stateToString());
		Mojo.Log.info("AppDownloadManager.updateDownloadState this._myApps[%s]: %s", app.publicApplicationId, this._myApps[app.publicApplicationId]);

		var updateType = null;
		
		if (app.saveToMyApps() && !this._myApps[app.publicApplicationId])
		{
			Mojo.Log.info("AppDownloadManager.updateDownloadState APP_ADDED ", app.publicApplicationId, app.stateToString());
			this._myApps[app.publicApplicationId] = app;
			updateType = this.APP_ADDED;
			
			Mojo.assert(this._activeQueue[app.publicApplicationId], "AppDownloadManager.updateDownloadState added to MyApps but app wasn't in the activeQ");
			delete this._activeQueue[app.publicApplicationId];
		}
		else if (app.removeFromMyApps() && this._myApps[app.publicApplicationId])
		{
			Mojo.Log.info("AppDownloadManager.updateDownloadState APP_DELETED ", app.publicApplicationId, app.stateToString());
			delete this._myApps[app.publicApplicationId];
			updateType = this.APP_DELETED;
			
			// move it to active queue if we are not the only observer
			if (app._observers.length > 1) 
			{
				this._activeQueue[app.publicApplicationId] = app;
			}
		}
		else if (this._myApps[app.publicApplicationId])
		{
			Mojo.Log.info("AppDownloadManager.updateDownloadState APP_UPDATED ", app.publicApplicationId, app.stateToString());
			updateType = this.APP_UPDATED;
		}
			
		// app is updated, notify observers
		if (updateType != null)
		{
			for (var i = 0; i < this._observers.length; i++) 
			{
				if (this._observers[i].updateMyApps)
					this._observers[i].updateMyApps(app, updateType);
			}
		}
	},	
	
	_appInstallServiceStatusCB: function(status, singleUpdate, apps)
	{
		Mojo.Log.info("AppDownloadManager._appInstallServiceStatusCB singleUpdate:%s, apps: %j", singleUpdate, apps);
		if (!status) return;
		
		for (var i = 0; i < apps.length; i++)
		{
			var app = this.getAppDownload(apps[i].id);
			app.updateFromStatus(apps[i].id, apps[i].details);
		}
		
		// on first response, when we get the entire list
		// of active apps send "ready" update
		if (!singleUpdate)
		{
			this._sendMyAppsListIsReady();
		}
	},
	
	allow1xDownload: function(allow, callback)
	{
		var self = this;
		Weave.Services.request("palm://com.palm.downloadmanager", 
		{
			method: 'allow1x',
			parameters: {"value": allow},
		},
		function(response)
		{
			self._allow1xDownload = allow;
			callback && callback(true);
		},
		function(response)
		{
			Mojo.Log.info("AppDownloadManager.allow1xDownload failed %j", response);
			callback && callback(false);
		});	
	},
	
	canAllow1xDownload: function()
	{
		return this._allow1xDownload;
	},
	
	cleanup: function()
	{
		Mojo.Log.info("AppDownloadManager.cleanup");
		if (this._allow1xDownload)
			this.allow1xDownload(false);
			
		// this is just causing errors 
		// "could not find call XXX to cancel"
		//delete this._appInstallerService;
		//delete this._appManagerService;
		//delete this._downloadManagerService;
	},
		
});


var Catalog 			= Catalog || {}; 
Catalog.AppDownloadMngr = Catalog.AppDownloadMngr || new AppDownloadManager;



