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

import org.apache.log4j.Level;
import org.apache.log4j.Logger;

import edu.duke.donaldLab.jdshot.context.SymmetryContext;
import edu.duke.donaldLab.jdshot.disco.StructureScores.Score;
import edu.duke.donaldLab.jdshot.disco.StructureScores.ScoreSample;
import edu.duke.donaldLab.share.analysis.RestraintCalculator;
import edu.duke.donaldLab.share.nmr.DistanceRestraint;
import edu.duke.donaldLab.share.nmr.DistanceRestraintMapper;
import edu.duke.donaldLab.share.perf.LoggingMessageListener;
import edu.duke.donaldLab.share.perf.Progress;
import edu.duke.donaldLab.share.protein.AtomAddressInternal;
import edu.duke.donaldLab.share.protein.AtomAddressReadable;
import edu.duke.donaldLab.share.protein.Protein;
import edu.duke.donaldLab.share.xplor.EnergyCalculator;
import edu.duke.donaldLab.share.xplor.StructureMinimizer;
import edu.duke.donaldLab.share.xplor.XplorException;

public class StructureEvaluator
{
	/**************************
	 * Definitions
	 **************************/
	
	private static final Logger m_log = Logger.getLogger( StructureEvaluator.class );
	
	
	/**************************
	 * Static Methods
	 **************************/
	
	public static StructureScores evaluate( SymmetryContext symmetryContext,
			List<Protein> structures,
			List<DistanceRestraint<AtomAddressInternal>> restraints )
			throws IOException, XplorException
	{
		return evaluate( symmetryContext, structures, restraints, 1 );
	}
	
	public static StructureScores evaluate( SymmetryContext symmetryContext, final List<Protein> structures, List<DistanceRestraint<AtomAddressInternal>> restraints, final int numThreads )
	throws IOException, XplorException
	{
		// replicate and convert the distance restraints for minimization
		final List<DistanceRestraint<AtomAddressInternal>> replicatedRestraints = symmetryContext.replicateDistanceRestraints( restraints );
		final List<DistanceRestraint<AtomAddressReadable>> replicatedRestraintsReadable = DistanceRestraintMapper.mapInternalToReadable(
			replicatedRestraints,
			symmetryContext.getClonedMonomer(),
			true
		);
		
		// init progress bar
		m_log.info( String.format( "Minimizing %d structures...", structures.size() ) );
		final Progress progress = new Progress( structures.size() );
		progress.setMessageListener( new LoggingMessageListener( m_log, Level.INFO ) );
		
		// minimize and scores the structures
		// NOTE: parallelization here is a simple static partitioning
		final StructureScores scores = new StructureScores( structures.size() );
		Thread[] threads = new Thread[numThreads];
		for( int i=0; i<numThreads; i++ )
		{
			final int threadId = i;
			threads[i] = new Thread( )
			{
				@Override
				public void run( )
				{
					// create the working directory
					File dirWork = new File( new File( System.getProperty( "java.io.tmpdir" ) ), "thread" + threadId );
					dirWork.mkdirs();

					// initialize some objects
					StructureMinimizer minimizer = new StructureMinimizer( dirWork );
					minimizer.setDistanceRestraints( replicatedRestraintsReadable );
					EnergyCalculator energyCalculator = new EnergyCalculator( dirWork );
					RestraintCalculator restraintCalculator = new RestraintCalculator();
					
					// for each protein in our static partition...
					for( int j=threadId; j<structures.size(); j+=numThreads )
					{
						Protein structure = structures.get( j );
						
						Score score = null;
						try
						{
							// score before minimization
							ScoreSample before = new ScoreSample(
								energyCalculator.getEnergy( structure ),
								restraintCalculator.getRmsd( structure, replicatedRestraints ),
								restraintCalculator.getNumSatisfied( structure, replicatedRestraints )
							);
							
							// minimize it
							Protein minimizedStructure = minimizer.minimize( structure );
							
							// score after minimization
							ScoreSample after = new ScoreSample(
								energyCalculator.getEnergy( minimizedStructure ),
								restraintCalculator.getRmsd( minimizedStructure, replicatedRestraints ),
								restraintCalculator.getNumSatisfied( minimizedStructure, replicatedRestraints )
							);
							score = new Score( before, after, minimizedStructure );
							
							synchronized( progress )
							{
								progress.incrementProgress();
							}
						}
						catch( XplorException ex )
						{
							m_log.error( "Minimization failed for structure " + j, ex );
						}
						catch( IOException ex )
						{
							m_log.error( "Minimization failed for structure " + j, ex );
						}
						
						synchronized( scores )
						{
							scores.set( j, score );
						}
					}
					
					dirWork.delete();
				}
			};
			threads[i].start();
		}
		
		// wait for the threads to complete
		for( int i=0; i<numThreads; i++ )
		{
			try
			{
				threads[i].join();
			}
			catch( InterruptedException ex )
			{
				// pass along the interrupt
				Thread.currentThread().interrupt();
			}
		}
		
		return scores;
	}
}
