#!/usr/bin/env perl

my $PROGID = 'pkiss';

sub getPath {
	my ($url) = @_;
	my @parts = split(m|/|, $url);
	pop @parts;
	unshift @parts, "./" if (@parts == 0);
	return join('/', @parts).'/';
}

use lib getPath($0)."../lib/";

use strict;
use warnings;
use Data::Dumper;
use Getopt::Long;
use foldGrammars::Utils;
use foldGrammars::Settings;
use foldGrammars::RNAcast;
use foldGrammars::IO;
use foldGrammars::Structure;
use POSIX 'isatty';

our @ALLMODES = ($Settings::MODE_MFE, $Settings::MODE_SUBOPT, $Settings::MODE_ENFORCE, $Settings::MODE_LOCAL, $Settings::MODE_SHAPES, $Settings::MODE_PROBS, $Settings::MODE_CAST, $Settings::MODE_EVAL, $Settings::MODE_ABSTRACT);
@References::ORDER = ('jan:schud:ste:gie:2011','the:ree:gie:2008','jan:gie:2010','voss:gie:reh:2006','mat:dis:chi:schroe:zuk:tur:2004','tur:mat:2009','ree:gie:2005','the:jan:gie:2010','jan:gie:2014');

my %PARAM;
$PARAM{mode} = {modes => \@ALLMODES, key => 'mode', type => 's', default => $Settings::MODE_SUBOPT, info => "Select the computation mode. Available modes are \"".join('", "', @ALLMODES)."\". Omit the ticks on input.\nDefault is \"@(DEFAULT)\"."};
$PARAM{windowsize} = {modes => [$Settings::MODE_MFE, $Settings::MODE_SUBOPT, $Settings::MODE_ENFORCE, $Settings::MODE_LOCAL, $Settings::MODE_SHAPES, $Settings::MODE_PROBS], key => 'windowSize', type => 'i', gapc => 'w', default => undef, info => "Activates window mode and computes substrings of size <int> for the input. After computation for the first <int> bases is done, the window is pushed <y> bases to the right and the next computation is startet. <y> is set by --@(windowincrement).\n<int> must be a non-zero positive integer, smaller than the input length."};
$PARAM{windowincrement} = {modes => [$Settings::MODE_MFE, $Settings::MODE_SUBOPT, $Settings::MODE_ENFORCE, $Settings::MODE_LOCAL, $Settings::MODE_SHAPES, $Settings::MODE_PROBS], key => 'windowIncrement', gapc => 'i', type => 'i', default => 1, info => "If --@(windowsize) is given, this parameter sets the offset for the next window to <int> bases.\n<int> must be a non-zero positive integer, smaller than --@(windowsize).\nDefault is @(DEFAULT)."};
$PARAM{temperature} = {modes => [$Settings::MODE_MFE, $Settings::MODE_SUBOPT, $Settings::MODE_ENFORCE, $Settings::MODE_LOCAL, $Settings::MODE_SHAPES, $Settings::MODE_PROBS, $Settings::MODE_CAST, $Settings::MODE_EVAL], key => 'temperature', gapc => 'T', type => 'f', default => 37, info => "Rescale energy parameters to a temperature of temp C.\n<float> must be a floating point number.\nDefault is @(DEFAULT) C."};
$PARAM{param} = {modes => [$Settings::MODE_MFE, $Settings::MODE_SUBOPT, $Settings::MODE_ENFORCE, $Settings::MODE_LOCAL, $Settings::MODE_SHAPES, $Settings::MODE_PROBS, $Settings::MODE_CAST, $Settings::MODE_EVAL], key => 'param', gapc => 'P', type => 's', default => undef, infoType => "paramfile", info => "Read energy parameters from paramfile, instead of using the default parameter set. See the RNAlib (Vienna RNA package) documentation for details on the file format.\nDefault are parameters released by the Turner group in 2004 (see [".References::getNumber('mat:dis:chi:schroe:zuk:tur:2004')."] and [".References::getNumber('tur:mat:2009')."])."};
$PARAM{minhairpinlength} = {modes => \@ALLMODES, key => 'minHairpinLength', gapc => 'z', type => 'i', default => 2, info => "Set minimal hairpin length for K-type pseudoknots. The first heuristic step in computung kissing hairpins, is to find stable, non-interrupted helices. These helices must have a minimal length, i.e. number of stacked base-pairs, of <int>. The higher the value, the faster the program, but also the less accurate. This affects only the stems of both hairpins, not their kissing helix!\n<int> must be a positive number.\nDefault is @(DEFAULT)."};
$PARAM{strategy} = {modes => [$Settings::MODE_MFE, $Settings::MODE_SUBOPT, $Settings::MODE_ENFORCE, $Settings::MODE_LOCAL, $Settings::MODE_SHAPES, $Settings::MODE_PROBS, $Settings::MODE_CAST], key => 'strategy', gapc => 's', type => 's', default => 'A', infoType => "char", info => "Select pseudoknot strategy. There are four different strategies how to compute kissing hairpins (K-type pseudoknots). We suggest 'A', see [".References::getNumber('the:jan:gie:2010')."] for details. If you choose 'P' only H-type pseudoknots can be computed.\nAvailable strategies are 'A','B','C','D' and 'P'. On input omit the ticks.\nDefault is '@(DEFAULT)'."};
$PARAM{maxknotsize} = {modes => [$Settings::MODE_MFE, $Settings::MODE_SUBOPT, $Settings::MODE_ENFORCE, $Settings::MODE_LOCAL, $Settings::MODE_SHAPES, $Settings::MODE_PROBS, $Settings::MODE_CAST], key => 'maxKnotSize', gapc => 'l', type => 'i', default => undef, info => "Set a maximal pseudoknot size. To speed up computation, you can limit the number of bases involved in a pseudoknot (and all it's loop regions) by giving <int>.\nOnly positive numbers are allowed for <int>\nBy default, there is no limitation, i.e. --@(maxknotsize) is set to input length."};
$PARAM{hpenalty} = {modes => [$Settings::MODE_MFE, $Settings::MODE_SUBOPT, $Settings::MODE_ENFORCE, $Settings::MODE_LOCAL, $Settings::MODE_SHAPES, $Settings::MODE_PROBS, $Settings::MODE_CAST, $Settings::MODE_EVAL], key => 'Hpenalty', gapc => 'x', type => 'f', default => +9.00, info => "Set init. energy penalty for an H-type pseudoknot. Thermodynamic energy parameters for pseudoknots have not been measured in a wet lab, yet. Thus, you might want to set the penalty for opening a H-type pseudoknot yourself.\n<float> must be a floating point number.\nDefault is @(DEFAULT) kcal/mol."};
$PARAM{kpenalty} = {modes => [$Settings::MODE_MFE, $Settings::MODE_SUBOPT, $Settings::MODE_ENFORCE, $Settings::MODE_LOCAL, $Settings::MODE_SHAPES, $Settings::MODE_PROBS, $Settings::MODE_CAST, $Settings::MODE_EVAL], key => 'Kpenalty', gapc => 'y', type => 'f', default => +12.00, info => "Set init. energy penalty for an K-type pseudoknot. Thermodynamic energy parameters for pseudoknots have not been measured in a wet lab, yet. Thus, you might want to set the penalty for opening a K-type pseudoknot yourself.\n<float> must be a floating point number.\nDefault is @(DEFAULT) kcal/mol."};
$PARAM{allowlp} = {modes => \@ALLMODES, key => 'allowLP', gapc => 'u', type => 'i', default => 0, info => "Lonely base pairs have no stabilizing effect, because they cannot stack on another pair, but they heavily increase the size of the folding space. Thus, we normally forbid them. Should you want to allow them set <int> to 1.\n<int> must be 0 (=don't allow lonely base pairs) or 1 (= allow them).\nDefault is @(DEFAULT), i.e. no lonely base pairs."};
$PARAM{absolutedeviation} = {modes => [$Settings::MODE_SUBOPT, $Settings::MODE_LOCAL, $Settings::MODE_SHAPES, $Settings::MODE_CAST], key => 'absoluteDeviation', gapc => 'e', type => 'f', default => undef, info => "This sets the energy range as an absolute value of the minimum free energy. For example, when --@(absolutedeviation) 10.0 is specified, and the minimum free energy is -10.0 kcal/mol, the energy range is set to 0.0 to -10.0 kcal/mol.\n<float> must be a positive floating point number.\nConnot be combined with --@(relativedeviation)."};
$PARAM{relativedeviation} = {modes => [$Settings::MODE_SUBOPT, $Settings::MODE_LOCAL, $Settings::MODE_SHAPES, $Settings::MODE_CAST], key => 'relativeDeviation', gapc => 'c', type => 'f', default => 10.0, info => "This sets the energy range as percentage value of the minimum free energy. For example, when --@(relativedeviation) 5.0 is specified, and the minimum free energy is -10.0 kcal/mol, the energy range is set to -9.5 to -10.0 kcal/mol.\n<float> must be a positive floating point number.\nBy default, --@(relativedeviation) is set to @(DEFAULT) %.\nCannot be combined with --@(absolutedeviation)."};
$PARAM{shapelevel} = {modes => [$Settings::MODE_SHAPES, $Settings::MODE_PROBS, $Settings::MODE_CAST, $Settings::MODE_EVAL, $Settings::MODE_ABSTRACT], key => 'shapeLevel', gapc => 'q', type => 'i', default => 5, info => "Set shape abstraction level. Currently, we provide five different levels (see ".References::getNumber('jan:schud:ste:gie:2011')."] for their definitions), where 5 is the most abstract and 1 the most concrete one.\n<int> must be a number between 5 and 1.\nDefault is @(DEFAULT) (the most abstract one)."};
$PARAM{lowprobfilter} = {modes => [$Settings::MODE_PROBS], key => 'lowProbFilter', gapc => 'F', type => 'f', default => 0.000001, info => "This option sets a barrier for filtering out results with very low probabilities during calculation. The default value here is @(DEFAULT), which gives a significant speedup compared to a disabled filter. (See ".References::getNumber('voss:gie:reh:2006')."] for details.) Note that this filter can have a slight influence on the overall results. To disable this filter, use option --@(lowprobfilter) 0. \n<float> must be a positive floating point number smaller than 1."};
$PARAM{lowprobfilteroutput} = {modes => [$Settings::MODE_PROBS], key => 'outputLowProbFilter', gapc => undef, type => 'f', default => 0.000001, info => "This option sets a filter for omitting low probability results during output. It is just for reporting convenience. Unlike probability cutoff filter, this option does not have any influence on runtime or probabilities beyond this value. To disable this filter, use option --@(lowprobfilteroutput) 0. \n<float> must be a positive floating point number smaller than 1."};
$PARAM{help} = {modes => \@ALLMODES, key => 'help', default => undef, info => "show this brief help on version and usage"};
$PARAM{binarypath} = {modes => \@ALLMODES, key => 'binPath', type => 's', default => undef, info => $Settings::PROGINFOS{$PROGID}->{name}." expects that according Bellman's GAP compiled binaries are located in the same directory as the Perl wrapper is. Should you moved them into another directory, you must set --@(binarypath) to this new location!"};
$PARAM{binaryprefix} = {modes => \@ALLMODES, key => 'binPrefix', type => 's', default => $Settings::PROGINFOS{$PROGID}->{name}.'_', info => $Settings::PROGINFOS{$PROGID}->{name}." expects a special naming schema for the according Bellman's GAP compiled binaries. On default, each binary is named \"@(DEFAULT)\", followed by the mode, followed by \"_window\" for the window mode version. Thus, for non-window mode \"$Settings::MODE_SUBOPT\" the name would be \"@(DEFAULT)$Settings::MODE_SUBOPT\". With --@(binaryprefix) you can change the prefix into some arbitary one."};
$PARAM{probdecimals} = {modes => [$Settings::MODE_PROBS], key => 'probDecimals', type => 'i', default => 7, info => "Sets the number of digits used for printing shape probabilities.\n<int> must be a positive integer number.\nDefault is @(DEFAULT)."};
$PARAM{verbose} = {modes => \@ALLMODES, key => 'verbose', default => 0, type => 'i', info => "Prints the actual command for Bellman's GAP binary."};
$PARAM{varnaoutput} = {modes => [$Settings::MODE_MFE, $Settings::MODE_SUBOPT, $Settings::MODE_ENFORCE, $Settings::MODE_LOCAL, $Settings::MODE_SHAPES, $Settings::MODE_PROBS, $Settings::MODE_CAST, $Settings::MODE_EVAL], key => 'varna', default => undef, type => 's', info => "Provide a file name to which a HTML formatted version of the output should be saved in."};

