package CFX5::Simulation;

use strict;

use File::Temp qw(tempdir);

use CFX5::Simulation::Util qw(_check_defined GetDataSet);
use CFX5::FileTests;
use CFX5::PathManip qw(dirname basename fullpath join_file normalize_filename);
use CFX5::Output qw(:all);
use CFX5::Output::OutFile;
use CFX5::Output::Utils 'wraptext';
use CFX5::WorkDir;
use CFX5::Simulation::Configuration;
use CFX5::Simulation::AAS;
use CFX5::ResInfo;
use CFX5::CCLI;
use CFX5::FileOps qw(copy_file);
use CFX5::CCLDiff ; 
use CFX5::PatchInfo;
use CFX5::TimeDate qw(timedatestamp);

use CFX5::Config qw(setconf %Config export_conf getconf_default getconf
                    append_to_libpath);

use base 'CFX5::Simulation::SimSettings';

#packageall CFX5::Simulation
#packageall CFX5::Simulation::Configuration

sub new {
  my $class = shift;
  my %configmap;
  my %termconds;
  my %configIDFromName; 
  my $resinfo=new CFX5::ResInfo();
  my $self = {
              configmap=>\%configmap,
              resinfo=>$resinfo,
              simstep=>0,
              termconds=>\%termconds,
              configIDFromName=>\%configIDFromName,
             };
  bless $self, $class;

  $self->SUPER::initialise();
  $self->initialise(@_);
  
  return $self;
}

sub initialise {
  my $self = shift;
  $self->_newsetting('outfile');
  $self->_newsetting('rundir_obj');
  $self->_newsetting('fullname');
  $self->_newsetting('outobj');
  $self->_newsetting('outobjccl');
  $self->_newsetting('text_command');
  $self->_newsetting('ownoutfile');
  $self->_newsetting('rundir');
  $self->_newsetting('checkpoint_workdir');
  $self->_newsetting('mdeffile', '/SIMULATION CONTROL/EXECUTION CONTROL/RUN DEFINITION/Solver Input File');
  $self->_newsetting('cfgdefdir');
  $self->_newsetting('mresfile');
  $self->_newsetting('name');
  $self->_newsetting('batch');
  $self->_newsetting('configmap');
  $self->_newsetting('singleconfig');
  $self->_newsetting('indirect_startup');
  $self->_newsetting('indirect_options');
  $self->_newsetting('norun');
  $self->_newsetting('stop_now');
  $self->_newsetting('check_only');
  $self->_newsetting('check_for_gui');
  $self->_newsetting('start_aas');
}

sub check_read_ccl_option {
  my $self=shift;
  my $from=shift;
  my $ccllines=shift;

  my $fromname=shift;
  
  if (ref $from) {
    if (ref $from eq 'ARRAY') {
      @$ccllines=@$from;
      $fromname='memory' unless defined $fromname;
    } else {
      internal_error("Cannot handle reference to", ref($from), "in handle_read_ccl_option");
    }
  } else {
    # a scalar, read from file.
    local *FILE;
    if ($from eq '-') {
      *FILE=*STDIN;
      $fromname='standard input' unless defined $fromname;
    } else {
      CFX5::FileTests::datafile($from, "Command Language file $from")
          or return '';
      unless (open FILE, "<$from") {
        _error("Error reading Command Language file $from: $!");
        return '';
      }
    }

    $fromname=$from unless defined $fromname;
    
    while (<FILE>) {
      chomp $_;
      $_ =~ s![\r\n]*$!!;
      push @$ccllines, $_;
    }
    close FILE if ($from ne '-');
  }

  # Is it single config or multiconfig... look for clues
  # FLOW: it's single
  # Solver Input File not mdef/mres it's single
  # Everything else it's multiconfig, or can be treated as if it is
  my $multiconfig=1;
  my $multiline=0;
  foreach my $i (@$ccllines) {
    if($multiline or $i=~m!Solver Input File ?= ?(.*)!){
      my $file=$1;
      if($multiline){
        $file=$i;
      }
      elsif($file=~m!\\$!){ $multiline=1; next;}
      $file =~ s![\r\n]!!g;
      unless ($file=~m!\.(mdef|mres)$!){$multiconfig=0;}
      last; 
    }
    if($i=~m!^FLOW:!){$multiconfig=0; last;}
  }
  return not $multiconfig;
}  

#
################################################################################
#
#  Routines for setup
#
################################################################################
#

sub libccl {
  my $self = shift;
  my @libCCL = @_ ;
  
  my $api = $self->ccl_api_obj();
  my @currentCCL;
  $api->getObjectCCL("/LIBRARY", \@currentCCL);
  
  # CCL existed if we've picked up more than just the LIBRARY: ...  END pair
  my $haveCCL = @currentCCL > 2 ;

  # Given CCL - override the one we've got
  if (defined $libCCL[0]) {
    my $rv ;
    $rv=$api->deleteObj('/LIBRARY') if $haveCCL ;
    if (defined $rv) {
      _error("libccl: Error deleting library CCL:\n",
      $self->format_ccl_error($rv));
      return 0; 
    }
    $rv=$api->parseCCLData(\@libCCL);
    if (defined $rv) {
     _error("libccl: Error parsing library CCL:\n",
     $self->format_ccl_error($rv));
     return 0; 
    }
  }
  else {
    $api->getObjectCCL("/LIBRARY", \@libCCL) if $haveCCL;
  }
  return @libCCL ; 
}

sub rulesccl {
  my $self = shift;
  my @rulesCCL = @_ ;
  
  my $api = $self->ccl_api_obj();
  my @currentCCL;
  $api->getObjectCCL("/RULES", \@currentCCL);
  
  # CCL existed if we've picked up more than just the RULES: ...  END pair
  my $haveCCL = @currentCCL > 2 ;

  # Given CCL - override the one we've got
  if (defined $rulesCCL[0]) {
    my $rv ;
    $rv=$api->deleteObj('/RULES') if $haveCCL ;
    if (defined $rv) {
      _error("rulesccl: Error deleting RULES CCL:\n",
      $self->format_ccl_error($rv));
      return 0; 
    }
    $rv=$api->parseCCLData(\@rulesCCL);
    if (defined $rv) {
     _error("rulesccl: Error parsing RULES CCL:\n",
     $self->format_ccl_error($rv));
     return 0; 
    }
  }
  else {
    $api->getObjectCCL("/RULES", \@rulesCCL) if $haveCCL;
  }
  return @rulesCCL ; 
}

sub choose_name {
  my $self=shift;
  my $problem=$self->name();

  unless (defined $problem) {
    internal_error("Cannot calculate fullname until we have name");
    return undef;
  }

  # break this down into a directory and a filename
  my $probdir=dirname($problem);
  my $probname=basename($problem);

  local *CHECKDIR;

  # start reading the directory
  unless (opendir CHECKDIR, $probdir) {
    _error("Unable to work out next problem number as the destination directory\n\t'$probdir'",
         "\ncould not be read: $!");
    return undef;
  }

  my $max=0;
  my $f;
  while (defined ($f=readdir(CHECKDIR))) {
    if ($f =~ m{^\Q${probname}\E_([0-9][0-9][0-9])(\.(ansys|dir|out|res|trn|cfx))?$}i) {
      # it matches this problem
      $max=$1 if $1 > $max;
    }
  }

  closedir CHECKDIR;

  if ($max >= 999) {
    _error("The file number cannot exceed 999.",
           "\nPlease choose a new problem name, or work in a different directory.");
    return "xxx";
  }

  # now max is the biggest number we found for this problem, or 0 if we
  # didn't find any, just add one and zero-pad it.
  $max++;
  $max=("0"x(3-length($max))).$max;

  $self->fullname(fullpath($self->name().'_'.$max));

  # log just the basename, as this is for testing
  #$self->TraceLog('full-name-chosen',basename($self->fullname()));

  $self->fullname();
}

use vars qw($ReadCCLRecursionProtect $ReadCCLBG);
$ReadCCLRecursionProtect=0;
$ReadCCLBG=0;
sub read_ccl {
  my $self=shift;
  my $from=shift;

  my @ccllines;
  my $fromname=shift;
  
  $self->ensure_ccl_rules_loaded()
    unless $Config{CFX5_SOLVE_LAX_CCL};

  if (ref $from) {
    if (ref $from eq 'ARRAY') {
      @ccllines=@$from;
      $fromname='memory' unless defined $fromname;
    } else {
      internal_error("Cannot handle reference to", ref($from), "in read_ccl");
    }
  } else {
    # a scalar, read from file.
    local *FILE;
    if ($from eq '-') {
      *FILE=*STDIN;
      $fromname='standard input' unless defined $fromname;

      # this is the one which can often (eg, from the solver manager,
      # or the user) pause for a while, so let's take advantage of
      # that to load the rules.  This will also instantiate the API
      # object if necessary.
      $self->ensure_ccl_rules_loaded()
        unless $Config{CFX5_SOLVE_LAX_CCL};


    } else {
      CFX5::FileTests::datafile($from, "Command Language file $from")
          or return '';
      unless (open FILE, "<$from") {
        _error("Error reading Command Language file $from: $!");
        return '';
      }
    }

    $fromname=$from unless defined $fromname;

    _trace("Reading CCL from $fromname");
    while (<FILE>) {
      chomp;
      push @ccllines, $_;
    }
    close FILE if ($from ne '-');
  }

  my $api=$self->ccl_api_obj();

  # do we allow empty CCL?  It seems a little petty to refuse to take
  # it, but let's record it in case
  _debug("Warning: no lines of CCL from $from") unless @ccllines;

  my $origmdeffile=$self->mdeffile() || '';

  # now parse it
  my $rv=$api->parseCCLData(\@ccllines, '');
  if (defined $rv) {
    # oops, failed
    _error("Error reading Command Language data from $fromname:\n", 
           $self->format_ccl_error($rv));
    return '';
  }

  my $newmdeffile=$self->mdeffile() || '';
  if ($origmdeffile ne $newmdeffile and !$ReadCCLRecursionProtect) {
    local $ReadCCLRecursionProtect=1;
    # it's different
    $self->read_mdef_ccl(); # and ignore errors
    unless ($ReadCCLBG) {
      # if this is a call from read_ccl_bg, we don't want to re-parse
      # the input
      my $rv=$api->parseCCLData(\@ccllines, '');
      if (defined $rv) {
        # oops, failed
        _error("Error re-reading Command Language data from $fromname:\n", 
               $self->format_ccl_error($rv));
        return '';
      }
    }
  }
 
  1;
} 

sub read_ccl_bg {
  my $self=shift;
  local $ReadCCLBG=1;
  return $self->read_ccl(@_);
}

