#!/usr/bin/perl

use strict;
use Math::Spline;
use Math::Round;
use Data::Dumper;
use List::Util qw(max min);
use File::Basename;

use constant REQUARGS => 3;
use constant EEPSTART => 3;
use constant DATALEN  => 280;
use constant RECLEN   => 4;   #record length
use constant ADDRHEX  => "\n%03x: %s";
use constant LENBYTE  => 1;
use constant LENWORD  => 2;
use constant HRFDATA  => "[0-9a-zA-Z]{3}: (.+)";
use constant IPOSTEP  => 500;
use constant IPOMIN   => -10000;
use constant IPOPTS   => 3;
use constant INVALU16 => 65535;
use constant INVALI16 => -1;
use constant TEMPTXT  => 'tmp.txt';
use constant IPOEEP   => 'ipo.eep';
use constant PLOTSCR  => 'plot.plg';
use constant PLOTPROC => 'gnuplot -e';
use constant PROGPROC => 'avrdude -v -c avrisp2 -P usb';
use constant CONTROL  => '-p t861';
use constant MEMSECT  => '-U eeprom';
use constant MEMACTRD => 'r';
use constant MEMACTWR => 'w';
use constant MEMFMT   => 'r';
use constant EEPCMDRD => PROGPROC . ' ' . CONTROL . ' ' . MEMSECT . ':' . MEMACTRD . ':[fp_to_resulting_data]:' . MEMFMT;
use constant EEPCMDWR => PROGPROC . ' ' . CONTROL . ' ' . MEMSECT . ':' . MEMACTWR . ':[fp_to_source_data]:' . MEMFMT;


if((@ARGV != REQUARGS) and ($ARGV[0] ne 'help')) {
  print "\nrequire @{[REQUARGS]} arguments:";
  PrintArgs();
  print "\n-> type \"help\" for more\n\n";
  exit 1;
}
my $cmd = $ARGV[0];
my $fpSrc = $ARGV[1];
my $fpDst = $ARGV[2];
if($ARGV[0] eq 'b2h') {
  Bin2Hrf($fpSrc, $fpDst);
}
elsif($ARGV[0] eq 'h2b') {
  Hrf2Bin($fpSrc, $fpDst);
}
elsif($ARGV[0] eq 'mrg') {
  Merge($fpSrc, $fpDst, 0, EEPSTART, DATALEN);
}
elsif($ARGV[0] eq 'ipo') {
  Interpolate($fpSrc, $fpDst);
}
elsif($ARGV[0] eq 'gpd') {
  GenPlotData($fpSrc, $fpDst);
}
elsif($ARGV[0] eq 'plt') {
  Plot($fpSrc, $fpDst);
}
elsif($ARGV[0] eq 'isq') {
  InterSeq($fpSrc, $fpDst);
}
elsif($ARGV[0] eq 'help') {
  PrintHelp();
}
else {
  print "\ncommand \"$cmd\" not found\n\nuse:";
  PrintCmdUse();
}
#command prompt to new line
print "\n\n";


#merge a source binary file into destination binary file
sub Merge {
  #params:  file path to bin source
  #         file path to bin destination
  #         source start address
  #         destination start address
  #         number of bytes to merge
  my ($fpSrc, $fpDst, $addrSrc, $addrDst, $len) = @_;
  #open source
  open SRC, "<", $fpSrc or die $! . " $fpSrc";
  binmode SRC;
  #open destination to edit
  open DST, "+<", $fpDst or die $! . " $fpDst"; 
  binmode DST;
  #position source start
  seek SRC, $addrSrc, 0;
  #position destination start
  seek DST, $addrDst, 0;
  #do merge
  read SRC, my $dat, $len + 1;
  print DST $dat;
  # for(my $i = 0; $i < $len - 1; ++$i) {
    # read SRC, my $dat, 1;
    # print DST $dat;
  # }
  #free resources
  close SRC;
  close DST;
  return 0;
}

