/*-
 * #%L
 * Multiview stitching of large datasets.
 * %%
 * Copyright (C) 2016 - 2017 Big Stitcher developers.
 * %%
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as
 * published by the Free Software Foundation, either version 2 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 General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public
 * License along with this program.  If not, see
 * <http://www.gnu.org/licenses/gpl-2.0.html>.
 * #L%
 */
package net.preibisch.stitcher.gui.overlay;

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Stroke;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import mpicbg.spim.data.generic.AbstractSpimData;
import mpicbg.spim.data.generic.sequence.BasicViewDescription;
import mpicbg.spim.data.generic.sequence.BasicViewSetup;
import mpicbg.spim.data.registration.ViewRegistration;
import mpicbg.spim.data.sequence.ViewId;
import net.imglib2.realtransform.AffineTransform3D;
import net.imglib2.ui.OverlayRenderer;
import net.imglib2.ui.TransformListener;
import net.imglib2.util.Pair;
import net.imglib2.util.ValuePair;
import net.preibisch.legacy.io.IOFunctions;
import net.preibisch.mvrecon.fiji.spimdata.explorer.SelectedViewDescriptionListener;
import net.preibisch.mvrecon.fiji.spimdata.stitchingresults.PairwiseLinkInterface;
import net.preibisch.mvrecon.process.interestpointregistration.pairwise.constellation.grouping.Group;
import net.preibisch.mvrecon.process.interestpointregistration.pairwise.constellation.overlap.SimpleBoundingBoxOverlap;
import net.preibisch.stitcher.algorithm.globalopt.TransformationTools;

public class DemoLinkOverlay implements OverlayRenderer, TransformListener< AffineTransform3D >, SelectedViewDescriptionListener< AbstractSpimData<?> >
{
	final private ArrayList< Pair< Group< ViewId >, Group< ViewId > > > lastFilteredResults, lastInconsistentResults;
	private PairwiseLinkInterface results;
	private AbstractSpimData< ? > spimData;
	private AffineTransform3D viewerTransform;
	public boolean isActive;
	private ArrayList<Pair<Group<ViewId>, Group<ViewId>>> activeLinks; //currently selected in the GUI

	final Stroke dashed = new BasicStroke( 1, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL, 0, new float[]{4}, 0 );
	final Stroke thin = new BasicStroke( 1 );
	final Stroke thick = new BasicStroke( 1.5f );

	public DemoLinkOverlay( PairwiseLinkInterface results, AbstractSpimData< ? > spimData )
	{
		this.results = results;
		this.spimData = spimData;
		this.lastFilteredResults = new ArrayList<>();
		this.lastInconsistentResults = new ArrayList<>();
		viewerTransform = new AffineTransform3D();
		isActive = false;
		activeLinks = new ArrayList<>();
	}

	public void setPairwiseLinkInterface( final PairwiseLinkInterface pli )
	{
		this.results = pli;
	}

	// called by FilteredStitchingResults
	public ArrayList< Pair< Group< ViewId >, Group< ViewId > > > getFilteredResults()
	{
		return lastFilteredResults;
	}

	// called by e.g. Global Optimization
	public ArrayList< Pair< Group< ViewId >, Group< ViewId > > > getInconsistentResults()
	{
		return lastInconsistentResults;
	}

	@Override
	public void transformChanged(AffineTransform3D transform)
	{
		this.viewerTransform = transform;
	}

