package SPARCplug;

use strict;

#use Time::Piece;
require "parallelProcessing.pl";

our %raw_headers;
our $esorex_msg_level;
our $cal_directory;

sub run_calibrations {
	my $steps_arg          = shift(@_);
	my $band_arg           = shift(@_);
	my $angle_arg          = shift(@_);
	my $process_all        = shift(@_);
	my @possible_bands     = qw(H K HK IZ YJ);
	my @possible_rotangles = qw(0 60 120 180 240 300);
	my @possible_steps     = qw(DARK FLAT WAVECAL ILLUM);

	my @steps =
	  option_list( $steps_arg, \@possible_steps, "calibration steps" );
	my @bands = option_list( $band_arg, \@possible_bands, "bands" );
	my @rotangles =
	  option_list( $angle_arg, \@possible_rotangles, "rotator angles" );

	if ( scalar grep ( /^DARK$/, @steps ) ) {
		print "Analyzing dark current template data\n";
		run_dark($process_all);
		wait_for_subps();
	}

	if ( scalar grep ( /^FLAT$/, @steps ) ) {
		print "Analyzing flat field template data\n";
		foreach my $band (@bands) {
			foreach my $rotangle (@rotangles) {
				run_flatfield( $band, $rotangle, $process_all );
			}
		}
		wait_for_subps();
	}

	if ( scalar grep ( /^WAVECAL$/, @steps ) ) {
		print "Analyzing wave cal template data\n";
		foreach my $band (@bands) {
			foreach my $rotangle (@rotangles) {
				run_wavecal( $band, $rotangle, $process_all );
			}
		}
			wait_for_subps();
	}

	if ( scalar grep ( /^ILLUM$/, @steps ) ) {
		print "Analyzing illumination template data\n";
		foreach my $band (@bands) {
			run_illumination( $band, $process_all );
		}
		wait_for_subs();
	}
}

sub run_dark {
	my $process_all = shift(@_);
	my $tplid       = "KMOS_spec_cal_dark";
	my @frames =
	  grep ( ( $raw_headers{$_}{tpl_id} eq $tplid ), keys(%raw_headers) );
	if ( $#frames == -1 ) {
		print "Found no frames with TPL_ID $tplid\n";
		return;
	}
	my @tplStarts;
	foreach (@frames) {
		push @tplStarts, $raw_headers{$_}{tpl_start};
	}
	@tplStarts = sort { $b cmp $a } delete_multiple_entries(@tplStarts);
	foreach my $tplStart (@tplStarts) {
		my $timeStamp = $tplStart;
		$timeStamp =~ s/:/-/g;
		my $sofFile = "dark_${timeStamp}.sof";
		open( my $SOF, ">", $sofFile )
		  or die "Can't open dark SOF file $sofFile";
		foreach ( keys(%raw_headers) ) {
			next if $raw_headers{$_}{tpl_start} ne $tplStart;
			printf $SOF "$_    DARK\n";
		}
		close $SOF;
		system "sort -r -k2,2 -o $sofFile $sofFile";

		my $mvCmd;
		if ($process_all) {
			foreach my $cat (qw(master_dark badpixel_dark)) {
				$mvCmd .= "; mv -v ${cat}.fits ../../${cat}_${timeStamp}.fits";
			}
		}
		else {
			$mvCmd = "";
		}
		run_esorex_cmd2( "kmo_dark", $sofFile, $mvCmd );
		last if !$process_all;
	}
}

