/*
 * 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/.
 */

/**
 * @constructor
 */
function Pianos4ToolReview( params )
{
    // ------------------------------
    // Constructor
    // ------------------------------
    
    params = params || {};
    if( !params.lineColor )
        params.lineColor = '#dd3300';
    if( !params.fillColor )
        params.fillColor = '#101000';
    
    // ------------------------------
    // Public methods
    // ------------------------------
    
    this.getName = function()
    {
        return 'review';
    }

    this.getLabel = function()
    {
        return 'Review';
    }

    this.getIcon = function()
    {
        return 'icons/select_analysis.png';
    }

    this.bind = function( aView )
    {
        assert( !view );
        view = aView;
    }

    this.unbind = function()
    {
        assert( view );
        view = null;
    }

    this.activate = function()
    {
    }

    this.deactivate = function()
    {
    }

    this.onMouseDown = function( pos, target, event )
    {
        // if (popup)
        // return false;
        
        assert( !dragSel );
        dragSel = {
            from: pos,
            to: pos,
            quad: view.getSVG().append( 'svg:rect' ).attr( 'x', pos[ 0 ] ).attr( 'y', pos[ 1 ] ).attr( 'width', 0 ).attr( 'height', 0 ).style( 'stroke', params.lineColor )
                .style( 'stroke-width', 1.0 / view.getZoomFactor() ).style( 'fill', params.lineColor ).style( 'fill-opacity', 0.3 )
        };
    }

    this.onMouseMove = function( pos, target )
    {
        // if (popup)
        // return false;
        
        if( dragSel )
        {
            dragSel.to = pos;
            var p = $.extend( true, [], dragSel.from );
            var size = [
                pos[ 0 ] - p[ 0 ], pos[ 1 ] - p[ 1 ]
            ];
            for( var i = 0; i < 2; ++i )
            {
                if( size[ i ] < 0.0 )
                {
                    p[ i ] += size[ i ];
                    size[ i ] = Math.abs( size[ i ] );
                }
            }
            dragSel.quad.attr( 'x', p[ 0 ] ).attr( 'y', p[ 1 ] ).attr( 'width', size[ 0 ] ).attr( 'height', size[ 1 ] );
            dragSel.tl = p;
            dragSel.br = [
                p[ 0 ] + size[ 0 ], p[ 1 ] + size[ 1 ]
            ];
        }
    }

    this.onMouseUp = function( pos, target, event )
    {
        // if (popup)
        // return false;
        
        if( dragSel )
        {
            dragSel.quad.remove();
            
            if( 'tl' in dragSel )
            {
                var rotationData = rotationView[ view.getType() ];
                
                var sel = [];
                var minutiae = view.getData( 'minutiae' ).minutiae;
                for( id in minutiae )
                {
                    var min = minutiae[ id ];
                    var minrotatedpos = rotateAround( min.pos, rotationData, true );
                    
                    if( minrotatedpos[ 0 ] >= dragSel.tl[ 0 ] && minrotatedpos[ 0 ] <= dragSel.br[ 0 ] &&
                        minrotatedpos[ 1 ] >= dragSel.tl[ 1 ] && minrotatedpos[ 1 ] <= dragSel.br[ 1 ] )
                        sel.push( min );
                }
                
                if( sel.length )
                {
                    showPopup( sel );
                }
            }
            
            dragSel = null;
        }
        return true;
    }

    this.cancel = function()
    {
        if( popup )
            hidePopup();
        
        if( dragSel )
        {
            dragSel.quad.remove();
            dragSel = null;
        }
        
    }

    this.getOptionsPanel = function()
    {
        if( !options )
        {
            var panel = $( '<div />' ).addClass( 'pianos4_review_options' );
            
            panel.append( $( '<a />' ).attr( 'href', '#' ).text( 'Export ...' ).click( function( e )
            {
                e.preventDefault();
                
                var dlg = $( '<div />' ).text( 'Please choose the exportation format and options:' );
                var id_base = 'pianos4_review_opt_' + ( new Date().getTime() )

                var form = $( '<form />' ).attr( 'action', '?action=export' ).attr( 'enctype', 'multipart/form-data' ).attr( 'method', 'post' ).appendTo( dlg );
                
                var mkradios = function( title, key, items )
                {
                    var div = $( '<div />' ).css( 'margin', '0.5em 0' );
                    
                    var id = id_base + '_' + key;
                    var title = $( '<span />' ).text( title ).css( 'display', 'inline-block' ).css( 'width', '5em' );
                    div.append( title );
                    
                    for( var i in items )
                    {
                        var item = items[ i ];
                        var label = $( '<label />' ).attr( 'for', id + '_' + i ).text( item.label ).css( 'display', 'inline-block' ).css( 'width', '7em' );
                        var opt = $( '<input />' ).attr( 'id', id + '_' + i ).attr( 'type', 'radio' ).attr( 'name', key ).attr( 'value', item.value );
                        
                        if( item.disabled )
                        {
                            label.css( 'color', '#666666' );
                            opt.prop( 'disabled', true );
                        }
                        
                        if( i == 0 )
                            opt.prop( 'checked', true );
                        
                        div.append( opt ).append( label );
                    }
                    
                    form.append( div );
                };
                
                var views = pianos.getViews();
                var count = 0;
                var vsel = {};
                for( var i in views )
                {
                    var view = views[ i ];
                    var sel = view.getSelection();
                    for( var j in sel )
                        count++;
                    vsel[ i ] = sel;
                }
                
                mkradios( 'Subset', 'subset', [
                    {
                        label: 'All',
                        value: 'all'
                    }, {
                        label: 'Users ticked',
                        value: 'users'
                    }, {
                        label: 'Selection',
                        value: 'selection',
                        disabled: count == 0
                    }, {
                        label: 'Visible',
                        value: 'screen',
                        disabled: true
                    },
                ] );
                mkradios( 'Format', 'format', [
                    {
                        label: 'JSON',
                        value: 'json'
                    }, {
                        label: 'CSV',
                        value: 'csv'
                    },
                ] );
                mkradios( 'Delivery', 'delivery', [
                    {
                        label: 'Download',
                        value: 'download'
                    }, {
                        label: 'View online',
                        value: 'inline'
                    },
                ] );
                
                var downloadData = $( '<input />' ).attr( 'type', 'hidden' ).attr( 'name', 'data' ).appendTo( form );
                var exidData = $( '<input />' ).attr( 'type', 'hidden' ).attr( 'name', 'exid' ).appendTo( form );
                
                var opts = {
                    title: 'Export results',
                    width: 600,
                    modal: true,
                    buttons: {
                        'OK': function()
                        {
                            var settings = form.serializeObject();
                            var indata = null;
                            var exid = getURLParameter( 'id' );
                            if( settings.subset == 'users' )
                            {
                                var vmap = pianos.getUsersResultsVisibility();
                                indata = pianos.getData( null, function( annot, view )
                                {
                                    assert( 'userid' in annot && annot.userid in vmap );
                                    return vmap[ annot.userid ];
                                } );
                            } else if( settings.subset == 'selection' )
                            {
                                indata = pianos.getData( null, function( annot, view )
                                {
                                    var vtype = view.getType();
                                    if( !( vtype in vsel ) )
                                        return false;
                                    var sel = vsel[ vtype ];
                                    return annot.id in sel;
                                } );
                            } else if( settings.subset == 'screen' )
                            {
                                assert( false );
                            } else
                            {
                                indata = pianos.getData();
                            }
                            
                            var data = {};
                            var serverData = pianos.getServerData();
                            var serverDataIdx = null;
                            if( serverData )
                            {
                                // serverData is the complete dataset retrieved
                                // from the server (or per initialization)
                                // It contains the annotations as they were
                                // lastly fetched (which may differ from
                                // the annotations currently shown/stored in the
                                // various controls of the UI) and also
                                // an additionnal piece of info we want to
                                // export: the observations.
                                // It's a mere array of results, and needs to be
                                // indexed by user for fast retrieval.
                                serverDataIdx = {};
                                for( var i in serverData )
                                {
                                    var result = serverData[ i ];
                                    if( 'observations' in result )
                                    {
                                        assert( 'user' in result && 'id' in result.user );
                                        serverDataIdx[ result.user.id ] = i;
                                    }
                                }
                            }
                            
                            for( var vtype in indata )
                            {
                                var invdata = indata[ vtype ];
                                var vdata = {
                                    'image': null,
                                    'users': {}
                                };
                                for( atype in invdata )
                                {
                                    if( atype == 'image' )
                                    {
                                        vdata[ atype ] = invdata[ atype ];
                                    } else
                                    {
                                        var inadata = invdata[ atype ];
                                        for( var aid in inadata )
                                        {
                                            var annot = inadata[ aid ];
                                            var udata = null;
                                            if( !( annot.userid in vdata[ 'users' ] ) )
                                            {
                                                udata = {};
                                                if( serverDataIdx && annot.userid in serverDataIdx )
                                                {
                                                    assert( 'observations' in serverData[ serverDataIdx[ annot.userid ] ] );
                                                    udata[ 'observations' ] = $.extend( true, {}, serverData[ serverDataIdx[ annot.userid ] ].observations );
                                                } else
                                                {
                                                    udata[ 'observations' ] = {};
                                                }
                                                vdata[ 'users' ][ annot.userid ] = udata;
                                            } else
                                                udata = vdata[ 'users' ][ annot.userid ];
                                            
                                            var tdata = null;
                                            if( !( atype in udata ) )
                                            {
                                                tdata = {};
                                                udata[ atype ] = tdata;
                                            } else
                                                tdata = udata[ atype ];
                                            
                                            var copy = $.extend( true, {}, annot );
                                            delete copy.userid;
                                            tdata[ aid ] = copy;
                                        }
                                    }
                                }
                                assert( vdata[ 'image' ] );
                                data[ vtype ] = vdata;
                            }
                            
                            if( settings.delivery == 'download' )
                            {
                                downloadData.val( JSON.stringify( data ) );
                                exidData.val( exid );
                                form.submit();
                            } else
                            {
                                pianos.ajax( {
                                    pendingMessage: 'Exporting',
                                    type: 'post',
                                    processData: false,
                                    contentType: 'application/json',
                                    dataType: 'text',
                                    url: '?action=export',
                                    data: JSON.stringify( $.extend( settings, {
                                        data: data,
                                        exid: exid,
                                    } ) ),
                                    success: function( data )
                                    {
                                        showExportDialog( data );
                                    },
                                    error: function( xhr, status, error )
                                    {
                                        alert( 'HTTP error (code ' + xhr.status + '): ' + ( error ? error : 'connection failed' ) );
                                    },
                                } );
                            }
                            
                            $( this ).dialog( 'close' );
                        },
                        'Cancel': function()
                        {
                            $( this ).dialog( 'close' );
                        }
                    },
                    close: function()
                    {
                        $( this ).remove();
                    },
                };
                
                pianos.dialog( dlg, opts );
                
            } ) );
            
            options = panel;
        }
        
        return options;
    }

    // ------------------------------
    // Private variables
    // ------------------------------
    
    var self = this;
    var view = null;
    var dragSel = null;
    var popup = null;
    var options = null;
    
    // ------------------------------
    // Private functions
    // ------------------------------
    
    var showPopup = function( sel )
    {
        assert( view );
        
        if( !popup )
        {
            popup = $( '<div class="pianos4_tool_review_popup" />' );
            
            popup.dialog( {
                title: 'Annotations by user',
                autoOpen: false,
                width: 500,
                buttons: {
                    'OK': function()
                    {
                        hidePopup();
                    },
                },
            } );
        }
        
        var div = $( '<div />' );
        var overview = $( '<table class="overview"><thead><tr /></thead><tbody></tbody></table>' ).appendTo( div );
        var details = $( '<table class="details"><thead><tr /></thead><tbody></tbody></table>' );
        
        var rowClasses = [
            'even', 'odd'
        ];
        var minutiaTypes = {
            'bifurcation': 'BIF',
            'ridge_ending': 'END',
            'unknown2': 'TUNK',
            'unknown': 'PUNK',
            'difference': 'DIFF',
            'center': 'CENT',
        };
        
        var users = pianos.getUsers();
        var minutiae = {
            '-1': {}
        };
        for( var j in minutiaTypes )
            // We store the sum of all annotations from all users at the -1
            // index
            minutiae[ -1 ][ j ] = [];
        users[ -1 ] = {
            id: -1,
            name: 'All users'
        };
        
        details.find( 'thead tr' ).append( $( '<th />' ).text( 'User' ) ).append( $( '<th />' ).text( 'ID' ) ).append( $( '<th />' ).text( 'Type' ) ).append( $( '<th />' ).text( 'X' ) )
            .append( $( '<th />' ).text( 'Y' ) ).append( $( '<th />' ).text( 'Angle' ) ).append( $( '<th />' ).text( 'Pair' ) );
        
        for( var i in sel )
        {
            var annotation = sel[ i ];
            if( view.getAnnotationType( annotation.id ) == 'minutia' )
            {
                assert( annotation.userid in users && annotation.type in minutiaTypes );
                var user = users[ annotation.userid ];
                
                if( !( user.id in minutiae ) )
                {
                    minutiae[ user.id ] = {};
                    for( var j in minutiaTypes )
                        minutiae[ user.id ][ j ] = [];
                }
                minutiae[ user.id ][ annotation.type ].push( annotation.id );
                minutiae[ -1 ][ annotation.type ].push( annotation.id );
                
                var tr = $( '<tr />' ).addClass( rowClasses[ i % 2 ] );
                tr.append( $( '<td />' ).text( user.name ) );
                tr.append( $( '<td />' ).text( user.id ) );
                tr.append( $( '<td />' ).text( minutiaTypes[ annotation.type ] ) );
                tr.append( $( '<td />' ).text( annotation.pos[ 0 ].toFixed( 2 ) ) );
                tr.append( $( '<td />' ).text( annotation.pos[ 1 ].toFixed( 2 ) ) );
                tr.append( $( '<td />' ).text( annotation.angle.toFixed( 2 ) ) );
                tr.append( $( '<td />' ).text( 'pairing' in annotation ? annotation.pairing : '' ) );
                details.append( tr );
            }
        }
        
        var overview_labels = {
            'BIF': 'Bifurcation',
            'END': 'Ridge ending',
            'TUNK': 'Type unknown',
            'PUNK': 'Position unknown',
            'DIFF': 'Difference',
            'CENT': 'Center'
        };
        var overview_head_tr = overview.find( 'thead tr' );
        overview_head_tr.append( $( '<th />' ).text( 'Author' ) );
        for( var j in minutiaTypes )
        {
            overview_head_tr.append( $( '<th class="rotated" />' ).text( overview_labels[ minutiaTypes[ j ] ] ) );
        }
        
        var currentView = view;
        var overview_body = overview.find( 'tbody' );
        i = 0;
        for( var userid in minutiae )
        {
            var user = users[ userid ];
            var data = minutiae[ userid ];
            
            var tr = $( '<tr />' ).addClass( rowClasses[ i++ % 2 ] );
            tr.append( $( '<td class="username" />' ).text( user.name ) );
            for( var j in minutiaTypes )
                ( function( mids )
                {
                    var td = $( '<td />' );
                    if( mids.length > 0 )
                    {
                        $( '<span />' ).addClass( 'nonzero' ).text( mids.length ).data( 'pianos4_review', mids ).bind( 'mouseenter', function( e )
                        {
                            var d = $( this ).data( 'pianos4_review' );
                            toggleMinutiae( currentView, d, true );
                        } ).bind( 'mouseleave', function( e )
                        {
                            var d = $( this ).data( 'pianos4_review' );
                            toggleMinutiae( currentView, d, false );
                        } ).appendTo( td );
                    }
                    tr.append( td );
                } )( data[ j ] );
            overview_body.append( tr );
        }
        overview_body.find( 'tr:first' ).addClass( 'allusers' );
        
        var hint = $( '<div class="details_hint" />' ).append( $( '<span />' ).text( 'To paste into Excel: ' ) ).append( $( '<a href="#" />' ).text( 'select the data' ).bind( 'click', function( e )
        {
            e.preventDefault();
            // $(this).parent().siblings('table').get(0).select();
            selectElementContents( $( this ).parent().siblings( 'table' ).get( 0 ) );
        } ) ).append( $( '<span />' ).text( ', copy and click Paste > Paste Special > As Unicode Text' ) );
        
        div.append( $( '<div />' ).append( '<span class="toggle_details ui-icon ui-inline-icon ui-icon-triangle-1-e" />' ).append( $( '<a href="#toggle" class="toggle_details" />' )
            .text( 'Toggle details' ).bind( 'click', function( e )
            {
                e.preventDefault();
                $( this ).prev().toggleClass( 'ui-icon-triangle-1-s' );
                $( this ).next().toggle();
            } ) ).append( $( '<div />' ).append( details ).append( hint ).hide() ) );
        
        var content = popup.find( '>div' );
        content.remove();
        popup.append( div );
        
        popup.css( 'max-height', Math.max( 200, $( window ).height() - 200 ) + 'px' );
        
        popup.dialog( 'open' );
    }

    var hidePopup = function()
    {
        assert( popup );
        popup.dialog( 'close' );
        popup.remove();
        popup = null;
    }

    var toggleMinutiae = function( currentView, ids, on )
    {
        var layer = currentView.getSVG().select( '.pianos4_minutiae' );
        var clonables = {};
        for( var i in ids )
        {
            var mid = ids[ i ];
            if( on )
            {
                // We want to display an overlay over the reviewed minutia. We
                // can't just re-use the gizmo because
                // attributes of a <use> node cannot override those of the
                // referenced node - namely, the color. So
                // we find the node referenced by the gizmo, clone it, modify
                // the relevant attributes and insert it
                // into the SVG. This also looks because unlike jQuery, d3.js
                // does not have a .clone() method.
                var sel = layer.select( '#minutia_' + mid + ' ._sel' );
                var nodeID = sel.attr( 'xlink:href' );
                if( !( nodeID in clonables ) )
                    clonables[ nodeID ] = currentView.getSVG().select( 'defs ' + nodeID ).node();
                var toClone = clonables[ nodeID ];
                var cloned = toClone.cloneNode( true );
                d3.select( cloned ).attr( 'id', null ).classed( '_review', true ).attr( 'stroke', params.lineColor ).attr( 'fill', params.lineColor ).attr( 'fill-opacity', '0.3' );
                sel.node().parentNode.appendChild( cloned );
            } else
            {
                var m = layer.select( '#minutia_' + mid + ' ._review' );
                m.remove();
            }
        }
    }

    var showExportDialog = function( data )
    {
        var div = $( '<div />' );
        
        var pre = $( '<pre />' ).appendTo( div );
        
        pre.append( data );
        
        pianos.dialog( div, {
            title: 'Exported data',
            close: function()
            {
                $( this ).remove();
            },
            width: 500,
            height: Math.max( 150, $( window ).height() * 90.0 / 100.0 ),
            buttons: {
                'Close': function()
                {
                    $( this ).dialog( 'close' );
                }
            },
        } );
    }
}
