/*
 * PiAnoS (Picture Annotation System), A fingerprint examiner training tool
 * Copyright (C) 2011-2021 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/.
 */

Pianos4Backend.CreateExercise = function( selector, params )
{
    params = params || {};
    if( !( !params.folder != !params.id ) )
        udb.error( 'Programming error', 'One of the folder ID or the exercise ID must be specified' );
    // The conditional is !(a XOR b), where XOR is achieved by !a != !b
    // (http://www.howtocreate.co.uk/xor.html)
    if( !params.folder )
        params.folder = null;
    else
        params.folder = parseInt( params.folder );
    if( !params.id )
        params.id = null;
    else
        params.id = parseInt( params.id );
    if( !params.noImageURL )
        params.noImageURL = 'css/no_image_selected.png';
    if( !params.onCreate )
        params.onCreate = function( id )
        {
            toastr.success( 'Created exercise ID ' + id, '' );
        };
    if( !params.onSave )
        params.onSave = function( id )
        {
            toastr.success( 'Exercise ID ' + id + ' was saved', '' );
        };
    if( !params.user )
        udb.error( 'Programming error', 'The user must be specified' );
    if( typeof params.clone == 'undefined' )
        params.clone = false;
    
    this.init = function()
    {
        buildUI();
    }

    var self = this;
    var frozen = 0;
    var uploaders = [];
    
    var buildUI = function()
    {
        
        var rids = [];
        for( var i = 0; i < 4; ++i )
            rids.push( 'create_ex_' + ( new Date().getTime() ) + '_' + i );
        
        var form = $( '<form />' ).attr( 'action', '?' ).addClass( 'pianos4_main_form' ).append( $( '<div />' ).append( $( '<label />' ).attr( 'for', rids[ 0 ] ).text( 'Name' ) )
            .append( $( '<input />' ).attr( 'id', rids[ 0 ] ).attr( 'type', 'text' ).attr( 'name', 'name' ) ) ).append( $( '<div />' ).append( $( '<label />' ).attr( 'for', rids[ 1 ] )
            .text( 'Description' ) ).append( $( '<textarea />' ).attr( 'id', rids[ 1 ] ).attr( 'rows', 6 ).attr( 'cols', 30 ).attr( 'name', 'description' ) ) ).append( $( '<div />' )
            .append( $( '<label />' ).attr( 'for', rids[ 2 ] ).text( 'Mark image' ) ).append( $( '<input />' ).attr( 'id', rids[ 2 ] ).attr( 'type', 'hidden' ).attr( 'name', 'mark' ) )
            .append( createImageLoader( rids[ 2 ], 'MARK', params.clone ) ) ).append( $( '<div />' ).append( $( '<label />' ).attr( 'for', rids[ 3 ] ).text( 'Print image' ) ).append( $( '<input />' )
            .attr( 'id', rids[ 3 ] ).attr( 'type', 'hidden' ).attr( 'name', 'print' ) ).append( createImageLoader( rids[ 3 ], 'PRINT' ) ) ).append( $( '<div />' ).append( $( '<input />' )
            .attr( 'type', 'submit' ).attr( 'value', params.id ? 'Save' : 'Create' ).button() ) );
        
        form.bind( 'submit', onSubmit );
        
        var metaname = params.user.tutor ? 'exercise' : 'case';
        
        selector.addClass( 'pianos4_main' ).append( $( '<h2 />' ).text( params.id ? 'Edit ' + metaname + ' ' + params.id : 'Create a new ' + metaname ) ).append( form );
        
        selector.find( 'a[href=#upload]' ).each( function()
        {
            var $this = $( this );
            var uploader = new AjaxUpload( $this.attr( 'id' ), {
                action: '?action=upload_image',
                name: 'images[]',
                data: {
                    type: $this.data( 'image_type' ),
                },
                autoSubmit: true,
                responseType: 'json',
                onComplete: function( file, response )
                {
                    log( response );
                    if( response.error )
                    {
                        udb.error( 'Error while uploading image', response.message );
                    } else
                    {
                        selector.find( '#' + $this.data( 'target_id' ) ).val( response.ret );
                        $this.parents( '.pianos4_image_options' ).siblings( '.pianos4_image_preview' ).children( 'img' ).attr( 'src', '?action=get_image&thumb&id=' + response.ret );
                    }
                }
            } );
            uploaders.push( uploader );
        } );
        
        if( params.id )
        {
            freeze();
            
            if( params.clone )
            {
                udb.app().getModel().rpc( 'getExerciseCloneData', {
                    id: params.id
                }, function( ex )
                {
                    var data = $.extend( true, {}, ex );
                    data.name = 'Copy of ' + data.name;
                    data.description = '(Was: ' + data.description + ')';
                    data.print = null;
                    setData( data );
                    selector.find( '.pianos4_image_options:first' ).html( '' ).append( $( '<p />' ).css( 'margin-left', '1em' ).text( 'Analysis is the same as ' ).append( $( '<a />' )
                        .attr( 'href', '#exercise/' + ex.id ).attr( 'target', '_blank' ).addClass( 'clone_exercise_info' ).data( 'pianos4.clone_aid', ex.progress.aid ).text( ex.name ) ) );
                    
                    thaw();
                } );
            } else
            {
                udb.app().getModel().rpc( 'getExercise', {
                    id: params.id
                }, function( ex )
                {
                    setData( ex );
                    thaw();
                } );
            }
        }
    }

    var createImageLoader = function( id, imgType, readOnly )
    {
        var sp1 = $( '<span />' ).addClass( 'pianos4_image_preview' ).append( $( '<img />' ).attr( 'src', params.noImageURL ).attr( 'alt', 'No image selected' ) );
        
        var sp2 = $( '<span />' ).addClass( 'pianos4_image_options' );
        
        if( readOnly )
        {
            sp2.html( '<p style="margin-left:1em;color:#666">Loading ...</p>' );
        } else
        {
            var rid = 'uploader_' + ( new Date().getTime() );
            sp2.append( $( '<ul />' ).append( $( '<li />' ).append( $( '<a href="#library" />' ).text( 'Pick an image from the library' ).click( function( e )
            {
                e.preventDefault();
                if( frozen )
                    return false;
                var il = new Pianos4Backend.ImageLibrary( {
                    onSelect: function( image )
                    {
                        $( '#' + id ).val( image.id );
                        $( '#' + id ).siblings( 'span' ).find( '.pianos4_image_preview img' ).attr( 'src', '?action=get_image&thumb&id=' + image.id );
                    },
                    typefilter: imgType,
                } );
            } ) ) ).append( $( '<li />' ).append( $( '<a href="#upload" />' ).attr( 'id', rid ).data( 'target_id', id ).data( 'image_type', imgType ).text( 'Upload a new image' ).click( function( e )
            {
                e.preventDefault();
            } ) ) ) );
        }
        
        return $( '<span />' ).append( sp1 ).append( sp2 );
    }

    var onSubmit = function( e )
    {
        e.preventDefault();
        var errors = [];
        var data = $( this ).serializeObject();
        log( data );
        if( data.name.length == 0 )
            errors.push( 'The name must not be empty' );
        if( data.description.length == 0 )
            data.description = data.name;
        data.mark = parseInt( data.mark );
        if( isNaN( data.mark ) || data.mark <= 0 )
            errors.push( 'You must choose a mark image' );
        data.print = parseInt( data.print );
        if( isNaN( data.print ) || data.print <= 0 )
            errors.push( 'You must choose a print image' );
        
        if( errors.length )
        {
            udb.error( 'Could not create the exercise', errors.join( '\n' ) );
        } else
        {
            if( params.clone )
            {
                data.aid = $( '.clone_exercise_info' ).data( 'pianos4.clone_aid' );
                assert( typeof data.aid == 'number' && data.aid > 0 );
                udb.app().getModel().rpc( 'cloneExercise', data, function( id )
                {
                    if( params.onCreate )
                        params.onCreate( id );
                } );
            } else
            {
                if( params.id )
                {
                    data.id = params.id;
                    udb.app().getModel().rpc( 'saveExercise', data, function()
                    {
                        if( params.onSave )
                            params.onSave( params.id );
                    } );
                } else
                {
                    data.folder = params.folder;
                    udb.app().getModel().rpc( 'createExercise', data, function( id )
                    {
                        if( params.onCreate )
                            params.onCreate( id );
                    } );
                }
            }
        }
    }

    var freeze = function()
    {
        assert( frozen >= 0 );
        if( ++frozen > 0 )
        {
            selector.find( 'input:not([type=submit])' ).prop( 'disabled', true );
            selector.find( 'input[type=submit]' ).button( 'disable' );
            selector.find( 'textarea' ).prop( 'disabled', true );
            for( var i in uploaders )
                uploaders[ i ].disable();
        }
    }

    var thaw = function()
    {
        assert( frozen > 0 );
        if( --frozen == 0 )
        {
            selector.find( 'input:not([type=submit])' ).prop( 'disabled', false );
            selector.find( 'input[type=submit]' ).button( 'enable' );
            selector.find( 'textarea' ).prop( 'disabled', false );
            for( var i in uploaders )
                uploaders[ i ].enable();
        }
    }

    var setData = function( ex )
    {
        assert( parseInt( ex.id ) === params.id );
        selector.find( 'input[name=name]' ).val( ex.name );
        selector.find( 'textarea[name=description]' ).val( ex.description );
        selector.find( 'input[name=mark]' ).val( ex.mark ).siblings( 'span' ).find( '.pianos4_image_preview img' ).attr( 'src', ex.mark ? '?action=get_image&thumb&id=' + ex.mark : params.noImageURL );
        selector.find( 'input[name=print]' ).val( ex.print ).siblings( 'span' ).find( '.pianos4_image_preview img' )
            .attr( 'src', ex.print ? '?action=get_image&thumb&id=' + ex.print : params.noImageURL );
    }
}