sub read_mdef_ccl {
  my $self=shift;
  my $bg=shift || 0;

  my $mdeffile=$self->mdeffile();
  unless (defined $mdeffile) {
    internal_error("read_mdef_ccl called when no definition file has been specified");
    return 0;
  }
  unless (CFX5::FileTests::datafile($mdeffile)) {
    return 0;
  }

  unless ( defined $Config{CFX_CUSTOM_DATA_DIR} ) {
    my $datadirds=CFX5::CCLI::getcclds($mdeffile, 'G/CUSTOMETC');
    if ( ref $datadirds and defined $datadirds ){
      _trace("Reading custom data directory from definition file ...");
      my @data=$datadirds->data();
      $Config{CFX_CUSTOM_DATA_DIR}=$data[0];
      CFX5::CCLI::update_ccl_libs();
    } 
  }

  _trace("Reading Command Language dataset from definition file ...");
  my $ds=CFX5::CCLI::getcclds($mdeffile, 'G/COMMANDS');

  unless ( defined $ds and ref $ds ) {
    _error("Unable to read Command Language data from the definition file $mdeffile");
    return '';
  }

  local $ReadCCLRecursionProtect=1;

  my @ccl=$ds->data();
  my $rv=$self->read_ccl(\@ccl,"ANSYS CFX Solver Input File $mdeffile");
  _trace("Done reading Command Language dataset from definition file,",
         scalar(@ccl), "lines.");

  # detect "blank" datasets
  if (not @ccl or scalar(@ccl)==1 and not $ccl[0]) {
     _error("Empty CCL dataset in definition file $mdeffile");
     return '';
  }

  # Restore the mdeffile (in case it got changed on the read)
  $self->mdeffile($mdeffile);

  #
  # Update the CCL to the current version
  #
  # Maybe later...
  my $api=$self->ccl_api_obj();
  my $changes = $api->updateSimulationCCL();
  if ($changes) {
    my $cclupdatelogobj = $self->cclupdatelog();
    $cclupdatelogobj->append($changes);
  }
  
  # Pickup information from the last run
  # --> Simulation step
  # --> Any configurations that were supposed to but didn't get run
  my @simControl=GetDataSet($mdeffile,'G/SIM/CONTROL');
  if (defined $simControl[0]) {
    $self->{simstep} = $simControl[0][1];
  }
  @{$self->{activeConfigs}}=GetDataSet($mdeffile,'G/SIM/ACTIVECONFIGS');

  # Now cycle through and process configurations
  my @configs=GetDataSet($mdeffile, 'G/SIM/CCONFIG');

  # Reset the configmap; no longer need the single config object.
  if (defined $configs[0]) {
    $self->{configmap}={} ; 
  }
  else {
    _abort("The specified Solver Input File is not a multi-configuration case.  Please use the -def option instead of the -mdef option."); 
  }
  
  # Read the resinfo dataset
  my $haveresinfo=$self->{resinfo}->ReadFromFile($mdeffile);
  my %lastentry;
  if ($haveresinfo) {
    $self->{justRun}=();
    %lastentry=$self->{resinfo}->GetLast();
  }
  
  foreach my $i (@configs) {
    my $configname=join('', GetDataSet($mdeffile,"G/SIM/$i/NAME"));

    # Learn about this config from resinfo dataset    
    my %entry;
    if( $haveresinfo ){
      %entry=$self->{resinfo}->GetLast($configname);
      if (defined $entry{'SimStep'} and
          $entry{'SimStep'} == $self->{simstep}) { 
        push (@{$self->{justRun}}, $i) ; 
      }
    }

    # Signal use of alternate solver input file if this configuration ended
    # with a user requested stop file.
    if( $haveresinfo ) {
      if( defined $entry{ConfName} and 
          $entry{ConfName} eq $lastentry{ConfName} and
          $entry{ConfStep} == $lastentry{ConfStep} ) {
        return 0 unless CFX5::FileTests::datafile($entry{FilePath},
                                                  "ANSYS CFX Solver results file $entry{FilePath}");
        my @termconds=GetDataSet($entry{FilePath}, 'G/TERMCOND');
        foreach my $termcond (@termconds) {
          if ($termcond eq "Stop instruction received") {
            $self->configmap()->{$i}->{altSolverInputFile} = $entry{FilePath} ; 
          }
        }
      }
    }
        
    # Add configuration into the simulation's database
    my $configdef=join('', GetDataSet($mdeffile,"G/SIM/$i/DEFFILE"));
    my $configNextStep=1;
    $self->add_configuration($i,
                             $configNextStep,
                             $configname,
                             $configdef);
    $self->createConfig($i, 0);
    $self->initConfig($i, 0);
    
    # Get restart control information (if it exists) for the configuration
    # --> config. step repeat logic handled here too
    my @configControl=GetDataSet($mdeffile,"G/SIM/$i/CONTROL");
    if (defined $configControl[0]) {
      my $repeatStep = 0; 
      foreach my $a (@{$self->{activeConfigs}}) {
        if (defined $a and $i eq $a) {
          $repeatStep = 1;
          last; 
        }
      }
      $configNextStep = $configControl[0][1];
      unless ($repeatStep) {
        $configNextStep++ ; 
      }
      else {
        $configNextStep = ( $configNextStep == 0 ? 1: $configNextStep ); 
      }

      $self->add_configuration($i,
                               $configNextStep,
                               $configname,
                               $configdef);
    }
  }

  my @termcontrol;
  $api->getChildObjectList("SIMULATION CONTROL/TERMINATION CONTROL", \@termcontrol);
  foreach my $i (@termcontrol) {
    my $config=$self->read_ccl_parm("SIMULATION CONTROL/TERMINATION CONTROL/$i/Configuration Name");
    my $option=$self->read_ccl_parm("SIMULATION CONTROL/TERMINATION CONTROL/$i/Option");
    my $condname=$i;
    $condname=~s!^CONTROL CONDITION:!!g;
    $self->{termconds}{$condname}{'option'}=$option;
    $self->{termconds}{$condname}{'configname'}=$config;

    if( $option eq "Maximum Number of Configuration Steps" ){
      $self->{termconds}{$condname}{'maxsteps'}=$self->read_ccl_parm("SIMULATION CONTROL/TERMINATION CONTROL/$i/Maximum Number of Configuration Steps");
    }
    elsif ( $option eq "Solver Interrupt Conditions" ) {
      $self->{termconds}{$condname}{'condnames'}=$self->read_ccl_parm("SIMULATION CONTROL/TERMINATION CONTROL/$i/Condition Name List");
    }
    else {
      sim_error("Unrecognised termination control condition option: $option"); 
    }
  }
  return $rv;
}

sub ensure_ccl_rules_loaded {
  my $self=shift;

  if ($self->{cclapi}->{readRULES}) {
    return 1;
  }

  _trace("Reading CCL RULES ...");

  $self->{cclapi}->{readRULES}=1;

  my $api=$self->ccl_api_obj();

  my $rv;
  $rv=$api->readLibraries();
  if (defined $rv) {
    _abort("Error reading standard Command Language content definition:\n", $self->format_ccl_error($rv));
  }

  _trace("Done reading CCL RULES.");

  1;
}

=head3

 $sim->apply_ccl_auto($configccl{0}, $default_configccl_is_single);

 Apply CCL appropriately to single/multi configuration cases

 If the simulation has been set as single/multiconfig this setting takes precedence.
 Otherwise the single/multi status is determined from an input argument.
 Conflicting settings are not permitted.

 Single configuration:
  -> Apply to the configuration
  -> If determined as single from argument, set internally as single config.
  -> Set def file in config map to object def file
                         
 Multi configuration:
 -> Apply to the simulation

=cut

sub apply_ccl_auto
{
  my $self = shift;
  my $cclref = shift;
  my $spec_singleconfig = shift;
  
  if (@{$cclref})
  {
    # Simulation is internally defined as single/multi config
    if (defined $self->singleconfig())
    {
      if ($self->singleconfig())
      {
        my $conf_obj = $self->config_object();
        $conf_obj->read_ccl($cclref);
        $self->singleconfig_deffile($conf_obj->deffile()) if defined $conf_obj->deffile();
      }
      else
      {
        _error("Configuration CCL cannot be applied to a simulation") if $spec_singleconfig;
        $self->read_ccl($cclref);
      }
    }
    # Not defined: Determine if single/multi config from input argument
    else
    {
      if ($spec_singleconfig)
      {
        $self->singleconfig(1);
        my $conf_obj = $self->config_object();
        $conf_obj->read_ccl($cclref);
        $self->singleconfig_deffile($conf_obj->deffile()) if defined $conf_obj->deffile();
      }
      else
      {
        $self->read_ccl($cclref);
      }
    }
  }
}

=head3

  $sim->apply_ccl_config($configccl{$config}, $config);

  Apply CCL to a configuration (specified by name).

  - Apply CCL to a specific configuration.
  - Set cmdlineCCL in the config map to the specified CCL.
  - Update the reference CCL in case LIBRARY CCL was specified.

=cut

sub apply_ccl_config
{
  my $self = shift;
  my $cclref = shift;
  my $configname = shift;

  if (@{$cclref})
  {
    my $configid = $self->get_config_index($configname);
    _abort("Specified configuration does not exist: $configname\n") unless (defined $configid);
    $self->{configmap}{$configid}{0}{object}->read_ccl($cclref);
    $self->{configmap}{$configid}{cmdlineCCL} = $cclref; 
    $self->updateLibCCLRef($configid,0);
  }
}

#
# Recursive function to walk one cclapi (configuration) and create the same
# objects/params in another api (simulation), with some exceptions etc..
#
# Also, compare to an alternative path and report if they are identical.
#

sub _createSimExecCCL {
  my $self=shift;
  my $api=shift; # Source api
  my $apipath=shift; # Source root
  my $tmpapi=shift; # Dest api
  my $tmpapipath=shift; # Dest root
  my $altpath=shift; # Alternative path in dest
  my $nodiff=shift; # Internal variable for recursion.

  $nodiff=1 unless defined $nodiff; # Intially there are no difference.

  # Get list of children in source api.
  my ($childlist, $grouplist);
  $api->getChildList($apipath, \$childlist, \$grouplist);

  foreach my $child (@$childlist) {
    # Three types of children:
    #   Parameters    - just create them
    #   Singletons    - create the object then call recursively
    #                   to build that subtree
    #   Named Objects - same as singleton but the path is type:name
    unless($child->{className} eq "Param"){
      my $name;
      if( $child->{className} eq "Object" ) {
        $name=$child->{type}.":".$child->{name};
      }
      else {
        $name=$child->{name};
      }

      # Need some exceptions. There isn't a 1:1 mapping of configuration ccl
      # to simulation ccl

      # Skip PARALLEL HOST LIBRARY and START METHOD LIBRARY
      next if "$apipath/".$name eq "/SIMULATION CONTROL/EXECUTION CONTROL/PARALLEL HOST LIBRARY";
      next if "$apipath/".$name eq "/SIMULATION CONTROL/EXECUTION CONTROL/START METHOD LIBRARY";

      # Store original target paths, restore later
      my $origtmpapipath=$tmpapipath;
      my $origaltpath=$altpath;

      # Special handling for RUN DEFINITION
      # --> Move RUN DEFINITION/INITIAL VALUES SPECIFICATION to INITIAL VALUES SPECIFICATION
      # --> Don't create RUN DEFINITION target
      if( "$apipath/".$name eq "/SIMULATION CONTROL/EXECUTION CONTROL/RUN DEFINITION/INITIAL VALUES SPECIFICATION" ){
        $tmpapipath=~s!/RUN DEFINITION$!/!; 
        $altpath=~s!/RUN DEFINITION$!/!; 
      }

      # Create the target object.
      $tmpapi->createObject("$tmpapipath/".$name)
        unless $tmpapi->havePath("$tmpapipath/".$name) or $name eq "RUN DEFINITION";

      # Should all be ok here, call recursively in the subtree
      my $diff=$self->_createSimExecCCL($api, "$apipath/".$name, $tmpapi, "$tmpapipath/".$name, "$altpath/".$name, $nodiff);

      $nodiff=$nodiff && $diff;

      # Restore paths for next sibling
      $tmpapipath=$origtmpapipath; 
      $altpath=$origaltpath;
    }
    else {
      # If it's a parameter just set it in the tmpapi

      # Don't want any params in RUN DEFINITION, just objects
      next if "$apipath" eq "/SIMULATION CONTROL/EXECUTION CONTROL/RUN DEFINITION";

      my ($val, $altval);
      $api->getStringValue("$apipath/".$child->{name}, \$val);
      my $rv=$tmpapi->getStringValue("$altpath/".$child->{name}, \$altval);
      $nodiff=$nodiff && (defined $altval && $val eq $altval);
      $rv=$tmpapi->createParam("$tmpapipath/".$child->{name}, $val);
      die $rv." $tmpapipath/".$child->{name} if defined $rv;
    }
  }
  
  # If the values set are already set in the altpath return true, else false.
  # If any call in the recursion returns false then the overall result is false.
  return $nodiff;
}

#
# Recursive function to walk one cclapi (simulation) and create the same
# objects/params in another api (configuration), with some exceptions etc.. 
#

