/*
    Copyright (C) 2012 Bartosz Broda, Jacek Bochenek, Łukasz Biłeńkij, 
    Damian Jaworski, Wojciech Mazur, Dominik Piasecki, Maciej Piasecki, 
    Radosław Ramocki
    
    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 LICENSE and COPYING files for more details.
*/

#include "parallelWBST.h"

namespace parallel
{
	namespace WBST
	{

		//Loading WBST file, partially copied from options.cpp
		std::vector<WBSTQuestion> WBSTSlave::loadWBSTFile(std::string filename)
		{

			std::ifstream file(filename.c_str() );
			if(!file)
				throw std::runtime_error( "Cannot open wbst file: " + filename);
			std::string line;
			std::getline(file, line);//first line is only a header

			std::vector<WBSTQuestion> results=std::vector<WBSTQuestion>();

			while(std::getline(file,line) )
			{
				boost::trim(line);
				if(line.length() == 0 )
					continue;
				WBSTQuestion q(line);
				results.push_back( q );
			}
			return results;
		}
		WBSTMaster::~WBSTMaster()
		{
			for(int i=0;i<totalNumberOfTests;i++)
				delete testsTable[i];
			delete testsTable;
			if(sortResults)
			{
				for(int i=0;i<totalNumberOfTests;i++)
					delete endResults[i];
				delete endResults;
			}
		}
		void WBSTMaster::Finalize()
		{
			//Finishing results.csv
			if(sortResults)
			{
				for(int i=0;i<totalNumberOfTests;i++)
				{
					boost::filesystem::path path=wbstTests[testsTable[i][2]];
					output<< testsTable[i][0] << ";" << matrices[testsTable[i][0]].second << ";" << functions[testsTable[i][1]];

					output<< ";" << testsTable[i][2] << ";" << path.leaf() << ";";

					output<< endResults[i][0]<< ";" << endResults[i][1] << ";" << endResults[i][2]  << ";"
							<< double(endResults[i][0])/double(endResults[i][0]+endResults[i][1]) << std::endl;
				}
			}
			output<< "\nLegend:\nMatrices:\nNumber;Name;Path\n";
			for(size_t matrix_no = 0; matrix_no < matrices.size(); matrix_no++)
			{
				std::pair<std::string, std::string> &dirToMatrix = matrices[matrix_no];
				output << matrix_no << ";" << dirToMatrix.second << ";" << dirToMatrix.first << std::endl;
			}
			output << "\nWBST files:\nNumber;test;;;file\n";
			for(size_t test_no = 0; test_no < wbstTests.size(); test_no++)
			{
				boost::filesystem::path p(wbstTests[test_no]);
				output << test_no << ";" << p.leaf() << ";;;"<<p.string() << std::endl;
			}
			output.close();

			//Finishing report.txt
			double totalTime=MPI_Wtime()-startTime;
			reportFile<<"Number of nodes: "<<numOfNodes<<std::endl;
			reportFile<<"Number of matrices: "<<matrices.size()<<std::endl;
			reportFile<<"Number of functions: "<<functions.size()<<std::endl;
			reportFile<<"Number of tests: "<<wbstTests.size()<<std::endl;
			reportFile<<"Total number of tests: "<<totalNumberOfTests<<std::endl;
			reportFile<<"Time taken: "<<totalTime<<std::endl;
			reportFile.close();
		}

