var Bko = { data: {}, conf: {} };

////////////////////////////////////////
// Utility functions
Bko.util = {

	removeElementById: function(id) {
		var elem;
		
		elem = document.getElementById(id);
		elem.parentNode.removeChild(elem);
	}
	
	,urlForActionOnId: function(action, id){
		var url = Bko.conf.appRoot + '/applications';
		if(id) { url += '/' + id; }
		return url + '/' + action;
	}

	,isUri: function(value) {
		if(jQuery.type(value) !== "string") return false;
		if(value.length === 0) return true;

		return (value.match(new RegExp("^([a-z0-9+.-]+):(?://(?:((?:[a-z0-9-._~!$&'()*+,;=:]|%[0-9A-F]{2})*)@)?((?:[a-z0-9-._~!$&'()*+,;=]|%[0-9A-F]{2})*)(?::(\\d*))?(/(?:[a-z0-9-._~!$&'()*+,;=:@/]|%[0-9A-F]{2})*)?|(/?(?:[a-z0-9-._~!$&'()*+,;=:@]|%[0-9A-F]{2})+(?:[a-z0-9-._~!$&'()*+,;=:@/]|%[0-9A-F]{2})*)?)(?:\\?((?:[a-z0-9-._~!$&'()*+,;=:/?@]|%[0-9A-F]{2})*))?(?:#((?:[a-z0-9-._~!$&'()*+,;=:/?@]|%[0-9A-F]{2})*))?$")) != null);
	}
	
	,isDNS: function(value) {
		if(jQuery.type(value) !== "string") return false;
		if(value.length === 0) return true;
		
		return (value.match(new RegExp("^[.a-zA-Z0-9]+[-.a-zA-Z0-9]+[.a-zA-Z0-9]+$")) !== null);
	}
};

String.prototype.capitalize = function() {
	if(this.length < 0) return this;
	return this.charAt(0).toUpperCase() + this.slice(1);
}