sub _createConfigExecCCL {
  my $self=shift;
  my $api=shift; # Source API
  my $apipath=shift; # Source API root
  my $tmpapi=shift; # Dest API
  my $tmpapipath=shift; # Dest API root

  # Get a list of children in the current api path.
  my ($childlist, $grouplist);
  my $rv=$api->getChildList($apipath, \$childlist, \$grouplist);
  die "$rv $apipath" if defined $rv;

  foreach my $child (@$childlist) {
    # Three types of children:
    #   Parameters    - just create them
    #   Singletons    - create the object then call recursively 
    #                   to build that subtree
    #   Named Objects - same as singleton but the path is type:name
    unless($child->{className} eq "Param"){
      my $name;
      if( $child->{className} eq "Object" ) {
        $name=$child->{type}.":".$child->{name};
      } 
      else {
        $name=$child->{name};
      }

      # Need some exceptions. There isn't a 1:1 mapping of simulation ccl
      # to configuration ccl

      # Skip the RUN DEFINITION tree from the mdef file
      next if "$apipath/".$name eq "/SIMULATION CONTROL/EXECUTION CONTROL/RUN DEFINITION";

      # Move INITIAL VALUES SPECIFICATION to RUN DEFINITION/INITIAL VALUES SPECIFICATION
      # Store original path, restore it later.
      my $origtmpapipath=$tmpapipath;
      if( "$tmpapipath/".$name eq "/SIMULATION CONTROL/EXECUTION CONTROL/INITIAL VALUES SPECIFICATION" ){
        $tmpapipath="$tmpapipath/RUN DEFINITION";
        $tmpapi->createObject($tmpapipath);
      }
      $rv=$tmpapi->createObject("$tmpapipath/$name");
      die $rv if defined $rv;

      # Should all be ok here, call recursively in the subtree
      $self->_createConfigExecCCL($api, "$apipath/".$name, $tmpapi, "$tmpapipath/".$name);

      # Restore original path for the next sibling. 
      $tmpapipath=$origtmpapipath; 
    }
    else {
      # If it's a parameter just set it in the tmpapi
      my $val;
      $api->getStringValue("$apipath/".$child->{name}, \$val);
      $rv=$tmpapi->createParam("$tmpapipath/".$child->{name}, $val);
      die $rv." $tmpapipath/".$child->{name} if defined $rv;
    }
  }
}

#
# Generate configuration execution control ccl from simulation ccl
#

sub updateConfigExecCCL {
  my $self=shift;
  my $configid=shift;
  my $map=$self->configmap();
  my $name=$map->{$configid}{name};

  # Return if there is no simulation ccl
  undef unless $self->have_ccl_api_obj();
 
  my $api = $self->ccl_api_obj();

  # Create a temporary api object to store the configuration ccl
  my $tmpapi = CFX5::CCLI::new_api_object();
  # Have to initialise it with something
  my @ccl=("SIMULATION CONTROL:", "  EXECUTION CONTROL:", "  END", "END");
  $tmpapi->parseCCLData(\@ccl);

  # If config specific exec control exists then use it, otherwise use the 
  # global exec control.
  my $CECPath = "/SIMULATION CONTROL/CONFIGURATION CONTROL/CONFIGURATION: $name/CONFIGURATION EXECUTION CONTROL" ;
  my $ECPath  = "/SIMULATION CONTROL/EXECUTION CONTROL" ;
  if($api->havePath($CECPath)){
    $self->_createConfigExecCCL($api,    $CECPath,
                                $tmpapi, $ECPath);
  }
  elsif($self->have_ccl_path($ECPath)){
    $self->_createConfigExecCCL($api, $ECPath,
                                $tmpapi, $ECPath);
  }

  # Get the ccl text back from the api and return it.
  $tmpapi->getFormattedCCL("/", 78, \@ccl);
  return \@ccl if @ccl;
  return undef;
}

#
# Generate simulation execution control ccl from configuration ccl
#

sub updateSimExecCCL {
  my $self=shift;

  my $api=$self->ccl_api_obj();
  my $map=$self->configmap();

  foreach my $i (keys %{$map}){
    my $name=$map->{$i}{name};

    # Delete and recreate the existing config exec control 
    $api->deleteObj( "/SIMULATION CONTROL/CONFIGURATION CONTROL/CONFIGURATION: $name/CONFIGURATION EXECUTION CONTROL");
    $api->createObject( "/SIMULATION CONTROL/CONFIGURATION CONTROL/CONFIGURATION: $name/CONFIGURATION EXECUTION CONTROL");

    # Generate new config exec control
    my $nodiff=$self->_createSimExecCCL($map->{$i}->{0}{object}->ccl_api_obj(),
                                        "/SIMULATION CONTROL/EXECUTION CONTROL",
                                        $api,
                                        "/SIMULATION CONTROL/CONFIGURATION CONTROL/CONFIGURATION: $name/CONFIGURATION EXECUTION CONTROL",
                                        "/SIMULATION CONTROL/EXECUTION CONTROL");
    # If same as global exec control we don't need it, remove it.
    if( $nodiff ){
      $api->deleteObj( "/SIMULATION CONTROL/CONFIGURATION CONTROL/CONFIGURATION: $name/CONFIGURATION EXECUTION CONTROL");
    }
  }
}

sub get_config_index {
  my $self = shift;
  my $name = shift;

  return $self->{configIDFromName}{$name}; 
}

#
# Add definitional information about a configuration to the simulation's configuration map.  
# --> the configuration (job) objects will extract their settings from these values
sub add_configuration {
  my $self=shift;
  my $configid=shift;
  my $configstep=shift;
  my $configname=shift;
  my $configdef=shift;

  $self->{configmap}{$configid}{name}=$configname;
  $self->{configmap}{$configid}{def}=$configdef if defined $configdef;
  $self->{configmap}{$configid}{activeCond}=();
  $self->{configmap}{$configid}{nextstep}=$configstep;
  $self->{configmap}{$configid}{initstep}=$configstep-1;
  $self->{configIDFromName}{$configname} = $configid; 
  
  if(not $self->singleconfig()){
    my $cclpath="SIMULATION CONTROL/CONFIGURATION CONTROL/CONFIGURATION:$configname";
  }
}

=head3

  $config = $sim->config_object();
  $config = $sim->config_object(1,0);
  
  Return a configuration object for a specified config ID and step.
  For single config cases, the config ID and step are not required.

=cut

sub config_object
{
  my $self = shift;
  my $configid = shift;
  my $configstep = shift;

  unless (defined $configid)
  {
    _abort("Config ID unspecified in multiconfig simulation") unless ($self->singleconfig());
    $configid = 1;
  }

  unless (defined $configstep)
  {
    _abort("Config step unspecified in multiconfig simulation") unless ($self->singleconfig());
    $configstep = 0;
  }

  return $self->{configmap}{$configid}{$configstep}{object};
}

=head3

  $def = $sim->singleconfig_deffile( );
  sim->singleconfig_deffile('flow.def');

  For a single config ID, get/set the def file in the configuration map

=cut

sub singleconfig_deffile
{
  my $self = shift;

  if (@_) {
    _abort("Cannot set single-config def file unless simulation defined as single-config") unless ($self->singleconfig());
    $self->{configmap}{1}{def} = shift;
  }

  return $self->singleconfig() ? $self->{configmap}{1}{def} : undef;
}


sub set_config_option {
  my $self=shift;
  my $configname=shift;
  my $option=shift;
  my $value=shift;

  my $map=$self->configmap();

  foreach my $i (keys %{$map}){
    # If $configname is set then only set the option for a single config
    # --> Note some options can be multiply specified (values stored as array)
    if (defined $configname and $configname) {
      unless (exists $self->{configIDFromName}{$configname}) {
        _abort("Specified configuration does not exist: $configname\n");
      }
      if ($option eq '-initial-file' or
          $option eq '-initial-configuration') {
        push(@{$map->{$i}->{options}{$option}},$value) if $map->{$i}->{name} eq $configname;
      }
      else {
        if ($map->{$i}->{name} eq $configname) {
          $map->{$i}->{options}{$option}=$value ;
          $self->clear_conflicting_options($i,$option) ;
        }
      }
    }
    # Otherwise set it for every config
    else {
      if ($option eq '-initial-file' or
          $option eq '-initial-configuration' ) {
        push (@{$map->{$i}->{options}{$option}},$value) ; 
      }
      else {
        $map->{$i}->{options}{$option}=$value;
      }
    }
  }
}

# Check if a particular option already exists
sub have_config_option {
  my $self=shift;
  my $configname=shift;
  my $option=shift;

  my $map=$self->configmap();

  foreach my $i (keys %{$map}){
    # $configname is set: Check in specified configuration
    if (defined $configname and $configname) {
      if ($map->{$i}->{name} eq $configname) {
        return 1 if (exists $map->{$i}->{options}{$option});
      }
    }
    # $configname unset: Check in all configurations
    else {
      return 1 if (exists $map->{$i}->{options}{$option});
    }
  }

  return 0;
}

# Validate combination of options for a given configuration
sub validate_options {
  my $self=shift;
  my $config=shift;

  # General check for invalid option combinations
  my %invalid_options = (
        "-interpolate-iv" => ["-use-mesh-from-iv"],
      );

  my $map=$self->configmap();

  for my $option (keys %invalid_options) {
    if (exists $map->{$config}{options}{$option}) {
      for my $invoption (@{$invalid_options{$option}}) {
        if (exists $map->{$config}{options}{$invoption}) {
          _error("Options $option and $invoption (or their aliases) must not both be specified.");
        }
      }
    }
  }

  # Check if -parfile-read is used with any other partitioning option
  if (exists $map->{$config}{options}{'-parfile-read'})
  {
    my @invalid_options = qw(-part -part-only -part-coupled -part-independent -part-mode);
    for my $invoption (@invalid_options)
    {
      if (exists $map->{$config}{options}{$invoption})
      {
        _error("Option -parfile-read cannot be used with $invoption or any other partitioning control option. Partitioning information is already defined in the specified partition file.");
      }
    }
  }

  # Check for invalid options for indirect start methods
  my $step = 0;
  my $job = $map->{$config}->{$step}{object};
  if ($job->start_method_obj->is_indirect())
  {
    my @invalid_options = qw(-solver -partitioner -interpolator -part-only);
    for my $invoption (@invalid_options)
    {
      if (exists $map->{$config}{options}{$invoption})
      {
        _error("Option: $invoption (or its alias) cannot be used with an indirect start method.");
      }
    }
  }

  return 0;
}

# Config specfic option specficiations may need to clear previously set
# global/simulation level options
# (e.g. config specific serial must clear global parallel settings)
sub clear_conflicting_options {
  my $self=shift; 
  my $config=shift;
  my $option=shift;

  my %conflicts = (
        "-serial" => ["-par-local","-part","-part-only","-"],
        "-part" => ["-serial"], 
        "-par-local" => ["-serial"],
        "-single" => ["-double"],
        "-double" => ["-single"],
        "-ccl2flow-single" => ["-ccl2flow-double"],
        "-ccl2flow-double" => ["-ccl2flow-single"],
        "-cclsetup-single" => ["-cclsetup-double"],
        "-cclsetup-double" => ["-cclsetup-single"],
        "-part-single" => ["-part-double"],
        "-part-double" => ["-part-single"],
        "-interp-single" => ["-interp-double"],
        "-interp-double" => ["-interp-single"],
        "-solver-single" => ["-solver-double"],
        "-solver-double" => ["-solver-single"]
      );
  
  return unless defined $conflicts{$option} ;

  my $map=$self->configmap();
  
  foreach my $delOpt (@{$conflicts{$option}}) {
    delete $map->{$config}{options}{$delOpt} ; 
  }
}

sub apply_config_options {
  my $self=shift;
  my $config=shift;
  my $step=shift;

  my $map=$self->configmap();
  my $job=$map->{$config}->{$step}{object};

  use CFX5::Simulation::OptionHandling;

  # Initial update of global attributes from environment variable settings
  my $exec_type = defined $Config{CFX5_ARCH_TYPE} ? $Config{CFX5_ARCH_TYPE} : '';
  $job->global_exec_set_attr($exec_type);

  my $i = 1;
  my %bring_forward = map { $_ => $i++ } qw(-par
                                            -par-local
                                            -large
                                            -single
                                            -double
			                    -part-large
                                            -solver-large
			                    -solver-single
                                            -solver-double
                                            -interp-large
			                    -interp-single
			                    -interp-double);
  my (@b4, %b4, @norm);
  my @opts=keys(%{$map->{$config}{options}});
  while(@opts){
    my $opt=shift @opts;
    if ($bring_forward{$opt}) {
      $b4{$bring_forward{$opt}}=$opt;
    }
    # It does not need to be ordered, put it in the list that is appended
    # after the ordered options 
    else {
      push @norm, $opt;
    }
  }
  # Push the ordered options for this config back opts
  # Sort the b4 hash keys (which are the bring_forward values)
  # Map this back to the b4 values (which are the actual options)
  # An option's arg will be the bring_forward value of the option + 1
  push @opts, (map ( $b4{$_}, (sort {$a <=> $b} (keys %b4) ) ),@norm);

  foreach my $option (@opts){
    my $value=$map->{$config}{options}{$option};
    CFX5::Simulation::OptionHandling::handle($job, $option, $value);
  }
  
  return 1;
}

