#!/usr/bin/env perl

use strict;
use warnings;
use feature qw/state say/;
use 5.010;

use Getopt::Declare;
use Finnigan;

my $args = new Getopt::Declare q{
  [strict]
  [mutex: -h -w]
  [mutex: -a -r]
  [mutex: -header -n]
  -header			extract the log header (record structure)
  -d[ump]			dump the requested feature with file seek addresses
  -a[ll]			detailed dump of all field descriptors [requires: -d]
  -s[ize]			print record size [requires: -d]
  -n[unmber] <n:0+n>		extract the log entry number <n>
  -h[tml]			format as html
  -w[iki]			format as a wiki table
  -r[elative]			show relative addersess in the dump [requires: -d]
  <file>			input file [required]
}
  or exit(-1);

my $file = $args->{"<file>"};
-e $file or die "file '$file' does not exist";
-f $file or die "'$file' is not a plain file";
-s $file or die "'$file' has zero size";

# -----------------------------------------------------------------------------
open INPUT, "<$file" or die "can't open '$file': $!";
binmode INPUT;

my $file_header = Finnigan::FileHeader->decode(\*INPUT);
my $seq_row = Finnigan::SeqRow->decode(\*INPUT, $file_header->version);
my $cas_info = Finnigan::CASInfo->decode(\*INPUT);
my $rfi = Finnigan::RawFileInfo->decode(\*INPUT, $file_header->version);

my $run_header_addr = $rfi->preamble->run_header_addr;

# fast-forward to RunHeader
seek INPUT, $run_header_addr, 0;
my $run_header = Finnigan::RunHeader->decode(\*INPUT, $file_header->version);
my $inst_log_addr = $run_header->inst_log_addr;
my $inst_log_length = $run_header->sample_info->inst_log_length;

# can't FF; have to read the istrument IDs to reach the InstrumentLog
my $inst_id  = Finnigan::InstID->decode( \*INPUT );

# now at the start of InstrumentLog
my $header = Finnigan::GenericDataHeader->decode(\*INPUT);

if ( $args->{'-header'} ) {
  if ( exists $args->{-d} ) {
    if ( exists $args->{-s} ) {
      my $size = $header->size;
      say "size: $size";
    }
    if ( exists $args->{-a}) {
      if ( exists $args->{-h} ) {
        $header->dump(style => 'html', filter => ['n']);
        foreach my $i (0 .. $header->n - 1) {
          $header->field($i)->dump(style => 'html', header => undef);
        }
      }
      elsif ( exists $args->{-w} ) {
        $header->dump(style => 'wiki', filter => ['n']);
        foreach my $i (0 .. $header->n - 1) {
          $header->field($i)->dump(style => 'wiki', header => undef);
        }
      }
      else {
        $header->dump(relative => exists $args->{-r}, filter => ['n']);
        foreach my $i (0 .. $header->n - 1) {
          $header->field($i)->dump(relative => exists $args->{-r});
        }
      }
    }
    else {
      if ( exists $args->{-h} ) {
        $header->dump(style => 'html', relative => exists $args->{-r});
      }
      elsif ( exists $args->{-w} ) {
        $header->dump(style => 'wiki', relative => exists $args->{-r});
      }
      else {
        $header->dump(relative => exists $args->{-r});
      }
    }
  }
  else {
    foreach my $i (0 .. $header->n - 1) {
      say $header->field($i)->type
        . "\t" . $header->field($i)->length
          . "\t" . $header->field($i)->label;
    }
  }
}
else {
  # do the records
  my $record;
  my @range = (0, $inst_log_length - 1);
  my @selected = @range;
  if ( exists $args->{-n}) {
    my $n = $args->{-n}{"<n>"};
    if ($n - 1 < $range[0] or $n - 1 > $range[1]) {
      say STDERR "Record number $n is out of range. The file $file has only $inst_log_length log records";
      exit -1;
    }
    @selected = ($n - 1) x 2;
  }
  foreach my $i ( $range[0] .. $range[1] ) {
    exit if $i > $selected[1];
    $record = Finnigan::InstrumentLogRecord->decode(\*INPUT, $header->ordered_field_templates);
    if ( $i >= $selected[0] and $i <= $selected[1] ) {
      my $j = 0;
      if ( exists $args->{-d} ) {
        if ( exists $args->{-s} ) {
          my $size = $record->size;
          say "size: $size";
        }
        if ( exists $args->{-h} ) {
          $record->dump(style => 'html', relative => exists $args->{-r});
        }
        elsif ( exists $args->{-w} ) {
          $record->dump(style => 'wiki', relative => exists $args->{-r});
        }
        else {
          $record->dump(relative => exists $args->{-r});
        }
      }
      else {
        if ( exists $args->{-w} ) {
          say "|| seq || retention time || label || value ||";
          foreach my $key ( sort {(split /\|/, $a)[0] <=> (split /\|/, $b)[0]} keys %{$record->{data}}) {
            my ($stripped_key) = ($key =~ /^\d+\|(.*)$/);
	    $stripped_key ||= '';
            say "|| " . ($i + 1)
              . " || " . $stripped_key
                . " || " . $record->{data}->{$key}->{value}
                  . " || ";
            $j++;
          }
        }
        if ( exists $args->{-h} ) {
          say STDERR "sorry, HTML formatting only works in dump mode";
	  exit -1;
        }
        else {
          foreach my $key ( sort {(split /\|/, $a)[0] <=> (split /\|/, $b)[0]} keys %{$record->{data}}) {
            my ($stripped_key) = ($key =~ /^\d+\|(.*)$/);
            say $i + 1
              . "\t" . ($stripped_key || '')
                . "\t" . $record->{data}->{$key}->{value};
            $j++;
          }
        }
      } # if / if not -d
    } # if in range
  } # for each record
}

