/*---------------------------------------------------------------------------------------------------------------------
- File      : evaluator.cc                                                                Project ELSE, EPFL - DI/LIA -
-                                                                       Evaluation in Language and Speech Engineering -
- Author    : Seydoux Florian   Creation date : 04 Oct 1999                                                           -
- Eulogist  : -                 Approval date : -                  Version: 0.1                                       -
-                                                                                                                     -
- Descript. : Make the evaluation.                                                                                    -
-                                                                                                                     -
- Requested : -                                                                                                       -
-                                                                                                                     -
- Gaps      :                                                                                                         -
-                                                                                                                     -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- Rev. date | Reviser               | Revise's description                                                            -
- - - - - - + - - - - - - - - - - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- ../../....| ........              | ...                                                                             -
---------------------------------------------------------------------------------------------------------------------*/


#include <fstream>
#include <iostream>
#include "globaldef.h"
#include "messages_manager.h"
#include "user_configuration.h"
#include "tags_mapping_table.h"
#include "grace_tags.h"

#ifdef _USE_NAMESPACES
    namespace Else { 
#endif // _USE_NAMESPACES

void dumpHelp();
void checkParams(Parameters&);
int  main(int argc, char* argv[]);
void evaluate(ostream&, const HtmlToken&, const HtmlToken&);
void computing(istream&, ostream&);
void printFinal(ostream& os, const HtmlResultats& allresult, const char*const head);
typedef vector<conform_string> StringsCollection;

UserConfiguration    uconfig;
UserConfiguration    rconfig;
TagsMappingTable     tagsTable;
StringsCollection    inputs;
conform_string       output;
HtmlCase             caseId;
HtmlToken            token;
HtmlComment          comment;
HtmlResultats        resultats;
const ContractedTag* neutralizedTag;
bool                 log;

conform_string uconfigFile, rconfigFile;         // Name of the configuration file.

void dumpHelp()
{
    cerr << "Description:  (Evaluation part)\n"
            "  Evaluate a system; evaluate each case of the input (identified by the aligner), and produce an\n"
            "  overview .... \n" // a cmplt
            "\n"
            "Usage: [aligned data ->] evaluater <pconfig> <rconfig> {switches} {<aligned>} [ -> evaluated datas]\n"
            "  <pconfig>      : configuration file of the evaluated participant\n"
            "  <rconfig>      : reference's configuration file\n"
            "  <aligned>      : an (re)aligned file use as input (default: stdin)\n"
            "Switches:\n"
            "  -out output    : (re)write the result to 'output' (default: stdout).\n"
            "  -abs           : absolute evaluation: mapping system -> reference (default).\n"
            "  -rel           : relative evaluation: mapping system <- reference.\n"
            "  -log           : output human readeable format to stderr (patch temporaire)\n";
    cerr << "\n\n";
    exit (0);
}

void checkParams(Parameters& params)
{
    if ((params.size() < 2)
        ||
           !(strcmp(params.front(), "?"))
        ||
           !(strcmp(params.front(), "-?"))
        || 
           !(strcmp(params.front(), "-h"))
        ||
           !(strcmp(params.front(), "-help"))
        ||
           !(strcmp(params.front(), "--help"))
       ) dumpHelp();

	log = false;
	inputs.clear();
	output.clear();
	resultats.evalType() = HtmlResultats::Directe;
    uconfigFile = params.front(); params.erase(params.begin());
    rconfigFile = params.front(); params.erase(params.begin());

    for (Parameters::const_iterator currentArg = params.begin(); currentArg != params.end(); ++currentArg) {
        if (!strcmp(*currentArg, "-out")) { if (++currentArg != params.end()) output = *currentArg; continue; }
        if (!strcmp(*currentArg, "-rel")) { resultats.evalType() = HtmlResultats::Relative; continue; }
        if (!strcmp(*currentArg, "-abs")) { resultats.evalType() = HtmlResultats::Directe; continue; }
        if (!strcmp(*currentArg, "-log")) { log = true; continue; }
        inputs.push_back(conform_string(*currentArg));
    }
}

SingleRes elementaryCmp(const ContractedTag& reftag, const ContractedTag& systag)
{
#ifdef SPECIFIC
	bool n1, n2;
	if ((n1 = (reftag == *neutralizedTag)) || (n2 = (systag == *neutralizedTag))) return (n1 && n2 ? NonEval : Err); 
#endif
	if (reftag.empty() || systag.empty()) return NonEval;
	if (reftag.segmentation == systag.segmentation) return ((reftag == systag) ? Ok : Err);
	return NonEval;
}
	
void evaluate(ostream& os, const HtmlToken& ref, const HtmlToken& sys)
{
	// (miss) Il faut specialiser la procédure en fonction du type d'évaluation...
	// Une fois que la fct sera totalement validee, il faudra replacer le mapping apres les checks de base.
	
    HtmlRes resCase;
    HtmlTranscoded transcoded;
    Real pok;

    // PATCH temporaire:
//    if (sys.tag() == "?ERRALIGN/0.0") { sys.tag()=rconfig.unalignedTag; ref.tag()=rconfig.unalignedTag; }
//    else if (sys.tag() == "ERRALIGN/0.0") { sys.tag()=rconfig.unalignedTag; ref.tag()=rconfig.unalignedTag; }
//    else if (sys.tag() == "?ERRALIGN") { sys.tag()=rconfig.unalignedTag; ref.tag()=rconfig.unalignedTag; }
//    else if (ref.tag() == "?ERRALIGN/0.0") { ref.tag()=rconfig.unalignedTag; }
//    else if (ref.tag() == "ERRALIGN/0.0") { ref.tag()=rconfig.unalignedTag; }
//    else if (ref.tag() == "?ERRALIGN") { ref.tag()=rconfig.unalignedTag; }
	// Fin patch

    ++resultats.nbCases(); 
    if (ref.tag() == rconfig.unalignedTag)
    {
        resCase.res = NonEval;
        ++resultats.wrongAlign();
//	    resCase.write(os); return;
    }
    else if (ref.tag().find(rconfig.irrelevantTag) == 0)
    {
        resCase.res = NonEval;
        ++resultats.unevaluable();
//	    resCase.write(os); return; 
    } 
    else
    {
    	// Mapping direct.
	   	AlternatedTagsSet _ref((resultats.evalType() == HtmlResultats::Directe) ?
   		                        tagsTable.mapPivotToRef(BasicTag(ref.tag()))
   	    	                    : tagsTable.mapPivotToSys(BasicTag(ref.tag())));
   	                         
   		AlternatedTagsSet _sys((resultats.evalType() == HtmlResultats::Directe) ?
                                tagsTable.mapSysToRef(BasicTag(sys.tag()))
                                : AlternatedTagsSet(sys.tag()));  // copy cstr inutil !

		// Reste a examiner le cas du 'unknownTag' cote participant...  
		// Attention, unknownTag est moins prioritaire que l'impossibilite de mapper la reference,
		// et depend de la segmentation.

//		if (sys.tag().find(uconfig.unknownTag)==0){
//			cout << ">> Unknown: '" << _ref << "' -> '" << _sys << "' ";
//			if (_ref.empty()) cout << " Ref empty ";
//			else cout << " Ref not empty!!! ";
//		}
#ifdef SPECIFIC
		if (!_ref.empty() && (_ref.find(*neutralizedTag) == _ref.end()) && (sys.tag().find(uconfig.unknownTag) == 0))
		// Traitement a rectifier:
		// ^ -> ? donnera ERR (car 1 comp. erronee) au lieu de SIL, Perr = 1
		// (a moins qu'il n'y ait qu'une etiquette non neutralisee -> Err, ou aucune -> Nev).
		// Attention, il faut aussi etre sensible a la segmentation.
		// (Le traitement doit etre integrer dans la clause 'vraie' du 'if', avec qqch de
		// specifique pour l'ev. specifique.
#else
		if (!_ref.empty() && (sys.tag().find(uconfig.unknownTag) == 0))
#endif		
	    {
//	        cout << " spec trait...\n";
	    	//
	    	// Les contraintes sur le format du tag 'unknown' (c.f. compose uniquement avec lui-meme +
	    	// reduction des doublons) assure qu'il n'y a au maximum qu'un element par type de segmentation.
	    	// Des lors, la cardinalite de l'ensemble (nombre de tags atomique) donne le nombre de
	    	// segmentations differentes.
	    	// 
	    	if (resultats.evalType() == HtmlResultats::Directe) transcoded.val() = sys.tag(); // A optimiser.
	    	_sys = AlternatedTagsSet(sys.tag());
	    	Natural nbMatching(0); // Nombre de tag 'reference' ayant une segmentation compatible
	    	for (AlternatedTagsSet::const_iterator alt_ref = _ref.begin(); alt_ref != _ref.end(); ++alt_ref)
		        for (AlternatedTagsSet::const_iterator alt_sys = _sys.begin(); alt_sys != _sys.end(); ++alt_sys)
					if (alt_ref->segmentation == alt_sys->segmentation)
					{
						++nbMatching;
						break; // Il n'y a plus de _sysTag avec la meme segmentation... donc...
					}
			if (nbMatching)
			{
				Natural systemEquivalentTagsNb(_sys.size()*tagsTable.sizeRef());
				resCase.res = Sil;
				++resultats.silences();
				if (nbMatching == systemEquivalentTagsNb) ++resultats.silencesOk();
				pok = static_cast<float>(nbMatching)/static_cast<float>(systemEquivalentTagsNb);
	            resultats.averageSilOk() += pok;
    	        resultats.averageSilErr() += 1.0 - pok;
            } else {
            	resCase.res = NonEval;
            	++resultats.unevaluable();
            }
//          resCase.write(os); return;
   	    } else {
// 	    	cout << " nrm trait... \n";
	    	transcoded.val() = ((resultats.evalType() == HtmlResultats::Directe) ? _sys.str() : _ref.str());
	    	// Evaluation par tableau:
    		typedef vector<SingleRes> Line;
	    	Line valeurColonne;
	    	for (Natural i = _sys.size(); i > 0; --i) valeurColonne.push_back(NonEval);
	    	resCase.res = NonEval;

	    	for (AlternatedTagsSet::const_iterator alt_ref = _ref.begin(); alt_ref != _ref.end(); ++alt_ref)
	    	{
	            // Traitement du sous cas (_ref[i], sys):

	            SingleRes resSubCase(NonEval);
				Natural j(0);
		        for (AlternatedTagsSet::const_iterator alt_sys = _sys.begin(); alt_sys != _sys.end(); ++alt_sys, ++j)
		        {
	                // Comparaison élémentaire (ref[i], sys[j]), et mise à jour
	                // de la valeur calculée sur les colonnes (pour les cas silencieux)

		            switch (elementaryCmp(*alt_ref, *alt_sys))
		            {
	                	case NonEval : break;
		                case Ok      : resSubCase = ( (resSubCase != NonEval) ? Sil : Ok);
	                               	   valeurColonne[j] = Ok;
	                                   break;

						case Sil     : assert(false); // THROW ...
	                                   break;

						case Err     : resSubCase = ( (resSubCase != NonEval) ? Sil : Err);
	                                   if (valeurColonne[j] != Ok) valeurColonne[j] = Err;
	                                   break;
	                    default      : assert(false); // THROW ...
					}
				}
				switch (resSubCase) 
				{
	            	case NonEval : break;
	            	case Ok      : resCase.res = Ok; break;
	            	case Sil     : if (resCase.res != Ok) resCase.res = Sil; break;
	                case Err     : if (resCase.res == NonEval) resCase.res = Err; break;
	                default      : assert(false); break; // THROW ...
				}
			}


			Natural sumOk(0);  // Evaluation de la probabilité dans le cas d'un silence
			Natural sumErr(0);
	    
	    	switch (resCase.res)
	    	{
	        	case NonEval : ++resultats.unevaluable();
	                           break;

	            case Ok      : ++resultats.oks();
	                           break;

	            case Sil     : {
	            			   ++resultats.silences();
		                       // Rest à déterminer la probabilité de choix Ok/Err sous l'hyp. d'équiprob:
	    	                   // PSilOk = nbOk / (nbOk + nbErr)
	        	               // PSilErr = 1 - PSilOk  (on ne tiens pas compte des cas NEV) 

	                           for (Line::const_iterator i = valeurColonne.begin(); i != valeurColonne.end(); ++i)
	                           switch(*i)
	                           {
	                               case NonEval : break; // ignoré
	                               case Ok      : ++sumOk; break;
	                               case Sil     : assert(false); break; // THROW ...
	                               case Err     : ++sumErr; break;
	                               default      : assert(false); break; // THROW
	                           }
	                           if (!sumOk) ++resultats.silencesErr();
		                       if (!sumErr) ++resultats.silencesOk();
	 	                       pok = static_cast<float>(sumOk)/static_cast<float>(sumErr+sumOk);
		                       resultats.averageSilOk() += pok;
		                       resultats.averageSilErr() += 1.0 - pok;
	                       	   break;
	                       	   }
	                       
	            case Err     : ++resultats.errs();
	                           break;
	        }
        }
    }

/*    
    os.setf(ios::fixed, ios::floatfield);
    switch (resCase.res) {        
        case NonEval : os << "NEV:"; break;
        case Ok      : os << "OK :"; break;
        case Sil     : os << "SIL(" << setprecision(2) << pok << ',' << setprecision(2) << (1.0-pok) << "):"; break;
        case Err     : os << "ERR:"; break;
    }
	os.setf(0, ios::floatfield);
    os << '[' << resultats.nbCases() << "(" << resultats.unevaluable() << ") " << resultats.oks() << ",(";
    os.setf(ios::fixed, ios::floatfield);
	os << setprecision(2) << resultats.averageSilOk() << ',' << setprecision(2) << resultats.averageSilErr();
	os.setf(0, ios::floatfield);
	os << ")," << resultats.errs() << "]" << setfill('0') << setw(6) << ref.pos() << ' ' << sys.tok << " "
       << ref.tag() << " " << _sys.str() << " " << sys.tag() << endl;                                 

*/    
    resCase.write(os);
    os << endl;   // (Temporaire)
//    if (resCase.res == Sil) 
//        os << '(' << pok << ',' << (1.0-pok) << ',' << resultats.averageSilOk() << ',' << resultats.averageSilErr() << ')';
    transcoded.write(os);
    os << endl; // (temporaire)

// Temporaire: human readeable format...
    if (log)
    {
        ostringstream s;
        s << "  SIL(" << (Natural)(100*pok) << ',' << (Natural)(100*(1.0-pok)) << ')';
    	cerr.setf(ios::right, ios::adjustfield);
    	cerr << setfill(' ') << setw(7) << resultats.nbCases();
        cerr.setf(ios::left, ios::adjustfield);
	    switch (resCase.res) 
		{
        	case NonEval : cerr << setfill(' ') << setw(14) << "  NEV"; break;
        	case Ok      : cerr << setfill(' ') << setw(14) << "  OK"; break;
        	case Sil     : cerr << s.str() << "  "; if (s.str().size() == 11) cerr << ' '; break;
        	case Err     : cerr << setfill(' ') << setw(14) << "  ERR"; break;
        }
        cerr << ref.tok;
        for (Natural i(ref.tok.size()); i<22; ++i) cerr << ' ';
        cerr << ref.tag();
        for (Natural i(ref.tag().size()); i<32; ++i) cerr << ' ';
        cerr << sys.tag() << endl;
    }
}


void
computing(istream& in, ostream& os)
{
	HtmlToken ref;
	HtmlField* next;
	while (in) {
		// Read <CASE>
		while ( (next = HtmlField::nextTag(in)) != &caseId) {
			if (!next) return;
			if (next == &comment) { comment.HtmlField::read(in); comment.write(os); }
		}
		caseId.read(in);
		--caseId.caseNo();  // Compli. Martin.

		// Read ref <TOK>
		while ( (next = HtmlField::nextTag(in)) != &token) {
			if (!next) return;
			if (next == &comment) { comment.HtmlField::read(in); comment.write(os); }
		}
		ref.read(in);

		// Read sys <TOK>		
		while ( (next = HtmlField::nextTag(in)) != &token) {
			if (!next) return;
			if (next == &comment) { comment.HtmlField::read(in); comment.write(os); }
		}
		token.read(in);
		if (!in) return;
		caseId.write(os);
		os << endl; // (temp)
		evaluate(os, ref, token);
		ref.write(os);
		os << endl; //(Temp)
		token.write(os);
		os << '\n';
	}
}
// Temp: kappa test with martin.
void
printFinal(ostream& os, const HtmlResultats& allresult, const char*const head)
{
	os << head << "total case_nb (=err + ok + sil + noneval): " << allresult.nbCases() << '\n'
	   << head << "case_nb (=err + ok + sil): " << (allresult.errs() + allresult.oks() + allresult.silences()) << '\n'
	   << head << "noneval: " << allresult.unevaluable()+allresult.wrongAlign() << " (" << allresult.unevaluable() << '+'
	                   << allresult.wrongAlign() << ")\n"
	   << head << "noneval_ALIGN: " << allresult.wrongAlign() << '\n'
	   << head << "ok: " << allresult.oks() << '\n'
	   << head << "error: " << allresult.errs() << '\n'
	   << head << "sil: " << allresult.silences() << '\n';
	os.setf(ios::fixed, ios::floatfield);
	os << head << "silok_prb: " << setprecision(2) << allresult.averageSilOk() << '\n'
	   << head << "silerr_prb: " << setprecision(2) << allresult.averageSilErr() << '\n';
	os.setf(0, ios::floatfield);	   
	os << head << "sil_ok_case_nb: " << allresult.silencesOk() << '\n'
	   << head << "sil_err_case_nb: " << allresult.silencesErr() << '\n'
	   << head << "sil_sil_case_nb: " << (allresult.silences() - (allresult.silencesOk() + allresult.silencesErr())) << '\n';
	os.setf(ios::fixed, ios::floatfield);
	os << head << "score( P, D ): (" << setprecision(6) << allresult.precision() << " , "  << allresult.decision() << ")\n"
	   << head << "intervalle[Pmin, Pmoy, Pmax]: [" << allresult.minPrecision() << " , "
	                                                << allresult.averagePrecision() << " , "
	                                                << allresult.maxPrecision() << " ]\n";
	os.setf(0, ios::floatfield);	   
}	                                        

inline void freeMemory() 
{
	if (tagsTable.buffersEmpty()) { cerr << "\nERROR: Unable to allocate memory !\n"; exit(EXIT_FAILURE); }
	tagsTable.clearSavedMapping();
}


int main(int argc, char* argv[]) {
	set_new_handler(freeMemory);
    Parameters params(argc, argv);
    checkParams(params);
    if (!rconfig.readFrom(rconfigFile, UserConfiguration::Reference, false))
        msg.seriousError("Evaluater", "Reference configuration file is not readeable, or it has too serious error(s)");
    if (!uconfig.readFrom(uconfigFile, UserConfiguration::System, false))
        msg.seriousError("Evaluater", "System configuration file is not readeable, or it has too serious error(s)");
	if (! tagsTable.readReference(rconfig, rconfig.mapName, rconfig.mapMode))
        msg.seriousError("Evaluater", "Mapping table file is not readeable, or it has too serious error(s)");
	if (! tagsTable.readSystem(uconfig, uconfig.mapName, uconfig.mapMode))
        msg.seriousError("Evaluater", "Mapping table file is not readeable, or it has too serious error(s)");

	if (resultats.evalType() == HtmlResultats::Directe) neutralizedTag = &tagsTable.referenceNeutralTag;
	else neutralizedTag = &tagsTable.systemNeutralTag;

	HtmlField::defineHtmlTag(comment);
	HtmlField::defineHtmlTag(token);
	HtmlField::defineHtmlTag(caseId);

	ostream*  os;
	ofstream* ofs(0);

	if (output.empty()) os = &cout;
	else os = ofs = new ofstream(output.c_str(), ios::out|ios::trunc);

	// (miss) Write Header (comment with time stamp and other 'admin.' informations....
	if (log) cerr << "\nEvaluation " << (resultats.evalType() == HtmlResultats::Directe ? " absolue" : " relative")
	              << " de: " << uconfig.systemName << "\n\n"
	              << "CASEnb   RESULT      TOKEN                 REF_TAG                         SYS_TAG\n"
	              << "--------------------------------------------------------------------------------------------------\n";
	                  
	
//	(*os) << "#"; // tmp
	HtmlSystem(uconfig.systemName.substr(0,3), uconfig.systemName).write(*os);
	(*os) << '\n'; // tmp

	if (inputs.empty()) computing(cin, *os);
	else for (StringsCollection::const_iterator i = inputs.begin(); i != inputs.end(); ++i)
	{
		ifstream in(i->c_str(), ios::in);
		if (in) computing(in, *os);
		else msg.message(string("Input file '")+*i+string("' not found !"), STANDARD_MSG);
		in.close();
	}
    resultats.computeScore();
//  resultats.dump(cout);
//    (*os) << "#@";
    resultats.write(*os);
    (*os) << '\n';
//	printFinal(*os, resultats, "# # ");
    if (ofs) { ofs->close(); delete ofs; }
}