		WBSTMaster::WBSTMaster()
		{
		}
		void WBSTMaster::loadTestsTable(bool noLoop)
		{
			if(noLoop)
			{
				if(functions.size()!=wbstTests.size())
					throw std::runtime_error("Invalid use of parameter no-loop!");
				totalNumberOfTests=functions.size()*matrices.size();
			}
			else
				totalNumberOfTests=matrices.size()*functions.size()*wbstTests.size();
			//Every row contains triple - matrix,function,wbstTest

			testsTable = new int*[totalNumberOfTests];//[totalNumberOfTests][3];
			for(int i=0;i<totalNumberOfTests;i++)
			{
				testsTable[i] = new int[3];
			}
			for(unsigned int i=0,tableIterator=0;i<matrices.size();i++)
			{
				for(unsigned int j=0;j<functions.size();j++)
				{
					if(noLoop)
					{
						testsTable[j][0]=i;
						testsTable[j][1]=j;
						testsTable[j][2]=j;
					}
					else
					{
						for(unsigned int k=0;k<wbstTests.size();k++)
						{
							testsTable[tableIterator][0]=i;//matrices
							testsTable[tableIterator][1]=j;//functions
							testsTable[tableIterator][2]=k;//wbst
							tableIterator++;
						}
					}
				}
			}
		}
		void WBSTMaster::Init(WBSTOptions options,MPI_Status status)
		{
			sortResults = options.GetSortResults();
			functions = options.GetFunctions();
			matrices = options.GetPathToMatrixVector();
			wbstTests = options.GetWBSTFiles();
			MPI_Comm_size(MPI_COMM_WORLD,&numOfNodes);
			loadTestsTable(options.GetNoLoop());
			resultsDir=options.GetOutDir();
			testsDone = 0;
			stat = status;
			boost::filesystem::path resultsPath(options.GetOutDir());
			resultsPath.normalize();
			resultsPath/="results.csv";
			output.open((resultsPath).string().c_str());
			testsIterator =0 ;

			boost::filesystem::path reportPath(options.GetOutDir());
			reportPath.normalize();
			reportPath/="report.txt";
			std::string reportFilename=reportPath.string();
			reportFile.open(reportFilename.c_str());
			endResults=NULL;
			if(sortResults)
			{
				endResults = new int*[totalNumberOfTests];
				for(int i=0;i<totalNumberOfTests;i++)
					endResults[i] = new int[3];
			}
			startTime=MPI_Wtime();
			start = boost::posix_time::microsec_clock::local_time();
		}

		void WBSTMaster::receivedGiveWorkMsg(int sourceNode)
		{
			int message;
			if(testsIterator==totalNumberOfTests)
			{
				message=EXIT_CODE;
				mpiHandler.sendItem(message,MPI_INT,sourceNode);
			}
			else
			{
				mpiHandler.sendItem(testsIterator,MPI_INT,sourceNode);
				testsIterator++;
			}
		}

		void WBSTMaster::receivingResults(int sourceNode)
		{
			int message=ALL_OK;
			mpiHandler.sendItem(message,MPI_INT,sourceNode);
			int receivedTest;
			mpiHandler.recvItem(receivedTest,MPI_INT,sourceNode);
			testsDone++;
			SingleTestResult results;

			mpiHandler.recvItem(results.good,MPI_INT,sourceNode);
			mpiHandler.recvItem(results.bad,MPI_INT,sourceNode);
			mpiHandler.recvItem(results.skipped,MPI_INT,sourceNode);
			//Writing results to .csv file
			if(sortResults)
			{
				endResults[receivedTest][0] = results.good;
				endResults[receivedTest][1] = results.bad;
				endResults[receivedTest][2] = results.skipped;
			}
			else
			{
				boost::filesystem::path path=wbstTests[testsTable[receivedTest][2]];
				output<< testsTable[receivedTest][0] << ";" << matrices[testsTable[receivedTest][0]].second << ";" << functions[testsTable[receivedTest][1]];
				output<< ";" << testsTable[receivedTest][2] << ";" << path.leaf() << ";";
				output<< results.good << ";" << results.bad << ";" << results.skipped  << ";"
						<< double(results.good)/double(results.good+results.bad) << std::endl;
			}
		}
		void WBSTMaster::printETA()
		{
			using namespace boost::posix_time;
			//ETA
			ptime end = microsec_clock::local_time();
			time_duration len = end - start;
			if( len.total_milliseconds()> 5000)
			{
				total_time += len;
				long total_time_ms = total_time.total_milliseconds();
				long eta_ms=-1;
				if(testsDone!=0)
					eta_ms = (total_time_ms * (totalNumberOfTests-testsDone) ) / testsDone;
				std::cout << "Processed " << testsDone<< " / " << totalNumberOfTests
						<< " Elapsed: " << milliseconds(total_time_ms) 	<< " ETA: " << to_simple_string( milliseconds(eta_ms) )
						<< "            \n";
				std::cout.flush();
				start = microsec_clock::local_time();
			}//if len > 5000
		}

