package CFX5::Simulation::Configuration;

use strict;

use base 'CFX5::Simulation::ConfigSettings';

use CFX5::Output qw(:all error_text);
use CFX5::Output::CCLUpdateLog;
use CFX5::FileTests;

use CFX5::PathManip qw(dirname basename fullpath join_file normalize_filename);

use CFX5::FileOps qw(create_empty_file create_directory
                     append_to_file set_file_contents copy_file move_file
                     link_files concatenate_files);
use CFX5::FileStat;

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

use CFX5::WorkDir;

use CFX5::Commands qw(do_cmd do_cmd_quiet get_cmd_output parse_rv do_termerr_cmd);
use CFX5::CmdLine qw(parse_cmdline);

use CFX5::PkgLoader qw(cfx5_require);

use CFX5::CCLI;
use CFX5::Parallel;

use CFX5::Output::OutFile;
use CFX5::Output::CCL;

use CFX5::MonFile;
use CFX5::Files::Partition;

use CFX5::Simulation::Util qw(GetDataSet _check_defined);

use CFX5::PatchInfo;
use CFX5::ResInfo;
use CFX5::TimeDate qw(timedatestamp);

use Carp;

=head2 CONSTRUCTORS

=head3 new

  $job=new CFX5::Simulation::Configuration();

Creates a new job object.  If the environment variable
CFX5_DEBUG_JOB_SETTINGS is set, it is used to initialise the settings
by splitting at commas, and taking alternate values to be the setting
name and the setting value.  The settings are set through the setting
functions.

=cut

sub new {
  my $class = shift;
  my $parent=shift;
  my $self = {
             'parent'=>$parent,
             };
  bless $self, $class;

  $self->{solver_exes}={};

  # this initialises the settings object
  $self->SUPER::initialise();

  # now set up any new settings
  $self->initialise(@_);

  return $self;
}

sub parent {
  my $self=shift;
  return $self->{parent};
}

sub initialise {
  my $self=shift;
  if (exists $ENV{CFX5_DEBUG_JOB_SETTINGS}) {
    my @sl=split(/,/,$ENV{CFX5_DEBUG_JOB_SETTINGS});
    while (@sl > 1) {
      my $s=shift @sl;
      my $v=shift @sl;
      $self->$s($v);
    }
    if (@sl) {
      internal_error "Odd number of elements in CFX5_DEBUG_JOB_SETTINGS=$ENV{CFX5_DEBUG_JOB_SETTINGS}";
    }
  }

 { 
    my %pairs=(
               nr => 'Real',
               ni => 'Integer',
               nd => 'Double Precision',
               nc => 'Character',
               nl => 'Logical',
              );
    my @list;
    for my $suff (keys %pairs) {
      push @list, ["memory_$suff",
                   "/SIMULATION CONTROL/EXECUTION CONTROL/SOLVER STEP CONTROL/MEMORY CONTROL/$pairs{$suff} Memory Override"];
      push @list, ["memory_${suff}_part",
                   "/SIMULATION CONTROL/EXECUTION CONTROL/PARTITIONER STEP CONTROL/MEMORY CONTROL/$pairs{$suff} Memory Override"];
      push @list, ["memory_${suff}_interp",
                   "/SIMULATION CONTROL/EXECUTION CONTROL/INTERPOLATOR STEP CONTROL/MEMORY CONTROL/$pairs{$suff} Memory Override"];
      push @list, ["memory_${suff}_cclsetup",
                   "/SIMULATION CONTROL/EXECUTION CONTROL/CCLSETUP STEP CONTROL/MEMORY CONTROL/$pairs{$suff} Memory Override"];
    }
    for my $fl (@list) {
      my ($f,$c)=@$fl;
      $self->_newsetting($f, $c);
    }
  }

  $self->_newsetting('batch');
  $self->_newsetting('cclupdatelog');
  $self->_newsetting('name');
  $self->_newsetting('respect_suffixhist');
  $self->_newsetting('fullname');
  $self->_newsetting('deffile', '/SIMULATION CONTROL/EXECUTION CONTROL/RUN DEFINITION/Solver Input File');
  $self->_newsetting('max_num_its', "/FLOW/SOLVER CONTROL/CONVERGENCE CONTROL/Maximum Number of Iterations");
  $self->_newsetting('time_duration', "/FLOW/ANALYSIS TYPE/TIME DURATION/Total Time");
  $self->_newsetting('num_timesteps_per_run', "/FLOW/ANALYSIS TYPE/TIME DURATION/Number of Timesteps per Run");
  $self->_newsetting('time_per_run', "/FLOW/ANALYSIS TYPE/TIME DURATION/Time per Run");
  $self->_newsetting('timesteps', "/FLOW/ANALYSIS TYPE/TIME STEPS/Timesteps");
  $self->_newsetting('outfile');
  $self->_newsetting('outobj');
  $self->_newsetting('outobjccl');
  $self->_newsetting('ownoutfile');
  $self->_newsetting('resfile');
  $self->_newsetting('resfile_ccl', '/SIMULATION CONTROL/EXECUTION CONTROL/RUN DEFINITION/Solver Results File');
  $self->_newsetting('save_resfile');
  $self->_newsetting('resfile_err');
  $self->_newsetting('resfile_from');
  $self->_newsetting('rundir');
  $self->_newsetting('rundir_obj');
  $self->_newsetting('partitioner_exec_arch');
  $self->_newsetting('partitioner_exec');
  $self->_newsetting('interpolator_exec');
  $self->_newsetting('interpolator_exec_arch');
  $self->_newsetting('ccl2flow_exec');
  $self->_newsetting('ccl2flow_exec_arch');
  $self->_newsetting('cclsetup_exec');
  $self->_newsetting('cclsetup_exec_arch');
  $self->_newsetting('run_mode', '/SIMULATION CONTROL/EXECUTION CONTROL/RUN DEFINITION/Run Mode');
  $self->_newsetting('jobfile');
  $self->_newsetting('pjobfile');
  $self->_newsetting('save_jobfile');
  $self->_newsetting('save_pjobfile');
  $self->_newsetting('jobfile_53');
  $self->_newsetting('save_jobfile_53');
  $self->_newsetting('solver_priority', '/SIMULATION CONTROL/EXECUTION CONTROL/SOLVER STEP CONTROL/Runtime Priority');
  $self->_newsetting('memory_factor', '/SIMULATION CONTROL/EXECUTION CONTROL/SOLVER STEP CONTROL/MEMORY CONTROL/Memory Allocation Factor');
  $self->_newsetting('cat_factor', '/SIMULATION CONTROL/EXECUTION CONTROL/SOLVER STEP CONTROL/MEMORY CONTROL/Catalogue Size Override');
  $self->_newsetting('mms_factor', '/SIMULATION CONTROL/EXECUTION CONTROL/SOLVER STEP CONTROL/MEMORY CONTROL/MMS Allocation Factor');
  $self->_newsetting('memory_factor_part', '/SIMULATION CONTROL/EXECUTION CONTROL/PARTITIONER STEP CONTROL/MEMORY CONTROL/Memory Allocation Factor');
  $self->_newsetting('cat_factor_part', '/SIMULATION CONTROL/EXECUTION CONTROL/PARTITIONER STEP CONTROL/MEMORY CONTROL/Catalogue Size Override');
  $self->_newsetting('mms_factor_part', '/SIMULATION CONTROL/EXECUTION CONTROL/PARTITIONER STEP CONTROL/MEMORY CONTROL/MMS Allocation Factor');
  $self->_newsetting('memory_factor_interp', '/SIMULATION CONTROL/EXECUTION CONTROL/INTERPOLATOR STEP CONTROL/MEMORY CONTROL/Memory Allocation Factor');
  $self->_newsetting('cat_factor_interp', '/SIMULATION CONTROL/EXECUTION CONTROL/INTERPOLATOR STEP CONTROL/MEMORY CONTROL/Catalogue Size Override');
  $self->_newsetting('memory_factor_cclsetup', '/SIMULATION CONTROL/EXECUTION CONTROL/CCLSETUP STEP CONTROL/MEMORY CONTROL/Memory Allocation Factor');
  $self->_newsetting('cat_factor_cclsetup', '/SIMULATION CONTROL/EXECUTION CONTROL/CCLSETUP STEP CONTROL/MEMORY CONTROL/Catalogue Size Override');
  $self->_newsetting('outdir');
  $self->_newsetting('parfile_in', '/SIMULATION CONTROL/EXECUTION CONTROL/RUN DEFINITION/Initial Partition File');
  $self->_newsetting('parfile_out');
  $self->_newsetting('parfile_out_ccl', '/SIMULATION CONTROL/EXECUTION CONTROL/RUN DEFINITION/Partition File');
  $self->_newsetting('save_parfile');
  $self->_newsetting('part_mode', '/SIMULATION CONTROL/EXECUTION CONTROL/PARTITIONER STEP CONTROL/PARTITIONING TYPE/Option');
  $self->_newsetting('part_size_rule', '/SIMULATION CONTROL/EXECUTION CONTROL/PARTITIONER STEP CONTROL/PARTITIONING TYPE/Partition Size Rule');
  $self->_newsetting('part_weights', '/SIMULATION CONTROL/EXECUTION CONTROL/PARTITIONER STEP CONTROL/PARTITIONING TYPE/Partition Weight Factors');
  $self->_newsetting('part_metis_type', '/SIMULATION CONTROL/EXECUTION CONTROL/PARTITIONER STEP CONTROL/PARTITIONING TYPE/MeTiS Type');
  $self->_newsetting('ppe_obj');
  $self->_newsetting('ppe_running');
  $self->_newsetting('autostop_ppe');
  $self->_newsetting('host_list_is_set_up');
  $self->_newsetting('hstfile');
  $self->_newsetting('adaptinfo');
  $self->_newsetting('adapt_control');
  $self->_newsetting('adapt_initfile');
  $self->_newsetting('adapt_to_geometry');
  $self->_newsetting('adapt_db', '/SIMULATION CONTROL/EXECUTION CONTROL/RUN DEFINITION/Adaption Database File');
  $self->_newsetting('adapt_save_intermediate');
  $self->_newsetting('suppress_adapt');
  $self->_newsetting('text_command');
  $self->_newsetting('stop_now');
  $self->_newsetting('interrupt_now');
  $self->_newsetting('first_solver_run');
  $self->_newsetting('have_warned_solver_arch');
  $self->_newsetting('process_ccl');
  $self->_newsetting('deffile_no_ccl');
  $self->_newsetting('save_workdir');
  $self->_newsetting('lets_pretend');
  $self->_newsetting('write_debug');
  $self->_newsetting('debug_obj');
  $self->_newsetting('checkpoint_workdir');
  $self->_newsetting('interrupt_after');
  $self->_newsetting('stages_run');
  $self->_newsetting('scport', '/SIMULATION CONTROL/EXECUTION CONTROL/SOLVER STEP CONTROL/SYSTEM COUPLING/Host Port');
  $self->_newsetting('schost', '/SIMULATION CONTROL/EXECUTION CONTROL/SOLVER STEP CONTROL/SYSTEM COUPLING/Host Name');
  $self->_newsetting('scname', '/SIMULATION CONTROL/EXECUTION CONTROL/SOLVER STEP CONTROL/SYSTEM COUPLING/System Name');
  $self->_newsetting('sclic' , '/SIMULATION CONTROL/EXECUTION CONTROL/SOLVER STEP CONTROL/SYSTEM COUPLING/Licensing Port at Host');
  $self->_newsetting('scid'  , '/SIMULATION CONTROL/EXECUTION CONTROL/SOLVER STEP CONTROL/SYSTEM COUPLING/System Coupling ID');
  $self->_newsetting('analysis_type', "/FLOW/ANALYSIS TYPE/Option");
  $self->_newsetting('mfx_run_type', "/FLOW/ANALYSIS TYPE/EXTERNAL SOLVER COUPLING/Option");
  $self->_newsetting('is_mfx_run');
  $self->_newsetting('mfx_restart_db', '/SIMULATION CONTROL/EXECUTION CONTROL/MFX RUN CONTROL/MFX RUN DEFINITION/Previous ANSYS Run Database');
  $self->_newsetting('mfx_restart', '/SIMULATION CONTROL/EXECUTION CONTROL/MFX RUN CONTROL/MFX RUN DEFINITION/Restart ANSYS Run');
  $self->_newsetting('is_mfx_restart');
  $self->_newsetting('mfx_restart_type');
  $self->_newsetting('is_multiframe_restart');
  $self->_newsetting('mfx_run_mode', '/SIMULATION CONTROL/EXECUTION CONTROL/MFX RUN CONTROL/MFX RUN DEFINITION/MFX Run Mode');
  $self->_newsetting('mfx_process_input', '/SIMULATION CONTROL/EXECUTION CONTROL/MFX RUN CONTROL/MFX RUN DEFINITION/Process ANSYS Input File');
  $self->_newsetting('icem_install_root');
  $self->_newsetting('icem_exe');
  $self->_newsetting('ansys_install_root', '/SIMULATION CONTROL/EXECUTION CONTROL/MFX RUN CONTROL/MFX SOLVER CONTROL/ANSYS Installation Root');
  $self->_newsetting('ansys_exe');
  $self->_newsetting('ansys_workdir');
  $self->_newsetting('ansys_host', '/SIMULATION CONTROL/EXECUTION CONTROL/SOLVER STEP CONTROL/PROCESS COUPLING/Host Name');
  $self->_newsetting('ansys_port', '/SIMULATION CONTROL/EXECUTION CONTROL/SOLVER STEP CONTROL/PROCESS COUPLING/Host Port');
  $self->_newsetting('ansys_port_file');
  $self->_newsetting('ansys_input', "/FLOW/ANALYSIS TYPE/EXTERNAL SOLVER COUPLING/ANSYS Input File");
  $self->_newsetting('processed_ansys_input');
  $self->_newsetting('ansys_output');
  $self->_newsetting('ansys_jobname');
  $self->_newsetting('ansys_args', '/SIMULATION CONTROL/EXECUTION CONTROL/MFX RUN CONTROL/MFX SOLVER CONTROL/Additional ANSYS Solver Arguments');
  $self->_newsetting('ansys_license', '/SIMULATION CONTROL/EXECUTION CONTROL/MFX RUN CONTROL/MFX SOLVER CONTROL/ANSYS License');
  $self->_newsetting('cplg_name', '/SIMULATION CONTROL/EXECUTION CONTROL/SOLVER STEP CONTROL/PROCESS COUPLING/Process Name');
  $self->_newsetting('ansys_running');
  $self->_newsetting('ansys_pid');
  $self->_newsetting('multidomain_partitioning_option', '/SIMULATION CONTROL/EXECUTION CONTROL/PARTITIONER STEP CONTROL/Multidomain Option');
  $self->_newsetting('preferred_license', '/SIMULATION CONTROL/EXECUTION CONTROL/SOLVER STEP CONTROL/LICENSE CONTROL/Preferred License');
  $self->_newsetting('shared_license_port', '/SIMULATION CONTROL/EXECUTION CONTROL/SOLVER STEP CONTROL/LICENSE CONTROL/Shared License Port');
  $self->_newsetting('size_nr', '/SIMULATION CONTROL/EXECUTION CONTROL/SOLVER STEP CONTROL/MEMORY CONTROL/Real Memory Override');
  $self->_newsetting('size_ni', '/SIMULATION CONTROL/EXECUTION CONTROL/SOLVER STEP CONTROL/MEMORY CONTROL/Integer Memory Override');
  $self->_newsetting('size_nd', '/SIMULATION CONTROL/EXECUTION CONTROL/SOLVER STEP CONTROL/MEMORY CONTROL/Double Precision Memory Override');
  $self->_newsetting('size_nc', '/SIMULATION CONTROL/EXECUTION CONTROL/SOLVER STEP CONTROL/MEMORY CONTROL/Character Memory Override');
  $self->_newsetting('size_nl', '/SIMULATION CONTROL/EXECUTION CONTROL/SOLVER STEP CONTROL/MEMORY CONTROL/Logical Memory Override');
  $self->_newsetting('size_part_nr', '/SIMULATION CONTROL/EXECUTION CONTROL/PARTITIONER STEP CONTROL/MEMORY CONTROL/Real Memory Override');
  $self->_newsetting('size_part_ni', '/SIMULATION CONTROL/EXECUTION CONTROL/PARTITIONER STEP CONTROL/MEMORY CONTROL/Integer Memory Override');
  $self->_newsetting('size_part_nd', '/SIMULATION CONTROL/EXECUTION CONTROL/PARTITIONER STEP CONTROL/MEMORY CONTROL/Double Precision Memory Override');
  $self->_newsetting('size_part_nc', '/SIMULATION CONTROL/EXECUTION CONTROL/PARTITIONER STEP CONTROL/MEMORY CONTROL/Character Memory Override');
  $self->_newsetting('size_part_nl', '/SIMULATION CONTROL/EXECUTION CONTROL/PARTITIONER STEP CONTROL/MEMORY CONTROL/Logical Memory Override');
  $self->_newsetting('size_interp_nr', '/SIMULATION CONTROL/EXECUTION CONTROL/INTERPOLATOR STEP CONTROL/MEMORY CONTROL/Real Memory Override');
  $self->_newsetting('size_interp_ni', '/SIMULATION CONTROL/EXECUTION CONTROL/INTERPOLATOR STEP CONTROL/MEMORY CONTROL/Integer Memory Override');
  $self->_newsetting('size_interp_nd', '/SIMULATION CONTROL/EXECUTION CONTROL/INTERPOLATOR STEP CONTROL/MEMORY CONTROL/Double Precision Memory Override');
  $self->_newsetting('size_interp_nc', '/SIMULATION CONTROL/EXECUTION CONTROL/INTERPOLATOR STEP CONTROL/MEMORY CONTROL/Character Memory Override');
  $self->_newsetting('size_interp_nl', '/SIMULATION CONTROL/EXECUTION CONTROL/INTERPOLATOR STEP CONTROL/MEMORY CONTROL/Logical Memory Override');
  $self->_newsetting('size_cclsetup_nr', '/SIMULATION CONTROL/EXECUTION CONTROL/CCLSETUP STEP CONTROL/MEMORY CONTROL/Real Memory Override');
  $self->_newsetting('size_cclsetup_ni', '/SIMULATION CONTROL/EXECUTION CONTROL/CCLSETUP STEP CONTROL/MEMORY CONTROL/Integer Memory Override');
  $self->_newsetting('size_cclsetup_nd', '/SIMULATION CONTROL/EXECUTION CONTROL/CCLSETUP STEP CONTROL/MEMORY CONTROL/Double Precision Memory Override');
  $self->_newsetting('size_cclsetup_nc', '/SIMULATION CONTROL/EXECUTION CONTROL/CCLSETUP STEP CONTROL/MEMORY CONTROL/Character Memory Override');
  $self->_newsetting('size_cclsetup_nl', '/SIMULATION CONTROL/EXECUTION CONTROL/CCLSETUP STEP CONTROL/MEMORY CONTROL/Logical Memory Override');
  $self->_newsetting('numa', '/SIMULATION CONTROL/EXECUTION CONTROL/SOLVER STEP CONTROL/MEMORY CONTROL/NUMA Containment');
  $self->_newsetting('affinity', '/SIMULATION CONTROL/EXECUTION CONTROL/SOLVER STEP CONTROL/PROCESS AFFINITY CONTROL/Option');
  $self->_newsetting('output_summary_option');
  $self->_newsetting('solver_debug_prompt' );
  $self->_newsetting('solver_debug_prompt_user' );
  $self->_newsetting('resInfo' );
  $self->_newsetting('defRemesh' );
  $self->_newsetting('resRemesh' );
  $self->_newsetting('outRemesh' );
  $self->_newsetting('num_remesh' );
  $self->_newsetting('remeshObjs');
  $self->_newsetting('configname');
  $self->_newsetting('continue_history'); 
  $self->_newsetting('cont_hist_from', '/SIMULATION CONTROL/EXECUTION CONTROL/RUN DEFINITION/INITIAL VALUES SPECIFICATION/INITIAL VALUES CONTROL/Continue History From'); 
  $self->_newsetting('use_mesh_from', '/SIMULATION CONTROL/EXECUTION CONTROL/RUN DEFINITION/INITIAL VALUES SPECIFICATION/INITIAL VALUES CONTROL/Use Mesh From'); 

  1;
}