# Finalise configuration CCL before calling the run

sub finalise_config_ccl {
  my $self = shift;
  my $config = shift; 
  my $step   = shift;
  
  my $map=$self->configmap();
  my $api ;
  if ($self->singleconfig()) {
    $api = $map->{$config}->{$step}->{object}->ccl_api_obj();
    my $thisConfig = $map->{$config}->{$step}->{object} ;
    my $CCLConfigName = $thisConfig->configname() ;
    $map->{$config}->{name} = $CCLConfigName if defined $CCLConfigName ;
    $thisConfig->cplg_name() if $thisConfig->is_mfx_run() and $thisConfig->start_cfx();
  } else {
    $api = $self->ccl_api_obj();
  }

  # Simply return if no configuration is defined in the API
  my $configName = $map->{$config}->{name} ;
  my $configCCLPath = "/SIMULATION CONTROL/CONFIGURATION CONTROL/CONFIGURATION: $configName";
  return 1 unless $api->havePath($configCCLPath);
    
  # Apply (i.e. duplicate) configuration CCL from simulation API to
  # configuration specific API
  my $confApi = $api ; 
  unless ($self->singleconfig()) {
    my @configCCL ;
    push @configCCL, "SIMULATION CONTROL:" ;
    push @configCCL, "  CONFIGURATION CONTROL:" ;
    push @configCCL, "    CONFIGURATION: $configName" ;
    push @configCCL, $self->get_current_ccl("$configCCLPath"); ;
    push @configCCL, "    END" ;
    push @configCCL, "  END" ;
    push @configCCL, "END" ;
    $confApi = $map->{$config}->{$step}->{object}->ccl_api_obj();
    my $rv=$confApi->parseCCLData(\@configCCL, '');
    if (defined $rv) {
      _error("Failed to apply remesh definition to config.: $configName\n", 
             $self->format_ccl_error($rv));
      return 0;
    }      
  }
  
  # Finally remove anything we don't want
  # --> configuration execution control: was promoted to execution control already
  # --> activation control: user doesn't need to see this again
  # --> although we may already have removed initial values when reading the config def file,
  #     these settings may have been re-injected from the simluation level execution control
  #     so... we should remove them again if they need to be removed.
  $confApi->deleteObj("$configCCLPath/CONFIGURATION EXECUTION CONTROL") ;
  $confApi->deleteObj("$configCCLPath/ACTIVATION CONTROL") ;
  $confApi->deleteObj("/SIMULATION CONTROL/EXECUTION CONTROL/RUN DEFINITION/INITIAL VALUES SPECIFICATION")
    if (defined $map->{$config}->{altSolverInputFile});

  1 ;  
}

#
# Update simluation control CCL in both the configuration and simulation
# definitions
#
sub updateSimControlCCL {
  my $self=shift;

  my $step=0 ;
  my $map=$self->configmap();

  # Complete execution control for each configuration
  # --> update the configuration's execution control (scatter CCL from sim to config)
  # --> process command line options
  foreach my $config (keys %{$self->{configmap}}){
    $map->{$config}{execccl}=$self->updateConfigExecCCL($config) ;
    if (defined $map->{$config}{execccl}) {
      my $job=$map->{$config}->{$step}{object} ;
      $job->read_ccl(\@{$map->{$config}{execccl}}) ; 
    }
    $self->apply_config_options($config, $step);
  }
  
  return 1 if $self->singleconfig() ;
  
  # Now gather any config specific CCL back up to the simulation level
  # --> may have come from command line options or command line provided CCL
  $self->updateSimExecCCL();

  1; 
}

#
# Check CCL and options for the current simulation
#
sub Check {
  my $self=shift;
  
  my $rv;
  if( $self->singleconfig() ){
    $rv=$self->{configmap}{1}{0}{object}->Check();
    $self->validate_options(1) if ($rv);
  }
  else {
    $rv=1;
    foreach my $i (keys %{$self->{configmap}}){
      $rv = $rv && $self->{configmap}{$i}{0}{object}->Check();
      $self->validate_options($i);
    }
  }
  
  $rv;
}


sub read_ccl_parm {
  my $self=shift;
  my $cp=shift;
  my $c;
  
  my ($api,$haveparm);
  if ($self->have_ccl_api_obj()) {
    $api=$self->ccl_api_obj();
    if ($api->haveParameter("$cp")) {
      $haveparm=1;
    }
  }

  if (!$self->_setting_status($cp, 'isdefault') and $haveparm) {
    # there is a setting in the database, and it is not a default value
    my $rv=$api->getStringValue("$cp",\$c);
    if (defined $rv) {
      _error("Error getting $cp from Command Language database:\n",
	           $self->format_ccl_error($rv));
    }
  } else {
    # there may be a setting in the database, but if there is it's a
    # default.  If not, we want to make one anyway, so long as the
    # default is defined.
    my $defcall=$self->_setting_info($cp,'default');
    return undef unless ($defcall);
    my $def=$self->$defcall();
    return undef unless defined $def;

    # if we get here, we do have a default, so record it in the CCL
    # database
    $self->set_ccl_parm_int($cp,$def);
    # and record the fact that what is in the CCL database is a
    # default and should be ignored and recalculated if necessary
    $self->_setting_status($cp, isdefault => 1);
    # now we're done
    return $def;
  }

  $c =~ s!\\\#!\#!g;

  $c;
}

sub set_ccl_parm {
  my $self=shift;
  my $opts={};
  $opts=shift if (ref $_[0] eq 'HASH');
  my $cp=shift;
  my $c=shift;

  $self->set_ccl_parm_int($opts, $cp,$c);

  $self->_setting_status($cp, isdefault => 0);

  $c;
}

sub set_ccl_parm_int {
  my $self=shift;
  my $api=$self->ccl_api_obj();
  my $opts=(ref $_[0] eq 'HASH' ? shift : {});
  my $cp=shift;
  my $c=shift;

  unless (defined $c) {
    internal_error("Cannot set CCL parameter \"$cp\" to undefined value");
  }

  $c =~ s!\#!\\\#!g;

  if ($api->haveParameter($cp)) {
    # if we already have it, we can just set it like this, as it is
    # more efficient (?)
    my $rv=$api->setStringValue("$cp",$c);
    if (defined $rv) {
      _error("Error setting $cp in Command Language database:\n",
             $self->format_ccl_error($rv));
    }
  } else {
    # we construct a snippet of CCL which we can send to the API to
    # parse, as setStringValue does not allow creation of objects.
    my @path=split(m!/!,$cp);
    shift @path; # remove leading blank
    my $pname=pop @path;
    my @ccl;
    for my $n (@path) {
      if ($n =~ m/:/) {
        push @ccl , "$n";
      } else {
        push @ccl, "$n:";
      }
    }
    push @ccl , "$pname = $c";
    for my $n (@path) {
      push @ccl, "END";
    }
    my $rv=$api->parseCCLData(\@ccl,($opts->{library} ? 1 : 0));
    if (defined $rv) {
      _error("Error setting $cp in Command Language database:\n",
             $self->format_ccl_error($rv));
    }
  }

  $c;
}

sub delete_ccl_parm {
  my $self=shift;
  my $opts={};
  $opts=shift if (ref $_[0] eq 'HASH');
  my $cp=shift;

  my $api=$self->ccl_api_obj();
  my $rv=$api->deleteParam($cp);
  if (defined $rv) {
    _error("Error deleting $cp from Command Language database:\n",
           $self->format_ccl_error($rv));
    return '';
  }
  1;
}

sub delete_ccl_obj {
  my $self=shift;
  my $opts={};
  $opts=shift if (ref $_[0] eq 'HASH');
  my $cp=shift;

  # Replace FLOW with the flow object name, default to FLOW singleton
  if( defined $self->{settings}->{flow_name} ){
    $cp=~s!^/FLOW/!/$self->{settings}->{flow_name}/!g;
  }

  _debug("Removing ccl object");
  my $api=$self->ccl_api_obj();
  
  return 1 unless ($api->haveObject($cp)); 
  
  my $rv=$api->deleteObj($cp);
  if (defined $rv) {
    _error("Error deleting $cp from Command Language database:\n",
           $self->format_ccl_error($rv));
    return '';
  }
  1;
}

sub have_ccl_path {
  my $self=shift;
  my $opts={};
  $opts=shift if (ref $_[0] eq 'HASH');
  my $cp=shift;

  my $api=$self->ccl_api_obj();
  return $api->havePath($cp);
}
  

sub have_ccl_parm {
  my $self=shift;
  my $opts={};
  $opts=shift if (ref $_[0] eq 'HASH');
  my $cp=shift;

  my $api=$self->ccl_api_obj();
  return $api->haveParameter($cp);
}

sub get_current_ccl {
  my $self=shift;
  my $path=shift;
  $path='/' unless defined $path;

  my @ccl;
  # we're allowed 79 chars for writecmds/ccl2flow/etc, but the entire
  # output file is indented by a single space, so we limit it to 78 to
  # avoid line wrapping in the output file.
  my $rv=$self->ccl_api_obj()->getFormattedCCL($path, 78, \@ccl);
  if (defined $rv) {
    _error("Error listing current contents of the Command Language database:\n",
           $self->format_ccl_error($rv));
  }
  @ccl;
}



#
################################################################################
#
#  SIMULATION TEXT OUT FILE METHODS
#
################################################################################
#

sub start_outfile {
  my $self = shift;

  $self->{outfile} = {} unless exists $self->{outfile};
  if( $self->{outfile}->{ended} ) {
    internal_error( "Attempt to re-activate an ended output file" );
    return 0;
  }
  if( $self->{outfile}->{started} ) {
    return 1;
  }

  my $outfile=$self->outfile();
  _check_defined( $outfile, "output file" ) or return 0;
  CFX5::FileTests::datafile_w( $outfile, "output file $outfile" ) or return 0;
  
  my $object = new CFX5::Output::OutFile( $outfile );
  my @outlist=split(/ /, "abort simerror simwarn siminfo simnote trace debug simprint internal_error");
  add_output_object( $object, \@outlist);
  $self->outobj( $object );

  # Show time, date, user, host, architecture and command used
  my $tdstamp = timedatestamp();
  my $text = "This run of the CFX $Config{CFXREVISION} Solver started $tdstamp\n".
    "by user $Config{CFX_USER} on $Config{CFX_HOST}".
      " ($Config{CFX5_HOST_ARCH})";
  if (defined $self->text_command()) {
    if (ref $self->text_command() eq 'ARRAY') {
      my $col=0;
      my $width=70;
      my $margin='   ';
      $text .= " using the command:\n\n ";
      for my $l (@{$self->text_command()}) {
        $l='"'.$l.'"' if ($l =~ m/\s/);
        if ($col + length($l) > $width and $col != 0) {
          $text .= "\n".$margin;
          $col=0;
        }
        $text .= $l . ' ';
        $col += length($l);
      }
      $text .= "\n\n";
    } else {
      $text .= " using the command:\n\n" . $self->text_command() . "\n\n";
    }
  } else {
    $text .= ".\n\n";
  }

  # Show point release and patch information
  my $patchinfo = CFX5::PatchInfo->new();
  if ($patchinfo->is_nonempty())
  {
    my @patchtext = $patchinfo->get();
    $text .= sprintf "%s\n\n", join("\n ", @patchtext);
  }

  $text .= "Setting up CFX Solver run ...\n\n";
  sim_print($text);

  $self->{outfile}->{started}=1;

  # now we've got an output file to communicate via, shut down the
  # channel to the SM if we have one.
  $self->_CloseStdoutComms();

  # this is how we find it
  setconf('CFX_SIM_OUTPUT_FILE' => fullpath($outfile));
  export_conf('CFX_SIM_OUTPUT_FILE');

  # yay, we're done!
  1;
}

=head3 _CloseStdoutComms

If we have a CCL channel on stdout to communicate with the Solver
Manager, this sends some run details back and closes it.  In any case,
it starts copying errors to a gui-err.txt file.

=cut

