/***************************************************************

   The Subread software package is free software package:
   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 3 of the License,
   or (at your option) any later version.

   Subread 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.

   Authors: Drs Yang Liao and Wei Shi

  ***************************************************************/

#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <time.h>
#include <pthread.h>
#include <unistd.h>
#include <getopt.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/resource.h>
#include <sys/timeb.h>
#include <sys/stat.h>
#include <locale.h>
#include <ctype.h>
#include <unistd.h>
#include <getopt.h>

#include "LRMconfig.h"
#include "LRMsorted-hashtable.h"
#include "LRMbase-index.h"
#include "LRMchro-event.h"
#include "LRMfile-io.h"

int LRMvalidate_and_init_context(LRMcontext_t ** context, int argc, char ** argv);

int main(int argc, char ** argv){
	int retv=0;
	
	LRMcontext_t *context = NULL;
	retv = retv || LRMvalidate_and_init_context(&context, argc, argv);
	retv = retv || LRMshow_conf(context);
	retv = retv || LRMrun_task(context);
	retv = retv || LRMfinalise(context);
	retv = retv || LRMprint_mapping_summary(context);
	retv = retv || LRMdestroy_context(context);
	context = NULL;
	
	return retv;
}

int LRMprint_mapping_summary(LRMcontext_t * context){
	LRMprintf("\n\nAll finished.\n\nTotal processed reads : %d\n", context -> all_processed_reads);
	LRMprintf("Time: %.1f minutes\n\n" , (LRMmiltime() - context->start_running_time)/60);
	return 0;
}

int LRMvalidate_and_init_context(LRMcontext_t ** context, int argc, char ** argv){
	int c;
	
	(*context) = malloc(sizeof(LRMcontext_t));
	memset((*context), 0, sizeof(LRMcontext_t));
	LRMset_default_values_context(*context);

	(*context) -> input_file_name[0] = 0;
	(*context) -> output_file_name[0] = 0;	
	(*context) -> index_prefix[0] = 0;
	
	optind = 0;
	opterr = 1;
	optopt = 63;
	while ((c = getopt (argc, argv, "r:i:o:T:P:js"))!=-1){
		switch(c){
			case 'P':
				(*context) -> is_Phred_64=(optarg[0]=='6');
				break;
			case 'j':
				(*context) -> do_junction_detection = 1;
				break;
			case 'r':
				strcpy((*context) -> input_file_name, optarg);
				break;
			case 'i':
				strcpy((*context) -> index_prefix, optarg);
				break;
			case 's':
				(*context) -> is_SAM_output = 1;
				break;
			case 'o':
				strcpy((*context) -> output_file_name, optarg);
				break;
			case 'T':
				(*context) -> threads = min(max(1,atoi(optarg)),LRMMAX_THREADS);
				break;
			case 'n':
				(*context) -> max_subreads_per_segment = atoi(optarg);
				assert( (*context) -> max_subreads_per_segment< LRMMAX_SUBREAD_PER_SEGMENT_HARDLIMIT );
				break;
			case 'X':
				(*context) -> max_mismatched_bases_in_subread = atoi(optarg);
				assert((*context) -> max_mismatched_bases_in_subread <3);
				break;
			case 'O':
				(*context) -> segment_overlapping = atoi(optarg);
				break;
			case 'm':
				(*context) -> min_voting_number = atoi(optarg);
				break;
			case '?':
			default:
				return 1;
		}
	}

	if((*context) -> input_file_name[0] ==0 || (*context) -> output_file_name[0]==0 || (*context) -> index_prefix[0]==0){
		LRMprintf("Please specify the input, output files and the index.\n");
		return 1;
	}

	(*context) -> user_command_line[0]=0;
	for(c = 0; c<argc;c++)
		sprintf((*context) -> user_command_line+strlen( (*context) -> user_command_line), "\"%s\" ", argv[c]);
	

	LRMthread_lockinit(&(*context) -> input_lock);
	LRMthread_lockinit(&(*context) -> sam_bam_file_lock);
	
	(*context)-> sam_bam_chromosome_table = HashTableCreate(199);
	HashTableSetKeyComparisonFunction((*context)-> sam_bam_chromosome_table, LRMhash_strcmp);
	HashTableSetHashFunction((*context)-> sam_bam_chromosome_table, LRMhash_strhash);
	HashTableSetDeallocationFunctions((*context)-> sam_bam_chromosome_table, NULL, NULL);
	

	(*context)-> chromosome_size_list = ArrayListCreate(29);

	(*context)-> chromosome_size_table = HashTableCreate(199);
	HashTableSetKeyComparisonFunction((*context)-> chromosome_size_table, LRMhash_strcmp);
	HashTableSetHashFunction((*context)-> chromosome_size_table, LRMhash_strhash);
	HashTableSetDeallocationFunctions((*context)-> chromosome_size_table, free, NULL);

	(*context) -> sam_bam_chromosome_list = ArrayListCreate(29);
	
	LRMload_offsets(*context);

	int retv = LRMgeinput_open((*context)->input_file_name,&(*context) -> input_file);

	(*context)->sam_bam_file = fopen( (*context) -> output_file_name, "w");
	if(NULL == (*context)->sam_bam_file) retv = 1;
	
	(*context)->event_space = malloc(sizeof(LRMevent_t)*20000);
	(*context)->event_space_size = 20000;
	LRMthread_lockinit(&(*context) -> event_space_lock);
	(*context)->events_realignment  = HashTableCreate(320000);

	assert(LRMMAX_SUBREAD_PER_SEGMENT_HARDLIMIT >= (*context)->max_subreads_per_segment );

	return retv;
}


double LRMmiltime(){
	double ret;
	struct timeb trp;
	ftime(&trp);
	ret = trp.time*1.0+(trp.millitm*1.0/1000.0);
	return ret;
}


void LRMset_default_values_context(LRMcontext_t * context){
	context->threads = 1;
	context->start_running_time = LRMmiltime();
	context->multi_best_read_alignments = 1;

	context->max_indel_length = 15;
	context->max_junction_distance = 100000;
	context->max_mismatched_bases_in_subread = 0;
	context->max_subreads_per_segment = 10;
	context->min_voting_number = 1;
	context->segment_overlapping = 30;

	context->dynamic_programming_score_match = 2;
	context->dynamic_programming_score_create_gap = -2;
	context->dynamic_programming_score_extend_gap = -1;
}

int LRMshow_conf(LRMcontext_t * context){
	LRMprintf("\n ====== Subread long read mapping ======\n\n");
	LRMprintf("Threads: %d\n" , context->threads);
	LRMprintf("Input file: %s\n" , context->input_file_name);
	LRMprintf("Output file: %s (%s)\n" , context->output_file_name,  context->is_SAM_output?"SAM":"BAM");
	LRMprintf("Index: %s\n\n" , context->index_prefix);
	
	return 0;
}

int LRMinit_chunk(LRMcontext_t * context){
	if(context->all_processed_reads) memset(context ->  read_mapping_results, 0, sizeof(LRMread_mapping_result_t)*LRMREADS_PER_CHUNK);
	return 0;
}

int LRMrun_task(LRMcontext_t * context){
	int retv = 0;
	retv = LRMload_index( context );
	LRMprintf("Index was loaded; the gap bewteen subreads is %d bases\n", context -> current_index.index_gap );
	while(!(retv ||LRMinput_has_finished( context ))){
		retv=retv || LRMinit_chunk(context);
		retv=retv || LRMsave_input_pos(context);
		retv=retv || LRMiterate_reads( context , LRMRUNNING_STEP_VOTING);
		retv=retv || LRMrewind_input_pos(context);
		retv=retv || LRMiterate_reads( context , LRMRUNNING_STEP_REALIGN);
		retv=retv || LRMfinalise_chunk_reads(context);
	}
	return retv;
}

int LRMfinalise(LRMcontext_t * context){
	return 0;
}

int LRMdestroy_context(LRMcontext_t * context){

	LRMgehash_destory(&(context -> current_index));
	LRMgvindex_destory(&(context -> current_base_index));
	
	HashTableDestroy(context -> chromosome_size_table);
	ArrayListDestroy(context -> chromosome_size_list);

	HashTableDestroy(context -> sam_bam_chromosome_table);
	ArrayListDestroy(context -> sam_bam_chromosome_list);
	
	HashTableSetDeallocationFunctions(context -> events_realignment, NULL, free);
	HashTableDestroy(context -> events_realignment);

	free(context -> event_space);

	int readno;
	for(readno = 0; readno < LRMREADS_PER_CHUNK; readno++){
		if(context -> read_mapping_results[readno].segment_results != NULL)
			free(context -> read_mapping_results[readno].segment_results);
	}

	if(!context -> is_SAM_output){
		fwrite(context -> bam_file_tail_binary,1, context -> bam_file_tail_length, context->sam_bam_file);
	}

	LRMgeinput_close(&context->input_file);
	fclose(context->sam_bam_file);
	//free(context->user_command_line);
	free(context);
	return 0;
}


