#!/bin/perl 
#+
# nspeclog -- generate logsheet data from image headers
#
# Purpose: 
#       Given a list of images, parse the image headers and print
#       out a formatted list of data.  With no arguments, parse all of
#       the images in the current directory.
#
# Usage:
#       nspeclog [n | images]
# 
# Arguments:
#       n       = number of latest images to parse.
#       images  = names of images to parse.  If no images are listed,
#                       then all images in the current directory will be read
#                       (EXCEPT for backup.fits).
# 
# Output:
#       to STDOUT
# 
# Restrictions
#       None
# 
# Note:
#       The output from this program is extra wide.     To print it, use
#       this command:
#               enscript -1r -c -f Courier9 -P lw4s
# 
# Exit values:
#        0 = normal completion
#        1 = wrong number of arguments
#
# Example:
#       1) Generate log data for all images in the current directory:
#               nspeclog
# 
#       2) Generate log data for the last 5 images in the current directory:
#               nspeclog 5
# 
#       3) Generate log data for all images in the directory
#               /s/sdata602/nirspec/2003jan01/spec: 
#               nspeclog /s/sdata602/nirspec/2003jan01/spec/*.fits
#-
# Modification history:
#       2002-Dec-30     GDW     Original version (deeplog)
#       2003-Aug-27     GDW     Changed TTIME to EXPTIME
#       2004-Oct-10     GDW     Added "last n images" option
#	2004-Nov-09	jlyke	Adapted for NIRSPEC use (nspeclog)
#       2019-Jan-16     jlyke   NIRSPEC Upgrade
#       2021-Dec-01     jlyke   Updated format to GDopp version, handle KOA
#-----------------------------------------------------------------------

use Getopt::Std;
use File::Basename;

# get command-line options...
getopts("h");

# define the name of this program...
my( $base) = basename($0);

# define usage
my( $usage) = <<END
Usage:
       $base [-h] [n | images]

Arguments:
       n       = number of latest images to parse.
       images  = names of images to parse.  If no images are listed,
                       then all images in the current directory will be read
                       (EXCEPT for backup.fits).
 
