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

p4 = typeof p4 == 'object' ? p4 : {};
p4.app = null;

p4.RpcApi = function( params )
{
    params = params || {};
    if( !params.ajax )
        params.ajax = $.ajax;
    if( !params.error )
        params.error = function( title, message )
        {
            toastr.error( message, title, { timeout: 5000 } );
        };
    if( !params.schemaName )
        params.schemaName = null;
    if( !params.apiUrl )
        params.apiUrl = null;
    if( !params.apiUrlArgs )
        params.apiUrlArgs = [
            [
                'action', 'rpc'
            ]
        ];
    if( !params.whenLoaded )
        params.whenLoaded = null;
    for( var i in params.apiUrlArgs )
        for( var j = 0; j < 2; ++j )
            params.apiUrlArgs[ i ][ j ] = encodeURIComponent( params.apiUrlArgs[ i ][ j ] );
    
    this.schema = function()
    {
        return _schema;
    }

    this.srpc = function( method, args, kwargs, options )
    {
        if( typeof ( options ) == 'undefined' )
            var options = {};
        var retVal = null;
        
        options.async = false;
        
        options.onSuccess = function( ret, message )
        {
            retVal = ret;
        };
        
        self.rpc( method, args, kwargs, options );
        
        return retVal;
    }

    this.rpcUrl = function( method, args, kwargs )
    {
        var urlArgs = params.apiUrlArgs.concat( [
            [
                'method', encodeURIComponent( method )
            ]
        ] );
        var urlArgsInner = [];
        for( var i in urlArgs )
            urlArgsInner.push( urlArgs[ i ].join( '=' ) );
        var url = ( params.apiUrl || '' ) + '?' + urlArgsInner.join( '&' )
        if( typeof ( args ) == 'object' )
        {
            if( !( 'length' in args ) )
                throw 'p4.RpcApi.rpcUrl() : second argument must be an array';
            for( var i = 0; i < args.length; ++i )
                url += '&args[]=' + encodeURIComponent( args[ i ] );
        }
        if( typeof ( kwargs ) == 'object' )
        {
            for( var i in kwargs )
                url += '&' + encodeURIComponent( i ) + '=' + encodeURIComponent( kwargs[ i ] );
        }
        return url;
    }

    // "Smart" method to call model::rpcRaw(method,args,kwargs,options) that
    // infers the type and order of each parameter. Additionnally, it can take
    // a single function, in which case it behaves as the options object with
    // only
    // the 'onSuccess' member set.
    // e.g. p4.RpcApi.rpc('myMethod', {x:3,y:4})
    // p4.RpcApi.rpc('getFoo', function(foo) { log(foo); });
    // p4.RpcApi.rpc('setBar', [2,3,4])
    this.rpc = function( method, a, b, c )
    {
        var args = null;
        var kwargs = null;
        var options = null;
        var usePost = null;
        
        var assign = function( v )
        {
            if( typeof ( v ) == 'object' )
            {
                if( 'length' in v )
                {
                    if( args )
                        throw 'p4.RpcApi.rpc() : duplicate parameter: args';
                    args = v;
                } else if( 'onSuccess' in v )
                {
                    if( options )
                        throw 'p4.RpcApi.rpc() : duplicate parameter: options';
                    options = v;
                } else
                {
                    if( kwargs )
                        throw 'p4.RpcApi.rpc() : duplicate parameter: kwargs';
                    kwargs = v;
                }
            } else if( typeof ( v ) == 'function' )
            {
                if( options )
                    throw 'p4.RpcApi.rpc() : duplicate parameter: options';
                options = {
                    onSuccess: v
                };
            } else if( typeof ( v ) == 'boolean' )
            {
                usePost = v;
            }
        }
        assign( a );
        assign( b );
        assign( c );
        if( args === null )
            args = [];
        if( kwargs === null )
            kwargs = {};
        if( options === null )
            options = {};
        if( typeof ( usePost ) == 'boolean' )
            options.usePost = usePost;
        return self.rpcRaw( method, args, kwargs, options );
    },

    this.rpcRaw = function( method, args, kwargs, options )
    {
        assert( method );
        if( typeof ( args ) == 'undefined' )
            var args = [];
        if( typeof ( kwargs ) == 'undefined' )
            var kwargs = {};
        if( typeof ( options ) == 'undefined' )
            var options = {};
        var hasKwArgs = false;
        for( var i in kwargs )
        {
            hasKwArgs = true;
            break;
        }
        
        var usePost = ( hasKwArgs || args.length > 0 ) && ( typeof ( options.usePost ) != 'boolean' || options.usePost );
        
        var data = null;
        if( usePost )
        {
            data = {};
            for( var i in kwargs )
                data[ i ] = kwargs[ i ];
            if( args.length )
                data[ 'args[]' ] = args;
            data = JSON.stringify( data );
        }
        
        var wasSuccessful = false;
        
        var o = {
            url: usePost ? self.rpcUrl( method ) : self.rpcUrl( method, args, kwargs ),
            type: usePost ? 'post' : 'get',
            data: data,
            dataType: 'json',
            contentType: usePost ? 'application/json' : null,
            traditional: false,
            processData: false,
            success: function( data )
            {
                // When an unhandled exception occurs on the server-side, the
                // HTTP request
                // is a 200 OK, but the data.error flag is set.
                if( data.error )
                {
                    triggerError( data.message, method, args, kwargs );
                } else if( options.onSuccess )
                {
                    wasSuccessful = true;
                    options.onSuccess( data.ret, data.message );
                }
            },
            complete: function( data )
            {
                if( options.onComplete )
                    options.onComplete( wasSuccessful );
            }
        };
        
        o = $.extend( o, options );
        params.ajax( o );
    };
    
    var self = this;
    var _schema = null;
    
    var triggerError = function( message, method, args, kwargs )
    {
        var header = 'RPC error in ' + rpcFormat( method, args, kwargs );
        
        if( console && console.trace )
            console.trace( message );
        
        params.error( header, message );
    }

    var rpcFormat = function( method, args, kwargs )
    {
        assert( method );
        args = args || [];
        var kwargsArr = [];
        if( typeof ( kwargs ) == 'object' )
            for( var i in kwargs )
                kwargsArr.push( i + '=' + kwargs[ i ] )
        return method + '(' + args.join( ',' ) + ( kwargsArr.length ? ( args.length ? ',' : '' ) + kwargsArr.join( ',' ) : '' ) + ')';
    };
    
    ( ( function()
    {
        if( params.schemaName )
        {
            log( 'Fetching the schema ' + params.schemaName );
            self.rpc( 'getSchema', {
                name: params.schemaName
            }, {
                onSuccess: function( ret, message )
                {
                    _schema = ret;
                    if( params.whenLoaded )
                        params.whenLoaded();
                },
                usePost: false,
                pendingMessage: 'Fetching the schema',
            } );
        }
        
    } )() );
}

