#!/usr/bin/env perl

use XyceRegression::Tools;

$Tools = XyceRegression::Tools->new();
#$Tools->setDebug(1);
#$Tools->setVerbose(1);

# The input arguments to this script are: 
# $1 = location of Xyce binary
# $2 = location of xyce_verify.pl script
# $3 = location of compare script 
# $4 = location of circuit file to test
# $5 = location of gold standard prn file

# If Xyce does not produce a prn file, then we return exit code 10.
# If Xyce succeeds, but the test fails, then we return exit code 2.
# If the shell script fails for some reason, then we return exit code 1.
# Otherwise we return the exit code of compare or xyce_verify.pl

# Since the shell script runs Xyce and the comparison program, it is
# responsible for capturing any error output from Xyce and the STDOUT & STDERR
# from the comparison program.  The outside script, run_xyce_regression,
# expects the STDERR output from Xyce to go into $CIRFILE.err, the STDOUT
# output from comparison to go into $CIRFILE.prn.out and the STDERR output from
# comparison to go into $CIRFILE.prn.err.  

use Getopt::Long;

&GetOptions( "verbose!" => \$verbose );
$XYCE=$ARGV[0];
$XYCE_VERIFY=$ARGV[1];
#$XYCE_COMPARE=$ARGV[2];
$CIRFILE=$ARGV[3];
$GOLDPRN=$ARGV[4];

# these are the tolerances used in comparing the measures calculated by this 
# script to those calculated by Xyce.  Zerotol is needed because some measured
# values should be zero (and reltol won't make sense in that case).

my $absTol = 1.0e-4;
my $relTol = 0.02;
my $zeroTol = 1.0e-5;

if (defined($verbose)) { $Tools->setVerbose(1); }

sub verbosePrint { $Tools->verbosePrint(@_); }

$retval = -1;
$retval=$Tools->wrapXyce($XYCE,$CIRFILE);
if ($retval != 0) { print "Exit code = $retval\n"; exit $retval; }
if (not -s "$CIRFILE.prn" ) { print "Exit code = 14\n"; exit 14; }

#
# Did we make a measure file
#
if (not -s "$CIRFILE.mt0" ) { print "Exit code = 17\n"; exit 17; }

#
# Now we need to count the number of .MEASURE statemets in the netlist
# and make sure we have some output for each one in the output file
#
open(NETLIST, "$CIRFILE");
$numMeasures=0;
# these are used to store parameters which can limit or windows the scope of a measure
my @OnVals = (0.0, 0.0);
my @OffVals = (0.0, 0.0);
my @FromVals = (0.0, 0.0);
my @ToVals = (0.0, 0.0);
my @minVals = (0.0, 0.0);
my @riseVals = (0, 0);
my @crossVals = (0, 0);
my @findVals = (0.0, 0.0);
while( $line=<NETLIST> )
{
  if( $line =~ /^\.measure/i)
  {  
    $numMeasures++;
    # try to parse out ON= and OFF= values 
    # caution! must match case in netlist
    @words = split( /\s+/, $line);
    foreach $word (@words)
    {
      if( $word =~ "=" )
      {
        ($keyword, $value) = split( /=/, $word);
        # caution! need to match case in the netlist
        if( $keyword eq "on" )
        {
          @OnVals[$numMeasures-1] = $value;
        }
        elsif( $keyword eq "off" )
        {
          @OffVals[$numMeasures-1] = $value;
        }
        elsif( $keyword eq "from" )
        {
          @FromVals[$numMeasures-1] = $value;
        }
        elsif( $keyword eq "to" )
        {
          @ToVals[$numMeasures-1] = $value;
        }
        elsif( $keyword eq "minval" )
        {
          @minVals[$numMeasures-1] = $value;
        }
        elsif( $keyword eq "rise" )
        {
          @riseVals[$numMeasures-1] = $value;
        }
        elsif( $keyword eq "cross" )
        {
          @crossVals[$numMeasures-1] = $value;
        }
        else
        {
          # not great, but use to catch caes of 
          # when v(1)=some_value
          @findVals[$numMeasures-1] = $value;
        }
      }
    }
  }
}
close(NETLIST);

#
# Now look for the measure output file and count how many measurements are in it
#
open(RESULTS, "$CIRFILE.mt0");
$numMeasuresFound=0;
my @measureNames;
my @measureVals;
while( $line=<RESULTS> )
{
  if( $line =~ /=/)
  {  
    # parse out NAME = Value 
    chop $line;
    ($name,$sep,$value) = split(/ /, $line);
    print "name = $name value=$value\n";
    push @measureNames, $name;
    push @measureVals, $value;
    $numMeasuresFound++;
  }
}
close(RESULTS);