		void WBSTMaster::createLogIndex()
		{
			std::stringstream logFileName;
			boost::filesystem::path logPath(resultsDir);
			logPath.normalize();
			logFileName << "log_index.txt";
			std::string filename = logFileName.str();
			logPath/=filename;
			std::ofstream logIndexFile(logPath.string().c_str());
			if(!logIndexFile)
				throw std::runtime_error("Cannot open file for log index!");
			logIndexFile<<"Filename;matrix;function;wbst test\n";
			for(int i=0;i<totalNumberOfTests;i++)
				logIndexFile<<"log"<<i<<".txt;"<<matrices[testsTable[i][0]].second<<";"<<functions[testsTable[i][1]]<<";"<<wbstTests[testsTable[i][2]]<<std::endl;
			logIndexFile.close();
//			pMatrix->GetName() << "_" <<  functions[functionNo] << "_test"<<testNo<<".txt";
		}

		void WBSTMaster::Start()

		{
			output<< "Matrix no.; Matrix; Compare function;Test no.; Test name; Correct; Incorrect;Skipped;Accuracy\n";
			int message;
			MPI_Request request;
			int sourceNode;
			int probeFlag=0;
			while(testsDone<totalNumberOfTests)
			{
				probeFlag=mpiHandler.probeForMessage(-1);
				while(probeFlag==0)
				{
					printETA();
					sleep(1);
					probeFlag=mpiHandler.probeForMessage(-1);
				}
				sourceNode=mpiHandler.recvItem(message,MPI_INT,-1);
				if(message==GIVE_ME_TEST)
				{
					receivedGiveWorkMsg(sourceNode);
				}
				else if(message==READY_TO_WRITE)
				{
					receivingResults(sourceNode);
				}
				else
				{
					throw std::runtime_error("Invalid message received on master node!\n");
				}
			}
			message=EXIT_CODE;
			for(int i=1;i<numOfNodes;i++)
			{
				MPI_Isend(&message,1,MPI_INT,i,0,MPI_COMM_WORLD,&request);
				MPI_Request_free(&request);
			}
			createLogIndex();
		}

		void WBSTSlave::loadCompareFunction()
		{
			if(matrixNo!=oldMatrixNo)
			{
				pCmp.reset();
				pMatrix.reset();
				pMatrix = Matrices::IO::LoadSuperMatrix(matrices[matrixNo].first,matrices[matrixNo].second,ccs,true);
				oldMatrixNo=matrixNo;
				pCmp=smartcomparator::compare::FunctionFactory::CreateFunction(functions[functionNo],pMatrix);
				oldFunctionNo=functionNo;
			}
			else if(oldFunctionNo!=functionNo)
			{
				pCmp=smartcomparator::compare::FunctionFactory::CreateFunction(functions[functionNo],pMatrix);
				oldFunctionNo=functionNo;
			}
		}
		void WBSTSlave::sendResults(SingleTestResult results)
		{
			MPI_Request request;
			int message=READY_TO_WRITE;
			MPI_Isend(&message,1,MPI_INT,0,0,MPI_COMM_WORLD,&request);
			MPI_Request_free(&request);
			mpiHandler.recvItem(message,MPI_INT,0);
			if (message==ALL_OK) //else error
			{
				mpiHandler.sendItem(currentRow,MPI_INT,0);
				mpiHandler.sendItem(results.good,MPI_INT,0);
				mpiHandler.sendItem(results.bad,MPI_INT,0);
				mpiHandler.sendItem(results.skipped,MPI_INT,0);
			}
			else
			{
				throw std::runtime_error("Error! message!=ALL_OK\n");
			}
		}
		std::string WBSTSlave::getLogFileName()
		{

			std::stringstream logFileName;
			boost::filesystem::path logPath(resultsDir);
			logPath.normalize();
			logFileName << "log" << currentRow <<".txt";
//			logFileName << "log" << pMatrix->GetName() << "_" <<  functions[functionNo] << "_test"<<testNo<<".txt";
			std::string filename = logFileName.str();
			boost::algorithm::replace_all(filename,"/","_");
			logPath/=filename;
			return logPath.string();
		}