my $settings = {};
foreach my $param (keys %PARAM) {
	$settings->{$param} = $PARAM{$param}->{default};
}
my %help = ();
foreach my $param (keys %PARAM) {
	my $optionSec = $PARAM{$param}->{key};
	$optionSec .= "=".$PARAM{$param}->{type} if (exists $PARAM{$param}->{type});
	$help{$optionSec} = \$settings->{$param};
}

&GetOptions( 	
	%help
);

my $sequenceNumber = 0;
checkParameters($settings, \%PARAM);

usage() if (defined $settings->{'help'}); #user asks for help --> print usage and die
if (@ARGV == 0) {
	#input not given via command line parameter
	if (isatty(*STDIN)) {
		#we are somehow in an interactive mode
		if ($settings->{mode} eq $Settings::MODE_EVAL) {
			#expecting two inputs, first sequence, second structure
			print "You are in \"$Settings::MODE_EVAL\" mode. Please give me your RNA sequence:\n";
			my $inputSequence = <STDIN>; chomp $inputSequence;
			print "Second, I need your structure that should be evaluated:\n";
			my $inputStructure = <STDIN>; chomp $inputStructure;
			processInput({sequence => $inputSequence, header => "unnamed sequence", structure => $inputStructure}, $settings);
		} elsif ($settings->{mode} eq $Settings::MODE_ABSTRACT) {
			print "You are in \"".$settings->{mode}."\" mode. Please give me your Vienna-Dot-Bracket string, that shell be converted into a shape string:\n";
			my $inputStructure = <STDIN>; chomp $inputStructure;
			processInput($inputStructure, $settings);
		} elsif ($settings->{mode} eq $Settings::MODE_CAST) {
			print "You are in \"$Settings::MODE_CAST\" mode. Please give me the name of a FASTA file, containing a family of RNA sequences:\n";
			my $inputFile = <STDIN>; chomp $inputFile;
			processInput($inputFile, $settings);
		} else {
			#expecting a sequence or a filename
			print "You are in \"".$settings->{mode}."\" mode. Please give me either your RNA sequence, or a (multiple) FASTA file, containing your sequences:\n";
			my $input = <STDIN>; chomp $input;
			if (-e $input) {
				#since there is a file, having the name of the user input it is very likely that we really should read from a file
				processInput($input, $settings);
			} else {
				#otherwise, we assume it is a single, plain RNA sequence
				processInput({sequence => $input, header => "unnamed sequence"}, $settings);
			}
		}
	} else {
		#input must be delivered via pipe
		die "You are in \"$Settings::MODE_EVAL\" mode. But I can't distinguish RNA sequence from structure if you deliver input via PIPE. Please choose another way to input your information.\n" if ($settings->{mode} eq $Settings::MODE_EVAL);
		processInput(\*STDIN, $settings);
	}
} elsif (@ARGV == 1) {
	#rna sequence or filename, given as command line parameter
	if (-e $ARGV[0]) {
		#since there is a file, having the name of the user input it is very likely that we really should read from a file
		processInput($ARGV[0], $settings);
	} else {
		#otherwise, we assume it is a single, plain RNA sequence
		processInput({sequence => $ARGV[0], header => "unnamed sequence"}, $settings);
	}
} elsif (@ARGV == 2 && $settings->{mode} eq $Settings::MODE_EVAL) {
	#only for MODE_EVAL, rna sequence and secondary structure are given as two command line parameters
	my $inputStructure = $ARGV[1];
	if (-e $inputStructure) { #especially for BiBiServ: structure must be delivered as a filename, thus read the file to get the structure.
		my $command = Settings::getBinary('cat')." $inputStructure";
		$inputStructure = Utils::execute($command); 
		chomp $inputStructure;
	}
	processInput({sequence => $ARGV[0], header => "unnamed sequence", structure => $inputStructure}, $settings);
} else {
	print STDERR "You gave me too many inputs. Please ask for help, via \"".$Settings::PROGINFOS{$PROGID}->{name}." --".$PARAM{help}->{key}."\".\n";
	exit(1);
}