////////////////////////////////////////
// My Apps - the core app
Bko.myApps = function() {
	var that = {
			appFormRules: {
				name: { required: true
					,minlength: 3
					,maxlength: 48
				}
				,description: {
					required: true
					,minlength: 8
					,maxlength: 250
				}
				,callback_uri: {
					uri: true
					,minlength: 4
					,maxlength: 128
				}
				,app_id: {
					dns: true
					,minlength: 8
					,maxlength: 50
				}
				,website: {
					url: true
					,maxlength: 64
				}
			}
			,appFormMessages: {
				name: {
					required: "You must name your application."
					,minlength: "This name is a bit short."
				}
				,description: {
				   required: "Please provide a description for your app."
					,minlength: "Please be more descriptive."
					,maxlength: "Descriptions must be 250 characters or less."
				}
				,callback_uri: {
					uri: "This is not a valid URI."
					,minlength: "This callback is too short."
				}
				,app_id: {
					dns: "App IDs may only contain letters, numbers, dashes, and dots."
					,minlength: "App IDs must have at least 8 characters."
				}
				,website: {
					// no customization for now
				}
			}
		}
		,my = {
			apps:{}
			,action:'home'
			,appId: null
			,sectionContent: document.getElementById('section-content')
			,panels: {
				home: document.getElementById('apps_panel_home')
				,sidebar: document.getElementById('section-sidebar')
				,appMenuItemTemplate: document.getElementById('template-vm-app-item-content')
			}
			,menuItems: [
				{id:'home', type:'basic', content: 'Dashboard'}
				,{id:'create', type:'basic', content: 'Register a New Application'}
				]
			,currentMenuItem:null
		};

	that.isLoaded = function() {
		return my.panels.home !== null;
	};

	that.initialize = function() {
		var parts = document.location.hash.substr(1).split(':',2)
			,action ,id=null;
		
		if(parts.length > 0) { action = parts[0]; }
		if(parts.length > 1) { id = parts[1]; }

		that.store = Bko.appDb();
		that.viewC = Bko.createViewController();
		that.editC = Bko.createEditController();
		that.regC = Bko.createRegistrationController();

		my._refreshSidebar();
		that.setAction(action, id);
		
		jQuery.validator.addMethod('uri', Bko.util.isUri, "Please enter a valid URI.");
		jQuery.validator.addMethod('dns', Bko.util.isDNS, "Please enter a valid ID.");
	};

	that.setAction = function(action, id) {
		var menuItem, panel = null, app = null, hashcode, itemIdPart;
		itemIdPart = hashcode = action;
		
		// allow the proper controller to handle the action
		switch(action) {
			case 'view':
				panel = that.viewC.showApp(that.store.getApp(id));
				hashcode += ':' + id;
				itemIdPart = id;
				break;
			case 'create':
				panel = that.regC.showForm();
				break;
			case 'edit':
				panel = that.editC.editApp(that.store.getApp(id));
				hashcode += ':' + id;
				itemIdPart = id;
				break;
		}
		if(panel === null) {
			itemIdPart = hashcode = action = 'home';
			id = null;
			panel = my._showSimpleView('home');
		}

		// remove previous children and add the new section
		while(my.sectionContent.childNodes.length >= 1) {
			my.sectionContent.removeChild(my.sectionContent.firstChild);
		}
		my.sectionContent.appendChild(panel);
		$j("body").animate({scrollTop:0}, 400); // scroll page to top

		// select corresponding menu item
		if(my.currentMenuItem) {
			$j(my.currentMenuItem).removeClass('selected');
		}
		my.currentMenuItem = document.getElementById('dev_nav_'+itemIdPart);
		if(my.currentMenuItem) {
			my.currentMenuItem.className += ' selected';
		}
		
		// update the hash, for niceities
		window.location.hash = (hashcode === 'home') ? '' : hashcode;
		my.action = action;
		my.appId = id;
	};
	
	that.selectSidebarItem = function(id) {
		if(id === 'home' || id === 'create') {
			return that.setAction(id);
		}
		if(id === my.appId) {
			return false;
		} else {
			that.setAction('view',id);
		}
	}
	
	that.addDismissableNotice = function(text,style) {
		var noticeElem = document.createElement('div'), childElem;
		if(!style) { style = 'notice'; }
		
		noticeElem.className = 'inline-notice ' + style;
		noticeElem.id = 'inline-notice_id:' + (new Date).getTime().toString(16);
		noticeElem.style.display = 'none';

		childElem = document.createElement('div');
		childElem.className = 'dismiss';
		childElem.onclick = function() { $j(noticeElem).slideUp(50, function() { Bko.util.removeElementById(noticeElem.id); }) };
		noticeElem.appendChild(childElem);
		
		childElem = document.createElement('div');
		childElem.className = 'content';
		childElem.appendChild(document.createTextNode(text));
		noticeElem.appendChild(childElem);
		
		my.sectionContent.insertBefore(noticeElem, my.sectionContent.firstChild);
		$j(noticeElem).slideDown(100);
	};
	
	// given an app object, update it's data in the DB and refresh views
	that.refreshAppData = function(data) {
		if(!data || !data.hasOwnProperty('id')) { return; }
		that.store.addApp(data.id, data);
		my._refreshSidebar();
	};
	
	that.removeAppData = function(app) {
		that.store.removeApp(app.id);
		my._refreshSidebar();
	};
	
	my._showSimpleView = function(viewName) {
		var panel;
		
		if(my.panels.hasOwnProperty(viewName)) { 
			panel = my.panels[viewName];
		} else { return null; }

		// enable visibility and any form elements
		panel.style.display = 'block';
		return panel;
	};
	
	my._refreshSidebar = function() {
		var k, sidebar = my.panels.sidebar, apps, app;
		
		while(sidebar.childNodes.length >= 1) {
			sidebar.removeChild(sidebar.firstChild);
		}
		sidebar = sidebar.appendChild(document.createElement('div'));
		sidebar.className = 'apps-vertical-menu';
		
		// insert standard menu items
		for(k=0;k<my.menuItems.length;k++) {			
			sidebar.appendChild(my._createMenuItem(my.menuItems[k]));
		}

		// insert items for apps
		my._appendAppsForStatus('published',sidebar);
		my._appendAppsForStatus('active',sidebar);
		my._appendAppsForStatus('disabled',sidebar);
	};
	
	my._appendAppsForStatus = function(status, container) {
		var apps, item;
		
		apps = that.store.appsByStatus(status);
		if(apps.length) {
			item = document.createElement('div');
			item.className = 'vm-divider';
			item.appendChild(document.createTextNode(status.charAt(0).toUpperCase() + status.slice(1)));
			container.appendChild(item);

			apps.sort(function(a,b) { var a = a.name.toLowerCase(), b = b.name.toLowerCase(); 
				if(a < b) return -1; if(b < a)return 1; return 0; 
				})
			for(k=0;k<apps.length;k++) {
				container.appendChild(my._createMenuItem({
					action: 'view'
					,type: 'app'
					,content: my._createAppItemContent(apps[k])
					,id: apps[k].id
					}));
			}
		}
	};
	
	my._createAppItemContent = function(app) {
		var template = my.panels.appMenuItemTemplate.cloneNode(true);
		
		template.id = null;
		$j('#kv-app-tmp-name',template).text(app.name);
		$j('#kv-app-tmp-name',template).attr('id',null);
		$j('#kv-app-tmp-description',template).text(app.description);
		$j('#kv-app-tmp-description',template).attr('id',null);
		$j('#kv-app-tmp-created',template).text(app.getStatusWithDate);
		$j('#kv-app-tmp-created',template).attr('id',null);
		$j('#kv-app-tmp-icon',template).attr('src', app.getIconUrl());
		$j('#kv-app-tmp-icon',template).attr('id',null);
		
		template.style.display = 'inherit';
		return template;
	};
	
	my._createMenuItem = function(item) {
		var itemView = document.createElement('div');
		itemView.className = 'vm-item vm-'+item.type+'-item';
		$j(itemView).click(function() { return that.selectSidebarItem(item.id); } );
		
		itemView.id = 'dev_nav_'+item.id;
		if(item.type === 'basic') { $j(itemView).text(item.content); }
		else { 
			itemView.appendChild(item.content);
//			itemView.id += ':' + item.id;
		}

		return itemView;
	};
	
	return that;
} ();

