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

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

import edu.duke.donaldLab.jdshot.context.DistanceRestraintsContext;
import edu.duke.donaldLab.jdshot.context.SymmetryContext;
import edu.duke.donaldLab.jdshot.grid.CellIterator;
import edu.duke.donaldLab.jdshot.grid.CellReader;
import edu.duke.donaldLab.jdshot.grid.CellWriter;
import edu.duke.donaldLab.jdshot.grid.GridCell;
import edu.duke.donaldLab.jdshot.grid.SearchSpace;
import edu.duke.donaldLab.share.perf.Progress;

public class Searcher
{
	/**************************
	 *   Data Members
	 **************************/
	
	private SymmetryContext m_symmetryContext;
	private DistanceRestraintsContext m_distanceRestraintsContext;
	private File m_outCellsFile;
	private File m_inCellsFile;
	private int m_numAllowedInconsistencies;
	
	
	/**************************
	 *   Accessors
	 **************************/
	
	public int getNumAllowedInconsistencies( )
	{
		return m_numAllowedInconsistencies;
	}
	public void setNumAllowedInconsistencies( int value )
	{
		m_numAllowedInconsistencies = value;
	}
	
	
	/**************************
	 *   Methods
	 **************************/
	
	public void init( SymmetryContext symmetryContext, DistanceRestraintsContext distanceRestraintsContext, File outCellsFile, File inCellsFile )
	{
		// save parameters
		m_symmetryContext = symmetryContext;
		m_distanceRestraintsContext = distanceRestraintsContext;
		m_outCellsFile = outCellsFile;
		m_inCellsFile = inCellsFile;
		m_numAllowedInconsistencies = 0;
	}
	
	public void search( int numThreads )
	throws Exception
	{
		search( numThreads, 0 );
	}
	
	public void search( int numThreads, int batchSize )
	throws Exception
	{
		// if there are no in cells, create a new search space
		CellIterator iterCell = null;
		int numOldCells = 0;
		if( m_inCellsFile == null )
		{
			SearchSpace searchSpace = m_symmetryContext.newSearchSpace( m_distanceRestraintsContext.getMaxRestraint().getMaxDistance() );
			ArrayList<? extends GridCell> cells = searchSpace.getInitialCells();
			iterCell = new CellIterator( cells );
			numOldCells = cells.size();
		}
		// otherwise, get our cells from the file
		else
		{
			CellReader reader = new CellReader( m_inCellsFile, m_symmetryContext.getSymmetry() );
			iterCell = new CellIterator( reader );
			numOldCells = (int)reader.getNumCells();
		}
		int numNewCells = numOldCells * 2;
		
		// read the old cells into memory
		System.out.println( "Reading cells..." );
		Progress readProgress = new Progress( numOldCells, 5000 );
		ArrayList<GridCell> oldCells = new ArrayList<GridCell>( numOldCells );
		while( iterCell.hasNext() )
		{
			oldCells.add( iterCell.next() );
			readProgress.incrementProgress();
		}
		
		// allocate space for the new cells
		ArrayList<GridCell> newCells = new ArrayList<GridCell>( numNewCells );
		for( int i=0; i<numNewCells; i++ )
		{
			newCells.add( m_symmetryContext.getSymmetry().newGridCell() );
		}
		
		// allocate and initialize the consistency lists
		Consistency consistency = new Consistency( numNewCells, m_distanceRestraintsContext.getInternalRestraints() );
		
		// init our progress meter
		Progress progress = new Progress( numNewCells, 5000 );
		
		dynamicVersion( oldCells, newCells, consistency, progress, numThreads, batchSize );
		//staticVersion( oldCells, newCells, consistency, numThreads );
		
		// write out the cells
		int numInconsistentRestraints = consistency.getNumInconsistentRestraints();
		long numCellsWritten = writeConsistentCells( newCells, consistency, numInconsistentRestraints, m_numAllowedInconsistencies );
		
		// output some useful information
		System.out.println( "Inconsistent Restraints: " + numInconsistentRestraints );
		System.out.println( "Cells read: " + numOldCells );
		System.out.println( "Cells processed: " + numNewCells );
		System.out.println( "Cells accepted: " + numCellsWritten );
		System.out.println( "Cells pruned: " + ( numNewCells - numCellsWritten ) );
		System.out.println( "Cells processed per second: " + (float)newCells.size()/progress.getElapsedSeconds() );
	}
	
	
	/**************************
	 *   Functions
	 **************************/
	