sub processInput {
	my ($input, $refHash_settings) = @_;
	
	if (ref($input) =~ m/HASH/) {
		#input is a sequence
		if ($settings->{mode} eq $Settings::MODE_CAST) {
			die "You are in \"$Settings::MODE_CAST\" mode, but you provided just one sequence. Search for common shapes is useless, just use mode \"$Settings::MODE_SHAPES\" or provide at least two sequences!\n";
		} else {
			if (($settings->{mode} eq $Settings::MODE_EVAL) && (-e $input->{sequence})) {
				foreach my $refHash_seqBlock (@{Utils::applyFunctionToFastaFile($input->{sequence}, sub {my ($refHash_sequence) = @_; return @_;})}) {
					$input->{sequence} = $refHash_seqBlock->{result}->{sequence};
					$input->{header} = $refHash_seqBlock->{result}->{header};
					last;
				}
			}
			doComputation($input, $refHash_settings);
		}
	} elsif (ref($input) =~ m/GLOB/) {
		#input is STDIN
		if ($refHash_settings->{'mode'} eq $Settings::MODE_CAST) {
			RNAcast::cast_doComputation(\*STDIN, $refHash_settings, \&buildCommand, \%PARAM, $Settings::MODE_SHAPES, $Settings::PROGINFOS{$PROGID}->{name});
		} else {
			Utils::applyFunctionToFastaFile(\*STDIN, \&doComputation, $refHash_settings);
		}
	} else {
		#input is a filename
		die "The file '$input' does not exist!\n" if (not -e $input);
		if ($refHash_settings->{'mode'} eq $Settings::MODE_CAST) {
			RNAcast::cast_doComputation($input, $refHash_settings, \&buildCommand, \%PARAM, $Settings::MODE_SHAPES, $Settings::PROGINFOS{$PROGID}->{name});
		} else {
			Utils::applyFunctionToFastaFile($input, \&doComputation, $refHash_settings);
		}
	}
	
	IO::writeVarna($refHash_settings);
}