int LRMinput_has_finished( LRMcontext_t * context ){
	return context -> input_exhausted ;
}

int LRMload_index(LRMcontext_t * context){
	int retv = 0;
	char indextab_fname[LRMMAX_FILENAME_LENGTH];

	sprintf(indextab_fname, "%s.00.b.tab", context -> index_prefix);
	retv = retv || LRMgehash_load(&(context -> current_index), indextab_fname);

	sprintf(indextab_fname, "%s.00.b.array", context -> index_prefix);
	retv = retv || LRMgvindex_load(&(context -> current_base_index), indextab_fname);
	
	return retv;
} 


int LRMiterate_reads( LRMcontext_t * context, int task ){
	int retv = 0;
	retv = retv || LRMstart_thread( context , task );
	retv = retv || LRMwait_threads( context );
	retv = retv || LRMmerge_threads( context, task );
	return retv;
}

void * LRM_thread_runner (void * args){
	void ** argv = args;
	LRMcontext_t * context = argv[0];
	int thid = argv[1]-NULL;
	int task = argv[2]-NULL;	
	free(args);

	LRMchunk_read_iteration(context, thid, task);	
	
	return NULL;
}

int LRMstart_thread_init_context(LRMcontext_t * context, int thread_id, int step){
	LRMthread_context_t * thread_context = context -> thread_contexts+thread_id;
	memset(thread_context, 0, sizeof(LRMthread_context_t));
	thread_context->thread_id = thread_id;
	
	if(step == LRMRUNNING_STEP_VOTING){
		if( thread_context -> thread_id == 0 )LRMsambam_write_header(context, thread_context);
		thread_context -> dynamic_programming_movement_buffer = malloc(( 2* context -> max_indel_length + 1) * LRMDYNAMIC_MAXIMUM_GAP_LENGTH);
		thread_context -> dynamic_programming_score_buffer = malloc(sizeof(int) * ( 2 * context -> max_indel_length + 1) *( LRMDYNAMIC_MAXIMUM_GAP_LENGTH+1));
		thread_context -> dynamic_programming_indel_movement_buf = malloc( max( LRMDYNAMIC_MAXIMUM_GAP_LENGTH * 1.3, 300 ) +  context -> max_indel_length + 1 );
	}else if(step == LRMRUNNING_STEP_REALIGN){
		thread_context -> dynamic_programming_movement_buffer = malloc(( 2* context -> max_indel_length + 1) * LRMDYNAMIC_MAXIMUM_GAP_LENGTH);
		thread_context -> dynamic_programming_score_buffer = malloc(sizeof(int) * ( 2 * context -> max_indel_length + 1) *( LRMDYNAMIC_MAXIMUM_GAP_LENGTH+1));
		thread_context -> dynamic_programming_indel_movement_buf = malloc( max( LRMDYNAMIC_MAXIMUM_GAP_LENGTH * 1.3, 300 ) +  context -> max_indel_length + 1 );

		thread_context -> out_SAMBAM_buffer = malloc(2400000);
		if(thread_context -> out_SAMBAM_buffer == NULL) return 1;
		
		thread_context -> out_buff_used = 0;
		thread_context -> out_buff_capacity = 2400000;
	}
	return 0;
}

int LRMstart_thread(LRMcontext_t * context, int task ){
	int th_id, retv=0;
		
	for(th_id=0; th_id<context -> threads; th_id++){
		
		retv = retv || LRMstart_thread_init_context(context,th_id,task);
		if(retv)
			break;
		else {
			void ** th_args=malloc(sizeof(void *)*3); // to be freed in the thread.
			th_args[0] = context;
			th_args[1] = NULL + th_id;
			th_args[2] = NULL + task;
			LRMpthread_create(context -> running_threads+th_id, NULL, LRM_thread_runner, th_args);
		}
	}
	
	return retv;
}

int LRMwait_threads( LRMcontext_t * context ){
	int th_id;
	for(th_id=0; th_id<context -> threads; th_id++)
		LRMpthread_join(context -> running_threads[th_id], NULL);
	return 0;
}

void LRMmerge_threads_destroy_context(LRMcontext_t * context, LRMthread_context_t * thread_context, int task){
	if(task == LRMRUNNING_STEP_VOTING){
		free(thread_context -> dynamic_programming_movement_buffer);
		free(thread_context -> dynamic_programming_score_buffer);
		free(thread_context -> dynamic_programming_indel_movement_buf);
	}else if(task == LRMRUNNING_STEP_REALIGN){
		free(thread_context -> dynamic_programming_movement_buffer);
		free(thread_context -> dynamic_programming_score_buffer);
		free(thread_context -> dynamic_programming_indel_movement_buf);
		free(thread_context -> out_SAMBAM_buffer);
	}
}

int LRMmerge_threads( LRMcontext_t * context , int step){
	int retv = 0;
	int th_id;

	for(th_id=0; th_id<context -> threads; th_id++){
		
		if(step == LRMRUNNING_STEP_VOTING){
			retv = retv || LRMevents_reorder(context);
			retv = retv || LRMevents_build_entries(context);
		}else if(step == LRMRUNNING_STEP_REALIGN){
			LRMwrite_chunk_check_buffer_write(context,  context -> thread_contexts+th_id, 1);
			if(th_id == context -> threads-1)LRMbam_generate_tail_binary(context,  context -> thread_contexts+th_id);
		}else assert(0);		
		LRMmerge_threads_destroy_context(context,  context -> thread_contexts+th_id, step);
	}
	
	return retv;
}

int LRMrewind_input_pos(LRMcontext_t * context){
	context -> processed_reads_in_chunk = 0;
	if(context->input_file.file_type == LRMGENE_INPUT_GZIP_FASTQ)
		seekgz_seek(context->input_file.input_fp, &context->last_saved_zlib_pos);
	else
		fseeko(context->input_file.input_fp, context->last_saved_raw_pos, SEEK_SET);
	return 0;
}

int LRMsave_input_pos( LRMcontext_t * context){
	context -> processed_reads_in_chunk = 0;
	if(context->input_file.file_type == LRMGENE_INPUT_GZIP_FASTQ)
		seekgz_tell(context->input_file.input_fp, &context->last_saved_zlib_pos);
	else
		context -> last_saved_raw_pos = ftello(context->input_file.input_fp);
		
	return 0;
}

int LRMsplit_read_to_segments(LRMcontext_t * context, LRMread_iteration_context_t* iteration_context){
	int seg_curs = 0;
	iteration_context->total_segments = 0;
	if(iteration_context->read_length<16) return 1;

	int increment_step = LRMSEGMENT_MIN_LENGTH - context -> segment_overlapping; //;LRMSEGMENT_MIN_LENGTH - LRMSEGMENT_OVERLAPPING;
	
	while(1){
		int seg_end = seg_curs + increment_step;
		if(seg_end + LRMSEGMENT_MIN_LENGTH > iteration_context->read_length) seg_end = iteration_context->read_length;
		
		iteration_context->segment_texts[iteration_context->total_segments] = iteration_context->read_text + seg_curs;
		iteration_context->segment_quals[iteration_context->total_segments] = iteration_context->qual_text + seg_curs;
		iteration_context->segment_lengths[iteration_context->total_segments] =(seg_end == iteration_context->read_length ? iteration_context->read_length - seg_curs : LRMSEGMENT_MIN_LENGTH);
		
		iteration_context->total_segments  ++;
		seg_curs = seg_end;
		if(seg_curs >= iteration_context->read_length) break;
	}
	return 0;
}

