/* -------------------------------------------------------------------------- *
 *                                   OpenMM                                   *
 * -------------------------------------------------------------------------- *
 * This is part of the OpenMM molecular simulation toolkit originating from   *
 * Simbios, the NIH National Center for Physics-Based Simulation of           *
 * Biological Structures at Stanford, funded under the NIH Roadmap for        *
 * Medical Research, grant U54 GM072970. See https://simtk.org.               *
 *                                                                            *
 * Portions copyright (c) 2009 Stanford University and the Authors.           *
 * Authors: Mark Friedrichs, Mike Houston                                     *
 * Contributors:                                                              *
 *                                                                            *
 * This program 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 3 of the License, or       *
 * (at your option) any later version.                                        *
 *                                                                            *
 * This program 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 program.  If not, see <http://www.gnu.org/licenses/>.      *
 * -------------------------------------------------------------------------- */

#include <math.h>
#include <stdlib.h>

#include <sstream>

#include "BrookCommon.h"
#include "BrookPlatform.h"
#include "BrookStreamFactory.h"
#include "openmm/OpenMMException.h"

using namespace OpenMM;
using namespace std;

// bonded streams
    
const std::string BrookCommon::BondedParticleIndicesStream                        = "BondedParticleIndicesStream";
const std::string BrookCommon::BondedParametersStream                             = "BondedParametersStream";
const std::string BrookCommon::UnrolledForceStream                                = "UnrolledForceStream";
const std::string BrookCommon::BondedChargeStream                                 = "BondedChargeStream";
const std::string BrookCommon::BondedInverseMapStreams                            = "BondedInverseMapStreams";

// non-bonded streams

const std::string BrookCommon::NonBondedExclusionStream                           = "NonBondedExclusionStream";
const std::string BrookCommon::OuterVdwStream                                     = "OuterVdwStream";
const std::string BrookCommon::InnerSigmaStream                                   = "InnerSigmaStream";
const std::string BrookCommon::InnerEpsilonStream                                 = "InnerEpsilonStream";
const std::string BrookCommon::NonBondedChargeStream                              = "NonBondedChargeStream";
const std::string BrookCommon::PartialForceStream                                 = "PartialForceStream";

// OBC Gbsa streams

const std::string BrookCommon::ObcParticleRadiiStream                             = "ObcParticleRadiiStream";
const std::string BrookCommon::ObcScaledParticleRadiiStream                       = "ObcScaledParticleRadiiStream";
const std::string BrookCommon::ObcParticleRadiiWithDielectricOffsetStream         = "ObcParticleRadiiWithDielectricOffsetStream";
const std::string BrookCommon::ObcBornRadiiStream                                 = "ObcBornRadiiStream";
const std::string BrookCommon::ObcBornRadii2Stream                                = "ObcBornRadii2Stream";
const std::string BrookCommon::ObcIntermediateForceStream                         = "ObcIntermediateForceStream";
const std::string BrookCommon::ObcChainStream                                     = "ObcChainStream";

// StochasticDynamics streams

const std::string BrookCommon::SDPC1Stream                                        = "SDPC1Stream";
const std::string BrookCommon::SDPC2Stream                                        = "SDPC2Stream";
const std::string BrookCommon::SD2XStream                                         = "SD2XStream";
const std::string BrookCommon::SD1VStream                                         = "SD1VStream";
const std::string BrookCommon::VPrimeStream                                       = "VPrimeStream";
const std::string BrookCommon::XPrimeStream                                       = "XPrimeStream";
const std::string BrookCommon::InverseMassStream                                  = "InverseMassStream";

// Shake streams

const std::string BrookCommon::ShakeParticleIndicesStream                         = "ShakeParticleIndicesStream";
const std::string BrookCommon::ShakeParticleParameterStream                       = "ShakeParticleParameterStream";
const std::string BrookCommon::ShakeXCons0Stream                                  = "ShakeXCons0Stream";
const std::string BrookCommon::ShakeXCons1Stream                                  = "ShakeXCons1Stream";
const std::string BrookCommon::ShakeXCons2Stream                                  = "ShakeXCons2Stream";
const std::string BrookCommon::ShakeXCons3Stream                                  = "ShakeXCons3Stream";
const std::string BrookCommon::ShakeInverseMapStream                              = "ShakeInverseMapStream";

