/*
 * 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 Pianos4ToolTPS( params )
{
    // ------------------------------
    // Constructor
    // ------------------------------
    
    params = params || {};
    if( typeof ( params.v3 ) == 'undefined' )
        params.v3 = false;
    
    // ------------------------------
    // Public methods
    // ------------------------------
    
    this.getName = function()
    {
        return 'TPS';
    }
    
    this.getLabel = function()
    {
        return 'TPS';
    }
    
    this.getIcon = function()
    {
        return 'icons/tool_TPS.png';
    }
    
    this.bind = function( aView )
    {
        assert( !view );
        view = aView;
    }
    
    this.unbind = function()
    {
        assert( view );
        view = null;
    }
    
    this.activate = function()
    {
        views = pianos.getViewsdict();
        
        bindTo( 'mark' );
        bindTo( 'print' );
        
        update_TPS_parameters();
        
        pianos.toggleLayer( 'TPSlines', true );
        pianos.toggleLayer( 'GC', true );
        
        log( 'TPS tool activate' );
    }
    
    this.deactivate = function()
    {
        views.mark.remove_Ghost_Cursor_live();
        views.print.remove_Ghost_Cursor_live();
        
        unbindFrom( 'mark' );
        unbindFrom( 'print' );
        
        pianos.toggleLayer( 'TPSlines', false );
        pianos.toggleLayer( 'GC', false );
        
        removeAllTPSlines();
        
        log( 'TPS tool de-activate' );
    }

    this.onMouseDown = function( pos, target, event )
    {
    }

    this.onMouseUp = function( pos, target, event )
    {
        if( typeof( TPS_g.mark.be ) === "undefined" )
            update_TPS_parameters();
        
        var viewtype = view.getType();
        var clickedView = views[ viewtype ];
        
        var posnotrotated = pos;
        var rotationData = rotationView[ view.getType() ];
        pos = rotateAround( pos, rotationData );
        
        var p = TPS_project( TPS_g[ viewtype ], pos );
        
        clickedAnchor = new Pianos4TPSAnchore( viewtype, "user", pos );
        
        action = new Pianos4Action_CreateAnnotation( clickedView, 'GC', clickedAnchor );
        pianos.exec( action );
        
        updateAllViews();
        
        // Force toggle the GC layer
        pianos.toggleLayer( 'GC', true );
        
        return true;
    }

    this.onMouseMove = function( pos, target, event )
    {
    }

    this.getOptionsPanel = function()
    {
        if( !options )
        {
            var panel = $( '<div class="pianos4_TPS_options" />' );
            
            var generate = $( '<a />' )
                .text( 'Compute' )
                .attr( 'href', '#generate' )
                .bind( 'click', compute_TPS );
            
            var clear = $( '<a />' )
                .text( 'Clear' )
                .attr( 'href', '#clear' )
                .bind( 'click', clearanchors );
            
            panel.append( generate );
            panel.append( $( '<br />' ) );
            panel.append( clear );
            
            if( pianos.getParams().TPS_grid_url !== null )
            {
            	var grid = $( '<a />' )
		            .text( 'Get Grid' )
		            .attr( 'href', '#grid' )
		            .attr( 'id', 'gridlink' )
		            .bind( 'click', TPS_get_grid );
	            
	            panel.append( $( '<br />' ) );
	            panel.append( grid );
            }
            
            options = panel;
        }
        
        return options;
    }

    this.getHelp = function()
    {
        return Pianos4Help.compile([
            'This tool allows you to see the distortion between the mark and the reference.',
            'The distortions are displayed with blue rodes and with the ghost cursor.',
            'The blue rodes are calculated without the particular minutiae; the location of the end of the rode indicates where this minutia would be if not paired, indicating the distortion implied by this particular minutia. It is recommended to also search at the end of the rode if the distance seems too high; an error of annotation could be prensent otherwise.',
            'The ghost cursor indicate on the other image (reference if overing over the mark, and vice-versa) the location of the mouse corrected with the distortion reference/mark. This tool can help to location comparison elements. The ghost cursor is also present in the "Minutia tool" to help locating the current search zone on the other image (hold Ctrl with, the minutia tool).',
            'The calculation of the distortion is done based upon the publication of Bookstein, F. L. (1989). Principal warps: Thin-plate splines and the decomposition of deformations. IEEE Transactions on Pattern Analysis and Machine Intelligence, Vol. 11 (6), pp. 567-585',
            'Note: This tool not available for tutors at the moment',
            'The TPS calculation library is available here: https://github.com/mdedonno1337/TPSjs (javascript)'
        ]);
    }

    // ------------------------------
    // Private variables
    // ------------------------------
    
    var self = this;
    var view = null;
    var views = null;
    var popup = null;
    var options = null;
    var drawTimestamp = Date.now();
    var TPS_g = {};
    
    // ------------------------------
    // Private functions
    // ------------------------------
    
    var bindTo = function( to )
    {
        var d3svg = d3.select('#pianos .pianos4_' + to + ' .pianos4_view_canvas');
        d3svg.on('mousemove', function()
        {
            onMouseMoveLive( d3.event, views[ to ], to );
        });
    }
    
    var unbindFrom = function( from )
    {
        d3.select( '#pianos .pianos4_' + from + ' .pianos4_view_canvas' )
            .on( 'mousemove', null ); 
    }
    
    var onMouseMoveLive = function( event, view, viewtype )
    {
        if( typeof( TPS_g.mark ) !== "undefined" && typeof( TPS_g.mark.be ) !== "undefined" && Date.now() - drawTimestamp > 1000 / 60 )
        {
            var pos = view.getCanvasPos( [ event.clientX, event.clientY ] );
            
            var posnotrotated = pos;
            var rotationData = rotationView[ view.getType() ];
            pos = rotateAround( pos, rotationData );
            
            // var viewtype = view.getType();
            var otherview = viewtype == "print" ? "mark" : "print";
            var p = TPS_project( TPS_g[ viewtype ], pos );
            
            views[ otherview ].update_Ghost_Cursor_live( p );
            views[ viewtype ].remove_Ghost_Cursor_live();
            
            drawTimestamp = Date.now();
        }
    }
    
    var getAllPairedMinutiae = function()
    {
        var mark = pianos.getView( 'mark' );
        var ref = pianos.getView( "print" );
        var mannotations = mark.getAnnotationsDict();
        
        var markpos = Array();
        var refpos = Array();
        
        for( var i in mannotations.minutiae )
        {
            var m = mannotations.minutiae[ i ];
            if( 'pairing' in m )
            {
                markpos.push( m.pos );
                refpos.push( ref.getPairedMinutia( m.pairing ).pos );
            }
        }
        
        return {
            mark: markpos,
            ref: refpos
        };
    }
    
    var check_nb_minutiae = function( pair )
    {
    	if( pair.mark.length < 3 )
        {
    		toastr.error( "At least 3 minutiae have to be paired to calculate the TPS transformation.", "TPS Generate", { timeout: 5000 } )
        	return false;
		
        } else {
        	if( pair.mark.length == 3 )
        		toastr.info( "With only 3 minutiae paired, the distorsion is purly affine (shearing)", "TPS Generate", { timeout: 5000 } )
    		
    		return true;
        }
    }
    
    var compute_TPS = function()
    {
        update_TPS_parameters();
        updateAllViews();
    }
    
    var update_TPS_parameters = function()
    {

        var pair = getAllPairedMinutiae();
        if( check_nb_minutiae( pair ) )
        {
        	TPS_g.mark  = TPS_generate( pair.mark, pair.ref );
        	TPS_g.print = TPS_generate( pair.ref, pair.mark );
        }
        
        show_TPS_lines();
        
        var s = Math.max( TPS_g.mark.scale, 1 / TPS_g.mark.scale );
        s = ( s - 1 ) * 100;
        s = Math.round( s * 10 ) / 10;
        
        if( s > pianos.getParams().TPS_scale_limit_notification )
            toastr.info( "A difference in scale of " + s + "% is present between the mark and the print. The mark size could not be compatible with the reference. Please check the resolutions.", "Scale warning" )
    }
    
    var show_TPS_lines = function()
    {
        var rotate = function( array , times )
        {
            array = array.slice();
            while( times-- ){
                var temp = array.shift();
                array.push( temp )
            }
            return array;
        }
        
        removeAllTPSlines();
        
        var pair = getAllPairedMinutiae();
        
        for( var i in pair.mark )
        {
            var mark = rotate( pair.mark, i );
            var m = mark.shift();
            
            var ref = rotate( pair.ref, i );
            var p = ref.shift();
            
            g = TPS_generate( mark, ref );
            
            var proj = TPS_project( g, m );
            
            var line = new Pianos4TPSLines( views.mark, p, proj );
            views.print.add_TPS_line( line );
        }
        
        for( var i in pair.ref )
        {
            var mark = rotate( pair.mark, i );
            var m = mark.shift();
            
            var ref = rotate( pair.ref, i );
            var p = ref.shift();
            
            g = TPS_generate( ref, mark );
            
            var proj = TPS_project( g, p );
            
            var line = new Pianos4TPSLines( views.print, m, proj );
            views.mark.add_TPS_line( line );
        }
    }
    
    var removeAllTPSlines = function()
    {
        views.mark.removeTPSlines();
        views.print.removeTPSlines();
    }
    
    var TPS_get_grid = function()
    {
    	var pair = getAllPairedMinutiae();
    	
    	if( check_nb_minutiae( pair ) )
    	{
	    	var printimg = pianos.getView( 'print' ).getImage();
	    	
	    	data = {
	        	src: JSON.stringify( pair.ref ),
	        	dst: JSON.stringify( pair.mark ),
	        	maxx: printimg.width,
	        	maxy: printimg.height,
	        	height: printimg.height,
	        	res: printimg.resolution,
	        	unit: 'px',
	    	};
	    	
	    	pianos.ajax( {
	            pendingMessage: 'TPS Grid calculation',
	            type: 'post',
	            url: pianos.getParams().TPS_grid_url,
	            data: data,
	        	dataType: "text",
	        	async: true,
	        	success: function( data ) {
	        		if( data.length <= 0 )
	        		{
	        			toastr.error( 'No image returned', 'TPS Grid error', { timeout: 5000 } );
	        		} else {
		        		var div = $( '<div />' )
		        			.attr( 'align', 'center' );
		        		
		        		pianos.dialog( div, {
		        			title: 'Distorsion grid',
		        			autoOpen: false,
		        			width: 800,
		        			height: 800,
		        			modal: true,
		        			buttons: {
		        				'Close': function() {
		        					$(this).dialog( 'close' );
		        				},
		        			},
		        			close: function()
		        			{
		        			},
		        		});
		        		
		        		var content = $( '<img />' )
		        			.attr( 'src', 'data:image/png;base64,' + data )
		        			.css( 'max-width', '100%' )
		        			.css( 'max-height', '100%' )
		    			
						div.append( content );
						
		        		div.dialog( 'open' );
	    			}
	        	},
	        	error: function( xhr, status, error ){
	        		if( xhr.status == 0 )
	        			var msg = "TPS Grid server (" + pianos.getParams().TPS_grid_url + ") not running. Contact the sysadmin.";
	        		
	        		else if( xhr.status == 400 )
	        			var msg = "Error in the data provided to the TPS Grid server.";
	        		
	        		toastr.error( msg, 'TPS Grid error', { timeout: 5000 } );
	        		$( "#gridlink" )
	        			.css( 'pointer-events', 'none' )
	        			.css( 'color', 'gray' );
	        	}
	    	} );
        }
    }
    
    var updateView = function( from, to, fg )
    {
        to.clear_Ghost_Cursor_Projected();
        
        var anno = from.getData().GC;
        
        for( var key in anno )
        {
            var pos = TPS_project( fg, anno[ key ].pos );
            var projectedAnchor = new Pianos4TPSAnchore( to, "projected", pos );
            to.draw_Ghost_Cursor_Projected( projectedAnchor );
        }
    }
    
    var updateAllViews = function()
    {
        update_TPS_parameters();
        updateView( views.mark, views.print, TPS_g.mark  );
        updateView( views.print, views.mark, TPS_g.print );
    }
    
    var clearanchors = function()
    {
        views.mark.remove_all_GhostCursor_Anchor();
        views.print.remove_all_GhostCursor_Anchor();
        updateAllViews();
    }
}
