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



namespace wbst
{
	SingleTestThread::SingleTestThread( Matrices::SuperMatrixPtr pMatrix, std::vector<WBSTQuestion> questions, 
					smartcomparator::compare::CompareFunctionPtr pCmp, std::string  &logfile, int thread_no,
					const std::string &flex_class, int loginterval, boost::mutex &iomutex)
		: m_bIsRunning(true),
		m_pMatrix(pMatrix),
		m_vQuestions(questions),
		m_pCmp(pCmp),
		m_sLogFile(logfile),
		m_sFlexClass(flex_class),
		m_iThreadNo(thread_no),
		m_iLogInterval(loginterval),
		io_mutex(iomutex)
	{
	}

	void SingleTestThread::operator() ()
	{
		try
		{
			m_vGoodIndices.clear();
			m_mBadToChoosen.clear();
			m_vSkippedIndices.clear();

			m_StartTime = boost::posix_time::microsec_clock::local_time();
			m_Duration = boost::posix_time::milliseconds(0);
			for(size_t q = 0; q < m_vQuestions.size(); q++)
			{
				SingleQuestion( q );
				if( q % m_iLogInterval == 0 )
					PrintProgress(q);

			}// for each question
			WriteResults();
			PrintProgress( m_vQuestions.size() );
			m_bIsRunning = false;
		}
		catch(std::runtime_error &e)
		{
			std::cerr << "ERROR: " << e.what() << std::endl;
		}
	}// operaotr ()
	bool SingleTestThread::IsRunning()
	{
		return m_bIsRunning;
	}

	void SingleTestThread::SingleQuestion(size_t q)
	{
		WBSTQuestion &question = m_vQuestions[q];
		size_t size_rows = m_pMatrix->NumberOfRows();
		size_t q_row = m_pMatrix->GetRowIndex( m_sFlexClass + ":" + question.GetQuestion() );
		if(q_row > size_rows)
			q_row = m_pMatrix->GetRowIndex( question.GetQuestion() );
		if(q_row > size_rows)
		{
			m_vSkippedIndices.insert(q);
			return;
		}
		double max_sim = -100000;
		std::string best= "--blad--";
		const std::set<std::string> &decoys = question.GetDecoys();
		for(std::set<std::string>::const_iterator decoy_iter = decoys.begin(); 
				decoy_iter != decoys.end() ; decoy_iter++)
		{
			size_t d = m_pMatrix->GetRowIndex( m_sFlexClass + ":" +*decoy_iter );
			if(d > size_rows)
				d = m_pMatrix->GetRowIndex( *decoy_iter ); //if no flex class was given in a matrix

			if(d > size_rows)
			{
				m_vSkippedIndices.insert(q);
				return;
			}
			double sim = m_pCmp->Compare(q_row, d);
			if(sim > max_sim)
			{
				max_sim = sim;
				best = *decoy_iter;
			}

		}//for each question

		if(best == question.GetAnwser() )
			m_vGoodIndices.insert(q);
		else
			m_mBadToChoosen[q] = best;

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

	void SingleTestThread::PrintProgress(size_t question_no)
	{
		boost::posix_time::ptime end = boost::posix_time::microsec_clock::local_time();
		boost::posix_time::time_duration len = end - m_StartTime;
		m_Duration = len;
		long total_time = m_Duration.total_milliseconds();
		double q_per_sec = double( question_no+1) / total_time*1000;
		long eta_ms = (long)(1000*(m_vQuestions.size() - question_no - 1)/q_per_sec);

		double good = (double) m_vGoodIndices.size();
		double all = (double) (m_vGoodIndices.size() + m_mBadToChoosen.size() );
		double accuracy = good / all;

		std::stringstream str;
		str << "[" << m_iThreadNo << "] " << question_no << " / " << m_vQuestions.size()
			<< "| accuracy=" << accuracy << ", skipped=" << m_vSkippedIndices.size() 
			<< " | q/s= " << q_per_sec << "  eta: " << to_simple_string(boost::posix_time::milliseconds(eta_ms))
			<< " elapsed= " << m_Duration;
		PrintString( str.str() );
	}	

	SingleTestResult SingleTestThread::GetAggregatedResults()
	{
		SingleTestResult results;
		results.skipped = m_vSkippedIndices.size();
		results.good = m_vGoodIndices.size();
		results.bad = m_mBadToChoosen.size();
		return results;
	}

	void SingleTestThread::WriteResults()
	{
		std::ofstream out(m_sLogFile.c_str() );
		SingleTestResult result = GetAggregatedResults();

		if(!out)
		{
			std::cerr << result;
			throw std::runtime_error("Cannot open file for writing wbst result: " + m_sLogFile);
		}

		out << "<wbst tester_version="<< VERSION <<">\n";
		out << "\t<aggregated_results>\n";
		out << "\t\t<correct>" << m_vGoodIndices.size() << "</correct>\n";
		out << "\t\t<incorrect>" << m_mBadToChoosen.size() << "</incorrect>\n";
		out << "\t\t<skipped>" << m_vSkippedIndices.size() << "</skipped>\n";
		out << "\t\t<accuracy>" << double(m_vGoodIndices.size()) / double(m_vGoodIndices.size()+m_mBadToChoosen.size() ) << "</accuracy>\n";
		out << "\t</aggregated_results>\n";

		out << "\t<skipped_questions>\n";
		for(std::set<size_t>::iterator iter = m_vSkippedIndices.begin(); iter != m_vSkippedIndices.end(); iter++)
			out << "\t\t<skipped_question_line>" << m_vQuestions[*iter].ToString() << "</skipped_question_line>\n";
		out << "\t</skipped_questions>\n";

		out << "\t<wrong_anwsers>\n";
		for(std::map<size_t, std::string>::iterator iter = m_mBadToChoosen.begin(); iter != m_mBadToChoosen.end(); iter++)
			out << "\t\t<wrong_answer choosen=\""<< iter->second <<"\">" << m_vQuestions[iter->first].ToString() << "</wrong_answer>\n";
		out << "\t</wrong_anwsers>\n";
		
		out << "\t<correct_answered>\n";
		for(std::set<size_t>::iterator iter = m_vGoodIndices.begin(); iter != m_vGoodIndices.end(); iter++)
			out << "\t\t<answer>" << m_vQuestions[*iter].ToString() << "</answer>\n";
		out << "\t</correct_answered>\n";
		
		out << "</wbst>\n";
		out.close();
	}
	
	SingleTestResult::SingleTestResult()
	{
		good = bad = skipped = 0;
	}

	std::ostream & operator << (std::ostream& out, const SingleTestResult &res)
	{
		out << "Good: " << res.good << "\nBad: " << res.bad << "\nSkipped: " << res.skipped
			<< "\nAccuracy: " << double(res.good)/double(res.good+res.bad) << std::endl;
		return out;
	}
}//namespace wbst