Pianos4Backend.ImageLibrary = function( params )
{
    params = params || {};
    if( !params.title )
        params.title = 'Image Library';
    
    var self = this;
    var panel = null;
    
    ( function()
    {
        var div = $( '<div />' );
        
        var newp = $.extend( true, {}, params );
        newp.onSelect = function( id )
        {
            div.dialog( 'close' );
            if( params.onSelect )
                params.onSelect( id );
        };
        
        panel = new Pianos4Backend.ImageLibraryPanel( div, newp );
        panel.init();
        
        div.dialog( {
            autoOpen: true,
            modal: true,
            width: $( window ).width() * 80.0 / 100.0,
            height: $( window ).height() * 80.0 / 100.0,
            title: params.title,
            buttons: {
                'Close': function()
                {
                    $( this ).dialog( 'close' );
                },
            },
            close: function()
            {
                $( this ).remove();
            },
        } );
    } )();
}

Pianos4Backend.ImageLibraryPanel = function( parent, params )
{
    params = params || {};
    if( !params.onSelect )
        params.onSelect = function( image )
        {
            toastr.success( 'Selected image ID ' + image.id, '' );
        };
    if( !params.typefilter )
        params.typefilter = '*';
    if( !params.prompt )
        params.prompt = 'Please choose an image';
    
    this.init = function()
    {
        buildUI();
        load( true );
    }

    var self = this;
    var content = null;
    var lastLoadOptions = null;
    var loading = false;
    
    var buildUI = function()
    {
        content = $( '<div />' ).addClass( 'image_library' );
        
        var header = $( '<div />' ).addClass( 'image_library_header' )

        var rids = [];
        for( var i = 0; i < 2; ++i )
            rids.push( 'image_lib_' + ( new Date().getTime() ) + '_' + i );
        var form = $( '<form />' )
            .append(
                $( '<label />' )
                    .attr( 'for', rids[ 0 ] )
                    .text( 'Filter' )
            )
            .append(
                $( '<input />' )
                    .attr( 'id', rids[ 0 ] )
                    .attr( 'type', 'text' )
                    .attr( 'name', 'filter' )
                    .on( 'keyup', function()
                    {
                        load();
                    } )
            )
            .append(
                $( '<select />' )
                    .attr( 'id', rids[ 0 ] )
                    .attr( 'name', 'type' )
                    .bind( 'change', function() { load(); } )
                    .append(
                        $( '<option />' )
                            .attr( 'value', '*' )
                            .text( 'All types' )
                    )
                    .append(
                        $( '<option />' )
                            .attr( 'value', 'MARK' )
                            .text( 'Marks only' )
                    )
                    .append(
                        $( '<option />' )
                            .attr( 'value', 'PRINT' )
                            .text( 'Prints only' )
                    )
            );
        
        
        header.append( $( '<span />' ).text( params.prompt ) ).append( form );
        
        form.find( 'select[name=type]' ).val( params.typefilter );
        
        var div = $( '<div />' ).addClass( 'image_library_images' );
        
        content.append( header ).append( div );
        
        content.appendTo( parent );
    };
    
    var load = function( force )
    {
        var div = content.find( '.image_library_images' );
        div.html( '' ).append( $( '<div />' ).append( $( '<img class="loading" src="css/throbber-18-black-on-white.gif" alt="Loading ..." />' ) ).append( $( '<span />' ).text( 'Loading ...' ) ) );
        
        var opts = {};
        var filter = content.find( 'input[name=filter]' ).val();
        if( filter.length >= 1 )
            opts.filter = filter;
        var type = content.find( 'select[name=type]' ).val();
        if( type == 'MARK' || type == 'PRINT' )
            opts.type = type;
        
        if( !force || lastLoadOptions )
        {
            var differ = false;
            var compare = [
                'filter', 'type'
            ];
            for( var i in compare )
            {
                var cmp = compare[ i ];
                if( ( cmp in lastLoadOptions && !( cmp in opts ) ) || ( cmp in opts && !( cmp in lastLoadOptions ) ) )
                {
                    differ = true;
                    break;
                } else if( cmp in lastLoadOptions && cmp in opts && lastLoadOptions[ cmp ] != opts[ cmp ] )
                {
                    differ = true;
                    break;
                }
            }
            
            if( !differ )
                return;
        }
        
        loading = true;
        udb.app().getModel().rpc( 'getImageLibrary', opts, function( images )
        {
            loading = false;
            lastLoadOptions = opts;
            div.html( '' );
            for( var i in images )
                ( function( image )
                {
                    div.append( $( '<span />' ).addClass( 'image_library_image' ).append( $( '<span />' ).addClass( 'pianos4_image_preview' ).append( $( '<img />' )
                        .attr( 'src', '?action=get_image&thumb&id=' + image.id ).attr( 'alt', image.name ) ) ).append( $( '<span />' ).addClass( 'image_library_legend' ).text( image.name ) )
                        .click( function()
                        {
                            params.onSelect( image );
                        } ) );
                    
                } )( images[ i ] );
        } );
    };
}

Pianos4Backend.renderMessage = function( message )
{
    log( message );
    var frame = $( '<div />' ).addClass( 'ui-widget pianos4_frame' );
    var title = $( '<div />' ).addClass( 'ui-widget-header ui-corner-top' ).text( message.title );
    var meta = $( '<div />' ).css( 'float', 'right' ).css( 'font-size', '0.8em' ).css( 'font-weight', 'normal' ).append( $( '<span />' ).text( 'Posted by ' ) ).append( $( '<span />' )
        .text( message.creator ).css( 'font-weight', 'bold' ) ).append( $( '<span />' ).text( ' on ' ) ).append( $( '<span />' ).text( pgTimestamptzToDate( message.createdon ).toLocaleString() ) );
    title.append( meta );
    var content = $( '<div />' ).addClass( 'ui-widget-content ui-corner-bottom' );
    var nl2br = new RegExp( /\n/g );
    var lines = message.value.split( nl2br );
    for( var k in lines )
        content.append( $( '<div />' ).text( lines[ k ] ) );
    frame.append( title ).append( content );
    return frame;
}

Pianos4Backend.AdminHome = function( selector, params )
{
    params = params || {};
    if( !params.user )
        udb.error( 'Programming error', 'The user must be specified' );
    
    this.init = function()
    {
        buildUI();
        log( params.user );
    }

    var self = this;
    
    var buildUI = function()
    {
    	selector.addClass( 'pianos4_main' );
    	
    	var div = $( '<div />' );
    	if( params.user.switchedUser )
    		div.append( $( '<div />' ).css( 'color', 'red' ).append( $( '<h1 />' ).text( 'You are now logged as "' + params.user.name + '". Be careful.' ) ) );
    	
    	div.append( $( '<h2 />' ).text( 'Welcome to PiAnoS, ' + params.user.name ) );
    	
    	var multiCaseViewer = $( '<div />' )
    	    .append(
    	        $( '<a />' )
                    .attr( 'href', '#' )
                    .text( 'Multiple case viewer' )
                    .button()
                    .click( function( e )
                    {
                        e.preventDefault();
                        Pianos4Backend.multiCaseViewer();
                    } )
            );
    	div.append( multiCaseViewer );
    	
    	selector.append( div );
    	
        var loading = $( '<div />' ).append( $( '<em />' ).text( 'Loading messages, please wait ...' ) ).appendTo( div );
        
        udb.app().getModel().rpc( 'getHomeMessages', function( messages )
        {
            loading.remove();
            for( var i in messages )
                ( function( msg )
                {
                    var msgFrame = Pianos4Backend.renderMessage( msg );
                    msgFrame.appendTo( div );
                } )( messages[ i ] );
        } );
        
        if( params.view.homeData_show_all_exercises_for_admin )
    	{
	        udb
		        .app()
		        .getModel()
		        .rpc( 'getHomeData', function( data )
		        {
		        	log( data );
		        	
		        	var mainDiv = selector.find( '>div:first' );
	                
		        	var numStarted = 0, numDone = 0, numTotal = 0;
	                for( var i in data.progress )
	                {
	                	var ex = data.progress[ i ];
	                    numTotal++;
	                    ex.progress.label2 = 'To do';
	                    ex.progress.color = 'black';
	                    if( ex.progress.status >= 4 )
	                    {
	                        numDone++;
	                        ex.progress.label2 = 'Done !';
	                        ex.progress.color = 'green';
	                    } else if( ex.progress.status >= 0 )
	                    {
	                        numStarted++;
	                        ex.progress.label2 = 'To continue';
	                        ex.progress.color = 'blue';
	                    }
	                }
	                
	                var percent = 100.0 * numDone / numTotal;
	                
	                if( params.view.progress_bar )
	                {
	                    mainDiv.append( $( '<p />' ).append( $( '<strong />' ).text( 'Overall progress : ' ) ).append( $( '<span />' )
	                        .text( numDone + ' / ' + numTotal + ' trial' + ( numDone != 1 ? 's' : '' ) + ' done' + ( numStarted ? ', ' + numStarted + ' started' : '' ) ) ) ).append( $( '<div />' )
	                        .addClass( 'home_progressbar' ).progressbar( {
	                            value: percent
	                        } ).append( $( '<span />' ).addClass( '_percent_wrap' ).append( $( '<span />' ).addClass( '_percent' ).text( percent.toFixed( 0 ) + ' %' ) ) ) );
	                }
	                
	                if( params.view.homeData_as_todo_list )
	                	mainDiv.append( $( '<p />' ).append( $( '<strong />' ).text( 'Cases in progress:' ) ) );
	                else
	                	mainDiv.append( $( '<p />' ).append( $( '<strong />' ).text( 'All cases:' ) ) );
	                
	                var ul = $( '<ul />' );
	                var ul2 = $( '<ul />' );
	                for( var i in data.progress )
	                {
	                    var ex = data.progress[ i ];
	                    
	                    var li =
	                    	$( '<li />' )
	                    		.append(
	                    			$( '<span />' )
	                    				.addClass( 'highlight' )
				                    	.append(
				                    			$( '<span />' )
				                    			.addClass( 'home_details_status' )
				                    			.append( '[ ' )
				                    			.append(
				                    					$( '<span />' )
				                    					.text( ex.progress.label2 )
				                    					.css( 'color', ex.progress.color )
				                    			)
				                    			.append( ' ] ' )
				                    	)
			                    		.append(
		                    				$( '<span />' )
				                				.addClass( 'home_details_username' )
				                				.append( '[ ' )
				                				.append(
				            						$( '<a />' )
				            							.attr( 'href', '?#person/' + ex.userid )
				            							.text( ex.username )
				            					)
				            					.append( ' ] ' )
		            					)
		            					.append(
		        							$( '<span />' )
		        								.addClass( 'home_details_casename' )
		        								.append(
	        										$( '<a />' )
				        								.attr( 'href', '#exercise/' + ex.id )
				        								.text( ex.name )
			        							)
		        						)
	        					);
	                    
	                    if( params.view.homeData_as_todo_list & ex.progress.label2 == "Done !" )
	                		ul2.append( li );
	                	else
	                		ul.append( li );
	                }
	                mainDiv.append( ul );
	                
	                if( params.view.homeData_as_todo_list )
	            	{
	                	mainDiv.append( $( '<p />' ).append( $( '<strong />' ).text( 'Terminated cases:' ) ) );
	                	mainDiv.append( ul2 );
	            	}
		        } );
    	}
    }
}