__END__
=head1 NAME

uf-log - list or dump the instrument log entries in a Finnigan raw file

=head1 SYNOPSIS

uf-log [options] file

 Options:

  -header              extract the log header (record structure)
  -d[ump]              dump the requested feature showing file seek addresses
  -a[ll]               detailed dump of all field descriptors [requires: -d]
  -s[ize]              print record size [requires: -d]
  -n[unmber] <n:0+n>   extract the log entry number <n>
  -h[tml]              format as html
  -w[iki]              format as a wiki table
  -r[elative]          show relative addersess in the dump [requires: -d]
  <file>               input file [required]

=head1 OPTIONS

=over 4

=item B<-help>

Print a brief help message and exit.

=item B<-d[ump]>

Prints a table listing all fields in the requested object (a log entry
or the file header), with their seek addresses, sizes, names and
values. Individual entries can be selected with the B<-n[umber]>
option.

=item B<-n[umber]>

Gives the number of a single InstrumentLogRecord to extract

=item B<-h[tml]>

Format the dump output as an html table. When multiple entries are
specified, each will be rendered in its own table

=item B<-w[iki]>

Format the dump output as a wiki table.

=item B<-s[ize]>

Show structure size in bytes (works with the -d[ump] option).

=item B<-r[elative]>

Show relative addresses of all itmes in the dump. The default is to
show the absolute seek address. (works with the -d[ump] option)

=item B<-a[ll]>

Dump all GenericDataDescriptor entries in the file header (requires B<-header>)

=back

=head1 DESCRIPTION

B<uf-log> can be used to examine the instrument log stream in a
Finnigan raw file. The instrument log records typically contain more
than a hundred parameters, including operational data on the pumps,
power supplies, ion optics and injectors -- everything that may be
useful in the auditing of the instrument's performance.

Each record is timestamped with the current retention time of the
sample.

=head1 SEE ALSO

Finnigan::InstrumentLogRecord

=head1 EXAMPLES

=over 4

=item List all log records in the tabular form: <record number, time, label, value>

  uf-log sample.raw 

=item Print the fifth log record:

  uf-log -n 5 sample.raw

=item Dump the fifth log record in wiki format with total size and relative addresses:

  uf-log -dswr -n 5 sample.raw

=item Print the contents of the file header in the tabular form: <type, length, label>

  uf-log -header sample.raw

=item Dump the header in the compact wiki format, with a stringified GenericDataDescriptor list:

  uf-log -header -dw sample.raw

=item Dump the header in the extended wiki format, showing the
location of echa GenericDataDescriptor's element)

  uf-log -header -daw sample.raw

=back

=cut