#
# This is the common code used in the .MEASURE regression testing 
# 

package MeasureCommon;
use strict;

#
# Used in printResults subroutine
#
sub verbosePrint 
{ 
  my $Tools = XyceRegression::Tools->new();
  $Tools->verbosePrint(@_); 
}

#
# Check that all of the needed files exist for a .MEASURE
# statement for a .TRAN analysis
#
sub checkTranFilesExist 
{
  my ($XYCE,$CIRFILE)=@_;

  use XyceRegression::Tools;
  my $ModTools = XyceRegression::Tools->new();

  # does the .cir file exist
  my $retval = -1;
  $retval=$ModTools->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; }
}

# 
# Determine number of measures in .cir file  This subroutine
# assume that the subroutine checkTranFilesExist() has already been run. 
#
sub getNumMeasuresInCirFile 
{
  my ($CIRFILE)=@_;
  my $numMeasures=0;
  my $line;
  my @words;
  my $token;
  my @tokenParts;
  my @measureQuants;
  my $mqIdx;

  open(NETLIST, "$CIRFILE");
  while( $line=<NETLIST> )
  {
    if( $line =~ /^\.measure/i)
    {  
      $numMeasures++;
      @words = split(/ /, $line);
      if ( $#words < 4 )
      {
        # all .measure statements have at least five tokens
	print "Measure statement $numMeasures in file $CIRFILE lacked enough tokens\n";
        print "Exit code = 2\n"; exit 2; 
      }
      elsif ( (uc($words[3]) eq "FIND") && ($#words < 6) ) 
      {
        # all .measure statements of the form FIND-WHEN have at least seven tokens
	print "Measure statement $numMeasures in file $CIRFILE lacked enough tokens\n";
        print "Exit code = 2\n"; exit 2; 
      }
      if ( uc($words[3]) eq "FIND" ) 
      {
        $mqIdx=6;
      }
      else
      {
        $mqIdx=4;
      } 
      if ( $#words == $mqIdx)
      {
        # need to remove newline if the .MEASURE statement only has five tokens
	$token = $words[$mqIdx];
        chop($token);
      } 
      else
      { 
	$token = $words[$mqIdx];
      }
      # if the MEASURE has a WHEN clause then column 4 will have the format
      # v(n)=value. So, we ignore everything starting with the '=' in order
      # to parse out what the corresponding column will be in the .prn file
      #@tokenParts = split('=',$token);
      @tokenParts = split /[=:]/,$token;
      $measureQuants[$numMeasures-1] = $tokenParts[0];
    }
  }
  close(NETLIST);

  if ( $numMeasures == 0 )
  {
    print "No measure statements found in file $CIRFILE\n";
    print "Exit code = 2\n"; exit 2; 
  }

  return ($numMeasures,\@measureQuants);
}

# 
# Determine the presence and output variable for a FIND clause
# in a FIND-WHEN measure. 
#
sub getFindQuants 
{
  my ($CIRFILE)=@_;
  my $numMeasures=0;
  my $line;
  my @words;
  my $token;
  my @tokenParts;
  my @findQuants;
  my @findGiven;

  open(NETLIST, "$CIRFILE");
  while( $line=<NETLIST> )
  {
    if( $line =~ /^\.measure/i)
    {  
      $numMeasures++;
      @words = split(/ /, $line);
      if ( uc($words[3]) eq "FIND" )  
      {
        $findGiven[$numMeasures-1] = 1;
        $findQuants[$numMeasures-1] = $words[4];
      }
      else
      {
        $findGiven[$numMeasures-1] = 0;
        $findQuants[$numMeasures-1] = "";
      }
    } 
  }
  close(NETLIST);

  return (\@findQuants,\@findGiven);
}

#
# Process the measure output file and count how many measurements
# are in it.  Also save the name and value calculated by Xyce.  This subroutine
# assume that the subroutine checkTranFilesExist() has already been run. 
#
sub parseMeasureNamesValues
{
  my ($CIRFILE,$numMeasures,$mtFileIdx,$numSteps)=@_;
  if (not defined $mtFileIdx) { $mtFileIdx = 0; }
  if (not defined $numSteps) { $numSteps = 1; }

  my $numMeasuresFound=0;
  my @measureNames;
  my @measureVals;
  my $name; 
  my $sep;
  my $value;
  my $line;
  my $retval=0;

  open(RESULTS, "$CIRFILE.mt$mtFileIdx");
  while( $line=<RESULTS> )
  {
    if( $line =~ /=/ )
    { 
      # This works for all measures but FOUR 
      # parse out NAME = Value 
      chop $line;
      ($name,$sep,$value) = split(/ /, $line);
      push @measureNames, $name;
      push @measureVals, $value;
      $numMeasuresFound++;
    }
    elsif ( $line =~ /:/ )
    {
      # this works to parse the name and value of a FAILED FOUR measure
      chop $line;
      ($name,$value) = split(/:/, $line);
      push @measureNames, $name;
      push @measureVals, $value;
      $numMeasuresFound++;
    }
  }
  close(RESULTS);

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

  #
  # Make sure the measurement was not output multiple times in parallel
  # in either the output file or the measure file.
  my $numMeasOut=0;
  my $failWarn=0;
  my $numMeasMT0=0;   
  for (my $i=0; $i < $numMeasures-1; $i++)
  {
    $numMeasOut = `grep -icw $measureNames[ $i ] $CIRFILE.out`;
    $failWarn = `grep -icw \"$measureNames[ $i ] failed\" $CIRFILE.out`;

    #
    # this is an artificial breakdown between numSteps=1 and numSteps>1 because
    # failed measures are output one fewer time in non Step analysis
    # and in parallel failed steps are output on each processor to the log file making
    # it more confusing trying to test the number of failed steps.
    if ( $numSteps > 1 )
    {
      if ( $numMeasOut != $numSteps && $failWarn < $numSteps )
      {
        print "Xyce printed out $measureNames[$i] measure to standard output more than expected: numMeasOut = $numMeasOut numSteps = $numSteps failWarn = $failWarn.\n";
        $retval = 2;
      }
    }
    else 
    {
      if ( $numMeasOut != 1 && $failWarn != ($numMeasOut-1) )
      {
        print "Xyce printed out $measureNames[$i] measure to standard output more than expected: numMeasOut = $numMeasOut numSteps = $numSteps failWarn = $failWarn.\n";
        $retval = 2;
      }
    }

    $numMeasMT0 = `grep -icw $measureNames[ $i ] $CIRFILE.mt0`;
    if ( $numMeasMT0 != 1 )
    {
      print "Xyce printed out $measureNames[ $i ] measure to file multiple times.\n";
      $retval = 2;
    }
  }

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

  return (\@measureNames,\@measureVals);
}

#
# Parses key words from .MEASURE statement in .cir file.  This subroutine
# assume that the subroutine checkTranFilesExist() has already been run. 
# This approach has the downside of parsing the .CIR file N times.  It is
# extensible to a variable number of keywords being tested in the .sh file.
# It has special handling for the TO keyword, as noted below.
#
sub parseKeyWord
{
  my ($CIRFILE,$keyWordName,$endTime)=@_;
   
  open(NETLIST, "$CIRFILE");

  my $numMeasures=0;
  my @keyWordVals;
  my @words;
  my $keyword;
  my $value;
  my $line;
  my $idx;
  my @keyWordFound;
  my @fracMaxFound;
  while( $line=<NETLIST> )
  {
    if( $line =~ /^\.measure/i)
    {  
      $numMeasures++;
      #set default value to zero.
      $keyWordFound[$numMeasures-1]=0;
      $fracMaxFound[$numMeasures-1]=0;
      
      # try to parse out values for the string contained in $keyWordName
      @words = split( /\s+/, $line);
      for $idx (0 .. $#words)
      {
        if( $words[$idx] =~ "=" )
        {
          ($keyword, $value) = split( /=/, $words[$idx]);
          if( uc($keyword) eq uc($keyWordName) )
          {
            $keyWordVals[$numMeasures-1] = $value;
            $keyWordFound[$numMeasures-1]=1;
          }
        }
        elsif ( uc($keyWordName) eq "WHEN" && uc($words[$idx] ) eq "WHEN" )
        {
          # handles syntax of WHEN v(n)=value
	  $idx++;
          ($keyword, $value) = split( /=/, $words[$idx]);
          $keyWordVals[$numMeasures-1] = $value;
          $keyWordFound[$numMeasures-1]=1;
        }
        elsif ( ( uc($keyWordName) eq "TRIG" && uc($words[$idx] ) eq "TRIG" ) ||
                ( uc($keyWordName) eq "TARG" && uc($words[$idx] ) eq "TARG" ) )
        { 
          $idx++;
          ($keyword, $value) = split( /=/, $words[$idx]);
          if ($value ne "")
          {
            # handles syntax of TRIG v(n)=value  
            $keyWordVals[$numMeasures-1] = $value;
            $keyWordFound[$numMeasures-1]=1;
          }
          else
          {
	    $idx++; # next word was a space, so skip it 
            ($keyword, $value) = split( /=/, $words[$idx]);         
            if ( uc($keyword) eq "FRAC_MAX" )
            {
              # handles syntax of TRIG frac_max=value  
              $keyWordVals[$numMeasures-1] = $value;
              $keyWordFound[$numMeasures-1] = 1;
              $fracMaxFound[$numMeasures-1] = 1;
            }
            else
            {
              # handles syntax of TRIG v(n) value
              $keyWordVals[$numMeasures-1] = $words[$idx];
              $keyWordFound[$numMeasures-1]=1;
            }
          }
        }
      }

      # handle missing key word values
      if ($keyWordFound[$numMeasures-1] < 1)
      {
        # if the TO value is missing (equal to 0) then it is set to the
        # last sim time in the .PRN file.  
        if (uc($keyWordName) eq "TO")
        {
	  $keyWordVals[$numMeasures-1] = $endTime;
        }
        elsif (uc($keyWordName) eq "DEFAULT_VAL")
        {
          $keyWordVals[$numMeasures-1] = -1;
        }
        elsif (uc($keyWordName) eq "RISE" || uc($keyWordName) eq "FALL" ||
               uc($keyWordName) eq "CROSS")
        {
          # use the first one, if RISE, FALL or CROSS does not have a value
          $keyWordVals[$numMeasures-1] = 1;
        }
        elsif (uc($keyWordName) eq "AT")
        {
          # AT is used for the DERIV measure.  A value lte 0 is illegal.
          # This will be checked later in the .sh file
          $keyWordVals[$numMeasures-1] = -1;
        }
        else 
        { # default is zero for most keywords
          $keyWordVals[$numMeasures-1] = 0;
        }
      }
      
      # handle LAST qualifier for RISE, FALL and CROSS
      if  ( ( uc($keyWordName) eq "RISE" || uc($keyWordName) eq "FALL" ||
           uc($keyWordName) eq "CROSS" ) && ( uc($keyWordVals[$numMeasures-1]) eq "LAST" ) )
      {
        $keyWordVals[$numMeasures-1] = -1;
      }
    }
  }
  close(NETLIST);

  # debugging output
  print "$keyWordName values= @keyWordVals\n";

  if (uc($keyWordName) eq "RISE" || uc($keyWordName) eq "FALL" || uc($keyWordName) eq "CROSS" ||
      uc($keyWordName) eq "AT" || uc($keyWordName) eq "WHEN" || uc($keyWordName) eq "PRECISION" ||
      uc($keyWordName) eq "MINVAL" || uc($keyWordName) eq "RFC_LEVEL")
  {
    return(\@keyWordVals,\@keyWordFound);
  }
  elsif (uc($keyWordName) eq "TRIG" || uc($keyWordName) eq "TARG")
  {
    # if frac_max was not specified then keyWordVals is the absolute trigger level
    # if frac_max was specified then keyWordVals is the frac_max level
    return(\@keyWordVals,\@fracMaxFound);
  }
  else
  {
    return (@keyWordVals);
  }
}

#
# Parses key RISE/FALL/CROSS values from TRIG and TARG clauses in .MEASURE statements in 
# .cir file.  This subroutine assume that the subroutine checkTranFilesExist() has already
#  been run. This approach has the downside of parsing the .CIR file multiple 3 times for
#  RISE, FALL and CROSS.  It is extensible to the TD keyword also though. 
#
sub parseRFCforTrigTarg
{
  my ($CIRFILE,$keyWordName)=@_;
   
  open(NETLIST, "$CIRFILE");

  my $numMeasures=0;
  my $keyword;
  my @words;
  my $value;
  my $line;
  my $idx;
  my @trigRfcVals;
  my @trigRfcFound;
  my @targRfcVals;
  my @targRfcFound;
  my $inTrigBlock=0;
  my $inTargBlock=0;

  while( $line=<NETLIST> )
  {
    if( $line =~ /^\.measure/i)
    {  
      $numMeasures++;
      #set default value to zero.
      $trigRfcVals[$numMeasures-1]=0;
      $trigRfcFound[$numMeasures-1]=0;
      $targRfcVals[$numMeasures-1]=0;
      $targRfcFound[$numMeasures-1]=0;
      
      # try to parse out values for the string contained in $keyWordName
      @words = split( /\s+/, $line);
      for $idx (0 .. $#words)
      {
        if ( uc($words[$idx]) eq "TRIG" ) 
        { 
          $inTrigBlock=1;
          $inTargBlock=0;
          $idx++;
        }
        elsif ( uc($words[$idx]) eq "TARG" ) 
        { 
          $inTrigBlock=0;
          $inTargBlock=1;
          $idx++;
        }

        ($keyword, $value) = split( /=/, $words[$idx]);
        if (uc($keyword) eq $keyWordName && $inTrigBlock > 0)
        { 
          $trigRfcVals[$numMeasures-1] = $value;
          $trigRfcFound[$numMeasures-1]=1;
          $inTrigBlock=0;
        }
        elsif (uc($keyword) eq $keyWordName && $inTargBlock > 0)
        { 
          $targRfcVals[$numMeasures-1] = $value;
          $targRfcFound[$numMeasures-1]=1;
          $inTargBlock=0;
        }
      }

      # handle LAST qualifier for RISE, FALL and CROSS
      if ($trigRfcVals[$numMeasures-1] eq "LAST" )
      {
        $trigRfcVals[$numMeasures-1] = -1;
      }
      if ($targRfcVals[$numMeasures-1] eq "LAST" )
      {
        $targRfcVals[$numMeasures-1] = -1;
      }
    }
  }
  close(NETLIST);

  # debugging output
  print "Trig $keyWordName values= @trigRfcVals\n";
  print "Targ $keyWordName values= @targRfcVals\n";

  return (\@trigRfcVals,\@trigRfcFound,\@targRfcVals,\@targRfcFound,);
}

#
# Determines if we're in the correct Rise/Fall/Cross window
#
sub withinRFCwindow
{
  my ($currentVal,$previousVal,$isRising,$isFalling,$riseCount,$fallCount,$crossCount,
      $riseGiven,$fallGiven,$crossGiven,$riseVal,$fallVal,$crossVal,$crossLevel,
      $rfcLevelGiven,$whenGiven)=@_;
  if (not defined $crossLevel) { $crossLevel=0; }
  if (not defined $rfcLevelGiven) { $rfcLevelGiven=0; }
  if (not defined $whenGiven) { $whenGiven=0; }

  my $validRFCwindow=0;
  my $newRFCwindowForLast=0;
  my $newRiseWindow=0;
  my $newFallWindow=0;
  my $newCrossWindow=0;

  if ( $riseGiven || $fallGiven || $crossGiven )
  {
    if ( ($rfcLevelGiven > 0) || ($whenGiven > 0) )
    {
      if ( (($currentVal-$crossLevel >= 0) && ($previousVal-$crossLevel < 0)) )
      {
        $riseCount += 1;
        $newRiseWindow=1;
      }
      elsif ( (($currentVal-$crossLevel <= 0) && ($previousVal-$crossLevel > 0)) )
      {
        $fallCount += 1;
        $newFallWindow=1;
      }
    }
    else
    {
      # current approach to finding RISE/FALL count
      if ($currentVal > $previousVal && $isRising < 1)
      {
        $riseCount += 1;
        $isRising = 1.0;
        $isFalling = 0.0;
        $newRiseWindow=1;
      }
      if ($currentVal < $previousVal && $isFalling < 1)
      {
        $fallCount += 1;
        $isRising = 0.0;
        $isFalling = 1.0;
        $newFallWindow=1;
      }
    }

    if ( (($currentVal-$crossLevel <= 0) && ($previousVal-$crossLevel > 0)) ||
         (($currentVal-$crossLevel >= 0) && ($previousVal-$crossLevel < 0)) )
    {
      $crossCount += 1;
      $newCrossWindow = 1;
    } 
  
    # determine if this is a valid RFC window
    if ( ($riseGiven < 1 && $fallGiven < 1 && $crossGiven < 1) ||
         ( ($riseGiven > 0) && ( $riseCount == $riseVal  || $riseVal == -1) ) ||
         ( ($fallGiven > 0) && ( $fallCount == $fallVal  || $fallVal == -1) ) ||
         ( ($crossGiven > 0) && ( $crossCount == $crossVal  || $crossVal == -1) ) )
    {  
      $validRFCwindow=1;
    }
    else 
    {
      $validRFCwindow=0;
    }
    
    # debug
    # print ("RFC Count = ($riseCount,$fallCount,$crossCount), validRFCwindow = $validRFCwindow\n"); 
  }
  else
  {
    $validRFCwindow = 1;
  }

  $previousVal = $currentVal;

  if ( ( $riseVal == -1 && $riseGiven > 0 && $newRiseWindow > 0) || 
       ( $fallVal == -1 && $fallGiven > 0 && $newFallWindow > 0) ||
       ( $crossVal == -1 && $crossGiven > 0 && $newCrossWindow > 0) )
  {
    $newRFCwindowForLast = 1;
  } 

  return ($riseCount,$fallCount,$crossCount,$isRising,$isFalling,
          $validRFCwindow,$previousVal,$newRFCwindowForLast);
}

#
# This subroutine is used for the maximum signal value with the RFC window.
# It is needed for the FRAC_MAX adjustment for the TRIG-TARG measure.
#
sub findMaxWithinRFCwindow
{
  my ($dataFromXycePtr,$colIdx,$riseGiven,$fallGiven,$crossGiven,
      $riseVal,$fallVal,$crossVal,$crossLevel)=@_;

  my @dataFromXyce = @$dataFromXycePtr;
  my $isRising=0;
  my $isFalling=0;
  my $riseCount=0;
  my $fallCount=0;
  my $crossCount=0;
  my $firstTimeStepInWindow=0;
  my $firstStepInRFCWindow=0;
  my $validRFCWindow=0;
  my $firstValue=0;
  my $maxOfVars=0;

  my $i;
  my $previousVal;

  for $i (0 .. $#dataFromXyce )
  { 
    # set prevValue variable at first time step in window
    if ($firstTimeStepInWindow < 1)
    {
      $firstTimeStepInWindow = 1;
      $previousVal = $dataFromXyce[$i][$colIdx];
    }
      
    # determine if we're within the RFC window    
    ($riseCount,$fallCount,$crossCount,$isRising,$isFalling,
       $validRFCWindow,$previousVal) = 
          MeasureCommon::withinRFCwindow($dataFromXyce[$i][$colIdx],$previousVal,
	      $isRising,$isFalling,$riseCount,$fallCount,$crossCount,
              $riseGiven,$fallGiven,$crossGiven,
              $riseVal,$fallVal,$crossVal,$crossLevel);
        
    #print ("For index $i, validRFCwindow = $validRFCwindow\n");
      
    if( $validRFCWindow > 0 ) 
    {
      if ($firstValue==0)
      {
          $firstValue=1;
          $maxOfVars = $dataFromXyce[$i][$colIdx];
      }
      elsif ($dataFromXyce[$i][$colIdx] > $maxOfVars)
      {
          $maxOfVars = $dataFromXyce[$i][$colIdx];
      }
    }
  }

  return $maxOfVars;
}

#
# Updates the RISE/FALL/CROSS counts
#
sub updateRFCcounts
{
  my ($currentVal,$previousVal,$isRising,$isFalling,$riseCount,$fallCount,$crossCount)=@_;
  my $crossLevel = 0;
  
  # current approach to finding RISE/FALL count
  if ($currentVal > $previousVal && $isRising < 1)
  {
    $riseCount += 1;
    $isRising = 1.0;
    $isFalling = 0.0;
  }
  if ($currentVal < $previousVal && $isFalling < 1)
  {
    $fallCount += 1;
    $isRising = 0.0;
    $isFalling = 1.0;
  }

  if ( (($currentVal-$crossLevel < 0) && ($previousVal-$crossLevel > 0)) ||
       (($currentVal-$crossLevel > 0) && ($previousVal-$crossLevel < 0)) )
  {
    $crossCount += 1;
  } 
  
  return ($riseCount,$fallCount,$crossCount,$isRising,$isFalling);
}

#
# This parses data from a Xyce .PRN file generated by .TRAN  This subroutine
# assume that the subroutine checkTranFilesExist() has already been run. 
#
sub parseTranPrnFile{
  my ($CIRFILE,$stepNum)=@_;  
  if (not defined $stepNum) { $stepNum = 1; }
  
  open(XYCE_RESULTS, "$CIRFILE.prn");

  my @headerVarNames;
  my @dataFromXyce;
  my @lineOfDataFromXyce;
  my $line;
  my $varNum;
  my $numPts = 0;
  my $numStepBoundariesFound = 0;
  my $retval=0;

  while( $line=<XYCE_RESULTS> )
  {
    if( $line =~ /TIME/)
    {  
      # parse out Column Header names 
      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*//;
      @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));
      #    print STDERR "Line of data = \'$line\'\n";
      if ($lineOfDataFromXyce[0] == 0.0) 
      {
        $numStepBoundariesFound = $numStepBoundariesFound + 1;
      }
      if ( $numStepBoundariesFound == $stepNum )
      {
        for ($varNum=0; $varNum <= $#lineOfDataFromXyce; $varNum++)
        {
          #print STDERR "setting dataFromXyce[$numPts][$varNum] = $lineOfDataFromXyce[$varNum] \n";
          $dataFromXyce[$numPts][$varNum] = $lineOfDataFromXyce[$varNum];
        }
        $numPts++;
      }
      elsif ( ( $numStepBoundariesFound + 1 ) == ( $stepNum  +1 ) )
      {
        last;
      } 
    }
  }
  close(XYCE_RESULTS); 

  # debug output
  print "Col names in .prn file are @headerVarNames\n";

  # error condition.  Found no data points for current step number
  if ($numPts == 0)
  {
    print "Exited because no data for step number $stepNum in .PRN file.\n";
    print "Exit code = 2\n"; exit 2;
  }

  return(\@headerVarNames,\@dataFromXyce);
}

#
# This finds the column in the .prn file with the specified name
#
sub findColumnInPrnFile
{
  my ($colName,$headerVarNamesRef) = @_;
  my @headerVarNames = @$headerVarNamesRef;
  my $idx;
  my $retval=0;

  for $idx (0 .. $#headerVarNames)
  {
    if ( uc($colName) eq uc($headerVarNames[$idx]))
    {
      return ($idx);
    }
  }
  
  print "Failed to find column $colName in .PRN file.\n";
  $retval = 2; print "Exit code = $retval\n"; exit $retval;
}

#
# This subroutine calculates the relative and absolute errors between the measured
# and calculated values.  It also generates an exit code, and then returns to the
# .sh file
#
sub printResults
{
  my ($numMeasures,$absTol,$relTol,$zeroTol,$measureNamesRef,$measureValsRef,$calcValsRef) = @_;
  
  # deference the array references that were passed in, to get the arrays
  my @measureNames = @$measureNamesRef;
  my @measureVals = @$measureValsRef;
  my @calcVals = @$calcValsRef;
  my @absMeasureError;
  my @relMeasureError;
  
  # this subroutine will generate an exit code and then return to the .sh file.
  # this was done so that the re-measure tests work correctly.
  my $retval = 0;
  foreach my $i (0 .. $numMeasures-1)
  {
    if ( $calcVals[$i] eq "" )
    {
      print "For $measureNames[$i], calculated value is blank.  Please check test\n";
      $retval = 2; return $retval;
    } 
    elsif ( $calcVals[$i] == 0.0 && $measureVals[$i] !=0 )
    {
      print "For $measureNames[$i], calculated value is equal to 0.0 but measured value is not.";
      print "Please change test.\n";
      $retval = 2; return $retval;
    }
    elsif ( $calcVals[$i] == 0.0 && $measureVals[$i] == 0.0 )
    {
      # this allows for tests (like ON_TIME and OFF_TIME) were the measured and calculated
      # values should be zero.
      $absMeasureError[$i] = 0.0;
      $relMeasureError[$i] = 0.0;
    }
    else
    {
      # Form the error measurements
      $absMeasureError[$i] = abs( $measureVals[$i] - $calcVals[$i] );
      $relMeasureError[$i] = abs($absMeasureError[$i] / $calcVals[$i]);
    }

    print "For $measureNames[$i], (measured,calculated) values = ($measureVals[$i], $calcVals[$i])\n";
    print "       (abserror,relerror) = ($absMeasureError[$i], $relMeasureError[$i])\n";
    if( (abs($absMeasureError[$i]) > $absTol) || ((abs($measureVals[$i]) > $zeroTol) && 
      (abs($relMeasureError[$i]) > $relTol) ))
    {
      print "Out of tolerance with abstol = $absTol and reltol = $relTol \n";
      $retval = 2;
    }
  }

  if ( $retval != 0 )
  {
    print "Measure Test Failed with Exit code = $retval\n"; 
  }
  else
  {
    print "Measure Test Passed with Exit code = $retval\n";
  } 
  return $retval;
}

#
# this subroutine reads a file of text
# with the format
# tag = value
# and returns an array of tags and array of values
sub readMeasureOutputFile
{
  my ($file) = @_;
  my @varNames;  
  my @varValues;
  open(MEASUREFILE, "$file");
  my $numPts = 0;
  while( my $line=<MEASUREFILE> )
  {
    if( $line =~ /=/)
    {  
      # parse out Column Header names 
      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*//;
      my @lineWords = (split(/\s+/, $line));
      if( @lineWords == 3 )
      {
        # found a line that is text = text
        # note the '=' on the line is in array spot 1
        @varNames[$numPts] = @lineWords[0];
        if( @lineWords[2] eq "FAILED" )
        {
          @varValues[$numPts] = -1;
        }
        else
        {
          @varValues[$numPts] = @lineWords[2];
        }
        $numPts = $numPts + 1;
      }
    }
  } 

  return(\@varNames, \@varValues);
}
#
# this subroutine compares two .mt0 files to see if the results 
# match to within some reltol / abstol.
sub compareMeasureResultsFiles
{
  my( $file1, $file2, $absTol, $relTol, $zeroTol ) = @_;
  my $retval = 0;
  my @file1Names;
  my @file1Vals;
  my @file2Names;
  my @file2Vals;
  (@file1Names, @file1Vals) = readMeasureOutputFile( $file1 );
  (@file2Names, @file2Vals) = readMeasureOutputFile( $file2 );
  for( my $i=0; $i< @file1Vals; $i++ )
  {
    if( @file1Vals[$i] != -1 )
    {
      my $absdiff = abs(@file1Vals[$i] - $file2Vals[$i]);
      my $reldiff = $absdiff / @file1Vals[$i];
      if( (@file1Vals[$i] > $zeroTol) && (($absdiff > $absTol) || ($reldiff > $relTol)) )
      {
	$retval=-1;
        print("Error during re-measure test: @file1Names[$i] failed compare: @file1Vals[$i], @file2Vals[$i]\n");
        print "Re-measure Test Failed with Exit code = $retval\n";
        return $retval;
      }
    }
  }

  if ( $retval == 0 )
  {
    print "Re-measure Test Passed with Exit code = $retval\n";
  } 
  return $retval;
}

sub checkRemeasure
{
  my( $XYCE, $XYCE_VERIFY, $CIRFILE, $absTol, $relTol, $zeroTol, $fileExt, $stepNum ) = @_;

  # file extension allows checkRemeasure() to work with both prn and csd files.
  # If not file extension is specified then the default is prn.
  if (not defined $fileExt) { $fileExt = "prn"; }
  if (not defined $stepNum) {$stepNum = 1}

  print "Testing Re-measure\n";
  
  use File::Copy;
  foreach my $i (0 .. $stepNum-1)
  {
    move("$CIRFILE.mt$i","$CIRFILE.temp.mt$i");
  }

  # here is the command to run xyce with remeasure
  my $CMD="$XYCE -remeasure $CIRFILE.$fileExt $CIRFILE > $CIRFILE.remeasure.out";
  my $retval=system($CMD)>>8;
  
  if ($retval != 0) { print "Exit code = $retval\n"; exit $retval; }
  if (not -s "$CIRFILE.$fileExt" ) { print "Exit code = 14\n"; exit 14; }

  # Did we make the measure files
  foreach my $i (0 .. $stepNum-1)
  {
    if (not -s "$CIRFILE.mt$i" ) { print "Exit code = 17\n"; exit 17; }
  }

  # use file_compare 
  my $dirname = `dirname $XYCE_VERIFY`;
  chomp $dirname;
  my $fc = "$dirname/file_compare.pl";

  # rename the files and compare them
  foreach my $i (0 .. $stepNum-1)
  {
    move("$CIRFILE.mt$i","$CIRFILE.remeasure.mt$i");
    move("$CIRFILE.temp.mt$i","$CIRFILE.mt$i");
    `$fc $CIRFILE.remeasure.mt$i $CIRFILE.mt$i $absTol $relTol $zeroTol > $CIRFILE.remeasure.mt$i.out 2> $CIRFILE.remeasure.mt$i.err`;  
    $retval=$? >> 8;
    if ($retval != 0)
    {
      print("Re-measure failed for step $i\n");
      return $retval;
    }
  }

  return $retval;
}

sub checkNumberFormat
{
  my ($val,$precKnown,$precVal) = @_;
  my $retval = 0;
  my $idx;

  my $foundDot = 0;
  my $digitCount = 0;
  my $token;
  my $valLength = length($val);

  foreach $idx (0 .. $valLength)
  {
    $token = substr($val,$idx,1);
    #print "token for index $idx = $token\n";
    if ( ($token =~/[.]/) )
    {
      $foundDot=1;
      #print("\tFound dot character\n");
      if ($digitCount != 1) 
      {	  
        $retval =2; 
        print "Failed number format test.\n";
        print "Should only be one leading digit in scientific notation\n";
        print "Return code = $retval\n";
        return $retval;
      }
      else
      { 
        # reset the digit count, since we found a dot
	$digitCount = 0;
      }
    }
    elsif  ( ($token =~ /[0-9]/) )
    {
      #print "\tIncrementing digit count for character $token\n";
      $digitCount++;
    }
    elsif ( ($token =~ /e/) && ($foundDot > 0) )
    {
      #print "\tFound an exponential character (e).  Ending loop ...\n";
      
      #now do some checks that the exp is correct
      #if ( (length($val) > $idx+5) )
      #{
      #  $retval =2; 
      #  print "Failed number format test.\n";
      #  print "Inproperly formatted exponent. Length is too long.\n";
      #  print "Length and idx = ($valLength , $idx)\n"; 
      #  print "Return code = $retval\n";
      #  exit $retval;
      #}
      if ( (!(substr($val,$idx+2,1) =~ /[0-9]/ )) || (!(substr($val,$idx+3,1) =~ /[0-9]/)) )
      {
        $retval =2; 
        print "Failed number format test.\n";
        print "Inproperly formatted exponent. Last two characters are not digits.\n";
        print "Return code = $retval\n";
        return $retval;
      }

      if ( (substr($val,$idx+1,1) =~ /[+]/) || (substr($val,$idx+1,1) =~ /[-]/) )
      {
        # this means success if the previous test also passed
        last;
      }
      else
      {
        $retval =2; 
        print "Failed number format test.\n";
        print "Exponent lacked + or - sign.\n";
        print "Return code = $retval\n";
        return $retval;
      }
    }  
  }

  # also check the precision, if it's known
  if ( ($precKnown > 0) && ($digitCount != $precVal) )
  {
    $retval =2; 
    print "Failed precision test.\n";
    print "Specified and measured precision = ($precVal,$digitCount)\n";
    print "Exit code = $retval\n";
    return $retval;
  }

  # test succeeded if we get here
  return $retval;
}

#
# this subroutine is used to compare gold standard circuit.cir.mt? files 
# to what the simulator creates
sub compareFourierMeasureFiles
{
  my ($goldFile, $simFile, $phaseAbsTol, $relTol, $zeroTol, $defaultPrecision,$expectedDoubleCount) = @_;

  use Scalar::Util qw(looks_like_number);
  #
  # Now look for the measure output file and compare it to a 
  # gold standard line by line.
  #
  open(RESULTS, $simFile);
  open(GOLD_STD, $goldFile);
  my $lineNumber=1;
  my $retval = 0;
  my $doubleCount=0;
  my $line;
  my $line_gs;
  while( ($line=<RESULTS>) && ($line_gs=<GOLD_STD>) )
  {
    # process a line into text and 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*//;
    my @lineOfDataFromXyce = (split(/[\s,]+/, $line));
    #print "line of data from Xyce = @lineOfDataFromXyce\n";
    #print "end count = $#lineOfDataFromXyce\n";
    
    # process a line_gs into text and values.
    chop $line_gs;
    $line_gs =~ s/^\s*//;
    my @gsLineOfDataFromXyce = (split(/[\s,]+/, $line_gs));
    
    if( $#lineOfDataFromXyce != $#gsLineOfDataFromXyce )
    {
      print "Xyce's output to measure file doesn't match the gold standard at line $lineNumber\n";
      print "Xyce's output: $line\n";
      print "Gold Standard: $line_gs\n";
      $retval=2;
    }
    else
    {
      # the two files have the same number of items on a line.  
      # compare individual values as scalars  This will have the 
      # effect fo 
      for( my $i=0; $i<=$#lineOfDataFromXyce; $i++ )
      {
        #print "data item = $lineOfDataFromXyce[$i]\n";
        if (looks_like_number($lineOfDataFromXyce[$i]) && looks_like_number($gsLineOfDataFromXyce[$i] ) )
        {
          if( ( abs( $lineOfDataFromXyce[$i] ) < $zeroTol) &&  (abs( $gsLineOfDataFromXyce[$i] ) < $zeroTol ))
          {
             #print "number compare below zeroTol $lineOfDataFromXyce[$i] , $gsLineOfDataFromXyce[$i] ok\n";
          }
          elsif( (($i==3) || ($i==5)) && (abs( $lineOfDataFromXyce[$i] - $gsLineOfDataFromXyce[$i] ) < $phaseAbsTol )) 
          {
             # phase needs different handling
             #print "Phase comparison passed $lineOfDataFromXyce[$i] , $gsLineOfDataFromXyce[$i]\n";
          }
          elsif( ( (abs( $lineOfDataFromXyce[$i] - $gsLineOfDataFromXyce[$i] ))/abs($gsLineOfDataFromXyce[$i]) < $relTol )) 
          {
             # regular compare
          }
          else
          {
            print "On line $lineNumber of test and gold file found a numeric difference\n";
            print "Comparing Xyce's \"$lineOfDataFromXyce[$i]\" to GS \"$gsLineOfDataFromXyce[$i]\" as ";
            print "Failed numeric compare\n";
            $retval=2;
            last;
          }
          if( $lineOfDataFromXyce[$i] =~ /e/)
          {
	    $doubleCount++;
            #print "double count and data item = $doubleCount and $lineOfDataFromXyce[$i]\n";
            $retval = MeasureCommon::checkNumberFormat($lineOfDataFromXyce[$i],1,$defaultPrecision);
            if ( $retval != 0 )
            {
            print "test Failed because of number format!\n";
            $retval=2;
            last;
          }
        }
        }
        elsif( $lineOfDataFromXyce[$i] eq $gsLineOfDataFromXyce[$i] )
        {
          # same in string context so ok.
          # print "string compare ok\n";
        }
        else
        {
          print "Elements failed compare on line $lineNumber: \n";
          print "Xyce produced: \"$lineOfDataFromXyce[$i]\"\n";
          print "Gold standard: \"$gsLineOfDataFromXyce[$i]\"\n";
          $retval=2;
          last;
        }
      
      }
    }
    $lineNumber++;
  }
  close(RESULTS);
  close(GOLD_STD);

  if ( $doubleCount != $expectedDoubleCount )
  {
    print "Found $doubleCount doubles.  Expected $expectedDoubleCount doubles.\n";
    $retval=2; 
  }
  return $retval;
}

#
# We need the 1; at the end because, when Perl loads a module, Perl checks
# to see that the module returns a true value to ensure it loaded OK. 
#
1;

#
# End of common code used in each .MEASURE test
#  


