#!/usr/bin/perl -w
#
# compR script used to process one component or a system with:
#  compR xxx.ccf
# It is called from COMPS for each component of a system, the component
#  names obtained from system.pscf .
#
# Script should not be used on a stateless component (use COMPF -S instead)
#
# -X option substitutes the .ccft "theory" file (which may already exist) to run.
#
# The regular mode is interactive (turned off with -I option) and verbose
#  (off with -V option), including a crude spin counter.  In interactive, the
#  results are graphed, with many choices of what is shown.  
#
# In any case, the .ccft file is produced for a component.
#
# Functionality of the form:
#  compR [options] xxx [start stop count]
# where "xxx" does not have extension ".ccf" 
# asks for domain range, sampling info (if not given on command line)
# to run without subdomains
#
# -O (oh) option runs code (usually produced by SmeasS) from information 
#  in the system.pscf file.  The parameters are taken from the .ccf file
#  of the first component in the system, and two forms of output file for
#  plotting/analysis are created:  *.datt  in which all component states
#  are included, and *.dat for plotting.  In output.datt a typical line is:
#    input state1 state2 ... stateN output
#  In output.dat, the state N-tuple is reduced to
#  a single value of sqrt((state1**2 + ... + stateN**2)/N).

use component;
use samplings;
use status;
use stepfunc;

$Ooption = 0; #runs a "system" of multiple components, with composite state
$Voption = 1; #indicates verbose mode, includes spin counter; -V means off
$Ioption = 1; #interactive mode; -I means off
#not sure that -X works...
$Xoption = 0; # "execute" a theory, .ccft file (but still start with .ccf param)

$ccfProcessed = 0; # indicates that subdomain data has been read from a ccf file

# Process command-line arguments

ARGS:
$component = shift || die "No component executible or ccf file specified";
if ($component eq "-O") { $Ooption = 1; goto ARGS; }
if ($component eq "-V") { $Voption = 0; goto ARGS; }
if ($component eq "-I") { $Ioption = 0; goto ARGS; }
if ($component eq "-X") { $Xoption = 1; goto ARGS; }


#see if command line override
$p1 = shift;
$p2 = shift; 
$p3 = shift;
#debug
#die "$p3\n";
if ($component =~ /\.ccf$/i) {
# read input range and state range from ccf file
  $input_freq = 0;
  $ccf_component = $component;  #ccf name, before changed to executable
  open(COMP, $component) || die "Invalid ccf file specified";
    #first line of file points to real executible
    chomp($component = <COMP>);
    unless (substr($component,0,1) eq '/') { $component = "./".$component; }

    $array = \@input_subs;
    $line_count = 0;
    $in_sub_cnt = 0;
    while($linearr = <COMP>) {
      #read line from .ccf file
      if ($linearr eq "\n") {
        $in_sub_cnt = $line_count;
        $line_count = 0;
        $array = \@state_subs;
      }
      else {
        chomp($array->[$line_count] = $linearr);
        $line_count++;
      }
    }
  close(COMP);
  $input_start = (split(' ',$input_subs[0]))[0];
  $input_end = (split(' ',$input_subs[$in_sub_cnt-1]))[1];
  $seq_count = 0;
  foreach $subdomain (@input_subs) {
    $seq_count += (split(' ',$subdomain))[2];
  }
  $seq_count = 30 if ($seq_count < 30);
  if (defined($p3)) { #override from command line
    $input_start = $p1;
    $input_end = $p2;
    $seq_count = $p3;
  }

  $ccfProcessed = 1;
  if ($Xoption) {
    die "No 'theory' file to 'execute'" unless (-e $ccf_component."t");
    #open (CCF, "<".$ccf_component."t");
    #$line1 = <CCF>;
    #@brk = split(" ",$line1); 
    #$InitialState = $brk[3];
    #close (CCF);
  } #should do better, check relation between .ccf and .ccft
#debug
# print "sequences: $seq_count \n";
} else {
  $Xoption = 0; #must have .ccf to execute theory
  if ($Ooption) { #get input parameters from first component for system
    open (PSCF, "<system.pscf") or die "Must have \"system.pscf\" file";
    $first = <PSCF>; #polish line
    @polish = split(" ",$first);
    $firstcomp = $polish[0];
    for ($cc=1; $cc<=$firstcomp; $cc++) { #skip down to the first polish component
      $first = <PSCF>;  #.ccf names
    } 
    close (PSCF);
#debug
#print "first component: $first";
    open (FCOMP,"<$first") or die "Something wrong with first component $first";
    $lineval = <FCOMP>; #skip executable name
    $lineval = <FCOMP>; #first subdomain
    @trip = split(" ",$lineval);
    $input_start = $trip[0];
    $seq_count = $trip[2]; #count
    PL:
    while ($lineval=<FCOMP>) {
      chomp($lineval);
      last PL if ($lineval eq "");
      @trip = split(" ",$lineval);
      $seq_count += $trip[2]; #count
    }
    $seq_count = 20 if ($seq_count < 20);
    $input_end = $trip[1];
    close (FCOMP);
  } else { #ask user for parameters
    $input_start = shift;
    $input_end = shift;
    $seq_count = shift;
  }
}

