/***************************************
	Auteur : Pierre Aubert
	Mail : pierre.aubert@lapp.in2p3.fr
	Licence : CeCILL-C
****************************************/
// #include "tbb/task_scheduler_init.h"
// #include "tbb/tick_count.h"
#include "OptionParser.h"
#include "temporary_alloc.h"
#include "ProgressTime.h"
#include "intrinsics_propagation_link_block_parallel.h"
#include "MatrixHdf5.h"

///Create the OptionParser of this program
/**	@return OptionParser of this program
*/
OptionParser createOptionParser(){
	OptionParser parser(true, __PROGRAM_VERSION__);
	parser.setExampleLongOption("intrinsics_link_block_parallel_gray_scott --killrate=0.062 --feedrate=0.03 --nbimage=100 --nbrow=1080 --nbcol=1920 --output=outputFile.hdf5");
	parser.setExampleShortOption("intrinsics_link_block_parallel_gray_scott -k 0.062 -f 0.03 -n 100 -r 1080 -c 1920 -o outputFile.hdf5");
	
	float killRate(0.054f), feedRate(0.014f);
	size_t nbImage(100lu), nbRow(100lu), nbCol(200lu);
	parser.addOption("killrate", "k", killRate, "rate of the process which converts V into P");
	parser.addOption("feedrate", "f", feedRate, "rate of the process which feeds U and drains U, V and P");
	parser.addOption("nbimage", "n", nbImage, "number of images to be created");
	size_t nbExtraStep(1lu);
	parser.addOption("nbextrastep", "e", nbExtraStep, "number of extra steps to be computed between images");
	
	parser.addOption("nbrow", "r", nbRow, "number of rows of the images to be created");
	parser.addOption("nbcol", "c", nbCol, "number of columns of the images to be created");
	float dt(1.0f);
	parser.addOption("deltat", "t", dt, "time interval between two computation");
	
	size_t blockSizeRow(72lu), blockSizeCol(127lu);
	parser.addOption("blocksizerow", "l", blockSizeRow, "number of rows of the blocks");
	parser.addOption("blocksizecol", "m", blockSizeCol, "number of columns of the blocks");
	
	size_t nbThread(0lu);
	parser.addOption("nbthread", "d", nbThread, "number of threads to be used");
	
	std::string defaultOutputFile("output.h5");
	parser.addOption("output", "o", defaultOutputFile, "Output file to be created with results");
	return parser;
}
///Simulate the images
/**	@param nbRow : number of rows of the images to be created
 * 	@param nbCol : number of columns of the images to be created
 * 	@param nbImage : number of images to be created
 * 	@param nbExtraStep : number of extra steps to be computed between images
 * 	@param killRate : rate of the process which converts V into P
 * 	@param feedRate : rate of the process which feeds U and drains U, V and P
 * 	@param dt : time interval between two computation
 * 	@param outputFile : name of the file to be created
 * 	@param blockSizeRow : number of rows of the blocks
 * 	@param blockSizeCol : number of columns of the blocks
 * 	@param nbThread : number of thread to be used
 * 	@return true on succsess, false otherwise
*/
bool simulateImage(size_t nbRow, size_t nbCol, size_t nbImage, size_t nbExtraStep, float killRate, float feedRate, float dt, const std::string & outputFile,
		size_t blockSizeRow, size_t blockSizeCol, size_t nbThread)
{
	std::cout << "simulateImage : nbImage = "<<nbImage<<", nbRow = " << nbRow << ", nbCol = " << nbCol << std::endl;
	MatrixHdf5 fullMat;
	fullMat.setAllDim(nbCol, nbRow);
	fullMat.resize(nbImage);
	PTensor<float> tmpInU, tmpInV, tmpOutU, tmpOutV;
	float *tmpU1 = NULL, *tmpU2 = NULL, *tmpV1 = NULL, *tmpV2 = NULL;
	allocate_temporary(tmpU1, tmpU2, tmpV1, tmpV2, tmpInU, tmpInV, tmpOutU, tmpOutV, nbRow, nbCol);
	long nbStencilRow(3l), nbStencilCol(3l);
	
	float diffudionRateU(0.1f), diffusionRateV(0.05f);
	//This matrix of neigbour exchange is quite accurate but gives not so fun results
// 	float matDeltaSquare[] = 	{0.05f, 0.2f, 0.05f,
// 					0.2f, 0.0f, 0.2f,
// 					0.05f, 0.2f, 0.05f};
	float matDeltaSquare[] = 	{1.0f, 1.0f, 1.0f,
					1.0f, 1.0f, 1.0f,
					1.0f, 1.0f, 1.0f};
	//Let's convert these temporaries into intrinsics temporaries
	PTensor<float> tmpVecInU(AllocMode::ALIGNED), tmpVecInV(AllocMode::ALIGNED), tmpVecOutU(AllocMode::ALIGNED), tmpVecOutV(AllocMode::ALIGNED);
	tmpVecInU.fromScalToVecNeigbhour(tmpInU, PLIB_VECTOR_SIZE_FLOAT);
	tmpVecOutU.fromScalToVecNeigbhour(tmpOutU, PLIB_VECTOR_SIZE_FLOAT);
	tmpVecInV.fromScalToVecNeigbhour(tmpInV, PLIB_VECTOR_SIZE_FLOAT);
	tmpVecOutV.fromScalToVecNeigbhour(tmpOutV, PLIB_VECTOR_SIZE_FLOAT);
	PTensor<float> vecMatDeltaSquare(AllocMode::ALIGNED, nbStencilRow, nbStencilCol*PLIB_VECTOR_SIZE_FLOAT);
	reshuffle_broadcastTensor(vecMatDeltaSquare.getData(), matDeltaSquare, nbStencilRow, nbStencilCol, 0lu, PLIB_VECTOR_SIZE_FLOAT);
	float * ptrVecMatStencil = vecMatDeltaSquare.getData();
	PTensor<float> tmpScalOutV(AllocMode::ALIGNED);
	std::vector<PBlock<float> > vecBlockOutU, vecBlockOutV, vecBlockInU, vecBlockInV;
	tmpVecOutU.splitBlockLink(vecBlockOutU, blockSizeRow, blockSizeCol, 1lu);
	tmpVecOutV.splitBlockLink(vecBlockOutV, blockSizeRow, blockSizeCol, 1lu);
	tmpVecInU.splitBlockLink(vecBlockInU, blockSizeRow, blockSizeCol, 1lu);
	tmpVecInV.splitBlockLink(vecBlockInV, blockSizeRow, blockSizeCol, 1lu);
	size_t nbVecRow(tmpVecInV.getFullNbRow()), nbVecCol(tmpVecInV.getNbCol());
	ProgressTime progress(nbImage);
	progress.start();
	for(size_t i(0lu); i < nbImage; ++i){
		progress.print();
		for(size_t j(0lu); j < nbExtraStep/2lu; ++j){
			grayscott_propagation_link_block_parallel(
					vecBlockOutU, vecBlockOutV, vecBlockInU, vecBlockInV,
					ptrVecMatStencil, nbStencilRow, nbStencilCol,
					diffudionRateU, diffusionRateV, feedRate, killRate, dt);
			//Let's update the dupplicated values
			reshuffle_updateDupplicateVecNeighbour(tmpVecOutU.getData(), nbVecRow, nbVecCol, PLIB_VECTOR_SIZE_FLOAT);
			reshuffle_updateDupplicateVecNeighbour(tmpVecOutV.getData(), nbVecRow, nbVecCol, PLIB_VECTOR_SIZE_FLOAT);
			///Let's swap the in and out tensors
			grayscott_propagation_link_block_parallel(
					vecBlockInU, vecBlockInV, vecBlockOutU, vecBlockOutV,
					ptrVecMatStencil, nbStencilRow, nbStencilCol,
					diffudionRateU, diffusionRateV, feedRate, killRate, dt);
			//Let's update the dupplicated values
			reshuffle_updateDupplicateVecNeighbour(tmpVecInU.getData(), nbVecRow, nbVecCol, PLIB_VECTOR_SIZE_FLOAT);
			reshuffle_updateDupplicateVecNeighbour(tmpVecInV.getData(), nbVecRow, nbVecCol, PLIB_VECTOR_SIZE_FLOAT);
		}
		tmpScalOutV.fromVecToScalNeigbhour(tmpVecInV);	//The pointers were swaped
		fullMat.setRow(i, tmpScalOutV.getData());
	}
	progress.finish();
	std::cerr << "Done" << std::endl;
	//Let's save the output file
	fullMat.write(outputFile);
	return true;
}

