/*
    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 "wbst_tester.h"
#include "options.h"
#include "singletest.h"

namespace fs = boost::filesystem;

int main(int argc, char* argv[])
{
	Matrices::init_logging();
	using namespace wbst;

	try
	{
		WBSTTester tester(argc, argv);
		tester.Start();
	}
	catch(std::string &s)
	{
		std::cerr << "Old-type exception: " << s << std::endl;
		return -1;
	}
	catch(std::runtime_error &e)
	{
		std::cerr << "\nRuntime error: " << e.what() << std::endl;
		return -1;
	}
	catch(std::exception &e)
	{
		std::cerr << "\nExcetption: " << e.what() << std::endl;
		return -1;
	}

	return 0;
}

namespace wbst
{
	bool Triple::operator <(const Triple &t) const
	{
		if( matrix_no < t.matrix_no)
			return true;
		if( t.matrix_no < matrix_no )
			return false;
		if( cmp_no < t.cmp_no )
			return true;
		if( t.cmp_no < cmp_no)
			return false;

		return test_no < t.test_no;
	}

	WBSTTester::WBSTTester(int ac, char*av[])
	{
		WBSTOptions options(ac,av);
		m_vFunctions = options.GetFunctions();
		m_vPathToMatrix = options.GetPathToMatrixVector();
		m_vQuestions = options.GetQuestions();
		m_cThreads = options.GetNumberOfThreads();
		m_sOutDir = options.GetOutDir();
		m_vWBSTFiles = options.GetWBSTFiles();
		m_sFlexClass = options.GetFlexClass();
		m_bLoadCCS = options.GetLoadCCS();
		m_iLogInterval = 500;
	}

	void WBSTTester::PrintString(const std::string & str)
	{
		boost::mutex::scoped_lock lock(io_mutex);
		std::cout << str << std::endl;
	}

	void WBSTTester::Start()
	{
		using boost::shared_ptr;
		using boost::thread;
		Threads threads;
		int thread_no = 0;
		// for each matrix
		for(size_t matrix_no = 0; matrix_no < m_vPathToMatrix.size(); matrix_no++)
		{
			std::pair<std::string, std::string> &dir2name = m_vPathToMatrix[matrix_no];
			WaitForSomeThreadToFinish(threads);
			Matrices::SuperMatrixPtr pMatrix = Matrices::IO::LoadSuperMatrix(dir2name.first, dir2name.second, m_bLoadCCS, true);
			// for each cmp function
			for(size_t cmp_no = 0; cmp_no < m_vFunctions.size(); cmp_no++)
			{
				smartcomparator::compare::CompareFunctionPtr pCmp = BuildFunction( m_vFunctions[cmp_no], pMatrix );
				// for each test
				for(size_t test_no = 0; test_no < m_vQuestions.size(); test_no++)
				{
					std::vector<WBSTQuestion> &questions = m_vQuestions[test_no];
					std::stringstream name;
					std::string fun_name = m_vFunctions[cmp_no];
					boost::replace_all(fun_name, "\\", "_");
					boost::replace_all(fun_name, "/","_");
					boost::replace_all(fun_name, "|","_");
					name << "log" << pMatrix->GetName() << "_" <<  fun_name << "_test"<<test_no <<".txt";
					boost::filesystem::path p(m_sOutDir);
					p /= name.str();
					std::string logfile = p.string();
					WaitForSomeThreadToFinish(threads);
					shared_ptr<SingleTestThread> pTest( new SingleTestThread( pMatrix, questions, pCmp,
								logfile, thread_no ,m_sFlexClass, m_iLogInterval, io_mutex) );
					shared_ptr<thread> t( new thread(boost::ref(*pTest) ) );
					Triple triple(matrix_no, cmp_no, test_no);
					ThreadDataPtr pThreadData(new ThreadData( triple, t, pTest) );
					threads[thread_no++] = pThreadData;
				}
			}//for each cmp function
		}//for each matrix
		PrintString("Waiting for unfinished threads");
		WaitForAllThreads(threads);
		PrintString("Writing results" );
		WriteResults();
	}

	void WBSTTester::WriteResults()
	{
		boost::filesystem::path p(m_sOutDir);
		p /= "results.csv";
		std::string filename = p.string();
		std::ofstream file(filename.c_str() );
		std::ostream *out = &file;
		if(! *out)
			out = &std::cout;// this is only done in case of some error

		(*out) << "Matrix no.; Matrix; Compare function;Test no.; Test name; Correct; Incorrect;Skipped;Accuracy\n";

		for(std::map<Triple, SingleTestResult>::iterator iter = m_mResults.begin(); iter != m_mResults.end(); iter++)
		{
			const Triple &t = iter->first;
			SingleTestResult results = iter->second;
			boost::filesystem::path p(m_vWBSTFiles[t.test_no]);
			(*out) << t.matrix_no << ";" << m_vPathToMatrix[t.matrix_no].second << ";" << m_vFunctions[t.cmp_no]
				<< ";" << t.test_no << ";" << p.leaf() << ";";
			(*out) << results.good << ";" << results.bad << ";" << results.skipped  << ";"
				<< double(results.good)/double(results.good+results.bad) << std::endl;
		}

		(*out) << "\nLegend:\nMatices:\nNumber;Name;Path\n";
		for(size_t matrix_no = 0; matrix_no < m_vPathToMatrix.size(); matrix_no++)
		{
			std::pair<std::string, std::string> &dirToMatrix = m_vPathToMatrix[matrix_no];
			(*out) << matrix_no << ";" << dirToMatrix.second << ";" << dirToMatrix.first << std::endl;
		}
		(*out) << "\nWBST files:\nNumber;test;;;file\n";
		for(size_t test_no = 0; test_no < m_vQuestions.size(); test_no++)
		{
			boost::filesystem::path p(m_vWBSTFiles[test_no]);
			(*out) << test_no << ";" << p.leaf() << ";;;"<<p.string() << std::endl;
		}

		out->flush();
	}
	void WBSTTester::WaitForAllThreads(Threads &threads)
	{
		while(threads.size())
		{
			for(Threads::iterator iter = threads.begin();iter != threads.end(); iter++)
			{
				ThreadDataPtr data = iter->second;
				boost::shared_ptr<SingleTestThread> pThread = data->test;
				if(!(pThread->IsRunning() ) )
				{
					m_mResults[data->triple] = pThread->GetAggregatedResults();
					threads.erase(iter->first);
					break;
				}
				else
					data->thread->join();
			}
		}
	}
	void WBSTTester::WaitForSomeThreadToFinish(Threads &threads)
	{
		using boost::shared_ptr;
		using boost::thread;

		if( threads.size() >= m_cThreads )//wait for one to finish
		{
			boost::xtime sleep_time;
			memset(&sleep_time, 0, sizeof(boost::xtime) );
			sleep_time.sec = 5;
			while(threads.size() >= m_cThreads)
			{
				bool removed = false;
				for(Threads::iterator iter = threads.begin();iter != threads.end(); iter++)
				{
					ThreadDataPtr data = iter->second;
					shared_ptr<SingleTestThread> pThread = data->test;
					if(!(pThread->IsRunning() ) )
					{
						m_mResults[data->triple] = pThread->GetAggregatedResults();
						threads.erase(iter->first);
						removed = true;
						break;
					}
				}
				if(!removed)
				{
					Threads::iterator iter = threads.begin();
					ThreadDataPtr data = iter->second;
					shared_ptr<SingleTestThread> pThread = data->test;
					if(pThread->IsRunning() )
						data->thread->sleep(sleep_time);
					else
					{
						m_mResults[data->triple] = pThread->GetAggregatedResults();
						threads.erase(iter->first);
					}
				}//!removed
			}
		}// running > number of threads
	}

	smartcomparator::compare::CompareFunctionPtr WBSTTester::BuildFunction( const std::string& function, Matrices::SuperMatrixPtr pMatrix)
	{
		using namespace smartcomparator::compare;
		//return CompareFunctionPtr(new TScoreRankComparision(pMatrix));
		return smartcomparator::compare::FunctionFactory::CreateFunction( function, pMatrix);
	}
}

