#!/usr/bin/perl

## Date and Version
my $version= '3.0';
my $version_date = '05-05-2013';

##################################################
## Documentation POD
##################################################

##################################################
## Modules
##################################################
## Perl modules
use strict;
use warnings;
use diagnostics;
use Data::Dumper;

use Getopt::Long;
use File::Basename;
use Switch;

use Config;
if ($Config{useithreads}) {
	use threads;
	use Thread::Queue;
}

use Fcntl qw/ :flock /;

##################################################
## Globals variables
##################################################

# Initializations
my $Start_time_hr = humanReadableDate();
my $help = undef();
my $DEBUG = 0;
my $Monitoring_interval = 15; # In seconds
my %Parameters;

# Default values
my $Default_model = 'M1';
my $Default_parameters_sharing = 'sep';
my $Default_output_genotype = 'best';
my $Default_multi_alleles_tolerance = 'tol';
my $Default_min_coverage = 5;
my $Default_nb_thread = 4;
my $Default_fis = 0;
my $Default_paraclean = 'false';
my $Default_threshold = 0.001;
my $Default_dirichlet = 'true';
my $Default_skipparalogous = 'false';

my $default_error = 0;
my $default_precision = 0.001;
my $default_optimizer = "newton";
my $default_mask = 'true';

my $default_min_post_probability = 0.95;

my $default_pfasclean = 'false';
my $default_window_width = 4;
my $default_SNPdens_threshold = 0.4;
my $default_H_threshold = 0.8;

##################################################
## Manage options
##################################################

# Get options from command line
GetOptions ('help|h'						=> \$help,
			'alrfile|alr=s'					=> \$Parameters{'ALR_file'},
			'nb_thread|mth:i'				=> \$Parameters{'Number_of_thread'},
			'model|mod:s'					=> \$Parameters{'Reads2snp_model'},
			'parameters_sharing|sharing:s'	=> \$Parameters{'Reads2snp_parameters_sharing'},
			'output_genotype|geno:s'		=> \$Parameters{'Reads2snp_output_genotype'},
			'multi_alleles|mat:s'			=> \$Parameters{'Reads2snp_multi_alleles_tolerance'},
			'min_coverage|min:i'			=> \$Parameters{'Reads2snp_min_coverage'},
			'fis:f'							=> \$Parameters{'Reads2snp_fis'},
			'paraclean|par:s'				=> \$Parameters{'Reads2snp_paraclean'},	
			'threshold|thr:f'				=> \$Parameters{'Reads2snp_paraclean_threshold'},
			'dirichlet|dir:s'				=> \$Parameters{'Reads2snp_paraclean_dirichlet'},
			'skipparalogous|spa:s'			=> \$Parameters{'Reads2snp_paraclean_skipparalogous'},
			'error|err:f'                   => \$Parameters{'Reads2snp_paraclean_error'},
            'precision|pre:f'               => \$Parameters{'Reads2snp_paraclean_precision'},
			'optimizer|opt:s'               => \$Parameters{'Reads2snp_paraclean_optimizer'},
            'mask|kun:s'                    => \$Parameters{'Reads2snp_paraclean_mask'},
            'min_post_probability|gpv:f'    => \$Parameters{'Reads2snp_min_post_probability'},
            'pfasclean|pfc:s'               => \$Parameters{'Reads2snp_pfasclean'},
            'window_width|win:f'            => \$Parameters{'Reads2snp_pfasclean_window_width'},
            'SNPdens_threshold|snp:f'       => \$Parameters{'Reads2snp_pfasclean_SNPdens_threshold'},
            'H_threshold|het:f'             => \$Parameters{'Reads2snp_pfasclean_H_threshold'},
			'output|out:s'					=> \$Parameters{'ALR_gen_file'},
			'output_alleles|all:s'		    => \$Parameters{'Alleles'},
			#~ 'aeb_file|aeb:s'				=> \$Parameters{'Expression_bias_file'},
			#~ 'earr_file|earr:s'			=> \$Parameters{'Error_rate_file'},
			'species_data|data:s'			=> \$Parameters{'Species_data_file'},
			'clean|del:s'					=> \$Parameters{'clean_tmp_files'},
			'exec:s'						=> \$Parameters{'Execution_mode'}
		);

# Display help message if needed
if (defined($help)) {
	displayHelpMessage();
}

# Check parameters
checkParameters(\%Parameters);

# Generate additional output file name
generateOutputfileNames(\%Parameters);

# Backup old data
saveOldResults(\%Parameters);

# Galaxy mode only: create all files that will be recovered by Galaxy from_work_dir command
createFromWorkDirFiles(\%Parameters) if ($Parameters{'Execution_mode'} =~ /galaxy/i);

print Dumper(\%Parameters) if $DEBUG;


##################################################
## Main code
##################################################

# Welcome message
print "###########################################################\n";
print "# Welcome in Multi Reads2snp Galaxy Wrapper (Version $version) #\n"; 
print "###########################################################\n";
print "Start time: " . $Start_time_hr . "\n\n";

# Initializations
my %Status = ( 'Ok' => 0, 'No_output' => 0, 'Warning' => 0, 'Segmentation_fault' => 0, 'Error' => 0);
$Parameters{'Multithread_switch'} = 0;

# Verbose
print 'Selected ALR input file: ' . $Parameters{'ALR_file'} . "\n";
if (defined($Parameters{'Reads2snp_model'}) &&  $Parameters{'Reads2snp_model'} ne "") { print 'Selected model: ' . $Parameters{'Reads2snp_model'} . "\n\n"; }

# Multithreading activation/desactivation and display
toggleMultithreadSwitch(\%Parameters);

# Genotyping - Launch a reads2SNP instance for each contig of the global .alr file generated by sam2alr
if ($Parameters{'Multithread_switch'}) {
	multithreadGenotyping(\%Parameters, \%Status);
} else {
	serialGenotyping(\%Parameters, \%Status);
}

print "\n" . 'Global ALR_GEN output file: ' . $Parameters{'ALR_gen_file'} . "\n";
print 'Global ALLELES output file: ' . $Parameters{'Alleles'} . "\n\n\n";

# Display a summary of the execution (Informatic point of view)
print 'Execution statistics:' . "\n\n";
foreach my $Current_status (keys (%Status)) {
	print ' - ' . $Current_status . ': ' . $Status{$Current_status} . "\n";
}

print "\nStop time: " . humanReadableDate() . "\n";
print "###########################################################\n";
print "#                     End of execution                    #\n"; 
print "###########################################################\n";

##################################################
## Filename management related functions
##################################################

sub generateOutputfileNames {
	
	# Recovers parameters
	my $Ref_to_parameters = shift;
	
	# Some explanations: 
	# In local mode the names of the additionnal output files are derived from the name of the main input file
	# However, the current version of Galaxy Workflow Manager is not able to retrieve output files with non HARD CODED names (The value of the from_work_dir attribute is not interpreted !!)
	# Therefore it's impossible to use derived name in Galaxy mode
	
	if ($Ref_to_parameters->{'Execution_mode'} eq 'local') {
		# Names derived from the main input file name
		my $Input_file_basename = basename($Ref_to_parameters->{'ALR_file'}) ;
		$Ref_to_parameters->{'Main_error'} = $Input_file_basename . '.global_error';
		$Ref_to_parameters->{'Contig_no_output'} = $Input_file_basename . '.no_output_contigs';
		$Ref_to_parameters->{'Contig_with_warning'} = $Input_file_basename .  '.warning_contigs';
		$Ref_to_parameters->{'Contig_with_error'} = $Input_file_basename . '.error_contigs';
		$Ref_to_parameters->{'Contig_with_segfault'} = $Input_file_basename . '.segfault_contigs';
	} else {
		# Hard coded names (Galaxy mode)
		$Ref_to_parameters->{'Main_error'} = 'Reads2SNP_error';
		$Ref_to_parameters->{'Contig_no_output'} = 'Reads2SNP_no_output_contigs';
		$Ref_to_parameters->{'Contig_with_warning'} = 'Reads2SNP_warning_contigs';
		$Ref_to_parameters->{'Contig_with_error'} = 'Reads2SNP_error_contigs';
		$Ref_to_parameters->{'Contig_with_segfault'} = 'Reads2SNP_segfault_contigs';
	}
	
	return 0;
}


##################################################
## Galaxy mode specific functions
##################################################

sub createFromWorkDirFiles {
	
	# Recovers parameters
	my $Ref_to_parameters = shift;
	
	# Initializations
	my @Files_to_generate = ('ALR_gen_file', 'Main_error', 'Species_data_file');
	
	# Some explanations: 
	# In the last versions of Galaxy, an error message is raised when you try to recover a file that does not exists with the from_work_dir command.
	# Therefore, in galaxy mode, all the files requested by a from_work_dir command in the XML file must be created in the Perl wrapper.
		
	foreach my $Files_to_create (@Files_to_generate) {
		if (!-e $Ref_to_parameters->{$Files_to_create}) {
			open(GLOBAL_ERROR, '>' . $Ref_to_parameters->{$Files_to_create}) or die ('Cannot create/open file: ' . $Ref_to_parameters->{$Files_to_create} . "\n");
			close(GLOBAL_ERROR);
		}
	}
}


##################################################
## Backup related functions
##################################################