sub _CloseStdoutComms {
  my $self=shift;

  my $trundir=$self->rundir();
  _check_defined $trundir, "working directory" or return 0;
  # no test, we just hope
  # the following will create the directory.
  my $rundir = $self->rundir_obj();
  return 0 unless (defined $rundir);

  # create a file in the run directory to mirror the error messages
  # into, and if we've got a comms channel on stdout, close it.
  my $f=$rundir->creatable_file('gui-err.txt');
  $rundir->mark_output_file('gui-err.txt');
  my $cclredir=CFX5::Output::CCL->new($f);

  # set up the name for the GUI INFORMAITON objects
  (my $name=basename($self->fullname())) =~ s![-_]! !g;
  $name="Run $name";
  $cclredir->gui_objname($name);

  add_output_object($cclredir);
  $self->outobjccl($cclredir);

  # we send the same message both to the comms channel (if we have
  # one) and the text file.  The file first, to reduce races.
  my @msg=(
           "GUI INFORMATION:",
           "  RUN SETUP INFORMATION:",
           "    Run Name = ".basename($self->fullname()),
           "    Object Name = $name",
           "    Multiconfig = True", 
           "    Full Run Prefix = ".normalize_filename($self->fullname()),
           "    Working Directory = ".normalize_filename($self->rundir()),
           "    Output File = ".normalize_filename($self->outfile()),
          );
  if (defined $self->mdeffile()) {
    push @msg, "    Definition File = ".normalize_filename($self->mdeffile());
  }
  push @msg, (
              "    Messages File = $f",
              "  END",
              "END",
             );

  if (my $fh=$cclredir->open()) {
    print $fh join("\n",@msg),"\n";
    $cclredir->close();
  }

  # now we've got somewhere else for the messages to go, send back
  # some run information and shut down the channel.
  my $comms=CFX5::Output::CCL::get_ccl_comms();
  if ($comms) {
    # got a channel
    if (my $fh=$comms->open()) {
      print $fh (join("\n",@msg,''));
      $comms->close();
    }
    CFX5::Output::CCL::close_ccl_comms();
  }

  # Check for file comms
  my $fcomms = CFX5::Output::CCL::get_ccl_file_comms();
  if ( $fcomms ) {
    # got a channel
    if (my $fh=$fcomms->open()) {
      print $fh (join("\n",@msg,''));
      $fcomms->close();
    }
    CFX5::Output::CCL::close_ccl_file_comms();
  }

  1;
}



=head4 OutFileHeader

  $self->OutFileHeader('ANSYS CFX Solver');

Write a heading into the output file, to delimit runs of various
things.  This function will call C<StartOutFile> if necessary.

=cut

sub OutFileHeader {
  my $self=shift;
  my @header=@_;

  $self->start_outfile() or return 0;
  my $fh=$self->outobj()->open();
  unless ($fh) {
    _error("Cannot write to output file", $self->outfile().": $!");
    return 0;
  }

  my $width=72;
  print $fh "\n\n +", "-" x ($width-4), "+\n";
  print $fh " |", " " x ($width-4), "|\n";
  my ($pad1, $pad2); 
  foreach my $ll (@header) {
    $pad1=int(($width-4-length($ll))/2);
    $pad2=($width-4-$pad1-length($ll));
    print $fh " |", " " x $pad1, $ll, " " x $pad2, "|\n";
  }
  print $fh " |", " " x ($width-4), "|\n";
  print $fh " +", "-" x ($width-4), "+\n\n";

  unless ($self->outobj()->close()) {
    _error("Cannot finish write to output file", $self->outfile().": $!");
    return 0;
  }
  1;
}

sub outfile_simstep {
  my $self=shift; 
  my $astep=shift; 
  my $rstep=shift; 

  my $fh=$self->outobj()->open();
  unless ($fh) {
    _error("Cannot write to output file", $self->outfile().": $!");
    return 0;
  }

  my $step_txt; 
  if ($rstep == $astep) {
    $step_txt = sprintf("     %3s", $astep); 
  }
  else {
    $step_txt = sprintf("%3s (%3s)", $astep,$rstep);
  }

  my $width=72;
  print $fh "\n ", "=" x ($width-2), "\n" ; 
  print $fh " SIMULATION STEP = ", $step_txt, "\n";
  print $fh " ", "-" x ($width-2), "\n" ; 
  print $fh " |  Config.  | Configuration Name                                     |\n";
  print $fh " |   Step    | * Execution Details                                    |\n";
  print $fh " +-----------+", "-" x ($width-16),"+\n";
  
  unless ($self->outobj()->close()) {
    _error("Cannot finish write to output file", $self->outfile().": $!");
    return 0;
  }
  1;
}

sub OutFileHeader1 {
  my $self=shift; 
  my $text=shift; 

  my $fh=$self->outobj()->open();
  unless ($fh) {
    _error("Cannot write to output file", $self->outfile().": $!");
    return 0;
  }

  my $width=72;
  print $fh "\n ", "=" x ($width-2), "\n" ; 
  print $fh " ", $text, "\n";
  print $fh " ", "-" x ($width-2), "\n" ; 
  
  unless ($self->outobj()->close()) {
    _error("Cannot finish write to output file", $self->outfile().": $!");
    return 0;
  }
  1;
}

sub outfile_confStep {
  my $self=shift; 
  my $astep=shift; 
  my $rstep=shift; 
  my $confName=shift; 

  my $fh=$self->outobj()->open();
  unless ($fh) {
    _error("Cannot write to output file", $self->outfile().": $!");
    return 0;
  }

  my $step_txt; 
  if ($rstep eq $astep) {
    if ($astep eq '') {
      $step_txt = sprintf("         "); 
    } else
    {
      $step_txt = sprintf("     %3s ", $astep); 
    }
  }
  else {
    $step_txt = sprintf("%3s (%3s)", $astep,$rstep);
  }

  my $width=72;
  my $fmt = "%-$width"."s" ;
  my $text; 
  $text = sprintf(" | %s | %s", $step_txt, $confName); 
  $text = sprintf("%-70s|\n",$text); 
  print $fh $text;

  unless ($self->outobj()->close()) {
    _error("Cannot finish write to output file", $self->outfile().": $!");
    return 0;
  }
  1;
}

sub outfile_confStep_break {
  my $self=shift; 
  
  my $fh=$self->outobj()->open();
  unless ($fh) {
    _error("Cannot write to output file", $self->outfile().": $!");
    return 0;
  }

  my $width=72;
  print $fh " +-----------+", "-" x ($width-16),"+\n";

  unless ($self->outobj()->close()) {
    _error("Cannot finish write to output file", $self->outfile().": $!");
    return 0;
  }
  1;
}

sub end_outfile {
  my $self=shift;

  $self->{outfile} = {} unless exists $self->{outfile};
  unless ($self->{outfile}->{started}) {
    # oops, can't start again!
    _error("Attempt to end an ended output file");
    return 0;
  }
  return 1 if $self->{outfile}->{ended};

  my $outfile=$self->outfile();
  _check_defined($outfile, "output file") or return 0;
  CFX5::FileTests::datafile_w($outfile, "output file $outfile") or return 0;

  if ($self->ownoutfile() and defined (my $outobj=$self->outobj())) {
    my $fh=$outobj->open();
    unless ($fh) {
      _error("Cannot write to output file $outfile: $!");
      return 0;
    }
    print $fh "\nThis run of the ANSYS CFX Solver has finished.\n";
    unless ($outobj->close()) {
      _error("Cannot finish write to output file $outfile: $!");
      return 0;
    }

    remove_output_object($outobj,undef);
  }

  $self->{outfile}->{ended}=1;
  $self->outobj(undef);

  # yay, we're done!
  1;
}


#
################################################################################
#
#  Routines for main simulation execution loop
#
################################################################################
#

sub run {
  my $self=shift;

  # Start the 'as a Server' (AAS) server, if requested
  if ($self->start_aas()) {
    CFX5::Simulation::AAS->start($self);
  }

  #
  # Single Config
  # --> Just run and leave
  #
  
  if($self->singleconfig()){
    $self->{configmap}->{1}->{0}->{object}->text_command($self->text_command());
    $self->runConfig(1,0);
    return 1;
  }

  #
  # Multi Config
  # --> More work to do...
  #
  
  my $map=$self->configmap();
  
  my $cclupdatelogobj = $self->cclupdatelog();
  if ($cclupdatelogobj->have_data()) {
    $self->OutFileHeader('CFX Command Language Upgrade');
    while (my $text = $cclupdatelogobj->get_next()) {
      _trace('Individual CCL Upgrade for Simulation');
      $self->outobj->print($text);
    }
  }  

  $self->UnpackConfigDefFiles(); 

  # Generate initial output and simulation header
  sim_print("\n");
  $self->OutFileHeader("CFX Command Language for Run");
  sim_print("\n");
  foreach my $l ($self->get_current_ccl()) {
    sim_print($l) ;
  }

  my @header ; 
  push (@header, "Simulation Execution"); 
  if (defined $ENV{CFX_LOCAL_PERLLIB}) {
    push (@header, "  "); 
    push (@header, "PERLLIB :  $ENV{CFX_LOCAL_PERLLIB}"); 
  }
  $self->OutFileHeader(@header);
  
  #
  # Preprocessing
  #

  # Any over-rides in case we're just doing pre-processing
  if ($self->norun()) {
    @{$self->{activeConfigs}} = () ;
    foreach my $i (keys %{$map}){
      if (defined $map->{$i}->{0}{object}->interrupt_after() and
          $map->{$i}->{0}{object}->interrupt_after() eq "preparedef" ) {
        @{$map->{$i}->{activeCond}} = ();
        push (@{$map->{$i}->{activeCond}}, "<Preprocess solver input file only>") ;
        push (@{$self->{activeConfigs}}, $i);
      }
    }
    $self->{simstep} = 1 ; 
  }
  
  # See who's active
  # --> Clear justRun after we use it to update_active
  my $doSimSteps = $self->update_active($self->{activeConfigs}) ;
  $self->{justRun}=() ;
  unless (defined ${$self->{activeConfigs}}[0]) {
    $self->{simstep}+=1 ;
  }

  #
  # Main Simulation execution loops
  #

  my $simError = 0;
  my $userInterrupt = 0;
  my @termCond ;
  
  while($doSimSteps){
    $self->outfile_simstep($self->{simstep},$self->{simstep}) ; 
    
    foreach my $i (keys %{$map}){
      if(defined($map->{$i}->{activeCond}[0])) {
        my $rv = 1;
        $rv=$self->runConfig($i, $self->norun());

        
        unless ($rv) {
          $simError=1;
          $doSimSteps=0; 
          last; 
        }
        my @configTermCond = $self->CheckSimTermCond($i) ;

        # Check if configuration has been interrupted
        my $currentstep = $map->{$i}{nextstep} - 1;
        my $termcondref = $map->{$i}{$currentstep}{termcond};
        for my $termcond (@$termcondref) {
          if ($termcond eq 'Stop instruction received') {
            $userInterrupt = 1;
          }
        }

        my $immediateTerm = shift(@configTermCond);
        if (defined $immediateTerm) {
          push (@termCond,@configTermCond);
          $doSimSteps=0; 
          last if $immediateTerm ; 
        }
        # Only add this config to the justRun list if it ran to full completion
        # --> config won't be complete if immediate termination was registered
        push @{$self->{justRun}}, $i;
      }
    }
    
    # Stop after completing one step if we're not supposed to run
    if ($self->norun()){
      push (@termCond, "Done preprocessing configuration solver input files") ;
      $doSimSteps = 0;
    }

    $doSimSteps = $self->update_active() if ($doSimSteps);
        
    # Get ready for the next sim step
    if ($doSimSteps) {
      $self->{justRun}=() ;
      $self->{simstep}+=1 ;
    }
  }

  #
  # Simulation Tidyup
  #
  
  if (defined($termCond[0] or $simError)) {
    $self->OutFileHeader1("TERMINATION CONDITION SUMMARY");
    sim_print("") ;
    if ($simError) {
      sim_print  ("  * An error has occurred.\n") ;
    }
    elsif (@termCond) {
      foreach my $l (@termCond) {
        sim_print("  * ".$l."\n") ;
      }
    }
    else {
      sim_print  ("  * Simulation completed successfully.\n") ;
    }
  }

  # Interrupt further processing
  if ($self->norun()) {
    $self->OutFileHeader("Simulation Interrupted at Users Request");
    $self->CleanupRunDir(); 
    return 1; 
  }
  
  @header = ();
  if ($simError) {
    push (@header,"Simulation Incomplete"); 
    push (@header,"  "); 
    push (@header,"-- An error has occurred --");
  } else {
    push (@header,"Simulation Complete"); 
  }
  $self->OutFileHeader(@header);
  
  # At the last possible minute, shut down the object copying
  # messages to the gui-err.txt file so we can delete it!
  my $cclout=$self->outobjccl();
  if ($cclout) {
    # first we write a message that we've finished
    my $name=$cclout->gui_objname() || '';
    my $exittype;
    if ($simError) {
      $exittype = 'ERROR';
    } elsif ($userInterrupt) {
      $exittype = 'USER';
    } else {
      $exittype = 'NORMAL';
    }
    my @msg=(
             "GUI INFORMATION:",
             "  RUN CONCLUSION INFORMATION:  $name",
             "    Run Name = ".basename($self->fullname()),
             "    Results File = ".$self->mresfile(),
             "    Reason for Exit = $exittype",
             "  END",
             "END",
            );
    if (my $fh=$cclout->open()) {
      print $fh join("\n",@msg),"\n";
      $cclout->close();
    } else {
      _trace("Cannot write to CCL message file: $!");
    }

    # now shut down all this stuff.
    remove_output_object($cclout, undef);
    $self->outobjccl('');
  }
  
  $self->write_mres() ; 

  # wait for SM to exit
  $self->wait_for_sm_exit();
  
  $self->CleanupRunDir(); 
  
  1;
}

