/*----------------------------------------------------------------*
 *
 * File : simulation.c
 * Author : NTM
 * Created : 30/09/03
 *
 *
 * Copyright (C) Nicolas Thierry-Mieg, 2006.
 *
 *
 * This file is part of InterPool, written by 
 * Nicolas Thierry-Mieg (CNRS, France) Nicolas.Thierry-Mieg@imag.fr
 *
 * InterPool is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * InterPool 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
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with InterPool; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 *
 *-----------------------------------------------------------------*/

#include <stdlib.h> /* free, bsearch, exit, qsort, atoi, strtod */
#include <string.h> /* memset */
#include <stdio.h> /* printf and friends */

#include "config.h" /* USING_STD */
#include "types.h" /* MOT */
#include "distance.h" /* DIST_XXX */
#include "jobs.h" /* definition of JobIdentSim datatype */
#include "pools.h" /* STD, getpoolvalue */
#include "varia.h" /* simulateVars */
#include "signa.h" /* setOfSigs, freeSetOfSigs */
#include "signaIO.h" /* writeSig */
#include "fonc.h" /* compInts (for bsearch) */
#include"STD.h" /* optimalq */
#include "myrand.h" /* plantSeed */
#include "simulObservation.h" /* buildObservation, distToNoiseless, falseXXX */
#include "solvexpNaive.h" /* solvexpNaive */
#include "solvexpClosure.h" /* solvexpClosure */
#include "sigsToVVs.h" /* buildDeducedVV */

#include "simulation.h"



/* define DEBUG to produce additional output, including saving
   the generated sigs to files */
#undef DEBUG
//#define DEBUG /* to produce additional output */



/************************************************************************
 ******************* LOCAL FUNCTIONS ************************************
 ************************************************************************/

/********************** DECLARATIONS ************************************/

/*!
  \brief Compare the real simulated posvars to the merged deduced VV 
  corresponding to the coherentSigs (which MUST BE coherent!).
  Output to outStream (which must be already open for writing) a summary 
  of the results.
  \return Total number of wrongly tagged vars (ie either 
  a pos var marked 00 in the merged-deduced-VV, or a neg var marked 11).
*/
static int analyzeRes(int* posvars, setOfSigs coherentSigs, MOT* tabpool, 
		      int n, FILE* outStream) ;


/************************ BODIES ***************************************/

