/**
 * Copyright INRIA , contributors Peterlongo
 * pierre.peterlongo@inria.fr
 *
 *
 * This software is a computer program whose purpose is to detect the
 * presence of a sequence in a set of NGS reads, and to compute its average quality and coverage
 *
 * This software is governed by the CeCILL license under French law and
 * abiding by the rules of distribution of free software.  You can  use,
 * modify and/ or redistribute the software under the terms of the CeCILL
 * license as circulated by CEA, CNRS and INRIA at the following URL
 * "http://www.cecill.info".
 
 * As a counterpart to the access to the source code and  rights to copy,
 * modify and redistribute granted by the license, users are provided only
 * with a limited warranty  and the software's author,  the holder of the
 * economic rights,  and the successive licensors  have only  limited
 * liability.
 
 * In this respect, the user's attention is drawn to the risks associated
 * with loading,  using,  modifying and/or developing or reproducing the
 * software by the user in light of its specific status of free software,
 * that may mean  that it is complicated to manipulate,  and  that  also
 * therefore means  that it is reserved for developers  and  experienced
 * professionals having in-depth computer knowledge. Users are therefore
 * encouraged to load and test the software's suitability as regards their
 * requirements in conditions enabling the security of their systems and/or
 * data to be ensured and,  more generally, to use and operate it in the
 * same conditions as regards security.
 *
 * The fact that you are presently reading this means that you have had
 * knowledge of the CeCILL license and that you accept its terms.
 */

/*
 *
 *  Created on: 28 oct. 2010
 *      Author: Pierre Peterlongo
 */


#include<fragment_index.h>
#include<list.h>
#include<commons.h>
#include<extension_algorithm.h>
#include<outputs.h>
#include<limits.h> // MAXINT
#include <unistd.h>  /* sleep(1) */
#include <string.h> // strdup
#include <time.h>
#include <libchash.h>
#include <iostream>
#include <vector>
#include <fstream>
#include <string>
#include <algorithm>

using namespace std;

#ifdef _OPENMP
#include <omp.h>
#endif

const char * getVersion(){
    return  "kissreads module 1.2.6 - Copyright INRIA - CeCILL License";
}


