/**
 * 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 <assert.h>
#include <string.h>
#include <ctype.h> // for tolower

#include "couple.h"
#include "../commons.h"
#include "hash.h"
#include "list.h"
#include "read_groups.h"
#include "Read_coherence_mapsembler.h"


void create_new_group(group_t *new_group, group_info **current_group_info, const char* fragment)
{
	*new_group=list_create();test_alloc(*new_group);
	*current_group_info=malloc(sizeof(group_info));test_alloc(*current_group_info);
	(*current_group_info)->farthest_position=0;
	(*current_group_info)->number_substitutions=0;
	(*current_group_info)->nb_reads=0;
	(*current_group_info)->consensus=strdup(fragment);
	(*current_group_info)->fragment_coverage=calloc(strlen(fragment),sizeof(int));
	(*current_group_info)->contained_reads=list_create();test_alloc((*current_group_info)->contained_reads);
}



void free_group(group_t current_read_group, group_info *current_read_group_info)
{
	list_specific_free(current_read_group, (void *) free_charint_couple);
	free(current_read_group_info->consensus);
	free(current_read_group_info->fragment_coverage);
	list_specific_free(current_read_group_info->contained_reads, (void *) free_charint_couple);
	free(current_read_group_info);
}


int append_read_to_group(group_t current_group, group_info *current_group_info, char *current_read, int current_read_len, int position_fragment, const char * fragment, int len_fragment, int current_read_weight, int group_coverage_aware, int group_substitutions_aware, int max_substitutions_allowed, hash_t groups_consensuses)
{
	if (current_read_len==0)
		current_read_len=strlen(current_read);
	/*if (current_read_len==0)
	{ // empty reads are normal, for now, (see a to-do in read_coherencen_and_extension)
		printf("was going to append an empty read at position %d to group %X (fragment=%s), i'd rather stop\n",(int)current_group,position_fragment,fragment);
		exit(1);
	}*/
	int l;
	int previous_number_of_subst=current_group_info->number_substitutions;
	int previous_farthest_position=current_group_info->farthest_position;
	for (l=MAX(current_group_info->farthest_position,position_fragment);l<MIN(len_fragment,position_fragment+current_read_len);l++)
	{
		if (fragment[l]!=current_read[l-position_fragment])
		{
			/*// WIP-HASH
			// update group_consensuses
			if (current_group_info->number_substitutions>0)
				hash_remove(groups_consensuses,current_group_info->consensus);*/

			current_group_info->number_substitutions++;
		}
		/* WIP-HASH
		if (fragment[l]!=current_read[l-position_fragment])
		{
			// update group_consensuses
			int dummy=0;
			hash_insert(groups_consensuses,current_group_info->consensus,&dummy,0);
		}*/
	}
	current_group_info->farthest_position=MAX(current_group_info->farthest_position,position_fragment+current_read_len);
	if ( group_substitutions_aware && current_group_info->number_substitutions> max_substitutions_allowed)
	{
		if (debug_read_coherence)
		printf("if we append read %s to group %X, this group won't be read-coherent as it will have %d substitutions with the fragment\n",current_read,(int)current_group,current_group_info->number_substitutions);

		current_group_info->number_substitutions=previous_number_of_subst;
		current_group_info->farthest_position=previous_farthest_position;
		return 0;
	}

	// always compute group consensus after the previous check, else we might alter positions
	for (l=MAX(previous_farthest_position,position_fragment);l<MIN(len_fragment,position_fragment+current_read_len);l++)
	{
		current_group_info->consensus[l]=current_read[l-position_fragment];
	}

	// increase coverage at read positions
	increage_group_coverage(current_group_info, position_fragment, current_read_len, len_fragment);

	list_add_tail(current_group,create_charint_couple(current_read,position_fragment));
	current_group_info->nb_reads++;

	/*
	 * add remaining copies of the read to the contained reads
	 */
	if (current_read_weight>1)
		append_contained_read_to_group( current_group, current_group_info, current_read,  current_read_len,  position_fragment, fragment,  len_fragment,  current_read_weight-1, group_coverage_aware,  group_substitutions_aware,  max_substitutions_allowed,  groups_consensuses);

	return 1;
}

int separate_contained_reads = 0;
// separating contained reads is probably a good direction, but i havn't fully tested it yet. so i recommend disabling it.
// also, the strictly contained reads certainly need to be considered for forking.
// so there's only a limited advantage to separating reads.. maybe just discard repeated reads.

int append_contained_read_to_group(group_t current_group, group_info *current_group_info, char *current_read, int current_read_len, int position_fragment, const char * fragment, int len_fragment, int current_read_weight, int group_coverage_aware, int group_substitutions_aware, int max_substitutions_allowed, hash_t groups_consensuses)
{
	// normal, non-optimized mode: don't separate contained reads
	if (!separate_contained_reads)
		return append_read_to_group(current_group, current_group_info, current_read,  current_read_len,  position_fragment,  fragment,  len_fragment, current_read_weight, group_coverage_aware, group_substitutions_aware, max_substitutions_allowed,  groups_consensuses);

	/*
	 * add as many copies of the read to the contained reads
	 */

	int l;
	for (l=0;l<current_read_weight;l++)
	{
		// here, just increase coverage. no need to recompute consensus or substitutions

		if (current_read_len==0)
			current_read_len=strlen(current_read);

		// increase coverage at read positions
		increage_group_coverage(current_group_info, position_fragment, current_read_len, len_fragment);

		list_add_tail(current_group_info->contained_reads,create_charint_couple(current_read,position_fragment));
		current_group_info->nb_reads++;
	}

	return 1;
}