#convert a human readable file into binary format
sub Hrf2Bin{
  #params:  file path to hrf input file
  #         file path to binary output file
  my ($fpTxt, $fpEep) = @_;
  #open hrf file for reading
  open TXT, "<", $fpTxt or die $! . " $fpTxt";
  #capture whole file content
  my $txt = join '', <TXT>;
  close TXT;
  #open binary file for writing
  open EEP, ">", $fpEep or die $! . " $fpEep"; 
  binmode EEP;
  my $addr = 0;
  my $tmp = '';
  print "\n\nhrf to binary conversion:";
  foreach(split /\n/, $txt) {
    my @hd = $_ =~ m/@{[HRFDATA]}/gi;
    #bloody white space handling
    $hd[0] =~ s/^s+//gi;
    $hd[0] =~ s/s+$//gi;
    if($hd[0] eq '') { next; }
    #zero address?
    if($addr == 0) {
      #unsigned char (number of records in EEP)
      $tmp = 'C';
    }
    else {
      #odd address?
      if(($addr & 0x01) == 0x01) {
        #signed short (outer temperature)
        $tmp = 's';
      }
      #even address?
      else {
        #unsigned short (forerun temperature)
        $tmp = 'S';
      }
    }
    print EEP pack($tmp, $hd[0]);
    print "\n$hd[0]";
    ++$addr;
  }
  close EEP;
   print "\n\n";
}

#convert binary data file (typically from eep) to human readable format
#resolved data is furthermore stored in the referenced arrays, if valid
sub Bin2Hrf {
  #params:  file path of binary input file
  #         file path of hrf output file
  #         reference to array of outer temperature values
  #         reference to array of forerun temperature values
  my ($fpEep, $fpTxt, $ot, $ft) = @_;
  #open eep file for binary reading
  open EEP, "<", $fpEep or die $! . " $fpEep";
  binmode EEP;
  seek EEP, EEPSTART, 0;
  #open target text file for writing
  open TXT, ">", $fpTxt, or die $! . " $fpTxt";
  my $addr = EEPSTART;
  my $btRd = 0;
  #read number of stored records
  $btRd = read EEP, my $dat, LENBYTE;
  #keep the effective data length
  my $datlen = unpack('C', $dat);
  printf TXT ADDRHEX, $addr, $datlen;
  $addr += $btRd;
  for(my $i=0; $i < (DATALEN / RECLEN); ++$i) {
    #read one record (outer temp & forerun temp)
    $btRd = read EEP, $dat, LENWORD;
    my $val = unpack('s', $dat);
    $val = (($i >= $datlen) && ($val == 0)) ? INVALI16 : $val;
    #write data to txt file (if '0' and record index > effective data length, then convert to erased '0xff)
    printf TXT ADDRHEX, $addr, $val;
    #put outer temp into proper array
    push $ot, $val if defined $ot;
    $addr += $btRd;
    $btRd = read EEP, my $dat, LENWORD;
    $val =  unpack('S', $dat);
     $val = (($i >= $datlen) && ($val == 0)) ? INVALU16 : $val;
    printf TXT ADDRHEX, $addr, $val;
    #put forerun temp into proper array
    push $ft, $val if defined $ft;
    $addr += $btRd;
  }
  close EEP;
  close TXT;
}