void print_usage_and_exit(char * name){
	fprintf (stderr, "NAME\nkissReads, version %s\n", getVersion());
#ifdef INPUT_FROM_KISSPLICE
	fprintf (stderr, "\nSYNOPSIS\n%s <toCheck.fasta> <readsC1.fasta> [<readsC2.fasta> [<readsC3.fasta] ...] [-k value] [-S value] [-c value] [-d value] [-O value] [-l value] [-o name] [-u name] [-i index_stride] [-m align_file] [-p] [-s] [-f] [-h] \n", name);
#else
    fprintf (stderr, "\nSYNOPSIS\n%s <toCheck.fasta> <readsC1.fasta> [<readsC2.fasta> [<readsC3.fasta] ...] [-S value] [-c value] [-d value] [-O value] [-o name] [-u name] [-n] [-I] [-i index_stride] [-m align_file] [-p] [-s] [-f] [-h] \n", name);
#endif// INPUT_FROM_KISSPLICE
	fprintf (stderr, "\nDESCRIPTION\n");
	fprintf (stderr, "Checks for each sequence contained into the toCheck.fasta if\n");
	fprintf (stderr, "it is read coherent (each position is covered by at least \"min_coverage\" read(s)) with reads from readsA.fasta or readsB.fasta\n");
	fprintf (stderr, "A sequence s from toCheck is treated as follow:\n");
	fprintf (stderr, "  if (s coherent with at least one read set): output the sequence as follows\n");
	fprintf (stderr, "  \t >original fasta comment|C1:min<avg-corr_avg<max|C2:min<avg-cor_avg<max|C3...:\n");
	fprintf (stderr, "  \t >s\n");
	fprintf (stderr, "  With A:min<avg-cor_avg<max standing for : value of the position having minimal coverage in readsA.fasta < average coverage in readsA.fasta - R-squarred corrected average in readsA.fa < value of the position having maximal coverage in readsA.fasta\n");
	fprintf (stderr, "  The coverage is the number of reads that perfectly mapped a position\n");
	fprintf (stderr, "  Any other situation (s not coherent with any): couple non read coherent, not outputed \n");
    
	fprintf (stderr, "\nOPTIONS\n");
#ifdef INPUT_FROM_KISSPLICE        
	fprintf (stderr, "\t -k k-mer size: this is the k-mer size used in KisSplice.\n");
#endif        
	fprintf (stderr, "\t -S size_seed: will use seeds of length size_seed. Default: 25.\n");
    fprintf (stderr, "\t -O minimal_read_overlap: a read can be mapped if its overlap is a least \"minimal_read_overlap\". Default: S\n");
	fprintf (stderr, "\t -c min_coverage: a sequence is covered by at least min_coverage coherent reads. Default: 2\n");
    fprintf (stderr, "\t -d max_substitutions: Maximal number of substitutions authorized between a read and a fragment. Note that no substitution is allowed on the central position while anaylizing the kissnp output. Default: 1.\n");
#ifdef INPUT_FROM_KISSPLICE
    fprintf (stderr, "\t -l min_kissplice_overlap: Kissplice min_voerlap. Default: 3\n");
    fprintf (stderr, "\t -j counting option. 0: counts are summed and represent the whole path, 1: counts are only in the junctions, 2: all the counts are shown separately   . Default: 0\n");
#endif
    fprintf (stderr, "\t -p: only print coverage, do not separate between coherent and uncoherent sequences. Not compatible with -u option.\n");
	fprintf (stderr, "\t -o file_name: write read-coherent outputs. Default: standard output \n");
	fprintf (stderr, "\t -u file_name: write unread-coherent outputs. Not compatible with -p option. Default: standard output \n");
#ifndef INPUT_FROM_KISSPLICE
	fprintf (stderr, "\t -n the input file (toCheck.fasta) is a kissnp output (incompatible with -I option) \n");
    fprintf (stderr, "\t\t in this case: 1/ only the upper characters are considered (no mapping done on the extensions) and 2/ the central position (where the SNP occurs) is strictly mapped, no subsitution is authorized on this position.\n");
	fprintf (stderr, "\t -I the input file (toCheck.fasta) is an Intl output (incompatible with -n option) \n");
#endif // not INPUT_FROM_KISSPLICE
    fprintf (stderr, "\t -i index_stride (int value). This is a heuristic for limiting the memory footprint. Instead of indexing each kmer of the sequences contained into the toCheck.fasta, kissreads indexes kmers occurring each \"index_stride\" position. Default = 1 (no heuristic)\n");
    fprintf (stderr, "\t -t max number of threads (also limited by number of input files)\n");
	fprintf (stderr, "\t -m align_file, write a file of reads mapped to sequences in file align_file\n");
	fprintf (stderr, "\t -s silent mode\n");
    fprintf (stderr, "\t -f outputs coherent events in a standard fasta file format\n");
	fprintf (stderr, "\t -h prints this message and exit\n");
	exit(0);
}


//strands comparators
bool reallyDifferentStrands(char strand1, char strand2) {
  if (strand1=='?' || strand2=='?')
    return false; //'?' == wildcard, cannot define if they are really different
  return strand1 != strand2;
}

bool reallyEqualStrands(char strand1, char strand2) {
  if (strand1=='?' || strand2=='?')
    return false; //'?' == wildcard, cannot define if they are really equal
  return strand1 == strand2;
}