Pianos4Backend.AdminExercise = function( selector, params )
{
    params = params || {};
    if( !params.id )
        udb.error( 'Programming error', 'The exercise ID must be specified' );
    if( typeof params.showResultsPreview == 'undefined' )
        params.showResultsPreview = false;
    
    this.init = function()
    {
        buildUI();
    }

    var self = this;
    
    var buildUI = function()
    {
        udb.app().getModel().rpc( 'getExercise', {
            id: params.id,
            withAssignments: true
        }, function( ex )
        {
            log( ex );
            
            var div = $( '<div />' ).append( $( '<h2 />' ).text( ex.name ) );
            
            /*
             *	General information about the case
             */
            
            var info = $( '<div />' )
            	.addClass( 'ui-widget pianos4_frame' )
            	
            	.append(
        			$( '<div />' )
        				.addClass( 'ui-widget-header' )
        				.addClass( 'ui-corner-top' )
        				
        				.text( 'General information' )
        		)
        		.append(
        			$( '<div />' )
						.addClass( 'ui-widget-content' )
						.addClass( 'ui-corner-bottom' )
						
						.append(
							$( '<p />' )
								.addClass( 'exercise_description' )
								
								.text( ex.description )
						)
						
						.append(
							$( '<span />' )
								.addClass( 'pianos4_image_preview' )
								
								.append(
									$( '<a />' )
										.attr( 'href', '?action=get_image&id=' + ex.mark )
										.attr( 'target', '_blank' )
										.append(
											$( '<img />' )
												.attr( 'src', '?action=get_image&thumb&id=' + ex.mark )
												.attr( 'alt', 'Mark image' )
										)
								)
						)
						.append(
							$( '<span />' )
								.addClass( 'pianos4_image_preview' )
								
								.append(
									$( '<a />' )
										.attr( 'href', '?action=get_image&id=' + ex.print )
										.attr( 'target', '_blank' )
										.append(
											$( '<img />' )
												.attr( 'src', '?action=get_image&thumb&id=' + ex.print )
												.attr( 'alt', 'Print image' )
										)
								)
						)
						
						.append(
							$( '<span />' )
								.css( 'margin-left', '30px' )
								.css( 'position', 'absolute' )
								.append(
									$( '<span />' )
										.text( 'Images details' )
								)
								.append(
									$( '<ul />' )
										.append(
											$( '<li />' )
												.append(
													$( '<span />' )
														.css( 'display', 'inline-block' )
														.css( 'width', '6em' )
														.text( 'Mark name: ' )
												)
												.append(
													$( '<span />' )
														.text( ex.namemark )
												)
										)
										.append(
											$( '<li />' )
												.append(
													$( '<span />' )
														.css( 'display', 'inline-block' )
														.css( 'width', '6em' )
														.text( 'Print name: ' )
												)
												.append(
													$( '<span />' )
														.text( ex.nameprint )
												)
										)
								)
						)
    		);
            
            // Actions buttons
            var actionsbuttons = $( '<div />' )
				.css( 'margin-top', '1em' )
				.append(
					$( '<a />' )
						.attr( 'href', '?action=take&id=' + ex.id )
						.text( 'Review results' )
						.button()
				)
			
			if( params.user.anonymousreview )
			{
				actionsbuttons.append(
					$( '<a />' )
						.attr( 'href', '?action=take&id=' + ex.id + '&anonymousreview=1' )
						.text( 'Anonymously review results' )
						.button()
				)
				
				actionsbuttons.append(
					$( '<a />' )
						.text( 'Show/Hide results preview' )
						.css( 'margin-left', '1em' )
						.click( function( e ){
							var currentVisibility = $( '#resultsdiv' ).css( 'visibility' );
							if( currentVisibility == 'hidden' )
								$( '#resultsdiv' ).css( 'visibility', 'visible' );
							else
								$( '#resultsdiv' ).css( 'visibility', 'hidden' );
						} )
						.button()
				)
			}
			
			actionsbuttons.append(
					$( '<a />' )
						.css( 'margin-left', '1em' )
						.attr( 'href', '?#exercise/' + ex.id + '/edit' )
						.text( 'Edit' )
						.button()
			);
            
            info.append( actionsbuttons );
            
            div.append( info );
            
            /*
             *	Results available
             */
            if( params.showResultsPreview )
            {
                var frame = info.find( '.ui-widget-content:first' );
                frame.css( 'position', 'relative' );
                var d = $( '<div />' ).css( 'position', 'absolute' ).width( '300px' ).height( '100%' ).css( 'top', '0' ).css( 'right', '1em' ).appendTo( frame );
                setTimeout( function()
                {
                    d.html( '' );
                    var iframe = new Pianos4Backend.IFrame( $( '<div />' ).appendTo( d ), params.id, true, true );
                    iframe.init();
                }, 100 );
            }
            
            var resdiv = $( '<div />' );
            
            var aplural = ex.assignments.length == 1 ? 'result' : 'results';
            var results = $( '<div />' )
            		.addClass( 'ui-widget pianos4_frame' )
            		.attr( 'id', 'resultsdiv' )
            		.appendTo( div )
            		.append( $( '<div />' )
            			.addClass( 'ui-widget-header ui-corner-top' )
            			.text( 'Results' )
        			)
        			.append(
    					$( '<div />' )
    						.addClass( 'ui-widget-content ui-corner-bottom' )
    						.append( resdiv )
					);
            
            if( params.user.anonymousreview )
            	results.css( 'visibility', 'hidden' );
            
            var findLatestResultIndex = function( data, step )
            {
                var stepidx = -1;
                for( var i = 0; i < data.numresults; ++i )
                {
                    var phase = data.ressteps[ i ];
                    if( phase != step )
                        continue;
                    else
                        stepidx = i;
                }
                return stepidx;
            }

            var nl2br = new RegExp( /\n/g );
            
            var writeDataCell = function( data, stepidx, cell )
            {
                var concl = null;
                if( stepidx >= 0 )
                {
                    try
                    {
                        var jsonSrc = data.conclusions[ stepidx ];
                        concl = JSON.parse( jsonSrc );
                    } catch( e )
                    {
                        concl = null;
                    }
                }
                
                if( concl )
                {
                    var lines = concl.split( nl2br );
                    for( var i in lines )
                        cell.append( $( '<div />' ).text( lines[ i ] ) );
                } else
                {
                    cell.css( 'font-style', 'italic' ).text( 'No data' );
                }
            }
            
            var cols = [
                {
                    title: 'User',
                    render: function( data, i, j, cell )
                    {
                        cell.append( $( '<a />' ).attr( 'href', '#person/' + data[ i ].user.id ).text( data[ i ].user.name ) );
                    },
                }, {
                    title: 'Status',
                    render: function( data, i, j, cell )
                    {
                        cell.text( data[ i ].progress.label );
                    },
                }
            ];
            
        	if( ! params.user.anonymousreview )
        	{
        		cols.push(
    				{
                        title: 'Analysis',
                        render: function( data, i, j, cell )
                        {
                            var d = data[ i ];
                            var step = findLatestResultIndex( d, 'Analysis' );
                            writeDataCell( d, step, cell );
                        },
                    }, {
                        title: 'Conclusion',
                        render: function( data, i, j, cell )
                        {
                            var d = data[ i ];
                            var step = findLatestResultIndex( d, 'Comparison' );
                            writeDataCell( d, step, cell );
                        },
                    }
    			);
        	}
        	
        	cols.push( {
                    title: '',
                    render: function( data, i, j, cell )
                    {
                        cell.append( $( '<img />' ).attr( 'src', 'css/icons/backend/Delete16.png' ).attr( 'alt', 'delete' ).css( 'cursor', 'pointer' ).click( function()
                        {
                            $( '<div />' ).text( 'Do you really want to delete these results ?' ).dialog( {
                                title: "Confirm results deletion",
                                modal: true,
                                buttons: {
                                    'OK': function()
                                    {
                                        var dlg = $( this );
                                        udb.app().getModel().rpc( 'deleteAssignment', {
                                            aid: parseInt( data[ i ][ 'id' ] )
                                        }, function()
                                        {
                                            toastr.success( 'Assignement deleted', '' );
                                            cell.parents( 'tr' ).remove();
                                            dlg.dialog( 'close' );
                                        } );
                                    },
                                    'Cancel': function()
                                    {
                                        $( this ).dialog( 'close' );
                                    },
                                },
                                close: function()
                                {
                                    $( this ).remove();
                                }
                            } );
                            
                        } ) );
                    },
                }
            );
            
            resdiv.qtable( {
                data: ex.assignments,
                columnMode: 'mapping',
                columns: cols,
            } );
            
            selector.addClass( 'pianos4_main' ).append( div );
        } );
    }
}