Example:
       1) Generate log data for all images in the current directory:
               nspeclog
 
       2) Generate log data for the last 5 images in the current directory:
               nspeclog 5
 
       3) Generate log data for all images in the directory
               /s/sdata602/nirspec/2003jan01/spec: 
               nspeclog /s/sdata602/nirspec/2003jan01/spec/*.fits
END
    ;

# check for help flag...
die "$usage" if( $opt_h);

# declarations...
$| = 1;
my( $observers, $date, $directory);
my( @images);
my( $i, $j);
my( $m, $n, $title_format);
my( %HeadValue);
my( $null) = 'INDEF';

# define logsheet fields...
&AddColumn( 'DATAFILE',  'Filename', '%-17s ');
&AddColumn( 'TARGNAME', 'Target', '%-17s ');
&AddColumn( 'OBJECT', 'Object', '%-17s ');
&AddColumn( 'UT', 'UTC', '%-12s ');
&AddColumn( 'AIRMASS',  '  AM', '%6.2f ');
&AddColumn( 'TRUITIME',  'Itime', '%7.2f ');
&AddColumn( 'COADDS', 'Coadds', '%6s ');
&AddColumn( 'SAMPMODE', 'Reads', '%6s ');
&AddColumn( 'SLITNAME', 'Slit', '%-11s ');
&AddColumn( 'SCIFILT1',    'Filter1', '%-10s ');
&AddColumn( 'SCIFILT2',    'Filter2', '%-10s ');
&AddColumn( 'ECHLPOS', 'Echelle', '%7.2f ');
&AddColumn( 'DISPPOS',    'X-disp', '%7.2f ');
&AddColumn( 'SLITPA',  'Slit PA', '%8.2f ');
&AddColumn( 'COMMENT',   'Comments', '%-30s ');

# check args...
die "Usage: $0 [images]\n" if ( @ARGV < 0 );

# build default image list...
@images = glob("*.fits*");

# expunge bad filenames from image list.
# NOTE: that we traverse the list BACKWARDS because we are removing elements from the list as we go...
for ( $i=@images-1 ; $i>=0 ; $i--){
  if( ($images[$i] =~ m/backup.fits/) or        # remove backup files
      ($images[$i] =~ m/^cam/) or               # remove cam files
      not ($images[$i] =~ m/.fits$/ or $images[$i] =~ m/.fits.gz$/)){
    splice( @images, $i, 1) 
    }
}

# parse args...
if ( @ARGV == 1 and $ARGV[0] =~ /^\d+$/ ) {
  $m = $ARGV[0];
  if ( $m > @images ) { $m = scalar(@images) };
  $m = -$m;
  @images = @images[ $m .. -1 ];        # extract last $m elements of image list
} elsif ( @ARGV > 0 ) {
  @images = @ARGV;
}

# verify number of images...
die "ERROR: No images found\n" 
  unless @images;

# get some information from the first NIRSPEC image...
#for( $i=0 ; $i < @images ; $i++){
#  %HeadValue = &GetFitsHead( $images[$i]);
#  last if (defined($HeadValue{'CURRINST'})
#           and $HeadValue{'CURRINST'} =~ m/^NIRSP/)
#}
#die "ERROR: No NIRSPEC images found\n" 
#  unless $HeadValue{'CURRINST'} =~ m/^NIRSP/;

# print header...
printf "\n";

printf "%-72s", "Project:";
printf "UT Date: %s", &BlankIfUndef($HeadValue{"DATE-OBS"});
printf "\n";

printf "%-72s", "Observers: " . &BlankIfUndef($HeadValue{"OBSERVER"});
printf "Weather:";
printf "\n";

printf "%-72s", "Data directory: " . &BlankIfUndef($HeadValue{"OUTDIR"});
printf "Seeing:";
printf "\n";

printf "\n";

$line = '';
for ( $i=0 ; $i<@X::keyword ; $i++ ) {
  $X::format[$i] =~ m/^%-?(\d+)/;
  $n = $1;
  $X::format[$i] =~ m/(\s+)$/;
  $space = $1;
  $buf = sprintf "%-${n}s$space", $X::title[$i];
  print $buf;
  $buf =~ s/./_/g;
  $line .= $buf;
}
printf "\n";
printf "$line\n";

# set initial values of object and telescope offsets
$Object = "";
$Filter = "";
$RaBase = &Ra2Num($HeadValue{'RABASE'});
$DecBase = &Dec2Num($HeadValue{'DECBASE'});

# loop over images...
for ( $j=0 ; $j<@images ; $j++ ) {

  # reset $Comment
  $Comment = "";

  # read image header...
  %HeadValue = &GetFitsHead( $images[$j]);

  # skip non-NIRSPEC images...
  #next unless (defined($HeadValue{'CURRINST'})
  #             and $HeadValue{'CURRINST'} =~ m/^NIRSP/);

  # build filename field...
#  $HeadValue{'FILENAME'} = (fileparse( $images[$j], '\..*'))[0];
  # build filename field...compare DATAFILE keyword w/ filename
  # remove ".fits" from DATAFILE header keyword
  $HeadValue{'DATAFILE'} =~ s/.fits//;
  # remove ".fits.gz" or ".fits" from filenames in list of images
  $filename = (fileparse( $images[$j], '\.fits*/'))[0];
  $filename =~ s/.fits.gz//;
  $filename =~ s/.fits//;
  if ( $HeadValue{'DATAFILE'} ne $filename){
# KOA filenames N[CS].YYYYMMDD.SSSSS
      $Comment = $Comment.$HeadValue{'DATAFILE'};
      $HeadValue{'DATAFILE'} = $filename;
  }
  # shorten the SAMPMODE and include NUMREADS
$sampmode = substr($HeadValue{'SAMPMODE'}, 0, 1) . "_" . $HeadValue{'NUMREADS'};
  $HeadValue{'SAMPMODE'} = $sampmode;
  
  # read some keywords to adapt the printed values
#  $NewObj = $HeadValue{'OBJECT'};
  $NewObj = $HeadValue{'TARGNAME'};
  $NewFilt = $HeadValue{'FILTER'};
  # If UTC is in header, collect some more dcs keywords
  if ( defined($HeadValue{'UT'}) ) {
      $Ra = &Ra2Num($HeadValue{'RA'});
      $Dec = &Dec2Num($HeadValue{'DEC'});
#print $Ra;
#print $Dec;
  }

  if ( $NewObj ne $Object ) {
      $Comment = $Comment." New Object";
  } elsif ( $NewFilt ne $Filter ) {
      $Comment = $Comment." New Filter";
  } else {
      $RaDith = 3600.*($Ra - $RaBase)*cos($Pi/180.*$Dec);
      $DecDith = 3600.*($Dec - $DecBase);
#         print "$RaDith, $DecDith\n";
      if ( ($RaDith != 0.) || ($DecDith != 0.) ) {
         $DitherSize = 3600.*sqrt(($RaDith*cos($Pi/180.*$Dec))**2 + $DecDith**2);
#         print "$RaDith, $DecDith\n";
# Setup the comment
# Option 1 is raoff, decoff
          $Comment = $Comment . " en " . sprintf("%7.3f", $RaDith) . " " . sprintf("%7.3f",
$DecDith) . "\"";
# Option 2 is the combined dither size and time it took
#         $Comment = "Dith\: " . sprintf("%.2f", $TimeDiff) . "s " . sprintf("%.2f", $DitherSize) . "\"";
#         $Comment = "Dither\: " . sprintf("%.2f", $TimeDiff);
      } else {
# When no dither option 1 states "No Dither"
          $Comment = $Comment;
# Option 2 also prints the time between images
#         $Comment = "No Dith\: " . sprintf("%.2f", $TimeDiff) . "s ";
      }
  }

  # set the comment field
