/*******************************************************************************
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 * 
 * This library 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 Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
 * USA
 * 
 * Contact Info:
 * 	Bruce Donald
 * 	Duke University
 * 	Department of Computer Science
 * 	Levine Science Research Center (LSRC)
 * 	Durham
 * 	NC 27708-0129 
 * 	USA
 * 	brd@cs.duke.edu
 * 
 * Copyright (C) 2011 Jeffrey W. Martin and Bruce R. Donald
 * 
 * <signature of Bruce Donald>, April 2011
 * Bruce Donald, Professor of Computer Science
 ******************************************************************************/


package edu.duke.donaldLab.jdshot.cluster;

import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;

import edu.duke.donaldLab.jdshot.grid.GridPoint;
import edu.duke.donaldLab.jdshot.grid.StructureGenerator;
import edu.duke.donaldLab.jdshot.grid.Symmetry;
import edu.duke.donaldLab.share.analysis.RmsdCalculator;
import edu.duke.donaldLab.share.analysis.StructureAligner;
import edu.duke.donaldLab.share.clustering.distance.DistanceMatrix;
import edu.duke.donaldLab.share.math.CompareReal;
import edu.duke.donaldLab.share.perf.MessageListener;
import edu.duke.donaldLab.share.perf.Progress;
import edu.duke.donaldLab.share.protein.Protein;
import edu.duke.donaldLab.share.protein.Subunit;
import edu.duke.donaldLab.share.protein.tools.ProteinGeometry;

public class DuplicateFilter
{
	/**************************
	 *   Definitions
	 **************************/
	
	private enum Mode
	{
		Matrix,
		Rmsd;
	}
	
	
	/**************************
	 *   Data Members
	 **************************/
	
	private DistanceMatrix m_distances;
	private Subunit m_monomer;
	private int m_numSubunits;
	private HashMap<GridPoint,Integer> m_indexMap;
	private MessageListener m_messageListener;
	private Mode m_mode;
	private StructureGenerator m_structureGenerator;
	private Protein m_cachedLeftStructure;
	private GridPoint m_cachedLeftPoint;
	
	
	/**************************
	 *   Constructors
	 **************************/
	
	public DuplicateFilter( DistanceMatrix distances )
	{
		this();
		
		// save parameters
		m_distances = distances;
		m_mode = Mode.Matrix;
	}
	
	public DuplicateFilter( Subunit monomer, int numSubunits )
	{
		this();
		
		// save parameters
		m_monomer = monomer;
		m_numSubunits = numSubunits;
		m_mode = Mode.Rmsd;
	}
	
	private DuplicateFilter( )
	{
		// init defaults
		m_distances = null;
		m_monomer = null;
		m_numSubunits = -1;
		m_indexMap = null;
		m_messageListener = null;
		m_structureGenerator = null;
		m_cachedLeftStructure = null;
		m_cachedLeftPoint = null;
	}
	
	
	/**************************
	 *   Accessors
	 **************************/
	
	public void setMessageListener( MessageListener messageListener )
	{
		m_messageListener = messageListener;
	}
	
	
	/**************************
	 *   Methods
	 **************************/
	
	public LinkedList<GridPoint> filter( Symmetry symmetry, LinkedList<GridPoint> points )
	{
		switch( m_mode )
		{
			case Matrix:
				initMatrix( points );
			break;
			
			case Rmsd:
				initRmsd( symmetry );
			break;
		}
		
		// ALERT
		Progress progress = null;
		if( m_messageListener != null )
		{
			m_messageListener.message( "filtering..." );
			progress = new Progress( points.size(), 10000 );
			progress.setMessageListener( m_messageListener );
		}
		
		// move unique points to a new list
		LinkedList<GridPoint> filtered = new LinkedList<GridPoint>();
		
		// for each point...
		while( !points.isEmpty() )
		{
			GridPoint leftPoint = points.removeFirst();
			
			// for each point
			Iterator<GridPoint> iterRight = points.iterator();
			while( iterRight.hasNext() )
			{
				GridPoint rightPoint = iterRight.next();
				
				// don't allow self-pairings
				if( leftPoint == rightPoint )
				{
					continue;
				}
				
				// get the distance
				double dist = getDistance( leftPoint, rightPoint );
				
				// if the distance is small enough, consider the structures equal
				if( CompareReal.eq( 0.0, dist ) )
				{
					// remove the right point
					// NOTE: linked list iterators should be able to handle element removals
					iterRight.remove();
				}
			}
			
			// add this point to the unique list
			filtered.add( leftPoint );
			
			// ALERT
			if( progress != null )
			{
				progress.incrementProgress();
			}
		}
		
		return filtered;
	}
	
	
	/**************************
	 *   Functions
	 **************************/
	
	private void initMatrix( LinkedList<GridPoint> points )
	{
		// ALERT
		if( m_messageListener != null )
		{
			m_messageListener.message( "building index map..." );
		}
		
		// build an map from grid point to index
		// NOTE: the default hashcode() and equals() should be ok here
		m_indexMap = new HashMap<GridPoint,Integer>();
		int i = 0;
		for( GridPoint point : points )
		{
			m_indexMap.put( point, i++ );
		}
	}
	
	private void initRmsd( Symmetry symmetry )
	{
		// use just the backbone structure
		m_monomer = m_monomer.getBackbone();
		
		// get our objects
		m_structureGenerator = symmetry.newStructureGenerator( m_monomer );
	}
	
	private double getDistance( GridPoint left, GridPoint right )
	{
		switch( m_mode )
		{
			case Matrix: return getMatrixDistance( left, right );
			case Rmsd: return getRmsdDistance( left, right );
		}
		
		// just to make the compiler happy
		assert( false );
		return Double.NaN;
	}
	
	private double getMatrixDistance( GridPoint left, GridPoint right )
	{
		Integer leftIndex = m_indexMap.get( left );
		Integer rightIndex = m_indexMap.get( right );
		
		// just in case...
		assert( leftIndex != null );
		assert( rightIndex != null );
		
		return m_distances.get( leftIndex, rightIndex );
	}
	
	private double getRmsdDistance( GridPoint left, GridPoint right )
	{
		// get the left structure
		if( left != m_cachedLeftPoint )
		{
			// generate the structure
			m_cachedLeftPoint = left;
			m_cachedLeftStructure = m_structureGenerator.getStructure( left, m_numSubunits );
			ProteinGeometry.center( m_cachedLeftStructure );
		}
		Protein leftStructure = m_cachedLeftStructure;
		
		// get the right structure
		Protein rightStructure = m_structureGenerator.getStructure( right, m_numSubunits );
		
		// align them
		StructureAligner.alignOptimally( leftStructure, rightStructure );
		//StructureAligner.align( leftStructure, rightStructure );
		
		// compute the RMSD
		return RmsdCalculator.getRmsd( leftStructure, rightStructure );
	}
}