sub doComputation {
	my ($refHash_sequence, $settings) = @_;

	if (($settings->{mode} eq $Settings::MODE_EVAL) && (not exists $refHash_sequence->{structure})) {
		#since we are in EVAL mode, I guess that half the sequence is RNA, the other half should be a structure
		$refHash_sequence->{structure} = substr($refHash_sequence->{sequence}, length($refHash_sequence->{sequence})/2);
		$refHash_sequence->{sequence} = substr($refHash_sequence->{sequence}, 0, length($refHash_sequence->{sequence})/2);
	} elsif ($settings->{mode} eq $Settings::MODE_ABSTRACT) {
		#we have to convert a Vienna string into a shape class string. We use the eval binaries with a generic RNA input.
		$refHash_sequence->{structure} = $refHash_sequence->{sequence};
		my $dummySequence = "a" x length($refHash_sequence->{structure});
		#to be able to form stacks fake input sequence is a bit more complicated than all 'a'.
		my %pairs = %{Structure::getPairList($refHash_sequence->{structure})};
		foreach my $open (keys(%pairs)) {
			$dummySequence = substr($dummySequence, 0, $open).'G'.substr($dummySequence, $open+1);
			$dummySequence = substr($dummySequence, 0, $pairs{$open}).'C'.substr($dummySequence, $pairs{$open}+1);
		}
		$refHash_sequence->{sequence} = $dummySequence;
	}
	if ($refHash_sequence->{sequence} !~ m/^\s*((A|C|G|U|T)+)\s*$/i) {
		print STDERR "sequence '".$refHash_sequence->{header}."' has been skipped, due to non RNA letter. Only A,C,G,U,T,a,c,g,u,t are allowed.";
	}
	my $seq = $refHash_sequence->{sequence};
	$seq =~ s/t/u/gi;
	my $command = buildCommand($settings, $refHash_sequence);
	my $structureInputFile = undef;
	my $structureArg = "";
	if (($settings->{'mode'} eq $Settings::MODE_EVAL) || ($settings->{'mode'} eq $Settings::MODE_ABSTRACT)) {
		die "You are in \"$Settings::MODE_EVAL\", but your input of RNA sequence and structure is of unequal length!\n" if (length($refHash_sequence->{sequence}) != length($refHash_sequence->{structure}));
		$structureInputFile = Utils::writeInputToTempfile($refHash_sequence->{structure});
		$structureArg = " -f $structureInputFile";
	}
	
	print "Actual call was: $command \"$seq\" $structureArg\n" if ($settings->{verbose});
	my $inputFile = Utils::writeInputToTempfile($seq);
	my $result = Utils::execute("$command -f $inputFile $structureArg 2>&1");
	Utils::execute(Settings::getBinary('rm')." -f $inputFile") if (!$settings->{verbose});
	Utils::execute(Settings::getBinary('rm')." -f $structureInputFile") if ((defined $structureInputFile) && (!$settings->{verbose}));

	print "\n" if ($sequenceNumber != 0);
	IO::parse($result, $refHash_sequence, $Settings::PROGINFOS{$PROGID}->{name}, $settings);
	
	$sequenceNumber++;
	return undef;
}