int main(int argc, char** argv){
	OptionParser parser = createOptionParser();
	parser.parseArgument(argc, argv);
	const OptionMode & defaultMode = parser.getDefaultMode();
	float killRate(0.062f), feedRate(0.03f), dt(1.0f);
	size_t nbImage(100lu), nbRow(1080lu), nbCol(1920lu), nbExtraStep(1lu);
	defaultMode.getValue(killRate, "killrate");
	defaultMode.getValue(feedRate, "feedrate");
	defaultMode.getValue(nbImage, "nbimage");
	defaultMode.getValue(nbExtraStep, "nbextrastep");
	defaultMode.getValue(nbRow, "nbrow");
	defaultMode.getValue(nbCol, "nbcol");
	defaultMode.getValue(dt, "deltat");
	
	size_t blockSizeRow(72lu), blockSizeCol(127lu);
	defaultMode.getValue(blockSizeRow, "blocksizerow");
	defaultMode.getValue(blockSizeCol, "blocksizecol");
	
	size_t nbThread(0lu);
	defaultMode.getValue(nbThread, "nbthread");
	
	std::string outputFile("");
	defaultMode.getValue(outputFile, "output");
	bool b(simulateImage(nbRow, nbCol, nbImage, nbExtraStep, killRate, feedRate, dt, outputFile, blockSizeRow, blockSizeCol, nbThread));
	return b - 1;
}