#
# UnpackConfigDefFiles is so named because eventually, we'll be unpacking
# configuration definition data from the .mdef and .mres files.  For R12,
# however, we'll make do with them being in a sub-directory that has the
# same basename as the solver input file.
#

sub UnpackConfigDefFiles {
  my $self = shift;
  
  return if $self->norun() ;
  
  my $map=$self->configmap();

  #
  # Loop over configurations, and copy config def files from the solver input
  # file based location to the current run directory.
  # 
  foreach my $i (keys %{$map}){
    my $configDefFile=$map->{$i}->{def} ;
    my $src = join_file($self->cfgdefdir(),$configDefFile) ; 
    my $dst = join_file($self->rundir(),$configDefFile) ; 
    copy_file($src,$dst) ;
  }
  
  #
  # Now record that we're to use the current run directory as our source
  # for configuration defintion data from now on.
  #
  $self->cfgdefdir($self->rundir());
}

sub CleanupRunDir {
  my $self = shift; 
  $self->rundir_obj()->enter() ;
    map {unlink $_ if (-e $_)}
      ( "gui-err.txt",
        "stp" ) ; 
  $self->rundir_obj()->leave() ;
  1; 
}

sub wait_for_sm_exit {
  my $self=shift;

  my $checks=0;
  my $count;
  while (($count=$self->sm_is_running()) 
         and $checks++ < $Config{CFX5_SOLVE_MONITOR_TIMEOUT}) {
    _debug("Waiting for SM to finish monitoring, $count still going");
    sleep(1);
  }

  if ($count) {
    _warn("After waiting for $Config{CFX5_SOLVE_MONITOR_TIMEOUT} seconds, $count solver manager process(es) appear not to have noticed that this run has ended.  You may get errors removing some files if they are still open in the solver manager.");
  }

  1;
}

sub sm_is_running {
  my $self=shift;

  my $count=0;

  my $dir=$self->rundir();

  local *DIR;
  unless (opendir DIR, $dir) {
    _error("Error reading working directory $dir: $!");
    return 0;
  }

  my $f;
  while (defined ($f = readdir DIR)) {
    $count++ if $f =~ m!^sm\..*\.lock$!i;
  }
  closedir DIR;

  $count;
}

sub createConfig {
  my $self=shift;
  my $configid=shift;
  my $step=shift;
  my $map=$self->configmap();

  $map->{$configid}{$step}{object}=CFX5::Simulation::Configuration->new($self);
}

sub initConfig {
  my $self=shift;
  my $configid=shift;
  my $step=shift;
  my $map=$self->configmap();

  # Set the configuration object's def file
  # --> Alternate solver input file is for restarts after a user requested stop; 
  #     this setting is deleted after the configuration is run once
  # --> Note that an unset cfgdefdir should typically indicate that this is a single
  #     configuration simulation (i.e. input file is .def).
  my $altSolverInputFile = $map->{$configid}->{altSolverInputFile}; 
  if (defined $altSolverInputFile) {  
    $map->{$configid}->{$step}{object}->deffile($altSolverInputFile);
  } else {
    my $deffile = ( defined $self->cfgdefdir() ?
                    join_file($self->cfgdefdir(),$map->{$configid}->{def}) :
                    $map->{$configid}->{def} ); 
    $map->{$configid}->{$step}{object}->deffile($deffile);
  }
  exit_if_error() ;

  # Read the solver input file's CCL
  $map->{$configid}->{$step}{object}->read_def_ccl();
  
  # Ensure we've applied all relevant command line options
  # --> These may or may not have CCL implications, so do them as late as possible
  $self->apply_config_options($configid,$step); 

  #  
  # For multi-config runs, conditionally over-write LIBRARY and FLOW CCL
  if (!$self->singleconfig()) {
    # Overwrite FLOW CCL using values from from last res file 
    # if it exists (rather than simply using previously stored flowccl.
    # --> This ensures that changes made to res file CCL will be 
    #     respected if the user hacks around this way.
    my $lastRes ;
    if ($self->{resinfo}->{nResInfo}) {
      my $configname = $map->{$configid}{name} ;
      my %entry=$self->{resinfo}->GetLast($configname);
      $lastRes = $entry{FilePath} if defined $entry{FilePath} ;
    }
    $self->updateFlowCCLRef($configid, $step, $lastRes);
    return 0 if (error_pending());

    # Overwrite config RULES CCL with values from simulation
    $self->updateRulesCCLConfig($configid, $step);
    return 0 if (error_pending());

    # Overwrite config LIBRARY CCL with values from simulation
    $self->updateLibCCLConfig($configid, $step);
    return 0 if (error_pending());
    
    # Re-parse any config specific CCL that was given at the command line
    # --> Since this CCL could modify either LIBRARY or FLOW CCL, we need to
    #     rerun updates to the reference CCL. Note that the preceeding
    #     updateFlowCCLRef and updateLibCCLConfig calls should remain where they
    #     are because these bring CCL -INTO- the configuration. 
    # --> Note that this should probably only be done the first time a config
    #     is run during this simulation execution
    if (defined $map->{$configid}->{cmdlineCCL}) {
      $self->updateCCLConfig($configid, $step, \@{$map->{$configid}->{cmdlineCCL}});
      return 0 if (error_pending());
      $self->updateFlowCCLRef($configid, $step);
      $self->updateLibCCLRef($configid, $step);
    }
  }

  $map->{$configid}->{$step}{object}->batch(1);
  if($map->{$configid}->{$step}{object}->is_mfx_run()){
    $map->{$configid}->{$step}{object}->mfx_run_mode("Start ANSYS and CFX");
    $map->{$configid}->{$step}{object}->mfx_process_input('On');
    $map->{$configid}->{$step}{object}->mfx_restart('Off');
  }
}

sub runConfig {
  my $self=shift;
  my $configid=shift;
  my $norun=shift; 
  
  my $map=$self->configmap();
  my $step=$map->{$configid}->{nextstep};
  $map->{$configid}->{nextstep}+=1;
  $self->{currentconfig}=$configid;

  # Setup the new step
  # --> Cleanup of previous step for multi-config runs
  #     (provided that it is not step=0, which holds our baseline CCL, etc...)
  if($self->singleconfig()){
    $step=0;
  } else {
    $map->{$configid}->{$step-1} = () unless ($step-1 == 0) ; 
    $self->createConfig($configid, $step);
    $self->initConfig($configid, $step);
  }

  if($self->singleconfig()){
    $map->{$configid}->{$step}{object}->ChooseName()
      unless (defined $map->{$configid}->{$step}{object}->fullname()) ;
  }
  else {
    $map->{$configid}->{$step}{object}->ChooseName($self->fullname());
    $self->outfile_confStep($step,$step,$map->{$configid}{name});
    $self->outfile_confStep('','',"* Act. by : ".$map->{$configid}->{activeCond}[0]);
    for (my $ii=1; $ii<scalar(@{$map->{$configid}->{activeCond}});$ii++) {
      $self->outfile_confStep('','',"            ".$map->{$configid}->{activeCond}[$ii]);
    }
    my $cclout=$self->outobjccl();
    if($cclout) {
      (my $name=basename( $map->{$configid}->{$step}{object}->fullname() ) ) =~ s![-_]! !g;
      my @msg=(
               "GUI INFORMATION:",
               "  CONFIG SETUP INFORMATION: $name",
               "    Run Name = ".basename( $map->{$configid}->{$step}{object}->fullname()),
               "    Object Name = $name",
               "    Full Run Prefix = ".$map->{$configid}->{$step}{object}->fullname(),
               "    Working Directory = ".$map->{$configid}->{$step}{object}->rundir(),
               "    Output File = ".$map->{$configid}->{$step}{object}->fullname(),
               "    Messages File = ".join_file($map->{$configid}->{$step}{object}->rundir(), 'gui-err.txt'),
               "  END",
               "END",
      );
      if (my $fh=$cclout->open()) {
          print $fh join("\n",@msg),"\n";
          $cclout->close();
      }
    }
  }
 
  # Ensure the configuration simulation control CCL is up to date
  # --> Update (and then store) configuration's exec control CCL
  #     based on what's in the simulation's CCL
  unless ($self->singleconfig()) {
    $map->{$configid}{execccl}=$self->updateConfigExecCCL($configid);
    $map->{$configid}->{$step}{object}->read_ccl(\@{$map->{$configid}{execccl}}) if defined $map->{$configid}{execccl};
  }
  return 0 unless $self->finalise_config_ccl($configid,$step); 
  
  my $rv=0 ;
  if ($norun and !$self->singleconfig()) {
    $rv=$self->WritePreprocConfigDef($configid,$step); 
  }
  else {
    $rv=$map->{$configid}->{$step}{object}->Run() ; 
  }

  return $rv if $self->singleconfig();

  # Record results file and record termination info
  # --> don't forget to add an conditions generated outside the solver
  my @termconds ;
  my @termtype ;
  if (-e $map->{$configid}->{$step}{object}->resfile()) {
    $map->{$configid}->{$step}{res}=$map->{$configid}->{$step}{object}->resfile();
    @termconds=GetDataSet($map->{$configid}->{$step}{res}, 'G/TERMCOND');
    @termtype=GetDataSet($map->{$configid}->{$step}{res}, 'G/TERMCONDTYPE');
  }
  if (defined $map->{$configid}->{$step}->{object}{termcond} and
      defined $map->{$configid}->{$step}->{object}{termtype} ) {
    push (@termconds, @{$map->{$configid}->{$step}->{object}{termcond}}); 
    push (@termtype,  @{$map->{$configid}->{$step}->{object}{termtype}});
  }
  $map->{$configid}->{$step}{termcond}=\@termconds;
  $map->{$configid}->{$step}{termtype}=\@termtype;

  # Report run status for the user
  my $status ; 
  if(!$rv){
    $status = "Failed" ; 
  } else {
    $status = "Succeeded" ; 
    foreach my $termcond (@termconds) {
      if ($termcond eq "Stop instruction received") {
        $status = $termcond ; 
      }
    }
  }
  $self->outfile_confStep('','',"* Status  : $status"); 

  # Acknowledge change to /LIBRARY CCL
  # --> already done in config.out, but we should do this in sim.out so that
  #     user is aware that subsequent runs of any config. will be affected
  if ($self->updateLibCCLRef($configid,$step)) {
    $self->outfile_confStep('','',"* NOTE    : The LIBRARY definition changed during this"); 
    $self->outfile_confStep('','',"            run.  These changes will be applied to all"); 
    $self->outfile_confStep('','',"            remaining configuration runs."); 
  }
  return 0 if (error_pending()); 

  # Acknowledge change to /RULES CCL
  # --> already done in config.out, but we should do this in sim.out so that
  #     user is aware that subsequent runs of any config. will be affected
  if ($self->updateRulesCCLRef($configid,$step)) {
    $self->outfile_confStep('','',"* NOTE    : The RULES definition changed during this"); 
    $self->outfile_confStep('','',"            run.  These changes will be applied to all"); 
    $self->outfile_confStep('','',"            remaining configuration runs."); 
  }
  return 0 if (error_pending());

  # Acknowledge change to /FLOW CCL
  # --> already done in config.out, but we should do this in sim.out so that
  #     user is aware that subsequent runs of this config. will be affected
  if ($self->updateFlowCCLRef($configid,$step)) {
    $self->outfile_confStep('','',"* NOTE    : The FLOW definition changed during this"); 
    $self->outfile_confStep('','',"            run.  These changes will be applied to all"); 
    $self->outfile_confStep('','',"            remaining runs of this configuration."); 
  }
  return 0 if (error_pending());

  $self->outfile_confStep_break();

  #
  # Cleanup after running configuration
  # --> Clear activating conditions if we don't intend to re-run the current configuration
  #     + always clear if configuration run succeeded
  #     + only clear configuration specific stop instruction was issued by user, rather
  #       than a simulation wide stop instruction (i.e. subsequent restarts need to continue
  #       from the current config if the stop instruction was simulation wide)
  # --> Clear alternate solver input file setting if it exists
  # --> Clear command line CCL setting if it exists (should be part of res files, etc. now)
  $map->{$configid}->{activeCond}=() if ($status eq "Succeeded" or
                                         ($status eq "Stop instruction received" and !$self->stop_now()) ) ; 
  $map->{$configid}->{altSolverInputFile} = () if defined ($map->{$configid}->{altSolverInputFile}); 
  $map->{$configid}->{cmdlineCCL} = () if defined ($map->{$configid}->{cmdlineCCL});
  
  return $rv;
}