sub buildCommand {
	my ($settings, $refHash_sequence) = @_;
	
	my $cmd = "";
	$cmd .= $settings->{'binarypath'};
	$cmd .= "/" if (substr($cmd, -1, 1) ne "/");
	$cmd .= $settings->{'binaryprefix'};
	if ($settings->{'mode'} eq $Settings::MODE_ABSTRACT) {
		$cmd .= $Settings::MODE_EVAL;
	} else {
		$cmd .= $settings->{'mode'};
	}
	if (defined $settings->{'windowsize'}) {
		$cmd .= "_window";
		my $windowSize = $settings->{'windowsize'};
		$windowSize = length($refHash_sequence->{sequence}) if ($settings->{'windowsize'} > length($refHash_sequence->{sequence}));
		$cmd .= " -".$PARAM{windowsize}->{gapc}." ".$windowSize;
		$cmd .= " -".$PARAM{windowincrement}->{gapc}." ".$settings->{'windowincrement'};
	}
	$cmd .= " -".$PARAM{temperature}->{gapc}." ".$settings->{'temperature'} if ($settings->{'temperature'} != $PARAM{temperature}->{default});
	$cmd .= " -".$PARAM{param}->{gapc}." ".$settings->{'param'} if (defined $settings->{'param'});
	$cmd .= " -".$PARAM{minhairpinlength}->{gapc}." ".$settings->{'minhairpinlength'} if ($settings->{'minhairpinlength'} != $PARAM{minhairpinlength}->{default});
	$cmd .= " -".$PARAM{strategy}->{gapc}." ".$settings->{'strategy'} if (uc($settings->{'strategy'}) ne uc($PARAM{strategy}->{default}));
	$cmd .= " -".$PARAM{maxknotsize}->{gapc}." ".$settings->{'maxknotsize'} if (defined $settings->{'maxknotsize'});
	$cmd .= " -".$PARAM{hpenalty}->{gapc}." ".$settings->{'hpenalty'} if ($settings->{'hpenalty'} != $PARAM{hpenalty}->{default});
	$cmd .= " -".$PARAM{kpenalty}->{gapc}." ".$settings->{'kpenalty'} if ($settings->{'kpenalty'} != $PARAM{kpenalty}->{default});
	$cmd .= " -".$PARAM{allowlp}->{gapc}." ".$settings->{'allowlp'} if ($settings->{'allowlp'} != $PARAM{allowlp}->{default});
	$cmd .= " -".$PARAM{relativedeviation}->{gapc}." ".$settings->{'relativedeviation'} if ($settings->{'relativedeviation'} != $PARAM{relativedeviation}->{default});
	$cmd .= " -".$PARAM{absolutedeviation}->{gapc}." ".$settings->{'absolutedeviation'} if (defined $settings->{'absolutedeviation'});
	$cmd .= " -".$PARAM{shapelevel}->{gapc}." ".$settings->{'shapelevel'} if ($settings->{'shapelevel'} != $PARAM{shapelevel}->{default});
	$cmd .= " -".$PARAM{lowprobfilter}->{gapc}." ".$settings->{'lowprobfilter'} if ($settings->{'lowprobfilter'} != $PARAM{lowprobfilter}->{default});

	return $cmd;
}