////////////////////////////////////////
// App object
Bko.app = function(json_obj) {
	var that = json_obj, my = {};
	
	that.getStatus = function() {
		if(that.isDisabled) { return 'disabled'; }
		else if(that.isPublished) { return 'published'; }
		return 'active';
	};
	
	that.getRedirectUri = function() {
		if(that.callbackUri) { return that.callbackUri; }
		return 'oob';
	};

	that.getIconUrl = function() {
		if(that.image) { return Bko.conf.kivaRoot+'/img/w73h73/'+that.image.id+'.jpg'; }
		return '/img/anonymous_app_icon.png';
	};
	
	that.getStatusWithDate = function() {
		var ret = 'Registered';
		
		if(that.isPublished) { ret = 'Published'; }
		else if(that.isDisabled) { ret = 'Disabled'; }
		
		return ret + ' ' + (new Date(that.createdTime * 1000).toLocaleDateString() );
	};
		
	return that;
};

////////////////////////////////////////
// App db (eg, storage)
Bko.appDb = function() {
	var that = {}, my = { db:{}, keys:[] };

	that.addApp = function(id, appData) {
		// add the id to the key store if we don't have it already
		if(! my.db.hasOwnProperty(id)) { my.keys.push(id); }
		// update the app data
		my.db[id] = Bko.app(appData);
	};
	
	that.getApp = function(id) {
		if(my.db.hasOwnProperty(id)) {
			return my.db[id];
		}
		return null;
	};
	
	that.removeApp = function(id) {
		var k, newKeys = [];
		if(my.db.hasOwnProperty(id)) {
			delete my.db[id];
		}
		for(k=0;k<my.keys.length;k++) {
			if(my.keys[k] !== id) { newKeys.push(my.keys[k]); }
		}
		my.keys = newKeys;
	};
	
	that.allAppIds = function() {
		return my.keys;
	};
	
	that.appsByStatus = function(status) {
		var ret = [];
		
		for(k=0;k<my.keys.length;k++) {
			app = my.db[my.keys[k]];
			if(app.getStatus() == status) {
				ret.push(app);
			}
		}
		return ret;
	};

	my._initialize = function() {
		var k, apps = Bko.data.apps;
		
		for(k=0; k< apps.length;k++) {
			that.addApp(apps[k].id, apps[k]);
		}
	};
	
	my._initialize();
	return that;
};