// Random number streams

const std::string BrookCommon::ShuffleStream                                      = "ShuffleStream";
const std::string BrookCommon::RandomValuesStream                                 = "RandomValuesStream";

// Random number streams

const std::string BrookCommon::BrookVelocityCenterOfMassRemovalWorkStream         = "VelocityCenterOfMassRemovalWorkStream";
const std::string BrookCommon::BrookVelocityCenterOfMassRemovalMassStream         = "VelocityCenterOfMassRemovalMassStream";

/** 
 * Constructor
 * 
 */

BrookCommon::BrookCommon( ){

// ---------------------------------------------------------------------------------------

   // static const std::string methodName      = "BrookCommon::BrookCommon";
   // static const int debug                   = 1;

// ---------------------------------------------------------------------------------------

   _numberOfParticles             = 0;
   _particleSizeModified          = 0;

   _particleStreamWidth           = -1;
   _particleStreamHeight          = -1; 
   _particleStreamSize            = -1;

   _log                           = NULL;
   _isActive                      = 0;

}   
 
/** 
 * Destructor
 * 
 */

BrookCommon::~BrookCommon( ){

// ---------------------------------------------------------------------------------------

   //static const std::string methodName      = "BrookCommon::~BrookCommon";
   //static const int debug                   = 1;

// ---------------------------------------------------------------------------------------

}

/** 
 * Get number of particles
 * 
 * @return  number of particles
 *
 */

int BrookCommon::getNumberOfParticles( void ) const {
   return _numberOfParticles;
}

/** 
 * Get number of particles
 * 
 * @param  numberOfParticles number of particles
 * @return  number of particles
 *
 */

int BrookCommon::setNumberOfParticles( int numberOfParticles ){
   if( numberOfParticles != _numberOfParticles ){
      _particleSizeModified = numberOfParticles;
   }
   _numberOfParticles = numberOfParticles;
   return _numberOfParticles;
}

/** 
 * Get particle stream width
 * 
 * @param platform  platform
 *
 * @return  particle stream width
 *
 */

int BrookCommon::getParticleStreamWidth( const Platform& platform ){

// ---------------------------------------------------------------------------------------

   // static const std::string methodName      = "BrookCommon::getParticleStreamWidth";
   // static const int debug                   = 1;

// ---------------------------------------------------------------------------------------

   // get particle stream width

   if( _particleStreamWidth < 0 ){
      _getParticleStreamDimensions( platform );
   }
   return _particleStreamWidth;
}

/** 
 * Get particle stream width
 * 
 * @return  particle stream width
 *
 */

int BrookCommon::getParticleStreamWidth( void ) const {

// ---------------------------------------------------------------------------------------

   // static const std::string methodName      = "BrookCommon::getParticleStreamWidth";
   // static const int debug                   = 1;

// ---------------------------------------------------------------------------------------

   return _particleStreamWidth;
}

/** 
 * Get particle stream height
 * 
 * @param platform platform
 *
 * @return  particle stream height 
 *
 */

int BrookCommon::getParticleStreamHeight( const Platform& platform ){

// ---------------------------------------------------------------------------------------

   // static const std::string methodName      = "BrookCommon::getParticleStreamHeight";
   // static const int debug                   = 1;

// ---------------------------------------------------------------------------------------

   // get particle stream height

   if( _particleStreamHeight < 0 ){
      _getParticleStreamDimensions( platform );
   }
   return _particleStreamHeight;
}

/** 
 * Get particle stream height
 * 
 * @return  particle stream height 
 *
 */

