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

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import edu.duke.donaldLab.share.mapping.AddressMapper;
import edu.duke.donaldLab.share.mapping.NameMapper;
import edu.duke.donaldLab.share.mapping.NameScheme;
import edu.duke.donaldLab.share.math.Matrix3;
import edu.duke.donaldLab.share.nmr.DistanceRestraint;
import edu.duke.donaldLab.share.nmr.DistanceRestraintWriter;
import edu.duke.donaldLab.share.pdb.ProteinReader;
import edu.duke.donaldLab.share.pdb.ProteinWriter;
import edu.duke.donaldLab.share.protein.AtomAddressReadable;
import edu.duke.donaldLab.share.protein.Protein;
import edu.duke.donaldLab.share.pseudoatoms.PseudoatomBuilder;

public class StructureMinimizer extends XplorBase
{
	/**************************
	 *   Definitions
	 **************************/
	
	private static final String MinimizeScriptPath = "/resources/xplor/minimize.py.tmpl";
	private static final String PdbInName = "in.pdb";
	private static final String NoesName = "noes.tbl";
	private static final String RdcsName = "rdcs.tbl";
	private static final int DefaultNumCycles = 1000;
	
	private enum Tags
	{
		PdbInPath,
		PdbOutPath,
		UseNoes,
		NoesPath,
		UseRdcs,
		RdcsPath,
		SaupeMatrix,
		NumSteps;
	}
	
	
	/**************************
	 *   Data Members
	 **************************/
	
	private List<DistanceRestraint<AtomAddressReadable>> m_distanceRestraints;
	// private List<Rdc> m_rdcs;
	private Matrix3 m_saupeMatrix;
	private int m_numSteps;
	
	
	/**************************
	 *   Constructors
	 **************************/
	
	public StructureMinimizer( )
	{
		// use a temporary directory as the work directory
		this( System.getProperty( "java.io.tmpdir" ) );
	}
	
	public StructureMinimizer( String workPath )
	{
		this( new File( workPath ) );
	}
	
	public StructureMinimizer( File workDir )
	{
		super( workDir );
		
		// init defaults
		m_distanceRestraints = null;
		// m_rdcs = null;
		m_saupeMatrix = null;
		m_numSteps = DefaultNumCycles;
	}
	
	
	/**************************
	 *   Accessors
	 **************************/
	
	public void setNumSteps( int val )
	{
		m_numSteps = val;
	}
	public int getNumSteps( )
	{
		return m_numSteps;
	}
	
	public void setDistanceRestraints( List<DistanceRestraint<AtomAddressReadable>> restraints )
	{
		// make a deep copy of the distance restraints so we can modify them
		m_distanceRestraints = new ArrayList<DistanceRestraint<AtomAddressReadable>>( restraints.size() );
		for( DistanceRestraint<AtomAddressReadable> restraint : restraints )
		{
			m_distanceRestraints.add( new DistanceRestraint<AtomAddressReadable>( restraint ) );
		}
	}
	
	
	/**************************
	 *   Methods
	 **************************/
	
	/* UNDONE: implement RDCs
	public void setRdcs( List<Rdc> rdcs, Matrix3 saupeMatrix )
	{
		m_rdcs = rdcs;
		m_saupeMatrix = saupeMatrix;
	}
	*/
	
	public Protein minimize( Protein structure )
	throws IOException, XplorException
	{
		// perform the minimization
		File structureFile = File.createTempFile( "structure", ".pdb", getWorkDir() );
		minimize( structureFile, structure );
		
		// read in the protein
		Protein minimizedStructure = new ProteinReader().read( structureFile );
		NameMapper.ensureProtein( minimizedStructure, NameScheme.New );
		PseudoatomBuilder.getInstance().build( minimizedStructure );
		
		// cleanup the temp file
		structureFile.delete();
		
		return minimizedStructure;
	}
	
	public void minimize( String outPdbPath, Protein structure )
	throws IOException, XplorException
	{
		minimize( new File( outPdbPath ), structure );
	}
	
	public void minimize( File pdbOutFile, Protein structure )
	throws IOException, XplorException
	{
		// write the in PDB file
		File pdbInFile = addTempFile( new File( getWorkDir(), PdbInName ) );
		new ProteinWriter().write( structure, pdbInFile );
		
		// set up script tags
		boolean useDistanceRestraints = m_distanceRestraints != null;
		boolean useRdcs = false; //m_rdcs != null;
		setTag( Tags.PdbInPath, quote( pdbInFile.getName() ) );
		setTag( Tags.PdbOutPath, quote( pdbOutFile.getAbsolutePath() ) );
		if( useDistanceRestraints )
		{
			setTag( Tags.UseNoes, PythonTrue );
			
			// xplor requires old-style atom names for NOEs
			NameMapper.ensureDistanceRestraints( structure, m_distanceRestraints, NameScheme.Old );
			
			// make sure NOEs with pseudoatoms use the masks, not the names
			AddressMapper.mapPseudoaomNamesToMasks( structure, m_distanceRestraints );
			
			// write the NOEs to the file
			File noesFile = addTempFile( new File( getWorkDir(), NoesName ) );
			new DistanceRestraintWriter().write( noesFile, m_distanceRestraints );
			
			setTag( Tags.NoesPath, quote( noesFile.getName() ) );
		}
		else
		{
			setTag( Tags.UseNoes, PythonFalse );
			setTag( Tags.NoesPath, PythonNull );
		}
		if( useRdcs )
		{
			// UNDONE: write the RDCs to the file
			File rdcsFile = addTempFile( new File( getWorkDir(), RdcsName ) );
			
			setTag( Tags.UseRdcs, PythonTrue );
			setTag( Tags.RdcsPath, quote( rdcsFile.getName() ) );
			setTag( Tags.SaupeMatrix, String.format(
				"SymMat3( %f, %f, %f, %f, %f, %f )",
				m_saupeMatrix.data[0][0],
				m_saupeMatrix.data[1][0], m_saupeMatrix.data[1][1],
				m_saupeMatrix.data[2][0], m_saupeMatrix.data[2][1], m_saupeMatrix.data[2][2]
			) );
		}
		else
		{
			setTag( Tags.UseRdcs, PythonFalse );
			setTag( Tags.RdcsPath, PythonNull );
			setTag( Tags.SaupeMatrix, PythonNull );
		}
		setTag( Tags.NumSteps, Integer.toString( m_numSteps ) );
		
		// run the script!
		File scriptFile = addTempFile( emitFile( MinimizeScriptPath ) );
		runScript( scriptFile );
		waitForXplor();
		
		cleanupTempFiles();
		
		// just in case...
		assert( pdbOutFile.exists() );
	}
}