void LRMreverse_read_and_qual(LRMcontext_t * context, LRMthread_context_t * thread_context, LRMread_iteration_context_t * iteration_context){
	LRMreverse_read(iteration_context -> read_text, iteration_context -> read_length);
	LRMreverse_quality(iteration_context -> qual_text, iteration_context -> read_length);
	
	int segi;
	for(segi = 0; segi < iteration_context->total_segments / 2; segi++){
		//#warning " positions of the subreads should be moved after reversing -- for the best sequencing quality "
		unsigned int tmp_offsets[LRMMAX_SUBREAD_PER_SEGMENT_HARDLIMIT];
		memcpy(tmp_offsets, iteration_context->subread_offsets[segi], sizeof(int)* LRMMAX_SUBREAD_PER_SEGMENT_HARDLIMIT);
		memcpy(iteration_context->subread_offsets[segi], iteration_context->subread_offsets[ iteration_context->total_segments-segi-1 ],sizeof(int)* LRMMAX_SUBREAD_PER_SEGMENT_HARDLIMIT);
		memcpy(iteration_context->subread_offsets[iteration_context->total_segments-segi-1], tmp_offsets, sizeof(int)* LRMMAX_SUBREAD_PER_SEGMENT_HARDLIMIT);
	}

	for(segi = 0; segi < iteration_context->total_segments / 2; segi++){
		int old_left_length = iteration_context->segment_lengths[segi];
		int old_right_length = iteration_context->segment_lengths[iteration_context->total_segments - segi -1];

		int old_left_start = iteration_context -> segment_texts[segi] - iteration_context->read_text;
		int old_right_start = iteration_context -> segment_texts[ iteration_context->total_segments - segi -1 ] - iteration_context->read_text;
		int old_left_new_start = iteration_context -> read_length - (old_left_start + old_left_length);
		int old_right_new_start = iteration_context -> read_length - (old_right_start + old_right_length);

		iteration_context -> segment_texts[segi] = iteration_context->read_text + old_right_new_start;
		iteration_context -> segment_quals[segi] = iteration_context->qual_text + old_right_new_start;
		iteration_context -> segment_texts[iteration_context->total_segments -  segi -1] = iteration_context->read_text + old_left_new_start;
		iteration_context -> segment_quals[iteration_context->total_segments -  segi -1] = iteration_context->qual_text + old_left_new_start;

		int tmpi = iteration_context -> segment_lengths[ iteration_context->total_segments -  segi -1];
		iteration_context -> segment_lengths[ iteration_context->total_segments -  segi -1] = iteration_context -> segment_lengths[segi];
		iteration_context -> segment_lengths[segi] = tmpi;
	}

	if(iteration_context->total_segments % 2){
		int old_len = iteration_context -> segment_lengths[segi];
		int segi = iteration_context->total_segments / 2;
		int old_start = iteration_context -> segment_texts[segi] - iteration_context->read_text;
		int new_start = iteration_context->read_length - (old_start + old_len);

		iteration_context -> segment_texts[segi] = iteration_context->read_text + new_start;
		iteration_context -> segment_quals[segi] = iteration_context->qual_text + new_start;
	}
}

void LRMdo_one_voting_read_process_setres(LRMcontext_t * context, LRMread_iteration_context_t * iteration_context, LRMsegment_mapping_result_t * seg_result, int replace_index, LRMgene_vote_t *vote_table, int iii, int jjj){
	int x1;
	for(x1 = LRMSEGMENT_MAX_CANDIDATES-2; x1 >= replace_index; x1--)
		memcpy(seg_result->candidates + x1 + 1, seg_result->candidates + x1, sizeof(LRMsegment_mapping_candidate_t));
	
	seg_result->candidates[replace_index].first_base_position = vote_table -> pos[iii][jjj];
	seg_result->candidates[replace_index].indel_length_inside = vote_table -> current_indel_cursor[iii][jjj];
	seg_result->candidates[replace_index].confident_coverage_start = vote_table -> coverage_start[iii][jjj];
	seg_result->candidates[replace_index].confident_coverage_end = vote_table -> coverage_end[iii][jjj];
	seg_result->candidates[replace_index].votes = vote_table -> votes[iii][jjj];
	seg_result->candidates[replace_index].masks = vote_table -> masks[iii][jjj];

	if(0 && seg_result->candidates[replace_index].votes>2){
		char postxt[100];
		LRMpos2txt(context, seg_result->candidates[replace_index].first_base_position , postxt);
		LRMprintf("REPLACE CANDIDATE %d : to %s (%s), V=%d\n", replace_index, postxt, (seg_result->candidates[replace_index].masks & LRMIS_NEGATIVE_STRAND)?"NEG":"POS", seg_result->candidates[replace_index].votes);
	}
	
	memcpy(seg_result->candidates[replace_index].indel_records, vote_table ->indel_recorder[iii][jjj], sizeof(short)*3*LRMMAX_INDEL_SECTIONS);
}

int LRMdo_one_voting_read_process_samechro(LRMcontext_t * context, unsigned int p1, unsigned int p2){
	char * chro_name1, *chro_name2;
	int chro_pos1, chro_pos2;
	LRMlocate_gene_position(context, p1, &chro_name1, & chro_pos1);
	LRMlocate_gene_position(context, p2, &chro_name2, & chro_pos2);
	
	return chro_name1 == chro_name2; // they can be compared in this way because they are pointers in the sam_bam_chromosome_list.
}


#define LRMseg_fetch_result(mapr, sid) ( (mapr) -> segment_results + ((iteration_context -> is_reversed == 0)?(sid):( iteration_context -> total_segments - sid - 1 ) ) )

void LRMdo_one_voting_read_process_votetab(LRMcontext_t * context, LRMthread_context_t * thread_context, LRMread_iteration_context_t * iteration_context, int this_seg_id){
	LRMread_mapping_result_t * mapping_result = context -> read_mapping_results + iteration_context -> read_no_in_chunk;
	LRMsegment_mapping_result_t * seg_result = LRMseg_fetch_result( mapping_result, this_seg_id );
	seg_result -> extracted_subreads = iteration_context -> extracted_subreads;
	
	int iii, jjj, hhh;
	for(iii=0; iii < LRMGENE_VOTE_TABLE_SIZE; iii++){
		for(jjj = 0; jjj < iteration_context -> vote_table.items[iii]; jjj++){
			if( iteration_context -> vote_table.votes [iii][jjj] < context -> min_voting_number  ||  iteration_context -> vote_table.votes [iii][jjj] <= seg_result -> candidates[LRMSEGMENT_MAX_CANDIDATES - 1].votes) continue;
			int replace_index = LRMSEGMENT_MAX_CANDIDATES - 1;
			int kkk;
			for(kkk = 0; kkk < LRMSEGMENT_MAX_CANDIDATES; kkk ++){
				if(seg_result->candidates[kkk].votes < iteration_context -> vote_table.votes [iii][jjj]){
					replace_index = kkk;
					break;
				}
			}
			LRMdo_one_voting_read_process_setres(context, iteration_context, seg_result, replace_index, &iteration_context -> vote_table, iii, jjj);			
		}
	}
	
	if(context -> do_junction_detection)
		for(hhh = 0; hhh < LRMSEGMENT_MAX_CANDIDATES; hhh++){

			if(0){
				char p1txt[100];
				LRMpos2txt(context, seg_result->candidates[hhh].first_base_position, p1txt);
				LRMprintf("process_votetab: [%d] votes=%d, pos=%s (%u)\n", hhh, seg_result->candidates[hhh].votes, p1txt, seg_result->candidates[hhh].first_base_position);
			}

			if(seg_result -> candidates[hhh].votes<1)break;
			if((seg_result -> candidates[hhh].masks & LRMIS_NEGATIVE_STRAND) != (iteration_context -> is_reversed ?  LRMIS_NEGATIVE_STRAND : 0)) continue;
			seg_result -> candidates[hhh].secondary_votes = 0;

			unsigned int best_secondary_half_pos = 0;
			int best_secondary_score = -1, best_secondary_split_point = -1, best_secondary_is_GT_AG = -1, best_secondary_votes = -1, best_left_offset_indels = 0;
			
			for(iii=0; iii < LRMGENE_VOTE_TABLE_SIZE; iii++){
				for(jjj = 0; jjj < iteration_context -> vote_table.items[iii]; jjj++){
					if(iteration_context -> vote_table.votes [iii][jjj] > seg_result -> candidates[hhh].votes || iteration_context -> vote_table.votes [iii][jjj] <1) continue;
					
					long long dist0 = seg_result->candidates[hhh].first_base_position;
					dist0 -= iteration_context -> vote_table.pos[iii][jjj];
					int is_junction_distance = abs(dist0) > 3 && abs(dist0) < context -> max_junction_distance;
					int is_same_chro = LRMdo_one_voting_read_process_samechro(context, seg_result->candidates[hhh].first_base_position, iteration_context -> vote_table.pos[iii][jjj]);
					//LRMprintf("TEST JUNCTION COND: %u ~ %u : DIST=%d, SAME=%d\n", seg_result->candidates[hhh].first_base_position, iteration_context -> vote_table.pos[iii][jjj], is_junction_distance, is_same_chro );
					if(is_junction_distance && is_same_chro){
						int this_split_point = -1, this_is_GT_AG = -1, left_indel_offset = 0;
						int indel_length_in_anchor = seg_result->candidates[hhh].indel_length_inside;
						int indel_length_in_secondary = iteration_context -> vote_table.current_indel_cursor[iii][jjj];
						int this_score = LRMdonor_score(context, thread_context, iteration_context, seg_result -> candidates + hhh, this_seg_id, iteration_context ->vote_table.pos[iii][jjj] , iteration_context ->vote_table.coverage_start[iii][jjj] , iteration_context -> vote_table.coverage_end[iii][jjj], indel_length_in_anchor, indel_length_in_secondary, & this_split_point, & this_is_GT_AG , &left_indel_offset);

						if(0 && this_score > 0){
							char pos1txt[100], pos2txt[100];
							LRMpos2txt(context, seg_result -> candidates[hhh].first_base_position, pos1txt);
							LRMpos2txt(context, iteration_context ->vote_table. pos[iii][jjj], pos2txt);
							LRMprintf("TEST JUNCTION SCORE CAND %d: %s ~ %s = %d, <?< %d ; VOTES=%d + %d\n", hhh, pos1txt, pos2txt, this_score, best_secondary_score, seg_result -> candidates[hhh].votes, iteration_context -> vote_table.votes [iii][jjj]);
						}
						
						if(this_score > best_secondary_score ){
							best_secondary_half_pos =  iteration_context ->vote_table. pos[iii][jjj];
							best_secondary_split_point = this_split_point;
							best_secondary_is_GT_AG = this_is_GT_AG;
							best_secondary_votes = iteration_context -> vote_table.votes [iii][jjj];
							best_secondary_score = this_score;
							best_left_offset_indels = left_indel_offset;
						}
					}
				}
			}

			if(best_secondary_score > 0){
				seg_result -> candidates[hhh].secondary_position = best_secondary_half_pos;
				seg_result -> candidates[hhh].secondary_votes = best_secondary_votes;
				seg_result -> candidates[hhh].junction_split_point = best_secondary_split_point;
				seg_result -> candidates[hhh].junction_is_GT_AG = best_secondary_is_GT_AG;
				seg_result -> candidates[hhh].junction_left_offset_indels = best_left_offset_indels;
			}
		}
	//END: if context -> do_junction_detection
}