sub checkParameters {
	my ($settings, $refHash_params) = @_;
	
	my $diePrefix = "wrong command line parameter:\n  ";
	
	Utils::automatedParameterChecks(\%PARAM, $settings, \@ALLMODES, $diePrefix);
	Utils::checkBinaryPresents($settings, $diePrefix, [$Settings::MODE_CAST], [], $refHash_params);
	
	die $diePrefix."--".$PARAM{'windowsize'}->{key}." must be a positive integer!\n" if ((defined $settings->{'windowsize'}) && ($settings->{'windowsize'} < 1));
	die $diePrefix."--".$PARAM{'windowsize'}->{key}." is smaller than --".$PARAM{'windowincrement'}->{key}." !\n" if ((defined $settings->{'windowsize'}) && ($settings->{'windowsize'} < $settings->{'windowincrement'}));
	die $diePrefix."the parameter file you specified could not be found.\n" if ((defined $settings->{'param'}) && (not -e $settings->{'param'}));
	die $diePrefix."--".$PARAM{'minhairpinlength'}->{key}." must be a positive integer.\n" if ($settings->{'minhairpinlength'} <= 0);
	die $diePrefix."there is no strategy '".$PARAM{'strategy'}->{key}."'. Please select one of 'A', 'B', 'C', 'D', 'P'.\n" if ($settings->{'strategy'} !~ m/^A|B|C|D|P$/i);
	die $diePrefix."--".$PARAM{'maxknotsize'}->{key}." must be a positive integer.\n" if ((defined $settings->{'maxknotsize'}) && ($settings->{'maxknotsize'} <= 0));
	die $diePrefix."--".$PARAM{'allowlp'}->{key}." can either be 0 or 1, to forbid or disallow lonely base pairs.\n" if ($settings->{'allowlp'} !~ m/^0|1$/);
	die $diePrefix."--".$PARAM{'shapelevel'}->{key}." must be a number between 5 and 1.\n" if (($settings->{'shapelevel'} < 1) || ($settings->{'shapelevel'} > 5));
	die $diePrefix."--".$PARAM{'lowprobfilter'}->{key}." must be a non-negative floating point number below 1.\n" if (($settings->{'lowprobfilter'} >= 1) || ($settings->{'lowprobfilter'} < 0));
	die $diePrefix."--".$PARAM{'lowprobfilteroutput'}->{key}." must be a non-negative floating point number below 1.\n" if (($settings->{'lowprobfilteroutput'} >= 1) || ($settings->{'lowprobfilteroutput'} < 0));
	die $diePrefix."--".$PARAM{'absolutedeviation'}->{key}." and --".$PARAM{'relativedeviation'}->{key}." cannot be set at the same time!\n" if ((defined $settings->{'absolutedeviation'}) && ($settings->{'relativedeviation'} != $PARAM{'relativedeviation'}->{default}));
	die $diePrefix."--".$PARAM{'probdecimals'}->{key}." must be a positive integer number!\n" if ($settings->{'probdecimals'} < 0);
	
	$settings->{'strategy'} = 'D' if (($settings->{'mode'} eq $Settings::MODE_EVAL) || ($settings->{'mode'} eq $Settings::MODE_ABSTRACT)); #must be set to D, to allow for all pseudoknots that can be computed by pKiss.
	
}