/*!
  Compare the real simulated posvars to the merged deduced VV 
  corresponding to the coherentSigs (which MUST BE coherent!).
  Output to outStream (which must be already open for writing) a summary 
  of the results.
  \return Total number of wrongly tagged vars (ie either 
  a pos var marked 00 in the merged-deduced-VV, or a neg var marked 11).
  special case: return value -1 means no solutions were found. When using
  the solvexpNaive method, consider increasing PROFMAX (in solvexpNaive.c)... 
  or using another solvexp method!
*/
static int analyzeRes(int* posvars, setOfSigs coherentSigs, MOT* tabpool, 
		      int n, FILE* outStream)
{
  int nbOfSigs = coherentSigs.nbOfSigs ;
  int distance = coherentSigs.distance ;

  MOT* mergedDeducedVV = buildDeducedVV(coherentSigs, tabpool, n) ;


  if (mergedDeducedVV == NULL)
    { /* no solutions were found, nothing to analyze */
      fprintf(outStream, "NO solutions found upto distance %d\n\n", distance-1) ;

      if (nbOfSigs != 0) // sanity check
	{
	  fprintf(stderr, 
		  "in analyzeRes, mergedDeducedVV is NULL but nbOfSigs is %d (should be 0)! DEBUG ME!\n",
		  nbOfSigs) ;
	  exit(1) ;
	}

      return -1 ;
    }

  /* else, do all the following work */
  else
    {
      fprintf(outStream, "%d solutions found, at distance %d\n", nbOfSigs, distance) ;

      int foundpos = 0 ; /* number of posvars correctly found */
      int foundneg = 0 ; /* number of negvars correctly found */
      int foundambi = 0 ; /* number of ambis obtained */
      int wrongpos = 0 ; /* number of wrongly tagged pos vars */
      int wrongneg = 0 ; /* number of wrongly tagged neg vars */

      int nbPosVars = posvars[0] ; /* number of pos vars (stored at [0], see varia.h) */

#ifdef DEBUG
      fprintf(outStream, "number of positive vars: %d\n", nbPosVars) ;
#endif /* DEBUG*/

      /* first, count mistaggings as neg (ie nb of posvars tagged 00 in mergedDeducedVV) */
      {
	int i ;
	for (i=1; i<=nbPosVars; i++)
	  {
	    int currentPosvar = posvars[i] ;
	    if (getpoolvalue(mergedDeducedVV,currentPosvar) == 0)
	      {
#ifdef DEBUG
		fprintf(outStream, "variable %d is positive, but tagged 00 in mergedDeducedVV\n", 
			currentPosvar) ;
#endif /* DEBUG*/
		wrongpos++ ;
	      }
	  }
      }

      /* then examine the value of each variable in mergedDeducedVV, and do:
	 - if varvalue==11, verify that var is pos (else increment wrongneg) 
	   and increment foundpos;
	 - else, if varvalue==00 increment foundneg (the check that var is not pos has
	   been done in the first step, for efficiency since most variables are negative);
	 - else (varvalue==01), increment foundambi.
      */

      posvars[0] = -1 ; /* dummy value for nbPosVars, so the array is sorted */
      /* (required for bsearch) and nbPosVars is never matched to var */

      {
	int var ; /* loop var */
	for (var=0; var<n; var++)
	  {
	    int varvalue = getpoolvalue(mergedDeducedVV, var) ;
	    if (varvalue==11)
	      { /* is var truly positive? */
		/* exploit the fact that posvars is sorted: use bsearch */
		if (bsearch(&var, posvars, nbPosVars+1, sizeof(int), compInts) == NULL)
		  { /* ==NULL <=> var is NOT positive */
#ifdef DEBUG
		    fprintf(outStream, "variable %d is negative, but tagged 11 in mergedDeducedVV\n", var) ;
#endif /* DEBUG*/
		    wrongneg++ ;
		  }
		else
		  {
		    foundpos++ ;
		  }
		
	      }
	    else if (varvalue==0)
	      { /* the number of falsely neg-tagged vars has been previously calculated, */
		/* we will substract it from foundneg at the end */
		/* right now, just assume any neg-tagged var is truly neg */
		foundneg++ ;
	      }
	    else /* varvalue == 01 */
	      {
		foundambi++ ;
	      }
	  }
      }

      posvars[0] = nbPosVars ; /* reset posvars[0] to the correct value */
      foundneg -= wrongpos ; /* substract falsely neg-tagged vars from foundneg */
      
      /* output summary of founds */
      fprintf(outStream, "correctly identified %d positive vars\n", foundpos) ;
      if (wrongpos!=0)
	fprintf(outStream, "wrongly tagged as negatives %d positive vars\n", wrongpos) ;
      fprintf(outStream, "correctly identified %d negative vars\n", foundneg) ;
      if (wrongneg!=0)
	fprintf(outStream, "wrongly tagged as positives %d negative vars\n", wrongneg) ;
      fprintf(outStream, "%d vars are ambiguous\n", foundambi) ;
      fprintf(outStream, "\n") ; /* final newline */
      /* discard mergedDeducedVV and return */
      free(mergedDeducedVV) ;
      return(wrongneg+wrongpos) ;
    }
}


/************************************************************************
 ******************* EXPORTED FUNCTIONS *********************************
 ************************************************************************/