sub run_flatfield {
	my $band        = shift(@_);
	my $rotangle    = shift(@_);
	my $process_all = shift(@_);
	my $tplid       = "KMOS_spec_cal_calunitflat";

	my @frames = grep ( (
			     ( $raw_headers{$_}{tpl_id} eq "KMOS_spec_cal_calunitflat" )
			  && ( $raw_headers{$_}{grating_band} eq $band )
			  && ( $raw_headers{$_}{rot_angle} ne ""
				&& $raw_headers{$_}{rot_angle} == $rotangle )
		),
		keys(%raw_headers) );
	if ( $#frames == -1 ) {
		print
"Found no frames with TPL_ID $tplid, band $band, rotator angle $rotangle\n";
		return;
	}

	my @tplStarts;
	foreach (@frames) {
		push @tplStarts, $raw_headers{$_}{tpl_start};
	}
	@tplStarts = sort { $b cmp $a } delete_multiple_entries(@tplStarts);
	foreach my $tplStart (@tplStarts) {
		my $timeStamp = $tplStart;
		$timeStamp =~ s/:/-/g;
		my $sofFile = "flat_${band}_${rotangle}_${timeStamp}.sof";
		open( my $SOF, ">", $sofFile )
		  or die "Can't open SOF file $sofFile";
		foreach ( keys(%raw_headers) ) {
			next if $raw_headers{$_}{tpl_start} ne $tplStart;
			my $ca = $raw_headers{$_}{rot_angle};
			next if !( $ca eq "" || $ca == $rotangle );
			my $label = "FLAT_ON";
			$label = "FLAT_OFF" if $raw_headers{$_}{filter_band} eq "Block";
			printf $SOF "$_    $label\n";
		}
		my $file = findClosestCalFile( "badpixel_dark", $tplStart );
		my $pwd = `pwd`;
		chop($pwd);
		printf $SOF "${pwd}/${file}  BADPIXEL_DARK\n";
		close $SOF;
		system "sort -r -k2,2 -o $sofFile $sofFile";

		my $mvCmd;
		my $ba_spec =
		  uc("${band}${band}${band}_${rotangle}");
		foreach my $cat (qw(master_flat badpixel_flat xcal ycal flat_edge)) {
			$mvCmd .=
"; mv -v ${cat}_${ba_spec}.fits ../../${cat}_${ba_spec}_${timeStamp}.fits";
		}

		run_esorex_cmd2( "kmo_flat", $sofFile, $mvCmd );
		last if !$process_all;
	}
}

sub run_wavecal {
	my $band        = shift(@_);
	my $rotangle    = shift(@_);
	my $process_all = shift(@_);
	my $tplid       = "KMOS_spec_cal_wave";
	my @frames      = grep ( (
			     ( $raw_headers{$_}{tpl_id} eq $tplid )
			  && ( $raw_headers{$_}{grating_band} eq $band )
			  && ( $raw_headers{$_}{rot_angle} ne ""
				&& $raw_headers{$_}{rot_angle} == $rotangle )
		),
		keys(%raw_headers) );
	if ( $#frames == -1 ) {
		print
"Found no frames with TPL_ID $tplid, band $band, rotator angle $rotangle\n";
		return;
	}

	my @tplStarts;
	foreach (@frames) {
		push @tplStarts, $raw_headers{$_}{tpl_start};
	}
	@tplStarts = sort { $b cmp $a } delete_multiple_entries(@tplStarts);
	foreach my $tplStart (@tplStarts) {
		my $timeStamp = $tplStart;
		$timeStamp =~ s/:/-/g;
		my $sofFile = "wavecal_${band}_${rotangle}_${timeStamp}.sof";
		open( my $SOF, ">", $sofFile )
		  or die "Can't open SOF file $sofFile";
		foreach ( keys(%raw_headers) ) {
			next if $raw_headers{$_}{tpl_start} ne $tplStart;
			my $ca = $raw_headers{$_}{rot_angle};
			next if !( $ca eq "" || $ca == $rotangle );
			my $label = "ARC_ON";
			$label = "ARC_OFF" if $raw_headers{$_}{filter_band} eq "Block";
			printf $SOF "$_    $label\n";
		}
		my $brs = sprintf( "${band}${band}${band}_%d",
			$rotangle );
		my $pwd = `pwd`;
		chop($pwd);
		my $file;
		$file = findClosestCalFile( "master_flat_${brs}", $tplStart );
		printf $SOF " ${pwd}/${file}  MASTER_FLAT\n";
		$file = findClosestCalFile( "badpixel_flat_${brs}", $tplStart );
		printf $SOF " ${pwd}/${file}  BADPIXEL_FLAT\n";
		$file = findClosestCalFile( "xcal_${brs}", $tplStart );
		printf $SOF " ${pwd}/${file}  XCAL\n";
		$file = findClosestCalFile( "ycal_${brs}", $tplStart );
		printf $SOF " ${pwd}/${file}  YCAL\n";
		$file = findClosestCalFile( "flat_edge_${brs}", $tplStart );
		printf $SOF " ${pwd}/${file}  FLAT_EDGE\n";
		printf $SOF "$cal_directory/kmos_ar_ne_list_%s.fits  ARC_LIST\n",
		  lc($band);
		printf $SOF "$cal_directory/kmos_wave_band.fits  WAVE_BAND\n";
		printf $SOF "$cal_directory/kmos_wave_ref_table.fits  REF_LINES\n";
		close $SOF;
		system "sort -r -k2,2 -o $sofFile $sofFile";

		my $mvCmd;
		my $ba_spec =
		  uc("${band}${band}${band}_${rotangle}");
		foreach my $cat (qw(lcal det_img_wave)) {
			$mvCmd .=
"; mv -v ${cat}_${ba_spec}.fits ../../${cat}_${ba_spec}_${timeStamp}.fits";
		}

		run_esorex_cmd2( "kmo_wave_cal", $sofFile, $mvCmd );
		last if !$process_all;
	}
}