die "Invalid component specified" unless (-x $component);

unless (defined($input_start) && defined($input_end)) {
 print "Enter starting value for input range: "; chop($input_start = <STDIN>);
 print "Enter ending value for input range: "; chop($input_end = <STDIN>);
}

unless ($seq_count) {
SEQ_COUNT:
 print "Enter number of input sequences to sample: "; chop($seq_count = <STDIN>);
 if ($seq_count < 1) { goto SEQ_COUNT; }
}

system "rm -f *.state"; #make sure state slates is clean
system "rm -f *.errlog"; #clear log files in case executing a SystemCode with XqtS
&status::init($seq_count, "Sampling component ".$component
              ." in [".$input_start.",".$input_end.") for ".$seq_count); 

# Prepare input sampling

if ($ccfProcessed) { &stepfunc::init(\@input_subs, \@state_subs); }

$count = 0;
@inputs = ();
@in_state = ();
@Counts = (); #array of $count values that end sequences
$Ct = 0; #index into $Counts
##
@outputs = ();
@runtimes = ();
@out_state = ();
$test_point_count = 0;

srand(3.14159265); #we want the same sequence every time this runs
# Loop through input sequences
for $i (1..$seq_count) {

  $input_freq = int(rand($seq_count)) + 1;
  $test_point_count += $input_freq;
  $input_sampler = samplings->randomize($input_start, $input_end, $input_freq);

  if ($Xoption) {
    $base = &component::baseName($component);
    $errfile = $base.".errlog";
    $statefile = $base.".state";
    if (-e $errfile) {unlink $errfile;} #remove any existing error log
    $compObj = component->new(component => "./XqtS", 
      statefile => $statefile, comparg => $ccf_component);
  } else {
    $compObj = component->new(component => $component);
  }
  #run component once on midpoint to get initial state
  $input = ($input_end + $input_start)/2;
  $compObj->run($input) || die "Can't run component";
  $theout = $compObj->getOutput;
  if ($theout =~ /LOOPING/) {
    die "Code is probably looping on state-initialization input $input";
  }
  chomp($InitialState = $compObj->getState);
#debug
#print "initial state was: $InitialState\n";

  # Loop through input
  $input = $input_sampler->next();
#debug
#print "Input sequence # $i\n";
  while ($input !~ /^\.$/) {
   $inputs[$count] = $input; # Record starting values
   chomp($in_state[$count] = $compObj->getState);
#debug
#print "$input $in_state[$count] \n";

   # Run component
   $compObj->run($input) || die "Cannot run component";
  $theout = $compObj->getOutput;
  $thestate = $compObj->getState;
#debug
#print "$theout\n";
  if ($theout =~ /LOOPING/) {
    die "Code is probably looping on input $input, state $thestate";
  }
   $outputs[$count] = $theout;
   $runtimes[$count] = $compObj->getRuntime;
   
   # Get persistent state
   chomp($out_state[$count] = $compObj->getState);
#debug
#print "$out_state[$count]"; die "...that's one";

   # Process approximations
   if ($ccfProcessed) {
      unless ( &stepfunc::process($input, $in_state[$count], $outputs[$count], $runtimes[$count], $out_state[$count]) ) {
          if ($Voption) { &status::showmesg("Input ($input) / initial state ($in_state[$count]) outside subdomain boundaries!"); }
      }
   }

   $count++;
#   if ($Voption) { &status::progress($count); }

   $input = $input_sampler->next();
  } # Next input

$Counts[$Ct++] = $count-1; #record the end-of-seq subscript
#debug
#print "seq ctr: $count \n";

if ($Voption) { &status::progress($i); }

} # Next input sequence

$compObj->NoState;

# Create graphing files

open(SEQ, ">sequence.dat");
open(IMP, ">impulse.dat");
open(OUTPUT, ">output.dat");
open(RUNTIME, ">runtime.dat");
open(STATE, ">state.dat");
#if ($Ooption) { #system, files with multiple state values
  open(OUTALL, ">output.datt");
  open(RUNALL, ">runtime.datt");
  open(STATEALL, ">state.datt");
