/**
 * Copyright INRIA and ENS, contributors Peterlongo and Chikhi
 * pierre.peterlongo@inria.fr
 * rayan.chikhi@irisa.fr
 *
 * This software is a computer program whose purpose is to detect the
 * presence of a starter in a set of NGS reads, and to provide the neighbor
 * in case of success..
 *
 * 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.
 */

#ifdef __cplusplus
extern "C"
{
#endif
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include "../commons.h"
#include "consensus_common.h"

#define ACTG(s) ((s=='A')?0:( (s=='C')?1:( (s=='T')?2:3)))
#define is_ACTG(s) (s=='A' || s=='C' || s=='T' || s=='G')

/*
 * init votes array
 */
void init_votes(int votes[MAX_VOTES][4])
{
	int vx,vy;
	for (vx=0;vx<MAX_VOTES;vx++)
		for (vy=0;vy<4;vy++)
			votes[vx][vy]=0;
}

/*
 * filter vote array according to threshold
 */
void filter_votes(int votes[MAX_VOTES][4], int threshold)
{
	int vx,vy;
	for (vx=0;vx<MAX_VOTES;vx++)
	{
		for (vy=0;vy<4;vy++)
			if (votes[vx][vy]<threshold)
			{//printf("eliminated %d at position %d\n",vy,vx);
				votes[vx][vy]=0;
			}
	}
}

/*
 * removes votes after a position
 */
void trim_votes(int votes[MAX_VOTES][4], int position)
{
	int vx,vy;
	for (vx=position;vx<MAX_VOTES;vx++)
			for (vy=0;vy<4;vy++)
				votes[vx][vy]=0;
}


/*
 * tell the user if he's set error_threshold strangely
 */
void analyze_votes(int votes[MAX_VOTES][4], int error_threshold)
{
	int vx,vy;
	int background_noise=0;
	int consensus_votes=0;
	int nb_total_votes=0;
	for (vx=0;vx<MAX_VOTES;vx++)
	{
		int max=0,max_vy=0;
		int reached_end=1;
		for (vy=0;vy<4;vy++)
		{
			if (votes[vx][vy]!=0)
				reached_end=0;
			if (votes[vx][vy]>max)
			{
				max_vy=vy;
				max=votes[vx][vy];
			}
		}
		if (reached_end)
			break;
		nb_total_votes++;
		consensus_votes+=votes[vx][max_vy];
		for (vy=0;vy<4;vy++)
			if (vy!=max_vy)
				background_noise+=votes[vx][vy];
	}
	if (nb_total_votes==0)
		return;
	consensus_votes/=nb_total_votes;
	background_noise/=nb_total_votes;
	if (error_threshold <= background_noise)
	{
		printf("\nWarning: the error_threshold parameter (-t %d) might be too low, consider raising it.\nPer starter position, and on average, the most represented base is seen ~%d times,\nand the other bases are seen ~%d times",error_threshold,consensus_votes,background_noise);
	}
	if (error_threshold>2*consensus_votes)
	{
		printf("\nWarning: the error_threshold parameter (-t %d) might be too high, consider lowering it.\nPer starter position, and on average, the most represented base is seen ~%d times,\nand the other bases are seen ~%d times",error_threshold,consensus_votes,background_noise);
	}

}


/*
 * examine votes for read coherence
 * return -1 if there is a position with no votes at all
 * return the number of substitutions (positions with strictly more than 1 voted nucleotide)
 */
int read_coherence_from_votes(int votes[MAX_VOTES][4], int start, int end, int error_threshold)
{
	int vx,vy;
	int number_of_substitutions=0;
	for (vx=start;vx<end;vx++)
	{
		int number_of_valid_nucleotides=0;
		for (vy=0;vy<4;vy++)
			if (votes[vx][vy]>=error_threshold)
				number_of_valid_nucleotides++;
		if (number_of_valid_nucleotides==0)
				return -1;
		else
			if (number_of_valid_nucleotides>1)
				number_of_substitutions++;
	}
	return number_of_substitutions;
}

/*
 * populate votes according to the current read, properly anchored to the fragment (with position_read and position_reference)
 */
void populate_votes(char *read, int read_len, int position_read, int position_reference, int votes[MAX_VOTES][4])
{
//		printf("read %s, pos_read=%d pos_ref=%d\n", read, position_read, position_reference); //DEB
			int i;
			// populate votes
			for (i=position_read;i<read_len;i++)
			{
				if (!is_ACTG(read[i])) continue;
				if (position_reference+i-position_read>=MAX_VOTES)
					{ printf("increase MAX_VOTES! %d %d %d\n",position_reference+i-position_read,i,read_len);exit(1);}
				votes[position_reference+i-position_read][ACTG(read[i])]++;
			}
}

void init_consensus_coverage(char *consensus_coverage, int len, int max_len)
{
	int i;
	for (i=0;i<len;i++)
		consensus_coverage[i]=1;
	for (i=len;i<max_len;i++)
		consensus_coverage[i]=0;
}

void increase_consensus_coverage(char *consensus_coverage, int up_to)
{
	int i;
	for (i=0;i<up_to;i++)
		consensus_coverage[i]++;
}

/*
 * perform_consensus_with_read returns a list of consensuses (a consensus = fragment + extended sequence)
 */
void perform_consensus_with_read(char *fragment, char *read, int position, int threshold, int votes[MAX_VOTES][4], char ** consensus , int *nb_consensuses, int * consensuses_weights, char ** consensuses_coverages)
{
			int i;
			int max_consensus_length=MAX_VOTES;
			char * cur_consensus = (char *)malloc(max_consensus_length+1);
			test_alloc(cur_consensus);

			int consensus_idx=0;

			if ( fragment!=NULL )
			{
				char *cur_s=fragment;

				// create cur_consensus with the part of the fragment before the read
				while (consensus_idx<position)
					cur_consensus[consensus_idx++]=*(cur_s++);
			}

			cur_consensus[consensus_idx]='\0';

			char * error_corrected  = (char *)malloc((strlen(read)+1));
			test_alloc(error_corrected);

			/*
			 * perform error-correction on the read
			 */
			rudimentary_error_correction(read,votes,position,threshold,error_corrected);
			//printf("(consensus thresholded) cur consensus %s error corrected %s\n",cur_consensus,error_corrected);

			/*
			 * append the error_corrected read to cur_consensus
			 */
			strcat(cur_consensus,error_corrected);

			int add=1;
			int subsume=-1;

			/*
			 * add cur_consensus to the list of consensuses, check if it's redundant or if it subsumes another consensus
			 */
			//printf("(consensus thresholded) preparing to add %s\n",cur_consensus);
			for (i=0;i<*nb_consensuses;i++)
				{
					/*
					 * if cur_consensus is longer than consensus[i] and consensus[i] is contained in cur_consensus, cur_consensus subsumes consensus[i]
					 */
					if (strlen(consensus[i])<strlen(cur_consensus))
					{
						if (strstr(cur_consensus,consensus[i]))
						{
							subsume=i;
							add=0;break;
						}
					}
					/*
					 * if cur_consensus is shorter than consensus[i] and cur_consensus is contained in consensus[i], cur_consensus is useless
					 */
					else
						if (strstr(consensus[i],cur_consensus))
						{
							consensuses_weights[i]++;
							increase_consensus_coverage(consensuses_coverages[i],strlen(consensus[i]));
							subsume=-1;
							add=0;break;
						}
				}

			/*
			 * if it subsumes a consensus, replace it
			 */
			//printf("(consensus thresholded) outcome subsume %d add %d\n",subsume,add);
			if (subsume!=-1){
				free(consensus[subsume]);
				consensus[subsume] = strdup(cur_consensus);
				consensuses_weights[subsume]++;
				increase_consensus_coverage(consensuses_coverages[subsume],strlen(cur_consensus));

			}

			/*
			 * if it's not redundant and did not subsume something else, add the consensus
			 */
			if (add==1)
			{
				//printf("(consensus thresholded) added %s\n",cur_consensus);
//				if (*nb_consensuses>MAX_CONSENSUS){printf("increase consensus[%d][]!\n",MAX_CONSENSUS);exit(1);}
				if(*nb_consensuses<=MAX_CONSENSUS){
					consensus[*nb_consensuses] = strdup(cur_consensus);
					consensuses_coverages[*nb_consensuses] = malloc(max_consensus_length+1);
					init_consensus_coverage(consensuses_coverages[*nb_consensuses], strlen(consensus[*nb_consensuses]), max_consensus_length);
					consensuses_weights[*nb_consensuses]=1;
					(*nb_consensuses)++;
				}
			}
			free(cur_consensus);
			free(error_corrected);
}

/**
 * perform read error correction
 * if a nucleotide has a vote-count lower than the threshold, it is considered as an error and we attempt to correct it
 * else, nothing is done
 * for each position, we attempt to correct a nucleotide (eg. A)
 * by searching how many other nucleotides (T,C,G) have a vote-count > threshold:
 * if there is only one such nucleotide (eg. T), it replaces the old nucleotide (A)
 * if there is strictly more than one (eg T and C), it is an ambiguity and we stop here, returning the error-corrected read up to this position
 * this function modifies the error_corrected reads
 * this function returns the number of corrections made on the read
 * */

int rudimentary_error_correction( char *ext, int votes[MAX_VOTES][4], int vote_start_pos, int threshold, char *error_corrected)
{
	int i;
	int len_ext=strlen(ext);
	int error_corrected_pos=0;
	int nb_error_corrected = 0;
	char ACTG[4]={'A','C','T','G'};

	for (i=0;i<len_ext;i++)
	{
		if (vote_start_pos+i<0)
		{
			//error_corrected[error_corrected_pos++]=ext[i];
			/*
			 * mark 'U'ndetermined positions since we have no votes for them (they are before the vote array)
			 */
			error_corrected[error_corrected_pos++]='U';
			continue;
		}
		if (is_ACTG(ext[i]) && votes[vote_start_pos+i][ACTG(ext[i])]>=threshold)
			error_corrected[error_corrected_pos++]=ext[i];
		else
		{
			int j;
			int nb_found=0;
			int found=0;
			for (j=0;j<4;j++)
				if ((!is_ACTG(ext[i])) || j!=ACTG(ext[i]))
					if (votes[vote_start_pos+i][j]>=threshold)
					{
						nb_found++;
						found=j;
					}
			if (nb_found==1){
				error_corrected[error_corrected_pos++]=ACTG[found];
				nb_error_corrected++;
			}
			else
			{
				/*
				 * mark 'U'ndetermined position since we have 1) either no votes for it 2) ambiguous votes
				 */
				//error_corrected[error_corrected_pos++]='U';
				/*
				 * maybe not after all
				 */
				 break;
			}
		}
	}		
	error_corrected[error_corrected_pos]='\0';
	//printf("c error correction for ext %s is %s\n",ext,error_corrected);
	return nb_error_corrected;
}

// returns 1 if a and b are the same sequence modulo N's, 0 otherwise
int sequences_are_equals(char *a, int len_a, char *b, int len_b)
{
	int i;

	if (len_a!=len_b)
		return 0;


	for (i=0;i<len_a;i++)
		if (a[i] != 'N' && b[i] != 'N' && a[i] != b[i]){
			return 0;
		}

	return 1;
}

/*
 * naive perfect overlap function, return 1 if read1 perfectly overlaps with read2, discarding U's
 *
 * ------------------read 1
 *          |||||||||
 *          ------------------- read2
 */
int overlap_perfect(char *read1, int pos_read1, int read1_len, char *read2, int pos_read2)
{
	int i=0;
	int relative_position=pos_read2-pos_read1;

	if (i+relative_position>read1_len)
		return 0;
	while (*(read1+i+relative_position)!='\0' && *(read2+i)!='\0')
	{
		if (read1[i+relative_position]!='U' && read2[i]!='U')
			if (read1[i+relative_position]!=read2[i])
				{
					return 0;
				}
		i++;
	}
	return 1;
}


#ifdef __cplusplus
}
#endif