sub usage {
	my ($settings) = @_;
	
my $HELP = <<EOF;
# $Settings::PROGINFOS{$PROGID}->{name}: RNA secondary structure predictions including pseudoknots
#        version $Settings::PROGINFOS{$PROGID}->{version} ($Settings::PROGINFOS{$PROGID}->{date})
#        Stefan Janssen (bibi-help\@techfak.uni-bielefeld.de)
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

USAGE: 
perl $Settings::PROGINFOS{$PROGID}->{name} [-mode] [-options] <fasta file name or RNA sequence>

 $Settings::PROGINFOS{$PROGID}->{name} comes with the following different modes of predictions:
EOF
;
	$HELP .= Utils::printIdent("  ".$Settings::MODE_MFE."     : ", Utils::usage_convertInfoText("Computes the single energetically most stable secondary structure for the given RNA sequence. This structure might contain a pseudoknot of type H (simple canonical recursive pseudoknot) or type K (simple canonical recursive kissing hairpin), but need not to. Co-optimal results will be suppressed, i.e. should different prediction have the same best energy value, just an arbitrary one out of them will be reported.", \%PARAM))."\n";
	$HELP .= Utils::printIdent("  ".$Settings::MODE_SUBOPT."  : ", Utils::usage_convertInfoText("Often, the biological relevant structure is hidden among suboptimal predictions. In \"$Settings::MODE_SUBOPT mode\", you can also inspect all suboptimal solutions up to a given threshold (see parameters --@(absolutedeviation) and --@(relativedeviation)). Due to semantic ambiguity of the underlying \"microstate\" grammar, sometimes identical predictions will show up. As Vienna-Dot-Bracket strings they seem to be the same, but according to base dangling they differ and thus might even have slightly different energies. See ".References::getNumber('jan:schud:ste:gie:2011')."] for details.", \%PARAM))."\n";
	$HELP .= Utils::printIdent("  ".$Settings::MODE_ENFORCE." : ", Utils::usage_convertInfoText("Energetically best pseudoknots might be deeply buried under suboptimal solutions. Use \"$Settings::MODE_ENFORCE\" mode to enforce a structure prediction for each of the for classes: \"nested structure\" (as \"RNAfold\" would compute, i.e. without pseudoknots), \"H-type pseudoknot\", \"K-type pseudoknot\" and \"H- and K-type pseudoknot\". Useful if you want to compute the tendency of folding a pseudoknot or not, like in ".References::getNumber('the:ree:gie:2008')."].", \%PARAM))."\n";
	$HELP .= Utils::printIdent("  ".$Settings::MODE_LOCAL."   : ", Utils::usage_convertInfoText("Computes energetically best and suboptimal local pseudoknots. Local means, leading and trailing bases can be omitted and every prediction is a pseudoknot.", \%PARAM))."\n";
	$HELP .= Utils::printIdent("  ".$Settings::MODE_SHAPES."  : ", Utils::usage_convertInfoText("Output of \"$Settings::MODE_SUBOPT\" mode is crowded by many very similar answers, which make it hard to focus to the \"important\" changes. The abstract shape concept ".References::getNumber('jan:gie:2010')."] groups similar answers together and reports only the best answer within such a group. Due to abstraction, suboptimal analyses can be done more thorough, by ignoring boring differences.\n(see parameter --@(shapelevel))", \%PARAM))."\n";
	$HELP .= Utils::printIdent("  ".$Settings::MODE_PROBS."   : ", Utils::usage_convertInfoText("Structure probabilities are strictly correlated to their energy values. Grouped together into shape classes, their probabilities add up. Often a shape class with many members of worse energy becomes more probable than the shape containing the mfe structure but not much more members. See ".References::getNumber('voss:gie:reh:2006')."] for details on shape probabilities.", \%PARAM))."\n";
	$HELP .= Utils::printIdent("  ".$Settings::MODE_CAST."    : ", Utils::usage_convertInfoText("This mode is the RNAcast approache, see ".References::getNumber('the:jan:gie:2010')."].\nFor a family of RNA sequences, this method independently enumerates the near-optimal abstract shape space, and predicts as the consensus an abstract shape common to all sequences. For each sequence, it delivers the thermodynamically best structure which has this common shape.\nInput is a multiple fasta file, which should contain at least two sequences.\nOutput is sorted by \"score\" of common shapes, i.e. summed free energy of all sequences. R is the rank (= list position) of the shape in individual sequence analysis.", \%PARAM))."\n";
	$HELP .= Utils::printIdent("  ".$Settings::MODE_EVAL."    : ", Utils::usage_convertInfoText("Evaluates the free energy of an RNA molecule in fixed secondary structure, similar to RNAeval from the Vienna group. Multiple answers stem from semantic ambiguity of the underlying grammar.\nIt might happen, that your given structure is not a structure for the sequence. Maybe your settings are too restrictive, e.g. not allowing lonely base-pairs (--@(allowlp)), too long hairpin stems for pseudoknots (--@(minhairpinlength)) or the given pseudoknot is more complex than those of ".$Settings::PROGINFOS{$PROGID}->{name}.".\nIf you input a (multiple) FASTA file, ".$Settings::PROGINFOS{$PROGID}->{name}." assumes that exactly first half of the contents of each entry is RNA sequence, second half is the according structure. Whitespaces are ignored.", \%PARAM))."\n";
	$HELP .= Utils::printIdent("  ".$Settings::MODE_ABSTRACT." : ", Utils::usage_convertInfoText("Converts a Vienna-Dot-Bracket representation of a secondary structure into a shape string.", \%PARAM))."\n";

	my @paramGroups = ();
	push @paramGroups, {name => "GENERAL OPTIONS", elements => ['mode', 'absolutedeviation', 'relativedeviation', 'shapelevel', 'lowprobfilter', 'lowprobfilteroutput', 'windowsize', 'windowincrement']};
	push @paramGroups, {name => "PSEUDOKNOT OPTIONS", elements => ['strategy', 'minhairpinlength', 'maxknotsize', 'hpenalty', 'kpenalty']};
	push @paramGroups, {name => "FOLDING OPTIONS", elements => ['allowlp', 'temperature', 'param']};
	push @paramGroups, {name => "SYSTEM OPTIONS", elements => ['binarypath', 'binaryprefix', 'probdecimals', 'help', 'verbose','varnaoutput']};
	foreach my $refHash_group (@paramGroups) {
		$HELP .= $refHash_group->{name}.":\n";
		for my $par (@{$refHash_group->{elements}}) {
			$HELP .= Utils::printParamUsage($PARAM{$par}, \%PARAM, \@ALLMODES)."\n";
		}
	}

	$HELP .= "REFERENCES:\n";
	foreach my $refID ('jan:schud:ste:gie:2011','the:ree:gie:2008','jan:gie:2010','voss:gie:reh:2006','mat:dis:chi:schroe:zuk:tur:2004','tur:mat:2009','ree:gie:2005','the:jan:gie:2010') {
		$HELP .= References::printReference($refID);
	}
	$HELP .= "CITATION:\n    If you use this program in your work you might want to cite:\n\n";
	foreach my $refID ('jan:gie:2014') {
		$HELP .= References::printReference($refID);
	}

	print $HELP;
	exit(0);
}