sub run_illumination {
	my $band        = shift(@_);
	my $process_all = shift(@_);

	my $tplid    = "KMOS_spec_cal_skyflat";
	my @skyflats = grep ( (
			     ( $raw_headers{$_}{tpl_id} eq $tplid )
			  && ( $raw_headers{$_}{grating_band} eq $band )
		),
		keys(%raw_headers) );
	if ( $#skyflats == -1 ) {
		print "Found no frames with TPL_ID $tplid, band $band\n";
		return;
	}
	my @sortedSkyFlats =
	  sort { $raw_headers{$b}{date_obs} cmp $raw_headers{$a}{date_obs} }
	  @skyflats;
	my $tplStart    = $raw_headers{ $sortedSkyFlats[0] }{tpl_start};
	my $expTime     = $raw_headers{ $sortedSkyFlats[0] }{exp_time};
	my $rotangle    = $raw_headers{ $sortedSkyFlats[0] }{rot_angle};
	my $sofFile     = "skyflat_${band}.sof";
	my $calRotAngle = fit_rotator_steps($rotangle);
	open( my $SOF, ">", $sofFile )
	  or die "Can't open dark SOF file $sofFile";

	foreach (@sortedSkyFlats) {
		next
		  if $raw_headers{$_}{tpl_start} ne $tplStart
			  || $raw_headers{$_}{exp_time} ne $expTime;
		my $label = "FLAT_SKY";
		printf $SOF "$_    $label\n";
	}
	my $bandString = "${band}${band}${band}";
	printf $SOF "master_dark.fits   MASTER_DARK\n";
	printf $SOF "master_flat_${bandString}_%d.fits   MASTER_FLAT\n",
	  $calRotAngle;
	printf $SOF "xcal_${bandString}_%d.fits   XCAL\n", $calRotAngle;
	printf $SOF "ycal_${bandString}_%d.fits   YCAL\n", $calRotAngle;
	printf $SOF "lcal_${bandString}_%d.fits   LCAL\n", $calRotAngle;
	printf $SOF "$cal_directory/kmos_wave_band.fits  WAVE_BAND\n";

	close $SOF;

	run_esorex_cmd( "kmo_illumination", $sofFile );
}

sub fit_rotator_steps {
	my $angle = shift(@_);
	my $rotator;

	#	print "$angle --->";
	$angle += 360 if $angle < 0;

	#	print "$angle --->";
	for ( $rotator = 0 ; $rotator <= 360 ; $rotator += 60 ) {

		#		print "$rotator ...";
		last if abs( $angle - $rotator ) <= 30;
	}
	$rotator = 0 if $rotator == 360;

	#	print " ==> $rotator\n";
	return $rotator;
}

sub findClosestCalFile {
	my $prefix          = shift();
	my $startTimeString = shift();
	my $file;
	my ( $startTime, $cmd, @fileList, @fields, $time, $min );

	$startTime = `date -u -d $startTimeString +%s`;

	$cmd = "dfits ${prefix}*.fits|fitsort -d tpl.start";
	open( my $DFITS, "-|", $cmd );
	while (<$DFITS>) {
		@fields = split;
		$time   = `date -u -d $fields[1] +%s`;
		push @fileList, { name => $fields[0], time => $time };
	}
	close $DFITS;

	$min = $startTime;
	foreach my $href (@fileList) {
		my $diff = abs( $startTime - $href->{time} );
		if ( $diff < $min ) {
			$min  = $diff;
			$file = $href->{name};
		}
	}
	return $file;
}