int BrookCommon::getParticleStreamHeight( void ) const {

// ---------------------------------------------------------------------------------------

   // static const std::string methodName      = "BrookCommon::getParticleStreamHeight";
   // static const int debug                   = 1;

// ---------------------------------------------------------------------------------------

   return _particleStreamHeight;
}

/** 
 * Get particle stream size
 * 
 * @param platform  platform
 *
 * @return  particle stream size
 *
 */

int BrookCommon::getParticleStreamSize( const Platform& platform ){

// ---------------------------------------------------------------------------------------

   // static const std::string methodName      = "BrookCommon::getParticleStreamSize";
   // static const int debug                   = 1;

// ---------------------------------------------------------------------------------------

   // get particle stream size

   if( _particleStreamSize < 0 ){
      _getParticleStreamDimensions( platform );
   }
   return _particleStreamSize;
}

/** 
 * Get particle stream size
 * 
 * @return  particle stream size
 *
 */

int BrookCommon::getParticleStreamSize( void ) const {
   return _particleStreamSize;
}

/** 
 * Get particle stream dimensions
 * 
 * @param platform                  platform
 *
 */

void BrookCommon::_getParticleStreamDimensions( const Platform& platform ){

// ---------------------------------------------------------------------------------------

   // static const std::string methodName      = "BrookCommon::_getParticleStreamDimensions";
   // static const int debug                   = 1;

// ---------------------------------------------------------------------------------------

   // get particle stream size

   const BrookPlatform& brookPlatform           = dynamic_cast<const BrookPlatform&> (platform);
   const BrookStreamFactory& brookStreamFactory = dynamic_cast<const BrookStreamFactory&> (platform.getDefaultStreamFactory() );
   _particleStreamWidth                         = brookStreamFactory.getDefaultParticleStreamWidth();
   _particleStreamSize                          = brookPlatform.getStreamSize( getNumberOfParticles(), _particleStreamWidth, NULL );
   _particleStreamHeight                        = (int) ( ((float) _particleStreamSize)/( (float) _particleStreamWidth) + 0.001);

   return;
}

/** 
 * Get log file reference
 * 
 * @return  log file reference
 *
 */

FILE* BrookCommon::getLog( void ) const {
   return _log;
}

/** 
 * Set log file reference
 * 
 * @param  log file reference
 *
 * @return  DefaultReturnValue
 *
 */

int BrookCommon::setLog( FILE* log ){
   _log = log;
   return BrookCommon::DefaultReturnValue;
}

/** 
 * Get flag signalling whether active
 * 
 * @return  flag signalling whether active
 *
 */

int BrookCommon::isActive( void ) const {
   return _isActive;
}

/** 
 * Set flag signalling whether active
 * 
 * @param  flag signalling whether active
 *
 * @return  DefaultReturnValue
 *
 */

int BrookCommon::setIsActive( int isActive ){
   _isActive= isActive;
   return BrookCommon::DefaultReturnValue;
}

/* 
 * Get contents of object
 *
 * @param tab         tab
 * @param description description
 * @param value       value
 *
 * @return string containing contents
 *
 * */

std::string BrookCommon::_getLine( const std::string& tab, const std::string& description, const std::string& value ) const {

// ---------------------------------------------------------------------------------------

   static const std::string methodName      = "BrookCommon::_getLine";

   static const unsigned int MAX_LINE_CHARS = 256;
   char line[MAX_LINE_CHARS];

// ---------------------------------------------------------------------------------------

   std::stringstream message;
   memset( line, ' ', MAX_LINE_CHARS ); 
#ifdef WIN32
   (void) sprintf_s( line, MAX_LINE_CHARS, "%s %-40s %s", tab.c_str(), description.c_str(), value.c_str() );
#else
   (void) sprintf( line, "%s %-40s %s", tab.c_str(), description.c_str(), value.c_str() );
#endif
   message << std::string( line ) << std::endl;

   return message.str();

}

/* 
 * Given number of stream elements and width, returns the appropriate
 * height of the stream
 *
 * @param streamSize   stream size 
 * @param width        stream width
 *
 * @return stream height
 *
 */