if( $numMeasures != $numMeasuresFound )
{
  verbosePrint "Numer of requested measures did not match number of printed measures.\n";
  print "Exit code = 2\n"; exit 2; 
}

# Now look at Xyce's output file and read in Xyce's data
open(XYCE_RESULTS, "$CIRFILE.prn");
my @calculatedMeasureVals;
my @headerVarNames;
my @dataFromXyce;
my $numPts = 0;
while( $line=<XYCE_RESULTS> )
{
  if( $line =~ /TIME/)
  {  
    # parse out Header names 
    chop $line;
    @headerVarNames = split(/\s+/, $line);
  }
  elsif($line =~ /End of/)
  {
    # end of Xyce simulation line 
    # do nothing in this case
  }
  else
  {
    # save var values 
    chop $line;
    # Remove leading spaces on line, otherwise the spaces become element 0 of "@lineOfDataFromXyce" instead of the first column of data.
    $line =~ s/^\s*//;
    @lineOfDataFromXyce = (split(/\s+/, $line));
    for ($varNum=0; $varNum < @lineOfDataFromXyce; $varNum++)
    {
      #print "setting dataFromXyce[$numPts][$varNum] = $lineOfDataFromXyce[$varNum] \n";
      $dataFromXyce[$numPts][$varNum] = $lineOfDataFromXyce[$varNum];
    }
    $numPts++;
  }
}
close(XYCE_RESULTS);


# now calculate the values for these measures.  This is the part of 
# the test script that will be different for each one. 
# 
my @calMeasure = (0.0, 0.0);
my @numberOfCrosses = (0, 0);

# For this measure we need to find the time when a given volage 
# equaled or exceeded a set value.  Note if "rise" is specified such 
# as "rise=2" when we want the second time when the signal reached 
# that value. if 'cross=n' then we find the n't time we cross that value.

for $i (1 .. $#dataFromXyce )
{  
  # find the time point Xyce output which 
  # is closest to the desired $x1Vals or $x2Vals
  for $j (0 .. 1)
  {
    #if ( (($dataFromXyce[$i-1][1] < $findVals[$j] ) && ($dataFromXyce[$i][1] > $findVals[$j] )) && ($numberOfRises[$j] == $riseVals[$j]) )
    if ( (($dataFromXyce[$i-1][2] < $findVals[$j] ) && ($dataFromXyce[$i][2] > $findVals[$j] )) ||
         (($dataFromXyce[$i-1][2] > $findVals[$j] ) && ($dataFromXyce[$i][2] < $findVals[$j] )) )
    {
      $numberOfCrosses[$j] += 1;
      if( $numberOfCrosses[$j] == $crossVals[$j] )
      {
        # interpolate between the bounding points. 
        $calMeasure[$j] = $dataFromXyce[$i][1] - ( ($dataFromXyce[$i][1] - $dataFromXyce[$i-1][1])/($dataFromXyce[$i][2] - $dataFromXyce[$i-1][2])) * ($dataFromXyce[$i][2] - $findVals[$j]);
        # before interpolation we just tool the right sided limit
        # $calMeasure[$j] = $dataFromXyce[$i-1][1];
      }
    }
  }
}

my @absMeasureError;
my @relMeasureError;

# calculate the measure
for $i (0 .. 1)
{
  #$calMeasure[$i] /= $dataFromXyce[$#dataFromXyce-1][0];
  $absMeasureError[$i] = abs( $measureVals[$i] - $calMeasure[$i] );
  if ( $calMeasure[$i] != 0 )
  {
    $relMeasureError[$i] = abs( $absMeasureError[$i] / $calMeasure[$i]);
  }
}

$retval = 0;
foreach $i (0 .. 1)
{
  print "For measure $measureNames[$i] calculated value = $calMeasure[$i]: abserror = $absMeasureError[$i] relerror=$relMeasureError[$i]\n";
  if( ($absMeasureError[$i] > $absTol) || (($measureVals[$i] > $zeroTol) && ($relMeasureError[$i] > $relTol) ))
  {
    print "Out of tolerance with abstol = $absTol and reltol = $relTol \n";
    $retval = 2;
  }
}


if ( $retval != 0 )
{
  verbosePrint "test Failed!\n";
  print "Exit code = $retval\n"; 
  exit $retval;
}
  
verbosePrint "test Passed!\n";
print "Exit code = $retval\n"; 
exit $retval;