=head2 DESTRUCTORS

=head3 EndOfRun

  $job->EndOfRun();

Depending on what state the job is in, this will perform various
actions to make sure it is consistent before it shuts down.  This
function should I<always> be called at the end of a job.  It does not
do things like auto-updating files; currently, the only action it
performs is to call C<EndOutFile>, which is necessary in order not to
confuse the solver manager.

This function is idempotent.

=cut

sub EndOfRun {
  my $self=shift;

  if (exists $self->{outfile} 
      and $self->{outfile}->{started}) {
    $self->EndOutFile();
  }
  1;
}

=head3 TidyupRun

  $job->TidyupRun();

This function does more to ensure that a run is stopped cleanly than
C<EndOfRun> does; it is a "standard cleanup" rather than an emergency
exit hook.

Currently it calls the C<output_marked_files_done> method on the
L<CFX5::WorkDir> object associated with this job, if any, and then
(unless C<save_workdir> is TRUE) deallocates the working directory.
In addition, if there is no 'res' file in the working directory, it
will check the C<resfile_from> setting to try to find an alternative
file which it can output.

If the PVM daemon is running (C<ppe_running>), and it has been told to
stop it if necessary (C<autostop_ppe>, which is true by default), it
will shut down the PVM daemon.

Finally, it also calls the C<EndOfRun> function.

=cut

