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

import edu.duke.donaldLab.jdshot.context.DistanceRestraintsContext;
import edu.duke.donaldLab.jdshot.context.SymmetryContext;
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.jdshot.search.messages.CellsRequest;
import edu.duke.donaldLab.jdshot.search.messages.CellsResponse;
import edu.duke.donaldLab.jdshot.search.messages.CellsResult;
import edu.duke.donaldLab.share.mpi.ClientState;
import edu.duke.donaldLab.share.mpi.MPIEndpoint;
import edu.duke.donaldLab.share.mpi.Server;
import edu.duke.donaldLab.share.perf.Progress;

public class SearchServer extends Server
{
	/**************************
	 *   Data Members
	 **************************/
	
	private SymmetryContext m_symmetryContext;
	private DistanceRestraintsContext m_distanceRestraintsContext;
	private File m_outCellsFile;
	private File m_inCellsFile;
	private int m_numAllowedInconsistencies;
	private Progress m_progress;
	private ArrayList<GridCell> m_oldCells;
	private ArrayList<GridCell> m_newCells;
	private int m_numCellsServed;
	private Consistency m_consistency;
	
	
	/**************************
	 *   Constructors
	 **************************/
	
	public SearchServer( SymmetryContext symmetryContext, DistanceRestraintsContext distanceRestraintsContext, File outCellsFile, File inCellsFile, int numAllowedInconsistencies )
	{
		// save parameters
		m_symmetryContext = symmetryContext;
		m_distanceRestraintsContext = distanceRestraintsContext;
		m_outCellsFile = outCellsFile;
		m_inCellsFile = inCellsFile;
		m_numAllowedInconsistencies = numAllowedInconsistencies;
		
		// init defaults
		m_progress = null;
		m_oldCells = null;
		m_newCells = null;
		m_numCellsServed = 0;
		m_consistency = null;
	}
	
	
	/**************************
	 *   Events
	 **************************/
	
	public void handleMessage( CellsRequest request )
	throws MPIException
	{
		sendCells( request.numCellsRequested, request.getSourceRank() );
	}
	
	public void handleMessage( CellsResult result )
	throws MPIException
	{
		// send next cells first
		sendCells( result.numCellsRequested, result.getSourceRank() );
		
		// then update old cells
		for( int i=0; i<result.cells.size(); i++ )
		{
			m_newCells.set( result.startIndex + i, result.cells.get( i ) );
		}
		m_consistency.update( result.consistency, result.startIndex );
		
		m_progress.incrementProgress( result.cells.size() );
	}
	
	
	/**************************
	 *   Functions
	 **************************/
	
	@Override
	protected void init( )
	throws Exception
	{
		MPIEndpoint.log( "Search starting..." );
		
		// if there are no in cells, create a new search space
		if( m_inCellsFile == null )
		{
			MPIEndpoint.log( "Initialized cells from search space" );
			SearchSpace searchSpace = m_symmetryContext.newSearchSpace( m_distanceRestraintsContext.getMaxRestraint().getMaxDistance() );
			m_oldCells = new ArrayList<GridCell>();
			m_oldCells.addAll( searchSpace.getInitialCells() );
		}
		// otherwise, get our cells from the file
		else
		{
			MPIEndpoint.log( "Reading cells..." );
			CellReader reader = new CellReader( m_inCellsFile, m_symmetryContext.getSymmetry() );
			m_oldCells = new ArrayList<GridCell>( (int)reader.getNumCells() );
			Progress readProgress = new Progress( m_oldCells.size(), 5000 );
			readProgress.setMessageListener( this );
			while( reader.hasNext() )
			{
				m_oldCells.add( reader.readCell() );
				readProgress.incrementProgress();
			}
		}
		
		// allocate space for the new cells
		m_newCells = new ArrayList<GridCell>( m_oldCells.size() * 2 );
		for( int i=0; i<m_oldCells.size() * 2; i++ )
		{
			m_newCells.add( null );
		}
		
		m_consistency = new Consistency( m_newCells.size(), m_distanceRestraintsContext.getInternalRestraints() );
		
		// start the progress bar
		m_progress = new Progress( m_newCells.size(), 5000 );
		m_progress.setMessageListener( this );
		m_numCellsServed = 0;
	}
	
	@Override
	protected void shutdown( )
	throws Exception
	{
		MPIEndpoint.log( "Search complete!" );
		
		// write out the cells
		int numInconsistentRestraints = m_consistency.getNumInconsistentRestraints();
		
		long numCellsWritten = writeConsistentCells( numInconsistentRestraints );
		
		// output some useful information
		MPIEndpoint.log( "Inconsistent NOEs: " + numInconsistentRestraints );
		MPIEndpoint.log( "Cells read: " + m_oldCells.size() );
		MPIEndpoint.log( "Cells processed: " + m_newCells.size() );
		MPIEndpoint.log( "Cells accepted: " + numCellsWritten );
		MPIEndpoint.log( "Cells pruned: " + ( m_newCells.size() - numCellsWritten ) );
		MPIEndpoint.log( "Cells processed per second: " + (float)m_newCells.size()/m_progress.getElapsedSeconds() );
	}
	
	private void sendCells( int numCellsRequested, int rank )
	throws MPIException
	{
		int numCells = Math.min( numCellsRequested, m_oldCells.size() - m_numCellsServed );
		CellsResponse response = new CellsResponse( numCells );
		
		response.startIndex = m_numCellsServed;
		for( int i=0; i<numCells; i++ )
		{
			response.cells.add( m_oldCells.get( m_numCellsServed++ ) );
		}
		MPIEndpoint.sendMessageAsync( response, rank );
		
		// if we sent 0 cells, this client is complete
		if( numCells == 0 )
		{
			setClientState( rank, ClientState.Complete );
		}
	}
	
	private long writeConsistentCells( int numInconsistentRestraints )
	throws IOException
	{
		// just in case...
		assert( m_newCells.size() == m_consistency.getCells().length );
		
		MPIEndpoint.log( "Writing out consistent cells..." );
		Progress progress = new Progress( m_newCells.size(), 5000 );
		progress.setMessageListener( this );
		CellWriter writer = new CellWriter( m_outCellsFile );
		for( int i=0; i<m_newCells.size(); i++ )
		{
			if( m_consistency.getCells()[i] <= numInconsistentRestraints + m_numAllowedInconsistencies )
			{
				writer.writeCell( m_newCells.get( i ) );
			}
			
			progress.incrementProgress();
		}
		writer.close();
		
		return writer.getNumCellsWritten();
	}
}