	@Override
	public void drawOverlays(Graphics g)
	{
		// dont do anything if the overlay was set to inactive or we have no Tile selected (no links to display)
		if (!isActive || activeLinks.size() == 0)
			return;

		for ( Pair<Group<ViewId>, Group<ViewId>> p: activeLinks)
		{
			if ( spimData.getSequenceDescription().getMissingViews() != null )
			{
				p.getA().filterMissingViews( spimData.getSequenceDescription().getMissingViews().getMissingViews() );
				p.getB().filterMissingViews( spimData.getSequenceDescription().getMissingViews().getMissingViews() );
			}

			// local coordinates of views, without BDV transform 
			final double[] lPos1 = new double[ 3 ];
			final double[] lPos2 = new double[ 3 ];
			// global coordianates, after BDV transform
			final double[] gPos1 = new double[ 3 ];
			final double[] gPos2 = new double[ 3 ];
			
			BasicViewDescription<?> vdA = spimData.getSequenceDescription().getViewDescriptions().get( p.getA().iterator().next() );
			BasicViewDescription<?> vdB = spimData.getSequenceDescription().getViewDescriptions().get( p.getB().iterator().next() );
			ViewRegistration vrA = spimData.getViewRegistrations().getViewRegistration(  p.getA().iterator().next() );
			ViewRegistration vrB = spimData.getViewRegistrations().getViewRegistration(  p.getB().iterator().next() );

			long[] sizeA = new long[vdA.getViewSetup().getSize().numDimensions()];
			long[] sizeB = new long[vdB.getViewSetup().getSize().numDimensions()];
			spimData.getSequenceDescription().getViewDescriptions().get( p.getA().iterator().next() ).getViewSetup().getSize().dimensions( sizeA );
			spimData.getSequenceDescription().getViewDescriptions().get( p.getB().iterator().next() ).getViewSetup().getSize().dimensions( sizeB );
			
			// TODO: this uses the transform of the first view in the set, maybe do something better?
			AffineTransform3D vt1 = spimData.getViewRegistrations().getViewRegistration( p.getA().iterator().next() ).getModel();
			AffineTransform3D vt2 = spimData.getViewRegistrations().getViewRegistration( p.getB().iterator().next() ).getModel();
			
			boolean overlaps = SimpleBoundingBoxOverlap.overlaps( SimpleBoundingBoxOverlap.getBoundingBox(	vdA.getViewSetup(), vrA ), SimpleBoundingBoxOverlap.getBoundingBox( vdB.getViewSetup(), vrB ) );

			if (!overlaps)
				continue;

			final AffineTransform3D transform = new AffineTransform3D();
			transform.preConcatenate( viewerTransform );

			for( int i = 0; i < 3; i++)
			{
				// start from middle of view
				lPos1[i] += sizeA[i] / 2;
				lPos2[i] += sizeB[i] / 2;
			}

			vt1.apply( lPos1, lPos1 );
			vt2.apply( lPos2, lPos2 );
			
			transform.apply( lPos1, gPos1 );
			transform.apply( lPos2, gPos2 );

			Graphics2D g2d = null;

			if ( Graphics2D.class.isInstance( g ) )
				g2d = (Graphics2D) g;
			/*
			if ( lastFilteredResults.contains( p ) || lastFilteredResults.contains( TransformationTools.reversePair( p ) ) )
			{
				g.setColor( Color.ORANGE );
				if ( g2d != null ) g2d.setStroke( dashed );
			}
			else if ( lastInconsistentResults.contains( p ) || lastInconsistentResults.contains( TransformationTools.reversePair( p ) ) )
			{
				g.setColor( Color.RED );
				if ( g2d != null ) g2d.setStroke( dashed );
			}
			else if ( results.getPairwiseLinks().contains( p ) || results.getPairwiseLinks().contains( TransformationTools.reversePair( p ) ) )
			{
				g.setColor( Color.GREEN );
				if ( g2d != null ) g2d.setStroke( thick );
			}
			else
			{
				g.setColor( Color.GRAY );
				if ( g2d != null ) g2d.setStroke( dashed );
			}
			*/

			if ( overlapsWith( p, lastFilteredResults ) )
			{
				g.setColor( Color.ORANGE );
				if ( g2d != null ) g2d.setStroke( dashed );
			}
			else if ( overlapsWith( p, lastInconsistentResults ) )
			{
				g.setColor( Color.RED );
				if ( g2d != null ) g2d.setStroke( dashed );
			}
			else if ( overlapsWith( p, results.getPairwiseLinks() ) )
			{
				g.setColor( Color.GREEN );
				if ( g2d != null ) g2d.setStroke( thick );
			}
			else
			{
				g.setColor( Color.GRAY );
				if ( g2d != null ) g2d.setStroke( dashed );
			}

			g.drawLine((int) gPos1[0],(int) gPos1[1],(int) gPos2[0],(int) gPos2[1] );
		}
	}

	public static boolean overlapsWith( final Pair<Group<ViewId>, Group<ViewId>> p1, final Collection< Pair<Group<ViewId>, Group<ViewId>>> pairList )
	{
		for ( final Pair<Group<ViewId>, Group<ViewId>> p2 : pairList )
			if ( overlapsWith( p1, p2 ) )
				return true;

		return false;
	}

	public static boolean overlapsWith( final Pair<Group<ViewId>, Group<ViewId>> p1, final Pair<Group<ViewId>, Group<ViewId>> p2 )
	{
		boolean overlap1 = false;
		boolean overlap2 = false;

		for ( final ViewId v1A : p1.getA().getViews() )
			if ( p2.getA().contains( v1A ) )
				overlap1 = true;

		for ( final ViewId v1B : p1.getB().getViews() )
			if ( p2.getB().contains( v1B ) )
				overlap2 = true;

		if ( overlap1 && overlap2 )
			return true;

		overlap1 = false;
		overlap2 = false;

		for ( final ViewId v1A : p1.getA().getViews() )
			if ( p2.getB().contains( v1A ) )
				overlap2 = true;

		for ( final ViewId v1B : p1.getB().getViews() )
			if ( p2.getA().contains( v1B ) )
				overlap1 = true;

		if ( overlap1 && overlap2 )
			return true;
		else
			return false;
	}

	public void clearActiveLinks()
	{
		activeLinks.clear();
	}
	
	public void setActiveLinks(List<Pair<Group<ViewId>, Group<ViewId>>> vids)
	{
		activeLinks.clear();
		activeLinks.addAll( vids );
	}

	@Override
	public void setCanvasSize(int width, int height){}


	@Override
	public void selectedViewDescriptions(
			List< List< BasicViewDescription< ? extends BasicViewSetup > > > viewDescriptions)
	{
		List<Pair<Group<ViewId>, Group<ViewId>>> res = new ArrayList<>();
		for (int i = 0; i<viewDescriptions.size(); i++)
			for (int j = i+1; j<viewDescriptions.size(); j++)
			{
				Group<ViewId> groupA = new Group<>();
				groupA.getViews().addAll( viewDescriptions.get( i ) );
				Group<ViewId> groupB = new Group<>();
				groupB.getViews().addAll( viewDescriptions.get( j ) );
				res.add( new ValuePair< Group<ViewId>, Group<ViewId> >( groupA, groupB ) );
			}
		setActiveLinks( res );
	}


	@Override
	public void updateContent(AbstractSpimData< ? > data)
	{
		this.spimData = data;
	}


	@Override
	public void save()
	{
	}


	@Override
	public void quit()
	{
	}

}