/*
 * PiAnoS (Picture Annotation System), A fingerprint examiner training tool
 * Copyright (C) 2011-2012 University of Lausanne, Switzerland
 * https://ips-labs.unil.ch/pianos
 * 
 * This program is free software: you can redistribute it and/or modify it under
 * the terms of the GNU Affero General Public License as published by the Free
 * Software Foundation, either version 3 of the License, or (at your option) any
 * later version.
 * 
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
 * details.
 * 
 * You should have received a copy of the GNU Affero General Public License
 * along with this program. If not, see http://www.gnu.org/licenses/.
 */

/**
 * The main object representing the interface (*needs* to be a singleton)
 * 
 * @constructor
 */
function Pianos4Backend(params)
{
	udb.inherit(this, udb.application, params, 'Pianos4Backend');
	
	// ------------------------------
	// Constructor
	// ------------------------------
	params = params || {};
	if (!params.divid) params.divid = 'pianos-backend';
	if (!params.user) params.user = { id: 0, name: 'Unnamed user', tutor: false };
	if (!params.icons) params.icons = { 
		folder: ['css/icons/backend/folder.gif','css/icons/backend/folderopen.gif'], 
		pgroup: ['css/icons/backend/Address-book16.png','css/icons/backend/Address-book-open16.png'],
		exercise: 'css/icons/backend/page.gif',
		person: 'css/icons/backend/Person16.png',
		person_inactive: 'css/icons/backend/Person16.png',
		_delete: 'css/icons/backend/Delete16.png',
		adduser: 'css/icons/backend/NewPerson16.png',
		addgroup: 'css/icons/backend/NewAddress-book16.png',
		refresh: 'css/icons/backend/arrow_refresh.png',
		management: 'css/icons/backend/cog_edit.png',
		image: 'css/icons/backend/images.png',
		message: ['css/icons/backend/page.png','css/icons/backend/page_add.png','css/icons/backend/page_edit.png','css/icons/backend/page_delete.png'],
	};
	if (typeof(params.hasSearchBox) == 'undefined') params.hasSearchBox = false;
	if (typeof params.showResultsPreview == 'undefined') params.showResultsPreview = false;
	
	// ------------------------------
	// Public methods
	// ------------------------------
	
	this.run = function()
	{
		self.__super__.run();
		
		model = new udb.model({
			apiUrlArgs: [['action','rpc']],
		});
		self.setModel(model);

		buildUI();
	}

	this.cache = function(key, value)
	{	
		if (typeof(value) == 'undefined')
		{
			if (!(key in _cache))
				return null;
			return _cache[key];
		}
		else
		{
			_cache[key] = value;
		}
	}
	
	this.refreshTreePart = function(branch)
	{
		tree.iterate(function(item) {
			if (item.data && item.data.type == branch)
			{
				tree.reload(item.id);
				return true;
			}
		});
	}
	
	// ------------------------------
	// Private variables
	// ------------------------------
	
	var self = this;
	var layoutObj = null;
	var tree = null;
	var api = null;
	var panels = {};
	var model = null;
	var _cache = {};
	var searchResults = null;
	var treeFilters = {
		showInactiveUsers: false,
		showPersonalFolders: false,
	};
	var treeItems = {
	};
	
	// ------------------------------
	// Private functions
	// ------------------------------
	

	var buildUI = function()
	{
		var mainDiv = $('#'+params.divid);
		
		mainDiv
			.append(buildTopPane())
			.append(buildLeftPane())
			.append(buildCenterPane());

		mainDiv.css('height', '100%');

		var state = Pianos4Backend.getOrCreateStateCookie();
		var onSidePaneResized = function(direction, pane, item) {
			var state = Pianos4Backend.getOrCreateStateCookie();
			state.layout[direction] = item.size;
			Pianos4Backend.saveStateCookie(state);
		}
		
		layoutObj = mainDiv.layout({
				defaults: {
					fxName: 'none',
					// spacing_open: params.layoutBorderWidth,
					// spacing_closed: params.layoutBorderWidth,
					// applyDefaultStyles: true,
				},
				center: {
					onresize: function() { onWindowSizeChanged(false); },
				},
				west: {
					size: state.layout.west,
					onresize: onSidePaneResized,
				},
				north: {
					resizable: false,
					closable: false,
					spacing_open: 0,
					size: 32,
				},
			});

		mainDiv.addClass('evt_udb-tree-selchanged').bind('udb-tree-selchanged', function(event, data) { 
			onTreeItemSelected(data.cur);
		});
		
		$(window).bind('hashchange', onHashChange);
		
		// Call onWindowSizeChanged now to perform the initial layout
		onWindowSizeChanged(true);

		onHashChange();
	}

	var buildTopPane = function()
	{
		var div = $('<div />');
		div.addClass('ui-layout-north ui-widget');
		div.append(
			$('<div />').addClass('ui-widget-content')
				.append($('<span />').addClass('app_title').text('PiAnoS 4').click(function() {
				}))
				.append($('<img class="loading" src="css/throbber-18-black-on-white.gif" title="Initializing" alt="Loading ..." />'))
				.append($('<span />').addClass('main_links'))
		);
		
		var ml = div.find('.main_links');
		
		if( params.user.tutor & params.view.show_switchUser )
		{
			ml.append( $('<a />').css( 'cursor', 'pointer' ).text( 'Switch user' ).click(
				function(){
					var complete = function( data )
					{
						var dlgopts = {
							autoOpen : true,
							modal : false,
							width : 800,
							title : 'All active users',
							close : function()
							{
								$( this ).remove();
							},
							buttons : {
								'Close' : function()
								{
									$( this ).dialog( 'close' );
								}
							},
						};
						
						if( !data || data.error )
						{
							div.addClass( 'ui-state-error' ).css( 'padding', '0 0.5em' ).append( $( '<h3 />' ).text( 'error' ) );
							if( data && data.message )
								div.append( $( '<pre />' ).text( data.message ) );
						} else {
							var div = $( '<div />' );
							var id_base = 'pianos4_resultdlg_opt_' + ( new Date().getTime() );
							
							div.qtable( {
								data : data,
								columnMode : 'mapping',
								columns : [
									{
										title : 'User ID',
										render : function( data, i, j, cell )
										{
											cell.text( data[ i ].id );
										}
									},
									{
										title : 'Name',
										render : function( data, i, j, cell )
										{
											cell.append( $( '<a />' ).text( data[ i ].name ).attr( 'href', '?action=switchUser&user=' + data[ i ].id ) );
										}
									},
									{
										title : 'Account type',
										render : function( data, i, j, cell )
										{
											cell.append( $( '<a />' ).text( data[ i ].tutors ) );
										}
									},
								],
							} );
							
							var dlg = $( '<div />' ).addClass( 'pianos4_dialog' ).css( 'max-height', $( window ).height() - 140 ).append( div );
							
							dlg.dialog( dlgopts );
						}
					}
					
					model.rpc('getUserList', {}, function( users ) {
						complete( users );
					} )
				}
			) );
		}
		
		$([['Logout ('+params.user.name+(params.user.tutor ? ' - Tutor' : '')+')','?action=logout']]).each(function() { ml.append($('<a />').css( 'margin-left', '30px' ).attr('href',this[1]).text(this[0])); });
		
		return div;
	}
	
	var processDir = function( folder, contextMenuFunc )
	{
		if( folder.type == "folder" )
		{
			return {
				label : folder.name,
				icon : params.icons.folder,
				hasChildren : parseInt( folder.numexercisesandfolders ) > 0,
				data : {
					type : 'folder',
					entity : folder
				},
				cmenu : contextMenuFunc,
				load : [
				    function( cb ) {
						model.rpc(
							'getFolderExercisesAndSubFolders',
							{
								countExercises: true,
								showPersonal: treeFilters.showPersonalFolders,
								folderID: folder.id
							},
							cb
						);
						
					},
					
					function( ex )
					{
						return processDir( ex, contextMenuFunc )
					}
				]
			}
			
		} else if( folder.type == "exercise" ) {
			return {
				label : folder.name,
				icon : params.icons.exercise,
				data : {
					type : 'exercise',
					entity : folder
				},
				cmenu : contextMenuFunc,
			}
		}
	}
	
	var buildLeftPane = function()
	{
		var div = $('<div />');
		div.addClass('ui-layout-west ui-widget');
		var inner = $('<div />').appendTo(div);
		
		var udbNode = new udb.node({element:inner});
		tree = new udb.tree({
			parent: udbNode,
			title:'PiAnoS database',
			onItemExpanded: function(item) { onTreeItemExpanded(item); },
			onItemCollapsed: function(item) { onTreeItemCollapsed(item); },
		});
		
		if (params.user.tutor && params.hasSearchBox)
		{
			var onClick = function() {
				var value = $('#pianos4_search_box input').val();
				if (value.length == 0)
					return;
				form.find('input').prop('disabled', true);
				form.find('button').button('disable');
				onSearch(value, function() { 
					form.find('input').prop('disabled', false);
					form.find('button').button('enable');
				});
			};

			var search = new udb.panel({parent:udbNode, title:'Search'});
			var form = $('<div />').attr('id','pianos4_search_box')
				.append($('<form />').bind('submit', function(e) { e.preventDefault(); onClick(); })
					.append($('<input />').attr('type','text').css('width','100%').css('max-width','80%'))
					.append($('<button />').text('OK').css('width','15%').button())
				);
			form.find('button').click(function() { onClick(); }).find('.ui-button-text').css('padding',0);
			search.dom(true).append(form);
		}
		
		var filters = null;
		if (params.user.tutor)
		{
			filters = new udb.panel({parent:udbNode, title:null});
			var form = $('<div />').attr('id','pianos4_tree_filters').css('font-size','0.8em')
				.append($('<form />').bind('submit', function(e) { e.preventDefault(); })
					.append($('<input />').attr('id', 'p4_tree_filter_inactive').attr('type','checkbox').click(function() { onTreeFiltersChanged(); }))
					.append($('<label />').attr('for', 'p4_tree_filter_inactive').text('Inactive users'))
					.append($('<input />').attr('id', 'p4_tree_filter_personal').attr('type','checkbox').click(function() { onTreeFiltersChanged(); }))
					.append($('<label />').attr('for', 'p4_tree_filter_personal').text('Personal folders'))
				);
			filters.dom(true).append(form);
		}
		
		var sz = new udb.sizer('vertical');
		sz.add(tree, 1, udb.sizer.EXPAND);
		if (filters)
			sz.add(filters, 0, udb.sizer.EXPAND);
		if (search)
			sz.add(search, 0, udb.sizer.EXPAND);
		udbNode.setSizer(sz);
		
		var contextMenuFunc = params.user.tutor ? onTreeContextMenu : null;
		
		tree_exercises = tree.addEx({
			parent: tree.root(), 
			label: 'All exercises', 
			icon: params.icons.folder,
			data: {type:'allex'},
			cmenu: contextMenuFunc,
			load: [	function(cb) { 
						model.rpc('getFolders', {'countExercises':true, 'showPersonal':treeFilters.showPersonalFolders}, cb); 
					}, 
					
					function( folder )
					{
						return processDir( folder, contextMenuFunc )
					}
			],
		});
		treeItems.folders = tree_exercises;
		
		var autoLoadExCallback = null;
		if (params.user.tutor)
		{
			autoLoadExCallback = function(item) {
				var state = Pianos4Backend.getOrCreateStateCookie();
				tree.iterate(function(item) {
					if (item.data && item.data.type == 'folder' && item.data.entity.id in state.tree.folders)
						tree.load(item.id);
				});
			};
		}
		else
		{
			autoLoadExCallback = function(item) {
				var folders = tree.getItemChildren(item.id);
				for (var i in folders) 
					tree.load(folders[i].id); 
			};
		}
		
		tree.load(tree_exercises, null, null, autoLoadExCallback);
		
		if (params.user.tutor)
		{
			var tree_users = tree.addEx({
				parent: tree.root(), 
				label: 'All users', 
				icon: params.icons.pgroup,
				data: {type:'allusers'},
				cmenu: contextMenuFunc,
				load: [	function(cb) { 
							model.rpc('getUserGroups', {'countUsers':true, 'showInactive':treeFilters.showInactiveUsers}, cb); 
						}, 
						function(group) {
							return {
								label: group.name, 
								icon: params.icons.pgroup,
								hasChildren: parseInt(group.nummembers) > 0,
								data: {type:'pgroup', entity: group},
								cmenu: contextMenuFunc,
								load: [function(cb) {
											model.rpc('getUsers', {'groupID':group.id, 'showInactive':treeFilters.showInactiveUsers}, cb); 
										}, 
										function(user) {
											return {
												label: user.name, 
												icon: function() {
													return params.icons.person;
												},
												data: {type:'person', entity: user},
												cmenu: contextMenuFunc,
											};
										}
								],
							};
						}
				],
			});
			treeItems.pgroups = tree_users;
			
			var autoLoadGroupsCallback = function(item) {
				var state = Pianos4Backend.getOrCreateStateCookie();
				tree.iterate(function(item) {
					if (item.data && item.data.type == 'pgroup' && item.data.entity.id in state.tree.pgroups)
						tree.load(item.id);
				});
			}
			
			tree.load(tree_users, null, null, autoLoadGroupsCallback);
			
			var tree_management = tree.addEx({
				parent: tree.root(),
				label: 'Management',
				icon: params.icons.management,
				data: {type:'management'},
			});
			var tree_management_images = tree.addEx({
				parent: tree_management,
				label: 'Images',
				icon: params.icons.image,
				data: {type:'manage_images',hash:'manage/images'},
			});
			var tree_management_messages = tree.addEx({
				parent: tree_management,
				label: 'Messages',
				icon: params.icons.message[0],
				data: {type:'manage_images',hash:'manage/messages'},
			});
		}
		
		// for (var i in $.browser)
			// log('$.browser.'+i+' = '+$.browser[i]);
		
		return div;
	}

	var buildCenterPane = function()
	{
		var div = $('<div />');
		div.addClass('ui-layout-center ui-widget');
		div.append($('<div />').addClass('ui-widget-content'));
		return div;
	}

	var onWindowSizeChanged = function(isInitial)
	{
		var left = $('.ui-layout-west');
		var leftsz = [left.width(),left.height()];
		log(leftsz);
		tree.getParent().dom().width(leftsz[0]).height(leftsz[1]);
		tree.getParent().layout();
	}
	
	this.has_updateUI = true;
	this.updateUI = function(operations)
	{
		var loading = $('#'+params.divid+' .loading');
			
		if (operations.length > 0)
		{
			loading.removeCSS('visibility');
			var label = 'Working ...';
			for (var op in operations) { if (op=='length') continue; label = operations[op]; break; }
			setStatus(label);
		}
		else
		{
			loading.css('visibility','hidden');
			
			setStatus('Ready');
		}
	}

	var setStatus = function(status)
	{
		log('Status: '+status);
		var loading = $('#'+params.divid+' .loading');
		loading.attr('title', status);
	}
	
	var onHashChange = function(event)
	{
		var hash = location.hash;
		log("onHashChange() : '"+hash+"'");
		var constructor = null;
		var opts = null;
		if (hash.length > 1)
		{
			var path = hash.substr(1, hash.length);
			log('QUERY='+path);
			var tokens = path.split('/');
			if (tokens.length >= 2)
			{
				var id = parseInt(tokens[1]);
				var objtype = tokens[0];
				if (id > 0)
				{
					switch (objtype)
					{
					case 'exercise':
						if (tokens.length > 2)
						{
							var clone = tokens[2] == 'clone';
							if (tokens[2] == 'edit' || clone)
							{							
								constructor = Pianos4Backend.CreateExercise;
								opts = {
									onCreate: function(id) {
										// tree.reload(item.parent);
										tree.reload(tree_exercises);
										location.hash = 'exercise/'+id;
									},
									id: id,
									user: params.user,
									clone: clone,
								};
							}
						}
						
						if (!constructor)
							constructor = params.user.tutor ? Pianos4Backend.AdminExercise : Pianos4Backend.UserExercise;

						if (!opts)
							opts = {id: id, user: params.user, showResultsPreview: params.showResultsPreview};
							
						break;
						
					case 'createexercise':
						constructor = Pianos4Backend.CreateExercise;
						opts = {
							onCreate: function(exid) {
								refreshTree('folder', id);
								location.hash = 'exercise/'+exid;
							},
							folder: id,
							user: params.user,
						};
						break;
						
					case 'folder':
						// if (params.user.tutor)
						constructor = Pianos4Backend.AdminFolder;
						opts = {
							id: id,
							user: params.user
						};
						break;
						
					case 'person':
						constructor = Pianos4Backend.AdminUser;
						opts = {
							id: id,
						};
						break;
					}
				}
				else if (objtype == 'manage')
				{
					switch (tokens[1])
					{
					case 'images':
						constructor = Pianos4Backend.ImageManager;
						var state = Pianos4Backend.getOrCreateStateCookie();
						opts = {
							pageLength: state.imageman.page,
						};
						break;
					
					case 'messages':
						constructor = Pianos4Backend.MessageManager;
						opts = {
							icons: params.icons.message,
						};
						break;
					}
				}
			}
		}
			
		if (!constructor)
		{
			if (params.user.tutor)
				constructor = Pianos4Backend.AdminHome;
			else
				constructor = Pianos4Backend.UserHome;
			opts = {
				user: params.user,
				view: params.view
			}
		}
		
		if (constructor)
			showPanel(constructor, opts);
	}
	
	var showPanel = function(constructor, params)
	{
		var pane = $('.ui-layout-center>.ui-widget-content');
		pane.html('');
		var container = $('<div />').appendTo(pane);
		var panel = new constructor(container, params);
		panel.init();
	}
	
	var getPopupOptions = function(item)
	{
		switch (item.data.type)
		{
		case 'allex':
			return _getPopupOptions([['Refresh', onRefreshNode, params.icons.refresh],'--',['Create new folder', onCreateNewFolder, params.icons.folder[0]]]);
			break;
		case 'folder':
			return _getPopupOptions([
				['Refresh', 
					onRefreshNode, 
					params.icons.refresh],
				'--',
				['Create new exercise', 
					function(item) {
						location.hash = 'createexercise/'+item.data.entity.id;
					},
					params.icons.exercise],
				['Rename', onEditFolder, params.icons.exercise],
				'--',
				['Create new folder', onCreateNewFolder, params.icons.folder[0]],
				['Delete folder', 
					onDeleteFolder, 
					params.icons._delete]
			]);
			break;
		case 'allusers':
			return _getPopupOptions([['Refresh', onRefreshNode, params.icons.refresh],'--',['Create new group', onCreateNewGroup, params.icons.addgroup]]);
			break;
		case 'pgroup':
			return _getPopupOptions([['Refresh', onRefreshNode, params.icons.refresh],'--',['Create new user', onCreateNewUser, params.icons.adduser],['Rename', onEditUserGroup, params.icons.person],'--',['Delete group', onDeleteGroup, params.icons._delete]]);
			break;
		case 'person':
			return _getPopupOptions([['Edit user', onEditUser, params.icons.person],'--',['Delete user', onDeleteUser, params.icons._delete]]);
			break;
		case 'exercise':
			return _getPopupOptions([
				['Edit exercise', 
					function(item) {
						location.hash = 'exercise/'+item.data.entity.id+'/edit';
					}, 
					params.icons.exercise],
				'--',
				['Delete exercise', 
					onDeleteExercise, 
					params.icons._delete]
			]);
			break;
		default:
			return [];
			break;
		}	
	}
	
	var _getPopupOptions = function(items)
	{
		var opt = [];
		for (var i in items)
		{
			var item = items[i];
			if (typeof(item) == 'string' || !item.length)
				opt.push(item);
			else
			{
				var o = { label:item[0], action:item[1] };
				if (item.length>2)
					o.icon = item[2];
				opt.push(o);
			}
		}
		return opt;
	}

	var onTreeContextMenu = function(item, evt)
	{
		assert(params.user.tutor);
		
		var options = getPopupOptions(item);
		
		if (options.length == 0)
			return;
			
		var w = $(window);
		var cancel = $('<div />').css('position','absolute').css('top',0).css('left',0).css('width',w.width()).css('height',w.height()).css('z-index','9').bind('mousedown', function(e) {
			menu.remove();
			cancel.remove();
			$(document).unbind('mousedown', cancel);
		}).appendTo('body');
			
		
		var menu = $('<ul />').css('position','absolute').css('top',evt.clientY).css('left',evt.clientX).css('z-index','10');
		for (var i in options) (function(opt) {
			if (typeof(opt) == 'string')
			{
				menu.append($('<li />').addClass('ui-menu-item').append($('<div />').css('width','100%').css('height','1px').css('margin','3px').css('background-color','#dddddd')));
			}
			else
			{
				var icon = !opt.icon ? '' : $('<span />').css('display','inline-block').css('vertical-align','middle').append($('<img />').attr('src',opt.icon));
				menu.append($('<li />').append($('<a />').append($('<div />').append(icon).append($('<span />').text(opt.label).css('display','inline-block').css('margin-left','0.5em').css('vertical-align','middle'))).attr('href','#').bind('click', function(e) {
					e.preventDefault();
					log('chose '+opt.label);
					menu.remove();
					cancel.remove();
					opt.action(item);
				})));
			}
		})(options[i]);
		menu.menu().appendTo('body');
	}
	
	var onRefreshNode = function(item)
	{
		tree.reload(item.id);
	}
	
	var onCreateNewFolder = function(item)
	{
		if( item.data.type == "allex" )
			childof = null;
		else
			childof = item.data.entity.id;
		
		var dlgopts = {
			title: 'New folder',
			fields: [
				{ type: 'text', name: 'name', label: 'Name', constraints: ['not-empty'] },
			],
			ok: function(data) {
				assert('name' in data);
				model.rpc('createFolder', {'name': data.name, 'childof': childof}, function() {
					tree.reload(item.id);
				});
			},
		};
		
		Pianos4Backend.showFormDialog(dlgopts);
		
	}

	var _onEditItemName = function(item, api_func)
	{
		var dlgopts = {
			title: "Rename '"+item.data.entity.name+"'",
			fields: [
				{ type: 'text', name: 'name', label: 'Name', value: item.data.entity.name, constraints: ['not-empty'] },
			],
			ok: function(data) {
				assert('name' in data);
				model.rpc(api_func, {'id': item.data.entity.id, 'name': data.name}, function() {
					tree.reload(item.parent);
				});
			},
		};
		
		Pianos4Backend.showFormDialog(dlgopts, true);
	}
	
	var onEditFolder = function(item)
	{
		log('editing folder');
		log(item);
		_onEditItemName(item, 'setFolderName')
	}
	
	var onEditUserGroup = function(item)
	{
		log('editing user group');
		log(item);
		_onEditItemName(item, 'setUserGroupName')
	}
	
	var onCreateNewGroup = function(item)
	{
		var dlgopts = {
			title: 'New user group',
			fields: [
				{ type: 'text', name: 'name', label: 'Name', constraints: ['not-empty'] },
			],
			ok: function(data) {
				assert('name' in data);
				model.rpc('createUserGroup', {'name': data.name}, function() {
					self.cache('acl-groups',null);
					tree.reload(item.id);
				});
			},
		};
		
		Pianos4Backend.showFormDialog(dlgopts);
	}
	
	var onCreateNewUser = function(item)
	{
		var dlgopts = {
			title: 'New user',
			fields: [
				{ type: 'text', name: 'username', label: 'Username', constraints: ['not-empty'] },
				{ type: 'text', name: 'name', label: 'Display name', constraints: ['not-empty'] },
				{ type: 'password', name: 'password', label: 'Password', constraints: ['not-empty']  },
				{ type: 'password', name: 'password2', label: 'Repeat password', constraints: {'not-empty':true,'must-match':['password','Password and confirmation must match']} },
			],
			ok: function(data) {
				assert('username' in data && 'name' in data && 'password' in data);
				model.rpc('createUser', {'username':data.username, 'name': data.name, 'password':data.password, 'group':item.data.entity.id}, function() {
					tree.reload(item.id);
				});
			},
		};
		
		Pianos4Backend.showFormDialog(dlgopts);
		
	}
	
	var onDeleteGroup = function(item)
	{
		var dlgopts = {
			title: 'Confirm user group deletion',
			fields: [
				{ type: 'static', label: 'Are you sure you wish to delete the user group '+item.label+' ?' },
			],
			ok: function(data) {
				model.rpc('deleteUserGroup', {'id': item.data.entity.id}, function() {
					self.cache('acl-groups', null);
					tree.reload(tree.getItemParent(item.id).id);
				});
			},
		};
		
		Pianos4Backend.showFormDialog(dlgopts);	
	}
	
	var onDeleteUser = function(item)
	{
		var datadiv = $('<div />').append($('<img class="loading" src="css/throbber-18-black-on-white.gif" alt="Loading ..." />')).append($('<span />').text('Gathering user data ...'));
		
		var dlgopts = {
			title: 'Confirm user deletion',
			fields: [
				{ type: 'static', label: 'Are you sure you wish to delete the user '+item.label+' ?' },
				{ type: 'dom', content: datadiv },
			],
			ok: function(data) {
				model.rpc('deleteUser', {'id': item.data.entity.id}, function() { 
					tree.reload(tree.getItemParent(item.id).id);
				});
			},
		};
		
		Pianos4Backend.showFormDialog(dlgopts);
		
		model.rpc('getUserDataSummary', {'id': item.data.entity.id}, function(summary) {
			datadiv.html('').append($('<div />').css('font-weight','bold').text('User data that will be deleted:'))
				.append($('<div />').text(summary.assignments+' assignment(s)'));
		});
	}
	
	var onEditUser = function(item)
	{
		assert(item.data && item.data.entity);
		var user = item.data.entity;
		log(user);
		assert(user.groupids.length == user.groups.length && user.groupids.length == 1);
		var groupid = user.groupids[0];
		
		var showDialog = function(groups)
		{
			var dlgopts = {
				title: 'Edit user',
				fields: [
					{ type: 'text', name: 'username', label: 'Username', value: user.username, disabled:true },
					{ type: 'text', name: 'name', label: 'Display name', constraints: ['not-empty'], value: user.name },
					{ type: 'select', name: 'group', label: 'Group', values: groups, value: groupid },
					{ type: 'checkbox', name: 'active', label: 'Active', values: groups, checked: user.active },
				],
				ok: function(data) {
					assert('name' in data);
					log(data);
					var mods = [];
					if (user.name != data.name)
						mods.push(['name',data.name]);
					if (groupid != data.group)
						mods.push(['group',data.group]);
					var isActive = 'active' in data && data.active == 'on';
					if (user.active != isActive)
						mods.push(['active',isActive]);
					if (mods.length)
					{
						var p = {id: item.data.entity.id};
						for (var i in mods)
							p[mods[i][0]] = mods[i][1];
						model.rpc('updateUser', p, function() {
							if ('name' in p || 'group' in p || 'active' in p)
								tree.reload(item.parent);
							if ('group' in p)
							{
								tree.iterate(function(item) {
									if (item.data && item.data.entity && item.data.entity.id == data.group)
									{
										tree.reload(item.id);
										return true;
									}
								});
							}
						});
					}
				},
			};
			Pianos4Backend.showFormDialog(dlgopts);
		}
		

		var cachedGroups = udb.app().cache('all-groups');
		if (!cachedGroups)
		{
			udb.app().getModel().rpc('getUserGroups', function(groups) {
				udb.app().cache('all-groups', groups);
				showDialog(groups);
			});
		}
		else
		{
			showDialog(cachedGroups);
		}
	}

	var onDeleteExercise = function(item)
	{
		var datadiv = $('<div />').append($('<img class="loading" src="css/throbber-18-black-on-white.gif" title="Initializing" alt="Loading ..." />')).append($('<span />').text('Gathering exercise data ...'));
		
		var dlgopts = {
			title: 'Confirm exercise deletion',
			fields: [
				{ type: 'static', label: 'Are you sure you wish to delete the exercise '+item.label+' ?' },
				{ type: 'dom', content: datadiv },
			],
			ok: function(data) {
				model.rpc('deleteExercise', {'id': item.data.entity.id}, function() { 
					tree.reload(tree.getItemParent(item.id).id);
				});
			},
		};
		
		Pianos4Backend.showFormDialog(dlgopts);
		
		model.rpc('getExerciseDataSummary', {'id': item.data.entity.id}, function(summary) {
			datadiv.html('').append($('<div />').css('font-weight','bold').text('Exercise data that will be deleted:'))
				.append($('<div />').text(summary.assignments+' assignment(s)'));
		});
	}

	var onDeleteFolder = function(item)
	{
		var datadiv = $('<div />').append($('<img class="loading" src="css/throbber-18-black-on-white.gif" title="Initializing" alt="Loading ..." />')).append($('<span />').text('Gathering folder data ...'));
		
		var dlgopts = {
			title: 'Confirm folder deletion',
			fields: [
				{ type: 'static', label: 'Are you sure you wish to delete the folder '+item.label+' ?' },
				{ type: 'dom', content: datadiv },
			],
			ok: function(data) {
				model.rpc('deleteFolder', {'id': item.data.entity.id}, function() { 
					tree.reload(tree.getItemParent(item.id).id);
				});
			},
		};
		
		Pianos4Backend.showFormDialog(dlgopts);
		
		model.rpc('getFolderDataSummary', {'id': item.data.entity.id}, function(summary) {
			datadiv.html('').append($('<div />').css('font-weight','bold').text('Exercise data that will be deleted:'))
				.append($('<div />').text(summary.exercises+' exercises(s)'))
				.append($('<div />').text(summary.assignments+' assignment(s)'))
				.append($('<div />').append($('<a href="#" />').text('More ...').click(function(e){
					e.preventDefault();
					var details = $(this).parent().next();
					details.toggle();
					$(this).text(details.is(':visible') ? 'Less' : 'More ...');
				})))
				.append($('<div />').hide().qtable({
					columns: ['ID','Name','Num. assignments'],
					data: summary.all,
				}))
				;
		});
	}
	
	var onTreeItemSelected = function(id)
	{
		var item = tree.getItem(id);
		log(item);
		if (item.data && item.data.type && item.data.entity)
		{
			location.hash = '#'+item.data.type+'/'+item.data.entity.id;
		}
		else if (item.data && item.data.hash)
		{
			location.hash = '#'+item.data.hash;
		}
		else
			location.hash = '';
	}
	
	var refreshTree = function(type, id)
	{
		tree.iterate(function(item) {
			if (item.data && item.data.type == type && item.data.entity.id == id)
			{
				if (type == 'exercise')
				{
					assert(item.parent);
					tree.reload(item.parent);
				}
				else
				{
					tree.reload(item.id);
				}
				return true;
			}
		});
	}
	
	var onSearch = function(what, endCallback)
	{
		assert(params.hasSearchBox);

		if (!searchResults)
			searchResults = (function() {
				var create = function() {
					div = $('<div />').attr('id', 'pianos4_search_results').css('display','none')
						.addClass('ui-widget ui-widget-content ui-corner-all')
						
					label = $('<div />').appendTo(div);
					
					$('body').append(div);
				};
				var set = function(what, results)
				{
					div.find('.table').remove();
					var message;
					if (results.length == 0)
					{
						message = "No results match '"+what+"'";
						label.text('').removeClass('title').addClass('nomatch').text(message);
					}
					else
					{
						message = "Search results for '"+what+"'";
						label.text('').removeClass('nomatch').addClass('title').text(message);
						var geticon = function(type) {
							assert(type in params.icons);
							if (typeof(params.icons[type]) == 'string') return params.icons[type];
							else return params.icons[type][0];
						}
						$('<div />').addClass('table').qtable({
							data: results,
							columnMode: 'mapping',
							showHeaders: false,
							columns: [
								{ title: 'Rank', render: function(data, i, rowIndex, cell) { cell.text(parseInt(i,10)+1); } },
								{ title: 'Type', render: function(data, i, rowIndex, cell) {
									var icon;
									if (data[i].type == 'person' && data[i].desc.indexOf('Inactive') >= 0)
										icon = 'person_inactive';
									else
										icon = data[i].type;
									cell.append($('<img />').attr('src', geticon(icon)));
								} },
								{ key: 'name', title: 'Name', render: function(data, i, rowIndex, cell) { 
									cell.append($('<a />').text(data[i][rowIndex]).attr('href','#'+data[i].type+'/'+data[i].id)); 
								} },
								{ key: 'desc', title: 'Description' },
							],
						}).appendTo(div);
					}
				};
				var dochandler = function(e) {
					assert(shown);
					log('doc handler');
					log(e.target);
					var $e = $(e.target);
					if (!$e.is('a') && ($e.is('#pianos4_search_results') || $e.parents('#pianos4_search_results').length))
						return;
					api.hide();
				};
				var self = this;
				var shown = false;
				var div = null;
				var label = null;
				var api = {
					show: function(what, results) {
						if (!div) create();
						var sb = $('#pianos4_search_box');
						var sboffs = sb.offset();
						div.css('left', (sboffs.left + sb.outerWidth() + 5)+'px')
							.css('bottom', (5)+'px');
						set(what, results);
						div.show();
						$(document).bind('click', dochandler);
						shown = true;
					},
					hide: function() {
						$(document).unbind('click', dochandler);
						div.hide();
						shown = false;
					},
				};
				return api;
			})();
			
		udb.app().getModel().rpc('search', {what:what}, {
			onSuccess: function(result) {
				log(result);
				searchResults.show(what, result);
			},
			onComplete: function(success) {
				if (!success)
					searchResults.hide();
				if (endCallback) endCallback();
			},
		});
	}
	
	var onTreeFiltersChanged = function()
	{
		var showInactiveUsers = $('#p4_tree_filter_inactive').prop('checked');
		if (showInactiveUsers != treeFilters.showInactiveUsers)
		{
			treeFilters.showInactiveUsers = showInactiveUsers;
			tree.reload(treeItems.pgroups);
		}
		
		var showPersonalFolders = $('#p4_tree_filter_personal').prop('checked');
		if (showPersonalFolders != treeFilters.showPersonalFolders)
		{
			treeFilters.showPersonalFolders = showPersonalFolders;
			tree.reload(treeItems.folders);
		}
		
	}
	
	var onTreeItemExpanded = function(item)
	{
		if (item.data && item.data.entity)
		{
			var state = Pianos4Backend.getOrCreateStateCookie();
			var t = item.data.type+'s';
			state.tree[t][item.data.entity.id] = true;
			Pianos4Backend.saveStateCookie(state);
		}
	}
	
	var onTreeItemCollapsed = function(item)
	{
		if (item.data && item.data.entity)
		{
			var state = Pianos4Backend.getOrCreateStateCookie();
			var t = item.data.type+'s';
			delete state.tree[t][item.data.entity.id];
			Pianos4Backend.saveStateCookie(state);
		}
	}	
}
