#!/usr/bin/perl --
use warnings;
use strict;
use English;
use FindBin qw($RealBin);
use Getopt::Long;
use IPC::Open3;

# Check to see if they've mistakenly downloaded the source distribution
# since several people have made this mistake

if (-e "$RealBin/uk/ac/babraham/SeqMonk/SeqMonkApplication.java") {
	die "This is the source distribution of SeqMonk.  You need to get the compiled version if you want to run the program\n";
}


# We now need to scan the command line for switches which we're going
# to pass on to the main java program.

my $help;
my $memory;

my $result = GetOptions('help' => \$help,
						'memory=i' => \$memory,
						 );
						 
if ($memory) {
	die "\n[ERROR] The use of the -m parameter is now deprecated.  
	
Use the settings under Edit > Preferences > Memory within SeqMonk if you really need to change the default memory allowance\n\n";
}

# Check the simple stuff first

if ($help) {
	# Just print the help and exit
	print while(<DATA>);
	exit;
}



if ($ENV{CLASSPATH}) {
	$ENV{CLASSPATH} .= ":$RealBin:$RealBin/htsjdk.jar:$RealBin/Jama-1.0.2.jar:$RealBin/commons-math3-3.5.jar";
}
else {
	$ENV{CLASSPATH} = "$RealBin:$RealBin/htsjdk.jar:$RealBin/Jama-1.0.2.jar:$RealBin/commons-math3-3.5.jar";
}

# If we're on OSX and launched via an app bundle we get a -psn argument passed, which we don't
# want, so get rid of that if it's present
if ($ARGV[0] and $ARGV[0] =~ /^-psn/) {
	shift @ARGV;
}


# If people are using this launcher under windows then their classpath needs to be separated by semicolons
# instead of colons.
	
if ($^O =~ /Win/) {
	$ENV{CLASSPATH} =~ s/.jar:/\.jar;/g;
	$ENV{CLASSPATH} =~ s/\Q$RealBin:/$RealBin;/g;
}

warn "CLASSPATH is : $ENV{CLASSPATH}\n";

# We need to make sure the language is English so we can parse the output of some
# standard commands, however if we leave this in place it can break things for people
# in other locales so we need to restore the original value.

my $original_lang = undef;
my $original_lcall = undef;

if (exists $ENV{LANG}) {
	$original_lang = $ENV{LANG};
}	

if (exists $ENV{LC_ALL}) {
	$original_lcall = $ENV{LC_ALL};
}

$ENV{LANG}='C';
$ENV{LC_ALL}='C';


# We need to find the java interpreter.  We'll start from the assumption that this
# is included in the path.

my $java_exe = "java";

# We might have bundled a jre with the installation.  If that's the case then we'll
# use the interpreter which is bundled in preference to the system one.

# Windows first
if (-e "$RealBin/jre/bin/java.exe") {
	$java_exe = "$RealBin/jre/bin/java.exe";
}
# Linux
elsif (-e "$RealBin/jre/bin/java") {
	$java_exe = "$RealBin/jre/bin/java";
}
# OSX
elsif (-e "$RealBin/jre/Contents/Home/bin/java") {
	$java_exe = "$RealBin/jre/Contents/Home/bin/java";
}

warn "Java interpreter is '$java_exe'\n";

warn `\"$java_exe\" -version`."\n";

my @java_args;




# If we have been passed a filename to open then we need to make sure we capture this so we can
# pass it on to the final java command.
my $file = $ARGV[0];
$file = "" unless ($file);


$memory = determine_optimal_memory();



# We now need to correct for the fact that java doesn't honour the
# heap size you set
my $actual_memory = correct_memory($memory,$java_exe);

warn "Correcting for VM actual requested allocation for $memory is $actual_memory\n";

unshift @java_args,"-Xmx${actual_memory}m";

# Expand the default stack size to allow for large recursive sorts
unshift @java_args,"-Xss4m";

# Put the environment back to how we found it
if (defined $original_lang) {
	$ENV{LANG} = $original_lang;
}
else {
	delete $ENV{LANG};
}

if (defined $original_lcall) {
	$ENV{LC_ALL} = $original_lcall;
}
else {
	delete $ENV{LC_ALL};
}


warn "Command is: $java_exe @java_args uk.ac.babraham.SeqMonk.SeqMonkApplication $file\n";

exec $java_exe,@java_args, "uk.ac.babraham.SeqMonk.SeqMonkApplication", $file;

sub print_error {
	
	# We wrap errors like this so we can keep a windows shell window open
	# so the user can see any errors we generate
	
	my ($error) = @_;
	
	warn $error;
	
	$_ = <STDIN>;
	
	exit 1;
}