sub TidyupRun {
  my $self=shift;

  # we'll pass these back to whatever GUI we're using if they look
  # interesting
  my @fileinfo;

  if (not $self->start_method_obj()->is_indirect()){
    #
    # if necessary halt the ANSYS solver
    # 
    if ($self->ansys_running()){
      if($self->stop_now() and $self->stop_now()==1){
        $self->StopAnsys(1);
      } else {
        $self->StopAnsys();
        $self->outobj()->message("The results from this run of the ANSYS solver have been written to", $self->ansys_workdir());
      }
    }  

    if( defined $self->ansys_port_file() && -e $self->ansys_port_file() ){
      unlink $self->ansys_port_file();
    }
  
    #
    # if necessary, halt the PVM daemon
    #
    if ($self->start_cfx() and $self->ppe_running() and $self->autostop_ppe()) {
      my $ppe=$self->ppe_obj();
      $ppe->stop();
      $self->ppe_running(0);
    }

    # Get the final CCL, other configurations may require it
    $self->read_ccl(join_file($self->rundir(), 'ccl')) if $self->HaveFile('ccl');

    # Run APIP
    unless ($self->parent()->indirect_startup()) {
      $self->GetSubtaskObject('APIP')->Run(@_);
    }

    #
    # Clean out the working directory
    #
    if (exists $self->{settings}->{rundir_obj}) {
      my $rundir=$self->{settings}->{rundir_obj};
      if (ref($rundir) and $rundir->isa('CFX5::WorkDir')) {
        # it's an object

        if ($self->stop_now() # need to check for this in case it's non-numeric
            and $self->stop_now() == 1
            and $self->HaveFile('stp')
            and !$self->is_sc_run() # No need for CFX-SC run because the solver ignores STOP request
            and !$self->HaveFile('solver.sta')) {
          # for the Solver Manager, if the solver didn't catch the stp
          # file.  Do this directly as we care deeply and passionately
          # about the formatting.  If we caught an error as well, then
          # don't give this message.
          my $fh=$self->outobj()->open();
          if ($fh) {
            print $fh qq( Execution terminating: "STP" file found.\n\n);
            $self->{termcond} = ["Stop instruction received"] ; 
            $self->{termtype} = [0] ; 
            $self->outobj()->close()
              or _error("Cannot finish writing to out file: $!");
          } else {
            _error("Cannot write to out file: $!");
          }
        }

        unless ($self->HaveFile('res')) {
          _debug("no res file in the working directory");
          if (defined $self->resfile_from()) {
            my $file=$self->resfile_from();
            _debug("Res file from $file");
            unless (test_f $file) {
              _debug("File missing, ignoring");
            } else {
              if ($self->stop_now() and $self->stop_now() > 1) {
                # error exit
                if (move_file($file, $self->resfile_err())) {
                  push @fileinfo, "    Error Results File = ". $self->resfile_err();
                } else {
                  _error("Unable to rename $file to", $self->resfile_err(), ": $!");
                }
              } else {
                if (move_file($file, $self->resfile())) {
                  push @fileinfo, "    Results File = ". $self->resfile();
                } else {
                  _error("Unable to rename $file to", $self->resfile(), ": $!");
                }
              }
              # we don't have it, we don't care, ignore it.
              $rundir->mark_output_file_ignore('res') 
                if $rundir->know_file('res');
            }
          }
        } else {
          # we've got one, record it
          push @fileinfo, "    Results File = ". $self->resfile();
          $self->outobj()->message("The results from this run of the ANSYS CFX Solver have been written to", $self->resfile());
        }

        if ($self->HaveFile('crash')) {
          $rundir->notice_file('crash');
          my $xtra='';
          if ($self->HaveFile('crash.lck')) {
            # if we died while writing the crash file, warn about this
            # at the same time
            $xtra="  Note that a lock file was left for the crash file when the solver exited, and so it is probably incomplete.";
            $rundir->notice_file('crash.lck');
            $rundir->remove_file('crash.lck');
          }
          if ($rundir->output_file_done(crash => $self->resfile_err())) {
            _warn("The ANSYS CFX Solver has written a crash recovery file.  This file has been saved as", $self->resfile_err(), "and may be an aid to diagnosing the problem or restarting the run.  More details should be available in the solver output section of the output file.$xtra");
            push @fileinfo, "    Error Results File = ". $self->resfile_err();
          } else {
            _warn("The ANSYS CFX Solver has written a crash recovery file, but an error occurred while trying to save the file.  The working directory\n  ",$self->rundir(),"\nwill not be deleted: please tidy up and remove this directory manually.$xtra");
            $self->save_workdir(1);
          }
        }

        # check for unexpected files, or files we explicitly decided to
        # leave
        {
          my @extrafiles = $self->list_remaining_files($rundir);
          my @unrec;
          my $mon_data_option = 'Delete Old Files';
          my $ccl_param = '/FLOW/OUTPUT CONTROL/MONITOR OBJECTS/MONITOR DATA RETENTION/Option';
          if ($self->have_ccl_parm($ccl_param)) { $mon_data_option = $self->read_ccl_parm($ccl_param); }

          for my $f (@extrafiles) {
            if ($f =~ m/\.smn/i and $mon_data_option eq 'Delete Old Files') {
              # steady-state solver monitoring files, ignore if option
              1;
            } elsif ($f =~ m/\.lck$/i) {
              # if we've already signalled some errors, don't worry
              # about lock files, they're quite likely to be present
              unless (error_pending()) {
                _warn("A CFX-style lock file was left in the working directory at the end of the run.",
                      "\nThis probably means that one or more subsidiary programs were unable to release $f before the ANSYS CFX solver finished,",
                      "perhaps because that file was being monitored by CFD-Post.",
                      "\nThere should be more details in the error messages above, and in the","\noutput file for this run.");
              }
            } elsif ($f eq 'core') {
              # we don't want to save these usually
              _note("A core file was found in the working directory at the end of the run, and will not be saved.")
                unless $self->save_workdir();
            } elsif ($f =~ m!^sm\..*\.lock$!) {
              # SM lock file, we'll deal with these later
              1;
            } elsif ($f =~ m!^tmp\.!i) {
              # we assume tmp.* are temporary files, and just throw them
              # away.  I imagine this will not be a problem!
              1;
            } elsif ($f =~ m!^licdebug\.cfx\.[0-9]+\.out$!
                     and not $rundir->is_non_empty($f)) {
              # ANSYS licensing code writes these debug logs, but
              # usually empty; just throw them away if they're empty
              1;
            } elsif ($f eq 'startup.ccl') {
              # assume this is from an indirect start method.  Really,
              # we should check!
              1;
            } elsif ($f eq 'monpost') {
              # Text file index for CFD-Post monitoring. Dont need to keep.
              1;
            } elsif ($f =~ m/\.(trn|bak)$/i) {
              # Transient files should have been dealt with already
              1;
            } else {
              push @unrec, $f;
            }
          }
          if (@unrec) {
            #_note("Saving", scalar(@unrec), "unrecognised file(s): ", join(' ',@unrec));
            $self->CleanupFiles(@unrec);
            $self->outobj()->message("The following user files have been saved in the directory", $self->outdir().":\n\n".
                                     join(", ", @unrec));
            #_warn("Removing", scalar(@unrec), "unrecognised file(s): ", join(' ',@unrec));
          }
        }

        # check for missing files
        {
          my @missingfiles = $rundir->list_missing_files();
          my @missing;
          for my $f (@missingfiles) {
            if ($f eq 'res') {
              _info("\nNo results were written for visualisation or a restart.");
              $rundir->mark_output_file_ignore($f);
            } elsif ($f eq 'out') {
              _info("\nNo ANSYS CFX Output File was written by the ANSYS CFX Solver.");
              $rundir->mark_output_file_ignore($f);
            } else {
              push @missing, $f;
            }
          }
          if (@missing) {
            _warn("The following", scalar(@missing), "expected file(s) are missing: ", join(' ',@missing));
          }
        }

        # Record why we're leaving
        my $exittype;
        if ($self->stop_now() and $self->stop_now() > 1) {
          $exittype = 'ERROR';
        } elsif ($self->stop_now()) {
          $exittype = 'USER';
        } else {
          $exittype = 'NORMAL';
        }

        # Finalize the G/RES_INFO dataset and write to the result file unless this is an indirect run
        unless($self->parent()->indirect_startup()){
          $self->finalize_resinfo($exittype);
        }

        # save the remaining files we want to save
        $rundir->output_marked_files_done();

        # Suspected Workbench run: Warn that final location may differ
        if ($self->is_wbrun()) {
          $self->outobj()->message("For CFX runs launched from Workbench, the final locations of directories and files generated may differ from those shown.");
        }

        # 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) {
          my $exitinfo;
          if($self->parent()->indirect_startup()){
             $exitinfo="INDIRECT INFORMATION";
          }
          else {
             $exitinfo="RUN CONCLUSION INFORMATION";
          }


          # first we write a message that we've finished
          my $name=$cclout->gui_objname() || '';
          my @msg=(
                   "GUI INFORMATION:",
                   "  $exitinfo:  $name",
                   "    Run Name = ".basename($self->fullname()),
                   @fileinfo,
                   "    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: $!");
          }

          # Write the run information to the res file
          $self->write_runinfo();
        
          # now shut down all this stuff.
          remove_output_object($cclout, undef);
          $self->outobjccl('');
        }

        # wait for SM to exit
        $self->wait_for_sm_exit() unless $self->parent()->indirect_startup();

        # write custom data dir info if applicable
        if(test_f $self->resfile() and 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}) ;

          my $io = CFX5::DefFile->new($self->resfile()) ;

          unless (ref $io) {
            _error("Write custom data dir failed - could not open file: ".$self->resfile());
          }

          unless ($io->write_dataset($ds)) {
            _error("Write custom data dir failed - dataset write filed");
          } 
        }

        # now we can delete the directory
        unless ($self->save_workdir() or $self->parent()->indirect_startup()) {
          $rundir->junk_directory();
        }
        $self->{settings}->{rundir_obj} = ''; # now it won't be re-generated
      } else {
        if ($^W) { carp "Additional invocation of TidyupRun"; }
      }
    }

    unless ($Config{windows_nt}) {
      $self->SummariseTimes();
    }

    unless( $self->parent()->indirect_startup()){
      $self->EndOfRun();
    }
  }
  else {
    #Indirect start method, gather necessary info from the results file

    my $runinfoapi=CFX5::CCLI::new_api_object();

    # Did the user interrupt the job or an error?
    my $resfile;
    my @runinfo;
    my @runccl;
    my $exittype;

    if(test_f $self->resfile()){
      $exittype='NORMAL';
      $resfile=$self->resfile();
      push @fileinfo, "    Results File = $resfile";
    }
    elsif(test_f $self->resfile_err()){
      $resfile=$self->resfile_err();
      push @fileinfo, "    Error Results File = $resfile";
    }


    if(defined $resfile){
      @runinfo = GetDataSet($resfile, "G/RUNINFO");
      $runinfoapi->parseCCLData(\@runinfo, 0);
      (my $name = basename($self->fullname())) =~ s![-_]! !g;
      $name = "Run $name";
      $runinfoapi->getStringValue("GUI INFORMATION/INDIRECT INFORMATION: $name/Reason for Exit", \$exittype); 
      @runccl = GetDataSet($resfile, "G/COMMANDS");
      $self->read_ccl(\@runccl);
    }
    else {
      $exittype='ERROR';
      _error("Indirect solver job failed");
    }

    if($exittype eq 'USER'){
      $self->{termcond}=["Stop instruction received"];
      $self->{termtype}=[0];
    }

    my $cclout=$self->outobjccl();
    if ($cclout) {
      # first we write a message that we've finished
      my $name=$cclout->gui_objname() || '';
      my @msg=(
               "GUI INFORMATION:",
               "  RUN CONCLUSION INFORMATION:  $name",
               "    Run Name = ".basename($self->fullname()),
               @fileinfo,
               "    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: $!");
      }

      # Write the run information to the res file
      $self->write_runinfo();
        
      # now shut down all this stuff.
      remove_output_object($cclout, undef);
      $self->outobjccl('');
    }

    unless($self->parent()->indirect_startup()){
      # wait for SM to exit
      $self->wait_for_sm_exit();

      $self->GetSubtaskObject('APIP')->Run(@_);

      # Save the working directory if a child start method didn't finalize
      unless ($self->child_sm_isfinalized()) {        
        _warn("A child start method did not finalize normally. To preserve files, the working directory:\n  ",$self->rundir(),"\nwill not be deleted. Please tidy up and remove this directory manually. Files may be incomplete.");
        $self->save_workdir(1);
      }

      unless ($self->save_workdir()) {
        $self->{settings}->{rundir_obj}->junk_directory();
      }

      $self->finalize_resinfo($exittype);
    }
  }
  1;
}

=head4 child_sm_isfinalized

  if ($job->child_sm_isfinalized()) ....

  Checks for an INDIRECT INFORMATION CCL object in the gui-err.txt file
  which indicates finalization of a child start method.

=cut

sub child_sm_isfinalized
{
  my $self = shift;

  my $msg_file = join_file($self->rundir,'gui-err.txt');
  if (test_r($msg_file)) {
    my @ccltext = split(m/\n\r*/, CFX5::FileOps::get_file_contents($msg_file));
    my $api = CFX5::CCLI->new_api_object();
    unless ($api->parseCCLData(\@ccltext)) {
      my @childobjlist;
      $api->getChildObjectList('GUI INFORMATION',\@childobjlist);
      for my $childobj (@childobjlist) {
        return 1 if ($childobj =~ m/INDIRECT INFORMATION/);
      }
    }
  }

  return 0;
}


sub finalize_resinfo {
  my $self=shift;
  my $exittype=shift;

  return if ($self->lets_pretend());

  # Finalize the G/RES_INFO dataset and write to result file
  # --> Error:    make sure that there are no entries for the current configuration
  # --> No Error: introduce entry for the current configuration
  my $currentConfig = $self->parent()->{currentconfig} ;
  my $simStep  = $self->parent()->{simstep} ; 
  my $confName = $self->parent()->{configmap}{$currentConfig}{name};
  my $confStep = $self->parent()->{configmap}{$currentConfig}{nextstep}-1;
  if ($exittype eq "ERROR") {
    $self->resInfo()->ClearEntries($simStep,$confStep,$confName) ;
  }
  else {
    my $localres = ((test_f $self->resfile()) ? $self->resfile() : join_file($self->rundir(),'res'));
    my $pathLevels = ($self->parent()->singleconfig() ? 1 : 2); 
    my $resInfoPath = basename($self->resfile(),$pathLevels);
    if (test_f $localres) {
      $self->resInfo()->AddEntry($simStep,$confStep,$confName,$resInfoPath) ;
      $self->resInfo()->WriteToFile($localres) ;
    }
  }
  1;
}

# hook for bloodsim
sub list_remaining_files {
  my $self=shift;
  my $rundir=shift;
  return $rundir->list_remaining_files();
}

use CFX5::Config::def CFX5_SOLVE_MONITOR_TIMEOUT => sub { 60; };

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;
}

=head4 CleanupFiles

  $job->CleanupFiles(@files);

This call is used to clean up I<unrecognised> files from the working
directory, and move them to the "random output files" directory.  It
is used for the transient and backup files after a solver run, and
also for unknown files left at the end of a run.

It does not give any messages on success, although it will signal
errors on failure in the usual way.  If called with an empty list, it
will do nothing and return success.

=cut

sub CleanupFiles {
  my $self=shift;
  my @files=@_;

  return 1 unless @files;

  #
  # get the working directory object
  #
  my $workdir = $self->rundir_obj();
  return 0 unless (defined $workdir);

  #
  # yes, we've got some, so we have to make a directory to put them in
  #
  my $outdir=$self->outdir();
  my $succ=CFX5::FileTests::datadir_cw
    ($outdir, "Directory $outdir for Additional Output Files");

  # now try to make the directory, if we need to
  if ($succ and ! test_d $outdir) {
    $succ=create_directory($outdir);
    unless ($succ) {
      _error("Error creating directory $outdir for additional output files: $!");
    } else {
      # this test should be redundant, but it does no harm to check.
      # There are ways it could fail, for example if the user has a
      # duff umask, although in that case we're unlikely to get this
      # far ...
      $succ=CFX5::FileTests::datadir_w
        ($outdir, "Directory $outdir for Additional Output Files");
    }
  }

  # OK, we've got a directory, now move all the results.
  if ($succ) {
    for my $f (@files) {
      unless ($workdir->notice_file($f => join_file($outdir,$f))
              and $workdir->output_file_done($f)) {
        $succ=0;
      }
    }
  }

  $self->TraceLog('cleanup-files',@files);

  unless($succ) {
    # for whatever reason, we're short a directory to dump the
    # transient results files in.  So we'll save the working
    # directory, and let the user know what we are doing.
    _warn("An error has occurred during creation of a directory for additional output files.  To avoid losing results, the working directory", $self->rundir(), "will be kept at the end of the run.  Please tidy this directory up yourself when you have extracted what you need from it.");
    $self->save_workdir(1);
    return 0;
  }

  # and return our status
  $succ;
}

=head3 DESTROY

This object has a C<DESTROY> method, which simply calls C<EndOfRun>.

=cut

sub DESTROY {
  $_[0]->EndOfRun();
}

=head2 SETTINGS

See L<CFX5::Simulation::Settings> for more details.  For convenience, the
summary of usage is reproduced here.

The settings functions are all called as follows.  To set a setting:

  $job->setting("new-value");
  $job->setting(undef);

to get the current value of a setting:

  $job->setting();

The return value is the (previous in the first case) value of the
setting, which can be the default value if it has not been explicitly
set.  Errors in the arguments are indicated by called C<_error>, so
one may do something like:

  $job->setting1($s1);
  $job->setting2($s2);
  exit_if_error();

See C<exit_if_error> and C<error_pending>.  If a setting succeeds,
this does not mean that there are no problems, just no obvious ones.
For example, if one is doing a solver run with CCL the definition file
must currently be writable (this will change before realease, though),
but the C<deffile> function will quite happily accept it; it will not
accept it if the file does not exist.

Each setting I<setting> also has an associated function
C<_ok_I<setting>>, which is responsible for checking that a putative
value for a setting is OK; if not, it should signal an error and
return FALSE.  There is also a function C<_def_I<setting>>, which
returns the default value for a setting.  These functions are I<not>
designed to be called from outside the module.

=head3 ChooseName

This function works out a value for C<fullname> based on the current
value of C<name>, by appending _I<999>.  It will give an error if
C<name> is not defined, via C<internal_error> as normal, and return
undef; if it succeeds, it will return the new name.  Note that this
I<will overwrite> any existing setting of C<fullname>, but note that
there is no default for C<fullname>, so if you want to set it if not
set, do something like

  $job->ChooseName()
    unless defined $job->fullname();