int BrookCommon::getStreamHeight( int streamSize, int streamWidth ){

// ---------------------------------------------------------------------------------------

   //static const std::string methodName      = "BrookCommon::getStreamHeight";

// ---------------------------------------------------------------------------------------

	int streamHeight = streamSize/streamWidth;

	if( streamSize % streamWidth ){
      streamHeight++;
   }

	return streamHeight;
}

/* 
 * Given number of stream elements, get stream width & height
 *
 * @param streamSize    stream size 
 * @param streamWidth   output stream width
 * @param streamHeight  output stream height
 *
 * @return stream height
 *
 */

void BrookCommon::getStreamDimensions( int streamSize, int *streamWidth, int *streamHeight ){

// ---------------------------------------------------------------------------------------

   //static const std::string methodName      = "BrookCommon::getStreamDimensions";

// ---------------------------------------------------------------------------------------

	// There are two conditions - stream should be as square 
	// as possible, but should also be multiple of 16 along
	// one dimension
	
	float s = sqrtf( (float) streamSize );

	// find nearest multiple of 16 to the perfect square size

	int low  = ( (int) floor( s/16.0f ) ) * 16;

	if ( !low ) {
		*streamWidth  = 16;
		*streamHeight = getStreamHeight( streamSize, *streamWidth );
	} else { 

	   int high = low + 1;

		// I'm not sure 48 is such a good stream width. Things seem
		// to be faster with 32 or 64. Can make this a special
		// case later.
		
		// Choose low or high depending on which one is 
		// more square

		int htlow  = getStreamHeight( streamSize, low );
		int hthigh = getStreamHeight( streamSize, high );
		
		if ( abs( htlow - low ) < abs( hthigh - high ) ) {
			*streamWidth   = low;
			*streamHeight  = htlow;
		} else {
			*streamWidth   = high;
			*streamHeight  = hthigh;
		}
	}
	return;
}

/* 
 * Allocate array
 *
 * @param length        length of array
 * @param width         width  of array
 *
 * @return ptr to array
 *
 */

RealOpenMM** BrookCommon::allocateRealArray( int length, int width ) const {

// ---------------------------------------------------------------------------------------

   //static const std::string methodName      = "BrookCommon::allocateRealArray";

// ---------------------------------------------------------------------------------------

   RealOpenMM** array  = new RealOpenMM*[length];
   RealOpenMM*  buffer = new RealOpenMM[length*width];
   for( int ii = 0; ii < length; ii++ ){
      array[ii] = buffer;
      buffer   += width;
   }
   return array;
}

/* 
 * Free array
 *
 * @param array         array to be freed (assumed allocated using BrookCommon::allocateRealArray
 *
 * @return DefaultReturnValue
 *
 */

int BrookCommon::disposeRealArray( RealOpenMM** array ) const {

// ---------------------------------------------------------------------------------------

   //static const std::string methodName      = "BrookCommon::disposeRealArray";

// ---------------------------------------------------------------------------------------

   delete[] array[0];
   delete[] array;

   return DefaultReturnValue;
}

/* 
 * Copy 1D BrookOpenMMFloat* array to 2D array of RealOpenMM
 *
 * @param length        length of array
 * @param width         width  of array
 * @param array1D       array to copy
 *
 * @return ptr to array
 *
 */

RealOpenMM** BrookCommon::copy1DArrayTo2DArray( int length, int width, BrookOpenMMFloat* array1D ) const {

// ---------------------------------------------------------------------------------------

   //static const std::string methodName      = "BrookCommon::copy1DArrayTo2DArray";

// ---------------------------------------------------------------------------------------

   RealOpenMM** array  = allocateRealArray( length, width );
   int index           = 0;
   for( int ii = 0; ii < length; ii++ ){
     for( int jj = 0; jj < width; jj++ ){
        array[ii][jj] = static_cast<RealOpenMM> (array1D[index++]);
     }
   }

   return array;
}