#}
PL: for ($i = 0; $i < $count; $i++) { 
 #unless (defined($in_state[$i])) { #skip the unitialized initial case $i==0
   #next PL;
   #}
  $combinstate = 0; #init sum of squares of states
  $comboutstate = 0;
#TBD fix SEQ and IMP for multiple state values on Ooption
 print SEQ  $inputs[$i]." ".$in_state[$i]." 0"."\n";
 if ($i == $Counts[0]) { #end of sequence item
   print SEQ "\n"; #separate sequences
   print IMP $inputs[$i]." ".$in_state[$i]." ".$outputs[$i]."\n";
   shift @Counts;
 }
#end TBD
   print OUTALL $inputs[$i]." ".$in_state[$i]." ".$outputs[$i]."\n";
   print RUNALL $inputs[$i]." ".$in_state[$i]." ".$runtimes[$i]."\n";
   print STATEALL $inputs[$i]." ".$in_state[$i]." ".$out_state[$i]."\n";
 @composin = split(" ",$in_state[$i]);
 if (scalar(@composin) > 1) { #fix plot files
   @composout = split(" ",$out_state[$i]);
   $inct = 0; $outct = 0;
   for ($ss=0;$ss<scalar(@composin);$ss++) {
     if ($composin[$ss] =~ /\d/) {
       $combinstate += $composin[$ss]**2;
       $inct++;
     }
     if ($composout[$ss] =~ /\d/) {
       $comboutstate += $composout[$ss]**2;
       $outct++;
     }
   }
   $in_state[$i] = sqrt($combinstate/$inct);  #destroy multiple values with vector magnitude
   $out_state[$i] = sqrt($comboutstate/$outct);
 }
 print OUTPUT $inputs[$i]." ".$in_state[$i]." ".$outputs[$i]."\n";
 print RUNTIME $inputs[$i]." ".$in_state[$i]." ".$runtimes[$i]."\n";
 print STATE $inputs[$i]." ".$in_state[$i]." ".$out_state[$i]."\n";
}
close(SEQ);
close(IMP);
close(STATE);
close(RUNTIME);
close(OUTPUT);
#if ($Ooption) {
  close(OUTALL);
  close(RUNALL);
  close(STATEALL);
#}
if ($ccfProcessed) { &stepfunc::create_splot_files("out_step.dat", "run_step.dat", "state_step.dat"); }
#check for .errlog files 
$co = system "ls *.errlog > /dev/null 2>&1";
unless ($co) { 
  warn "There were error logs created by XqtS on this run:";
  print `ls *.errlog`;
}

  # default options for graphing -- Add options to the
  # beginning of hash, they go to end of @args.
  %opts = ();
  if ($ccfProcessed) {
        $opts{'Show step approximations? [0-1]|[0-1]'} = 1;
        $opts{'Show subdomain boundaries? [0-1]|[0-1]'} = 0;
  }
# save all plots to postscript files using default options
  if ($ccfProcessed) {
    &plot("output.dat", "out_step.dat", "output.ps", "Output");
    &plot("runtime.dat", "run_step.dat", "runtime.ps", "Run Time");
    &plot("state.dat", "state_step.dat", "state.ps", "Final State");
  } else { #messy...
    &plot("output.dat", "", "output.ps", "Output");
    &plot("runtime.dat", "", "runtime.ps", "Run Time");
    &plot("state.dat", "", "state.ps", "Final State");
  }

  if ($ccfProcessed && !$Xoption) {
         &stepfunc::create_ccft_files($ccf_component."t", "theory C R $InitialState");} 
	 #create .ccft name with proper options

  print "Sampled $seq_count sequences, $test_point_count points.\n";
if ($Ioption) { #show interactive menu
  $plot = "";
  $step = "";
MENU:
  print "\nInteractive mode menu\n";
  print "---------------------\n";
  print "1. Configure graphing options\n";
  print "2. Show graph of input x state vs. output\n";
  print "3. Show graph of input x state vs. runtime\n";
  print "4. Show graph of input x state vs. final state\n";
  print "5. Show input-state random walk\n";
  print "6. Show output at end of sequences\n";
  print "7. Exit\n\n";
  print "Enter number:  "; chop($i = <STDIN>);
  
  if ($i == 1) {
    %opts = &configopts(%opts);
    goto MENU;
  } elsif ($i == 2) {
    $plot = "output.dat";
    $step = "out_step.dat";
    $label = "Output";
  } elsif ($i == 3) {
    $plot = "runtime.dat";
    $step = "run_step.dat";
    $label = "Runtime";
  } elsif ($i == 4) {
    $plot = "state.dat";
    $step = "state_step.dat";
    $label = "Final State";
  } elsif ($i == 5) {
    $plot = "sequence.dat";
    $step = "sequence.dat";
    $label = "Input Sequences";
    $how = "with lines";
  } elsif ($i == 6) {
    $plot = "impulse.dat";
    $step = "impulse.dat";
    $how = "with impulses";
    $label = "End-sequence Output";
  } elsif ($i == 7) {
    exit;
  } else {print "Bad choice!\n"; goto MENU;}

  @args = values %opts;
  if ($i<=4 && $i>=2) { #mainline graph
    if ($ccfProcessed) {#error table to STDOUT
      &stepfunc::ErrorTable($label);
    }
    else {
      $step = "";
    }
  }
  &plot($plot, $step, "", $label, $how, @args);

  goto MENU;
} 