p4.lab = p4.lab || {};

p4.lab.icons = {
    folder: [
        'css/icons/backend/folder.gif', 'css/icons/backend/folderopen.gif'
    ],
    pgroup: [
        'css/icons/backend/Address-book16.png', 'css/icons/backend/Address-book-open16.png'
    ],
    exercise: 'css/icons/backend/page.gif',
    person: 'css/icons/backend/Person16.png',
    person_inactive: 'css/icons/backend/Person16.png',
    _delete: 'css/icons/backend/Delete16.png',
    adduser: 'css/icons/backend/NewPerson16.png',
    addgroup: 'css/icons/backend/NewAddress-book16.png',
    refresh: 'css/icons/backend/arrow_refresh.png',
    management: 'css/icons/backend/cog_edit.png',
    image: 'css/icons/backend/images.png',
};

p4.lab.app = function( selector, params )
{
    // ------------------------------
    // Constructor
    // ------------------------------
    params = params || {};
    if( !params.user )
        params.user = {
            id: 0,
            name: 'Unnamed user',
            admin: false
        };
    if( !params.icons )
        params.icons = $.extend( true, {}, p4.lab.icons );
    
    // ------------------------------
    // Public methods
    // ------------------------------
    
    this.run = function()
    {
        p4.app = self;
        buildUI();
    }

    this.api = new p4.RpcApi();
    
    // ------------------------------
    // Private variables
    // ------------------------------
    
    var self = this;
    var layout = null;
    var panes = {};
    
    // ------------------------------
    // Private functions
    // ------------------------------
    
    var buildUI = function()
    {
        log( 'building main ui' );
        var me = $( '<div />' );
        panes.header = new p4.lab.AppHeader( me );
        panes.explorer = new p4.lab.Explorer( me );
        panes.main = new p4.lab.Home( me );
        
        // The heights of both <html> and <body> are already set to 100% using
        // the CSS, but we re-assign this style anyway. The container (passed
        // as the 'selector' argument) div must also has 100% height.
        $( 'html, body' ).add( selector ).add( me ).css( 'height', '100%' );
        
        // Don't know if it's a bug or feature, but calling layout() before the
        // content is actually added to the DOM has no effect. I expect a FOUC
        // to happen here on rare occasions (and bugs also).
        selector.append( me );
        
        layout = me.layout( {
            defaults: {
                fxName: 'none',
            // spacing_open: params.layoutBorderWidth,
            // spacing_closed: params.layoutBorderWidth,
            // applyDefaultStyles: true,
            },
            center: {
            // onresize: function() { onWindowSizeChanged(false); },
            },
            west: {
                size: 200,
            },
            north: {
                // resizable: false,
                // closable: false,
                // spacing_open: 0,
                size: 32,
            },
        } );
    }
}

