/*******************************************************************************
 * 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.share.analysis;

import java.util.ArrayList;
import java.util.Iterator;

import edu.duke.donaldLab.share.geom.Vector3;
import edu.duke.donaldLab.share.mapping.AddressMapper;
import edu.duke.donaldLab.share.math.Quaternion;
import edu.duke.donaldLab.share.math.RotationOptimizer;
import edu.duke.donaldLab.share.protein.Atom;
import edu.duke.donaldLab.share.protein.AtomAddressInternal;
import edu.duke.donaldLab.share.protein.Protein;
import edu.duke.donaldLab.share.protein.AtomAddressReadable;
import edu.duke.donaldLab.share.protein.Subunit;
import edu.duke.donaldLab.share.protein.tools.AtomPositionIterator;
import edu.duke.donaldLab.share.protein.tools.ProteinGeometry;

public class StructureAligner
{
	/**************************
	 *   Static Methods
	 **************************/
	
	public static void align( Protein reference, Protein computed )
	{
		// just align the computed protein to the reference protein using the first atom
		Atom referenceAtom = reference.getAtom( reference.getBackboneAtomIndex().next() );
		Atom computedAtom = computed.getAtom( computed.getBackboneAtomIndex().next() );
		Vector3 offset = new Vector3( referenceAtom.getPosition() );
		offset.subtract( computedAtom.getPosition() );
		
		// apply the offset to every atom in the computed protein
		Iterator<Subunit> iterSubunit = computed.getSubunits().iterator();
		while( iterSubunit.hasNext() )
		{
			Subunit subunit = iterSubunit.next();
			Iterator<AtomAddressInternal> iterAtom = subunit.getAtomIndex().iterator();
			while( iterAtom.hasNext() )
			{
				Atom atom = computed.getAtom( iterAtom.next() );
				atom.getPosition().add( offset );
			}
		}
	}
	
	/* Jeff: 12/02/2008
		These optimal alignment algorithm are based on
		http://cnx.org/content/m11608/latest/
		
		Note: all optimal alignmest methods assume the centroid of the reference
		atom set is at the origin.
	*/

	public static void alignOptimally( Protein reference, Protein computed )
	{
		// just in case...
		assert( reference.getSubunits().size() == computed.getSubunits().size() );
		assert( reference.getNumAtoms() == computed.getNumAtoms() );
		
		// first, align the proteins by centroids
		ProteinGeometry.center( computed );
		
		// get the optimal rotation from computed to reference
		Quaternion optimalRotation = RotationOptimizer.getOptimalRotation(
			new AtomPositionIterator( reference ),
			new AtomPositionIterator( computed )
		);
		
		// apply the rotation to the computed structure
		ProteinGeometry.rotate( computed, optimalRotation );
	}

	public static void alignOptimally( Subunit reference, Subunit computed )
	{
		// just in case...
		assert( reference.getAtomIndex().size() == computed.getAtomIndex().size() );
		
		// first, align the proteins by centroids
		ProteinGeometry.center( computed );
		
		// get the optimal rotation from computed to reference
		Quaternion optimalRotation = RotationOptimizer.getOptimalRotation(
			new AtomPositionIterator( reference ),
			new AtomPositionIterator( computed )
		);
		
		// apply the rotation to the computed structure
		ProteinGeometry.rotate( computed, optimalRotation );
	}
	
	public static void alignOptimallyBySubunit( Protein reference, Protein computed, int subunitId )
	{
		/*
			Note: this method assumes the centroid of the reference subunit
			is at the origin.
		*/
		
		Subunit referenceSubunit = reference.getSubunit( subunitId );
		Subunit computedSubunit = computed.getSubunit( subunitId );
		
		// just in case...
		assert( referenceSubunit.getAtomIndex().size() == computedSubunit.getAtomIndex().size() );
		
		// first, align the proteins by centroids
		Vector3 translation = ProteinGeometry.getCentroid( computedSubunit );
		translation.negate();
		ProteinGeometry.translate( computed, translation );
		
		// get the optimal rotation from computed to reference
		Quaternion optimalRotation = RotationOptimizer.getOptimalRotation(
			new AtomPositionIterator( reference, referenceSubunit.getAtomIndex().iterator() ),
			new AtomPositionIterator( computed, computedSubunit.getAtomIndex().iterator() )
		);
		
		// apply the rotation to the computed structure
		ProteinGeometry.rotate( computed, optimalRotation );
	}
	
	public static void alignOptimallyByAtoms( Protein reference, Protein computed, ArrayList<AtomAddressReadable> readableAddresses )
	{
		// get the sequences of our atom addresses
		ArrayList<AtomAddressInternal> referenceAddresses = new ArrayList<AtomAddressInternal>();
		ArrayList<AtomAddressInternal> computedAddresses = new ArrayList<AtomAddressInternal>();
		for( AtomAddressReadable readableAddress : readableAddresses )
		{
			referenceAddresses.addAll( AddressMapper.mapAddress( reference, readableAddress ) );
			computedAddresses.addAll( AddressMapper.mapAddress( computed, readableAddress ) );
		}
		
		alignOptimallyByAtoms( reference, computed, referenceAddresses, computedAddresses );
	}
	
	public static void alignOptimallyByAtoms( Protein reference, Protein computed, ArrayList<AtomAddressInternal> referenceAddresses, ArrayList<AtomAddressInternal> computedAddresses )
	{
		// first, align the proteins by centroids
		Vector3 translation = ProteinGeometry.getCentroid( computed, computedAddresses.iterator() );
		translation.negate();
		ProteinGeometry.translate( computed, translation );
		
		// get the optimal rotation from computed to reference
		Quaternion optimalRotation = RotationOptimizer.getOptimalRotation(
			new AtomPositionIterator( reference, referenceAddresses.iterator() ),
			new AtomPositionIterator( computed, computedAddresses.iterator() )
		);
		
		// apply the rotation to the computed structure
		ProteinGeometry.rotate( computed, optimalRotation );
	}
}