Pianos4Backend.AdminUser = function( selector, params )
{
    params = params || {};
    if( !params.id )
        udb.error( 'Programming error', 'The user ID must be specified' );
    
    this.init = function()
    {
        buildUI();
    }

    var self = this;
    
    var buildUI = function()
    {
        udb.app().getModel().rpc( 'getUser', {
            id: params.id,
            withAssignments: true
        }, function( user )
        {
            log( user );
            
            var div = $( '<div />' ).append( $( '<h2 />' ).text( user.name ) );
            
            var displv = function( label, value )
            {
                return $( '<p />' ).css( 'margin', '0.3em 0' ).append( $( '<span />' ).css( 'display', 'inline-block' ).css( 'width', '12em' ).text( label ) ).append( $( '<span />' ).text( value ) );
            };
            
            var info = $( '<div />' )
                .addClass( 'ui-widget pianos4_frame' )
                .appendTo( div )
                .append(
                    $( '<div />' )
                        .addClass( 'ui-widget-header' )
                        .addClass( 'ui-corner-top' )
                        .text( 'General information' )
                )
                .append(
                    $( '<div />' )
                        .addClass( 'ui-widget-content' )
                        .addClass( 'ui-corner-bottom' )
                        .append( displv( 'active', user.active ? 'yes' : 'no' ) )
                        .append( displv( 'username', user.username ) )
                        .append( displv( 'tutor', user.tutor ? 'yes' : 'no' ) )
                        .append( displv( 'email', user.email ) )
                        .append( displv( 'groups', [ { name: [] } ].concat( user.groups ).reduce( function( a, b )
                        {
                            return {
                                name: a.name.concat( b.name )
                            };
                        } ).name.join( ', ' ) ) )
                        .append( displv( 'Last connection', user.last_login ) )
            );
            
            var actionsbuttons = $( '<div />' )
                .css( 'margin-top', '10px' )
                .css( 'margin-bottom', '10px' );
            
            if( ! user.tutor )
            {
                var reset = $( '<a />' )
                    .text( 'Reset user password' )
                    .button()
                    .click( function( e )
                    {
                        Pianos4Backend.ResetUserPassword( user );
                    } )
                    .appendTo( actionsbuttons )
            }
            
            $( '<a />' )
                .text( 'Switch to user account' )
                .button()
                .click( function( e )
                {
                    window.location.href = '?action=switchUser&user=' + user.id
                } )
                .appendTo( actionsbuttons );
            
            actionsbuttons.appendTo( div );
            
            var resdiv = $( '<div />' );
            
            var results = $( '<div />' ).addClass( 'ui-widget pianos4_frame' ).appendTo( div ).append( $( '<div />' ).addClass( 'ui-widget-header ui-corner-top' ).text( 'Results' ) )
                .append( $( '<div />' ).addClass( 'ui-widget-content ui-corner-bottom' ).append( resdiv ) );
            
            var findLatestResultIndex = function( data, step )
            {
                var stepidx = -1;
                for( var i = 0; i < data.numresults; ++i )
                {
                    var phase = data.ressteps[ i ];
                    if( phase != step )
                        continue;
                    else
                        stepidx = i;
                }
                return stepidx;
            }

            var nl2br = new RegExp( /\n/g );
            
            var writeDataCell = function( data, stepidx, cell )
            {
                var concl = null;
                if( stepidx >= 0 )
                {
                    try
                    {
                        var jsonSrc = data.conclusions[ stepidx ];
                        concl = JSON.parse( jsonSrc );
                    } catch( e )
                    {
                        concl = null;
                    }
                }
                
                if( concl )
                {
                    var lines = concl.split( nl2br );
                    for( var i in lines )
                        cell.append( $( '<div />' ).text( lines[ i ] ) );
                } else
                {
                    cell.css( 'font-style', 'italic' ).text( 'No data' );
                }
            }

            resdiv.qtable( {
                data: user.assignments,
                columnMode: 'mapping',
                columns: [
                    {
                        title: 'Exercise',
                        render: function( data, i, j, cell )
                        {
                            cell.append( $( '<a />' ).attr( 'href', '#exercise/' + data[ i ].exid ).text( data[ i ].exname ) );
                        },
                    }, {
                        title: 'Status',
                        render: function( data, i, j, cell )
                        {
                            cell.text( data[ i ].progress.label );
                        },
                    }, {
                        title: 'Analysis',
                        render: function( data, i, j, cell )
                        {
                            var d = data[ i ];
                            var step = findLatestResultIndex( d, 'Analysis' );
                            writeDataCell( d, step, cell );
                        },
                    }, {
                        title: 'Conclusion',
                        render: function( data, i, j, cell )
                        {
                            var d = data[ i ];
                            var step = findLatestResultIndex( d, 'Comparison' );
                            writeDataCell( d, step, cell );
                        },
                    }, {
                        title: '',
                        render: function( data, i, j, cell )
                        {
                            cell.append( $( '<img />' ).attr( 'src', 'css/icons/backend/Delete16.png' ).attr( 'alt', 'delete' ).css( 'cursor', 'pointer' ).click( function()
                            {
                                $( '<div />' ).text( 'Do you really want to delete these results ?' ).dialog( {
                                    title: "Confirm results deletion",
                                    modal: true,
                                    buttons: {
                                        'OK': function()
                                        {
                                            var dlg = $( this );
                                            udb.app().getModel().rpc( 'deleteAssignment', {
                                                aid: parseInt( data[ i ][ 'id' ] )
                                            }, function()
                                            {
                                                cell.parents( 'tr' ).remove();
                                                dlg.dialog( 'close' );
                                            } );
                                        },
                                        'Cancel': function()
                                        {
                                            $( this ).dialog( 'close' );
                                        },
                                    },
                                    close: function()
                                    {
                                        $( this ).remove();
                                    }
                                } );
                                
                            } ) );
                        },
                    }
                ],
            } );
            
            selector.addClass( 'pianos4_main' ).append( div );
        } );
    }
}