		void WBSTSlave::fetchWork(bool &exitFlag)
		{
			MPI_Request request;
			int message=GIVE_ME_TEST;
			MPI_Isend(&message,1,MPI_INT,0,0,MPI_COMM_WORLD,&request);
			MPI_Request_free(&request);
			mpiHandler.recvItem(message,MPI_INT,0);
			if(message==EXIT_CODE)
			{
				exitFlag=true;
				return;
			}
			currentRow=message;
			matrixNo=testsTable[currentRow][0];
			functionNo=testsTable[currentRow][1];
			testNo=testsTable[currentRow][2];
		}

		void WBSTSlave::Start()
		{
			SingleTest *singleTest=NULL;
			std::vector<WBSTQuestion> wbst;
			bool exitFlag=false;
			while(!exitFlag)
			{
				fetchWork(exitFlag);
				if(exitFlag)
					break;
				loadCompareFunction();
				wbst=loadWBSTFile(wbstTests[testNo]);
				std::string logPath = getLogFileName();
				if(singleTest!=NULL)
					delete singleTest;
				singleTest = new SingleTest(pMatrix,wbst,pCmp,flexClass,logPath);
				std::cout<<"Computing "<<currentRow<<" test: matrix "<<matrixNo<<", function: "
						<<functionNo<<", test: "<<testNo<<" on node "<<myRank<<std::endl;
				singleTest->runTest();
				//Sending back results
				sendResults(singleTest->GetAggregatedResults());
				if(!noXML)
					singleTest->WriteResults();
				delete singleTest;
				singleTest = NULL;
			}
		}
		WBSTSlave::~WBSTSlave()
		{
			for(int i=0;i<totalNumberOfTests;i++)
				delete testsTable[i];
			delete testsTable;
		}

		WBSTSlave::WBSTSlave()
		{
		}

		void WBSTSlave::loadTestsTable(bool noLoop)
		{
			if(noLoop)
			{
				if(functions.size()!=wbstTests.size())
					throw std::runtime_error("Invalid use of parameter no-loop!");
				totalNumberOfTests=functions.size()*matrices.size();
			}
			else
				totalNumberOfTests=matrices.size()*functions.size()*wbstTests.size();
			//Every row contains triple - matrix,function,wbstTest

			testsTable = new int*[totalNumberOfTests];//[totalNumberOfTests][3];
			for(int i=0;i<totalNumberOfTests;i++)
			{
				testsTable[i] = new int[3];
			}
			for(unsigned int i=0,tableIterator=0;i<matrices.size();i++)
				for(unsigned int j=0;j<functions.size();j++)
				{
				if(noLoop)
				{
					testsTable[j][0]=i;
					testsTable[j][1]=j;
					testsTable[j][2]=j;
				}
				else
					for(unsigned int k=0;k<wbstTests.size();k++)
					{
					testsTable[tableIterator][0]=i;//matrices
					testsTable[tableIterator][1]=j;//functions
					testsTable[tableIterator][2]=k;//wbst
					tableIterator++;
				}
			}
		}

		void WBSTSlave::Init(WBSTOptions options,MPI_Status status)
		{
			matrices=options.GetPathToMatrixVector();
			wbstTests = options.GetWBSTFiles();
			functions = options.GetFunctions();
			flexClass = options.GetFlexClass();
			resultsDir = options.GetOutDir();

			ccs = options.GetLoadCCS();
			noXML = options.GetNoXML();
			stat = status;

			loadTestsTable(options.GetNoLoop());

			oldMatrixNo=-1;
			oldFunctionNo=-1;

			MPI_Comm_rank(MPI_COMM_WORLD,&myRank);
		}


		void runParallelWBST(WBSTOptions options,int ac, char **av)
		{
			int myRank;
			MPI_Status stat;
			MPI_Init(&ac,&av);
			MPI_Comm_rank(MPI_COMM_WORLD,&myRank);
			if(myRank==0)
			{
				WBSTMaster master;
				master.Init(options,stat);
				master.Start();
				master.Finalize();
				std::cout<<"Master finished.\n";
			}
			else
			{
				WBSTSlave slave;
				slave.Init(options,stat);
				slave.Start();
				std::cout<<"Slave "<<myRank<<" finished.\n";
			}
			MPI_Finalize();
		}
	}
}