#
# Routine to update the CCL for a configuration, based upon provided CCL
#
sub updateCCLConfig {
  my $self = shift;
  my $configid = shift;
  my $step = shift;
  my $ccl = shift;
  
  my $map=$self->configmap();
  my $api=$map->{$configid}->{$step}{object}->ccl_api_obj();
  
  my $rv=$api->parseCCLData(\@{$ccl});
  if (defined $rv) {
   _error("updateCCLConfig: Error parsing CCL:\n",
   $self->format_ccl_error($rv));
   return 0; 
  }

  1 ;  
}

#
# Routine to update the LIBRARY CCL for a configuration, based upon the
# stored reference CCL
#
sub updateLibCCLConfig {
  my $self = shift;
  my $configid = shift;
  my $step = shift;
  
  my $map=$self->configmap();
  
  # Get current CCL from the configuration, if it exists
  my $api=$map->{$configid}->{$step}{object}->ccl_api_obj();
  my @currentCCL;
  $api->getObjectCCL("/LIBRARY", \@currentCCL);
  my $haveCCL = @currentCCL > 2 ;

  # Error if neither of config's CCL or reference CCL exists
  unless ($haveCCL or $self->libccl()) {
    _error("updateLibCCLConfig: No LIBRARY CCL exists."); 
    return 0 ;
  }
    
  # Create the reference LIBRARY CCL if it doesn't yet exist
  # --> this automatically implies no updates to config CCL, so we can return
  unless ($self->libccl()) {
    $self->libccl(@currentCCL) ;
    return 0 ;
  }
  
  # Still here... Update LIBRARY CCL for config 
  if ($haveCCL){
    my $rv=$api->deleteObj('/LIBRARY');
    if (defined $rv) {
      _error("updateLibCCLConfig: Error deleting library CCL:\n",
      $self->format_ccl_error($rv));
      return 0; 
    }
  }
  my @libCCL = $self->libccl() ;
  
  my $rv=$api->parseCCLData(\@libCCL);
  if (defined $rv) {
   _error("updateLibCCLConfig: Error parsing library CCL:\n",
   $self->format_ccl_error($rv));
   return 0; 
  }

  # Check if the configuration's LIBRARY CCL changed with this update
  my @ccldiffs = CFX5::CCLDiff->compareCCL(\@libCCL, \@currentCCL);
  return 0 unless (@ccldiffs) ; 
    
  1 ; 
}

#
# Routine to update the reference LIBRARY CCL 
# --> This reference CCL is compared against the CCL at the end of a config.
#     run, if differences are found:
#     1) the user is informed, and
#     2) the new CCL replaces the reference data
#
sub updateLibCCLRef {
  my $self = shift;
  my $configid = shift;
  my $step = shift;  

  my $map=$self->configmap();
  my $api; 

  $api=$map->{$configid}->{$step}{object}->ccl_api_obj();
  my @currentCCL; 
  $api->getObjectCCL("/LIBRARY", \@currentCCL);
  my $haveCCL = @currentCCL > 2 ;

  # Need some sort of library CCL
  unless ($haveCCL or $self->libccl()) {
    _error("updateLibCCL: No LIBRARY CCL exists."); 
    return 0 ;
  }

  # Create stored CCL if it doesn't already exist
  # --> this implies no updates to simulation CCL
  unless ($self->libccl()) {
    $self->libccl(@currentCCL) ;
    return 0 ;
  }

  my @libCCL = $self->libccl(); 

  # Check if the LIBRARY CCL the configuration holds differs from the stored CCL
  my @ccldiffs = CFX5::CCLDiff->compareCCL(\@libCCL, \@currentCCL);
  return 0 unless (@ccldiffs); 

  # Store the change if we're still here...
  $self->libccl(@currentCCL);

  1; 
}

#
# Routine to update the RULES CCL for a configuration, based upon the
# stored reference CCL
#
sub updateRulesCCLConfig {
  my $self = shift;
  my $configid = shift;
  my $step = shift;

  my $map=$self->configmap();

  # Get current CCL from the configuration, if it exists
  my $api=$map->{$configid}->{$step}{object}->ccl_api_obj();
  my @currentCCL;
  $api->getObjectCCL("/RULES", \@currentCCL);
  my $haveCCL = @currentCCL > 2;

  # Return if neither of config's CCL or reference CCL exists
  return 0 unless ($haveCCL or $self->rulesccl());

  # Create the reference RULES CCL if it doesn't yet exist
  # --> this automatically implies no updates to config CCL, so we can return
  unless ($self->rulesccl()) {
    $self->rulesccl(@currentCCL);
    return 0;
  }

  # Still here... Update RULES CCL for config 
  if ($haveCCL){
    my $rv=$api->deleteObj('/RULES');
    if (defined $rv) {
      _error("updateRulesCCLConfig: Error deleting RULES CCL:\n",
      $self->format_ccl_error($rv));
      return 0;
    }
  }
  my @rulesCCL = $self->rulesccl();

  my $rv=$api->parseCCLData(\@rulesCCL);
  if (defined $rv) {
   _error("updateRulesCCLConfig: Error parsing RULES CCL:\n",
   $self->format_ccl_error($rv));
   return 0; 
  }

  # Check if the configuration's RULES CCL changed with this update
  my @ccldiffs = CFX5::CCLDiff->compareCCL(\@rulesCCL, \@currentCCL);
  return 0 unless (@ccldiffs);

  1;
}

#
# Routine to update the reference RULES CCL 
# --> This reference CCL is compared against the CCL at the end of a config.
#     run, if differences are found:
#     1) the user is informed, and
#     2) the new CCL replaces the reference data
#
sub updateRulesCCLRef {
  my $self = shift;
  my $configid = shift;
  my $step = shift;

  my $map=$self->configmap();
  my $api; 

  $api=$map->{$configid}->{$step}{object}->ccl_api_obj();
  my @currentCCL; 
  $api->getObjectCCL("/RULES", \@currentCCL);
  my $haveCCL = @currentCCL > 2 ;

  # No update if no RULES CCL exists
  return 0 unless ($haveCCL or $self->rulesccl());

  # Create stored CCL if it doesn't already exist
  # --> this implies no updates to simulation CCL
  unless ($self->rulesccl()) {
    $self->rulesccl(@currentCCL);
    return 0 ;
  }

  my @rulesCCL = $self->rulesccl(); 

  # Check if the RULES CCL the configuration holds differs from the stored CCL
  my @ccldiffs = CFX5::CCLDiff->compareCCL(\@rulesCCL, \@currentCCL);
  return 0 unless (@ccldiffs); 

  # Store the change if we're still here...
  $self->rulesccl(@currentCCL);

  1; 
}

#
# Routine to update the FLOW CCL for a configuration, based upon the
# stored reference CCL
#
sub updateFlowCCLConfig {
  my $self = shift;
  my $configid = shift;
  my $step = shift;
  
  my $map=$self->configmap();
  my $flowCCLHeader = $map->{$configid}->{$step}{object}->flow_name() ;
  
  my $haveCCLConfig = defined $map->{$configid}->{flowccl} ;
  
  # Get current CCL from the configuration, if it exists
  my $api=$map->{$configid}->{$step}{object}->ccl_api_obj();
  my @currentCCL;
  $api->getObjectCCL("/$flowCCLHeader", \@currentCCL);
  my $haveCCL = @currentCCL > 2 ;

  # Error if neither of config's CCL or reference CCL exists
  unless ($haveCCL or $haveCCLConfig) {
    _error("updateFlowCCLConfig: No FLOW CCL exists."); 
    return 0 ;
  }
    
  # Create the reference FLOW CCL if it doesn't yet exist
  # --> this automatically implies no updates to config CCL, so we can return
  unless ($haveCCLConfig) {
    $map->{$configid}->{flowccl} = \@currentCCL ;
    return 0 ;
  }
  
  # Still here... Update FLOW CCL for config 
  my $rv=$api->deleteObj("/$flowCCLHeader");
  if (defined $rv) {
    _error("updateFlowCCLConfig: Error deleting FLOW CCL:\n",
    $self->format_ccl_error($rv));
    return 0; 
  }

  my @flowCCL = $map->{$configid}->{flowccl} ;
  
  $rv=$api->parseCCLData(\@flowCCL);
  if (defined $rv) {
    _error("updateFlowCCLConfig: Error parsing FLOW CCL:\n",
   $self->format_ccl_error($rv));
   return 0; 
  }

  # Check if the configuration's LIBRARY CCL changed with this update
  my @ccldiffs = CFX5::CCLDiff->compareCCL(\@flowCCL, \@currentCCL);
  return 0 unless (@ccldiffs) ; 
    
  1 ; 
}

#
# Routine to update the reference FLOW CCL for a configuration
# --> This reference CCL is compared against the CCL at the end of a config.
#     run, if differences are found:
#     1) the user is informed, and
#     2) the new CCL replaces the reference data
#
sub updateFlowCCLRef {
  my $self = shift;
  my $configid = shift;
  my $step = shift;
  my $cclSourceFile = shift; 
  
  my $map=$self->configmap();
  my $flowCCLHeader = $map->{$configid}->{$step}{object}->flow_name() ;

  #
  # Get FLOW CCL from a source file, if we're asked to do so
  if (defined $cclSourceFile) {
    my $rv;
    my $ds=CFX5::CCLI::getcclds($cclSourceFile, 'G/COMMANDS');
    unless ( defined $ds and ref $ds ) {
      _error("Unable to read Command Language data from the definition file $cclSourceFile");
      return 0;
    }
    my @srcCCL=$ds->data();
    
    return 0 unless (defined $srcCCL[0] and @srcCCL); 
    my $srcapi = CFX5::CCLI::new_api_object();
    $rv=$srcapi->parseCCLData(\@srcCCL, '');
    if (defined $rv) {
      _error("updateFlowCCLRef: Error reading Command Language data from $cclSourceFile"); 
      return 0 ;
    }
    
    # Use getObjectCCL to extract the FLOW CCL from the source API
    # --> for some reason getFormattedCCL didn't return the proper CCL?!?
    @srcCCL = (); 
    $srcapi->getObjectCCL("/$flowCCLHeader", \@srcCCL);

    # Parse new FLOW into the configuration's API  
    my $dstapi=$map->{$configid}->{$step}{object}->ccl_api_obj();
    $rv=$dstapi->deleteObj("/$flowCCLHeader");
    if (defined $rv) {
      _error("Error deleting flow CCL:\n", $self->format_ccl_error($rv));
    }
    $rv=$dstapi->parseCCLData(\@srcCCL);
    if (defined $rv) {
      _error("Error parsing flow CCL:\n", $self->format_ccl_error($rv));
      return 0;
    }

    # Store new FLOW for comparison after the run
    pop(@srcCCL);
    shift(@srcCCL);
    $map->{$configid}->{flowccl} = \@srcCCL ;
      
    # No change between them...
    return 0 ; 
  }

  my @currentCCL = $map->{$configid}->{$step}{object}->get_current_ccl("/$flowCCLHeader");

  # No Stored CCL means no change
  unless (defined $map->{$configid}->{flowccl}) {
    $map->{$configid}->{flowccl} = \@currentCCL ;
    return 0; 
  }
  
  # Return no change if that's the case
  my @ccldiffs = CFX5::CCLDiff->compareCCL(\@{$map->{$configid}->{flowccl}}, \@currentCCL);
  return 0 unless (@ccldiffs) ; 
    
  # Store the change if we're still here...
  $map->{$configid}->{flowccl} = \@currentCCL ;
  
  1 ; 
}