Pianos4Backend.AdminFolder = function( selector, params )
{
    params = params || {};
    if( !params.id )
        udb.error( 'Programming error', 'The folder ID must be specified' );
    if( !params.user )
        udb.error( 'Programming error', 'The user must be specified' );
    
    this.init = function()
    {
        buildUI();
    }

    var self = this;
    var folder = null;
    
    var buildUI = function()
    {
        udb.app().getModel().rpc( 'getFolder', {
            id: params.id
        }, function( _folder )
        {
            
            folder = _folder;
            
            var div = $( '<div />' ).append( $( '<h2 />' ).text( folder.name ) );
            
            acldiv = $( '<div />' ).attr( 'id', 'pianos4_acldiv_' + params.id );
            
            var isPersonalFolder = folder.personalof && parseInt( folder.personalof, 10 ) === params.user.id;
            
            var metaname = params.user.tutor || !isPersonalFolder ? 'exercise' : 'case';
            
            var explural = metaname + ( folder.exercises.length == 1 ? '' : 's' );
            var info = $( '<div />' ).addClass( 'ui-widget pianos4_frame' ).append( $( '<div />' ).addClass( 'ui-widget-header ui-corner-top' ).text( 'General information' ) ).append( $( '<div />' )
                .addClass( 'ui-widget-content ui-corner-bottom' ).append( $( '<div />' ).text( folder.exercises.length + ' ' + explural + ' in this folder' ) ).append( acldiv ) ).appendTo( div );
            
            if( params.user.tutor )
            {
                resetACLDiv();
                
                var cachedGroups = udb.app().cache( 'acl-groups' );
                if( !cachedGroups )
                {
                    udb.app().getModel().rpc( 'getUserGroups', {
                        noAdmins: true,
                        countUsers: true
                    }, function( groups )
                    {
                        udb.app().cache( 'acl-groups', groups );
                        showACL( folder.acl, groups );
                    } );
                } else
                {
                    showACL( folder.acl, cachedGroups, acldiv );
                }
            } else
            {
                if( isPersonalFolder && params.user.can_create_personnal_folder )
                    info.find( '.ui-widget-content' ).append( $( '<a />' ).attr( 'href', '#createexercise/' + params.id ).text( 'Create case here' ).button() )
            }
            
            var exercises = $( '<div />' ).addClass( 'ui-widget pianos4_frame' ).append( $( '<div />' ).addClass( 'ui-widget-header ui-corner-top' ).text( metaname[ 0 ].toUpperCase() + metaname
                .substr( 1 ) + 's' ) ).append( $( '<div />' ).addClass( 'ui-widget-content ui-corner-bottom' ) ).appendTo( div );
            
            var excontainer = exercises.find( '.ui-widget-content' );
            
            var renderImage = function( data, i, j, cell, alt )
            {
                cell.append( $( '<span />' ).addClass( 'pianos4_image_preview' ).append( $( '<img />' ).attr( 'src', '?action=get_image&thumb&id=' + data[ i ][ j ] ).attr( 'alt', alt ) ) );
            };
            
            $( '<div />' ).appendTo( excontainer ).qtable( {
                data: folder.exercises,
                columns: [
                    /*
                     * { key: 'id', title: 'ID' },
                     */{
                        key: 'mark',
                        title: 'Mark image',
                        render: function( a, b, c, d )
                        {
                            return renderImage( a, b, c, d, 'Mark image' );
                        },
                    }, {
                        key: 'print',
                        title: 'Print image',
                        render: function( a, b, c, d )
                        {
                            return renderImage( a, b, c, d, 'Print image' );
                        },
                    }, {
                        key: 'name',
                        title: 'Name',
                    }, {
                        key: 'description',
                        title: 'Description',
                    }
                ],
                columnMode: 'mapping',
                cellWrap: function( data, i )
                {
                    return $( '<a />' ).addClass( 'qtable_cell_link' ).attr( 'href', '#exercise/' + data[ i ].id )
                },
                trClass: 'qtable_row_link',
            } );
            
            selector.addClass( 'pianos4_main' ).append( div );
        } );
    }

    var resetACLDiv = function()
    {
        $( '#pianos4_acldiv_' + params.id ).html( '<span>Loading user groups ...</span><span><img src="css/throbber-18-black-on-white.gif" alt="Please wait ..." /></span>' );
    };
    
    var showACL = function( acl, allgroups, acldiv )
    {
        acldiv = acldiv || $( '#pianos4_acldiv_' + params.id );
        
        var gmap = {};
        for( var i in allgroups )
        {
            var group = allgroups[ i ];
            group[ 'id' ] = parseInt( group[ 'id' ] );
            gmap[ group.id ] = group.name;
        }
        
        var names = [];
        for( var i in acl )
            names.push( gmap[ acl[ i ] ] );
        
        var namesspan = $( '<span />' );
        if( names.length )
            namesspan.text( names.join( ', ' ) );
        else
            namesspan.css( 'font-style', 'italic' ).text( 'None' );
        
        acldiv.html( '' ).append( $( '<div />' ).append( $( '<span />' ).css( 'font-weight', 'bold' ).text( 'Allowed groups: ' ) ).append( namesspan ) ).append( $( '<div />' ).append( $( '<a />' )
            .attr( 'href', '#' ).text( 'Change access rights' ).button().click( function( e )
            {
                e.preventDefault();
                showACLDialog( acl, allgroups, function( newacl )
                {
                    resetACLDiv();
                    udb.app().getModel().rpc( 'setFolderACL', {
                        id: params.id,
                        acl: newacl
                    }, function()
                    {
                        showACL( newacl, allgroups );
                    } );
                } );
            } ) ).append( $( '<a />' ).attr( 'href', '#createexercise/' + params.id ).css( 'margin-left', '1em' ).text( 'Create exercise here' ).button() ).append( $( '<a />' ).attr( 'href', '#' )
            .css( 'margin-left', '1em' ).text( 'Duplicate folder' ).button().click( function( e )
            {
                e.preventDefault();
                var div = $( '<div />' ).append( $( '<p />' ).text( 'Please enter a name for the new folder' ) ).append( $( '<p />' ).append( $( '<input />' ).attr( 'type', 'text' )
                    .attr( 'name', 'name' ).css( 'width', '20em' ).val( 'Copy of ' + folder.name ) ) );
                div.dialog( {
                    title: 'Duplicate folder',
                    modal: true,
                    close: function()
                    {
                        $( this ).remove();
                    },
                    buttons: {
                        'OK': function()
                        {
                            var name = div.find( 'input[name=name]' ).val();
                            var $this = $( this );
                            udb.app().getModel().rpc( 'duplicateFolder', {
                                id: folder.id,
                                name: name
                            }, function( folderID )
                            {
                                $this.dialog( 'close' );
                                window.location.hash = 'folder/' + folderID;
                                udb.app().refreshTreePart( 'allex' );
                            } );
                        },
                        'Cancel': function()
                        {
                            $( this ).dialog( 'close' );
                        },
                    },
                } );
            } ) ) );
    }

    var showACLDialog = function( acl, allgroups, callback )
    {
        var div = $( '<div />' );
        div.append( $( '<p />' ).text( 'Choose the user groups that have access to this folder.' ) );
        var ol = $( '<ol />' ).css( 'list-style-type', 'none' ).css( 'padding', '0' ).appendTo( div );
        for( var i in allgroups )
        {
            var group = allgroups[ i ];
            var li = $( '<li />' ).text( group[ 'name' ] ).data( 'value', group[ 'id' ] ).css( 'padding', '0.2em 0.5em' ).css( 'cursor', 'pointer' );
            var usrplural = group[ 'nummembers' ] == 1 ? 'user' : 'users';
            li.append( $( '<span />' ).css( 'float', 'right' ).css( 'font-style', 'italic' ).css( 'font-size', '0.9em' ).text( '(' + group[ 'nummembers' ] + ' ' + usrplural + ')' ) );
            var idx = $.inArray( group[ 'id' ], acl );
            if( idx >= 0 )
                li.addClass( 'ui-state-highlight' );
            else
                li.addClass( 'ui-state-default' );
            li.click( function()
            {
                $( this ).toggleClass( 'ui-state-highlight' ).toggleClass( 'ui-state-default' );
            } );
            ol.append( li );
        }
        div.append( $( '<p />' ).append( $( '<a />' ).css( 'float', 'left' ).attr( 'href', '#' ).text( 'Remove all' ).click( function( e )
        {
            e.preventDefault();
            div.find( 'li' ).removeClass( 'ui-state-highlight' ).addClass( 'ui-state-default' );
        } ) ).append( $( '<a />' ).css( 'float', 'right' ).attr( 'href', '#' ).text( 'Select all' ).click( function( e )
        {
            e.preventDefault();
            div.find( 'li' ).removeClass( 'ui-state-default' ).addClass( 'ui-state-highlight' );
        } ) ) );
        
        div.dialog( {
            title: "Folder access control",
            modal: true,
            buttons: {
                'OK': function()
                {
                    var newacl = [];
                    $( 'li', this ).each( function()
                    {
                        if( $( this ).hasClass( 'ui-state-highlight' ) )
                            newacl.push( $( this ).data( 'value' ) );
                    } );
                    $( this ).dialog( 'close' );
                    callback( newacl );
                },
                'Cancel': function()
                {
                    $( this ).dialog( 'close' );
                },
            },
            close: function()
            {
                $( this ).remove();
            }
        } );
    }
}