sub correct_memory {
	
	my ($requested_memory,$java_exe) = @_;
	
	my $actual_memory = `"$java_exe" -Xmx${requested_memory}m uk.ac.babraham.SeqMonk.Utilities.ReportMemoryUsage`;
	
	if ($actual_memory =~ /^(\d+)/) {
		$actual_memory = $1;
		
		$actual_memory = int($requested_memory * ($requested_memory/$actual_memory));
		
		return $actual_memory;
	}
	else {
		warn "Failed to correct requested memory. Request output was '$actual_memory'";
		return $requested_memory;
	}
}

sub determine_optimal_memory {
	
	# We'll set a ceiling for the memory allocation.  On a 32-bit OS this is going
	# to be 1536m (the max it can safely handle)
	my $max_memory = 1331;
	
	
	# We need not only a 64 bit OS but 64 bit java as well. It's easiest to just test
	# java since the OS support must be there if you have a 64 bit JRE.
	
	
	my ($in,$out);
	open3(\*IN,\*OUT,\*OUT,"$java_exe -version") or print_error("Can't find $java_exe");
	close IN;
	while (<OUT>) {
		if (/64-Bit/) {
			$max_memory = 10240;
			
			# If they have a 64 bit OS then they might have overridden the max memory
			# limit in their preferences and we'll honour this if they have.
			
			my $prefs_max = get_prefs_max();
			if ($prefs_max and $prefs_max > $max_memory) {
				$max_memory = $prefs_max;
			}
			
		}
	}
	close OUT;
		
	warn "Memory ceiling is $max_memory\n";
	
	# The way we determine the amount of physical memory is OS dependent.
	my $os = $^O;
	
	my $physical;
	if ($os =~ /Win/) {
		$physical = get_windows_memory($max_memory);
		# Set this to get the OS to do upscaling on high DPI displays
		push @java_args, "-Dsun.java2d.dpiaware=false";
		
	}
	elsif ($os =~/darwin/) {
		$physical = get_osx_memory($max_memory);
		
		# Add the OSX specific options to use a standard OSX menu bar
		# and set the program name to something sensible.
				
		push @java_args, '-Xdock:name=SeqMonk';
		push @java_args, "-Xdock:icon=$RealBin/../Resources/seqmonk.icns";
		push @java_args, "-Dapple.laf.useScreenMenuBar=true";

	}
	elsif ($os =~ /bsd/) {
		$physical = get_osx_memory($max_memory);	
	}
	else {
		push @java_args, "-Dawt.useSystemAAFontSettings=on";
		push @java_args, "-Dswing.aatext=true";

		$physical = get_linux_memory($max_memory);
	}
	
	warn "Raw physical memory is $physical\n";
	
	# We then set the memory to be the minimum of 2/3 of the physical
	# memory or the ceiling, whichever is lower.
	$physical = int(($physical/3)*2);
	
	if ($max_memory < $physical) {
		return $max_memory;
	}
	
	warn "Using $physical MB of RAM to launch seqmonk\n";
	return $physical;
	
}

sub get_prefs_max () {

	# We need to get the location of the seqmonk prefs file and see if it contains
	# a memory setting.
	
	my $prefs_file = `"$java_exe" uk.ac.babraham.SeqMonk.Utilities.PrefsPrinter`;
	
	$prefs_file =~ s/[\r\n]+//g;
	$prefs_file =~ s/\\/\//g;
	
	warn "Prefs file is at: $prefs_file\n";
	
	unless (-e $prefs_file) {
		return 0;
	}
	
	open (IN,$prefs_file) or do {
		warn "ERROR: Can't open prefs file $prefs_file: $!";
		return(0);
	};
	
	while (<IN>) {
		if (/Memory\t(\d+)/) {
			warn "Set memory to $1 from prefs file\n";
			return ($1);
		}	
	}
	
	warn "WARNING: No memory setting found in prefs file\n";

}


sub get_linux_memory {

	# Before we look at physical memory we can check whether we're running on
	# a SLURM cluster.  In this case the amount of memory we have isn't the 
	# amount which is on the machine, but is the allocation we've been given,
	# which will be lower than the physical memory on the node.
	
	if (exists $ENV{SLURM_JOB_ID}) {
		my $scontrol = `scontrol show jobid=$ENV{SLURM_JOB_ID}`;
		
		if ($scontrol =~ /TRES=.*mem=(\d+)([MG])/) {
			if ($2 eq 'M') {
				return $1;
			}
			else {
				return $1 * 1024;
			}
		}
		else {
			warn "Found a SLURM JOB ID, but couldn't get a memory allocation from\n$scontrol\n";
		}	
	}	

	# We get the amount of physical memory on linux by parsing the output of free
	
	open (MEM,"free -m |") or print_error("Can't launch free on linux: $!");
	
	while (<MEM>) {
		if (/^Mem:\s+(\d+)/) {
			return $1;
		}
	}
	
	close MEM;
	
	print_error("Couldn't parse physical memory from the output of free");
}