#read and interpolate data from eep
#write data to file (before and after interpolation)
sub Interpolate {
  #params:  file path of binary input file
  #         file path to plotable interpolated data output file
  my ($bin, $fpipo) = @_;
  #arrays: outer temperature ot (x), forerun temperature ft (y)
  my (@ot, @ft) = (), ();
  #fetch eep data in hrf 
  Bin2Hrf($bin, $fpipo, \@ot, \@ft);
  CleanRecords(\@ot, \@ft);
  #make a test print of the array contents
  print "\n\nbefore interpolation:\nouter (" . @ot . "): @ot";
  print "\n\nforerun (" . @ft . "): @ft";
  my $sp = Math::Spline->new(\@ot, \@ft);
  # my $ot = 1750;
  # my $ift = round($sp->evaluate($ot));
  # print "\n\ninterpolation:\nforerun temp @ outer temp $ot: $ift";
  #find extrema of ot
  my ($otMax, $otMin) = (max(@ot), min(@ot));
  print "\not extrema:\nmin: $otMin, max: $otMax";
  my $rot = [];
  my $rft = [];
  my $ret = {'ot' => $rot, 'ft' => $rft};
  #interpolate and generate plot data
  if($otMin <= IPOMIN and @ot >= IPOPTS) {
    open TXT, ">", $fpipo or die $! . " $fpipo";
    for(my ($i, $ot) = (0, IPOMIN); $i < (DATALEN / RECLEN); ++$i, $ot += IPOSTEP) {
      push $rot, $ot;
      push $rft, round($sp->evaluate($ot));
      my $oldOt = $i <= $#ot ? $ot[$i] : 0;
      my $oldFt = $i <= $#ft ? $ft[$i] : 0;
      printf TXT "\n%06d %06d %06d %06d", $oldOt, $oldFt, $ot, $rft->[$i];
    }
  }
  else {
    print "\n\nerror: minimum of @{[IPOMIN]} not fullfilled\nor number of interpolation points less than @{[IPOPTS]}";
    return -1;
  }
  #make a test print of the resulting array contents
  print "\n\nafter interpolation:\nouter (" . @{$rot} . "): @{$rot}";
  print "\n\nforerun (" . @{$rft} . "): @{$rft}";
  #generate hrf file as a precondition to form binary eep data
  #fileparse returns ("file name", "path including ending slash", "ext")
  my @fpres = fileparse($fpipo);
  my $tmpHrf = $fpres[1] . TEMPTXT;
  if(GenHrf($tmpHrf, $rot, $rft) == 0) {
    Hrf2Bin($tmpHrf, $fpres[1] . IPOEEP);
  }
  return 0;
}

#generate a hrf file from data arrays (outer temp, forerun temp)
#both arrays must be of the same size
sub GenHrf {
  #params:  file path to newly hrf being created
  #         reference to array of outer temperature values
  #         reference to array of forerun temperature values
  my ($hfp, $ot, $ft) = @_;
  my $len = @{$ot};
  if((not defined $ot) or (not defined $ft) or ($len != @{$ft})) {
    print "\n\nhrf generation: data length mismatch";
    return -1;
  }
  my $addr = 3;
  #open hrf for writing
  open TXT, ">", $hfp, or die $! . " $hfp";
  printf TXT ADDRHEX, $addr, $len;
  ++$addr;
  for(my $i = 0; $i < $len; ++$i) {
    printf TXT ADDRHEX, $addr, $ot->[$i];
    $addr += 2;
    printf TXT ADDRHEX, $addr, $ft->[$i];
    $addr += 2;
  }
  close TXT;
  return 0;
}

#cleans up data records (data in the two given arrays)
sub CleanRecords {
  #params:  array of outer temperature values
  #         array of forerun temperature values
  my ($ot, $ft) = @_;
  #remove invalid values
  @{$ot} = grep { $_ != INVALI16 } @{$ot};
  @{$ft} = grep { $_ != INVALU16 } @{$ft};
  #sort ascending
  my $swp = 1;
  while($swp) {
    $swp = 0;
    for(my $i=0; $i < @{$ot} - 1; ++$i) {
      if($ot->[$i] > $ot->[$i+1]) {
        #swap values in both! arrays
        my $tmp = $ot->[$i];
        $ot->[$i] = $ot->[$i+1];
        $ot->[$i+1] = $tmp;
        $tmp = $ft->[$i];
        $ft->[$i] = $ft->[$i+1];
        $ft->[$i+1] = $tmp;
        $swp = 1;
      }
    }
  }
}

#generate plot data from binary eep data
sub GenPlotData {
  #params:  file path to binary eep data
  #         file path to resulting txt plot data
  my ($fpEep, $fpPlot) = @_;
  my ($ot, $ft) = ([], []);
  Bin2Hrf($fpEep, $fpPlot, $ot, $ft);
  CleanRecords($ot, $ft);
  open TXT, ">", $fpPlot or die $! . " $fpPlot"; 
  for(my $i = 0; $i < @{$ot}; ++$i) {
    printf TXT "\n%06d %06d", $ot->[$i], $ft->[$i];
  }
  close TXT;
  return 0;
}