=head4 The Algorithm

It looks for files with extensions "out", "res", "dir" and "trn", or
ending with the I<_999> but no extension; if any of these files exist,
it considers this number for being a maximum.  Once it has found a
maximum, it increments it and uses this as the new job number.

=cut

sub ChooseName {
  my $self=shift;
  my $prefix=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;
  if(defined $prefix){
    $probdir=$prefix;
  }
  else {
    $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;

  # Consider results file names referenced by the RES_INFO datasets
  # in any initial values files and the def file (which could be a res file)
  if ($self->respect_suffixhist()) {
    my $inifile_last = undef;
    my $resfile_last = undef;
    my @inifiles = $self->GetIniFiles();
    my $deffile = $self->deffile();
    push @inifiles, $deffile if (defined $deffile);
    for my $inifile (@inifiles)
    {
      next unless (test_f $inifile);
      my $resinfo = new CFX5::ResInfo();
      if ($resinfo->ReadFromFile($inifile)) {
        for (my $i=0; $i<$resinfo->GetCount(); $i++) {
          my %resentry = $resinfo->GetEntry($i);
          my $f = basename($resentry{FilePath});
          if ($f =~ m{^\Q${probname}\E_([0-9][0-9][0-9])(\.res)?$}i) {
            if ($1 > $max) {
              $max=$1;
              $inifile_last = basename($inifile);
              $resfile_last = $f;
            }
          }
        }
      }
    }

    if ($inifile_last)
    {
      _print(sprintf("Results file '%s' was referenced by initial values file '%s'. The current run will therefore be named '%s_%3.3d'.",
                     $resfile_last, $inifile_last, $probname, $max+1));
    }
  }

  # Check suffix is valid
  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(join_file($probdir, $probname.'_'.$max));

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

  $self->fullname();
}

=head3 FinishedStage

  $self->FinishedStage('solver');

This function is used with the C<interrupt_after> setting, to note that a
named stage has now finished.  If C<interrupt_after> is set, this function
will set the C<interrupt_now> setting when necessary.  The C<stages_run>
setting is used to keep track of what has been done.

=cut

sub FinishedStage {
  my $self=shift;
  my $stage=shift;

  # update the list of stages run
  my @run=@{$self->stages_run()};
  push @run, $stage;
  $self->stages_run(\@run);

  # now, see if we need to exit
  return unless $self->interrupt_after();

  my ($where,$count)=split(/\*/,$self->interrupt_after());
  $count=1 unless defined $count;
  for (grep { $_ eq $where } @run) {
    $count--;
  }

  _trace "$count runs of $where remaining";

  if ($count < 1) {
    $self->interrupt_now(1);
  }
}

=head3 DERIVED SETTINGS

The settings in this section are those which were "first class" in
previous versions, but are now derived from other settings.  They are
preserved for compatibility only.

=head4 do_solve

Returns TRUE if the run mode is "Full", or FALSE otherwise (ie,
"Partition-only").  Sets the run mode in the obvious way!

=cut

sub do_solve {
  my $self=shift;
  my $c;

  # Return false unless we are starting the CFX solver
  return 0 unless $self->start_cfx();
  
  if (@_) {
    $c=$self->do_solve();
    if (@_ > 1) {
      internal_error("Too many arguments to do_solve");
    }
    my $new=shift;
    if ($new) {
      $self->run_mode("Full");
    } else {
      $self->run_mode("Paritioner Only");
    }
  }
  unless (defined $c) {
    if ($self->run_mode() eq 'Full') {
      $c=1;
    } else {
      $c='';
    }
  }
  $c;
}

=head4 parallel

Set to: 1 for parallel mode, 0 for serial mode.

=cut

sub parallel {
  my $self=shift;
  if (@_) {
    internal_error("Cannot set derived attribute parallel");
  }
  internal_error("XXX don't call me");
  $self->parallel_mode() eq 'Serial' ? '' : 1;
}

=head4 do_partition

1 to run the partitioner I<on this adaption step>.  Derived from the
settings of C<run_mode>, C<parallel_mode> and C<parfile_in>.

=cut

sub do_partition {
  my $self=shift;
  return 0 unless $self->prep_cfx();
  if (@_) {
    internal_error("Cannot set derived attribute do_partition");
  }
  if ($self->run_mode() eq 'Partitioner Only') {
    return 1;
  }
  if (defined $self->processes() and $self->processes() > 1) {
    if (defined $self->parfile_in()) {
      if ($self->first_solver_run()) {
        return 0;
      }
      return 1;
    }
    return 1;
  }
  return 0;
}

=head2 TIMING INFORMATION

Routines to record and to show information about the time taken by
various sub-processes.

=head3 RunningComponent

  $job->RunningComponent('solver');
  # run the solver
  $job->RunningComponent('');

Sets the name of the component which is currently running, and records
the process timings so far.  This information can then be used to show
a summary of times at the end of a run.

So that the time taken by the script itself can be determined, when no
external component is running, the function should be called with the
empty string as the argument.  Note that we assume that the time taken
by this function is negligible.

=cut

sub RunningComponent {
  my $self=shift;
  my $comp=shift;

  $comp='script' if $comp eq '';

  # trace our progress
  _trace("Entering component $comp");

  $self->{timings}={} unless defined $self->{timings};
  my $tm=$self->{timings};

  unless (exists $tm->{current}) {
    $tm->{current} = {
                      comp => 'script',
                      time => [0.0,0.0,0.0,0.0],
                     };
  }

  # the start and end times
  my @now=times();
  my @then=@{$tm->{current}{time}};

  # what component took that time?
  my $prevcomp=$tm->{current}{comp};
  $tm->{comps}={} unless exists $tm->{comps};
  $tm->{comps}{$prevcomp}=[0,0,0,0] unless exists $tm->{comps}{$prevcomp};

  # add the difference to that component's time
  for my $c (0..3) {
    $tm->{comps}{$prevcomp}[$c] += $now[$c] - $then[$c];
  }

  # now record the new current component
  $tm->{current} = {
                    comp => $comp,
                    time => \@now,
                   };

  # OK, we're done
  1;
}

=head3 SummariseTimes

  $job->SummariseTimes();

Writes a summary of the timing information to the out file.  Should
probably only be called once.  By default, this routine will do
nothing, but users can set the CFX5_SHOW_RUN_TIMING variable to get it
to display the information.

=cut

sub SummariseTimes {
  my $self=shift;

  # no info
  return 0 unless exists $self->{timings}{comps};

  # we don't do this any more
  return 1 unless $Config{CFX5_SHOW_RUN_TIMING};

  $self->OutFileHeader('Run Timing Information');

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

  # make sure we're up-to-date (ish)
  $self->RunningComponent('');

  # now, take what we know, and make it nice.
  my $info=$self->{timings}{comps};
  my %names=(
             solver => 'Flow Solver',
             partitioner => 'Partitioner',
             refiner => 'Mesh Adaption',
             script => 'Supporting Script',
             ccl => 'CFX Command Language Processing',
            );
  my %used;
  for my $c (keys %$info) {
    $used{$c}++;
  }
  for my $c (qw(ccl partitioner solver refiner script)) {
    if (exists $used{$c}) {
      my @c=@{$info->{$c}};
      my $total=$c[0]+$c[1]+$c[2]+$c[3];
      my $system=sprintf("%2.02f",$c[1]+$c[3]);
      print $fh " $names{$c}: ", _FormatTimeString($total), " ($system system)\n";
      delete $used{$c};
    }
  }
  # find any missing
  for my $c (keys %used) {
    my @c=@{$info->{$c}};
    my $total=$c[0]+$c[1]+$c[2]+$c[3];
    my $system=sprintf("%2.02f",$c[1]+$c[3]);
    print $fh " FIXME: component $c: ", _FormatTimeString($total), " ($system system)\n";
    delete $used{$c};
  }

  {
    my @c=times();
    print $fh "\n Total time taken: ", _FormatTimeString($c[0]+$c[1]+$c[2]+$c[3]), "\n\n";
  }

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

  1;
}

=head4 _FormatTimeString

  print "10000 seconds is ", _FormatTimeString(10000);

Converts a number of seconds (which may include a decimal part) into a
descriptive text string.

=cut

sub _FormatTimeString {
  my $secs=shift;
  my @rv;
  my $go;

  if ($secs > 24*60*60) {
    my $days=int($secs/(24*60*60)); # int rounds down
    push @rv, "$days days";
    $secs -= $days*24*60*60;
    $go++;
  }
  if ($go or $secs > 60*60) {
    my $hours=int($secs/(60*60)); # int rounds down
    push @rv, "$hours hours";
    $secs -= $hours*60*60;
    $go++;
  }
  if ($go or $secs > 60) {
    my $mins=int($secs/60); # int rounds down
    push @rv, "$mins minutes";
    $secs -= $mins*60;
    $go++;
  }
  $secs=sprintf("%2.02f",$secs);
  if ($go) {
    push @rv, "and $secs seconds.";
  } else {
    push @rv, "$secs seconds.";
  }

  my $last=pop @rv;
  join(", ",@rv)." ".$last;
}

=head2 USER SETTINGS

The functions in this section are designed to enable extra
information, which was not anticipated in the design -- probably
because the object is being used in a non-standard way -- to be
carried around with the job object without risking overwriting
important internal data.

Each client should choose a unique name, which is used to prevent
overlap between alternative clients.  For example, the regression
tests for this module use "testing".  Under this name, any number of
key/value pairs may be stored; if more than this is required, an
arbitary Perl reference may be stored in one of the values.  It is the
responsibility of clients to ensure that they do not overlap with
other clients, but in particular, ANSYS CFX standard modules should
probably not use this function.  Instead, if more functionality is
required this object should be extended, subclassed or contained.

=head3 usersetting

This function is used to manipulate the user settings.  It can be used
to set a value:

  $job->usersetting('testing', def_md5 => md5sum($job->deffile()));

and to read it:

  $md5sum = $job->usersetting('testing', 'def_md5');

=cut

sub usersetting {
  my $self=shift;
  my $class=shift;

  $self->{usersettings}{$class} = {}
    unless exists $self->{usersettings}{$class};
  my $sets=$self->{usersettings}{$class};

  my $name=shift;
  internal_error("Missing or undefined setting name in usersetting")
    unless defined $name;

  my $old=$sets->{$name};
  if (@_) {
    $sets->{$name} = shift;
  }
  internal_error("Extra arguments to usersetting")
    if (@_);

  $old;
}

=head2 COMPONENT-BASED CHECKS, PREPARATION AND RUNNING

The functions in this section come in triples: each component I<Comp>
has three user-callable functions CheckI<Comp>, PrepareI<Comp> and
RunI<Comp>, which check that the component has what it requries,
prepare a directory for it, and both prepare a directory and run the
component, respectively.  There is currently no way to run the
component without setting it up first.

Internally, each component has a C<_Do*> function, which takes the
same arguments as its C<Check*> and C<Prepare*> counterparts (usually
none), but with an additional I<first> argument which is "1" to do the
setup, "0" to only do the checks.  Some components will also allow "2"
to run the component; others will have separate Run functions instead
of supporting this extra argument.  The C<_Do> functions should not be
called by users, and are subject to change.

=head3 Misc Helpers

=head4 StartOutFile

  $job->StartOutFile() or return 0;

If the output file has not already been started, starts it; otherwise,
a NOP.  Should be used as soon as any files for a job exist, usually
as part of preparation for a component.  It may also be called
directly.

If this function is called after C<EndOutFile> it will signal an
error; an output file may not be re-started after it has been ended.

Starting an output file basically involves adding the following lines
-- or similar -- to the top:

  This run of the ANSYS CFX Solver started at 03:32:40 on 04 Aug 1999
  by user cfx5test on helios (sun_usparc_solaris2.7)

This is used by the solver manager.

It also adds a handler to L<CFX5::Output> via the C<add_output_object>
call, such that error messages get copied into the output file.

=cut

sub StartOutFile {
  my $self=shift;
  $self->{outfile} = {} unless exists $self->{outfile};
  if ($self->{outfile}->{ended}) {
    # oops, can't start again!
    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;

  # If output file path length > 255 characters, solver won't use it correctly,
  # so use 'out' instead, but only if symlinks are supported.
  my $outfile_solver = fullpath($outfile);
  if (length($outfile_solver) > 255)
  {
    unless (CFX5::FileOps::can_symlink())
    {
      _error("Output file path would exceed 255 characters and symlinks are not supported: $outfile_solver");
      return 0;
    }
    $outfile_solver = 'out';
  }

  # start the file -- make a CFX5::Output::File object for it
  my $object=new CFX5::Output::OutFile($outfile);
  add_output_object($object);
  $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";
  }

  if (0) {
    my $str=$self->solver_exec_name();
    my $exec=$self->solver_exec();
    $text .= "Using the $str from $exec.\n\n";

    if ($self->write_debug()) {
      $self->TraceLog("solver-is",$exec);
    }
  }

  # 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";

  if (0) {
    # write to the file
    my $fh=$object->open();
    unless ($fh) {
      _error("Cannot write to output file $outfile: $!");
      return 0;
    }
    print $fh $text;
    unless ($object->close()) {
      _error("Cannot finish write to output file $outfile: $!");
      return 0;
    }
  } else {
    $object->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();

  # Export output file name for use by the solver
  setconf('CFX_SOLVER_OUTPUT_FILE' => $outfile_solver);
  export_conf('CFX_SOLVER_OUTPUT_FILE');

  # If defined, export output summary option used by the solver
  my $summary_option = $self->output_summary_option();
  if (defined $summary_option) {
    setconf('CFX_OUTPUT_SUMMARY_OPTION' => $summary_option);
    export_conf('CFX_OUTPUT_SUMMARY_OPTION');
  }

  # yay, we're done!
  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=shift;

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

  if(0) {
    print $fh "\n", "=" x 80, "\n";
    my $pad=((80-length($header))/2);
    print $fh " " x $pad, $header, "\n";
    print $fh "-" x 80, "\n\n";
  } else {
    my $width=72;
    print $fh "\n +", "-" x ($width-4), "+\n";
    print $fh " |", " " x ($width-4), "|\n";
    my $pad1=int(($width-4-length($header))/2);
    my $pad2=($width-4-$pad1-length($header));
    print $fh " |", " " x $pad1, $header, " " 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;
  }

  # fine

  1;
}

=head4 EndOutFile

Finish the output file, by adding a line

  This run of the ANSYS CFX Solver has finished.

to the end.  If called more than once, or if called before
C<StartOutFile>, it will signal an error.

This does I<not> write the final information, such as timing
information, to the output file; see C<TidyupRun>.

TODO: run this in C<DESTROY> iff the output file is started.

=cut

sub EndOutFile {
  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;
}

=head4 StartProviding

  $self->StartProviding();

Initiate a checking run, with no files marked as provided.  Until this
call is made, all calls to C<ProvideFile> will be ignored, and calls
to C<HaveFile> will return FALSE.

=cut

sub StartProviding {
  my $self=shift;
  # make an anonymous listref ...
  $self->{provides} = [] unless exists $self->{provides};
  # ... and add an empty hashref to it
  unshift @{$self->{provides}}, {};
}

=head4 DoneProviding

  $self->DoneProviding();

Discard the information from the previous C<StartProviding>, reverting
to the information (if any) which was available before it was called.
If this is called as many times as C<StartProviding>, the object will
return to the original state where everything is ignored.

From within this object, one can also localise the C<provides> member
to "automatically" stop providing when a scope is exited:

  local $self->{provides};
  $self->StartProviding();

This feature should not be used outside the module, though, as it is
liable to change without warning.

=cut

sub DoneProviding {
  my $self=shift;
  shift @{$self->{provides}} if exists $self->{provides};
  # if there aren't any entries left in the list, we delete it, as
  # this is easier to check for in the other functions.
  delete $self->{provides} unless scalar(@{$self->{provides}});
}

=head4 ProvideFile

  $self->ProvideFile('par');

Note that this step will create the given file, so checks for it may
be skipped in later C<Check*> invocations; see C<HaveFile>

=cut

sub ProvideFile {
  my $self=shift;
  my $f=shift;

  unless (exists $self->{provides}) {
    # we're not interested
    return 0;
  }

  $self->{provides}->[0]->{$f}++;
}

=head4 UnProvideFile

  $self->UnProvideFile('par');

Record that this step will "use up" the named file, removing it from
the working directory; this is the opposite of C<ProvideFile>.

This will undo I<all> provisions of a file; even if C<ProvideFile> is
called multiple times, C<UnProvideFile> need be called only once.

=cut

sub UnProvideFile {
  my $self=shift;
  my $f=shift;

  unless (exists $self->{provides}) {
    # we're not interested
    return 0;
  }

  $self->{provides}->[0]->{$f} = 0;
}

=head4 HaveFile

  check_file('par') unless $self->HaveFile('par');

If we are in 'providing' mode (ie, between calls to C<StartProviding>
and C<DoneProviding>), this checks whether anybody has provided the
requested file via C<ProvideFile>, and returns TRUE if anybody has, or
FALSE otherwise.

If we are not in providing mode, but there is a working directory
object, calls the C<have_file> function on the working directory
object to see if the file exists for real.

=cut

sub HaveFile {
  my $self=shift;
  if (exists $self->{provides}) {
    return $self->{provides}->[0]->{$_[0]};
  } elsif ($self->have_setting('rundir_obj')) {
    my $rundir=$self->rundir_obj();
    return $rundir->have_file($_[0]);
  }
  return 0;
}

=head4 TraceLog

  $job->TraceLog('name',@flags);

If C<write_debug> is TRUE, writes a line to the trace log; see
L<CFX5::Simulation::Configuration::Debug> for more details.  This log is used for regression
testing, so be careful what goes into it.

=cut

sub TraceLog {
  my $self=shift;
  if ($self->write_debug()) {
    $self->debug_obj()->log(@_);
  }
  _trace(@_);
  1;
}

=head3 RunDir

=head4 _DoRunDir

=cut

sub _DoRunDir {
  my $self=shift;

  # make sure we've got something sensible here
  my $do=shift;
  internal_error("First argument must be 0 or 1 or 2") 
    unless defined $do and ($do eq 0 or $do eq 1 or $do eq 2);

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

  if ($do) {
    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;

    $rundir->creatable_file('solver.sta');
    $rundir->creatable_file('stp');

    # switch this on to enable the checkpoints of the working directory,
    # for debugging.
    if ($self->checkpoint_workdir()) {
      $rundir->enable_checkpoints();
    }

    if ( $self->prep_ansys() or $self->start_ansys() ) {
      internal_error("Failed to create directory: ".$self->ansys_workdir()) unless create_directory($self->ansys_workdir());

    # Copy required files if it's a restart
    if ($self->is_mfx_restart()) {
      my @filelist;
      my $jobname=basename($self->mfx_restart_db());
      my $type=$jobname;
      $jobname=~s!\.r?db$!!gi;
      $type=~s!^.*\.!!g;

      if ( ! CFX5::FileStat::test_e $self->mfx_restart_db() ) {
         internal_error("ANSYS Restart database not found");
      }
          
      # Use the Multiframe database if it exists
      if( ($type eq 'rdb') || ($type eq 'RDB')) {
        $self->mfx_restart_type('Multiframe');
        @filelist=( $jobname.'.rdb',
                    $jobname.'.ldhi',
                    $jobname.'.rst',
                    $jobname.'.rth',
                    $jobname.'.m*',
                    $jobname.'.r[0-9][0-9][0-9]');
      }
      elsif ( ($type eq 'db') || ($type eq 'DB')) {
        $self->mfx_restart_type('Singleframe');
        @filelist=( $jobname.'.db',
                    $jobname.'.osav',
                    $jobname.'.esav',
                    $jobname.'.emat',
                    $jobname.'.rst',
                    $jobname.'.rth');
      } 
      else {
        internal_error("Invalid ANSYS Restart database. The .db or .rdb extension must be used");
      }

      if($self->start_ansys()){
        
        my $restDir = dirname($self->mfx_restart_db());
        my $workDir = $self->ansys_workdir() ;
        if($Config{windows_nt}) {
           require Win32;
           $restDir=Win32::GetShortPathName($restDir);
        }
        foreach my $i (@filelist) {
          my $j;        
          my $file=join_file($restDir, $i);
          $file =~s/\s/\\ /g unless ($Config{windows_nt}) ; 
          while (defined( $j = glob($file))) {
            if(CFX5::FileStat::test_e $j) {
              _trace("Copying ANSYS restart file: $j");
              copy_file($j,$workDir) or internal_error("Copy ANSYS restart files failed: $j");
            }
          }
        }
      }
    }
  }
    

  }

  return 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);

  # Return unless full name defined
  return 0 unless (defined $self->fullname());

  # 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",
           "    Full Run Prefix = ".normalize_filename(fullpath($self->fullname())),
           "    Working Directory = ".normalize_filename(fullpath($self->rundir())),
           "    Output File = ".normalize_filename(fullpath($self->outfile())),
          );
  if (not $self->parent()->singleconfig()){
    my $scclout=$self->parent()->outobjccl();
    if ($scclout) {
      my $name=$scclout->gui_objname();
      push @msg, "    Parent = ".$name if defined $name;
    }
  }
  if (defined $self->deffile()) {
    push @msg, "    Solver Input File = ".normalize_filename(fullpath($self->deffile()));
  }
  my @iniFiles = $self->GetIniFiles() ; 
  if (defined $iniFiles[0]) {
    push @msg, "    Initial Values File = ".normalize_filename($iniFiles[0]);
  }
  if ($self->start_ansys()) {
    push(@msg, "    ANSYS Job Name = ".$self->ansys_jobname(),
               "    ANSYS NLH File = ".join_file($self->ansys_workdir(),$self->ansys_jobname().".nlh"),
               "    ANSYS GST File = ".join_file($self->ansys_workdir(),"ANSYS.gst"),
               "    ANSYS Out File = ".$self->ansys_output(),
       );
  }
  push @msg, (
              "    Messages File = $f",
              "  END",
              "END",
             );

  if (not $self->parent()->indirect_startup() and 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();
  }

  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;
}