Pianos4Backend.multiCaseViewer = function()
{
    $( '#multiCaseViewer' ).remove();
    
    var mainid = "";
    var multiCaseViewer = "";
    
    var finalURL = "";
    
    var allFolders = [];
    
    var maincase = $( '<select multiple/>' )
        .attr( 'id', 'multiCaseViewerChosen' )
        .attr( 'data-placeholder', 'Select cases to merge from list...' )
        .attr( 'class', 'chosen-select' )
        .append( $( '<option value="">Loading cases</option>' ) );
    
    var casespreview = $( '<div />' )
        .attr( 'id', 'casespreview' )
        .css( 'display', 'grid' )
        .css( 'grid-template-columns', '1fr' )
        .css( 'grid-gap', '5px' );
    
    var updateVals = function()
    {
        var url = "?action=take&id=";
        
        var lst = $( this ).val();
        var fulllist = $( this ).val();
        
        if( lst != null && lst.length > 0 )
        {
            mainid = lst.shift();
            
            finalURL = url + mainid
            
            if( lst.length != 0 )
            {
                multiCaseViewer = lst.join( "," );
                finalURL += "&multiCaseViewer=1";
            }
            
            $( '#casespreview' ).empty();
            
            for( var i in fulllist )
            {
                $( '#casespreview' ).append(
                    getPreviewDIV( fulllist[ i ] )
                );
            }
        } else {
            $( '#casespreview' ).empty();
        }
    }
    
    var getPreviewDIV = function( id )
    {
        var ret = $( '<div />' )
            .attr( 'id', 'casespreview_id' + id )
            .css( 'display', 'grid' )
            .css( 'grid-template-columns', '1fr 1fr 3fr' )
            .css( 'grid-gap', '5px' )
            .css( 'border', '2px solid #CCCCCC' )
            .css( 'border-radius', '20px' )
            .css( 'padding-top', '5px' )
            .css( 'padding-bottom', '5px' )
            
            .append(
                $( '<div />' )
                    .css( 'align-self', 'center' )
                    .css( 'justify-self', 'center' )
                    .attr( 'id', 'casespreview_id' + id + '_mark' )
            )
            .append(
                $( '<div />' )
                    .css( 'align-self', 'center' )
                    .css( 'justify-self', 'center' )
                    .attr( 'id', 'casespreview_id' + id + '_print' )
            )
            .append(
                $( '<div />' )
                    .attr( 'id', 'casespreview_id' + id + '_meta' )
            );
        
        $.ajax( {
            url: '?action=rpc&method=getExercise',
            data: { id: id },
            type: "POST",
            dataType: 'json',
            
            success: function( data )
            {
                $( '#casespreview_id' + data.ret.id + '_mark' ).html(
                    $( '<img />' )
                        .attr( 'src', '?action=get_image&thumb&id=' + data.ret.mark )
                        .css( 'width', '150px' )
                        .css( 'height', '150px' )
                );
                
                $( '#casespreview_id' + data.ret.id + '_print' ).html(
                    $( '<img />' )
                        .attr( 'src', '?action=get_image&thumb&id=' + data.ret.print )
                        .css( 'width', '150px' )
                        .css( 'height', '150px' )
                );
                
                $( '#casespreview_id' + data.ret.id + '_meta' ).html(
                    $( '<div />' )
                        .css( 'display', 'grid' )
                        .css( 'grid-template-columns', 'auto 1fr' )
                        .css( 'grid-gap', '5px 30px' )
                        .append( $( '<strong />' ).text( "Case name" )   ).append( $( '<span />' ).text( data.ret.name ) )
                        .append( $( '<strong />' ).text( "Description" ) ).append( $( '<span />' ).text( data.ret.description ) )
                        .append( $( '<strong />' ).text( "Mark name" )   ).append( $( '<span />' ).text( data.ret.namemark ) )
                        .append( $( '<strong />' ).text( "Mark id" )     ).append( $( '<span />' ).text( data.ret.mark ) )
                        .append( $( '<strong />' ).text( "Print name" )  ).append( $( '<span />' ).text( data.ret.nameprint ) )
                        .append( $( '<strong />' ).text( "Print id" )    ).append( $( '<span />' ).text( data.ret.print ) )
                );
            }
        } );
        
        return ret;
    }
    
    var getExerciseInfo = function( id )
    {
        $.ajax( {
            url: '?action=rpc&method=getAllExercises',
            
            type: "POST",
            dataType: 'json',
            
            success: function( data )
            {
                if( data.error === false )
                {
                    $( '#multiCaseViewerChosen' ).empty();
                    
                    for( var i in data.ret )
                    {
                        var d = data.ret[ i ];
                        
                        if( $( '#multiCaseViewerChosen > #option_' + d.folderid ).length == 0 )
                        {
                            $( '#multiCaseViewerChosen' )
                                .append(
                                    $( '<optgroup />' )
                                        .attr( 'id', 'option_' + d.folderid )
                                        .attr( 'label', d.foldername )
                                ); 
                        } 
                        $( '#multiCaseViewerChosen > #option_' + d.folderid )
                            .append(
                                $( '<option/>' )
                                    .attr( 'id', 'option_' + d.exname )
                                    .attr( 'value', d.exid )
                                    .text( d.foldername + " - " + d.exname )
                                    .trigger( 'chosen:updated' )
                            );
                    
                    }
                    
                    $( '#multiCaseViewerChosen' ).trigger("chosen:updated");
                    
                } else {
                    toastr.error( "Server eror" );
                }
            },
            error: function( data )
            {
                toastr.error( "Network error" );
            }
        } );
    }
    
    var editor = $( '<form />' )
        .css( 'display', 'grid' )
        .css( 'grid-template-columns', '1fr' )
        .css( 'grid-gap', '5px' );
    
    editor
        .append( maincase )
        .append( casespreview );
    
    var dlgopts = {
        autoOpen: true,
        modal: true,
        width: $( window ).width() * 0.8,
        height: $( window ).height() * 0.8,
        resizable: true,
        title: "Multiple cases viewer",
        buttons: {
            'Review multiple cases': function()
            {
                $.ajax( {
                    url: '?action=rpc&method=multiCaseViewerSessionSave',
                    type: "POST",
                    data: {
                        mainid: mainid,
                        multiCaseViewer: multiCaseViewer,
                    },
                    dataType: 'json',
                    
                    success: function( data )
                    {
                        location.href = finalURL;
                    },
                    error: function( data )
                    {
                        toastr.error( "Network error" );
                    }
                } );
            },
            'Close': function()
            {
                $( this ).dialog( 'close' );
                $( this ).remove();
            },
        }
    };
    
    $( '<div />' )
        .attr( 'id', 'multiCaseViewer' )
        .addClass( 'pianos4_dialog' )
        .append( editor )
        .dialog( dlgopts );
    
    $( '#multiCaseViewerChosen' )
        .chosen( { search_contains: true } )
        .change( updateVals );
    
    getExerciseInfo();
}