////////////////////////////////////////////////
// EditController (view app details/actions)
Bko.createEditController = function() {
	var that = {}, my = {
		app: null  // current app for view/action
		,panel: document.getElementById('apps_panel_edit')
	};

	/**
	 * Sets up a new app for editing.
 	 */
	that.editApp = function(app) {
		if(!app) { return null; }
		
		my.app = app;
		my._prepareEditPanelForAppId();
		my.panel.style.display = 'block';
		
		return my.panel;
	};

	that.cancel = function() {
		// return to view state
		Bko.myApps.setAction('view', my.app.id);
	};

	that.saveChanges = function() {
		// attempt save and return to view
		my.form.submit();
	};

	my.onSaveSuccess = function(data) {
		my.form.enable();

		// now add the new app to the DB and update UI
		Bko.myApps.refreshAppData(data.app);
		Bko.myApps.setAction('view', data.app.id.toString(10));
		Bko.myApps.addDismissableNotice('Application changes saved.','success');
	};

	my.onSaveFailure = function(error) {
		my.form.enable();
		return false;
	};

	my._prepareEditPanelForAppId = function() {
		my.form.reset();
		
		$j('#appedit-header',my.panel).text('Edit: ' + my.app.name);

		$j('#appedit-name',my.panel).val(my.app.name);
		$j('#appedit-description',my.panel).val(my.app.description);
		$j('#appedit-appId',my.panel).text(my.app.appId);
		$j('#appedit-callbackUri',my.panel).val(my.app.callbackUri);

		$j('#appedit-websiteUrl',my.panel).val(my.app.websiteUrl);
		$j('#appedit-icon',my.panel).attr('src',my.app.getIconUrl());
		
		if(my.app.canKeepSecrets) {
			$j('#appview-secrets-yes',my.panel).show();
			$j('#appview-secrets-no',my.panel).hide();
		} else {
			$j('#appview-secrets-yes',my.panel).hide();
			$j('#appview-secrets-no',my.panel).show();
		}
		
		// set form action
		$j('#apps_form_edit',my.panel).attr('action',Bko.util.urlForActionOnId('edit',my.app.appId));
	};
	
	my._initialize = function() {
		my.form = Bko.form(document.getElementById('apps_form_edit'), my.onSaveSuccess, my.onSaveFailure);
		my.form.rules = Bko.myApps.appFormRules;
		my.form.messages = Bko.myApps.appFormMessages;
		
		$j('#appedit-action-save',my.panel).click(that.saveChanges);		
		$j('#appedit-action-cancel',my.panel).click(that.cancel);
//		$j('#appedit-action-save',my.panel).click(that.saveChanges);
	}

	my._initialize();
	return that;
};

////////////////////////////////////////////////
// RegistrationController (view app details/actions)
Bko.createRegistrationController = function() {
	var that = {}, my = {
		panel: document.getElementById('apps_panel_create')
		,form: null  // see init
	};

	/**
	 * Sets up a new app for editing.
 	 */
	that.showForm = function() {		
		my._preparePanel();
		my.panel.style.display = 'block';
		
		return my.panel;
	};

	that.register = function() {
		// attempt save and return to view
		my.form.submit();
	};

	my.onRegisterSuccess = function(data) {
		// now add the new app to the DB and update UI
		Bko.myApps.refreshAppData(data.app);
		Bko.myApps.setAction('view', data.app.id.toString(10));
		Bko.myApps.addDismissableNotice('Congratulations! Your new app was registered without a hitch!'
			,'success');
	};

	my.onRegisterFailure = function(error) {
		my.form.enable();
		return false;
	};

	my._preparePanel = function() {
		my.form.reset();
	};
	
	my._initialize = function() {
		var domForm = document.getElementById('apps_form_create');
		my.form = Bko.form(domForm, my.onRegisterSuccess, my.onRegisterFailure);
		my.form.rules = Bko.myApps.appFormRules;
		my.form.messages = Bko.myApps.appFormMessages;

		$j('#appcreate-action-register').click(that.register);

		// set form action (not app-dependent)
		$j('#apps_form_create',my.panel).attr('action',Bko.util.urlForActionOnId('register'));
	}

	my._initialize();
	return that;
};