int main(int argc, char **argv) {
#ifdef READ2INV
    printf("Compiled with READ2INV: output only motifs where au-vb is specific to one datasets and av'-u'b is specific to the other\n");
#endif
#ifdef CLASSICAL_SPANNING
    printf("Compiled with CLASSICAL_SPANNING: a position is considered as covered as soon as a read maps this position (else a position is considered as covered if the kmer starting at this position is fully covered by a read)\n");
#endif
#ifdef INPUT_FROM_KISSPLICE
    printf("Compiled with INPUT_FROM_KISSPLICE: dealing with kissplice output and to count separately junctions and central portions\n");
#endif
    
    
    minimal_read_overlap=0;
#ifndef INPUT_FROM_KISSPLICE
    char map_snps=0; // input is a kissnp output
#endif
    char map_invs=0; // input is an intl (read2sv) output.
    only_print=0; // if "only_print" == 1 do not separate between coherent and uncoherent.
    
    char no_subsutitution_on_central_position=0; // By default: authorize a subsitution on the central position
    char input_only_upper=0; // By default: all characters (upper or lower) are read
    int number_paths_per_event=1; // By default (generic usage) each event is composed by a unique sequence.
#ifdef INPUT_FROM_KISSPLICE
	kisspliceKmerSize = -1;
    min_overlap=3; // default value.
    countingOption = 0 ; // default value
    no_subsutitution_on_central_position=0; // just to be sure, as normaly this is the default behaviour
    input_only_upper=1; // don't considere the lower script characters.
    number_paths_per_event=2; // each splicing event is composed by two sequences in the fasta file.
#endif
    
    int index_stride =1;
	setvbuf(stdout,(char*)NULL,_IONBF,0); // disable buffering for printf()
	time_t before_all, after_all;
	before_all = time(NULL);
	size_seeds=25;
    
	int i;
	int nb_events_per_set;
    int max_substitutions=1;
    int max_threads = 1;
#ifdef _OPENMP
     max_threads =  omp_get_num_procs();
#endif
    //	size_before_reads_starting=16384;
	int min_coverage=2; // minimal number of reads per positions extending the initial fragment

  bool checkStrands=false;
  int strandAbsoluteThreshold;
  float strandRelativeThreshold;
    
	if(argc<2){
		print_usage_and_exit(argv[0]);
	}
    
	char * toCheck_file =strdup(argv[1]);
	FILE * coherent_out = stdout;
	FILE * uncoherent_out = stdout;
	FILE * sam_out = NULL;
	silent=0; standard_fasta=0;
    
    nbits_nbseeds = 8*sizeof(uint64_t)- NBITS_OFFSET_SEED ;
    mask_nbseed  = ( 1ULL <<  (uint64_t) nbits_nbseeds  ) -1 ;
    mask_offset_seed = (1ULL << (NBITS_OFFSET_SEED)) -1 ;
    
	// GET ALL THE READ FILES
	// find the number of read sets
	number_of_read_sets=0;
	while(number_of_read_sets+2<argc && argv[number_of_read_sets+2][0]!='-') number_of_read_sets++;
	char ** reads_file_names = (char **) malloc(sizeof(char *)*number_of_read_sets);
    const char * coherent_file_name="";
    const char * uncoherent_file_name="";
    const char * samout_file_name="";
    // copy the read set file names
	number_of_read_sets=0;
	while(number_of_read_sets+2<argc && argv[number_of_read_sets+2][0]!='-'){
		reads_file_names[number_of_read_sets]=strdup(argv[number_of_read_sets+2]);
		number_of_read_sets++;
	}
	while (1)
	{
#ifdef INPUT_FROM_KISSPLICE
        int temoin = getopt(argc - number_of_read_sets - 1, &argv[number_of_read_sets + 1], "a:r:c:d:k:S:O:o:u:q:m:i:xfps-:j:l:t:");
#else
        int temoin = getopt(argc - number_of_read_sets - 1, &argv[number_of_read_sets + 1], "c:d:S:O:o:u:q:m:i:fpsnI-:t:");
#endif //INPUT_FROM_KISSPLICE
		if (temoin == -1){
			break;
		}
		switch (temoin)
		{
            case 'x':
                checkStrands=true;
                printf("Kissreads stranded mode selected!");
                break;
            case 'a':
                strandAbsoluteThreshold=atoi(optarg);
                strandAbsoluteThreshold=max(0, strandAbsoluteThreshold);
                break;
            case 'r':
                strandRelativeThreshold=atof(optarg);
                strandRelativeThreshold=max(0.0f, strandRelativeThreshold);
                break;
            case 'c':
                min_coverage=atoi(optarg);
                if(min_coverage<1) min_coverage=1;
                break;
            case 'p':
                only_print=1;
                break;
#ifdef INPUT_FROM_KISSPLICE
            case 'k':
                kisspliceKmerSize = atoi(optarg);
                break;
            case 'l':
                min_overlap=atoi(optarg);
                break;
            case 'j':
              countingOption  = atoi(optarg);
              break;
#endif //INPUT_FROM_KISSPLICE
            case 'o':
                coherent_file_name = strdup(optarg);
                coherent_out = fopen(optarg, "w");
                if(coherent_out == NULL){
                    fprintf(stderr,"cannot open %s for writing results, exit\n", optarg);
                    exit(1);
                }
                break;
            case 'u':
                uncoherent_file_name = strdup(optarg);
		        uncoherent_out = fopen(optarg, "w");
                if(uncoherent_out == NULL){
                    fprintf(stderr,"cannot open %s for writing results, exit\n", optarg);
                    exit(1);
                }
                break;
            case 'd':
                max_substitutions=atoi(optarg);
                break;
            case 't':
                max_threads=atoi(optarg);
                break;
            case 'i':
                index_stride=atoi(optarg);
                if(index_stride<0) index_stride=1;
                break;
                
#ifndef INPUT_FROM_KISSPLICE
            case 'n':
                map_snps=1;
                no_subsutitution_on_central_position=1;
                input_only_upper=1;
                number_paths_per_event=2;
                
                break;
            case 'I':
                map_invs=1;
                no_subsutitution_on_central_position=0;
                input_only_upper=0; // read all characters for now (still no extension pluged to intl).
                number_paths_per_event=4;
                
                break;
#endif
                
            case 'O':
                minimal_read_overlap=atoi(optarg);
                break;
                
            case 'S':
                size_seeds=atoi(optarg);
                
                if(size_seeds<10){
                    fprintf(stderr,"%d too small to be used as seed length. kisSnpCheckReads continues with seeds of length 10\n",size_seeds);
                    size_seeds=10;
                }
                break;
            case 'h':
                print_usage_and_exit(argv[0]);
                break;
            case 'f':
                standard_fasta=1;
                break;
            case 's':
                silent=1;
                break;
            case 'm':
                samout_file_name = strdup(optarg);
                sam_out = fopen(optarg, "w");
                if(sam_out == NULL){
                    fprintf(stderr,"cannot open %s for writing sam like results, exit\n", optarg);
                    exit(1);
                }
                fprintf(sam_out, "event_header\tmapped_read\tid_read_file\tposition_where_read_is_mapped\tread_id\tAS\tSB\tASSB\tS\tAB\n");

                
                break;
                
            case '-':
                if(strcmp("version", optarg)==0){
                    printf("kissReads version %s\n", getVersion());
                    exit(0);
                }
                printf(" what next ? %s\n",optarg);
                break;
            default:
                print_usage_and_exit(argv[0]);
		}
	}
    
	if ( argc  - optind <2)
	{
		print_usage_and_exit(argv[0]);
	}
    
#ifndef INPUT_FROM_KISSPLICE
    if(map_snps && map_invs){
        fprintf(stderr, "cannot use both options -n and -I\n");
        exit(1);
    }
#endif
    
    
    if(size_seeds>32){
        fprintf(stderr,"Sorry kissreads does not accept k>32, exit\n");
        exit(1);
    }
    
    if(size_seeds>minimal_read_overlap) minimal_read_overlap=size_seeds;

    if (only_print) uncoherent_out = coherent_out;
    
#ifdef INPUT_FROM_KISSPLICE    
    if (kisspliceKmerSize == -1) {
        fprintf(stderr, "Sorry, you must specify -k parameter, exit\n");
        exit(1);
    }
#endif    
    
    if(!silent){
        printf("This is kissreads, version %s\n", getVersion());
        printf("COMMAND LINE:\n");
        for(i=0;i<argc;i++) printf("%s ",argv[i]);
        printf("\n");
        printf("PARAMETERS SUM UP:\n");
        printf("\tIN\n");
        printf("\t References file: %s\n", toCheck_file);
        printf("\t Queries file%s:",number_of_read_sets>1?"s":"");
        for (i=0;i<number_of_read_sets;i++)printf(" %s", reads_file_names[i]);
        printf(".\n");
#ifndef INPUT_FROM_KISSPLICE
        if(map_invs || map_snps){
            if(map_snps) printf("\t *(-n) Input file is considered as a kissnp output\n");
            if(map_invs) printf("\t *(-I) Input file is considered as a TakeABreak output\n");
        }
        else printf("\t *Generic input file\n");
#endif
#ifdef INPUT_FROM_KISSPLICE
        printf("\t *INPUT_FROM_KISSPLICE: Input file is considered as a Kissplice output\n");
#endif
        
        
        printf("\tMAPPING PARAMETERS\n");
#ifdef INPUT_FROM_KISSPLICE        
        printf("\t *(-k) k-mer size %d\n", kisspliceKmerSize);
#endif
        printf("\t *(-S) Size seeds %d\n", size_seeds);
        printf("\t *(-i) Index Stride %d (will index a seed each %d positions)\n", index_stride, index_stride);
        printf("\t *(-O) minimal_read_overlap %d (a read is mapped on a fragment if the overlap is at least %d positions)\n", minimal_read_overlap,minimal_read_overlap);
        printf("\t \t Note that minimal_read_overlap is at least equal to size_seeds\n");
        printf("\t *(-c) Minimal coverage %d\n", min_coverage);
#ifdef KMER_SPANNING
        printf("\t \t KMER_SPANNING: Each kmer(=%d) as defined by \"minimal_read_overlap\" spanning each position should be covered by at least %d reads.\n", minimal_read_overlap, min_coverage);
#else
        printf("\t \t CLASSICAL_SPANNING: Each position should be covered by at least %d reads.\n", min_coverage);
#endif
        printf("\t *(-d) Authorize at most %d substitutions during the mapping\n", max_substitutions);
#ifdef INPUT_FROM_KISSPLICE
        printf("\t *(-l) Kissplice count uses min_overlap %d\n", min_overlap);
        printf("\t *(-j) Kissplice countingOption %d\n", countingOption);
#endif
        
        
        printf("\tOUT\n");
        if (only_print) printf("\t print all results in file: %s\n", coherent_file_name);
        else{
            printf("\t *(-o) Read-coherent results file: %s\n", coherent_file_name);
            printf("\t *(-u) Read-uncoherent results file: %s\n", uncoherent_file_name);
        }
        printf("\t *(-f) Standard fasta output mode: %s\n",standard_fasta?"yes":"no");
        if(sam_out) printf("\t *(-m) Reads mapped results file: %s\n", samout_file_name);
        
        printf("\tMISC.\n");
        printf("\t *(-t) Max %d threads.\n", max_threads);
    }
    
	init_static_variables(size_seeds);
	
	gzFile * reads_files = (gzFile*) malloc(sizeof(gzFile)*number_of_read_sets);
	p_fragment_info * results_against_set;
    
	char found;
	// test if all formats are in fastq
	quality=1;
	for (i=0;i<number_of_read_sets;i++){
        quality &= strstr(reads_file_names[i],"fastq") || strstr(reads_file_names[i],"fq") || strstr(reads_file_names[i],"txt");
	}
	if(quality) quality=1;
    
    
	for (i=0;i<number_of_read_sets;i++){
        if (quality){
            found = strstr(reads_file_names[i],"fastq") || strstr(reads_file_names[i],"fq") || strstr(reads_file_names[i],"txt");
            if (!found ){		fprintf(stderr,"\nwith q option, fastq reads files are needed, wrong %s, exit\n",reads_file_names[i]);		exit(1);	}
        }
        else
	    {
            found = strstr(reads_file_names[i],"fastq") || strstr(reads_file_names[i],"fq") || strstr(reads_file_names[i],"txt") || strstr(reads_file_names[i],"fa") || strstr(reads_file_names[i],"fasta") || strstr(reads_file_names[i],"fna");
            if (!found ){		fprintf(stderr,"\naccepted extensions are .fa[.gz], .fasta[.gz], .fna[.gz] for fasta format and .fq[.gz], .fastq[.gz], .txt[.gz] for fastq format, wrong %s, exit\n",reads_file_names[i]);		exit(1);	}
	    }
	}
	
	file=gzopen(toCheck_file,"r");
	if(file == NULL){
        fprintf(stderr,"cannot open file %s, exit\n",toCheck_file);
        exit(1);
	}
	// how many fragments do we have (fragments are in fastq format)?
    char * line =  (char *)malloc(sizeof(char)*1048576); // 1048576
	number_of_starters = number_of_sequences_in_file(file,line);
    free(line);
    
    nb_events_per_set = (number_of_starters/number_paths_per_event);
    
    
    if(!silent){
        printf("Number of sequences in %s file: %d (%d events)\n", toCheck_file, number_of_starters, nb_events_per_set);
    }
    
    results_against_set = index_starters_from_input_file (size_seeds, nb_events_per_set, number_paths_per_event, input_only_upper, index_stride);
    
     int nbthreads = number_of_read_sets;
     if (nbthreads > max_threads) nbthreads = max_threads;
    
    if(!silent){
        printf("Mapping.\n");
    }

    int silented=0;
#ifdef _OPENMP
    if (!silent) silented=1;
    silent=1; // avoids melting messages
#endif
    

    
#pragma omp parallel for if(nbthreads>1 && sam_out==NULL) num_threads(nbthreads) private(i)
    for (i=0;i<number_of_read_sets;i++){
        reads_files[i]=gzopen(reads_file_names[i],"r");
        if(reads_files[i] == NULL){		fprintf(stderr,"cannot open reads file %s, exit\n",reads_file_names[i]);		exit(1);	}
        
        if(!silent) printf("\nCheck read coherence... vs reads from %s (set %d)\n", reads_file_names[i], i);
        
        
        
        read_coherence(reads_files[i], reads_file_names[i], size_seeds,  min_coverage, results_against_set,  number_of_starters, i, quality, nb_events_per_set,  number_paths_per_event, sam_out, max_substitutions, no_subsutitution_on_central_position, minimal_read_overlap);
        
        
        gzclose(reads_files[i]);
    }

    //new check: checking if the reads are coherent in a strand-specific-way
    //define the consensus strand of the mapped read for each fragment
    if (checkStrands) {
      string explanationsFilename = uncoherent_file_name + string(".explanations");
      ofstream explanations(explanationsFilename.c_str());

      for (int fragment = 0; fragment < number_of_starters; fragment++) {

        //DEBUG code
        //cout << "DEBUG" << endl;
        //cout << all_starters[fragment]->w << endl;
        //DEBUG code

        for (int readSet = 0; readSet < number_of_read_sets; readSet++) {
          //checks absolute values: the largest value must be >= ksStrandedAbsoluteThreshold
          int largestValue = max(all_starters[fragment]->nbOfReadsMappingInFW[readSet], all_starters[fragment]->nbOfReadsMappingInRC[readSet]);
          bool respectsAbsoluteThreshold = (largestValue >= strandAbsoluteThreshold);

          //checks relative values: largest value/(largest+smallest) must be >= ksStrandedRelativeThreshold
          int total = all_starters[fragment]->nbOfReadsMappingInFW[readSet]+all_starters[fragment]->nbOfReadsMappingInRC[readSet];
          bool respectsRelativeThreshold =
              (total == 0) ? false : //avoids division by 0
              (((double)(largestValue)/total) >= strandRelativeThreshold);

          //If any of the filters do not pass, the consensus is '?', which equals '+' or '-'
          if (!respectsAbsoluteThreshold || !respectsRelativeThreshold)
            all_starters[fragment]->consensusStrandOfMappedReads[readSet] = '?';
          //now, set the '+' or '-' strand
          else if (all_starters[fragment]->nbOfReadsMappingInFW[readSet] >=
              all_starters[fragment]->nbOfReadsMappingInRC[readSet])
            all_starters[fragment]->consensusStrandOfMappedReads[readSet] = '+';
          else
            all_starters[fragment]->consensusStrandOfMappedReads[readSet] = '-';

          //DEBUG code
          //cout << "Readset: " << readSet << " - consensus " << all_starters[fragment]->consensusStrandOfMappedReads[readSet] << endl;
          //DEBUG code
        }
      }


      //check if the bubble is strand-coherent
      //TODO: do this just in case of KisSplice only (INPUT_FROM_KISSPLICE), where we have always 2 fragments / event
      for (int event = 0; event < nb_events_per_set * 2; event += 2) {
        if (!one_coherent(results_against_set, event, number_of_read_sets) ||
            !one_coherent(results_against_set, event + 1, number_of_read_sets))
          continue; //it is already not coherent

        //check if the bubble is strand-coherent
        int keepGoing = 1;
        int upperPathIndex = event;
        int lowerPathIndex = event + 1;

        //1. check if the read files agree on the same strand in upper and lower path
        for (int readSet = 0; readSet < number_of_read_sets && keepGoing; readSet++) {
          /*
          if (all_starters[upperPathIndex]->consensusStrandOfMappedReads[readSet] !=
              all_starters[lowerPathIndex]->consensusStrandOfMappedReads[readSet]) {
          */
          if (reallyDifferentStrands(
              all_starters[upperPathIndex]->consensusStrandOfMappedReads[readSet],
              all_starters[lowerPathIndex]->consensusStrandOfMappedReads[readSet])) {
            //nope
            itIsUncoherent(all_starters[upperPathIndex], number_of_read_sets);
            itIsUncoherent(all_starters[lowerPathIndex], number_of_read_sets);

            //reason
            explanations << "Bubble (" << all_starters[upperPathIndex]->comment << ", " <<
            all_starters[lowerPathIndex]->comment << ") " <<
            "is strand-uncoherent because in read file " << readSet << " the consensus strand on the upper path is " <<
            getNiceDescriptionOfTheConsensusStrand(all_starters[upperPathIndex], readSet) <<
            ", but the consensus strand on the lower path is " <<
            getNiceDescriptionOfTheConsensusStrand(all_starters[lowerPathIndex], readSet) << endl;
            keepGoing = 0;
          }
        }
        if (!keepGoing) continue;

        //2. check if the mate-pairs of the read files agree on opposite strands in each fragment
        for (int fragment = upperPathIndex; fragment <= lowerPathIndex && keepGoing; fragment++) {
          for (int readSet = 0; readSet < number_of_read_sets; readSet += 2) {
            /*
            if (all_starters[upperPathIndex]->consensusStrandOfMappedReads[readSet] ==
                all_starters[lowerPathIndex]->consensusStrandOfMappedReads[readSet + 1]) {
            */
            if (reallyEqualStrands(
                all_starters[upperPathIndex]->consensusStrandOfMappedReads[readSet],
                all_starters[lowerPathIndex]->consensusStrandOfMappedReads[readSet + 1])) {
              //nope
              itIsUncoherent(all_starters[upperPathIndex], number_of_read_sets);
              itIsUncoherent(all_starters[lowerPathIndex], number_of_read_sets);

              //reason
              explanations << "Bubble (" << all_starters[upperPathIndex]->comment << ", " <<
              all_starters[lowerPathIndex]->comment << ") " <<
              "is strand-uncoherent because the " << (fragment == upperPathIndex ? "upper" : "lower") << " path's " <<
              "LEFT reads (read file " << readSet << ") consensus strand is " <<
              getNiceDescriptionOfTheConsensusStrand(all_starters[upperPathIndex], readSet) <<
              ", but the RIGHT reads (read file " << (readSet + 1) << ") consensus strand is " <<
              getNiceDescriptionOfTheConsensusStrand(all_starters[lowerPathIndex], readSet) << endl;
              keepGoing = 0;
              break;
            }
          }
        }
        if (!keepGoing) continue;

        //3. check if all left and right read files agree on the same strand
        vector<int> leftReadFilesAgreedOnFW;
        vector<int> leftReadFilesAgreedOnRC;
        vector<int> rightReadFilesAgreedOnFW;
        vector<int> rightReadFilesAgreedOnRC;
        for (int readSet = 0; readSet < number_of_read_sets; readSet++) {
          if (readSet%2==0) { //if we are looking at the left read set
            for (int fragment = upperPathIndex; fragment <= lowerPathIndex; fragment++) {
              if (all_starters[fragment]->consensusStrandOfMappedReads[readSet] == '+')
                leftReadFilesAgreedOnFW.push_back(readSet);
              if (all_starters[fragment]->consensusStrandOfMappedReads[readSet] == '-')
                leftReadFilesAgreedOnRC.push_back(readSet);
            }
          }else { //if we are looking at the right read set
            for (int fragment = upperPathIndex; fragment <= lowerPathIndex; fragment++) {
              if (all_starters[fragment]->consensusStrandOfMappedReads[readSet] == '+')
                rightReadFilesAgreedOnFW.push_back(readSet);
              if (all_starters[fragment]->consensusStrandOfMappedReads[readSet] == '-')
                rightReadFilesAgreedOnRC.push_back(readSet);
            }
          }
        }

        //3.1: check if all left read files agree on the same strand
        if (leftReadFilesAgreedOnFW.size() > 0 && leftReadFilesAgreedOnRC.size() > 0) {
          //nope
          itIsUncoherent(all_starters[upperPathIndex], number_of_read_sets);
          itIsUncoherent(all_starters[lowerPathIndex], number_of_read_sets);

          //reason
          explanations << "Bubble (" << all_starters[upperPathIndex]->comment << ", " <<
          all_starters[lowerPathIndex]->comment << ") " <<
          "is strand-uncoherent because the following LEFT read files: [";
          for (std::size_t i = 0; i < leftReadFilesAgreedOnFW.size(); i++)
            explanations << leftReadFilesAgreedOnFW[i] << ", ";
          explanations << "] say that the reads map on the + strand while the following LEFT read files: [";
          for (std::size_t i = 0; i < leftReadFilesAgreedOnRC.size(); i++)
            explanations << leftReadFilesAgreedOnRC[i] << ", ";
          explanations << "] say that the reads map on the - strand" << endl;
          keepGoing = 0;
        }
        if (!keepGoing) continue;

        //3.2: check if all right read files agree on the same strand
        if (rightReadFilesAgreedOnFW.size() > 0 && rightReadFilesAgreedOnRC.size() > 0) {
          //nope
          itIsUncoherent(all_starters[upperPathIndex], number_of_read_sets);
          itIsUncoherent(all_starters[lowerPathIndex], number_of_read_sets);

          //reason
          explanations << "Bubble (" << all_starters[upperPathIndex]->comment << ", " <<
          all_starters[lowerPathIndex]->comment << ") " <<
          "is strand-uncoherent because the following RIGHT read files: [";
          for (std::size_t i = 0; i < rightReadFilesAgreedOnFW.size(); i++)
            explanations << rightReadFilesAgreedOnFW[i] << ", ";
          explanations << "] say that the reads map on the + strand while the following RIGHT read files: [";
          for (std::size_t i = 0; i < rightReadFilesAgreedOnRC.size(); i++)
            explanations << rightReadFilesAgreedOnRC[i] << ", ";
          explanations << "] say that the reads map on the - strand" << endl;
          keepGoing = 0;
        }
        if (!keepGoing) continue;

        //3.3: check if everything is '?' - in this case, we say that the bubble is also uncoherent
        if (
            leftReadFilesAgreedOnFW.size() == 0 && leftReadFilesAgreedOnRC.size() == 0 &&
            rightReadFilesAgreedOnFW.size() == 0 && rightReadFilesAgreedOnRC.size() == 0) {
          //yeap
          itIsUncoherent(all_starters[upperPathIndex], number_of_read_sets);
          itIsUncoherent(all_starters[lowerPathIndex], number_of_read_sets);

          //reason
          explanations << "Bubble (" << all_starters[upperPathIndex]->comment << ", " <<
          all_starters[lowerPathIndex]->comment << ") " <<
          "is strand-uncoherent because there was not enough evidence to decide a strand " <<
          "(try lowering -strandedAbsoluteThreshold and/or -strandedRelativeThreshold if this was a false positive)." << endl;
          keepGoing = 0;
        }
        if (!keepGoing) continue;


        //4. Reverse the bubble if the left reads maps to the - strand
        //The left reads must always map to the bubble in a forward way
        //If they map in the RC of the bubble, we RC the sequence of the bubble, to force them map in a forward way
        char strandTheLeftReadsMapsTo = '\0';
        if (leftReadFilesAgreedOnFW.size()>0) strandTheLeftReadsMapsTo = '+';
        else if (leftReadFilesAgreedOnRC.size()>0) strandTheLeftReadsMapsTo = '-';
        else if (rightReadFilesAgreedOnFW.size()>0) strandTheLeftReadsMapsTo = '-';
        else if (rightReadFilesAgreedOnRC.size()>0) strandTheLeftReadsMapsTo = '+';

        //reverse the bubble
        if (strandTheLeftReadsMapsTo=='-') {
          reverseEverything(all_starters[upperPathIndex]);
          reverseEverything(all_starters[lowerPathIndex]);
        }
      }
      explanations.close();
    }

    if (silented) silent=0;
    
    if(number_paths_per_event==2)
        print_results_2_paths_per_event(coherent_out, uncoherent_out, results_against_set, number_of_read_sets, nb_events_per_set, quality, checkStrands);
    else if(map_invs)
        print_results_invs(coherent_out, uncoherent_out, results_against_set, number_of_read_sets, nb_events_per_set, quality);
    else   print_generic_results(coherent_out, uncoherent_out, results_against_set, number_of_read_sets, nb_events_per_set, quality); // TODO
    
    free(seed_table);
    FreeHashTable((struct HashTable*)seeds_count);
    for (i=0;i<nb_events_per_set;i++)
    {
        free(results_against_set[i]->read_coherent);
        free(results_against_set[i]->number_mapped_reads);
        free(results_against_set[i]->nbOfReadsMappingInFW);
        free(results_against_set[i]->nbOfReadsMappingInRC);
        free(results_against_set[i]->consensusStrandOfMappedReads);
        int read_set_id;
        for (read_set_id=0;read_set_id<number_of_read_sets;read_set_id++)
        {
            free(results_against_set[i]->read_coherent_positions[read_set_id]);
            free(results_against_set[i]->sum_quality_per_position[read_set_id]);
        }
        free(results_against_set[i]->read_coherent_positions);
        free(results_against_set[i]->sum_quality_per_position);
        free(results_against_set[i]);
    }
    free(results_against_set);

    
	gzclose(file);
	fclose(coherent_out);
	if (!only_print) fclose(uncoherent_out);
	if(sam_out) fclose(sam_out);
	if(!silent) {
        printf("Results are in %s and %s", coherent_file_name, uncoherent_file_name);
        if(sam_out) printf(" (mapped reads are in %s)", samout_file_name);
        printf(".\n");
		after_all = time(NULL);
		printf("Total time: %.0lf secs\n",  difftime(after_all, before_all));
	}
	return 0;
}