sub get_osx_memory {
	
	# We get the amount of physical memory on OSX by parsing the output of top
	
	open (MEM,"top -l 1 -n 0 |") or print_error("Can't get amount of memory on OSX: $!");
	
	# Output from pre-mavericks OSX looks like this:
	#
	# Processes: 143 total, 5 running, 2 stuck, 136 sleeping, 842 threads
	# 2013/10/24 08:22:58
	# Load Avg: 1.47, 1.12, 0.90
	# CPU usage: 2.12% user, 10.63% sys, 87.23% idle
	# SharedLibs: 1040K resident, 0B data, 0B linkedit.
 	# MemRegions: 33232 total, 2277M resident, 99M private, 781M shared.
	# PhysMem: 1650M wired, 3229M active, 1137M inactive, 6016M used, 2168M free.
	# VM: 306G vsize, 1026M framework vsize, 5152376(0) pageins, 0(0) pageouts.
	# Networks: packets: 3155246/3187M in, 2197390/1031M out.
	# Disks: 214828/7466M read, 673736/15G written.
	#
	# In Mavericks it changed to look like this:
	#
	# Processes: 172 total, 3 running, 6 stuck, 163 sleeping, 817 threads 
	# 2013/10/24 08:27:53
	# Load Avg: 1.42, 1.78, 2.59 
	# CPU usage: 11.66% user, 11.66% sys, 76.66% idle 
	# SharedLibs: 12M resident, 14M data, 0B linkedit.
	# MemRegions: 28889 total, 2123M resident, 104M private, 371M shared.
	# PhysMem: 6442M used (1287M wired), 1743M unused.
	# VM: 388G vsize, 1065M framework vsize, 104(0) swapins, 126(0) swapouts.
	# Networks: packets: 2711222/3332M in, 387599/29M out.
	# Disks: 340088/8060M read, 174291/11G written.
	#
	# We're parsing the PhyMem line in each case to get the full amount of
	# system memory.
	
	my $total_mem = 0;
	
	while (<MEM>) {
		if (/^PhysMem:.*?(\d+)([MG])\s+used.*?(\d+)([MG])\s+(free|unused)/) {
		
			if ($2 eq 'G') {
				$total_mem += $1 * 1024;
			}
			else {
				$total_mem += $1;			
			}
			if ($4 eq 'G') {
				$total_mem += $3 * 1024;
			}
			else {
				$total_mem += $3;
			}
		}	

	}
	
	close MEM;
	
	unless ($total_mem) {
		print_error("Could't parse physical memory from the output of top");
	}
	
	return $total_mem;
	
}

sub get_windows_memory {
	
	warn "Getting windows physical memory\n";
	
	# This code was adapted from an answer posted by Tom Feiner on
	# stackoverflow
	#
	# http://stackoverflow.com/questions/423797/how-can-i-find-the-exact-amount-of-physical-memory-on-windows-x86-32bit-using-per
	
	my ($max_memory) = @_;
	
	eval {
		require Win32::OLE;
		Win32::OLE->import (qw( EVENTS HRESULT in ));
		1;
	} or do {
		print_error("Couldn't load Win32 module to determine windows memory");
	};
	
    my $WMI = Win32::OLE->GetObject( "winmgmts:{impersonationLevel=impersonate,(security)}//./" ) || print_error ("Could not get Win32 object: $OS_ERROR");
    my $total_capacity = 0;

	foreach my $object (in($WMI->InstancesOf( 'Win32_PhysicalMemory' ))) {
		$total_capacity += $object->{Capacity};
	}

    my $total_capacity_in_mb = $total_capacity / (1024*1024);
        
    return $total_capacity_in_mb;
}

__DATA__

            SeqMonk - A high throughput sequence visualisation tool

SYNOPSIS

	seqmonk [-m 4000]


DESCRIPTION

	SeqMonk is a graphical application for the visualisation and analysis of
	high throughput mapped read data.
    
    The options for the program as as follows:
    
    -h --help       Print this help file and exit
    
                        
BUGS

    Any bugs in SeqMonk should be reported either to simon.andrews@babraham.ac.uk
    or in https://github.com/s-andrews/seqmonk/issues
                   
    