////////////////////////////////////////////////
// ViewController (view app details/actions)
Bko.createViewController = function() {
	var that = {}, my = {
		app: null  // current app for view/action
		,panel: document.getElementById('apps_panel_view')
	};

	/**
	 * Sets an displayes a new app for this view.
 	 */
	that.showApp = function(app) {
		if(!app) { return null; }
		
		my.app = app;
		my._prepareViewPanelForAppId();
		my.panel.style.display = 'block';
		
		return my.panel;
	};
	
	that.toggleShowSecret = function() {
		var secretContainer = $j('#appview-consumerSecret')
			,secretControl = $j('#appview-action-showSecret');
		
		if(secretContainer.is(':hidden')) {
			secretContainer.slideDown(100);
			secretControl.text('hide');
		} else {
			secretContainer.slideUp(100);
			secretControl.text('reveal');
		}
	}
	
	that.toggleDisable = function() {
		var action = 'enable';

		if(!my.app.isDisabled) { action = 'disable';}
		my._sendActionForApplication(action,my.app.appId);
		return false;
	};

	that.togglePublish = function() {
		var action = 'unpublish';

		if(!my.app.isPublished) { action = 'publish';}
		my._sendActionForApplication(action,my.app.appId);
		return false;
	};

	that.resetSecrets = function() {
		my._sendActionForApplication('reset_tokens',my.app.appId);
		return false;
	};

	that.edit = function() {
		Bko.myApps.setAction('edit', my.app.id);
	};

	that.appDelete = function() {
		if(confirm("Are you sure you want to delete this app?\n\nAll tokens will be destroyed. Any applications using this ID will no longer have access to the API. You cannot reuse the same App ID after you have delted this app.")) 		{
			my._sendActionForApplication('delete',my.app.appId);
		}
		return false;
	};
;
	my._sendActionForApplication = function(action, appId) {
		// todo: disable all buttons before until action completed
		$j(':button',my.panel).attr('disabled',true);

		jQuery.ajax({
			'type':'POST'
			,'url':Bko.util.urlForActionOnId(action,appId)
			,'success':(action === 'delete') ? my._onAppActionDeleteSuccess : my._onAppActionUpdateSuccess
			,'error':my._onAppActionUpdateFailure
			,'dataType':'json'
		});
	};
	
	my._onAppActionUpdateSuccess = function(data) {
		// absorb new data, refresh view
		Bko.myApps.refreshAppData(data.app);
		Bko.myApps.setAction('view', data.app.id);

		$j(':button',my.panel).attr('disabled',false);
		Bko.myApps.addDismissableNotice('Great! That action succeeded.','success');
	};
	my._onAppActionDeleteSuccess = function(data) {
		Bko.myApps.removeAppData(my.app);
		Bko.myApps.setAction('home');

		$j(':button',my.panel).attr('disabled',false);
		Bko.myApps.addDismissableNotice('Application deleted.','failure');	
	};
	my._onAppActionUpdateFailure = function(data) {
		alert("Sorry, the operation failed. Please try refreshing the page, or contact Kiva if you continue to have problems..");
		$j(':button',my.panel).attr('disabled',false);
	};	
	
	/**
	 * One-time setup stuff
 	 */
	my._initialize = function() {
		$j('#appview-action-disable',my.panel).click(that.toggleDisable);		
		$j('#appview-action-publish',my.panel).click(that.togglePublish);
		$j('#appview-action-resetSecrets',my.panel).click(that.resetSecrets);
		$j('#appview-action-edit',my.panel).click(that.edit);
		$j('#appview-action-delete',my.panel).click(that.appDelete);
		$j('#appview-action-showSecret').click(that.toggleShowSecret);
	};
	
	/**
	 * Set properties of view panel for display
 	 */
	my._prepareViewPanelForAppId = function() {		
		var app = my.app;
		if(!app) return;
		
		$j('#appview-name',my.panel).text(app.name);
		$j('#appview-statusDate',my.panel).text( app.getStatusWithDate() );
		$j('#appview-statusDate',my.panel).attr('class',app.getStatus()+'-state');
		if(app.websiteUrl) {
			$j('#appview-website',my.panel).text(app.websiteUrl);
			$j('#appview-website',my.panel).attr('href',app.websiteUrl);
			$j('#appview-website',my.panel).show();
		} else {
			$j('#appview-website',my.panel).attr('href','');
			$j('#appview-website',my.panel).hide();
		}
		$j('#appview-icon',my.panel).attr('src',app.getIconUrl());
		$j('#appview-description',my.panel).text(app.description);
		$j('#appview-appId',my.panel).text(app.appId);
		$j('#appview-consumerKey',my.panel).text(app.appId);
		$j('#appview-redirectUri',my.panel).text(app.getRedirectUri());

		// secret
		$j('#appview-consumerSecret', my.panel).hide();
		$j('#appview-action-showSecret', my.panel).text('reveal');
		$j('#appview-consumerSecret',my.panel).text(app.clientSecret);
		if(app.clientSecret && app.clientSecret.length > 0) {
			$j('#appview-action-showSecret', my.panel).show();
			$j('#appview-secretIntro', my.panel).show();
			$j('#appview-noSecret', my.panel).hide();
		} else {
			$j('#appview-action-showSecret', my.panel).hide();
			$j('#appview-secretIntro', my.panel).hide();
			$j('#appview-noSecret', my.panel).show();
		}
		
		// stats (set randomly now for fun)
		rn = Math.round(Math.random()*100);
		$j('#appview-usersRegistered',my.panel).text(rn);
		$j('#appview-tokensIssued',my.panel).text(rn+6);
		$j('#appview-tokensRevoked',my.panel).text(Math.round(rn/4));
		
		
		// some fields/controls, conditional
		if(app.canKeepSecrets) {
			$j('#appview-identityClassification',my.panel).html('<code>VERIFYIBLE</code> - You must keep your client secret confidential and use it to sign all OAuth requets. This allows Kiva Partners to authorize your application.');
			$j('#appview-partnerCallback',my.panel).show();
		} else {
			$j('#appview-identityClassification',my.panel).html('<code>FORGEABLE</code> - Since your app can’t keep secrets you have not been issued any. The identity of your app can be spoofed by other applications and as a result your app is somewhat limited to which permissions it can request from users.');
			$j('#appview-partnerCallback',my.panel).hide();
		}
		
		// update UI based on state
		$j('#appview-action-disable',my.panel).text('Disable'); // reset these for simplicity
		$j('#appview-action-publish',my.panel).text('Publish');
		$j('#appview-action-disable',my.panel).attr('disabled',false);
		$j('#appview-action-publish',my.panel).attr('disabled',false);
		$j('#appview-action-disable',my.panel).removeClass('green');
		if(app.isDisabled) {
			$j('#appview-action-publish',my.panel).attr('disabled',true);
			$j('#appview-action-disable',my.panel).text('Enable');
			$j('#appview-action-disable',my.panel).addClass('green');
			if(app.isDisabledByKiva) {
				$j('#appview-action-disable',my.panel).attr('disabled',true);
				Bko.myApps.addDismissableNotice("This application has been disabled by Kiva. You cannot re-enable it from this panel. Please contact customer service for more information about your app.",'failure');
			}
		}
		if(app.isPublished) {
			$j('#appview-action-publish',my.panel).text('Unpublish');
		}
	};

	my._initialize();
	return that;	
};

