#!/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 tune 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 object size [requires: -d]
  -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);

# The following three structures must be skipped in order to reach the
# tune file header: ErroLog, scan event hierarchy, and ScanParameters header.

# Skip to the error log and read through
seek INPUT, $run_header->sample_info->error_log_addr, 0;
my $error_log_length = Finnigan::Decoder->read(\*INPUT, ['length' => ['V', 'UInt32']])->{data}->{length}->{value};
foreach my $i ( 0 .. $error_log_length - 1) {
  Finnigan::Error->decode(\*INPUT);
}
# Read through the scan event hierarchy
my $nsegs = Finnigan::Decoder->read(\*INPUT, ['nsegs' => ['V', 'UInt32']])->{data}->{nsegs}->{value};
foreach my $i ( 0 .. $nsegs - 1) {
  my $n = Finnigan::Decoder->read(\*INPUT, ['n' => ['V', 'UInt32']])->{data}->{n}->{value};
  foreach my $j ( 0 .. $n - 1) {
    Finnigan::ScanEventTemplate->decode(\*INPUT, $file_header->version);
  }
}
# Skip the ScanParameters header
Finnigan::GenericDataHeader->decode(\*INPUT);

# Now at the start of the Tune File header
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 Tune File itself
  my $record = Finnigan::GenericRecord->decode(\*INPUT, $header->ordered_field_templates);
  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 "|| label || value ||";
      foreach my $key ( sort {(split /\|/, $a)[0] <=> (split /\|/, $b)[0]} keys %{$record->{data}}) {
        my ($stripped_key) = ($key =~ /^\d+\|(.*)$/);
        $stripped_key ||= '';
        say "|| " . $stripped_key
          . " || " . $record->{data}->{$key}->{value}
            . " ||";
      }
    }
    else {
      foreach my $key ( sort {(split /\|/, $a)[0] <=> (split /\|/, $b)[0]} keys %{$record->{data}}) {
        my ($stripped_key) = ($key =~ /^\d+\|(.*)$/);
        $stripped_key ||= '';
        say "$stripped_key\t$record->{data}->{$key}->{value}";
      }
    }
  } # if / if not -d
}

__END__
=head1 NAME

uf-tune - list or dump the instrument settings and calibration data from a Finnigan raw file

=head1 SYNOPSIS

uf-tune [options] file

 Options:

  -header              extract the tune 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 object size [requires: -d]
  -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 tune file
or its header), with their seek addresses, sizes, names and
values. Individual entries can be selected with the B<-n[umber]>
option.

=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 elements 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-tune> can be used to examine the embedded tune file, either by
listing its entries (which were intended for human consumption), or by
dumping the details of its encoding.

=head1 SEE ALSO

Finnigan::ScanParameters
uf-params

=head1 EXAMPLES

uf-tune sample.raw 

  (lists all tune file entries in the tabular form: <label, value>)


uf-tune -dswr sample.raw
  (dumps the tune file contents in wiki format with total size and relative addresses)

uf-tune -header sample.raw

  (prints the contents of the tune file header in the tabular form: <type, length, label>)

uf-tune -header -dw sample.raw

  (dumps the header in the compact wiki format, with a stringified
  GenericDataDescriptor list)

uf-tune -header -daw sample.raw

  (dumps the header in the extended wiki format, showing the
  location of echa GenericDataDescriptor's element)


=cut
