/*******************************************************************************
 * 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.ArrayList;
import java.util.List;

import edu.duke.donaldLab.jdshot.context.SymmetryContext;
import edu.duke.donaldLab.jdshot.disco.AnnulusIndex.AnnulusEntry;
import edu.duke.donaldLab.jdshot.grid.cn.CnGridPoint;
import edu.duke.donaldLab.share.analysis.AssignmentFilter;
import edu.duke.donaldLab.share.analysis.RestraintCalculator;
import edu.duke.donaldLab.share.geom.Annulus2;
import edu.duke.donaldLab.share.geom.Circle;
import edu.duke.donaldLab.share.geom.Vector2;
import edu.duke.donaldLab.share.nmr.Assignment;
import edu.duke.donaldLab.share.nmr.DistanceRestraint;
import edu.duke.donaldLab.share.nmr.DistanceRestraintMapper;
import edu.duke.donaldLab.share.nmr.DistanceRestraintWriter;
import edu.duke.donaldLab.share.protein.AtomAddressInternal;
import edu.duke.donaldLab.share.protein.Protein;
import edu.duke.donaldLab.share.protein.Subunit;

public class ViolationCalculator
{
	/**************************
	 *   Definitions
	 **************************/
	
	public static class Violation
	{
		public Vector2 facePoint;
		public Vector2 annulusPoint;
		public double distance;
		public double atomDistance;
		public Protein oligomer;
		public DistanceRestraint<AtomAddressInternal> restraint;
		
		public Violation( Vector2 facePoint, Vector2 annulusPoint )
		{
			this.facePoint = facePoint;
			this.annulusPoint = annulusPoint;
			this.distance = facePoint.getDistance( annulusPoint );
		}
		
		public void upgrade( DistanceRestraint<AtomAddressInternal> restraint, Protein oligomer, double atomDistance )
		{
			this.restraint = restraint;
			this.oligomer = oligomer;
			this.atomDistance = atomDistance;
		}
		
		public String getAssignString( )
		{
			return new DistanceRestraintWriter().writeToString( DistanceRestraintMapper.mapInternalToReadable( restraint, oligomer ) );
		}
	}
	
	
	/**************************
	 *   Static Methods
	 **************************/
	
	public static ArrayList<Violation> getViolations( List<FaceInfo> msrs, final AnnulusIndex annulusIndex, List<DistanceRestraint<AtomAddressInternal>> inconsistentRestraints, SymmetryContext symmetryContext, Subunit monomer )
	{
		RestraintCalculator restraintCalculator = new RestraintCalculator();
		ArrayList<Violation> violations = new ArrayList<Violation>();
		for( DistanceRestraint<AtomAddressInternal> restraint : inconsistentRestraints )
		{
			// get the violation
			Violation violation = getViolation( msrs, annulusIndex.getEntries( restraint ) );
			if( violation == null )
			{
				continue;
			}
			
			// build the symmetric oligomer
			Protein oligomer = symmetryContext.getOligomer(
				new CnGridPoint( violation.facePoint.x, violation.facePoint.y, 0.0, 0.0 ),
				monomer
			);
			
			// get the atom position violation for only the restraint assignments that have annuli
			double minDist = restraintCalculator.getViolation( oligomer, restraint, new AssignmentFilter<AtomAddressInternal>( )
			{
				@Override
				public AssignmentFilter.Result filter( DistanceRestraint<AtomAddressInternal> restraint, Assignment<AtomAddressInternal> assignment )
				{
					if( annulusIndex.hasAnnulus( restraint, assignment ) )
					{
						return AssignmentFilter.Result.Pass;
					}
					return AssignmentFilter.Result.Block;
				}
			} );
			
			// save the violation
			violation.upgrade( restraint, oligomer, minDist );
			violations.add( violation );
			
			/* TEMP: visualization
			if( violation.atomDistance <= 0.0 )
			{
				// show the annulus violation
				try
				{
					org.jfree.chart.JFreeChart plot = Plotter.plotAnnuliViolation( msrs, annulusIndex.getEntries( restraint ), violation );
					ChartWriter.writePng(
						plot,
						new java.io.File( String.format( "/tmp/Disco.debugVis.getViolations.%03d.png", plot.hashCode() ) )
					);
				}
				catch( IOException ex )
				{
					ex.printStackTrace( System.err );
				}//
				
				// show the atom position violation
				try
				{
					edu.duke.donaldLab.share.kinemage.Kinemage kinemage = new edu.duke.donaldLab.share.kinemage.Kinemage( "Distance Restraint Violation" );
					KinemageBuilder.appendProtein( kinemage, oligomer );
					KinemageBuilder.appendDistanceRestraints( kinemage, oligomer, java.util.Arrays.asList( restraint ) );
					new KinemageWriter().showAndWait( kinemage );
				}
				catch( Exception ex )
				{
					ex.printStackTrace( System.err );
				}//
			}*/
		}
		return violations;
	}
	
	
	/**************************
	 *   Static Functions
	 **************************/
	
	private static Violation getViolation( List<FaceInfo> msrs, List<AnnulusEntry> entries )
	{
		// return the minimum violation over all annulus/msr combos
		double minDist = Double.POSITIVE_INFINITY;
		Violation minViolation = null;
		for( AnnulusEntry entry : entries )
		{
			for( FaceInfo msr : msrs )
			{
				Violation violation = getViolation( msr, entry.annulus );
				if( violation != null && violation.distance < minDist )
				{
					minDist = violation.distance;
					minViolation = violation;
				}
			}
		}
		return minViolation;
	}
	
	private static Violation getViolation( FaceInfo msr, Annulus2 annulus )
	{
		Vector2 closestPoint = msr.getPointClosestTo( annulus.center );
		Vector2 farthestPoint = msr.getPointFarthestFrom( annulus.center );
		
		double innerDist = annulus.center.getDistance( closestPoint );
		double outerDist = annulus.center.getDistance( farthestPoint );
		
		// is there really a violation?
		boolean isViolation = outerDist < annulus.minRadius || innerDist > annulus.maxRadius;
		if( !isViolation )
		{
			return null;
		}
		
		Vector2 facePoint = null;
		Vector2 annulusPoint = null;
		if( outerDist <= annulus.minRadius )
		{
			// face is on the bouded side
			facePoint = farthestPoint;
			annulusPoint = new Circle( annulus.center, annulus.minRadius ).getPointClosestTo( facePoint );
		}
		else if( innerDist >= annulus.maxRadius )
		{
			// face is on the unbounded side
			facePoint = closestPoint;
			annulusPoint = new Circle( annulus.center, annulus.maxRadius ).getPointClosestTo( facePoint );
		}
		assert( facePoint != null && annulusPoint != null );
		
		/* TEMP: visualize this
		try
		{
			org.jfree.chart.JFreeChart plot = Plotter.plotAnnulusViolation( msr, annulus, innerDist, outerDist, facePoint, annulusPoint );
			ChartWriter.writePng(
				plot,
				new java.io.File( String.format( "/tmp/Disco.debugVis.getViolation.%03d.png", plot.hashCode() ) )
			);
		}
		catch( IOException ex )
		{
			ex.printStackTrace( System.err );
		}*/
		
		return new Violation( facePoint, annulusPoint );
	}
}