////////////////////////////////////////////////
// Form object - for now, just supports create
Bko.form = function(form,success,failure) {
	var that = { rules: {} }, my = { domForm: form, onSuccess:success, onFailure:failure };
	
	that.reset = function() {
		// prepare the form for future display.
		$j(':input',my.domForm).attr('disabled',false);
		my.domForm.reset();	
	};
	
	that.enable = function() {
		// prepare the form for editing again
		$j(':input',my.domForm).attr('disabled',false);
	};

	that.disable = function() {
		// prepare the form for editing again
		$j(':input',my.domForm).attr('disabled',true);
	};
	
	that.submit = function() {
		$j(form).validate({
			rules: that.rules
			,messages: that.messages
			,submitHandler: my._onValidationSuccess
			,errorClass: "invalid"
		});
		$j(form).submit();
	};

	my._onValidationSuccess = function(domForm) {
		console.log("Validate Success!");
		
		// use a robust ajax request unless icon upload requires iframe
		if(form.elements['icon'].value === "") {
			my._ajaxSubmit();
		} else {
			my._iframeSubmit();
		}
		
		// disable all form elements before we let the user do stuff
		that.disable();
		return false;
	};

	my._onSuccessPrehandler = function(data, textStatus, jqXHR) {
		// need more sophisticated data object validation
		console.log('request success: '+JSON.stringify(data)); 
		if(!(data.hasOwnProperty('app') && data.app.hasOwnProperty('id'))) {
			throw "Invalid app object returned from form submission.";
		}
		
		if(my.onSuccess) { my.onSuccess(data); }
	};
	my._onFailurePrehandler = function(error) {
		var errorTxt;

		// give a custom handler a chance to process the error.
		if(my.onFailure && my.onFailure(error)) { return; }
		
		// otherwise, we show a backup error dialog
		errorTxt = "Sorry, your request failed. You probably entered some invalid values that we failed to detect before the request was submitted. Please report this error to Kiva.";
		if(error) {
			errorTxt += "\n\n(Status: " + error.status;
			if(error.code) { errorTxt += ", Code: " + error.code + ", " + error.message; }
			errorTxt += ")";
		}
		alert(errorTxt);
	};
	
	my._ajaxSubmit = function() {
		var params = $j(form).serialize();
		console.log(params);
		
		jQuery.ajax({
			'type':'POST'
			,'url':form.action
			,'data':params
			,'success':my._onSuccessPrehandler
			,'error':my._onAjaxFailurePrehandler
			,'dataType':'json'
			,'jsonp': false
			,'jsonpCallback': "noCallback"
		});
	};

	my._onAjaxFailurePrehandler = function(jqXHR, textStatus, errorThrown) {
		var error;
		console.log('AJAX request failure: '+ JSON.stringify(jqXHR));
		console.log(jqXHR.responseText);
		console.log(textStatus);
		console.log(errorThrown);

		// try to get error data on expected API-type failures
		try {error = JSON.parse(jqXHR.responseText);} catch(e) {};
		if(error && error.hasOwnProperty('error')) { error = error.error; }
		else { error = {}; }
		error.status = jqXHR.status;

		my._onFailurePrehandler(error);
	};
	
	my._iframeSubmit = function(cbSuccess, cbFailure) {
		var iframeId = 'iframe_submit_target_' + (new Date).getTime()
			, iframeObj = my._createSubmissionIframe(iframeId);

console.log(my.domForm);
		my.domForm.target = iframeId;
		my.domForm.elements['iframe_callback'].value = 'Bko.myApps.iframeSubmitCallback';
		my.domForm.appendChild(iframeObj);

		Bko.myApps.iframeSubmitCallback = function() {
			my._onIframeCallback(iframeId);
		};
		
		my.domForm.submit();
	};
	
	my._onIframeCallback = function(iframeId) {
		var responseDiv = window.frames[iframeId].document.getElementById('response')
			, error, responseObj = { status: 200, code: "unknown", message: "Malformed server response." };
			
		// remove temporary iframe object	(async)	
		setTimeout( function() { 
			var iframeObj = document.getElementById(iframeId);
			console.log("IFID: " + iframeId);
			if(iframeObj) { iframeObj.parentNode.removeChild(iframeObj); }
			},1);
		// prevent unexpected future callbacks
		Bko.myApps.iframeSubmitCallback = null;		

		// check the response and make the proper callback
		try {
			if(responseDiv) {
				responseObj = jQuery.parseJSON(responseDiv.textContent);
			}
		} catch(e) {
			console.log("Failed to parse JSON response.");
			error.message = "Unable to parse server JSON response.";
		}
		if(responseObj) {
			if(responseObj.hasOwnProperty('error')) {
				error = responseObj.error;
			} else {
				my._onSuccessPrehandler(responseObj); 
				return;
			}
		}		

		my._onFailurePrehandler(error);
	};
	
	my._createSubmissionIframe = function(formId) {
		var iframeObj = document.createElement('iframe');
		
		iframeObj.id = formId;
		iframeObj.style.display = 'none';
		
		return iframeObj;
	};
/*
	my._mapify = function(arr) {
		var k, map = {};
		for(k=0;k<arr.length;k++) {
			map[arr[k].name] = arr[k].value;
		}
		return map;
	}
*/
	
	return that;
};

window.onload = function() {
	if(Bko.myApps.isLoaded()) {
		Bko.myApps.initialize();
	}
};