=head3 Subtasks

=head4 GetSubtaskObject

  my $subo=$self->GetSubtaskObject($subtask);

Returns an object of the correct class to carry out the nominated
subtask, initialised to point back to this object.  See
L<CFX5::Simulation::Configuration::Subtask>. 

TODO: Currently, we do not cache these objects, but create new ones
every time.  It is debatable whether this is better -- it could save
memory, but it could stop us being able to cache data in the objects
for later.  One for further thought.

=cut


sub GetSubtaskObject {
  my $self=shift;
  my $task=shift;

  # try to find the object.  In the default case, this is an object of
  # class C<CFX5::Simulation::Configuration::$subtask>, but if this object is a subclass
  # itself, we want to check all the way up the inheritance tree to
  # find the first one we match.  We do this recursively: first, we
  # try to find a subclass of the $baseclass (which will be our own
  # class) with the name of the task we want.  Then we recurse up the
  # tree, calling ourselves to try again with different classes.
  my $taskpkg="CFX5::Simulation::Configuration::$task";
  if ($INC{$taskpkg} or defined eval "require $taskpkg;") {
    if (UNIVERSAL::can($taskpkg, 'new')) {
      # it exists, and it has a new call: groovy!  Use it.
      #no strict;
      _debug("Getting subtask object for $task from $taskpkg");
      my $obj=($taskpkg)->new($self);
      return $obj;
    }
  }
  warn("$@");

  # if we have still failed, we don't just return undef as that would
  # lead to confusing errors.  Instead, we abort.  This could change
  # later if desired.
  internal_error("Unable to find subtask class for $task in", ref($self));
}

=head3 CombDef

=head4 CheckCombDef

CombDef requires a working directory, an output file, and a definition
file.  It will use an initial values file if specified.

=cut

sub CheckCombDef {
  my $self=shift;
  my $cdo=$self->GetSubtaskObject('CombDef');
  $cdo->Check(@_);
}

=head4 PrepareCombDef

Preparing CombDef will start an output file if this has not already
been done, and concatenate the definition file with the initial values
file if necessary.  It provides the file 'def'.

=cut

sub PrepareCombDef {
  my $self=shift;
  my $cdo=$self->GetSubtaskObject('CombDef');
  $cdo->Prepare(@_);
}

=head4 RunCombDef

Running CombDef will do CCL processing on the definition file.  This
keeps the same filename, but will add a new C<G/MMS> dataset
containing a translated version of the CCL file.

=cut

sub RunCombDef {
  my $self=shift;
  my $cdo=$self->GetSubtaskObject('CombDef');
  $cdo->Run(@_);
}

=head3 Remeshing

=head4 CheckRemeshing

Remeshing requires a working directory, an output file, a definition
file, and a results file. 

=cut

sub CheckRemeshing {
  my $self=shift;
  $self->GetSubtaskObject('Remeshing')->Check(@_) ;
}

=head4 QueryRemeshNow

Remeshing requires a working directory, an output file, a definition
file, and a results file. 

=cut

sub QueryRemeshNow {
  my $self=shift;
  $self->GetSubtaskObject('Remeshing')->QueryRemeshNow(@_) ;
}

=head4 PrepareRemeshing

Preparing Remeshing will start an output file if this has not already
been done, and concatenate the definition file with the initial values
file if necessary.  It provides the file 'def'.

=cut

sub PrepareRemeshing {
  my $self=shift;
  $self->GetSubtaskObject('Remeshing')->Prepare(@_) ;
}

=head4 RunRemeshing

Running Remeshing will insert new mesh into definition file.

=cut

sub RunRemeshing {
  my $self=shift;
  $self->GetSubtaskObject('Remeshing')->Run(@_);
}

=head3 Adapt

=head4 CheckAdapt

The adaption code requires a file 'res', which will usually be the
output from a solver run (see C<Solve>).  It produces a file 'def'
which can be used as input to a further solver run.  The checks also
look at the other settings under L</ADAPTION>.

If there is an adaption control file specified (see C<adapt_control>),
many checks will be skipped, as only the mesh adaption code is capable
of reading such files.  The checks will be re-done when execution
reaches the C<RunAdapt> function, so error messages will still be
given.  Probably C<CheckAdapt> should be re-run once the control file
has been parsed, but this will be handled by the C<Run> function.

=cut

sub CheckAdapt {
  my $self=shift;
  $self->GetSubtaskObject('Adapt')->Check(@_);
}

=head4 InitAdapt

  $job->InitAdapt();

This call is needed because of the way the refiner initialisation
files are set up (see C<adapt_initfile>).  In order to support
information hiding, if an initialisation file is passed, we need to
ask the refiner to modify our definition file before starting the run
proper; however, we don't want to modify the user's original
definition file.  So if C<adapt_initfile> is specified, we can only
get the real setup after running C<CombDef>.

This call is designed to fill that hole, by reading the definition
file information as soon as possible after the definition file has
been set up; specifically, before we run the solver, since that could
take a while if we're going to abort when it's finished ... if an init
file was specified, we also re-run the CheckAdapt call at this stage.