# This subroutine takes a hash that describes the options
# and builds a menu based on that hash. When the user is finished
# editing the options through the menu, the subroutine returns the
# hash with the modified values.
######################################
# Argument : a hash of options in this format...
#  Key: display name|regular expression
#    Where display name is the name to display for the option and the regular expression is the
#    test for valid settings for this option.
#  Value: the option's setting
sub configopts {
  return unless (defined($_[0]));
  my (%mopts, $count, $key, $disp, $regex, @tmp, $in);
  %mopts = @_;
OMNU:
  print "\n\nGraphing options\n";
  print "----------------\n";
  $count = 1;
  foreach $key (keys %mopts) {
   ($disp, $regex) = split(/\|/, $key);
   print "$count. $disp  ".$mopts{$key}."\n";
   $tmp[$count] = $key;
   $count++;
  }
  print "\nEnter number to change, or press return to go back: "; chop($in = <STDIN>);
  if (($in =~ /^$/) || ($in < 1) || ($in > $count)) { return %mopts; }
  $count = $in;
  print "Enter new value: "; chop($in = <STDIN>);
  ($disp, $regex) = split(/\|/, $tmp[$count]);
  if ($in !~ /$regex/) { print "Invalid setting\n"; goto OMNU; }
  $mopts{$tmp[$count]} = $in;
  goto OMNU;
}
exit;


# $_[0] = file name of data
# $_[1] = file name of comparison plot, "" if none
# $_[2] = file name of ps file; to terminal if ""
# $_[3] = z axis label 
# $_[4] = type of plot (lines, points, etc.) -- default lines
# $_[5] = show subdomain boundaries?
sub plot {
 my ($data, $stepfile, $psfile, $label, $howplot, $showsubs) = @_;
 my @subd;
 die "Data file does not exist" unless (-e $data);
 unless (defined($howplot)) {
   $howplot = "with lines lt 3";
 }  
 open(GNUPLOT, ">temp");
    print GNUPLOT "set xlabel \"Input\"\n";
    if ($Ooption) {
      print GNUPLOT "set ylabel \"Composite State\"\n";
    } else {
      print GNUPLOT "set ylabel \"State\"\n";
    }
    print GNUPLOT "set zlabel \"$label\"\n";
  if ($showsubs) {
    foreach $subdomain (@input_subs) {
     @subd = split(' ',$subdomain);
     print GNUPLOT "set arrow from first $subd[0],graph 0,graph 0 to first $subd[0],graph 1,graph 0 lt 6 lw 2 nohead\n";
     print GNUPLOT "set arrow from first $subd[1],graph 0,graph 0 to first $subd[1],graph 1,graph 0 lt 6 lw 2 nohead\n";
    }
    foreach $subdomain (@state_subs) {
     @subd = split(' ',$subdomain);
     print GNUPLOT "set arrow from graph 0,first $subd[0],graph 0 to graph 1,first $subd[0],graph 0 lt 3 lw 2 nohead\n";
     print GNUPLOT "set arrow from graph 0,first $subd[1],graph 0 to graph 1,first $subd[1],graph 0 lt 3 lw 2 nohead\n";
    }
  }
  if ($psfile) { # make PostScript plots
    print GNUPLOT "set terminal postscript color\n";
    print GNUPLOT "set output \"$psfile\"\n";
  } else {
    print GNUPLOT "set terminal x11\n";
  }
  #print GNUPLOT "set hidden3d\n";
  print GNUPLOT "splot \"$data\"";
  if ($stepfile) {
    print GNUPLOT ", \"$stepfile\" $howplot \n";
  }
  print GNUPLOT "\n";
  unless ($psfile) { print GNUPLOT "pause -1 \"Press return to continue.\"\n"; }
 close(GNUPLOT);
 system("gnuplot temp");
}