p4.lab.DataTable = function( selector, collectionId )
{
    var showError = function( message )
    {
        $( '<div />' ).css( 'white-space', 'pre' ).text( message ).dialog( {
            title: 'Error while loading table model',
            buttons: {
                'OK': function()
                {
                    $( this ).dialog( 'close' );
                },
            },
            close: function()
            {
                $( this ).remove();
            },
        } );
    }

    var jTableRpc = function( cont )
    {
        $.ajax( {
            url: '?action=jtable&c=' + encodeURIComponent( collectionId ) + '&m=model',
            dataType: 'json',
            success: function( data )
            {
                log( data );
                if( data.Result == 'OK' )
                {
                    if( typeof data.Fields != 'object' )
                        showError( "Invalid server response: missing 'Fields' in dataset" );
                    else if( typeof data.Validation != 'object' )
                        showError( "Invalid server response: missing 'Validation' in dataset" );
                    else
                        cont( data.Fields, data.Validation );
                } else
                    showError( data.Message );
            },
            error: function( a, b, c )
            {
                log( a );
                log( b );
                log( c );
                showError( b + '\n' + c );
            },
        } );
    }

    var init = function()
    {
        var me = $( '<div />' ).append( $( '<div />' ).addClass( 'jtable-main-container' ).append( $( '<div />' ).addClass( 'jtable-title' ).append( $( '<div />' ).addClass( 'jtable-title-text' )
            .text( 'Loading table model ...' ) ) ) );
        selector.append( me );
        
        // p4.app.api.rpc('hello');
        jTableRpc( post_init );
    }

    var post_init = function( _fields, validation )
    {
        var me = selector.find( '>div:first' );
        
        var fields = $.extend( true, {}, _fields/*
                                                 * , { id: { key: true, list:
                                                 * false }, username: { title:
                                                 * 'Username', //width: '40%' },
                                                 * name: { title: 'Name',
                                                 * //width: '20%' }, active: {
                                                 * title: 'Active', type:
                                                 * 'checkbox', values: { false :
                                                 * 'Disabled', true : 'Active' },
                                                 * formText: 'Active', }, email: {
                                                 * title: 'Email', //width:
                                                 * '20%' }, }
                                                 */);
        
        var enccid = encodeURIComponent( collectionId );
        me.html( '' ).jtable( {
            title: 'Table of ' + collectionId,
            tableId: 'p4.lab.' + collectionId,
            actions: {
                listAction: '?action=jtable&m=list&c=' + enccid,
                createAction: '?action=jtable&m=create&c=' + enccid,
                updateAction: '?action=jtable&m=update&c=' + enccid,
                deleteAction: '?action=jtable&m=delete&c=' + enccid,
            },
            sorting: true,
            paging: true,
            fields: fields,
            // Initialize validation logic when a form is created
            formCreated: function( event, data )
            {
                for( var fname in validation )
                {
                    var method = validation[ fname ];
                    data.form.find( 'input[name="' + fname + '"]' ).addClass( 'validate[' + method + ']' );
                }
                data.form.validationEngine( 'attach', {
                    promptPosition: 'inline',
                    scroll: false
                } );
            },
            // Validate form when it is being submitted
            formSubmitting: function( event, data )
            {
                return data.form.validationEngine( 'validate' );
            },
            // Dispose validation logic when form is closed
            formClosed: function( event, data )
            {
                data.form.validationEngine( 'hide' );
                data.form.validationEngine( 'detach' );
            }
        } ).jtable( 'load' );
    }
    var self = this;

    ;
    ( function()
    {
        init();
    } )();
}

p4.lab.AppHeader = function( selector, params )
{
    var init = function()
    {
        params = params || {};
        selector.append( $( '<div />' ).addClass( 'ui-layout-north' ).text( 'i am the header' ) );
    }
    var self = this;

    ;
    ( function()
    {
        init();
    } )();
}

p4.lab.Explorer = function( selector, params )
{
    var init = function()
    {
        params = params || {};
        selector.append( $( '<div />' ).addClass( 'ui-layout-west' ).text( 'i am the explorer' ) );
    }
    var self = this;

    ;
    ( function()
    {
        init();
    } )();
}

p4.lab.Home = function( selector, params )
{
    var init = function()
    {
        params = params || {};
        
        var me = $( '<div />' ).addClass( 'ui-layout-center' ).text( 'i am the home page' );
        var table = new p4.lab.DataTable( me, 'users' );
        selector.append( me );
    }
    var self = this;

    ;
    ( function()
    {
        init();
    } )();
}