=cut

sub InitAdapt {
  my $self=shift;
  $self->GetSubtaskObject('Adapt')->Init(@_);
}

=head4 PrepareAdapt

Preparing the adaption module is a work in progress right now!  See
C<CheckAdapt>.

=cut

sub PrepareAdapt {
  my $self=shift;
  $self->GetSubtaskObject('Adapt')->Prepare(@_);
}

=head4 RunAdapt

=cut

sub RunAdapt {
  my $self=shift;
  $self->GetSubtaskObject('Adapt')->Run(@_);
}

=head4 QueryAdaptNow

=cut

sub QueryAdaptNow {
  my $self=shift;
  $self->GetSubtaskObject('Adapt')->QueryAdaptNow(@_);
}

=head3 PPE

=cut

sub CheckPPE {
  my $self=shift;
  $self->GetSubtaskObject('PPE')->Check(@_);
}

sub PreparePPE {
  my $self=shift;
  $self->GetSubtaskObject('PPE')->Prepare(@_);
}

sub InterrogatePPE {
  my $self=shift;
  $self->GetSubtaskObject('PPE')->Interrogate(@_);
}

sub SetupPPEHostList {
  my $self=shift;
  $self->GetSubtaskObject('PPE')->SetupHostList(@_);
}

=head3 Partition

Sets up and runs the partitioner, given the relevant settings.

=cut

=head4 CheckPartition

The partitioner requires a file 'def', and will start an out file if
necessary.  It requires either an input partition file (C<parfile_in>)
or a number of partitions (C<partitions>) to be specified.  It also
requires that the solver executable be present.

=cut

sub CheckPartition {
  my $self=shift;
  $self->GetSubtaskObject('Partition')->Check(@_);
}

=head4 PreparePartition

Preparing the partitioner simply creates the input partition file, if
there is one, and marks the output partition file for automatic output
if required.

=cut

sub PreparePartition {
  my $self=shift;
  $self->GetSubtaskObject('Partition')->Prepare(@_);
}

=head4 RunPartition

=cut

sub RunPartition {
  my $self=shift;
  $self->GetSubtaskObject('Partition')->Run(@_);
}

=head4 CheckSolve

The Solve component requires the solver executable, a working
directory, an output file name, a results file name.  For a parallel
run it needs a partition file and a hst file.

=cut

sub CheckSolve {
  my $self=shift;
  $self->GetSubtaskObject('Solve')->Check(@_);
}

=head4 CheckAnsys

Check the prerequisites for the Ansys solver, or if already started
check that it's still ok

=cut

sub CheckAnsys {
  my $self=shift;
  return 1 unless $self->is_mfx_run();
  return 1 unless $self->start_ansys();
  if($self->ansys_running()){
    return $self->GetSubtaskObject('ANSYS')->check_status(@_);
  } else {
    return $self->GetSubtaskObject('ANSYS')->Check(@_);
  }
}

sub CheckCCL2MF {
  my $self=shift;
  if( $self->is_mfx_run() and $self->mfx_process_input() ) {
    return $self->GetSubtaskObject('CCL2MF')->Check(@_);
  }
  1;
}

sub RunCCL2MF {
  my $self=shift;
  if( $self->is_mfx_run() and $self->mfx_process_input() ) {
    return $self->GetSubtaskObject('CCL2MF')->Run(@_);
  }
  1;
}


=head4 PrepareSolve

This will export CFX_DATA_DIR, ensure that the definition file is set
up, copy in the hst file and par file if necessary, and set the res
file and res file to be outputted if required.

=cut

sub PrepareSolve {
  my $self=shift;
  $self->GetSubtaskObject('Solve')->Prepare(@_);
}

=head4 PrepareAnsys

No implelmented - maybe setup the run directory here

=cut

sub PrepareAnsys {
  return 1;
}

=head4 RunSolve

Runs the solver, using C<memory_factor> if specified.

=cut

sub RunSolve {
  my $self=shift;
  $self->GetSubtaskObject('Solve')->Run(@_);
}

=head4 RunAnsys

Runs the Ansys solver (in the backgound)

=cut

sub RunAnsys {
  my $self=shift;
  $self->GetSubtaskObject('ANSYS')->Run(@_);
}

=head4 StopAnsys

Halt the Ansys solver

=cut

sub StopAnsys {
  my $self=shift;
  $self->GetSubtaskObject('ANSYS')->stop(@_);
}

=head4 Check/RunIndirect

Check/Run via an indirect start method

=cut

sub CheckIndirect {
  my $self=shift;
  $self->GetSubtaskObject('Indirect')->Check(@_);
}

sub RunIndirect {
  my $self=shift;
  $self->GetSubtaskObject('Indirect')->Run(@_);
}

sub write_runinfo {
  my $self=shift;
  return CFX5::CCLI::writeccl( $self->resfile(), join_file($self->rundir(),"gui-err.txt") , 1, 'G/RUNINFO' ) if -w $self->resfile();
  return CFX5::CCLI::writeccl( $self->resfile_err(), join_file($self->rundir(),"gui-err.txt") , 1, 'G/RUNINFO' ) if -w $self->resfile_err();
}    

=head3 Pretendable Component Executables

We provide a set of functions, one per executable, which switch on the
value of C<lets_pretend> to either call the executable, or to emulate
its effects.

Originally, these routines would return the raw return value from
do_cmd.  They are slowly being changed to return the parsed value
instead, and to use C<do_termerr_cmd> to save the output of the
command where appropriate.

The versions which pretend functionality are not nearly so picky as
the real things; they are there more to speed things up for the
purposes of regression testing and so on.

=head2 GLOBAL WRAPPERS AND SEQUENCING

=head4 Check

=cut

sub Check {
  my $self=shift;
  $self->_DoGlobal(0,@_);
}

=head4 Run

=cut

sub Run {
  my $self=shift;
  $self->_DoGlobal(1,@_);
}

sub _DoGlobal {
  my $self=shift;

  # make sure we've got something sensible here
  my $do=shift;
  internal_error("First argument must be 0 or 1") 
    unless defined $do and $do eq 0 or $do eq 1;

  # first off, check our internal CCL database for consistency
  $self->validate_ccl_settings()
    or return 0;

  goto interrupt_run if $self->interrupt_now();

  my $workdir;

  my $sm = $self->start_method_obj();
  return 0 unless (defined $sm);
  if ($sm->is_indirect()) {
    if ($do) {
      $workdir = $self->rundir_obj();
      $self->RunIndirect();
      goto abort_run if error_pending();
      goto finalize_indirect if ($sm->finalize_indirect());
    }
    else {
      $self->CheckIndirect();
      return 0 if error_pending();
    }
    return 1;
  }

  # $do is 0 for checking or 1 for running.  This is slightly
  # different from the usage above, where the first argument is 0 for
  # checking or 1 for *preparing*.  We do it this way to avoid
  # duplicating the control logic, thereby cutting down on errors.
  # ("Single Choice Principle").
  unless ($do) {
    $self->StartProviding();
  }

#
#-------------------------------------------------------------------------------
# Generate low level solver input file (i.e. 'def') 
#-------------------------------------------------------------------------------
#
  if ($do) {
    $self->RunCombDef();
    $workdir = $self->rundir_obj();
    goto abort_run if error_pending();
  }
  else {
    $self->CheckCombDef();
    return 0 if error_pending();
  }
#
#-------------------------------------------------------------------------------
# See if non-standard preprocessing is needed (e.g. a remeshing step)
#-------------------------------------------------------------------------------
#
# Set local activation flags for various tasks
  my $doPart      = 1 ; 
  my $doSolve     = 1 ;
  my $doPrep      = $do ; 
  my $doAdapt     = 1 ;
  my $doRemeshing     = 0 ;
  my $doInitRemeshing = 0 ;
  
  if ($do) {
    
    my @resTermCondList ;
    my @resTermCondTypeList ; 
  
    my $solverInputFile = $workdir->file_intpath('def') ; 
    if (-e $solverInputFile) {
      @resTermCondList = GetDataSet($solverInputFile,"G/TERMCOND") ; 
      return 0 if error_pending() ; 
      @resTermCondTypeList = GetDataSet ($solverInputFile,"G/TERMCONDTYPE") ; 
      return 0 if error_pending() ;
    }
    
    # Purge solver-internal termination conditions
    # --> Remeshing will not be activated if an internal termcond is set  
    ManageTermCond('DeleteCond','Stop instruction received',
                   \@resTermCondList,\@resTermCondTypeList) ;

    # Setup for remeshing 
    $self->PrepareRemeshing();
    
    # See if a remeshing pre-processing step is needed
    # --> set local task activation flags
    # --> rename files to get those that are expected (i.e. res and mon)
    $doRemeshing = $self->QueryRemeshNow(\@resTermCondList,\@resTermCondTypeList);
    if ($doRemeshing) {
      $doPart      = 0 ; 
      $doSolve     = 0 ; 
      $doPrep      = 0 ; 
      $doAdapt     = 0 ;
      $doInitRemeshing = 1 ;
      
      $workdir->rename_file('def' => 'res')
        or return 0;
      $workdir->rename_file('mon.old' => 'mon')
        or return 0;
    }
  }
  
  goto abort_run if error_pending();

  # Setup adaptation and remeshing stuff now that solver def file is complete
  if ($do) {
    $workdir->checkpoint('before adaption initialisation');
    $self->InitAdapt() or goto abort_run;
  }
  else{
    $self->CheckRemeshing();
  }
  goto abort_run if error_pending();
  
  # Res-info dataset processing for single config cases.
  # This MUST be done based on the 'def' file, which is the solver exe's input file.
  # --> Only do this if a def file will exist (may not for some mfx run modes)
  # --> Configuration step over-ridden here to help Post deal with topology
  #     changes across restarts (Post will treate conf step changes as 'config' changes)
  #     - essentiall, just rename step=0 to step=new
  my $expectDefFile = 1 ; 
  if (defined $self->mfx_run_mode()) {
    $expectDefFile = 0
      if ($self->mfx_run_mode() ne "Start ANSYS and CFX" and
          $self->mfx_run_mode() ne "Start CFX only") ;           
  }
  
  if ($do and $self->parent()->singleconfig() and $expectDefFile) {
    my $inputFile = 'def' ;
    $inputFile = 'res' if $doInitRemeshing ; 
    $self->parent()->{resinfo}->ReadFromFile(join_file($self->rundir(),$inputFile)) ;
    my %lastentry;

    my $currentConfig = $self->parent()->{currentconfig} ;
    my $confName = $self->parent()->{configmap}{$currentConfig}{name};
    %lastentry   = $self->resInfo()->GetLast($confName);
    my $confStep = $lastentry{ConfStep} if defined $lastentry{ConfStep} ;
    $confStep++ ;
    $self->parent()->{configmap}{$currentConfig}{$confStep} = $self->parent()->{configmap}{$currentConfig}{0} ; 
    $self->parent()->{configmap}{$currentConfig}{0} = {} ;
    $self->parent()->{configmap}{$currentConfig}{initstep}=$confStep; 
    $self->parent()->{configmap}{$currentConfig}{nextstep}=$confStep+1; 
  }
  
  $self->FinishedStage('preparedef') if $do;
  goto interrupt_run if $self->interrupt_now();
  
#
#===============================================================================
# Main solve loop (get new mesh, partition, interpolate, solve,...)
#===============================================================================
#
  while(1) {

    last if $self->stop_now();
#
#-------------------------------------------------------------------------------
# Partitioning
#-------------------------------------------------------------------------------
#
    if ($doPart) {
      if ($self->do_partition() ) {
        # we want to run the partitioner, so we need to do things with it
        if ($do) {
          $workdir->checkpoint('before partitioner');
          $self->RunPartition() or goto abort_run;
          last unless $self->start_cfx();
        } else {
          $self->CheckPartition() or goto abort_run;
        }
        goto abort_run if error_pending();
      }
      
      $self->FinishedStage('partitioner') if $do;
      goto interrupt_run if $self->interrupt_now();
      
      last if $self->stop_now();
    }
      
#
#-------------------------------------------------------------------------------
# Solver
#-------------------------------------------------------------------------------
#
    if ($doSolve) {
      if ($self->do_solve()) {
        if ($do) {
          # Check that the ANSYS solver is still ok
          goto abort_run unless $self->CheckAnsys();
          $workdir->checkpoint('before solver');
          $self->RunSolve() or goto abort_run;        
        } else {
          $self->CheckSolve() or goto abort_run;
        }
        goto abort_run if error_pending();
      }
      elsif ($self->start_ansys())  { # Start ANSYS only
         if ($do) {
           CFX5::Commands::wait_bg_cmd($self->ansys_pid());
           $self->ansys_running(0);
           last;
         }
      }
      else { # Process input file only
        if( $do) {
           sleep 3;
           last;
        }
      } 
      $self->FinishedStage('solver') if $do;
      goto interrupt_run if $self->interrupt_now();

      last if $self->stop_now();
    }

#
#-------------------------------------------------------------------------------
# Preparation for post-solver tasks
#-------------------------------------------------------------------------------
# --> See which tasks are active
# --> Each query call should accept the term cond arrays, and:
#     1) return 1/0 if the task is required, and
#     2) remove term cond entries from the provided arrays if those entries
#        correspond to the task's activation condition(s)
# --> If termination conditions remain at the end of this processing,
#     then this is an indication that the run should probably stop without
#     further processing.
#
    if ($doPrep) {  
      # Pick up the solver generated termination conditions
      my @resTermCondList ;
      my @resTermCondTypeList ; 
      my $resFile = $workdir->file_intpath("res") ; 
      if (-e $resFile) {
        @resTermCondList = GetDataSet($resFile,"G/TERMCOND") ; 
        last if error_pending() ; 
        @resTermCondTypeList = GetDataSet($resFile,"G/TERMCONDTYPE") ; 
        last if error_pending() ;
      }
      
      # Is remeshing needed?
      $doRemeshing = $self->QueryRemeshNow(\@resTermCondList,\@resTermCondTypeList);
      
      # Other tasks...
      
      # Final preprocessing
      # --> no remeshing if there are any outstanding termination conditions 
      $doRemeshing = 0 if (defined $resTermCondList[0]) ;
    }      
#
#-------------------------------------------------------------------------------
# Remeshing
#-------------------------------------------------------------------------------
#
    if ($do) {
      if ($doRemeshing) {
        $workdir->checkpoint('before remeshing') ;

        #
        # Execute remeshing
        unless ($self->RunRemeshing("res")) {
          _error("Remeshing failed.");
          goto abort_run ;
        }
        #
        # Re-run CombDef to interpolate last results onto new mesh
        # --> produces a new 'def' file that can be run by the solver
        $self->RunCombDef("Remeshing");
      }
    }
    else {
      $self->CheckRemeshing() ;
    }
    goto abort_run if error_pending();
    
    $self->FinishedStage('Remeshing') if $do;
    goto interrupt_run if $self->interrupt_now();

    # Special handling if stop file found
    # --> remeshing will have combined old results and new mesh to produce a new 'def' file
    # --> ensure that the def/res file has the correct termination information
    # --> rename this to 'res' so that it is properly addressed in the regular tidyup machinery
    if ($self->stop_now()) {
      $self->WriteSTPTermCond($workdir->file_intpath('def')); 
      $workdir->mark_output_file('def',$self->resfile());
      last; 
    }
#
#-------------------------------------------------------------------------------
# Adaption
#-------------------------------------------------------------------------------
#
    my $doAdaption = 0 ; 
    if ($doAdapt) {
      $doAdaption = $self->QueryAdaptNow() ;
      if ($doAdaption) {
        if ($do) {
          $workdir->checkpoint('before adaption');
          $self->RunAdapt() or goto abort_run;
        } else {
          $self->CheckAdapt() or goto abort_run;
        }
      }
      goto abort_run if error_pending();
      
      $self->FinishedStage('refiner') if $do;
      goto interrupt_run if $self->interrupt_now();
  
      last if $self->stop_now();
    }
#
#-------------------------------------------------------------------------------
# Main solve loop repetition logic
#-------------------------------------------------------------------------------
#
    # Reset local activation flags, etc... if remeshing was run
    # as a startup process
    if ($doInitRemeshing) {
      $doPart      = 1 ; 
      $doSolve     = 1 ; 
      $doPrep      = 1 ; 
      $doAdapt     = 1 ;
      $doInitRemeshing = 0 ; 
    }

    # Break loop if in check mode
    last unless $do ; 
    
    # Break loop if adaption or remeshing hasn't been activated
    last unless ($doAdaption or $doRemeshing) ;
  }

  unless ($do) {
    $self->DoneProviding();
  }

  finalize_indirect:
  
  if ($do) {
    $workdir->checkpoint('about to tidy up');
  }

  if ($do) {
    $self->TidyupRun();
  }
  return '' if error_pending();
  return 1;

 interrupt_run:
  _warn("Interrupting run prematurely at user request");
  $self->EndOfRun();
  unless ($do) {
    $self->DoneProviding();
  }
  return 1;

 abort_run:
  # code to execute if the run fails.  This should tidy up and then
  # return an error code.
  unless ($do) {
    $self->DoneProviding();
  }
  $self->TidyupRun();
  return '';
}