Pianos4Backend.ImageManager = function( parent, params )
{
    params = params || {};
    if( !params.pageLength || params.pageLength < 0 )
        params.pageLength = 0;
    
    this.init = function()
    {
        buildUI();
        load( true );
    }

    var self = this;
    var content = null;
    var loading = false;
    var toolbar = null;
    var table = null;
    var allImages = null;
    var page = null;
    var pageLength = parseInt( params.pageLength );
    
    var buildUI = function()
    {
        toolbar = $( '<div />' );
        
        toolbar.append( $( '<span />' ).addClass( '_prev' ).button( {
            disabled: true
        } ).text( '<' ).click( function()
        {
            page = Math.max( 0, page - 1 );
            updateUI();
            renderCurrentPage();
        } ) ).append( $( '<span />' ).css( 'margin', '0 0.5em' )
            .html( 'Showing page <span id="imageman_pcur"> </span> / <span id="imageman_ptot"> </span> (items <span id="imageman_icur"> </span> / <span id="imageman_itot"> </span>)' ) )
            .append( $( '<span />' ).addClass( '_next' ).button( {
                disabled: true
            } ).text( '>' ).click( function()
            {
                page = Math.min( getNumPages(), page + 1 );
                updateUI();
                renderCurrentPage();
            } ) );
        
        var sel = $( '<select />' ).css( 'margin-left', '1em' );
        var paging = [
            10, 25, 50, 100
        ];
        for( var i in paging )
            sel.append( $( '<option />' ).attr( 'value', paging[ i ] ).text( paging[ i ] ) );
        sel.append( $( '<option />' ).attr( 'value', 0 ).text( 'All' ) );
        sel.val( pageLength );
        sel.on( 'change', function()
        {
            if( !loading )
            {
                pageLength = parseInt( sel.val() );
                page = Math.min( getNumPages() - 1, page );
                updateUI();
                renderCurrentPage();
                var state = Pianos4Backend.getOrCreateStateCookie();
                state.imageman.page = pageLength;
                Pianos4Backend.saveStateCookie( state );
            }
        } );
        toolbar.append( $( '<span />' ).css( 'margin-left', '3em' ).text( 'Items per page' ) ).append( sel );
        
        table = $( '<div />' );
        
        /*
         *  Action buttons
         */
        
        var actionbuttons = $( '<div />' )
            .css( 'margin-top', '10px' )
            .css( 'margin-bottom', '10px' );
        
        actionbuttons.append(
            $( '<a />' )
                .attr( 'href', '#' )
                .text( 'Upload multiple images (mark or print) to the gallery' )
                .button()
                .click( function( e )
                {
                    e.preventDefault();
                    Pianos4Backend.ImageManagerDropzones();
                } )
        );
        
        actionbuttons.append(
            $( '<a />' )
                .text( 'Delete unused images' )
                .button()
                .click( function( e )
                {
                    udb.app().getModel().rpc( 'deleteUnusedImages', {}, function( res )
                    {
                        location.reload();
                    } );
                } )
        );
        
        /*
         *  Filter bar
         */
        
        var filterBar = $( '<div />' )
            .append(
                $( '<span />' )
                    .text( "Filter:" )
            )
            .append(
                $( '<input />' )
                    .attr( 'id', 'filterBar' )
            )
        
        filterBar.on( 'keyup', function()
        {
            updateUI();
            renderCurrentPage();
        } );
        
        /*
         *  Page compositing
         */
        
        content = $( '<div />' )
            .addClass( 'pianos4_main' )
            .append( $( '<h2 />' ).text( 'Images Management' ) )
            .append( actionbuttons )
            .append( filterBar )
            .append( toolbar )
            .append( table )
            .appendTo( parent );
    };
    
    var load = function( force )
    {
        loading = true;
        var opts = {};
        udb.app().getModel().rpc( 'getImageLibrary', {
            extended: true
        }, function( images )
        {
            loading = false;
            allImages = images;
            page = 0;
            updateUI();
            renderCurrentPage();
        } );
    }

    var getNumPages = function()
    {
        return pageLength == 0 ? 1 : Math.ceil( allImages.length / pageLength );
    }

    var updateUI = function()
    {
        log( 'updateUI, typeof pagelength ' + typeof ( pageLength ) );
        toolbar.find( '#imageman_pcur' ).text( page + 1 );
        toolbar.find( '#imageman_itot' ).text( allImages.length );
        if( pageLength > 0 )
        {
            toolbar.find( '#imageman_ptot' ).text( getNumPages() );
            toolbar.find( '#imageman_icur' ).text( ( 1 + page * pageLength ) + ' - ' + Math.min( allImages.length, ( ( 1 + page ) * pageLength ) ) );
        } else
        {
            toolbar.find( '#imageman_ptot' ).text( 1 );
            toolbar.find( '#imageman_icur' ).text( allImages.length );
        }
        toolbar.find( '.ui-button._prev' ).button( page > 0 ? 'enable' : 'disable' );
        toolbar.find( '.ui-button._next' ).button( page < getNumPages() - 1 ? 'enable' : 'disable' );
    }

    var renderCurrentPage = function()
    {
        if( pageLength > 0 )
        {
            renderSlice( pageLength * page, pageLength * page + pageLength );
        } else
        {
            renderSlice( 0, allImages.length );
        }
    }
    
    var imageEditor = function( img )
    {
        $( '#imgeditordlg' ).remove();
        
        var oldres = img.resolution;
        
        var editor = $( '<div />' )
            .css( 'display', 'grid' )
            .css( 'grid-template-columns', '1fr 1fr' )
            .css( 'grid-gap', '30px' )
            .css( 'height', '100%' );
        
        var image = $( '<div />' )
            .css( 'align-self', 'center' )
            .css( 'justify-self', 'center' )
            .css( 'width', '100%' )
            .css( 'height', '100%' )
            .css( 'background-image', 'url( ?action=get_image&id=' + img.id + ' )' )
            .css( 'background-repeat', 'no-repeat' )
            .css( 'background-size', 'contain' )
            .css( 'background-position', 'center' );
        
        var metadata = $( '<div />' )
            .css( 'padding-top', '30px' )
            .css( 'display', 'grid' )
            .css( 'grid-template-columns', '10fr 1fr 30fr' )
            .css( 'grid-auto-rows', 'max-content' )
            .css( 'grid-gap', '5px' );
        
        var genResField = function( res )
        {
            return $( '<span />' )
                .attr( 'id', 'newresolutionvalue' )
                .text( res )
                .on( 'click', function(){
                    changeResolutionIn();
                } );
        }
        
        var changeResolutionIn = function()
        {
            var gfunc = changeResolutionOut;
            var field = $( '<input />' )
                .attr( 'id', 'newresolutionvalue' )
                .blur( gfunc )
                .keypress( function( e )
                {
                    if( e.which == 13 )
                    {
                        gfunc();
                    }
                } );
            
            $( '#resolutionfield' ).html( field );
            $( '#newresolutionvalue' ).focus();
        }
        
        var changeResolutionOut = function()
        {
            var res = $( '#newresolutionvalue' ).val();
            
            if( $.isNumeric( res ) )
            {
                $.ajax( {
                    url: '?action=rpc&method=changeResolution',
                    
                    type: "POST",
                    dataType: 'json',
                    data: { id: img.id, newResolution: res },
                    
                    success: function( data )
                    {
                        if( data.error === false )
                        {
                            toastr.success( "Resolution updates" );
                        } else {
                            toastr.error( "Database error" );
                        }
                    },
                    error: function( data )
                    {
                        toastr.error( "Network error" );
                    }
                } );
            } else {
                res = oldres;
            }
            
            $( '#resolutionfield' ).html( genResField( res ) );
        }
        
        metadata.append( $( '<strong />' ).text( "Name" ) );
        metadata.append( $( '<span />' ).text( "" ) );
        metadata.append( $( '<span />' ).text( img.name ) );
        
        metadata.append( $( '<strong />' ).text( "Id" ) );
        metadata.append( $( '<span />' ).text( "" ) );
        metadata.append( $( '<span />' ).text( img.id ) );
        
        metadata.append( $( '<strong />' ).text( "Width / Height" ) );
        metadata.append( $( '<span />' ).text( "" ) );
        metadata.append( $( '<span />' ).text( img.width + "px / " + img.height + "px" ) );
        
        metadata.append( $( '<strong />' ).text( "Resolution" ) );
        metadata.append( $( '<span />' ).text( "*" ) );
        metadata.append(
            $( '<span />' )
                .attr( 'id', 'resolutionfield' )
                .css( 'cursor', 'pointer' )
                .append( genResField( img.resolution ) )
        );
        
        metadata.append( $( '<strong />' ).text( "Type" ) );
        metadata.append( $( '<span />' ).text( "" ) );
        metadata.append( $( '<span />' ).text( img.type ) );
        
        metadata.append( $( '<strong />' ).text( "Upload date" ) );
        metadata.append( $( '<span />' ).text( "" ) );
        metadata.append( $( '<span />' ).text( img.uploaddate ) );
        
        metadata.append( $( '<strong />' ).text( "Owner" ) );
        metadata.append( $( '<span />' ).text( "" ) );
        metadata.append( $( '<span />' ).text( img.owner ) );
        
        metadata.append( $( '<strong />' ).text( "Usage" ) );
        metadata.append( $( '<span />' ).text( "" ) );
        metadata.append( $( '<span />' ).text( img.usage ) );
        
        var metadatadown = $( '<div />' )
            .css( 'position', 'absolute' )
            .css( 'bottom', '10px' )
            .css( 'right', '20px' )
            .text( "* click on the value to change" );
        
        var metadatatopdown = $( '<div />' )
            .append( metadata )
            .append( metadatadown );
        
        editor
            .append( image )
            .append( metadatatopdown );
        
        var dlgopts = {
            autoOpen: true,
            modal: true,
            width: $( window ).width() * 0.5,
            height: $( window ).height() * 0.5,
            resizable: true,
            title: "Image metadata viewer",
            buttons: {
                'Close': function()
                {
                    $( this ).dialog( 'close' );
                    location.reload();
                },
            }
        };
        
        $( '<div />' )
            .attr( 'id', 'imgeditordlg' )
            .addClass( 'pianos4_dialog' )
            .append( editor )
            .dialog( dlgopts );
    }
    
    var filterImages = function( images, filter )
    {
        var ret = [];
        var filterVal = $( '#filterBar' ).val();
        
        if( filterVal == "" )
        {
            return images
        } else {
            for( var i in images )
            {
                var tmp = images[ i ];
                var keys = [ 'name', 'owner', 'type', 'resolution' ];
                
                for( var key in keys )
                {
                    if( tmp[ keys[ key ] ].includes( filterVal ) )
                        ret.push( tmp );
                }
                if( tmp[ 'ex_names' ].join( '-' ).includes( filterVal ) )
                    ret.push( tmp );
            }
            
            return ret;
        }
    }
    
    var renderSlice = function( from, to )
    {
        log( 'Rendering from ' + from + ' to ' + to );
        
        var images = filterImages( allImages ).slice( from, to );
        
        var map = {};
        for( var i in images )
        {
            var img = images[ i ];
            var m = {};
            var names = [];
            var ids = [];
            for( var j in img.ex_ids )
            {
                var exid = img.ex_ids[ j ];
                var name = img.ex_names[ j ]
                if( name != 'NULL' && !( exid in m ) )
                {
                    ids.push( exid );
                    names.push( name );
                }
            }
            map[ i ] = [
                ids, names
            ];
        }
        table.html( '' );
        table.append( $( '<div />' ).qtable( {
            data: images,
            columnMode: 'mapping',
            columns: [
                {
                    title: '',
                    render: function( data, i, j, cell )
                    {
                        var image = data[ i ];
                        cell.append( $( '<img />' ).attr( 'src', '?action=get_image&thumb&id=' + image.id ).attr( 'alt', image.name ).css( 'width', '32px' ) );
                        cell.attr( 'data-id', image.id );
                    }
                }, {
                    title: 'Name',
                    render: function( data, i, j, cell )
                    {
                        var image = data[ i ];
                        cell.text( image.name );
                        cell.attr( 'data-name', image.name );
                    },
                }, {
                    title: 'Dimensions',
                    render: function( data, i, j, cell )
                    {
                        var img = data[ i ];
                        cell.text( img.width + 'x' + img.height + '@' + img.resolution );
                        cell.attr( 'data-width', img.width )
                        cell.attr( 'data-height', img.height )
                        cell.attr( 'data-resolution', img.resolution )
                    },
                }, {
                    title: 'Type',
                    render: function( data, i, j, cell )
                    {
                        var img = data[ i ];
                        cell.text( img.type[ 0 ] + img.type.slice( 1 ).toLowerCase() );
                        cell.attr( 'data-type', img.type );
                    },
                }, {
                    title: 'Uploaded on',
                    render: function( data, i, j, cell )
                    {
                        var date = pgTimestamptzToDate( images[ i ].createdon ).toLocaleString();
                        cell.text( date.toString() );
                        cell.attr( 'data-uploaddate', date.toString() );
                    },
                }, {
                    title: 'Owner',
                    render: function( data, i, j, cell )
                    {
                        cell.text( images[ i ].owner );
                        cell.attr( 'data-owner', images[ i ].owner );
                    },
                }, {
                    title: 'Usage',
                    render: function( data, i, j, cell )
                    {
                        assert( i in map );
                        cell.text( map[ i ][ 1 ].length );
                        cell.attr( 'data-usage', map[ i ][ 1 ].length );
                    },
                }, {
                    title: 'in exercises',
                    render: function( data, i, j, cell )
                    {
                        assert( i in map );
                        var m = map[ i ];
                        if( m[ 0 ].length == 0 )
                        {
                            cell.append( $( '<span />' ).text( 'None' ).css( 'font-style', 'italic' ) );
                        } else
                        {
                            for( var k = 0; k < m[ 0 ].length; ++k )
                            {
                                if( k > 0 )
                                    cell.append( ', ' );
                                cell.append( $( '<a />' ).attr( 'href', '#exercise/' + m[ 0 ][ k ] ).text( m[ 1 ][ k ] ) );
                            };
                        }
                    },
                }, {
                    title: '',
                    render: function( data, i, j, cell )
                    {
                        var image = images[ i ];
                        cell.append( $( '<img />' ).attr( 'src', 'css/icons/backend/Delete16.png' ).attr( 'alt', 'Delete' ).css( 'cursor', 'pointer' ).click( function()
                        {
                            var dlg = $( '<div />' ).append( $( '<p />' ).text( "Do you really want to delete the image '" + image.name + "' ?" ) );
                            assert( i in map );
                            var needsCascade = false;
                            if( map[ i ][ 0 ].length > 0 )
                            {
                                dlg.append( $( '<p />' ).css( 'font-weight', 'bold' ).text( 'Warning: all exercises (and results thereof) will be deleted. There is no undo !' ) );
                                needsCascade = true;
                            }
                            dlg.dialog( {
                                title: 'Confirm image deletion',
                                // width: 310,
                                modal: true,
                                buttons: {
                                    'OK': function()
                                    {
                                        udb.app().getModel().rpc( 'deleteImage', {
                                            id: image.id,
                                            cascade: needsCascade
                                        }, function()
                                        {
                                            toastr.success( "Image " + image.id + " deleted", '' );
                                            cell.parents( 'tr' ).remove();
                                            dlg.dialog( 'close' );
                                        } );
                                    },
                                    'Cancel': function()
                                    {
                                        $( this ).dialog( 'close' );
                                    },
                                },
                                close: function()
                                {
                                    $( this ).remove();
                                },
                            } );
                        } ) );
                        
                        cell.attr( 'data-dontclick', 'true' );
                    },
                }
            ]
        } ) );
        
        var rows = table.find( 'tbody > tr' );
        rows.each( function( index )
        {
            var dataset = {};
            $( this ).find( 'td' ).each( function()
            {
                $.extend( dataset, $( this )[ 0 ].dataset )                
            } );
            
            $( this ).find( 'td' ).each( function()
            {
                if( ! $( this )[ 0 ].dataset.dontclick )
                {
                    $( this ).on( 'click', function()
                    {
                        imageEditor( dataset );
                    } );
                }
            } );
        } );
    };
}