void LRMdo_one_voting_read_segment_extraction(LRMcontext_t * context, LRMthread_context_t * thread_context, LRMread_iteration_context_t * iteration_context, int this_seg_id){
	int seg_len = iteration_context -> segment_lengths[this_seg_id];
	char * seg_qual = iteration_context -> segment_quals[this_seg_id];

	iteration_context -> extracted_subreads = min(context -> max_subreads_per_segment ,(seg_len - 15 - context  -> current_index.index_gap) / 16 + 1);
	float subread_gap = (seg_len - context  -> current_index.index_gap + 1)*1. / iteration_context -> extracted_subreads;
	int subr_i, pos_i;
	

	//LRMprintf("EXTSUB: %s [%d] LEN=%d\t" , iteration_context -> read_name, this_seg_id , seg_len);
	for(subr_i = 0; subr_i < iteration_context -> extracted_subreads; subr_i++){
		pos_i = subr_i * subread_gap;
		iteration_context -> subread_offsets[this_seg_id][subr_i]=pos_i;

		//#warning "FOR COMPARISON ONLY ===================="
		//continue;

		int highest_qual = -1;
		int total_qual = 0;
		int search_end = pos_i + subread_gap - 1,  search_end_1 = pos_i + 15 + context  -> current_index.index_gap;

		for(; pos_i < search_end_1; pos_i++)
			total_qual += seg_qual[ pos_i ];
		highest_qual = total_qual;

		for(; pos_i < search_end; pos_i++){
			total_qual += seg_qual[ pos_i ];
			total_qual -= seg_qual[ pos_i -15 - context  -> current_index.index_gap];
			if(total_qual > highest_qual) {
				highest_qual = total_qual;
				iteration_context -> subread_offsets[this_seg_id][subr_i]=pos_i-14 - context  -> current_index.index_gap;
			}
		}
	//	LRMprintf("%d:%d\t", (int)(subr_i * subread_gap), iteration_context -> subread_offsets[this_seg_id][subr_i]);
	}
	//LRMprintf("\n");
}

void LRMdo_one_voting_read_segment(LRMcontext_t * context, LRMthread_context_t * thread_context, LRMread_iteration_context_t * iteration_context, int this_seg_id){
	LRMinit_gene_vote((& iteration_context-> vote_table));

	char * seg_text = iteration_context -> segment_texts[this_seg_id];
	int seg_len = iteration_context -> segment_lengths[this_seg_id];

	//LRMprintf("READ %s SEG %d REV %d [%dbp] at %p : %.*s\n", iteration_context -> read_name, this_seg_id, iteration_context -> is_reversed, seg_len, seg_text,  seg_len, seg_text);
	LRMdo_one_voting_read_segment_extraction(context, thread_context,iteration_context , this_seg_id);
	
	//LRMprintf("Extract subread from %d bp seg : %d\n", seg_len, iteration_context -> extracted_subreads);
	int this_subread_no, this_gap_offset;
	for(this_subread_no=0; this_subread_no< iteration_context ->extracted_subreads;this_subread_no++){
		for(this_gap_offset=0; this_gap_offset<context  -> current_index.index_gap; this_gap_offset++){
			int this_subread_offset = this_gap_offset + iteration_context -> subread_offsets[this_seg_id][this_subread_no];
			
			char * subread_string = seg_text + this_subread_offset;
			LRMgehash_key_t subread_integer = LRMgenekey2int(subread_string);
			
			LRMgehash_go_tolerance(context, thread_context, iteration_context,& context->current_index, subread_integer , this_subread_offset, seg_len, iteration_context -> is_reversed, & iteration_context-> vote_table, context -> max_indel_length, this_subread_no, context -> max_mismatched_bases_in_subread);
		}
	}

	if(0){
		LRMprintf("\nREAD %s [seg %d] STAGE %s : %.*s\n", iteration_context -> read_name, this_seg_id, iteration_context -> is_reversed?"NEG":"POS", seg_len, seg_text);
		LRMprint_v(context, iteration_context, 2);
	}
	LRMdo_one_voting_read_process_votetab(context, thread_context, iteration_context, this_seg_id);
}

int LRMfind_subread_end(int len, int total_subreads, int subread){
	return subread * 16;
}