if ( $printcomment == 0 ) {
  $HeadValue{$X::keyword[$#keyword]} = $Comment;
} 

  # reset variables for next time thru
  $Object = $NewObj;
  $Filter = $NewFilt;
  $RaBase = $Ra;
  $DecBase = $Dec;

  # print results...
  for ( $i=0 ; $i<@X::keyword ; $i++ ) {
    if ( defined $HeadValue{$X::keyword[$i]} and 
         $HeadValue{$X::keyword[$i]} ne $null ){
      $HeadValue{$X::keyword[$i]} =~ s/^\s+//; # remove leading whitespace
      $HeadValue{$X::keyword[$i]} =~ s/\s+$//; # remove trailing whitespace
      printf $X::format[$i], $HeadValue{$X::keyword[$i]}
    } else {
      $X::format[$i] =~ m/^%-?(\d+)/;
      $n = $1;
      if ( not defined $HeadValue{$X::keyword[$i]}){
        $buf = '?'
      } else {
        $buf = ''
      }
      printf "%-${n}s ", $buf;
    }
  }
  printf "\n";
}


#-----------------------------------------------------------------------
sub GetFitsHead {
#-----------------------------------------------------------------------
  
  # get file name to read header from
  my $infile = shift;
  my( $excess, $comment);
  
  # reset output hashes to null
  my %HeadValue = ();
  my %HeadComment = ();
  
  # open the file with a shared lock to prevent it being modified 
  # during reading...
  open TEST, "<$infile";
  flock TEST, 1;
  
  # open input file using appropriate method...
  if ($infile =~ /\.gz$/i) {
    open INFITS, "gunzip --stdout $infile |";
    $| = 1;     # enable autoflush on gunzip output
  } else {
    open INFITS, $infile;
  }  

  # initialize input variable to allow for end-of-header trapping
  $headline = "     ";
  
  # loop thru FITS file 80 bytes at a time until end-of-header mark found
  until (substr($headline,0,4) eq "END ") {
    read INFITS, $headline, 80
      or die "unexpected end of file on read";
    chomp($headline);

    # identify keyword name from first 8 bytes
    $name = substr($headline, 0, 8);
    $rest = substr($headline,9);

    # strip extra spaces off of keyword names
    $name =~ s/\s//g;

    # test if value is a string, handle single quotes if it is
    if (substr($rest,0,2) eq " '") {
      $lastquote = index($rest, "'", 2);
      $value = substr($rest,2,$lastquote-2);
      ($excess, $comment) = split (/\//, substr($rest, $lastquote));

    } else {
      # otherwise split value from comment using / separator
      ($value, $comment) = split (/\//, $rest);
      $value =~ s/\s//g;
    }

    # add next value and comment to respective hashes
    unless ($name eq "END" or $name eq "") {
      $HeadValue{$name} = $value;
      $HeadComment{$name} = $comment;
    }
  }
  
  # close input FITS file
  close INFITS;
  close TEST;
  return %HeadValue;
}

#-----------------------------------------------------------------------
sub AddColumn {
#-----------------------------------------------------------------------
  my( $keyword, $title, $format) = @_;
  push( @X::keyword, $keyword);
  push( @X::title, $title);
  push( @X::format, $format);
}

#-----------------------------------------------------------------------
sub Ra2Num {
#-----------------------------------------------------------------------
  my( $coord) = @_;
  my( @t) = split /:/, $coord, 3; 
  my( $ra) = 15.*($t[0] + $t[1]/60. + $t[2]/3600.);
  return $ra; 
}
#-----------------------------------------------------------------------
sub Dec2Num {
#-----------------------------------------------------------------------
  my( $coord) = @_;
  my( @t) = split /:/, $coord, 3;
  my( $first) = substr($t[0],0,1);
  my( $sign) = 1.;
  if ( $first =~ m/-/ ) {
    $sign = -1.;
    $t[0] = $sign*$t[0];
  } 
  my( $dec) = ($t[0]/1. + $t[1]/60. + $t[2]/3600.) * $sign;
  return $dec; 
}
#-----------------------------------------------------------------------
sub BlankIfUndef {
#-----------------------------------------------------------------------
  my( $value) = @_;
  if( defined $value){
    return $value
  } else {
    return ''
  }
}