/*
  simulation: Perform a batch of simulations as specified in thisJob, 
  and output results.
  One simulation is defined as:
  - 1. build the pools
  - 2. build a simulated VV
  - 3. generate the corresponding signature, possibly with noise
  - 4. solve that signature (and therefore obtain a set of  nearest 
     coherent signatures)
  - 5. build the (merged) deduced VV corresponding to this setOfSigs
  - 6. compare the simulated VV with the deduced VV, and output a summary
     of the comparison results
  
  Most parameters are now (as of 30/06/04) stored in a JobIdentSim structure,
  defined in jobs.h <br>
  NOTE: this function is charged with opening and closing an output stream,
  using the outFileName stored in thisJob.

  \param thisJob: holds all info relevant to current simulation job.
  \param mode: choose what solver you want to use. Current modes are:
         - 1: use solvexpNaive
	 - 2: use solvexpClosure (with method==1, ie findBestClosuresSim with Rec)
	 - 3: use solvexpClosure (with method==2, ie findBestClosuresSim with RecSubstracted)
	 - 4: use solvexpClosure (with method==3, ie findBestClosuresReal with Rec)
	 - 5: use solvexpClosure (with method==4, ie findBestClosuresReal with RecSubstracted)
*/
void simulation(JobIdentSim *thisJob, int mode)
{

  // check that mode is valid
  if ((mode < 1) || (mode > 5))
    {
      fprintf(stderr, "simulation called with unsupported mode %d! Exiting now\n", mode) ;
      exit(1) ;
    }

  int n = thisJob->n ;
  int nbPools = thisJob->nbPools ;
  char *designFile = thisJob->designFileName ;
  int nbPosVars = thisJob->nbPosVars ;
  int falsePos = thisJob->falsePos ;
  int falseNeg = thisJob->falseNeg ;
  int nsim = thisJob->nsim ;
  char *randomGenMethod = thisJob->randomGenMethod ;
  unsigned int seed = thisJob->seed ;
  char *outFileName = thisJob->outFileName ;

  /* create an output stream for writing */
  FILE *outStream ;

  /* OUTDIR is made in doSimulation.c. For parallel version
     we might have to move it here? */

  /* make sure the output file doesn't already exist */
  outStream = fopen(outFileName, "r");
  if (outStream != NULL)
    {
      fprintf(stderr,"In simulation: cannot write to %s, file already exists\n", outFileName);
      exit(1);
    }

  /* open stream for writing */
  outStream = fopen(outFileName, "w");
  /* for performance evaluation (timings), redirect output to /dev/null */
  /* outStream = fopen("/dev/null", "w"); */
  if (outStream == NULL)
    {
      fprintf(stderr,"In simulation: cannot open %s for writing\n", outFileName);
      exit(1);
    }
  
  /* header of output file: print common information */
  /* number of errors generated */
  int falseS = falseStrong(falsePos) ;
  int falseW = falseWeak(falsePos) ;
  int falseN = falseNone(falseNeg) ;
  int falseF = falseFaint(falseNeg) ;

#ifdef USING_STD
  int qopt = optimalq(n, nbPosVars) ;
  fprintf(outStream, "optimal q is %d\n", qopt) ;
#endif /* USING_STD */

  fprintf(outStream, "using design file=%s\n", designFile) ;
  fprintf(outStream, "nb of posVars=%d, nb of falseStrong=%d, falseWeak=%d,",nbPosVars,falseS,falseW);
  fprintf(outStream, " nb of falseFaint=%d, falseNone=%d\n",falseF,falseN);
  fprintf(outStream, "using costs: NEG==%d, FAINT==%d, WEAK==%d, POS==%d\n\n",
	  DIST_NEG, DIST_FAINT, DIST_WEAK, DIST_POS) ;
  fprintf(outStream, "performing %d simulations\n", nsim) ;
  fprintf(outStream, "using as random generator: %s, with seed: %u\n", randomGenMethod, seed) ;
  fprintf(outStream, "\n") ; /* final newline: end of header */


  /* plant seed */
  plantSeed(seed) ;
    
  {
    /* build pools */
    MOT* tabpool =  buildPools(designFile, n, nbPools) ;

    int i ; /* loop var */
    for (i=0; i<nsim; i++)
      {
#ifdef DEBUG
	fprintf(outStream, "starting sim number %d\n", i+1) ;
#endif /*DEBUG*/
	
	//build a simulated VV
	int* posvars = simulateVars(n, nbPosVars) ;
	
	//build the corresponding noisy observation
	signature* mySig = buildObservation(tabpool, n, nbPools,
					    posvars, falsePos, falseNeg) ;
	

#ifdef DEBUG
	/* print the sig in a file */
	char outFileNameSig[OUTFILELENGTH] ;
	int numchars = snprintf(outFileNameSig,OUTFILELENGTH,"%s.%i", outFileName, i);
	// make sure file name was not too long
	if ((numchars < 0) || (numchars >= OUTFILELENGTH))
	  {
	    fprintf(stderr, "in simulation, error building outFileNameSig: too long?\n");
	    exit(1);
	  }
	writeSig(outFileNameSig, mySig);
#endif /* DEBUG */

	setOfSigs deducedSetOfSigs ;

	if (mode==1)
	  {
	    deducedSetOfSigs = solvexpNaive(tabpool, mySig, n) ;
	  }

	else if ((mode>=2) && (mode<=5))
	  {
	    int method = mode - 1 ;
	    /* maxDist is only used for modes 2 and 3, solvexpClosure discards it
	       if mode==4 or 5 */
	    int maxDist = distToNoiseless(falsePos, falseNeg) ;
	    deducedSetOfSigs = solvexpClosure(tabpool, mySig, maxDist, method, n) ;
	  }

	else
	  {
	    /* shouldn't happen, valid modes are checked above */
	    fprintf(stderr, "in simulation: mode is invalid, but was NOT checked at the beginning! FIX ME!\n") ;
	    exit(1) ;
	  }
	
	int wrong = analyzeRes(posvars, deducedSetOfSigs, tabpool, n, outStream) ;
	if (wrong != 0)
	  {
	    /* perhaps stop this batch if simms? */
	    /* or maybe wait til this happens a few times? */
	    /* right now do nothing */
	    ;
	  }
	
	freeSig(mySig) ; /* mySig no longer needed, free it */
	free(posvars) ;
	freeSetOfSigs(deducedSetOfSigs) ;
      }

    free(tabpool);
  }

  /* print file trailer (useful for parsing the file) */
  fprintf(outStream, "___DONE___\n") ;
  
  fclose(outStream) ;
}