void LRMdo_one_voting_insert_chro_events(LRMcontext_t * context, LRMthread_context_t * thread_context, LRMread_iteration_context_t * iteration_context, int seg_id){
	int x2;
	
	LRMread_mapping_result_t * mapping_result = context -> read_mapping_results + iteration_context -> read_no_in_chunk;
	LRMsegment_mapping_result_t * seg_result = LRMseg_fetch_result( mapping_result, seg_id );
	
	if(context -> do_junction_detection)
		for(x2 = 0; x2 < LRMSEGMENT_MAX_CANDIDATES; x2++){
			LRMsegment_mapping_candidate_t * cand_res = seg_result -> candidates + x2;
			//LRMprintf("TRY INSERT JUNCTION: [%d] , VOTE=%d, 2ndVOTE=%d\n", x2, cand_res -> votes, cand_res -> secondary_votes);
			if(cand_res -> secondary_votes > 0){
				LRMevent_t new_event;
				memset(&new_event, 0, sizeof(LRMevent_t));
				new_event.event_type = LRMEVENT_TYPE_JUNCTION;
				new_event.small_side = - 1 + min(cand_res->first_base_position, cand_res->secondary_position) + cand_res->junction_split_point + cand_res->junction_left_offset_indels;
				new_event.large_side = max(cand_res->first_base_position, cand_res->secondary_position) + cand_res->junction_split_point;
				new_event.masks = cand_res-> junction_is_GT_AG?LRM_EVENT_IS_GT_AT_DONOR:0;

				//LRMprintf("INSERT JUNCTION EVENT: %u~%u\n", new_event.small_side, new_event.large_side);
				
				int retv = LRMchro_event_new(context, thread_context, iteration_context, &new_event);
				if(retv) return;
			}
		}
	//END: if context -> do_junction_detection
	
	// find and insert indels
	for(x2 = 0; x2 < LRMSEGMENT_MAX_CANDIDATES; x2++){
		LRMsegment_mapping_candidate_t * cand_res = seg_result -> candidates + x2;
		int last_correct_subread = cand_res -> indel_records[1]-1, last_indel = 0;
		int indel_i = 0;

		if(0){
			char ptxt[100];
			LRMpos2txt(context, cand_res -> first_base_position, ptxt);
			LRMprintf("CANDIDATE %s [SEG-%d][%d] START AT %s\n", iteration_context -> read_name, seg_id, x2, ptxt);
		}

		for(indel_i=1; indel_i<LRMMAX_INDEL_SECTIONS; indel_i++){
			//LRMprintf("CANDIDATE   INDEL[%d] = %d %d %d\n", indel_i,  cand_res -> indel_records[indel_i*3],  cand_res -> indel_records[indel_i*3+1],  cand_res -> indel_records[indel_i*3+2] );
			if( cand_res -> indel_records[indel_i*3]<1)break;

			int next_correct_subread = cand_res -> indel_records[indel_i*3] - 1;
			int last_correct_base = iteration_context -> subread_offsets[seg_id][last_correct_subread] - 10;
			int first_correct_base = iteration_context -> subread_offsets[seg_id][next_correct_subread]+ 13;

			int expected_indels_in_region=cand_res->indel_records[indel_i*3+2] - last_indel;
			last_correct_base = max(0, last_correct_base);
			last_correct_base = min(iteration_context -> read_length-1, last_correct_base);
			first_correct_base = min(first_correct_base, iteration_context -> read_length-1);
			first_correct_base = max(0, first_correct_base);
			first_correct_base = max(first_correct_base, last_correct_base);
			last_correct_subread = cand_res->indel_records[indel_i*3+1]-1;

			//LRMprintf("CANDIDATE   EXPINDEL=%d  , GAP_BASES = %d, %d\n", expected_indels_in_region, first_correct_base, last_correct_base);

			if(abs(expected_indels_in_region) <= context -> max_indel_length && first_correct_base - last_correct_base > 1){
				int currently_reversed = 1;
				char * corrected_read= iteration_context -> segment_texts[seg_id];
				
				if(( (cand_res -> masks & LRMIS_NEGATIVE_STRAND ) == 0 &&  currently_reversed) || 
  				  ((  cand_res -> masks & LRMIS_NEGATIVE_STRAND ) != 0 && !currently_reversed)) {
					LRMreverse_read( corrected_read , iteration_context -> segment_lengths[seg_id] );
					currently_reversed = !currently_reversed;
				}
				unsigned int chro_cursor = cand_res -> first_base_position + last_correct_base + last_indel, total_mismatched;
				int move_i, indel_movements = LRMindel_dynamic_search(context, thread_context, - expected_indels_in_region /* inversed definition */ , chro_cursor, corrected_read,  last_correct_base, first_correct_base, &total_mismatched);
				//LRMprintf("%s from %d MOVES=%s\n", (cand_res -> masks & LRMIS_NEGATIVE_STRAND )?"REV":"STD", last_correct_base , indel_movement_buff);

				if(total_mismatched <= 1 || (total_mismatched <= 2 && first_correct_base - last_correct_base > 30) || (total_mismatched <= 10 && first_correct_base - last_correct_base > 100)){
					int current_chr=-1, current_len = 0;
					for(move_i = 0; move_i < 1+ indel_movements; move_i++){
						int nch = thread_context -> dynamic_programming_movement_buffer[move_i];
						nch = (nch=='X')?'M':nch;
						if(current_chr!=nch){
							if(current_chr>0 && current_chr != 'M'){
								LRMevent_t new_event;
								memset(&new_event, 0, sizeof(LRMevent_t));
								new_event.indel_length = current_chr == 'D' ? current_len : - current_len;
								new_event.event_type = LRMEVENT_TYPE_INDEL;
								new_event.large_side = chro_cursor;
								new_event.small_side = current_chr == 'D' ? chro_cursor - current_len - 1 : (chro_cursor - 1);
								new_event.masks = cand_res-> junction_is_GT_AG?LRM_EVENT_IS_GT_AT_DONOR:0;
								
								if(0){
									char p1txt[100], p2txt[100], p0txt[100];
									LRMpos2txt(context, new_event.small_side , p1txt);
									LRMpos2txt(context, new_event.large_side , p2txt);
									LRMpos2txt(context, cand_res -> first_base_position , p0txt);
									if(1|| ( new_event.small_side >=  197828782 - 3 && new_event.small_side <= 197828782 + 5)){
										LRMprintf("\nINSERT INDEL EVENT FROM %s: %s~%s ; LEN=%d\n", iteration_context -> read_name, p1txt, p2txt, new_event.indel_length);
										LRMprintf("INSERT INDEL AT %s + %d + %d\n" , p0txt, last_correct_base, last_indel);
										LRMprintf("%s MOVES=%s\n\n", (cand_res -> masks & LRMIS_NEGATIVE_STRAND )?"REV":"STD" , thread_context -> dynamic_programming_movement_buffer);
									}
								}

								int retv = LRMchro_event_new(context, thread_context, iteration_context, &new_event);
								if(retv) return;
							}
							current_chr = nch;
							current_len = 0;
						}
						current_len++;
						if(nch !='I') chro_cursor ++;
					}
				}
				if(currently_reversed == 0) LRMreverse_read( corrected_read , iteration_context -> segment_lengths[seg_id] );
			}
			last_indel = cand_res->indel_records[indel_i*3+2];
		}
	}
}

void LRMdo_one_voting_read(LRMcontext_t * context, LRMthread_context_t * thread_context, LRMread_iteration_context_t * iteration_context){
	LRMread_mapping_result_t * mapping_result = context -> read_mapping_results + iteration_context -> read_no_in_chunk;
	if(iteration_context -> total_segments < 1) return;
	
	mapping_result -> segment_results = malloc(sizeof(LRMsegment_mapping_result_t) * iteration_context->total_segments);
	memset(mapping_result -> segment_results, 0, sizeof(LRMsegment_mapping_result_t) * iteration_context->total_segments);
	
	for(iteration_context->is_reversed = 0; iteration_context->is_reversed<2; iteration_context->is_reversed++){
		int seg_id;
		for(seg_id=0; seg_id<iteration_context -> total_segments; seg_id++){
			LRMdo_one_voting_read_segment(context, thread_context, iteration_context, seg_id);
			if(iteration_context->is_reversed) LRMdo_one_voting_insert_chro_events(context, thread_context, iteration_context, seg_id);
		}
		
		if(0 == iteration_context->is_reversed) LRMreverse_read_and_qual(context, thread_context, iteration_context);
	}
}


#define LRMSEGMENT_MAX_ANCHOR_POINTERS 50

typedef struct{
	unsigned int read_head_pos;
	unsigned int votes;
	short masks;
	unsigned short cand_number;
	unsigned int last_seg_start_pos, second_last_seg_start_pos;
	int segment_number;
	int segment_id[ LRMMAX_READ_LENGTH / LRMSEGMENT_MIN_LENGTH ];
	int realign_cand_id[ LRMMAX_READ_LENGTH / LRMSEGMENT_MIN_LENGTH ];
	long long last_distance;
	int last_votes;
} LRMread_final_candidate_t;

void LRMfix_cigar(LRMcontext_t * context, LRMthread_context_t *  thread_context,  LRMread_iteration_context_t * iteration_context, char * cigar){
	int ci, nch;
	unsigned int tmpi = 0;
	unsigned int lastlen = 0;
	int lastopt = 0, outi = 0;
	for(ci=0; 0!=(nch=cigar[ci]); ci++){
		if(nch <= '9' && nch >= '0'){
			tmpi = 10*tmpi +(nch-'0');
		}else{
			if(nch >= 'A' && nch <= 'Z'){
				if(nch != lastopt){
					if(lastlen>0)
						outi+=sprintf(cigar+outi, "%u%c", lastlen, lastopt);
					lastopt = nch;
					lastlen = 0;
				}
				lastlen += tmpi;
			}
			tmpi = 0;
		}
	}
	if(lastlen>0)outi+=sprintf(cigar+outi, "%u%c", lastlen, lastopt);
}