void increage_group_coverage(group_info *current_group_info, int position_fragment, int current_read_len, int len_fragment)
{

	int l;
	for (l=MAX(0,position_fragment);l<MIN(len_fragment,position_fragment+current_read_len);l++)
	{
		current_group_info->fragment_coverage[l]++;
	}
}

void copy_group_coverage(group_info *old_current_group_info,group_info *new_current_group_info, int len_fragment)
{
	int l;
	for (l=0;l<len_fragment;l++)
		new_current_group_info->fragment_coverage[l]=old_current_group_info->fragment_coverage[l];
}

int append_group_to_groups(int *nb_groups,list *read_groups, group_t new_group, group_info *new_group_info) {
	if (*nb_groups < MAX_GROUPS) {
		list_add(read_groups, create_pointers_couple((void *) new_group,
				(void *) new_group_info));
		(*nb_groups)++;
	} else {
		/* let's forget about this fragment.. too many groups */

		fprintf(stderr,
				"Too many sub-starters (>%d) were created for this fragment, aborting.\n",
				*nb_groups);

		free_group(new_group,new_group_info);
		return 1;
	}
	return 0;
}

void remove_group_from_groups(int *nb_groups,list *read_groups, cell *group_cell) {
	group_t current_read_group=(group_t)((p_pointers_couple)(group_cell->val))->a;
	group_info *current_read_group_info=(group_info *)((p_pointers_couple)(group_cell->val))->b;

	free_group(current_read_group,current_read_group_info);
	free_couple(((p_pointers_couple)(group_cell->val)));
	list_del(read_groups,group_cell); // Crado, on pourrait garder le lien vers le prédécesseur pour éliminer group_cell et éviter d'utiliser list_del en O(n)
									  // rayan thinks its no big deal..
	(*nb_groups)--;
}

int find_group(list *read_groups, group_t current_group)
{
	cell *group_cell=read_groups->first;
	while (group_cell != NULL)
	{
		group_t other_read_group = (group_t) ((p_pointers_couple) (group_cell->val))->a;
		if (current_group == other_read_group)
			return 1;
		group_cell=group_cell->prox;
	}
	return 0;
}

/*
 * free a list of reads groups
 */
void free_read_groups(list *read_groups, int also_free_groups)
{
	cell *group_cell=read_groups->first;
	while (group_cell != NULL)
	{
		cell *next=group_cell->prox;
		if (also_free_groups)
		{
			int dummy=1;
			remove_group_from_groups(&dummy,read_groups,group_cell);
		}
		else
		{
			// just free the list
			free_couple(((p_pointers_couple)(group_cell->val)));
			list_del(read_groups,group_cell);
		}
		group_cell=next;
	}
	list_free(read_groups);
}

void print_read_aligned(char * fragment, char *current_read, int position_fragment, int current_read_len)
{
	int j,p, space=70;
	for(j=0;j<space+position_fragment;j++) printf(" ");
	for(p=0;p<current_read_len;p++){
		if(position_fragment+p>=0 && position_fragment+p<strlen(fragment) && current_read[p] != fragment[position_fragment+p]) printf("%c", tolower(current_read[p]));
		else printf("%c", current_read[p]);
	}
	printf(" %d\n", position_fragment);
}

int print_group_cover(group_t group, group_info *group_info, char * fragment, signed int left){

	int j,p, space=70;
	printf("\n");
	for(j=0;j<space;j++) printf(" "); printf("%s = Fragment\n", fragment);
	for(j=0;j<space;j++) printf(" "); for (j=0;j<strlen(fragment);j++) { int coverage = group_info->fragment_coverage[j]; if (coverage>0xF) printf("+");
																			else if (coverage>0) printf("%X",coverage); }printf(" = Fragment coverage");

	if (left>=0)
	{
		if(left>0) printf(" left extension\n");
		else printf(" right extension\n");
	}
	if(left<0) printf("\n");
	cell *read=group->first;
	while (read != NULL)
	{
		char *current_read=(char *)((p_charint_couple)(read->val))->a;
		assert( current_read != NULL);
		int position_fragment=((p_charint_couple)(read->val))->b;
		int current_read_len=((p_charint_couple)(read->val))->a_len;

		print_read_aligned(fragment, current_read,position_fragment,current_read_len);

		read=read->prox;
	}

	// also print contained reads

	printf("\t\tcontained reads:\n");

	cell *contained_read=group_info->contained_reads->first;
		while (contained_read != NULL)
		{
			char *current_read=(char *)((p_charint_couple)(contained_read->val))->a;
			assert( current_read != NULL);
			int position_fragment=((p_charint_couple)(contained_read->val))->b;
			int current_read_len=((p_charint_couple)(contained_read->val))->a_len;

			print_read_aligned(fragment, current_read,position_fragment,current_read_len);

			contained_read=contained_read->prox;
		}

	return 1;
}



#ifdef __cplusplus
}
#endif