	private long writeConsistentCells( ArrayList<GridCell> newCells, Consistency consistency, int numInconsistentRestraints, int numAllowedInconsistencies )
	throws IOException
	{
		// just in case...
		assert( newCells.size() == consistency.getCells().length );
		
		System.out.println( "Writing out consistent cells..." );
		Progress progress = new Progress( newCells.size(), 5000 );
		
		// write the consistent cells to disk
		CellWriter writer = new CellWriter( m_outCellsFile );
		for( int i=0; i<newCells.size(); i++ )
		{
			if( consistency.getCells()[i] <= numInconsistentRestraints + numAllowedInconsistencies )
			{
				writer.writeCell( newCells.get( i ) );
			}
			
			progress.incrementProgress();
		}
		writer.close();
		
		return writer.getNumCellsWritten();
	}
	
	private void dynamicVersion( ArrayList<GridCell> oldCells, ArrayList<GridCell> newCells, Consistency consistency, Progress progress, int numThreads, int batchSize )
	throws Exception
	{
		// start some search processors
		SearchProcessorSynchronizer synch = new SearchProcessorSynchronizer(
			progress,
			oldCells.size(),
			newCells,
			consistency
		);
		Thread[] threads = new Thread[numThreads];
		for( int i=0; i<threads.length; i++ )
		{
			SearchProcessor processor = new SearchProcessor(
				synch,
				m_symmetryContext,
				m_distanceRestraintsContext,
				Collections.unmodifiableList( oldCells )
			);
			if( batchSize > 0 )
			{
				processor.setBatchSize( batchSize );
			}
			threads[i] = new Thread( processor );
			threads[i].start();
		}
		
		// wait for threads to finish
		for( int i=0; i<threads.length; i++ )
		{
			threads[i].join();
		}
	}
	
	/*
	private void staticVersion( ArrayList<GridCell> oldCells, ArrayList<GridCell> newCells, Consistency consistency, int numThreads )
	throws Exception
	{
		// start some search processors
		Thread[] threads = new Thread[numThreads];
		for( int i=0; i<threads.length; i++ )
		{
			StaticSearchProcessor processor = new StaticSearchProcessor(
				m_symmetryContext,
				m_noesContext,
				Collections.unmodifiableList( oldCells ),
				Collections.unmodifiableList( newCells ),
				i,
				numThreads
			);
			threads[i] = new Thread( processor );
			threads[i].start();
		}
		
		// wait for threads to finish
		for( int i=0; i<threads.length; i++ )
		{
			threads[i].join();
		}
	}
	
	private void serialVersion( ArrayList<GridCell> oldCells, ArrayList<GridCell> newCells, Consistency consistency, Progress progress )
	{
		SearchSpace searchSpace = m_symmetryContext.newSearchSpace( m_noesContext.getMaxRestraint().getMaxDistance() );
		CellSplitter splitter = m_symmetryContext.getSymmetry().newCellSplitter( searchSpace );
		CellChecker checker = m_symmetryContext.getSymmetry().newCellChecker( m_symmetryContext );
		GridCell left = null;
		GridCell right = null;
		int leftIndex = 0;
		int rightIndex = 0;
		
		for( int i=0; i<oldCells.size(); i++ )
		{
			GridCell cell = oldCells.get( i );
			
			// split the cell and process both children
			leftIndex = i * 2;
			rightIndex = i * 2 + 1;
			left = newCells.get( leftIndex );
			right = newCells.get( rightIndex );
			splitter.split( left, right, cell );
			processCell( newCells, leftIndex, checker, consistency );
			processCell( newCells, rightIndex, checker, consistency );
			
			progress.incrementProgress( 2 );
		}
	}
	
	private void processCell( ArrayList<GridCell> cells, int cellIndex, CellChecker checker, Consistency consistency )
	{
		GridCell cell = cells.get( cellIndex );
		
		// for each restraint...
		int numRestraints = m_noesContext.getPseudoRestraints().size();
		for( int i=0; i<numRestraints; i++ )
		{
			DistanceRestraint restraint = m_noesContext.getPseudoRestraints().get( i );
			
			int j = 0;
			int numInconsistentAssignments = 0;
			for( Assignment<AtomAddress> assignment : restraint )
			{
				if( checker.isConsistent( cell, restraint.getMaxDistance(), assignment ) )
				{
					consistency.markRestraintConsistent( i, j );
				}
				else
				{
					numInconsistentAssignments++;
				}
				
				j++;
			}
			
			if( numInconsistentAssignments == j )
			{
				consistency.markCellInconsistent( cellIndex );
			}
		}
	}
	*/
}