long long LRMcalculate_written_chro_pos(LRMcontext_t * context, LRMthread_context_t * thread_context, LRMread_iteration_context_t * iteration_context, int written_read_cursor, int start_read_cursor, char * cigar, unsigned int start_chro_pos){
	if(written_read_cursor <=start_read_cursor) return start_chro_pos;
	int tmpi = 0;
	int ci=0, nch;

	while(0!=(nch = cigar[ci++])){
		if(nch <='9' && nch >= '0') tmpi = tmpi*10+(nch-'0');
		else{
			if(nch == 'M'||nch == 'S' ||nch == 'N'||nch == 'D') start_chro_pos += tmpi;
			if(nch == 'M'||nch == 'S' ||nch == 'I') start_read_cursor += tmpi;
			if(start_read_cursor > written_read_cursor) {
				if(nch == 'I') return start_chro_pos;
				else return start_chro_pos - (start_read_cursor - written_read_cursor);
			}
			
			tmpi = 0;
		}
	}
	return -1;
}

#define update_read_res { if(read_res -> votes < selected_cand_list -> votes) { read_res -> votes = selected_cand_list -> votes ; read_res -> final_pos = selected_cand_list -> read_head_pos; read_res -> masks = selected_cand_list -> masks; read_res -> best_candidate = selected_cand_list;}  }