sub WriteSTPTermCond {
  my $self=shift;
  my $file=shift; 

  my $dest;
  return 1 unless ( CFX5::FileStat::test_e $file ) ;

  $dest = CFX5::DefFile->new($file) ;

  unless (ref $dest) {
    _error("WriteSTPTermCond failed - could not connect to file: $file");
    return 0; 
  }
  
  {
    my $ds = new CFX5::DefFile::DataSet(); 
    $ds->header(what=>'G/TERMCOND'); 
    $ds->header(where=>'EVERY'); 
    $ds->header(when=>0); 
    $ds->header(number=>1); 
    $ds->header(datatype=>"STRI"); 
    $ds->data("Stop instruction received");
    unless ($dest->write_dataset($ds)) {
      _error("WriteSTPTermCond failed - ".$ds->header->{what});
      return 0; 
    }
  }

  {
    my $ds = new CFX5::DefFile::DataSet(); 
    $ds->header(what=>'G/TERMCONDTYPE'); 
    $ds->header(where=>'EVERY'); 
    $ds->header(when=>0); 
    $ds->header(number=>1); 
    $ds->header(datatype=>"INTR"); 
    $ds->data("1");
    unless ($dest->write_dataset($ds)) {
      _error("WriteSTPTermCond failed - ".$ds->header->{what});
      return 0; 
    }
  }
  
  1 ;  
}

sub ManageTermCond {
  my ($action,
      $value,
      $condList, $typeList) = @_ ;

  return unless (defined $$condList[0] and defined $$typeList[0]) ; 
  
  if ($action eq 'DeleteCond') {
    for (my $ii=0; $ii<scalar(@$condList); $ii++) {
      if ($value eq $$condList[$ii]) {
        splice (@$condList,$ii,1); 
        splice (@$typeList,$ii,1);
      }
    }
  }
  elsif ($action eq 'DeleteType') {
    for (my $ii=0; $ii<scalar(@$condList); $ii++) {
      if ($value eq $$typeList[$ii]) {
        splice (@$condList,$ii,1); 
        splice (@$typeList,$ii,1);
      }
    }
  }
  else {
    internal_error "ManageTermCond called with unrecongised action: $action\n";
  }
  1;
}

=head2 COMMAND LANGUAGE HANDLING

=head4 read_ccl

 $job->read_ccl('my.ccl');
 $job->read_ccl('-');
 $job->read_ccl(\@ccllines);

Inserts CCL data into our copy of the CCL database.  The first
argument can be

=over

=item scalar

A normal scalar will be treated as a filename, unless it is the single
character '-' in which case data will be read from the standard input.

=item array reference

An array reference is assumed to contain a list of lines of CCL
(without newlines).

=back

If errors are encountered while reading the CCL, they are signalled
via the C<_error> call and FALSE is returned.  Otherwise, the return
value is TRUE.

No validation of the CCL database is done by this call, we only
validate once everything is read.

=cut

sub format_ccl_error {
  my $self=shift;
  CFX5::CCLI::format_ccl_error(@_);
}

sub flow_name {
  my $self=shift;
  return $self->{settings}->{flow_name};
}

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

  my @ccllines;
  my $fromname=shift;

  # If we're going to use the RULES, we need to load them before
  # anything else, as the input CCL might want to override them.
  $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 '-');
  }

  # Handle named FLOW objects
  #
  # The first FLOW object we encounter determines how we deal with subsequent
  # FLOW objects (e.g if a ccl file is passed on the comamnd line)
  #
  # Store the FLOW object name in the job object, if it is a singleton rename
  # When we encounter a second FLOW object:
  # --If it is a different FLOW object throw an error
  # --If it is the same FLOW object ok
  # --If it is a FLOW singleton map it to the named FLOW object

  foreach my $i (@ccllines) {
    $i =~ s/[\r\n]+$//;

    if($i=~m!^[\s\t]*FLOW[\s\t]*:[\s\t]*(.*)!){
      my $name=$1;

      if($name){

        if( defined $self->{settings}->{flow_name}
            and $self->{settings}->{flow_name} ne "FLOW: $name"){
          _error("\"FLOW: $name\" is not valid. \"$self->{settings}->{flow_name}\" already exists") unless "FLOW:$name" eq $self->{settings}->{flow_name};
        }

        else {
          _trace("First FLOW object encountered is FLOW: $name");
          $self->{settings}->{flow_name}="FLOW: $name";
        }

      } 

      elsif( defined $self->{settings}->{flow_name}){
        _trace("Mapping FLOW singleton to existing FLOW object $self->{settings}->{flow_name}");
        s!^[\s\t]*FLOW[\s\t]*:[\s\t]*$!$self->{settings}->{flow_name}!g for(@ccllines);
      }

      else {
        _trace("First FLOW object encountered is a singleton, upgrading to a named object");
        $self->{settings}->{flow_name}='FLOW: Flow Analysis 1';
        s!^[\s\t]*FLOW[\s\t]*:[\s\t]*$!$self->{settings}->{flow_name}!g for(@ccllines);
      }
      last;
    }
  }


  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 $origdeffile=$self->deffile() || '';

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

  # OK, if the definition file has changed, we'll read the CCL from
  # that, and then re-read the CCL we were just passed.
  my $newdeffile=$self->deffile() || '';

  if (!$self->parent()->indirect_startup() and
      $origdeffile ne $newdeffile and
      !$ReadCCLRecursionProtect and !$ReadCCLNoDef) {
    local $ReadCCLRecursionProtect=1;
    # it's different
    $self->read_def_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 '';
      }
    }
  }
  
  # Make sure that required CCL persists
  # --> Start methods could have been purged if the user (or SM) issued a &replace SIMULATION CONTROL
  $self->ensure_startmethods_loaded(); 

  $self->remove_invalid_ccl();

  my $changes = $api->updateSolverCCL();
  if ($changes) {
    my $cclupdatelogobj = $self->cclupdatelog();
    $cclupdatelogobj->append($changes);
  }

  1;
}

=head4 read_ccl_bg

 $job->read_ccl_bg('my.ccl');
 $job->read_ccl_bg('-');
 $job->read_ccl_bg(\@ccllines);

Inserts CCL data into our copy of the CCL database, in exactly the
same way as the C<read_ccl> call.  The only difference is that if the
definition file is set, and therefore its CCL is read, the supplied
CCL is I<not> re-read to overwrite that from the definition file.
This is used by the Solver Manager to provide defaults for settings
which can be overridden by settings existant in a definition (or more
likely, results) file.

=cut

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

=head4 read_ccl_nodef

 $job->read_ccl_nodef('my.ccl');
 $job->read_ccl_nodef('-');
 $job->read_ccl_nodef(\@ccllines);

Inserts CCL data into our copy of the CCL database, in exactly the
same way as the C<read_ccl> call.  The only difference is that the
CCL is not read from the def file, even if it is specified in the CCL.
This is used by cfx5control where the definition file will never
need to be specified from the CCL.

=cut

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

=head4 read_def_ccl

  $job->read_def_ccl();

Reads the CCL from the definition file, if one is specified.  If no
definition file has been specified, signals an internal error.  This
should be called before setting any other settings, as soon as the
definition file has been set up, to set up the defaults for the
various parameters.

If the definition file contains settings for the definition or initial
values files, they are discarded.

This is really a violation of the model where none of the settings are
used until the Check call is made, but I cannot see a good way around
it.  One alternative might be to save the settings that are made, and
simply re-impose them once the definition file has been read, but
well, yuck.

=cut

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

  my $deffile=$self->deffile();
  unless (defined $deffile) {
    internal_error("read_def_ccl called when no definition file has been specified");
    return 0;
  }
  unless (CFX5::FileTests::datafile($deffile, "ANSYS CFX Solver Input File $deffile")) {
    return 0;
  }

  unless ( defined $Config{CFX_CUSTOM_DATA_DIR} ) {
    my $datadirds=CFX5::CCLI::getcclds($deffile, '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($deffile);

  if (ref $ds) {
    # we're OK
    $self->deffile_no_ccl(0);
    1;
  } elsif (defined $ds and !ref $ds) {
    # read OK, but there wasn't any!
    _trace "No CCL data in $deffile: $ds";
    $self->deffile_no_ccl(1);
    return 1;
  } else {
    # error reading CCL
    _error("Unable to read Command Language data from the definition file $deffile");
    return '';
  }

  # we set this so that the read_ccl call will not check if the
  # definition file changes, since we are going to revert it
  # explicitly anyway, and since this is already the CCL from the
  # definition file, so it's kind of pointless ...
  local $ReadCCLRecursionProtect=1;

  my @ccl=$ds->data();
  my $rv=$self->read_ccl(\@ccl,"ANSYS CFX Solver Input File $deffile");
  _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]) {
    _debug("No CCL data from def file");
    $self->deffile_no_ccl(1);
  }

  # we know this one a priori
  $self->deffile($deffile);
  
  # Purge unwanted CCL
  # --> use G/TRANSIENT as indicator that this is a restart; i.e. that CCL didn't come from Pre
  # --> always purge: 
  #       preferred licenses and ports are also not valid
  # --> on restart purge:
  #       all of INITIAL VALUES SPECIFICATION: don't re-use IVs and all other settings in this singelton (may rely on the IVs)
  my $cclroot = '/SIMULATION CONTROL/EXECUTION CONTROL';
  for my $param ("$cclroot/SOLVER STEP CONTROL/LICENSE CONTROL/Preferred License",
                 "$cclroot/SOLVER STEP CONTROL/LICENSE CONTROL/Shared License Port",
                 "$cclroot/RUN DEFINITION/Solver Results File",
                 "$cclroot/RUN DEFINITION/Partition File")
  {
    $self->delete_ccl_parm($param) if $self->have_ccl_parm($param);
  }

  my $def=CFX5::DefFile->new($deffile);
  unless (ref $def) {
    _error("Unable to open input file $deffile");
    return 0;
  }
  my @dslist=$def->have_dataset(what => 'G/TRANSIENT');
  if(@dslist) {
    $self->delete_ccl_obj ("$cclroot/RUN DEFINITION/INITIAL VALUES SPECIFICATION");
  }
  return $rv;
}