Pianos4Backend.MessageManager = function( parent, params )
{
    params = params || {};
    if( !params.icons )
        throw 'Missing icons';
    assert( typeof params.icons == 'object' && 'length' in params.icons && params.icons.length >= 4 );
    
    this.init = function()
    {
        buildUI();
        load( true );
    }

    var self = this;
    var content = null;
    var toolbar = null;
    var list = null;
    var msgType = null;
    var msgVisibility = null;
    var loading = false;
    
    var buildUI = function()
    {
        list = $( '<div />' );
        
        toolbar = $( '<div />' ).css( 'margin-bottom', '1em' ).append( $( '<span />' ).text( 'New message' ).button().click( onNewMessage ) );
        
        content = $( '<div />' ).addClass( 'pianos4_main' ).append( $( '<h2 />' ).text( 'Messages Management' ) ).append( toolbar ).append( list ).appendTo( parent );
    };
    
    var renderMessage = function( message )
    {
        var frame = Pianos4Backend.renderMessage( message ).attr( 'id', 'message_' + message.id );
        frame.find( '.ui-widget-header' ).prepend( $( '<span />' ).append( $( '<img />' ).css( 'margin-right', '1em' ).button().attr( 'src', params.icons[ 2 ] ).click( function()
        {
            onEditMessage( message );
        } ) ).append( $( '<img />' ).css( 'margin-right', '1em' ).button().attr( 'src', params.icons[ 3 ] ).click( function()
        {
            onDeleteMessage( message );
        } ) ).append( $( '<span />' ).css( 'margin-right', '1em' ).text( '[' + message.type + ', ' + message.visibility + ']' ) ) );
        return frame;
    }

    var load = function()
    {
        loading = true;
        udb.app().getModel().rpc( 'getMessageConstants', function( constants )
        {
            msgTypes = constants[ 0 ];
            msgVisibilities = constants[ 1 ];
            assert( msgTypes.length > 0 && msgVisibilities.length > 0 );
            
            udb.app().getModel().rpc( 'getAllMessages', function( messages )
            {
                loading = false;
                for( var i in messages )
                    ( function( message )
                    {
                        list.append( renderMessage( message ) );
                    } )( messages[ i ] );
            } );
        } );
    };
    
    var onNewMessage = function()
    {
        var dlgopts = {
            title: 'New message',
            fields: [
                {
                    type: 'text',
                    name: 'title',
                    label: 'Title',
                    constraints: [
                        'not-empty'
                    ]
                }, {
                    type: 'select',
                    name: 'type',
                    label: 'Type',
                    value: msgTypes[ 0 ].id,
                    values: msgTypes
                }, {
                    type: 'select',
                    name: 'visibility',
                    label: 'Visibility',
                    value: msgVisibilities[ 0 ].id,
                    values: msgVisibilities
                }, {
                    type: 'textarea',
                    name: 'value',
                    label: 'Content',
                    constraints: [
                        'not-empty'
                    ]
                },
            ],
            ok: function( data )
            {
                assert( 'title' in data );
                assert( 'value' in data );
                udb.app().getModel().rpc( 'addMessage', {
                    'title': data.title,
                    'type': data.type,
                    'visibility': data.visibility,
                    'value': data.value
                }, function( msgid )
                {
                    udb.app().getModel().rpc( 'getMessage', {
                        'id': msgid
                    }, function( message )
                    {
                        list.append( renderMessage( message ) );
                    } );
                } );
            },
        };
        Pianos4Backend.showFormDialog( dlgopts );
    }

    var onEditMessage = function( message )
    {
        var dlgopts = {
            title: 'Edit message',
            fields: [
                {
                    type: 'text',
                    name: 'title',
                    label: 'Title',
                    constraints: [
                        'not-empty'
                    ],
                    value: message.title
                }, {
                    type: 'select',
                    name: 'type',
                    label: 'Type',
                    value: message.typeid,
                    values: msgTypes
                }, {
                    type: 'select',
                    name: 'visibility',
                    label: 'Visibility',
                    value: message.visibilityid,
                    values: msgVisibilities
                }, {
                    type: 'textarea',
                    name: 'value',
                    label: 'Content',
                    constraints: [
                        'not-empty'
                    ],
                    value: message.value
                },
            ],
            ok: function( data )
            {
                assert( 'title' in data );
                assert( 'value' in data );
                udb.app().getModel().rpc( 'updateMessage', {
                    'id': message.id,
                    'title': data.title,
                    'type': data.type,
                    'visibility': data.visibility,
                    'value': data.value
                }, function()
                {
                    udb.app().getModel().rpc( 'getMessage', {
                        'id': message.id
                    }, function( message )
                    {
                        list.find( '#message_' + message.id ).replaceWith( renderMessage( message ) );
                    } );
                } );
            },
        };
        Pianos4Backend.showFormDialog( dlgopts );
    }

    var onDeleteMessage = function( message )
    {
        var dlgopts = {
            title: 'Confirm message deletion',
            fields: [
                {
                    type: 'static',
                    label: "Are you sure you wish to delete the message '" + message.title + "' ?"
                },
            ],
            ok: function( data )
            {
                udb.app().getModel().rpc( 'deleteMessage', {
                    'id': message.id
                }, function()
                {
                    list.find( '#message_' + message.id ).remove();
                } );
            },
        };
        
        Pianos4Backend.showFormDialog( dlgopts );
    }

}

Pianos4Backend.ResetUserPassword = function( user )
{
    var resetui = $( '<div />' )
        .css( 'padding-top', '20px' );
    
    $( '<div />' )
        .text( "Change password for the user " + user.name + " (" + user.username + "):" )
        .append(
            $( '<div />' )
                .css( 'display', 'grid' )
                .css( 'grid-template-columns', '1fr 2fr' )
                .css( 'grid-gap', '2px' )
                .css( 'height', 'calc(100% - 20px)' )
                .css( 'width', '100%' )
                .css( 'margin-top', '2em' )
                .append( $( '<span />' ).text( 'New password' ) )
                .append(
                    $( '<input />' )
                        .attr( 'type', 'password' )
                        .attr( 'id', 'newpassword' )
                        .on( 'keydown', function( e )
                        {
                            if ( e.which == 13 )
                            {
                                e.preventDefault();
                                validate();
                            }
                        } )
                )
                .append( $( '<span />' ).text( 'Re-type password' ) )
                .append(
                    $( '<input />' )
                        .attr( 'type', 'password' )
                        .attr( 'id', 'newpasswordconfirm' )
                        .on( 'keydown', function( e )
                        {
                            if ( e.which == 13 )
                            {
                                e.preventDefault();
                                validate();
                            }
                        } )
                )
        )
        .appendTo( resetui );
    
    var validate = function()
    {
        var pass = $( '#newpassword' ).val();
        var conf = $( '#newpasswordconfirm' ).val();
        var error = false;
        
        if( pass == "" || conf == "" )
        {
            toastr.error( "No empty password allowed" );
            error = true;
        }
        
        if( pass != conf )
        {
            toastr.error( "Password and confirmation field not equal" );
            error = true;
        }
        
        if( ! error )
        {
            var h = hex_md5( user.username + ':' + pass );
            
            $.ajax( {
                url: '?action=rpc&method=updateUserPassword',
                data: {
                    uid: user.id,
                    name: user.username,
                    newpassword: h
                },
                type: "POST",
                dataType: 'json',
                
                success: function( data )
                {
                    if( ! data.error )
                    {
                        toastr.success( "Password updated!" );
                        $( '#changeuserpasswordui' ).dialog( 'close' );
                    } else {
                        toastr.error( data.message, "Server side error" );
                    }
                },
                error: function( data )
                {
                    toastr.error( "Network error" );
                }
            } );
        }
    }
    
    var dlgopts = {
        autoOpen: true,
        modal: true,
        width: 500,
        height: 250,
        resizable: true,
        title: "Reset user password",
        buttons: {
            'OK': function()
            {
                validate();
            },
            'Close': function()
            {
                $( this ).dialog( 'close' );
            },
        }
    }
    
    $( '<div />' )
        .attr( 'id', 'changeuserpasswordui' )
        .addClass( 'pianos4_dialog' )
        .append( resetui )
        .dialog( dlgopts );
}