sub WritePreprocConfigDef {
  my $self = shift;
  my $configid = shift;
  my $step = shift; 
  
  my $rv = 1;
  my $map = $self->configmap();
  my $srcFile = fullpath($map->{$configid}->{$step}{object}->deffile()) ;
  my $dstFile = basename($map->{$configid}->{$step}{object}->deffile()) ;
  $dstFile =~s/\.cfg/\.def/g; 
  
  $self->rundir_obj()->enter() ;
  
  my @preprocCCL = $map->{$configid}->{$step}{object}->get_current_ccl() ;
  copy_file($srcFile,$dstFile) ;
  CFX5::CCLI::writeccl($dstFile,\@preprocCCL,1) ;
  
  $self->rundir_obj()->leave() ;
  
  return $rv; 
}

sub get_active {
  my $self = shift ;

  my @activeConfigs = () ; 
  my $map=$self->configmap();
  foreach my $i (keys %{$map}) {
    push (@activeConfigs, $i) if defined($map->{$i}->{activeCond}[0]) ; 
  }
  return @activeConfigs ; 
}

sub update_active {
  my $self=shift;
  my $forceActive=shift;

  @{$forceActive} = () unless (defined ${$forceActive}[0]); 

  my $rv = 0;
  my $map=$self->configmap();

  #
  # Forced Activation
  # --> If it shows up in the list provided, then its active...
  #
  foreach my $i (@{$forceActive}) {
    push (@{$map->{$i}{activeCond}},"<Automatic Continuation>") unless defined ($map->{$i}{activeCond}[0]) ;
  }
  return 1 if (scalar(@{$forceActive}));
  
  #
  # Evaluated Activation
  #
  
  my $api=$self->ccl_api_obj();

  foreach my $i (keys %{$map}) {

    # Now see if any activation conditions in the CCL are satisfied    
    my $configname = $map->{$i}{name} ;
    my $cclpath="SIMULATION CONTROL/CONFIGURATION CONTROL/CONFIGURATION: $configname";

    my @actcontrol;
    $api->getChildObjectList("$cclpath/ACTIVATION CONTROL", \@actcontrol);
    foreach my $cond (@actcontrol) {  
      my $option=$self->read_ccl_parm("$cclpath/ACTIVATION CONTROL/$cond/Option");
      if    ($option =~ m!Start of Simulation! and 
             !defined($self->{justRun}[0])){
        $cond=~s!.*:!!g ;
        push (@{$map->{$i}{activeCond}},$cond);
        $rv = 1; 
      }
      elsif ($option =~ m!End of Configuration! and
             defined($self->{justRun}[0])){
        my $confList = $self->read_ccl_parm("$cclpath/ACTIVATION CONTROL/$cond/Configuration Name List");
        foreach my $conf (split(/,/, $confList)){
          $conf=~s!^[ \t]+!!g; 
          foreach my $justRun (@{$self->{justRun}}) {
            if ($conf eq $map->{$justRun}{name}) {
              $cond=~s!.*:!!g ;
              push (@{$map->{$i}{activeCond}},$cond);
              $rv = 1; 
            }
          }
        }
      }
    }
  }
  $rv ;
}


#
################################################################################
#
#  Routines for Cleanup
#
################################################################################
#

# Check for conditions satisfying simulation termination
# --> First entry of array returned indicates whether termination should be
#     immediate (1) or deferred (0) until the end of the simulation step

sub CheckSimTermCond {
  my $self=shift;
  my $justRun=shift ;
  
  my @termCond = ();
  
  my $map=$self->configmap();
  
  #
  # Real easy if we're only doing preprocessing
  return @termCond if $self->norun() ;
  
  my $configname = $map->{$justRun}{name} ;
    
  #
  # Look for deferred terminations first
  # --> No need to look at conditions that are defined for configs other than
  #     the one just run
  foreach my $termcond (keys %{$self->{termconds}}){
    next unless $self->{termconds}{$termcond}{configname} eq $configname ;
   
    # Max. num. config steps 
    if($self->{termconds}{$termcond}{option} eq "Maximum Number of Configuration Steps" and
       $map->{$justRun}{nextstep} > $self->{termconds}{$termcond}{maxsteps}){
      $termCond[0] = 0 ; 
      push (@termCond, "User: ".$termcond) ; 
    }
    
    # Specific config interrupt condition satisfied
    # --> Need to pick up array (from list) of configuration interrupt conds.
    #     that will terminate the simulation
    if($self->{termconds}{$termcond}{option} eq "Solver Interrupt Conditions") {
      my @simConfigTermConds = split(',', $self->{termconds}{$termcond}{condnames}); 
      my %entry = $self->{resinfo}->GetLast($configname);
      my @configTermConds=GetDataSet($entry{FilePath}, 'G/TERMCOND');
      foreach my $configTermCond (@configTermConds) {
        foreach my $simConfigTermCond (@simConfigTermConds) {
          if ($configTermCond eq $simConfigTermCond) {
            $termCond[0] = 0 ; 
            push (@termCond, "User: ".$termcond) ; 
            last; 
          }
        }
      }
    }
  }
  
  #
  # Look for immediate terminations last
  # --> simulation level stop
  if ($self->stop_now()) {
    $termCond[0] = 1 ; 
    push (@termCond, "User: Simulation stop instruction received") ;
  }
  
  return @termCond; 
}

sub write_mres {
  my $self=shift;
  my $file=$self->mresfile();

  unless ($self->{resinfo}->{nResInfo}) {
    sim_print(" No results written.");
    return 1; 
  }
  
  my @text = wraptext(63,$self->mresfile()) ;
  sim_print(" Results written to:");
  foreach my $line (@text) {
    sim_print("     $line"); 
  }

  #
  # General data
  my $ds = new CFX5::DefFile::DataSet(); 
  my $map=$self->configmap();

  #
  # Connect to MRES file
  my $mres;
  if( not CFX5::FileStat::test_e $file ){
    $mres = CFX5::DefFile->new_file($file);
  }
  else {
    $mres = CFX5::DefFile->new($file) ;
  }
  unless (ref $mres) {
    _error("write_mres failed - could not connect to file: $file");
    return 0; 
  }
  
  #
  # Connect to MDEF file
  my $mdef;
  $mdef = CFX5::DefFile->new($self->mdeffile()) ;
  unless (ref $mdef) {
    _error("write_mres failed - could not connect to file: $file");
    return 0; 
  }

  #
  # G/RES_INFO
  $self->{resinfo}->WriteToFile($file);
 
  #
  # G/SIM/* datasets that pre-existed from the definition
  # --> count variable indicates how many datasets copied; 
  #     could be zero if this is a single configuration case.
  my @datasets; 
  push (@datasets,['G/SIM/CCONFIG','EVERY',0]);
  foreach my $i (keys %{$map}){
    push(@datasets,["G/SIM/$i/NAME",'EVERY',0]); 
    push(@datasets,["G/SIM/$i/DEFFILE",'EVERY',0]); 
  }
  my $count = $mdef->copy_datasets($mres,@datasets) ; 

  #
  # G/SIM/CONTROL
  # --> Need to separate list of just-run configs because we can't support
  #     multiple strings per structure...
  {
    my $ds = new CFX5::DefFile::DataSet(); 
    $ds->header(what=>'G/SIM/CONTROL'); 
    $ds->header(where=>'EVERY'); 
    $ds->header(when=>0); 
    $ds->header(number=>1); 
    # SR: switch definition once io module is cleaned up...
    #$ds->header(datatype=>"STRC{INTR:2 STRI:1}"); 
    $ds->header(datatype=>"STRC{INTR:2}"); 
    $ds->data([-1,$self->{simstep}]);
    unless ($mres->write_dataset($ds)) {
      _error("write_mres failed - ".$ds->header->{what});
      return 0; 
    }
  }
  
  #
  # G/SIM/ACTIVECONFIGS
  # --> List of configurations that were supposed to run during this step
  #     but didn't for some reason (will need to continue w. these on restart)
  my @activeConfigs = $self->get_active() ;
  if (defined $activeConfigs[0]) {
    my $ds = new CFX5::DefFile::DataSet(); 
    $ds->header(what=>'G/SIM/ACTIVECONFIGS'); 
    $ds->header(where=>'EVERY'); 
    $ds->header(when=>0); 
    $ds->header(number=>scalar(@activeConfigs)); 
    $ds->header(datatype=>"STRI"); 
    $ds->data(@activeConfigs);
    unless ($mres->write_dataset($ds)) {
      _error("write_mres failed - ".$ds->header->{what});
      return 0; 
    }
  }

  #
  # G/SIM/CONFIGi/CONTROL
  foreach my $i (keys %{$map}){
    $ds->header(what=>"G/SIM/$i/CONTROL"); 
    $ds->header(where=>'EVERY'); 
    $ds->header(when=>0); 
    $ds->header(number=>1); 
    $ds->header(datatype=>"STRC{INTR:2}"); 
    $ds->data([-1,$self->{configmap}->{$i}->{nextstep}-1]);
    unless ($mres->write_dataset($ds)) {
      _error("write_mres failed - ".$ds->header->{what});
      return 0; 
    }
  } 
  
  #
  # G/COMMANDS 
  {
    my $ds = new CFX5::DefFile::DataSet(); 
    $ds->header(what=>'G/COMMANDS'); 
    $ds->header(where=>'EVERY'); 
    $ds->header(when=>0); 
    $ds->header(number=>scalar($self->get_current_ccl())); 
    $ds->header(datatype=>"STRI"); 
    $ds->data($self->get_current_ccl());
    unless ($mres->write_dataset($ds)) {
      _error("write_mres failed - ".$ds->header->{what});
      return 0; 
    }
  } 

  #
  # G/CUSTOMETC if applicable
  #
  if(defined $Config{CFX_CUSTOM_DATA_DIR}){
    my $ds = new CFX5::DefFile::DataSet(); 
    $ds->header(what=>'G/CUSTOMETC'); 
    $ds->header(where=>'EVERY'); 
    $ds->header(when=>0); 
    $ds->header(number=>1); 
    $ds->header(datatype=>"STRI"); 
    $ds->data($Config{CFX_CUSTOM_DATA_DIR}) ;
    unless ($mres->write_dataset($ds)) {
      _error("write_mres failed - ".$ds->header->{what});
      return 0;
    }
  }

  #
  # G/RUNINFO - the gui-err.txt dataset
  #
  return CFX5::CCLI::writeccl( $file, join_file($self->rundir(),"gui-err.txt") , 1, 'G/RUNINFO' ) if -w $file;

}  

sub _DoRunDir {
  my $self=shift;

  unless (defined $self->rundir()) {
    internal_error("Cannot get rundir_obj until we've got rundir");
    return 0;
  }

  if ($self->have_setting('rundir_obj')) {
    internal_error("_DoRunDir called when rundir_obj already exists");
  }

  my $rundir=new CFX5::WorkDir ($self->rundir());
  unless (defined $rundir) {
    # we failed to create the directory!  CFX5::WorkDir will have
    # signalled an error.
    return 0;
  }

  if (defined $self->fullname()) {
    $rundir->set_basename($self->fullname());
  }

  # record the setting for later
  $self->{settings}->{rundir_obj} = $rundir;

  return 1;
}

1;
