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

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.log4j.Logger;

import edu.duke.donaldLab.jdshot.context.DistanceRestraintsContext;
import edu.duke.donaldLab.share.geom.Vector3;
import edu.duke.donaldLab.share.mapping.AddressMapper;
import edu.duke.donaldLab.share.nmr.DistanceRestraint;
import edu.duke.donaldLab.share.protein.AtomAddressInternal;
import edu.duke.donaldLab.share.protein.AtomAddressReadable;
import edu.duke.donaldLab.share.protein.Subunit;

public class SubunitVariationAnalyzer
{
	/**************************
	 * Definitions
	 **************************/
	
	public static enum Mode
	{
		Rms
		{
			@Override
			public double getVariation( List<Subunit> subunits, AtomAddressInternal address )
			{
				// compute the rms distance to the centroid
				Vector3 centroid = getCentroid( subunits, address );
				double sum = 0.0;
				for( Subunit other : subunits )
				{
					sum += centroid.getSquaredDistance( other.getAtom( address ).getPosition() );
				}
				return Math.sqrt( sum / subunits.size() );
			}
		},
		Max
		{
			@Override
			public double getVariation( List<Subunit> subunits, AtomAddressInternal address )
			{
				// compute the max distance to the centroid
				Vector3 centroid = getCentroid( subunits, address );
				double maxDistance = 0.0;
				for( Subunit other : subunits )
				{
					maxDistance = Math.max( maxDistance, centroid.getDistance( other.getAtom( address ).getPosition() ) );
				}
				return maxDistance;
			}
		};
		
		public abstract double getVariation( List<Subunit> subunits, AtomAddressInternal address );
		
		private static Vector3 getCentroid( List<Subunit> subunits, AtomAddressInternal address )
		{
			Vector3 centroid = Vector3.getOrigin();
			for( Subunit other : subunits )
			{
				centroid.add( other.getAtom( address ).getPosition() );
			}
			centroid.scale( 1.0 / subunits.size() );
			return centroid;
		}
	}
	private static final Mode DefaultMode = Mode.Max;
	
	private static final Logger m_log = Logger.getLogger( SubunitVariationAnalyzer.class );
	
	
	/**************************
	 * Static Methods
	 **************************/
	
	public static Map<AtomAddressInternal,Double> getVariation( List<Subunit> subunits, DistanceRestraintsContext context )
	{
		return getVariation( subunits, context, DefaultMode );
	}
	
	public static Map<AtomAddressInternal,Double> getVariation( List<Subunit> subunits, DistanceRestraintsContext context, Mode mode )
	{
		// compute the variation
		Map<AtomAddressInternal,Double> variation = new HashMap<AtomAddressInternal,Double>();
		for( DistanceRestraint<AtomAddressInternal> restraint : context.getInternalRestraints() )
		{
			for( AtomAddressInternal address : restraint.getLefts() )
			{
				variation.put( address, mode.getVariation( subunits, address ) );
			}
			for( AtomAddressInternal address : restraint.getRights() )
			{
				variation.put( address, mode.getVariation( subunits, address ) );
			}
		}
		
		// LOGGING
		for( Map.Entry<AtomAddressInternal,Double> entry : variation.entrySet() )
		{
			AtomAddressReadable address = AddressMapper.mapAddress( subunits.get( 0 ), entry.getKey() );
			m_log.debug( address.toResidueAtomString() + "\t" + entry.getValue() );
		}
		
		return variation;
	}
	
	public static List<DistanceRestraint<AtomAddressInternal>> applyVariation( Map<AtomAddressInternal,Double> variation, List<DistanceRestraint<AtomAddressInternal>> restraints )
	{
		for( DistanceRestraint<AtomAddressInternal> restraint : restraints )
		{
			double leftVariation = getVariation( variation, restraint.getLefts() );
			double rightVariation = getVariation( variation, restraint.getRights() );
			restraint.setMaxDistance( restraint.getMaxDistance() + leftVariation + rightVariation );
			restraint.setMinDistance( Math.max( 0, restraint.getMinDistance() - leftVariation - rightVariation ) );
		}
		return restraints;
	}
	

	/**************************
	 * Static Functions
	 **************************/
	
	private static double getVariation( Map<AtomAddressInternal,Double> variation, Set<AtomAddressInternal> rights )
	{
		// find the max distance
		double maxVariation = 0.0;
		for( AtomAddressInternal address : rights )
		{
			Double v = variation.get( address );
			if( v != null )
			{
				maxVariation = Math.max( maxVariation, v );
			}
		}
		return maxVariation;
	}
}