int LRMread_final_result_merge(LRMcontext_t * context, LRMthread_context_t * thread_context, LRMread_iteration_context_t * iteration_context, LRMread_mapping_result_t * read_res){
	int x1, x2, x3;
	
	read_res -> best_candidate = NULL;
	ArrayList * entry_tab = ArrayListCreate(100);	// [ LRMread_final_candidate_t, ... ]
	ArrayListSetDeallocationFunction(entry_tab, free);

	for(x1=0;x1<iteration_context->total_segments;x1++){
		int segment_head_moved = iteration_context -> segment_texts [x1] - iteration_context -> read_text;
		int cond_found = 0;
		for(x2 = 0; x2 < LRMMERGING_MAX_CANDIDATES; x2++){
			int seg_cand_votes = iteration_context -> segment_best_votes[x1][x2];
			if(seg_cand_votes < 1) break;
			unsigned int seg_cand_segpos = iteration_context -> segment_best_pos[x1][x2];
			int seg_cand_masks = iteration_context -> segment_best_masks[x1][x2], is_closely_related = 0;

			for(x3 = 0; x3 < LRMMERGING_MAX_CANDIDATES; x3++){
				long long int delta = iteration_context -> segment_best_pos[x1][x3];
				delta -= seg_cand_segpos;
				if(delta < 0 && abs(delta)<	LRMREAD_RESULT_MERGE_TOLERANCE) is_closely_related = 1;
			}

			if(is_closely_related) continue;
			
			if(0){
				char postxt[100];
				LRMpos2txt(context, seg_cand_segpos, postxt);
				LRMprintf("Trying %s [seg %d] [cand %d] : pos=%s, votes=%d\n", iteration_context -> read_name, x1, x2, postxt, seg_cand_votes);
			}

			unsigned int seg_pos_moved = seg_cand_segpos - segment_head_moved, cani;
			
			for(cani = 0; cani < entry_tab -> numOfElements; cani++){
				long long min_positive_dist = 0x7fffffff;
				LRMread_final_candidate_t * selected_cand_list = NULL;
				LRMread_final_candidate_t * cand_rec = ArrayListGet(entry_tab, cani);
					
				long long dist0 = seg_cand_segpos ;
				if(cand_rec -> segment_id[cand_rec -> segment_number-1] < x1)dist0 -= cand_rec -> last_seg_start_pos;
				else dist0 -= cand_rec -> second_last_seg_start_pos;
					

				if( cand_rec -> masks & LRMIS_NEGATIVE_STRAND ) dist0 = -dist0;
				if(0){
					char postxt[100];
					LRMpos2txt(context, cand_rec -> last_seg_start_pos, postxt);
					LRMprintf(" Old Cand #%p : Old pos=%s, votes=%d, dist=%lld, masks=%d, %d\n", cand_rec, postxt, seg_cand_votes, dist0, seg_cand_masks, cand_rec -> masks );
				}
					
				if(( seg_cand_masks & LRMIS_NEGATIVE_STRAND ) == ( cand_rec -> masks & LRMIS_NEGATIVE_STRAND ) && dist0 < LRMREAD_RESULT_MERGE_TOLERANCE && dist0 >= 0 && (cand_rec -> segment_id[cand_rec -> segment_number-1] < x1 || (  cand_rec -> segment_id[cand_rec -> segment_number-1] == x1 && cand_rec -> last_distance > dist0 )) && dist0 < min_positive_dist){
					cond_found = 1;
					selected_cand_list = cand_rec;
					min_positive_dist = dist0;
				}

				if(selected_cand_list){
					if(selected_cand_list -> segment_id[selected_cand_list -> segment_number-1] == x1){
						selected_cand_list -> segment_number --;
						selected_cand_list -> votes -= selected_cand_list -> last_votes;
					}else selected_cand_list -> second_last_seg_start_pos = selected_cand_list -> last_seg_start_pos;

					selected_cand_list -> votes += seg_cand_votes;
					selected_cand_list -> segment_id[selected_cand_list -> segment_number] = x1;
					selected_cand_list -> realign_cand_id[selected_cand_list -> segment_number] = x2;
					selected_cand_list -> segment_number ++;
					assert(selected_cand_list -> segment_number  <=  LRMMAX_READ_LENGTH / LRMSEGMENT_MIN_LENGTH);
					selected_cand_list -> last_seg_start_pos = seg_cand_segpos;
					selected_cand_list -> last_votes = seg_cand_votes;
					selected_cand_list -> last_distance = min_positive_dist;
					if(0) LRMprintf(" Added to Old Cand #%p: Seg[%d] %d (V=%d); BEST CAND : %p (V=%d)\n", selected_cand_list, selected_cand_list -> segment_number - 1, x1, selected_cand_list -> votes, read_res -> best_candidate, read_res -> votes);
					update_read_res;
				}
				if(cond_found)break;
			}

			if(!cond_found){
				LRMread_final_candidate_t * selected_cand_list = malloc(sizeof(LRMread_final_candidate_t));
				memset(selected_cand_list, 0, sizeof(LRMread_final_candidate_t));
				selected_cand_list -> read_head_pos = seg_pos_moved;
				selected_cand_list -> votes = seg_cand_votes;
				selected_cand_list -> masks = seg_cand_masks;
				selected_cand_list -> segment_id[0] = x1;		
				selected_cand_list -> realign_cand_id[0] = x2;						
				selected_cand_list -> segment_number = 1;
				selected_cand_list -> last_seg_start_pos = seg_cand_segpos;
				selected_cand_list -> second_last_seg_start_pos = 0;
				selected_cand_list -> last_votes = seg_cand_votes;
				selected_cand_list -> last_distance = 0;

				ArrayListPush(entry_tab, selected_cand_list);
				update_read_res;
			}
		}
	}

	//LRMprintf("FINAL BEST CANDS : %p\n", read_res -> best_candidate);

	if(read_res -> best_candidate){
		LRMread_final_candidate_t * best_cand_rec = read_res -> best_candidate;
		long long final_mapping_pos = -1;
		long long merged_chro_cursor = -1, merged_read_cursor = -1;
		char * target_cigar = iteration_context -> merged_cigar;
		int target_cigar_ptr = 0, last_seg_last_base_read = -1;
		long long last_seg_last_base_chro = -1;

		memset(target_cigar, 0, LRMMERGE_CIGAR_SIZE+1);

		if(best_cand_rec -> masks & LRMIS_NEGATIVE_STRAND ) LRMreverse_read(iteration_context -> read_text, iteration_context -> read_length);
		

		// remove "S" sections from the middle parts

		for(x1 = 0; x1 < best_cand_rec -> segment_number ; x1 ++){
			int this_seg_id = ( best_cand_rec -> masks & LRMIS_NEGATIVE_STRAND ) ? best_cand_rec -> segment_id[best_cand_rec -> segment_number - x1 - 1] : best_cand_rec -> segment_id[x1];
			int this_segcand_id = ( best_cand_rec -> masks & LRMIS_NEGATIVE_STRAND ) ? best_cand_rec -> realign_cand_id[best_cand_rec -> segment_number - x1 - 1] : best_cand_rec -> realign_cand_id[x1];
			int cigar_max = strlen( iteration_context -> segment_cigars[this_seg_id][this_segcand_id]) + 1;
			cigar_max = min( LRMMERGE_CIGAR_SIZE, cigar_max );
			char * new_cigar = malloc(cigar_max+1);
			new_cigar[0]=0;
			int new_cigar_ptr = 0;
			int cci = 0, tmpi = 0, nch, is_first_section = 1;

			while(0!=(nch = iteration_context -> segment_cigars[this_seg_id][this_segcand_id] [cci++])){
				if(nch >='0' && nch <='9')
					tmpi = tmpi*10+(nch - '0');
				else{
					if(nch == 'S'){
						if(is_first_section == ((best_cand_rec -> masks & LRMIS_NEGATIVE_STRAND)?0:1))
							iteration_context -> segment_texts[this_seg_id] += tmpi;
						if(is_first_section)
							iteration_context -> segment_best_pos[this_seg_id][this_segcand_id] += tmpi;
						iteration_context -> segment_lengths[this_seg_id] -= tmpi;
					}else{
						//LRMprintf("CIGAR OLD:%s, NEW: %p, LEN=%d NCH='%c'(%d)     PTR SIZE=%d\n", iteration_context -> segment_cigars[this_seg_id][this_segcand_id], new_cigar, tmpi, nch, nch, cigar_max - new_cigar_ptr);
						//fflush(stderr);
						new_cigar_ptr += snprintf(new_cigar+new_cigar_ptr, cigar_max - new_cigar_ptr, "%d%c", tmpi, nch);
			//			LRMprintf("CIGAR NEW: %d + %s\n\n", new_cigar_ptr, new_cigar);
						//fflush(stderr);
					}
					is_first_section = 0;
					tmpi=0;
				}
			}
			strncpy(iteration_context -> segment_cigars[this_seg_id][this_segcand_id], new_cigar, LRMSEGMENT_CIGAR_SIZE);
			free(new_cigar);
		}


		long long validateCigar_cursor = -1;
		int written_read_cursor = 0;
		for(x1 = 0; x1 < best_cand_rec -> segment_number ; x1 ++){
			int is_first_section = 1;
			int this_seg_id = ( best_cand_rec -> masks & LRMIS_NEGATIVE_STRAND ) ? best_cand_rec -> segment_id[best_cand_rec -> segment_number - x1 - 1] : best_cand_rec -> segment_id[x1];
			int this_segcand_id = ( best_cand_rec -> masks & LRMIS_NEGATIVE_STRAND ) ? best_cand_rec -> realign_cand_id[best_cand_rec -> segment_number - x1 - 1] : best_cand_rec -> realign_cand_id[x1];

			if(1){
				int this_start_offset = iteration_context -> segment_texts[this_seg_id] - iteration_context -> read_text;

				assert(iteration_context -> segment_best_pos[this_seg_id][this_segcand_id] >=this_start_offset);

				if(best_cand_rec -> masks & LRMIS_NEGATIVE_STRAND ){
					int R_pos = iteration_context -> read_length - (iteration_context -> segment_texts[this_seg_id] - iteration_context -> read_text + iteration_context -> segment_lengths[this_seg_id]);
					assert(iteration_context -> segment_best_pos[this_seg_id][this_segcand_id] >=R_pos);
					this_start_offset = R_pos;
				}
				if(validateCigar_cursor<0) validateCigar_cursor = this_start_offset;

				if(final_mapping_pos < 0){
					if(this_start_offset > 0)if(target_cigar_ptr < LRMMERGE_CIGAR_SIZE)target_cigar_ptr +=  snprintf( target_cigar + target_cigar_ptr, LRMMERGE_CIGAR_SIZE - target_cigar_ptr, "%dS", this_start_offset);
					final_mapping_pos = iteration_context -> segment_best_pos[this_seg_id][this_segcand_id] - this_start_offset;
				}
				merged_chro_cursor =  iteration_context -> segment_best_pos[this_seg_id][this_segcand_id];
				merged_read_cursor = this_start_offset;

				if(1){
					char postxt[100];
					LRMpos2txt(context, iteration_context -> segment_best_pos[this_seg_id][this_segcand_id], postxt );
					LRMprintf("FINAL MERGING : %s [%d / %d] ; CHRO POS = %s (%s) ; READ POS = %d ; CIGAR = %s\n", iteration_context -> read_name, this_seg_id, iteration_context -> total_segments, postxt, (best_cand_rec -> masks & LRMIS_NEGATIVE_STRAND)?"NEG":"POS", this_start_offset, iteration_context -> segment_cigars[this_seg_id][this_segcand_id] );
				}

				if(1){
					long long chro_pos_from_new = LRMcalculate_written_chro_pos(context, thread_context, iteration_context, written_read_cursor, merged_read_cursor, iteration_context -> segment_cigars[this_seg_id][this_segcand_id], merged_chro_cursor);
					if(chro_pos_from_new < 0) continue;

					long long delta = 0;

					if(last_seg_last_base_chro>0){
						delta =chro_pos_from_new - last_seg_last_base_chro;
						if(this_start_offset > last_seg_last_base_read) {
							if(target_cigar_ptr < LRMMERGE_CIGAR_SIZE)target_cigar_ptr +=  snprintf( target_cigar + target_cigar_ptr, LRMMERGE_CIGAR_SIZE - target_cigar_ptr, "%dM", this_start_offset -  last_seg_last_base_read);
							delta -= this_start_offset -  last_seg_last_base_read;
							written_read_cursor = this_start_offset;
						}
					}

					if(0&&abs(delta) <= context -> max_indel_length  && LRMDYNAMIC_MAXIMUM_GAP_LENGTH -1 > this_start_offset - last_seg_last_base_read - min(0, delta)){
						unsigned int total_mismatched_bases = 0;
						int move_i, moves = LRMindel_dynamic_search(context, thread_context,-(int)delta, last_seg_last_base_chro,  iteration_context -> read_text , last_seg_last_base_read, this_start_offset , &total_mismatched_bases);
						if(moves > 0){
							int tmpi = 0;
							for(move_i = 0; move_i < moves; move_i++){
								tmpi ++;
								char nch = thread_context -> dynamic_programming_indel_movement_buf[move_i];
								nch =(nch == 'X'?'M':nch);
								char nnh = thread_context -> dynamic_programming_indel_movement_buf[move_i + 1];
								nnh =(nnh == 'X'?'M':nnh);
								if(nnh != nch){
									if(target_cigar_ptr < LRMMERGE_CIGAR_SIZE)target_cigar_ptr +=  snprintf( target_cigar + target_cigar_ptr, LRMMERGE_CIGAR_SIZE - target_cigar_ptr, "%d%c", tmpi, nch);
									tmpi = 0;
								}
							}
						}
					}else{
						if(delta){
							if(target_cigar_ptr < LRMMERGE_CIGAR_SIZE)target_cigar_ptr +=  snprintf( target_cigar + target_cigar_ptr, LRMMERGE_CIGAR_SIZE - target_cigar_ptr, "%lld%c" , abs(delta), delta > 0?'N':'I' );
							if(delta < 0)
								written_read_cursor += abs(delta);
						}
					}
				}

				if(merged_chro_cursor >= 0){
					int cci = 0, tmpi = 0, nch;
					while(0!=(nch = iteration_context -> segment_cigars[this_seg_id][this_segcand_id] [cci++])){
						if(nch >='0' && nch <='9')
							tmpi = tmpi*10+(nch - '0');
						else{
							if(nch == 'M' || nch == 'S' || nch == 'D' || nch == 'N')merged_chro_cursor += tmpi;
							if(nch == 'M' || nch == 'S' || nch == 'I') merged_read_cursor += tmpi;
							
							if(written_read_cursor <= merged_read_cursor){
									int writting_optlen = tmpi;
									if(nch == 'M' || nch == 'S' || nch == 'I') writting_optlen = (merged_read_cursor - written_read_cursor);
									if(target_cigar_ptr < LRMMERGE_CIGAR_SIZE)target_cigar_ptr +=  snprintf( target_cigar + target_cigar_ptr, LRMMERGE_CIGAR_SIZE - target_cigar_ptr, "%d%c" , writting_optlen, nch);
									written_read_cursor = merged_read_cursor;
							}
							tmpi = 0;
							is_first_section = 0;
						}
					}
					last_seg_last_base_read = merged_read_cursor;
					last_seg_last_base_chro = merged_chro_cursor;
				}
			}
		}
		if(last_seg_last_base_read < iteration_context -> read_length){
			if(target_cigar_ptr < LRMMERGE_CIGAR_SIZE)target_cigar_ptr += snprintf( target_cigar + target_cigar_ptr, LRMMERGE_CIGAR_SIZE - target_cigar_ptr, "%dS", iteration_context -> read_length - last_seg_last_base_read );
		}
		if(best_cand_rec -> masks & LRMIS_NEGATIVE_STRAND ) LRMreverse_read(iteration_context -> read_text, iteration_context -> read_length);

		iteration_context -> merged_position = final_mapping_pos;
		iteration_context -> merged_masks = best_cand_rec -> masks;
		if(1){
			char postxt[100];
			int mapped_length = 0;
			LRMpos2txt(context , final_mapping_pos, postxt);
			LRMprintf("\nFINAL READ %s to %s (%s)\n", iteration_context -> read_name, postxt, best_cand_rec -> masks & LRMIS_NEGATIVE_STRAND?"NEG":"POS");
			int matched_bases = LRMvalidate_mapping(context , iteration_context -> read_text, target_cigar, &context -> current_base_index, final_mapping_pos, best_cand_rec -> masks & LRMIS_NEGATIVE_STRAND, &mapped_length , 1);
			LRMprintf("Matched %d in %d  : %s\n",  matched_bases, mapped_length, target_cigar);
			LRMprintf("\n\n");
		}
	} // end : if the best candidate list is not NULL

	ArrayListDestroy(entry_tab);
	return 0;
}