sub saveOldResults {
	
	# Recovers parameters
	my $Ref_to_parameters = shift;

	# Initializations
	my @list_of_global_file = ('ALR_gen_file', 'Main_error', 'Contig_no_output', 'Contig_with_warning', 'Contig_with_error', 'Contig_with_segfault', 'Species_data_file');
	
	# Add optionnal files to the list if needed
	#~ push(@list_of_global_file, 'Expression_bias_file') if defined($Ref_to_parameters->{'Expression_bias_file'});
	#~ push(@list_of_global_file, 'Error_rate_file') if defined($Ref_to_parameters->{'Error_rate_file'});
	push(@list_of_global_file, 'Alleles') if defined($Ref_to_parameters->{'Alleles'});
	
	# Rename an already existing global file / Special Contig file if needed
	foreach my $Global_file_name (@list_of_global_file) {
		rename($Ref_to_parameters->{$Global_file_name}, $Ref_to_parameters->{$Global_file_name} . '.old.' . $Start_time_hr) if (-e $Ref_to_parameters->{$Global_file_name});
	}
	
	return 0;
}

##################################################################
## Serial/Monothread - Contig processing related functions
##################################################################

sub serialGenotyping {
	
	# Recovers parameters
	my ($Ref_to_parameters, $Ref_to_status) = @_;
	
	# Initializations
	my $Contig_counter = 0;
	my ($Current_file_content, $Current_contig_name) = ("", "");
	
	# Open the global .alr file generated by sam2alr
	open (MAIN_ALR, '<' . $Ref_to_parameters->{'ALR_file'}) or die ('Error: Cannot open/read file: ' . $Ref_to_parameters->{'ALR_file'} . "\n");
	
	print 'Genotyping procedure:' . "\n\n";

	while (my $Current_line = <MAIN_ALR>){

		if ($Current_line =~ /^[^#|@]/) { # If the current line ins't a comment line we analyse it
			
			if ($Current_line =~ /^>([\w\.]+)/) {
				
				if ($Contig_counter > 0) {
					
					processContig($Current_file_content, $Contig_counter, $Current_contig_name, $Ref_to_parameters, $Ref_to_status);
					
					# Reinitializations
					$Current_file_content = "";
				}
				
				# Store the new contig name for the next processContig call
				$Current_contig_name = $1;
				
				# Increase the number of treated contigs
				$Contig_counter++;
			}
			
			$Current_file_content .= $Current_line;
		}
	}

	# Process the last contig of the input file
	processContig($Current_file_content, $Contig_counter, $Current_contig_name, $Ref_to_parameters, $Ref_to_status);
	
	close (MAIN_ALR);
	
	return 0;
}

sub processContig {
	
	# Recovers parameters
	my ($File_content, $Contig_number, $Contig_name, $Ref_to_parameters, $Ref_to_status) = @_;
	
	# Initializations
	$Ref_to_parameters->{'Reads2SNP_input_file'} = basename($Ref_to_parameters->{'ALR_file'}) . '.contig_' . $Contig_number;
	$Ref_to_parameters->{'Reads2SNP_stdout_file'} = $Ref_to_parameters->{'Reads2SNP_input_file'} . '.out';
	$Ref_to_parameters->{'Reads2SNP_error_file'} = $Ref_to_parameters->{'Reads2SNP_input_file'} . '.err';
	$Ref_to_parameters->{'Reads2SNP_output_file'} = $Ref_to_parameters->{'Reads2SNP_input_file'} . '.gen';
	$Ref_to_parameters->{'Reads2SNP_allele'} = $Ref_to_parameters->{'Alleles'} . '.all';
	#~ $Ref_to_parameters->{'Reads2SNP_Expression_bias_file'} = $Ref_to_parameters->{'Reads2SNP_input_file'} . '.aeb';
	#~ $Ref_to_parameters->{'Reads2SNP_Error_rate_file'} = $Ref_to_parameters->{'Reads2SNP_input_file'} . '.earr';
	$Ref_to_parameters->{'Reads2SNP_data_file'} = $Ref_to_parameters->{'Reads2SNP_input_file'} . '.data';
	
	print ' Genotype searching for contig number ' . $Contig_number . ' (' . $Contig_name . ")\n";
	
	# Real processing
	createReads2SNPInputFile($File_content, $Ref_to_parameters);
	executeReads2SNP($Ref_to_parameters);
	
	# Determine output status
	my $Output_status = checkOutputFiles($Ref_to_parameters->{'Reads2SNP_output_file'}, $Ref_to_parameters->{'Reads2SNP_error_file'});
	
	# Update global stats
	$Ref_to_status->{$Output_status}++;

	# Display output status
	dealWithOutputStatus($Output_status, $Contig_number, $Contig_name, $Ref_to_parameters);
	
	# Remove temporary files if requested
	if ($Ref_to_parameters->{'clean_tmp_files'} eq 'yes') {
		if ($Output_status ne "Warning" && $Output_status ne "Error" && $Output_status ne "Segmentation_fault") {
			cleanItAfterMe($Ref_to_parameters->{'Reads2SNP_input_file'}, $Ref_to_parameters->{'Reads2SNP_output_file'}, $Ref_to_parameters->{'Reads2SNP_stdout_file'}, $Ref_to_parameters->{'Reads2SNP_error_file'});
			cleanItAfterMe($Ref_to_parameters->{'Reads2SNP_data_file'});
			#~ cleanItAfterMe($Ref_to_parameters->{'Reads2SNP_Expression_bias_file'}, $Ref_to_parameters->{'Reads2SNP_Error_rate_file'}, $Ref_to_parameters->{'Reads2SNP_data_file'});
		}
	}
	
	return 0;
}

########################################################################
## Parallel/Multithreading - Contig processing related functions
########################################################################

sub toggleMultithreadSwitch {
	
	# Recovers parameters
	my $Ref_to_parameters = shift;
	
	# Toggle the multithread switch
	if ($Ref_to_parameters->{'Number_of_thread'} > 1) { 
		print 'Multithreading: activated (' . $Ref_to_parameters->{'Number_of_thread'} . ' threads)' . "\n\n";
		$Ref_to_parameters->{'Multithread_switch'} = 1;
	} elsif ($Ref_to_parameters->{'Number_of_thread'} == 1) {
		print 'Multithreading: desactivated (Main thread only)' . "\n\n";
	} else {
		print 'Multithreading: desactivated (Not supported)' . "\n\n";
	}
	
	return 0;
}

sub multithreadGenotyping {
	
	# Recovers parameters
	my ($Ref_to_parameters, $Ref_to_status) = @_;
	
	# Initializations
	my $Contig_counter = 0;
	my ($Current_file_content, $Current_contig_name) = ("", "");
	
	# Open the global .alr file generated by sam2alr
	open (MAIN_ALR, '<' . $Ref_to_parameters->{'ALR_file'}) or die ('Error: Cannot open/read file: ' . $Ref_to_parameters->{'ALR_file'} . "\n");

	print 'ALR file analysis procedure:' . "\n\n";

	# Creation of Thread queues if needed
	my $Return_queue = Thread::Queue->new();
	my $ToDo_queue = Thread::Queue->new();

	while (my $Current_line = <MAIN_ALR>){

		if ($Current_line =~ /^[^#|@]/) { # If the current line ins't a comment line we analyse it
			
			if ($Current_line =~ /^>([\w\.]+)/) {
				
				if ($Contig_counter > 0) {
					
					# Add a task in the ToDo list/queue
					$ToDo_queue->enqueue({'Contig_content' => $Current_file_content, 'Contig_counter' => $Contig_counter, 'Contig_name' => $Current_contig_name, 'Flag' => 'TODO'});
					
					# Reinitializations
					$Current_file_content = "";
				} else {
					$Ref_to_parameters->{'Valid_sort_rule'} = getSortRule($1);
				}
				
				# Store the new contig name for the next processContig call
				$Current_contig_name = $1;
				
				# Increase the number of treated contigs
				$Contig_counter++;
			}
			
			$Current_file_content .= $Current_line;
		}
	}
	
	close (MAIN_ALR);

	# Add the final task to the ToDo list/queue
	$ToDo_queue->enqueue({'Contig_content' => $Current_file_content, 'Contig_counter' => $Contig_counter, 'Contig_name' => $Current_contig_name, 'Flag' => 'TODO'});
	
	print '  -> ' . $Contig_counter . ' contigs will be analyzed in parallel by ' . $Ref_to_parameters->{'Number_of_thread'} . ' threads ! (' . $Contig_counter . ' normal task(s) + ' . $Ref_to_parameters->{'Number_of_thread'} . ' exit task(s))' . "\n";
	print '  -> Monitoring interval is set to '. $Monitoring_interval . ' second(s)' . "\n\n";
	
	# Add an exit signal for each thread at the end of the ToDo list/queue
	$ToDo_queue->enqueue({'Flag' => 'EXIT'}) for(1..$Ref_to_parameters->{'Number_of_thread'});
	
	print 'Genotyping procedure:' . "\n\n";
	
	# Working crew hiring
	threads->create("genotypeIt", $ToDo_queue, $Return_queue, $Ref_to_parameters) for(1..$Ref_to_parameters->{'Number_of_thread'});

	# Wait for all tasks to be done
	#~ sleep $Monitoring_interval while threads->list(threads::running);
	while (threads->list(threads::running)) {
		print '  Status update - Number of uncomplete tasks - ' . $ToDo_queue->pending() . ' task(s)' . "\n";
		sleep $Monitoring_interval;
	}
	
	print "\n" . '  -> All contigs have been genotyped !!' . "\n"; 

	# Update status hash
	while(my $Status = $Return_queue->dequeue_nb()) {
		$Ref_to_status->{$Status}++;
	}
	
	# Sort output files if there is something to sort
	if (-e $Ref_to_parameters->{'ALR_gen_file'} && ! -z $Ref_to_parameters->{'ALR_gen_file'}) {

		# Sort the ALR_GEN file by contig number (Mandatory to be able to use ParaClean later)
		sortAlrGenByRealContigNumber($Ref_to_parameters);
		
		# Sort the Species data file by contig number (Can be commented for faster processing)
		sortSpeciesDataFile($Ref_to_parameters);
	}
	
	# Sort Alleles file by contig number
	if (-e $Ref_to_parameters->{'Alleles'} && ! -z $Ref_to_parameters->{'Alleles'}) {
		sortAllelesFile($Ref_to_parameters);
	}
	
	# Sort the Species data file by contig number (Can be commented for faster processing)
	sortSpeciesDataFile($Ref_to_parameters);
	
	return 0;
}

sub genotypeIt {
	
	# Recovers parameters
	my ($in_queue, $out_queue, $Ref_to_parameters) = @_;
	
	while(my $job_data = $in_queue->dequeue()) {
		# If there is no more work to do the worker is free to go
		last if $job_data->{'Flag'} eq 'EXIT';
		
		# Make the workers work hard
		print '  Thread ' . threads->self->tid() . ' - NEW JOB - Genotype searching for contig number ' . $job_data->{'Contig_counter'} . ' in progress (' . $job_data->{'Contig_name'} . ")\n" if $DEBUG;
		
		# Initializations
		$Ref_to_parameters->{'Reads2SNP_input_file'} = basename($Ref_to_parameters->{'ALR_file'}) . '.contig_' . $job_data->{'Contig_counter'};
		$Ref_to_parameters->{'Reads2SNP_stdout_file'} = $Ref_to_parameters->{'Reads2SNP_input_file'} . '.out';
		$Ref_to_parameters->{'Reads2SNP_error_file'} = $Ref_to_parameters->{'Reads2SNP_input_file'} . '.err';
		$Ref_to_parameters->{'Reads2SNP_output_file'} = $Ref_to_parameters->{'Reads2SNP_input_file'} . '.gen';
		$Ref_to_parameters->{'Reads2SNP_allele'} = $Ref_to_parameters->{'Reads2SNP_input_file'} . '.all';
		#~ $Ref_to_parameters->{'Reads2SNP_Expression_bias_file'} = $Ref_to_parameters->{'Reads2SNP_input_file'} . '.aeb';
		#~ $Ref_to_parameters->{'Reads2SNP_Error_rate_file'} = $Ref_to_parameters->{'Reads2SNP_input_file'} . '.earr';
		$Ref_to_parameters->{'Reads2SNP_data_file'} = $Ref_to_parameters->{'Reads2SNP_input_file'} . '.data';
		
		# Real processing
		createReads2SNPInputFile($job_data->{'Contig_content'}, $Ref_to_parameters);
		executeReads2SNP($Ref_to_parameters);
		
		# Determine output status
		my $Output_status = checkOutputFiles($Ref_to_parameters->{'Reads2SNP_output_file'}, $Ref_to_parameters->{'Reads2SNP_error_file'});

		# Display output status
		dealWithOutputStatus($Output_status, $job_data->{'Contig_counter'}, $job_data->{'Contig_name'}, $Ref_to_parameters);
		
		# Remove temporary files if requested
		if ($Ref_to_parameters->{'clean_tmp_files'} eq 'yes') {
			if ($Output_status ne "Warning" && $Output_status ne "Error" && $Output_status ne "Segmentation_fault") {
				cleanItAfterMe($Ref_to_parameters->{'Reads2SNP_input_file'}, $Ref_to_parameters->{'Reads2SNP_output_file'}, $Ref_to_parameters->{'Reads2SNP_stdout_file'}, $Ref_to_parameters->{'Reads2SNP_error_file'}, $Ref_to_parameters->{'Reads2SNP_allele'});
				cleanItAfterMe($Ref_to_parameters->{'Reads2SNP_data_file'});
				#~ cleanItAfterMe($Ref_to_parameters->{'Reads2SNP_Expression_bias_file'}, $Ref_to_parameters->{'Reads2SNP_Error_rate_file'}, $Ref_to_parameters->{'Reads2SNP_data_file'});
			}
		}

		$out_queue->enqueue($Output_status);
	}

	# End of the working day
	threads->detach;
}

sub sortAlrGenByRealContigNumber {
	
	# Recovers parameters
	my $Ref_to_parameters = shift;
	
	# Initializations
	my $Contig_counter = 0;
	my ($Unsorted_filename, $Current_file_content, $Current_contig_name, $Sort_rule) = ('Unsorted_gen_file.alr_gen', '', '', $Ref_to_parameters->{'Valid_sort_rule'});
	my %Contigs_data;
	
	# Rename and open the unsorted .alr_gen file and store all contigs into a hash table
	rename($Ref_to_parameters->{'ALR_gen_file'}, $Unsorted_filename);
	open (UNSORTED, '<' . $Unsorted_filename) or die ('Error: Cannot open/read file: ' . $Unsorted_filename . "\n");
	
	while (my $Current_line = <UNSORTED>){
		
		if ($Current_line =~ /^>(\w+)/) {
			
			if ($Contig_counter > 0) {
				
				$Contigs_data{$Current_contig_name} = $Current_file_content;
				$Current_file_content = "";
			}
			
			$Current_contig_name = $1;
			$Contig_counter++;
		}
		
		$Current_file_content .= $Current_line;
	}

	# Add the last contig to the hash table
	$Contigs_data{$Current_contig_name} = $Current_file_content;
	
	# Close and delete the unsorted file
	close (UNSORTED);
	unlink($Unsorted_filename);
	
	# Write the sorted genotype file
	open (SORTED, '>' . $Ref_to_parameters->{'ALR_gen_file'}) or die ('Error: Cannot create/open file: ' . $Ref_to_parameters->{'ALR_gen_file'} . "\n");

	foreach my $Genotyped_contig (sort $Sort_rule (keys %Contigs_data)) {
		print SORTED $Contigs_data{$Genotyped_contig};
	}
	
	close (SORTED);
	
	return 0;
}

sub sortAllelesFile {

	# Recovers parameters
	my $Ref_to_parameters = shift;
	
	# Initializations
	my $Contig_counter = 0;
	my ($Unsorted_filename, $Current_file_content, $Current_contig_name, $Sort_rule) = ('Unsorted_alleles.fasta', '', '', $Ref_to_parameters->{'Valid_sort_rule'});
	my %Contigs_data;
	
	# Rename and open the unsorted .pfas file and store all contigs into a hash table
	rename($Ref_to_parameters->{'Alleles'}, $Unsorted_filename);
	open (UNSORTED, '<' . $Unsorted_filename) or die ('Error: Cannot open/read file: ' . $Unsorted_filename . "\n");
	
	while (my $Current_line = <UNSORTED>){
	
		if ($Current_line =~ /^>(\w+)/) {
			
			if ($1 ne $Current_contig_name) {
			
				if ($Contig_counter > 0) {
					$Contigs_data{$Current_contig_name} = $Current_file_content;
				}
				
				$Current_contig_name = $1;
				$Contig_counter++;
				$Current_file_content = "";
			}
		}
		$Current_file_content .= $Current_line;
	}
	
	# Add the last contig to the hash table
	$Contigs_data{$Current_contig_name} = $Current_file_content;
	
	# Close and delete the unsorted file
	close (UNSORTED);
	unlink($Unsorted_filename);
	
	# Write the sorted allleles file
	open (SORTED, '>' . $Ref_to_parameters->{'Alleles'}) or die ('Error: Cannot create/open file: ' . $Ref_to_parameters->{'Alleles'} . "\n");
	
	foreach my $Contig (sort $Sort_rule (keys %Contigs_data)) {
		print "Contig: " . $Contig . "\n";
		print SORTED $Contigs_data{$Contig};
	}

	close (SORTED);
	
	return 0;
}

sub sortSpeciesDataFile {
	
	# Recovers parameters
	my $Ref_to_parameters = shift;
	
	# Initializations
	my ($Unsorted_spdata_filename, $Sort_rule, $Contig_name) = ('Unsorted_species_data.csv', $Ref_to_parameters->{'Valid_sort_rule'}, '');
	my %Species_data;
	
	# Rename and open the unsorted species data file
	rename($Ref_to_parameters->{'Species_data_file'}, $Unsorted_spdata_filename);
	open (UNSORTED_SP, '<' . $Unsorted_spdata_filename) or die ('Error: Cannot open/read file: ' . $Unsorted_spdata_filename . "\n");
	
	while (my $Current_line = <UNSORTED_SP>){
		if ($Current_line =~ /^#.*?\(([\w\.]+)\)/) {
			$Contig_name = $1;
			$Species_data{$Contig_name} = $Current_line;
		} else {
			$Species_data{$Contig_name} .= $Current_line;
		}
	}
	
	# Close and delete the unsorted file
	close (UNSORTED_SP);
	unlink($Unsorted_spdata_filename);
	
	# Write the sorted species data file
	open (SORTED_SP, '>' . $Ref_to_parameters->{'Species_data_file'}) or die ('Error: Cannot create/open file: ' . $Ref_to_parameters->{'Species_data_file'} . "\n");

	foreach my $Species (sort $Sort_rule (keys %Species_data)) {
		print SORTED_SP $Species_data{$Species};
	}
	
	close (SORTED_SP);
	
	return 0;
}

sub getSortRule {
	
	# Recovers parameters
	my $Sample = shift;
	
	# Initializations
	my $Valid_sort_rule = 'InAlphaNumericOrder';
	
	# Examinate the sample to determine which sort rules is needed
	if ($Sample =~ /Contig.*/) { $Valid_sort_rule = 'ByContigNumberAsc'; }
	elsif ($Sample =~ /gi.*/) { $Valid_sort_rule = 'ByGenbankIdentifierAsc'; }
	
	return $Valid_sort_rule;
}

##################################################
## Custom sort rules
##################################################

sub InAlphaNumericOrder {
	$a cmp $b;
}

sub ByContigNumberAsc {
	($a =~ /Contig(\d+)$/)[0] <=> ($b =~ /Contig(\d+)$/)[0];
}

sub ByGenbankIdentifierAsc {
	($a =~ /gi.(\d+)/)[0] <=> ($b =~ /gi.(\d+)/)[0];
}

##################################################
## Tools execution related functions
##################################################

sub createReads2SNPInputFile {
	
	# Recovers parameters
	my ($ALR_informations, $Ref_to_parameters) = @_;
	
	# Remove old file if exists
	if (-e $Ref_to_parameters->{'Reads2SNP_input_file'}) { unlink($Ref_to_parameters->{'Reads2SNP_input_file'}); }
	
	# New file creation
	open (R2SNP_INPUT, '>' . $Ref_to_parameters->{'Reads2SNP_input_file'}) or die ('Error: Cannot create/open file: ' . $Ref_to_parameters->{'Reads2SNP_input_file'} . "\n");
	print R2SNP_INPUT $ALR_informations;
	close (R2SNP_INPUT);
	
	return 0;
}

sub executeReads2SNP {
	
	# Recovers parameters
	my $Ref_to_parameters = shift;
	
	# Command line building
	my $cmd  = 'reads2snp -alr ' . $Ref_to_parameters->{'Reads2SNP_input_file'};
	$cmd .= ' -' . $Ref_to_parameters->{'Reads2snp_parameters_sharing'};
	$cmd .= ' -' . $Ref_to_parameters->{'Reads2snp_output_genotype'};
	$cmd .= ' -' .    $Ref_to_parameters->{'Reads2snp_multi_alleles_tolerance'};
	$cmd .= ' -min ' . $Ref_to_parameters->{'Reads2snp_min_coverage'};
	$cmd .= ' -fis ' . $Ref_to_parameters->{'Reads2snp_fis'};
	$cmd .= ' -' .    $Ref_to_parameters->{'Reads2snp_model'};

	if ($Ref_to_parameters->{'Reads2snp_paraclean'} eq 'true') {$cmd .= ' -par ';}
	$cmd .= ' -thr ' . $Ref_to_parameters->{'Reads2snp_paraclean_threshold'};
	$cmd .= ' -dir ' . $Ref_to_parameters->{'Reads2snp_paraclean_dirichlet'};
	if ($Ref_to_parameters->{'Reads2snp_paraclean_skipparalogous'} eq 'true') {$cmd .= ' -spa ';}
	
	$cmd .= ' -err ' . $Ref_to_parameters->{'Reads2snp_paraclean_error'};
    $cmd .= ' -pre ' . $Ref_to_parameters->{'Reads2snp_paraclean_precision'};
	$cmd .= ' -opt ' . $Ref_to_parameters->{'Reads2snp_paraclean_optimizer'};
    if ($Ref_to_parameters->{'Reads2snp_paraclean_mask'} eq 'false') {$cmd .= ' -kun ';}
    $cmd .= ' -gpv ' . $Ref_to_parameters->{'Reads2snp_min_post_probability'};
    if ($Ref_to_parameters->{'Reads2snp_pfasclean'} eq 'true') {$cmd .= ' -pfc ';}   
    $cmd .= ' -win ' . $Ref_to_parameters->{'Reads2snp_pfasclean_window_width'};
    $cmd .= ' -snp ' . $Ref_to_parameters->{'Reads2snp_pfasclean_SNPdens_threshold'};
    $cmd .= ' -het ' . $Ref_to_parameters->{'Reads2snp_pfasclean_H_threshold'};

	$cmd .= ' -gen ' . $Ref_to_parameters->{'Reads2SNP_output_file'};
	$cmd .= ' -all ' . $Ref_to_parameters->{'Reads2SNP_allele'};
	#~ $cmd .= ' -aeb ' . $Ref_to_parameters->{'Reads2SNP_Expression_bias_file'};
	#~ $cmd .= ' -err ' . $Ref_to_parameters->{'Reads2SNP_Error_rate_file'};
	$cmd .= ' -dat ' . $Ref_to_parameters->{'Reads2SNP_data_file'};
	$cmd .= ' 1> '   . $Ref_to_parameters->{'Reads2SNP_stdout_file'}; 
	$cmd .= ' 2> '   . $Ref_to_parameters->{'Reads2SNP_error_file'}; 
		
	print '  -> Reads2snp will be executed with the following command line: ' . $cmd . "\n" if ($DEBUG && !$Ref_to_parameters->{'Multithread_switch'});
	
	system ($cmd);
	
	return 0;
}

##################################################
## Output mangement related functions
##################################################

sub dealWithOutputStatus {
	
	# Recovers parameters
	my ($New_status, $Contig_number, $Contig_name, $Ref_to_parameters) = @_;
	
	# Display global output message if needed (mutltithread only)
	if ($Ref_to_parameters->{'Multithread_switch'} && $DEBUG) {
		#print '  Thread ' . threads->self->tid() . ' - JOB DONE - Status: ' . $New_status . ' - Contig number ' . $Contig_number . ' has been genotyped (' . $Contig_name . ")\n";
	}
	
	switch ($New_status) {
		case "Ok" { dealWithOkStatus($Ref_to_parameters, $Contig_number, $Contig_name); }
		case "No_output" { dealWithNoOutputStatus($Ref_to_parameters, $Contig_number, $Contig_name); }
		case "Warning" { dealWithWarningStatus($Ref_to_parameters, $Contig_number, $Contig_name); }
		case "Segmentation_fault" { dealWithSegFaultStatus($Ref_to_parameters, $Contig_number, $Contig_name); }
		case "Error" { dealWithErrorStatus($Ref_to_parameters, $Contig_number, $Contig_name); }
		else { print "Unexpected switch case !  !" }
	}
	
	return 0;
}

sub dealWithOkStatus {
	
	# Recovers parameters
	my ($Ref_to_parameters, $Contig_number, $Contig_name) = @_;
	
	# Verbose
	print '   --> Execution complete !!' . "\n\n" if (!$Ref_to_parameters->{'Multithread_switch'});
	
	# Add unitary result to the global output file
	addDataToGlobalOutputFile($Ref_to_parameters->{'Reads2SNP_output_file'}, $Ref_to_parameters->{'ALR_gen_file'}, $Contig_number, $Ref_to_parameters->{'Multithread_switch'});
	addDataToGlobalOutputFile($Ref_to_parameters->{'Reads2SNP_allele'}, $Ref_to_parameters->{'Alleles'}, $Contig_number, $Ref_to_parameters->{'Multithread_switch'});
	
	# STDOUT
	open (DATA, '<' . $Ref_to_parameters->{'Reads2SNP_stdout_file'}) or die ('Error: Cannot open/read file: ' . $Ref_to_parameters->{'Reads2SNP_stdout_file'} . "\n");	
	while (my $line = <DATA>){print STDOUT $line;}
	close (DATA);

	# Add unitary result to the additional global output files
	#~ if (-e $Ref_to_parameters->{'Reads2SNP_Expression_bias_file'}) {
		#~ addDataToGlobalOutputFile($Ref_to_parameters->{'Reads2SNP_Expression_bias_file'}, $Ref_to_parameters->{'Expression_bias_file'}, $Contig_number, $Ref_to_parameters->{'Multithread_switch'}, '## Allelic expression bias data for contig ' . $Contig_number . ' (' . $Contig_name . ') ##');
	#~ }
	#~ 
	#~ if (-e $Ref_to_parameters->{'Reads2SNP_Error_rate_file'}) { 
		#~ addDataToGlobalOutputFile($Ref_to_parameters->{'Reads2SNP_Error_rate_file'}, $Ref_to_parameters->{'Error_rate_file'}, $Contig_number, $Ref_to_parameters->{'Multithread_switch'}, '## Error rates data for contig ' . $Contig_number . ' (' . $Contig_name . ') ##');
	#~ }
	
	addDataToGlobalOutputFile($Ref_to_parameters->{'Reads2SNP_data_file'}, $Ref_to_parameters->{'Species_data_file'}, $Contig_number, $Ref_to_parameters->{'Multithread_switch'},'## Species data for contig ' . $Contig_number . ' (' . $Contig_name . ') ##');

	return 0;
}

sub dealWithNoOutputStatus {
	
	# Recovers parameters
	my ($Ref_to_parameters, $Contig_number, $Contig_name) = @_;
	
	# Verbose
	print '   --> There are no Reads2SNP results for contig ' . $Contig_number . ' (' . $Contig_name . ")..\n\n" if (!$Ref_to_parameters->{'Multithread_switch'});
	
	# Regroup contigs for which there is no normal output (for relaunch)
	addDataToGlobalOutputFile($Ref_to_parameters->{'Reads2SNP_input_file'}, $Ref_to_parameters->{'Contig_no_output'}, $Contig_number, $Ref_to_parameters->{'Multithread_switch'}) if ($Ref_to_parameters->{'Execution_mode'} eq 'local');

	return 0;
}

sub dealWithWarningStatus {
	
	# Recovers parameters
	my ($Ref_to_parameters, $Contig_number, $Contig_name) = @_;
	
	# Verbose
	if (!$Ref_to_parameters->{'Multithread_switch'}) {
		print '   --> Execution complete (with warnings) ! Results might be uncomplete ! Please check the error file for contig ' . $Contig_number . ' (' . $Contig_name . ")..\n\n";
	}
	
	# Add unitary result to the global output file
	addDataToGlobalOutputFile($Ref_to_parameters->{'Reads2SNP_output_file'}, $Ref_to_parameters->{'ALR_gen_file'}, $Contig_number, $Ref_to_parameters->{'Multithread_switch'});
	addDataToGlobalOutputFile($Ref_to_parameters->{'Reads2SNP_allele'}, $Ref_to_parameters->{'Alleles'}, $Contig_number, $Ref_to_parameters->{'Multithread_switch'});
	
	# Add unitary result to the additional global output files
	#~ if (-e $Ref_to_parameters->{'Reads2SNP_Expression_bias_file'}) {
		#~ addDataToGlobalOutputFile($Ref_to_parameters->{'Reads2SNP_Expression_bias_file'}, $Ref_to_parameters->{'Expression_bias_file'}, $Contig_number, $Ref_to_parameters->{'Multithread_switch'}, '## Allelic expression bias data for contig ' . $Contig_number . ' (' . $Contig_name . ') ##');
	#~ }
	#~ 
	#~ if (-e $Ref_to_parameters->{'Reads2SNP_Error_rate_file'}) { 
		#~ addDataToGlobalOutputFile($Ref_to_parameters->{'Reads2SNP_Error_rate_file'}, $Ref_to_parameters->{'Error_rate_file'}, $Contig_number, $Ref_to_parameters->{'Multithread_switch'}, '## Error rates data for contig ' . $Contig_number . ' (' . $Contig_name . ') ##');
	#~ }
	
	addDataToGlobalOutputFile($Ref_to_parameters->{'Reads2SNP_data_file'}, $Ref_to_parameters->{'Species_data_file'}, $Contig_number, $Ref_to_parameters->{'Multithread_switch'},'## Species data for contig ' . $Contig_number . ' (' . $Contig_name . ') ##');
	
	# Regroup contigs that generate warnings (for relaunch)
	addDataToGlobalOutputFile($Ref_to_parameters->{'Reads2SNP_input_file'}, $Ref_to_parameters->{'Contig_with_warning'}, $Contig_number, $Ref_to_parameters->{'Multithread_switch'}) if ($Ref_to_parameters->{'Execution_mode'} eq 'local');
	
	# Add new warnings to the global error file
	addDataToGlobalOutputFile($Ref_to_parameters->{'Reads2SNP_error_file'}, $Ref_to_parameters->{'Main_error'}, $Contig_number, $Ref_to_parameters->{'Multithread_switch'}, '## Warning(s) for contig ' . $Contig_number . ' (' . $Contig_name . ') ##'); 
	
	return 0;
}

sub dealWithSegFaultStatus {
	
	# Recovers parameters
	my ($Ref_to_parameters, $Contig_number, $Contig_name) = @_;
	
	# Verbose
	print '   --> A segmentation fault was raised during the processing of contig ' . $Contig_number . ' (' . $Contig_name . ")..\n\n" if (!$Ref_to_parameters->{'Multithread_switch'});
	
	# Regroup contigs that generate segmentation faults (for relaunch)
	addDataToGlobalOutputFile($Ref_to_parameters->{'Reads2SNP_input_file'}, $Ref_to_parameters->{'Contig_with_segfault'}, $Contig_number, $Ref_to_parameters->{'Multithread_switch'}) if ($Ref_to_parameters->{'Execution_mode'} eq 'local');
	
	# Add new segfaults to the global error file
	addDataToGlobalOutputFile($Ref_to_parameters->{'Reads2SNP_error_file'}, $Ref_to_parameters->{'Main_error'}, $Contig_number, $Ref_to_parameters->{'Multithread_switch'}, '## Segmentation fault for contig ' . $Contig_number . ' (' . $Contig_name . ') ##'); 

	return 0;
}

sub dealWithErrorStatus {
	
	# Recovers parameters
	my ($Ref_to_parameters, $Contig_number, $Contig_name) = @_;
	
	# Verbose
	print '   --> A fatal error occured during the processing of contig ' . $Contig_number . ' (' . $Contig_name . ")..\n\n" if (!$Ref_to_parameters->{'Multithread_switch'});
	
	# Regroup contigs that generate a error messages (for relaunch)
	addDataToGlobalOutputFile($Ref_to_parameters->{'Reads2SNP_input_file'}, $Ref_to_parameters->{'Contig_with_error'}, $Contig_number, $Ref_to_parameters->{'Multithread_switch'}) if ($Ref_to_parameters->{'Execution_mode'} eq 'local');
	
	# Add new errors to the global error file
	addDataToGlobalOutputFile($Ref_to_parameters->{'Reads2SNP_error_file'}, $Ref_to_parameters->{'Main_error'}, $Contig_number, $Ref_to_parameters->{'Multithread_switch'}, '## Error(s) for contig ' . $Contig_number . ' (' . $Contig_name . ') ##'); 
		
	return 0;
}


sub addDataToGlobalOutputFile {
	
	# Recovers parameters
	my ($New_data, $Global_file, $Counter, $lock, $Opt_header) = @_;
	
	# Add the content of the new result file to the global result file
	open(GLOBAL, '>>' . $Global_file) or die ('Error: Cannot create/open file: ' . $Global_file . "\n");
	
	# Lock the file if needed
	flock(GLOBAL, LOCK_EX) if ($lock);

	if ($Opt_header) { print GLOBAL $Opt_header . "\n"; }
	
	open (DATA, '<' . $New_data) or die ('Error: Cannot open/read file: ' . $New_data . "\n");	
	while (my $line = <DATA>){
		print GLOBAL $line;
	}
	close (DATA);
	
	# Close the global output file (In Perl, close() is an implicit unlock)
	close(GLOBAL);

	return 0;
}

##################################################
## Help/Check related functions
##################################################

sub displayHelpMessage {

	print('###################################################' . "\n");
	print('#     Reads2snp Galaxy Wrapper - Help section     #' . "\n"); 
	print('###################################################' . "\n\n");
	
	print 'Usage example (Minimal commande line):' . "\n\n";
	print "  reads2snp_wrapper.pl -alr sample.alr" . "\n\n\n";
	
	print 'Usage example (Maximal command line - short names):' . "\n\n";
	print "  reads2snp_wrapper.pl -alr sample.alr -mth " . $Default_nb_thread . " -mod " . $Default_model . " -sharing " . $Default_parameters_sharing . " -geno " . $Default_output_genotype . " -mat " . $Default_multi_alleles_tolerance . " -min " . $Default_min_coverage . " -data species_data.data -fis " . $Default_fis . " -par " . $Default_paraclean . " -thr " . $Default_threshold . " -dir " . $Default_dirichlet . " -spa " . $Default_skipparalogous . " -del yes -out sample.alr_gen\n\n\n";
	
	print 'Mandatory parameters :' . "\n\n";
	
	print " -alrfile/-alr file => Path and name of the input file in ALR format (mandatory).\n\n\n";
	
	print 'Common optional parameters :' . "\n\n";
	
	print " -help => Display this help message.\n\n";
	
	print " -nb_thread/-mth integer => Number of thread to use during the genotyping procedure (optional). Range of possible values [0 < nb_thread <= Max_computing_power_by_node].\n";
	print "\tIf not specified, the following default value will be used: " . $Default_nb_thread . "\n\n";
	
	print " -clean/-del string => Activate/desactivate temporary files removal (optional). Possible values are [yes|no].\n";
	print "\tBy default, temporary files will not be conserved for execution instance without error.\n\n";
	
	print " -output/-out string => Path and name of the main output file (Genotype prediction) in ALR_GEN format (optional).\n";
	print "\tIf not specified, a default name (derived from the name of the ALR input file) will be used (.gen extension).\n\n";
	
	print " -fis/-min float => F statistic (fis) (optional). Range of possible values [0..1].\n";
	print "\tIf not specified, the following default value will be used: " . $Default_fis . "\n\n\n";
	
	print 'Reads2SNP custom parameters :' . "\n\n";
	
	print " -model/-mod string => Name of a valid reads2SNP model (optional). Possible values are [M0|M1|M2].\n";
	print "    The various models diverge in the way they deals with errors (For example: M1a only consider sequencing errors while M2 also take into account the allelic expression bias).\n";
	print "\tIf not specified, the following default value will be used: " . $Default_model . "\n\n";
	
	print " -parameters_sharing/-sharing string => Define if parameters (related to errors management) are shared by species or not (optional). Possible values are [sh|sep|all].\n";
	print "\tIf not specified, the following default value will be used: " . $Default_parameters_sharing . "\n\n";
	
	print " -output_genotype/-geno string => Define which genotype will be written into reads2SNP main output file for each position and each individual (optional). Possible values are [best|rand].\n";
	print "    If set to <best> then the genotype with the highest probability will always be written.\n";
	print "    If set to <rand> then the genotype for a given site will be selected randomly between all possible genotype (depending on their probability).\n";
	print "\tIf not specified, the following default value will be used: " . $Default_output_genotype . "\n\n";
	
	print " -multi_alleles/-mat string => Accept, tolerate or prohibit multi alleles cases (more than two at a given position for a given individual) - (optional). Possible values are [acc|tol|for].\n";
	print "    If set to <acc> reads2SNP won't apply a particular filtering.\n";
	print "    If set to <tol> reads2SNP won't predict a genotype for the current position.\n";
	print "    If set to <for> reads2SNP will reject the full contig.\n";
	print "\tIf not specified, the following default value will be used: " . $Default_multi_alleles_tolerance . "\n\n";
	
	print " -min_coverage/-min integer => Minimum coverage (Number of reads by individual) needed for genotype prediction (optional). Possible values are [All positive integers].\n";
	print "\tIf not specified, the following default value will be used: " . $Default_min_coverage . "\n\n";
	
	#~ print " -aeb_file/-aeb string => Path and name of the additional output file for Allelic expression bias data (optional).\n";
	#~ print "\tOnly used for the following models: M2b|M3b|M4.\n";
	#~ print "\tIf not specified, a default name (derived from the name of the ALR input file) will be used (.aeb extension).\n\n";
	#~ 
	#~ print " -earr_file/-earr string => Path and name of the additional output file for Estimated average error rates data (optional).\n";
	#~ print "\tOnly used for the following models: M2a|M3a|M4.\n";
	#~ print "\tIf not specified, a default name (derived from the name of the ALR input file) will be used (.earr extension).\n\n";
	
	print " -species_data/-data string => Path and name of the additional output file containing error_rates/expression_bias summarized data in CSV format (semicolon separated, # for comments) (optional).\n";
	print "\tIf not specified, a default name (derived from the name of the ALR input file) will be used (.data extension).\n\n\n";
	
	print 'Paralog searching related parameters :' . "\n\n";
	
	print " -paraclean/-par boolean_string => Activate/desactivate paralogous position cleaning/removal (ParaClean) (optional). Possible values are [true|false].\n";
	print "\tIf not specified, the following default value will be used: " . $Default_paraclean . "\n\n";
	
	print " -threshold/-thr float => Define likelihood threshold (Threshold above which a position is considered paralogous) (optional). Range of possible values [0..1].\n";
	print "\tIf not specified, the following default value will be used: " . $Default_threshold . "\n\n";
	
	print " -dirichlet/-dir boolean_string => Use dirichlet-multinomial distribution model (true) or standard distribution model (false) (optional). Possible values are [true|false].\n";
	print "\tIf not specified, the following default value will be used: " . $Default_dirichlet . "\n\n";
	
	print " -skipparalogous/-spa boolean_string => Remove contigs that contains paralogous positions (true) or not (false) (optional). Possible values are [true|false].\n";
	print "\tIf not specified, the following default value will be used: " . $Default_skipparalogous . "\n\n";
	
	print("\n" . '###################################################' . "\n");
	print('#  Reads2snp Galaxy Wrapper - Help section - END  #' . "\n"); 
	print('###################################################' . "\n");
	exit (1);
}

sub checkParameters {
	
	# Recovers parameters
	my $Ref_to_parameters = shift;
	
	# Execution mode
	if (!defined($Ref_to_parameters->{'Execution_mode'}) || $Ref_to_parameters->{'Execution_mode'} eq "") {
		$Ref_to_parameters->{'Execution_mode'} = 'local';
	}
	
	# Deal with global options (Input, Main output and cleaning)
	if (!defined($Ref_to_parameters->{'ALR_file'})) {
		print 'Error: No .alr input file defined !' . "\n\n";
		displayHelpMessage();
	} else {
		checkInputFile($Ref_to_parameters->{'ALR_file'}, 'ALR');
	}
	
	if (!defined($Ref_to_parameters->{'ALR_gen_file'})) {
		print 'Warning: The name of the main output file has been omited ! A default name (derived from the name of the ALR input file) will be used !' . "\n\n";
		$Ref_to_parameters->{'ALR_gen_file'} = basename($Ref_to_parameters->{'ALR_file'}) . '.gen';
	}
	
	if (!defined($Ref_to_parameters->{'Alleles'})) {
		print 'Warning: The name of the alleles output file has been omited ! A default name (derived from the name of the ALR input file) will be used !' . "\n\n";
		$Ref_to_parameters->{'ALR_gen_file'} = basename($Ref_to_parameters->{'ALR_file'}) . '.pfas';
	}

	if (!defined($Ref_to_parameters->{'clean_tmp_files'})) {
		print 'Warning: The clean/del parameter has been omited ! Temporary files will not be conserved !' . "\n\n";
		$Ref_to_parameters->{'clean_tmp_files'} = 'yes';
		
	} elsif ($Ref_to_parameters->{'clean_tmp_files'} !~ /^(yes|no)$/i) {
		print 'Warning: The value (' . $Ref_to_parameters->{'clean_tmp_files'} . ') of the clean/del parameter given to this module is not valid ! Temporary files will not be conserved !' . "\n\n";
		$Ref_to_parameters->{'clean_tmp_files'} = 'yes';
	}
	
	# Check multithreading related options
	if (!$Config{useithreads}) {
		print 'Warning: Multithread support is not available with your current Perl installation (Perl 5.8.0 or greater is required) !' . "\n";
		print 'As a consequence, genotype predictions will be made in series' . "\n\n";
		$Ref_to_parameters->{'Number_of_thread'} = 0;
		
	} elsif (!defined($Ref_to_parameters->{'Number_of_thread'})) {
		print 'Warning: The number of thread to use during the genotyping procedure has been omited ! Default value ("' . $Default_nb_thread . '") will be used...' . "\n\n";
		$Ref_to_parameters->{'Number_of_thread'} = $Default_nb_thread;
		
	} elsif ($Ref_to_parameters->{'Number_of_thread'} !~ /^[0-9]+$/) {
		print 'Warning: The value (' . $Ref_to_parameters->{'Number_of_thread'} . ') of the "nb_thread" parameter given to this module is not valid ! Default value ("' . $Default_nb_thread . '") will be used !' . "\n\n";
		$Ref_to_parameters->{'Number_of_thread'} = $Default_nb_thread;
	}

	# Deal with Reads2snp specific options
	if (!defined($Ref_to_parameters->{'Reads2snp_model'})) {
		print 'Warning: No Reads2SNP model specified ! Default value ("' . $Default_model . '") will be used...' . "\n\n";	
		$Ref_to_parameters->{'Reads2snp_model'} = $Default_model;
		
	} elsif ($Ref_to_parameters->{'Reads2snp_model'} !~ /^(M0|M1|M2)$/i) {
		print 'Warning: The value (' . $Ref_to_parameters->{'Reads2snp_model'} . ') of the Reads2SNP "model" parameter given to this module is not valid ! Default value ("' .  $Default_model . '") will be used !' . "\n\n";
		$Ref_to_parameters->{'Reads2snp_model'} = $Default_model;
	}

	if (!defined($Ref_to_parameters->{'Reads2snp_parameters_sharing'})) {
		print 'Warning: No Reads2SNP "parameters_sharing" parameter specified ! Default value ("' . $Default_parameters_sharing . '") will be used...' . "\n\n";
		$Ref_to_parameters->{'Reads2snp_parameters_sharing'} = $Default_parameters_sharing;
		
	} elsif ($Ref_to_parameters->{'Reads2snp_parameters_sharing'} !~ /^(sh|sep|all)$/i) {
		print 'Warning: The value (' . $Ref_to_parameters->{'Reads2snp_parameters_sharing'} . ') of the Reads2SNP "parameters_sharing" parameter given to this module is not valid ! Default value ("' . $Default_parameters_sharing . '") will be used !' . "\n\n";
		$Ref_to_parameters->{'Reads2snp_parameters_sharing'} = $Default_parameters_sharing;
	}

	if (!defined($Ref_to_parameters->{'Reads2snp_output_genotype'})) {
		print 'Warning: No Reads2SNP "output_genotype" parameter specified ! Default value ("' . $Default_output_genotype . '") will be used...' . "\n\n";	
		$Ref_to_parameters->{'Reads2snp_output_genotype'} = $Default_output_genotype;
		
	} elsif ($Ref_to_parameters->{'Reads2snp_output_genotype'} !~ /^(best|rand)$/i) {
		print 'Warning: The value (' . $Ref_to_parameters->{'Reads2snp_output_genotype'} . ') of the Reads2SNP "output_genotype" parameter given to this module is not valid ! Default value ("' . $Default_output_genotype . '") will be used !' . "\n\n";
		$Ref_to_parameters->{'Reads2snp_output_genotype'} = $Default_output_genotype;
	}

	if (!defined($Ref_to_parameters->{'Reads2snp_multi_alleles_tolerance'})) {
		print 'Warning: No Reads2SNP "multi_alleles_tolerance" parameter specified ! Default value ("' . $Default_multi_alleles_tolerance . '") will be used...' . "\n\n";
		$Ref_to_parameters->{'Reads2snp_multi_alleles_tolerance'} = $Default_multi_alleles_tolerance;
		
	} elsif ($Ref_to_parameters->{'Reads2snp_multi_alleles_tolerance'} !~ /^(acc|tol|for)$/i) {
		print 'Warning: The value (' . $Ref_to_parameters->{'Reads2snp_multi_alleles_tolerance'} . ') of the Reads2SNP "multi_alleles_tolerance" parameter given to this module is not valid ! Default value ("' . $Default_multi_alleles_tolerance . '") will be used !' . "\n\n";
		$Ref_to_parameters->{'Reads2snp_multi_alleles_tolerance'} = $Default_multi_alleles_tolerance;
	}

	if (!defined($Ref_to_parameters->{'Reads2snp_min_coverage'})) {
		print 'Warning: No Reads2SNP minimum coverage specified ! Default value ("' . $Default_min_coverage . '") will be used...' . "\n\n";
		$Ref_to_parameters->{'Reads2snp_min_coverage'} = $Default_min_coverage;
		
	} elsif ($Ref_to_parameters->{'Reads2snp_min_coverage'} !~ /^[0-9]+$/) {
		print 'Warning: The value (' . $Ref_to_parameters->{'Reads2snp_min_coverage'} . ') of the Reads2SNP "min_coverage" parameter given to this module is not valid ! Default value ("' . $Default_min_coverage . '") will be used !' . "\n\n";
		$Ref_to_parameters->{'Reads2snp_min_coverage'} = $Default_min_coverage;
	}
	
	# Deal with additional and optional output files
	#~ if ($Ref_to_parameters->{'Reads2snp_model'} =~ /^(M2b|M3b|M4)$/) {
		#~ if (!defined($Ref_to_parameters->{'Expression_bias_file'})) {
			#~ print 'Warning: The name of the additional output file for Allelic expression bias data has been omited ! A default name (derived from the name of the ALR input file) will be used !' . "\n\n";
			#~ $Ref_to_parameters->{'Expression_bias_file'} = basename($Ref_to_parameters->{'ALR_file'}) . '.aeb';
		#~ }
	#~ } else {
		#~ $Ref_to_parameters->{'Expression_bias_file'} = undef;
	#~ }
	#~ 
	#~ if ($Ref_to_parameters->{'Reads2snp_model'} =~ /^(M2a|M3a|M4)$/) {
		#~ if (!defined($Ref_to_parameters->{'Error_rate_file'})) {
			#~ print 'Warning: The name of the additional output file for Estimated average error rates data has been omited ! A default name (derived from the name of the ALR input file) will be used !' . "\n\n";
			#~ $Ref_to_parameters->{'Error_rate_file'} = basename($Ref_to_parameters->{'ALR_file'}) . '.earr';
		#~ }
	#~ } else {
		#~ $Ref_to_parameters->{'Error_rate_file'} = undef;
	#~ }
	
	if (!defined($Ref_to_parameters->{'Species_data_file'})) {
		print 'Warning: The name of the additional output file containing error_rates/expression_bias summarized data has been omited ! A default name (derived from the name of the ALR input file) will be used !' . "\n\n";
		$Ref_to_parameters->{'Species_data_file'} = basename($Ref_to_parameters->{'ALR_file'}) . '.data';
	}
	
	# Deal with options used by Reads2SNP and ParaClean
	if (!defined($Ref_to_parameters->{'Reads2snp_fis'})) {
		print 'Warning: No fis value is specified ! Default value ("' . $Default_fis . '") will be used...' . "\n\n";
		$Ref_to_parameters->{'Reads2snp_fis'} = $Default_fis;
	}

	if (!defined($Ref_to_parameters->{'Reads2snp_min_post_probability'})) {
		print 'Warning: No minimum post value probability value specified ! Default value ("' . $default_min_post_probability . '") will be used...' . "\n\n";
		$Ref_to_parameters->{'Reads2snp_min_post_probability'} = $default_min_post_probability;
	} elsif ($Ref_to_parameters->{'Reads2snp_min_post_probability'} < 0 || $Ref_to_parameters->{'Reads2snp_min_post_probability'} > 1) {
		print 'Warning: The value (' . $Ref_to_parameters->{'Reads2snp_min_post_probability'} . ') of the "min_post_probability|gpv" parameter given to this module is not valid ! Default value ("' . $default_min_post_probability . '") will be used !' . "\n\n";
		$Ref_to_parameters->{'Reads2snp_min_post_probability'} = $default_min_post_probability;
	}


	# Deal with paraclean specific options
	if (defined($Ref_to_parameters->{'Reads2snp_paraclean'})) {
		if ($Ref_to_parameters->{'Reads2snp_paraclean'} eq "true") {
			
			if (!defined($Ref_to_parameters->{'Reads2snp_paraclean_threshold'})) {
				print 'Warning: No threshold value specified ! Default value ("' . $Default_threshold . '") will be used...' . "\n\n";
				$Ref_to_parameters->{'Reads2snp_paraclean_threshold'} = $Default_threshold;
				
			} elsif ($Ref_to_parameters->{'Reads2snp_paraclean_threshold'} < 0 || $Ref_to_parameters->{'Reads2snp_paraclean_threshold'} > 1) {
				print 'Warning: The value (' . $Ref_to_parameters->{'Reads2snp_paraclean_threshold'} . ') of the "paraclean|par" parameter given to this module is not a valid numeric value between 0 and 1 ! Default value ("' . $Default_threshold . '") will be used !' . "\n\n";
				$Ref_to_parameters->{'Reads2snp_paraclean_threshold'} = $Default_threshold;
			}
			
			if (!defined($Ref_to_parameters->{'Reads2snp_paraclean_dirichlet'})) {
				print 'Warning: Dirichlet option is not specified ! Default value ("' . $Default_dirichlet . '") will be used...' . "\n\n";
				$Ref_to_parameters->{'Reads2snp_paraclean_dirichlet'} = $Default_dirichlet;
				
			} elsif ($Ref_to_parameters->{'Reads2snp_paraclean_dirichlet'} !~ /^(true|false)$/i) {
				print 'Warning: The value (' . $Ref_to_parameters->{'Reads2snp_paraclean_dirichlet'} . ') of the "dirichlet|dir" parameter given to this module is not valid ! Default value ("' . $Default_dirichlet . '") will be used !' . "\n\n";
				$Ref_to_parameters->{'Reads2snp_paraclean_dirichlet'} = $Default_dirichlet;
			}
			
			if (!defined($Ref_to_parameters->{'Reads2snp_paraclean_skipparalogous'})) {
				print 'Warning: skipparalogous option is not specified ! Default value ("' . $Default_skipparalogous . '") will be used...' . "\n\n";
				$Ref_to_parameters->{'Reads2snp_paraclean_skipparalogous'} = $Default_skipparalogous;
				
			} elsif ($Ref_to_parameters->{'Reads2snp_paraclean_skipparalogous'} !~ /^(true|false)$/i) {
				print 'Warning: The value (' . $Ref_to_parameters->{'Reads2snp_paraclean_skipparalogous'} . ') of the "skipparalogous|spa" parameter given to this module is not valid ! Default value ("' . $Default_skipparalogous . '") will be used !' . "\n\n";
				$Ref_to_parameters->{'Reads2snp_paraclean_skipparalogous'} = $Default_skipparalogous;
			}
			
			if (!defined($Ref_to_parameters->{'Reads2snp_paraclean_error'})) {
				print 'Warning: paraclean error option is not specified ! Default value ("' . $default_error . '") will be used...' . "\n\n";
				$Ref_to_parameters->{'Reads2snp_paraclean_error'} = $default_error;
			} elsif ($Ref_to_parameters->{'Reads2snp_paraclean_error'} < 0 || $Ref_to_parameters->{'Reads2snp_paraclean_error'} > 0.3333) {
				print 'Warning: The value (' . $Ref_to_parameters->{'Reads2snp_paraclean_error'} . ') of the "error|err" parameter given to this module is not valid ! Default value ("' . $default_error . '") will be used !' . "\n\n";
				$Ref_to_parameters->{'Reads2snp_paraclean_error'} = $default_error;
			}
			
			if (!defined($Ref_to_parameters->{'Reads2snp_paraclean_precision'})) {
				print 'Warning: paraclean error option is not specified ! Default value ("' . $default_precision . '") will be used...' . "\n\n";
				$Ref_to_parameters->{'Reads2snp_paraclean_precision'} = $default_precision;
			} elsif ($Ref_to_parameters->{'Reads2snp_paraclean_error'} < 0 || $Ref_to_parameters->{'Reads2snp_paraclean_precision'} > 0.3333) {
				print 'Warning: The value (' . $Ref_to_parameters->{'Reads2snp_paraclean_error'} . ') of the "precision|pre" parameter given to this module is not valid ! Default value ("' . $default_precision . '") will be used !' . "\n\n";
				$Ref_to_parameters->{'Reads2snp_paraclean_precision'} = $default_precision;
			}
			
			if (!defined($Ref_to_parameters->{'Reads2snp_paraclean_optimizer'})) {
				print 'Warning: paraclean Optimizer is not specified ! Default value ("' . $default_optimizer . '") will be used...' . "\n\n";
				$Ref_to_parameters->{'Reads2snp_paraclean_optimizer'} = $default_optimizer;
			} elsif ($Ref_to_parameters->{'Reads2snp_paraclean_optimizer'} !~ /^(bfgs|newton)$/i) {
				print 'Warning: The value (' . $Ref_to_parameters->{'Reads2snp_paraclean_optimizer'} . ') of the "optimizer|opt" parameter given to this module is not valid ! Default value ("' . $default_optimizer . '") will be used !' . "\n\n";
				$Ref_to_parameters->{'Reads2snp_paraclean_optimizer'} = $default_optimizer;
			}
			
			if (!defined($Ref_to_parameters->{'Reads2snp_paraclean_mask'})) {
				print 'Warning: paraclean mask is not specified ! Default value ("' . $default_mask . '") will be used...' . "\n\n";
				$Ref_to_parameters->{'Reads2snp_paraclean_mask'} = $default_mask;
			} elsif ($Ref_to_parameters->{'Reads2snp_paraclean_mask'} !~ /^(true|false)$/i) {
				print 'Warning: The value (' . $Ref_to_parameters->{'Reads2snp_paraclean_mask'} . ') of the "optimizer|opt" parameter given to this module is not valid ! Default value ("' . $default_mask . '") will be used !' . "\n\n";
				$Ref_to_parameters->{'Reads2snp_paraclean_mask'} = $default_mask;
			}
			
		}
	} else {
		print 'Warning: Paraclean option is not specified ! Default value ("' . $Default_paraclean . '") will be used...' . "\n\n";
		$Ref_to_parameters->{'Reads2snp_paraclean'} = $Default_paraclean;
	}

	if (defined($Ref_to_parameters->{'Reads2snp_pfasclean'})) {
		if ($Ref_to_parameters->{'Reads2snp_pfasclean'} eq "true") {
		
		if (!defined($Ref_to_parameters->{'Reads2snp_pfasclean_window_width'})) {
			print 'Warning: pfasclean windows width is not specified ! Default value ("' . $default_window_width . '") will be used...' . "\n\n";
			$Ref_to_parameters->{'Reads2snp_pfasclean_window_width'} = $default_window_width;
		} elsif ($Ref_to_parameters->{'Reads2snp_pfasclean_window_width'} < 0) {
			print 'Warning: The value (' . $Ref_to_parameters->{'Reads2snp_pfasclean_window_width'} . ') of the "optimizer|opt" parameter given to this module is not valid ! Default value ("' . $default_window_width . '") will be used !' . "\n\n";
			$Ref_to_parameters->{'Reads2snp_pfasclean_window_width'} = $default_window_width;
		}
		
		if (!defined($Ref_to_parameters->{'Reads2snp_pfasclean_SNPdens_threshold'})) {
			print 'Warning: pfasclean windows width is not specified ! Default value ("' . $default_SNPdens_threshold . '") will be used...' . "\n\n";
			$Ref_to_parameters->{'Reads2snp_pfasclean_SNPdens_threshold'} = $default_SNPdens_threshold;
		} elsif ($Ref_to_parameters->{'Reads2snp_pfasclean_SNPdens_threshold'} < 0 || $Ref_to_parameters->{'Reads2snp_pfasclean_SNPdens_threshold'} > 1) {
			print 'Warning: The value (' . $Ref_to_parameters->{'Reads2snp_pfasclean_SNPdens_threshold'} . ') of the "optimizer|opt" parameter given to this module is not valid ! Default value ("' . $default_SNPdens_threshold . '") will be used !' . "\n\n";
			$Ref_to_parameters->{'Reads2snp_pfasclean_SNPdens_threshold'} = $default_SNPdens_threshold;
		}
		
		if (!defined($Ref_to_parameters->{'Reads2snp_pfasclean_H_threshold'})) {
			print 'Warning: pfasclean heterozygosity threshold is not specified ! Default value ("' . $default_H_threshold . '") will be used...' . "\n\n";
			$Ref_to_parameters->{'Reads2snp_pfasclean_H_threshold'} = $default_H_threshold;
		} elsif ($Ref_to_parameters->{'Reads2snp_pfasclean_SNPdens_threshold'} < 0 || $Ref_to_parameters->{'Reads2snp_pfasclean_H_threshold'} > 1) {
			print 'Warning: The value (' . $Ref_to_parameters->{'Reads2snp_pfasclean_H_threshold'} . ') of the "optimizer|opt" parameter given to this module is not valid ! Default value ("' . $default_H_threshold . '") will be used !' . "\n\n";
			$Ref_to_parameters->{'Reads2snp_pfasclean_H_threshold'} = $default_H_threshold;
		}
	  
	  }
		
	} else {
		print 'Warning: Pfasclean option is not specified ! Default value ("' . $default_pfasclean . '") will be used...' . "\n\n";
		$Ref_to_parameters->{'Reads2snp_pfasclean'} = $default_pfasclean;
	}
		


	return 0;
}

sub checkInputFile {
	
	# Recovers parameters
	my ($Input_file, $Type) = @_;
	
	# Check input file existence
	if (! -e $Input_file || -z $Input_file) {
		print 'Fatal error: The ' . $Type . ' file specified in the command line does not exist or is empty ! Please check file name, path and content !!' . "\n";
		print 'Selected file: ' . basename($Input_file) . "\n";
		print 'Corresponding directory: ' . dirname($Input_file) . "\n\n";
		exit(1);
	}
	
	# Check input file format
	checkInputFileFormat($Input_file, $Type);

	return 0;
}

sub checkInputFileFormat {
	
	# Recovers parameters
	my ($Input_file, $Type) = @_;
	
	# Initializations
	my $Counter = 0;
	
	# Check the number of line, the sequence name on the first line and the format of the third line (first position)
	if ($Type eq 'ALR') {
		open (INPUT, '<' . $Input_file) or die ('Error: Cannot open/read file: ' . $Input_file . "\n");
	
		while (my $Alr_line = <INPUT>){
			
			if ($Counter > 2) {
				last;
				
			} elsif ($Counter == 0 && $Alr_line !~ /^>([\w\.]+)/) {
				print 'Fatal error: The format of the first line of the ' . $Type . ' file specified in the command line is not valid !!' . "\n";
				print 'Selected file: ' . basename($Input_file) . "\n";
				print 'Corresponding directory: ' . dirname($Input_file) . "\n\n";
				
				print 'Example lines: >Contig23735 or >gi_291382774_ref_XM_002708152.1' . "\n\n";
				exit(1);
				
			} elsif ($Counter == 2 && $Alr_line !~ /^[ACGT]{1}\t[MP]{1}\t[\d\[\/\]]+/) {
				print 'Fatal error: The format of the third line of the ' . $Type . ' file specified in the command line is not a valid !!' . "\n";
				print 'Selected file: ' . basename($Input_file) . "\n";
				print 'Corresponding directory: ' . dirname($Input_file) . "\n\n";
				
				print 'Example line - Monomorphic position (4 individuals): T	M	3	4	3	7' . "\n";
				print 'Example line - Polymorphic position (3 individuals): A	P	3[0/3/0/0]	4[3/1/0/0]	2[0/2/0/0]' . "\n\n";
				print 'Your invalid line: ' . $Alr_line . "\n";
				exit(1);
			}
			$Counter++;
		}

		close (INPUT);
		
		# Don't forget the too short files
		if ($Counter < 3) {
			print 'Fatal error: The ' . $Type . ' file specified in the command line contains less than 3 lines !!' . "\n";
			print 'Selected file: ' . basename($Input_file) . "\n";
			print 'Corresponding directory: ' . dirname($Input_file) . "\n\n";
			exit(1);
		}
	}

	return 0;
}

sub checkOutputFiles {
	
	# Recovers parameters
	my ($Output_file, $Error_file) = @_;
	
	# Initializations
	my $Status = 'Unknown';

	if (-e $Error_file && ! -z $Error_file) {
		
		# Check for critical errors
		open (ERROR_FILE, '<' . $Error_file) or die ('Error: Cannot open/read file: ' . $Error_file . "\n");
		while (my $error_line = <ERROR_FILE>){
			if ($error_line =~ /Segmentation[\s_]*fault|segfault|Erreur de segmentation/i) { $Status = 'Segmentation_fault'; }
		}				
		close (ERROR_FILE);
		
		if ($Status ne 'Segmentation_fault') {
			
			if ( -e $Output_file && ! -z $Output_file) {
				$Status = 'Warning';
			} else {
				$Status = 'Error';
			}
		}
		
	} else {
		
		if ( -e $Output_file && ! -z $Output_file) {
			$Status = 'Ok';
		} else {
			$Status = 'No_output';
		}
	}
	
	return $Status;
}

##################################################
## Basic common functions
##################################################

sub humanReadableDate {
	
	# Recovers parameters
	my $time = shift || time;
	
	# Split time string
	my ($seconde, $minute, $heure, $jour, $mois, $annee, $jour_semaine, $jour_annee, $heure_hiver_ou_ete) = localtime($time);
	$mois  += 1;
	$annee += 1900;

	# On rajoute 0 si le chiffre est compris entre 1 et 9
	foreach ( $seconde, $minute, $heure, $jour, $mois, $annee ) { s/^(\d)$/0$1/; }
	
	return "$jour-$mois-$annee" . '_' . "$heure:$minute:$seconde";
}

sub cleanItAfterMe {
	
	# Remove all files given as arguments to this function
	foreach my $File_to_delete (@_) {
		if (-e  $File_to_delete) {
			unlink($File_to_delete);
		}
	}
	
	return 0;
}