=head4 set_validation_level

Sets the validation level in the CCL API instance.

=cut

sub set_validation_level {
  my $self=shift;
  my $api=$self->ccl_api_obj();
  my $lev=shift;

  $api->setValidationLevel($lev);
}

=head4 read_ccl_parm

  $deffile=$self->read_ccl_parm('/SIMULATION CONTROL/EXECUTION CONTROL/RUN DEFINITION/Solver Input File');

Reads the current setting of a parameter from the internal copy of the
CCL API, and returns it.  The path may be relative or absolute.  This
can be used to read information for which no explicit method is
provided on the object, or as an alternative for those which are
provided as methods.

=cut

sub read_ccl_parm {
  my $self=shift;
  my $cp=shift;
  my $c;
  
  # 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; 
  }

  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;
}

=head4 set_ccl_parm_int

  $deffile=$self->set_ccl_parm_int('/SIMULATION CONTROL/EXECUTION CONTROL/RUN DEFINITION/Solver Input File', "file.def");

Sets a parameter in the internal copy of the CCL API.  This is an
internal call, which does no checking or updating of status; for
normal usage, see C<set_ccl_parm>.

If the first parameter is a reference to a hash, it is taken as a set
of options (as is done in, for example, L<CGI>).  The
currently-available options are listed under C<set_ccl_parm>.

=cut

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;

  # 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;
  }


  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;
}

=head4 set_ccl_parm

  $deffile=$self->set_ccl_parm('/SIMULATION CONTROL/EXECUTION CONTROL/RUN DEFINITION/Solver Input File', "file.def");

Sets a parameter in the internal copy of the CCL API.  The path may be
relative or absolute.  This can be used to set information for which
no explicit method is provided on the object, or as an alternative for
those which are provided as methods.

If a checking routine is specified for the given parameter, it is
called.  Any record of the history of this parameter (eg, if it was
originally a default) is also removed.

If the first parameter is a reference to a hash, it is taken as a set
of options (as is done in, for example, L<CGI>).  The
currently-available options are:

=over

=item library

Set to TRUE if this is a library setting, rather than user.

=back

=cut

sub set_ccl_parm {
  my $self=shift;
  my $opts={};
  $opts=shift if (ref $_[0] eq 'HASH');
  my $cp=shift;
  my $c=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;
  }


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

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

  $c;
}

=head4 delete_ccl_parm

  $deffile=$self->delete_ccl_parm('/SIMULATION CONTROL/EXECUTION CONTROL/RUN DEFINITION/Solver Input File');

Deletes a parameter in the internal copy of the CCL API.  The path may
be relative or absolute.

=cut

sub delete_ccl_parm {
  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 initial values file setting");
  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;
}


=head4 have_ccl_parm

  if ($self->have_ccl_parm('/SIMULATION CONTROL/EXECUTION CONTROL/RUN DEFINITION/Solver Input File')) {
    # ...
  }

Checks whether we have a parameter in the internal copy of the CCL API.
The path may be relative or absolute.

=cut

sub have_ccl_parm {
  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;
  }

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

=head4 get_current_ccl

  print join("\n",$job->get_current_ccl());

Returns a list of lines representing the current contents of the
Command Language database, without newlines.

=cut

sub get_current_ccl {
  my $self=shift;
  my $root=shift;
  $root="/" unless defined $root;

  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($root, 78, \@ccl);
  if (defined $rv) {
    _error("Error listing current contents of the Command Language database:\n",
           $self->format_ccl_error($rv));
  }
  @ccl;
}



=head4 remove_invalid_ccl

  $job->remove_invalid_ccl();

Some of the settings in the EXECUTION CONTROL area are
context-dependent, for example some of the parallel settings.
However, it is easy to get some invalid settings set, for example by
reading a results file set up for a parallel run, and then setting it
to run in serial, but there will still be a Parallel Host List
parameter in the CCL.

The CCL API does have an alternative mode, setValidationLevel(1),
which allows I<any> parameters which are allowed by any context;
however, this is not sufficient, as this CCL will be used by other
parsers which require strict RULES compliance.  Partly for this
reason, and partly to ensure that the CCL recorded does actually
reflect the parameters which were used in the run.

=cut

sub remove_invalid_ccl {
  my $self=shift;

  # Don't presume that previously set host or port will be valid for this coupling run
  if( (not defined $self->mfx_run_mode()) or ($self->mfx_run_mode() ne "Start CFX only") ){
    $self->delete_ccl_parm('/SIMULATION CONTROL/EXECUTION CONTROL/SOLVER STEP CONTROL/PROCESS COUPLING/Host Name') if $self->have_ccl_parm('/SIMULATION CONTROL/EXECUTION CONTROL/SOLVER STEP CONTROL/PROCESS COUPLING/Host Name');
    $self->delete_ccl_parm('/SIMULATION CONTROL/EXECUTION CONTROL/SOLVER STEP CONTROL/PROCESS COUPLING/Host Port') if $self->have_ccl_parm('/SIMULATION CONTROL/EXECUTION CONTROL/SOLVER STEP CONTROL/PROCESS COUPLING/Host Port');
  }

  # Metis type is invalid unless the partitioning type is metis
  my $cclPath = "/SIMULATION CONTROL/EXECUTION CONTROL/PARTITIONER STEP CONTROL/PARTITIONING TYPE" ;
  if ($self->have_ccl_parm("$cclPath/Option")) {
    unless( $self->part_mode() eq "MeTiS"){
      $self->delete_ccl_parm("$cclPath/MeTiS Type") if $self->have_ccl_parm("$cclPath/MeTiS Type");
    }
  }
}

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();

  #internal_error("Use of internal CCL API object before it is initialised");
  my $rv;
  $rv=$api->readLibraries();
  if (defined $rv) {
    _abort("Error reading standard Command Language content definition:\n", $self->format_ccl_error($rv));
  }
  #$rv=$api->readRules('E:/Home/andy/src/cfx5/solver-script-ccl/iproduct/perl/RULES.me');
  #if (defined $rv) {
  #  _abort("Error reading standard Command Language content definition:\n", $self->format_ccl_error($rv));
  #}

  my $cclhosts=$Config{CFX5_HOSTS_CCL};
  if (defined $cclhosts
      and CFX5::FileTests::datafile($cclhosts, "ANSYS CFX Host File $cclhosts")) {
    # we want hosts from the hst file to override any in here
    _trace("Reading CFX CCL Hosts file $cclhosts");
    $self->ppe_obj()->read_cfxhosts($cclhosts);
  } else {
    _debug("No hostinfo.ccl file, not reading");
  }

  $self->ensure_startmethods_loaded() ;
  
  _trace("Done reading CCL RULES.");

  1;
}

sub ensure_startmethods_loaded {
  my $self = shift;
  
  my $api=$self->ccl_api_obj();

  # Don't do anything if we've already got what we need  
  return 1 if($api->havePath("/SIMULATION CONTROL/EXECUTION CONTROL/START METHOD LIBRARY")) ;

  my $start_methods=$Config{CFX5_START_METHODS_CCL};
  if (CFX5::FileTests::datafile($start_methods, "ANSYS CFX Start Methods File $start_methods")) {
    # we want start_methods from the user's CCL to override any in here
    _trace("Reading start methods file $start_methods");
    cfx5_require('CFX5::FileOps');
    my @cont=split(/\r?\n\r?/, CFX5::FileOps::get_file_contents($start_methods));
    my $rv=$api->parseCCLData(\@cont, 1);
    if (defined $rv) {
      _abort("Error reading Command Language for Start Methods:\n", $self->format_ccl_error($rv));
    }
  }
  1; 
}

=head4 validate_ccl_settings

  $job->validate_ccl_settings()
    or exit(1);

Ensures that all the EXECUTION CONTROL settings in the CCL are valid.
This involves using the API validate call, and also checking the
parameters against the check functions registered here, if available.

Any errors encountered will be raised with _error or equivalent, and
FALSE will be returned.  On success, TRUE will be returned.

=cut

sub validate_ccl_settings  {
  my $self=shift;
  my $api=$self->ccl_api_obj();

  if ($Config{CFX5_SOLVE_LAX_CCL}) {
    _warn("Not validating Command Language for execution control as CFX5_SOLVE_LAX_CCL is set.");
  } else {

    $self->ensure_ccl_rules_loaded();

    _trace("Validating Execution Control Command Language ...");
    my $rv=$api->validateObject('/SIMULATION CONTROL/EXECUTION CONTROL');
    _trace("Done validating Execution Control Command Language.");
    if (defined $rv) {
      my $correction = recommend_ccl_correction($rv);
      my $msg = "Errors were encountered in the CFX Command Language for this run: \n".$self->format_ccl_error($rv);
      $msg .= "\n$correction" if defined $correction ;
      _error($msg);
      return 0;
    }
  }

  if ($self->write_debug()) {
    $self->debug_obj()->log_ccl($api,'/SIMULATION CONTROL/EXECUTION CONTROL');
  }

  1;
}

=head4 delete_invalid_params

  $job->delete_invalid_params('/SIMULATION CONTROL/EXECUTION CONTROL');

Deletes any parameters which are invalid in the current context 
in the specified CCL object. Deletion is not recursive.

=cut

sub delete_invalid_params {
  my $self = shift;
  my $cclPath = shift;

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

  if ($Config{CFX5_SOLVE_LAX_CCL}) {
    _warn("Not validating Command Language for $cclPath as CFX5_SOLVE_LAX_CCL is set.");
  } else {
    $self->ensure_ccl_rules_loaded();

    if ($api->validateObject($cclPath)) {
      my @childparamlist;
      $api->getChildParamList($cclPath, \@childparamlist);
      for my $child (@childparamlist) {
        $api-> deleteParam("$cclPath/$child") unless ($api->isValid("$cclPath/$child"));
      }
    }
  }

  1;
}

=head4 validate_full_ccl

  $job->validate_full_ccl()
    or exit(1);

Ensures that all the settings in the CCL are valid.  This involves
using the API validate call, and also checking the parameters against
the check functions registered here, if available.

Any errors encountered will be raised with _error or equivalent, and
FALSE will be returned.  On success, TRUE will be returned.

=cut

sub validate_full_ccl  {
  my $self=shift;
  my $api=$self->ccl_api_obj();

  if ($Config{CFX5_SOLVE_LAX_CCL}) {
    _warn("Not validating Command Language for execution control as CFX5_SOLVE_LAX_CCL is set.");
  } else {

    $self->ensure_ccl_rules_loaded();

    _trace("Validating full Command Language ...");
    my $rv=$api->validateCCLData();
    _trace("Done validating full Command Language.");
    if (defined $rv) {
      _error("Errors were encountered in the CFX Command Language for this run:\n", $self->format_ccl_error($rv));
      return 0;
    }
  }

  1;
}


=head4 recommend_ccl_correction

  $correction = $job->recommend_ccl_correction($error) ; 
    
This will examines the ccl validation error and provides information about
corrective action that the user can apply.

=cut

sub recommend_ccl_correction {
  my $error=shift; 

  my $correction ;
  
  # Typically have INITIAL VALUES CONTROLs set, but don't have any initial values
  # objects.
  if ($error=~m!^Error: At least one sub-object of type 'INITIAL VALUES' is required!) {
    $correction = "\nSettings that require at least one INITIAL VALUES object definition exist"
                 ." in the Execution Control.  These settings include the parameters 'Use Mesh From'"
                 ." and 'Continue History From' or an otherwise empty Initial Values Specification."; 
  }
  return $correction ; 
}

=head4 update_physics_ccl

  $job->update_physics_ccl()
    or exit(1);

This will call the updateSolverCCL function in the CCL API to ensure
that the CCL it has is up-to-date with regard to the physics.  In the
future, it should be extended to validate the physics CCL as well.

Any errors encountered will be raised with _error or equivalent, and
FALSE will be returned.  On success, TRUE will be returned.

=cut

sub update_physics_ccl  {
  my $self=shift;
  my $api=$self->ccl_api_obj();

  my $changes = $api->updateSolverCCL();

  if ($changes) {
    my $cclupdatelogobj = $self->cclupdatelog();
    $cclupdatelogobj->append($changes);
    while (my $text = $cclupdatelogobj->get_next()) {
      _trace('Individual CCL Upgrade in update_physics_ccl');
      $self->outobj->print($text);
    }
  }

  1;
}

=head4 have_setting

This function queries whether or not a given setting has been
specified.  Normally, one would simply use the function for the given
setting, but some of the defaults have side-effects (for example, the
C<rundir_obj>), so this can be useful.  Where possible, avoid the use
of this call, though.

Note that no checking is done on the validity of the requested
setting.

  if ($run->have_setting('workdir_obj')) {
    # do something with the run directory
  }

=cut

sub have_setting {
  my $self=shift;
  my $s=shift;
  (exists($self->{settings}{$s}) ? 1 : '')
    or $self->{cclapi}->{index}->{$s};
}

=head4 is_wbrun

Check if the working directory name is consistent with the case
being run from Workbench.

=cut

sub is_wbrun {
  my $self = shift;
  my $rundir=$self->{settings}->{rundir_obj};
  return (
    ($rundir->dir() =~ m/\w+_pending/) and
    ($rundir->dir() =~ m/dp\d+_/)
  );
}

1;