void LRMdo_one_realign_read(LRMcontext_t * context, LRMthread_context_t * thread_context, LRMread_iteration_context_t * iteration_context){
	int flags=4;
	char * chro_name="*";
	int chro_pos = 0;
	int map_quality = 0;
	int mis_matched = 0;
	int seg_i;

	LRMread_mapping_result_t * read_res = context -> read_mapping_results + iteration_context -> read_no_in_chunk;
	memset(iteration_context -> segment_best_candidate_score, 0, sizeof(int) * LRMMAX_READ_LENGTH / LRMSEGMENT_MIN_LENGTH * LRMMERGING_MAX_CANDIDATES);

	for(seg_i = 0; seg_i < iteration_context -> total_segments ; seg_i++){
		LRMrealign_context_t realign_context;
		int * this_best_scores = iteration_context -> segment_best_candidate_score[seg_i];
		
		int cand_i;
		for(cand_i = 0; cand_i < LRMSEGMENT_MAX_CANDIDATES; cand_i ++){
			LRMsegment_mapping_candidate_t * cand_res = read_res -> segment_results[seg_i].candidates + cand_i;

			if(0){
				char postxt[100];
				LRMpos2txt(context, cand_res -> first_base_position , postxt);
				LRMprintf("TRY REALIGN READ %s [%d , %d] : V=%d ; POS=%s (%s)\n", iteration_context -> read_name, seg_i, cand_i, cand_res -> votes, postxt, (cand_res -> masks & LRMIS_NEGATIVE_STRAND)?"NEG":"POS");
			}
			if(cand_res -> votes < 1) break;
			
			memset(&realign_context, 0, sizeof(LRMrealign_context_t));
			realign_context.current_segment_id = seg_i;
			realign_context.current_candidate_id = cand_i;
			LRMrealign_one_segment(context, thread_context, iteration_context, &realign_context);
			if(realign_context.best_stack_score[0]+realign_context.best_stack_score[1] > this_best_scores[LRMMERGING_MAX_CANDIDATES-1]){
				int replace_i, replace_index = LRMMERGING_MAX_CANDIDATES-1, is_repeated = 0;
				for(replace_i = LRMMERGING_MAX_CANDIDATES-1; replace_i >= 0; replace_i --){
					if(realign_context.best_stack_score[0]+realign_context.best_stack_score[1] > this_best_scores[replace_i])
						replace_index = replace_i;
					if(iteration_context -> segment_best_pos[seg_i][replace_i] == realign_context.best_chro_pos) is_repeated = 1;
				}
				
				if(0 == is_repeated){
					for(replace_i = LRMMERGING_MAX_CANDIDATES - 2 ; replace_i >= replace_index; replace_i--){
						strncpy(iteration_context -> segment_cigars[seg_i][replace_i+1], iteration_context -> segment_cigars[seg_i][replace_i], LRMSEGMENT_CIGAR_SIZE);
						iteration_context -> segment_best_pos[seg_i][replace_i+1] = iteration_context -> segment_best_pos[seg_i][replace_i];
						iteration_context -> segment_best_masks[seg_i][replace_i+1] = iteration_context -> segment_best_masks[seg_i][replace_i];
						iteration_context -> segment_best_candidate_score[seg_i][replace_i+1] = iteration_context -> segment_best_candidate_score[seg_i][replace_i];
						iteration_context -> segment_best_votes[seg_i][replace_i+1] = iteration_context -> segment_best_votes[seg_i][replace_i];
					}
					
					strncpy(iteration_context -> segment_cigars[seg_i][replace_index], realign_context.best_cigar, LRMSEGMENT_CIGAR_SIZE);
					iteration_context -> segment_best_pos[seg_i][replace_index] = realign_context.best_chro_pos;
					iteration_context -> segment_best_masks[seg_i][replace_index] = cand_res -> masks;
					iteration_context -> segment_best_candidate_score[seg_i][replace_index] = realign_context.best_stack_score[0]+realign_context.best_stack_score[1] ;
					iteration_context -> segment_best_votes[seg_i][replace_index] = cand_res -> votes;
				}
			}
		}
	}

	strcpy(iteration_context -> merged_cigar, "*");
	LRMread_final_result_merge(context, thread_context, iteration_context, read_res);

	if(read_res -> votes > 0){
		LRMlocate_gene_position(context, iteration_context -> merged_position, &chro_name, & chro_pos);
		//fprintf(stderr, "RELA: %u =>  rv=%d\n", read_res -> final_pos, rv);
		map_quality = 10;// read_res -> votes + 10;
		if( iteration_context -> merged_masks & LRMIS_NEGATIVE_STRAND){
			flags=16;
			LRMreverse_read_and_qual(context, thread_context, iteration_context);
		}else flags = 0;
	}else flags = 4;

	LRMfix_cigar(context, thread_context, iteration_context, iteration_context -> merged_cigar);

	LRMwrite_chunk_add_buffered_output(context, thread_context, iteration_context, flags, chro_name, chro_pos, map_quality, iteration_context -> merged_cigar, mis_matched);
}

int LRMchunk_read_iteration(LRMcontext_t * context, int thread_id, int task){
	LRMthread_context_t * thread_context = context -> thread_contexts+ thread_id;

	LRMread_iteration_context_t * iteration_context;
	iteration_context = malloc(sizeof(LRMread_iteration_context_t));
	//LRMprintf(" ============  LITR_CONTEXT PTR=%p, SIZE=%lld  \n", iteration_context, sizeof(LRMread_iteration_context_t));
	memset(iteration_context, 0, sizeof(LRMread_iteration_context_t));
	while(1){
		int retv = LRMfetch_next_read(context, thread_context, &iteration_context-> read_length, iteration_context->read_name, iteration_context->read_text, iteration_context->qual_text, &iteration_context -> read_no_in_chunk);
		if(retv) break;

		LRMsplit_read_to_segments(context, iteration_context);
		if(task==LRMRUNNING_STEP_VOTING)
			LRMdo_one_voting_read(context, thread_context, iteration_context);
		else if(task==LRMRUNNING_STEP_REALIGN)
			LRMdo_one_realign_read(context, thread_context, iteration_context);
		else assert(0);
		
		if(iteration_context -> read_no_in_chunk % 2000 == 0)
			LRMprintf("Processing %d-th read for task %d; used %.1f minutes\n", context -> all_processed_reads + iteration_context -> read_no_in_chunk, task, (LRMmiltime() - context -> start_running_time)/60);

		//LRMprintf("R:%s, T:%s\n", iteration_context -> read_name, iteration_context -> read_text);
	}
	free(iteration_context);
	return 0;
}

int LRMfinalise_chunk_reads(LRMcontext_t* context){
	context ->  all_processed_reads += context -> processed_reads_in_chunk;
	return 0;
}

int FIXLENstrcmp(char * fixed_len, char * rname){
        int x=0;
        for(; fixed_len[x]; x++){
                if(rname[x]!=fixed_len[x]) return 1;
        }
        return 0;
}