#plot a series of data using gnuplot
sub Plot {
  #params:  file path of data file 1 to plot
  #         file path to plot script
  my ($fpPlot, $fpPs) = @_;
  my $st = system("@{[PLOTPROC]} \"filename=\'$fpPlot\'\" $fpPs");
  print "\n\nplot result: $? -> $!";
  return $st;
}

#read or write data to controler's eep
sub AccessEep{
  #params:  file path to binary data fetched from eep
  #         command to specify access (read 'r' or write 'w')
  my ($fpeep, $cmd) = @_;
  my $progcmd = '';
  if($cmd eq 'r') {
    $progcmd = EEPCMDRD;
  }
  elsif($cmd eq 'w') {
    $progcmd = EEPCMDWR;
  }
  $progcmd =~ s/\[.+?\]/$fpeep/;
  print "\n\neep access: $progcmd\n";
  if((my $st = system($progcmd)) != 0) {
    print "\neep access: status = $st\n";
    return $st;
  }
  return 0;
}

#experimental:
#fetches original eep data via avrdude from controller
#interpolates binary eep data
#draws the graphs before and after interpolation using gnuplot
#writes interpolated eep data back to controller
sub InterSeq {
  #params:  file path to binary data fetched from eep
  #         file path to plotable text file
  my($fpeep, $fpplot) = @_;
  my $ui = '';
  my $st = unlink $fpeep, $fpplot;
  print "\n\nsequencial deleting: $st file(s)";
  if(($st = AccessEep($fpeep, 'r')) != 0) {
    print "\n\nsequencial, read eep: status = $st\n";
    return $st;
  }
  if(($st = GenPlotData($fpeep, $fpplot)) != 0) {
    print "\n\nsequencial generate plot: status = $st\n";
    return $st;
  }
  if(($st = Plot($fpplot, PLOTSCR)) != 0) {
    print "\n\nsequencial plot: status = $st\n";
    return $st;
  }
  print "\n\ncontinue with interpolation? (y|n): ";
  if(($ui = <STDIN>) ne "y\n") {
    print "\n\nsequencial interpolate: aborting ...";
    return $st;
  }
  if(($st = Interpolate($fpeep, $fpplot)) != 0) {
    print "\n\nsequencial interpolate: status = $st\n";
    return $st;
  }
  if(($st = Plot($fpplot, PLOTSCR)) != 0) {
    print "\n\nsequencial plot: status = $st\n";
    return $st;
  }
  print "\n\ndo you still like to continue with binary processing? (y|n): ";
  if(($ui = <STDIN>) ne "y\n") {
    print "\n\nsequencial binary processing: aborting ...";
    return $st;
  }
  if(($st = Merge(IPOEEP, $fpeep, 0, EEPSTART, DATALEN)) != 0) {
    print "\n\nsequencial, merge interpolated data: status = $st\n";
    return $st;
  }
  if(($st = AccessEep($fpeep, 'w')) != 0) {
    print "\n\nsequencial, write eep: status = $st\n";
    return $st;
  }
  print "\n\nsequence done, thanks!";
}

#print program argument usage
sub PrintArgs{
  print "\narguments:\n1. command\n2. source file path\n3. destination file path"
}

#print command usage
sub PrintCmdUse {
  print "\nsupported commands:\n\"b2h\" to convert a binary into hrf\n\"h2b\" to convert a hrf into binary\n\"mrg\" to merge a sorce bin into a destination bin\n\"ipo\" to interpolate data from eep\n\"gpd\" to generate plot data from eep\n\"plt\" to execute a plot using given txt data\n\"isq\" to execute a sequence of reading eep, interpolate, plot and writing eep back\n  \"help\" to print help";
}

#print help text
sub PrintHelp {
  print "\neeprom patcher (C) by woodz 01.04.2014\n";
  PrintArgs();
  print "\n";
  PrintCmdUse();
}




