sub run_esorex_cmd2 {
	my $recipe  = shift(@_);
	my $sofFile = shift(@_);
	my $mvCmd   = shift(@_);
	my $cmd     = "esorex --msg-level=$esorex_msg_level $recipe $sofFile";

	my $fitsfields =
	  'ins.{filt,grat}1.name ocs.rot.naangle tpl.id tpl.start date-obs exptime';
	my $fcmd = "bash -c \"";
	$fcmd .= "echo Content of SOF file $sofFile; cat $sofFile; ";
	$fcmd .=
"echo FITS header information of SOF files; gawk '{print \\\$1}' $sofFile |xargs dfits|fitsort $fitsfields; ";
	$fcmd .= "time $cmd; ";

	#$fcmd .= "echo \\\"`date`\\\">test.log;";
	$fcmd .= "echo Returned exit value \\\$? ";
	$fcmd .= "$mvCmd" if defined($mvCmd);
	$fcmd .= "\"";

	#	$fcmd .= "\" >$sofFile.log 2>&1";
	queue_cmds( $fcmd, $sofFile, "Processing $sofFile", "$sofFile.log" );
}

sub run_esorex_cmd {
	my $recipe  = shift(@_);
	my $sofFile = shift(@_);
	my $mvCmd   = shift(@_);
	my $cmd     = "esorex --msg-level=$esorex_msg_level $recipe $sofFile";

	my $pid = fork();
	die "Fork failed $!" unless defined $pid;
	if ( !$pid ) {
		my $fitsfields =
'ins.{filt,grat}1.name ocs.rot.naangle tpl.id tpl.start date-obs exptime';
		my $fcmd = "bash -c \"";
		$fcmd .= "echo Content of SOF file $sofFile; cat $sofFile; ";
		$fcmd .=
"echo FITS header information of SOF files; gawk '{print \\\$1}' $sofFile |xargs dfits|fitsort $fitsfields; ";

		#		$fcmd .= "time $cmd; ";
		$fcmd .= "echo Returned exit value \\\$? ";
		$fcmd .= "$mvCmd" if defined($mvCmd);
		$fcmd .= "\" >$sofFile.log 2>&1";
		print $fcmd;
		exec $fcmd;

# "bash -c \"cat $sofFile; gawk '{print \\\$1}' $sofFile |xargs dfits|fitsort $fitsfields;$cmd; echo Returned exit value \\\$?\" >$sofFile.log 2>&1";
	}

	print "PID $pid : $cmd\n";
}

sub wait_for_subs {
	my $pid;
	print "PIDs finished already: ";
	$pid = 0;
	while ( $pid != -1 ) {
		$pid = wait();
		print "$pid " if $pid ne -1;
	}
	print "\n";
}

sub option_list {
	my $value_string          = shift(@_);
	my $arrayref              = shift(@_);
	my @possible_values       = @$arrayref;
	my $msg_part              = shift(@_);
	my %requested_values_hash = ();
	my @requested_values      = ();
	my @values                = split( ",", $value_string );

	if ( $#values == 0 && $values[0] eq "LIST" ) {
		printf "Known values for $msg_part are %s\n",
		  join( ", ", @possible_values );
		return @requested_values;
	}

	if ( $#values == 0 && $values[0] eq "ALL" ) {
		shift(@values);
		unshift( @values, @possible_values );
	}

	# add all possible values if only excluding values args are given
	unshift( @values, @possible_values )
	  if $#values + 1 == scalar grep ( /^-/, @values );

	foreach (@possible_values) {
		$requested_values_hash{$_} = 0;
	}
	my $detected_error = 0;
	foreach (@values) {
		my $cstep = $_;
		$cstep = substr( $_, 1 ) if substr( $_, 0, 1 ) eq "-";
		if ( !defined( $requested_values_hash{$cstep} ) ) {
			print "$msg_part $_ is unknown\n";
			$detected_error = 1;
		}
		else {
			$requested_values_hash{$cstep} = 1;
			$requested_values_hash{$cstep} = 0 if substr( $_, 0, 1 ) eq "-";
		}
	}

	#	print join( ",", @values ), "\n";
	#	foreach ( keys %requested_values_hash ) {
	#		print "$_ -> $requested_values_hash{$_}\n";
	#	}

	foreach ( keys %requested_values_hash ) {
		push( @requested_values, $_ ) if $requested_values_hash{$_};
	}
	return @requested_values;
}

sub delete_multiple_entries {
	my %all;
	$all{$_} = 0 for @_;
	return ( keys %all );
}
1;
