=head1 NAME

  CFX5::Simulation::Configuration::Adapt - Mesh Adaption handling for CFX5::Simulation::Configuration

=head1 SYNOPSIS

This object is used by L<CFX5::Simulation::Configuration>, to manage the mesh adaption.

=head1 DESCRIPTION

$Id$

=cut

package CFX5::Simulation::Configuration::Adapt;

use base 'CFX5::Simulation::Subtask';

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

use CFX5::PathManip qw(dirname basename fullpath join_file);
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::Config qw(%Config export_conf);

use CFX5::DefFile;
use CFX5::FileStat;

use CFX5::CCLI;
use CFX5::PDB;

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::Simulation::Util '_check_defined';

use Carp;

=head3 Adapt

The Adapt component is responsible for setting up and running the
refiner.  This takes a file 'res', refines the mesh according to
parameters stored in the file, and then writes a file 'def' with the
new mesh in it.  Additionally, it modifies the definition file to
indicate whether adaption is to continue or not.

The Adapt component, as well as the standard Check, Prepare and Run
functions, has a call C<AdaptControl>.  This is used if a control file
is to be passed to the refiner, to allow it to set up its internal
data structures, and to write the initial information to the
definition file indicating whether or not to run the adaption.
Because this step needs to read and write the final definition file,
much of the checking for adaption in C<CheckAdapt> will be skipped if
a refiner control file has been passed.  We do not expect users to do
this often.

=cut

sub _Do {
  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;

  my $skipinit=shift;

  # we check the executable first, before we bother making directories
  # or anything
  my $exec=$Config{CFX5_REFINER_EXEC};
  _check_defined $exec, "ANSYS CFX refiner executable" or return 0;
  CFX5::FileTests::execfile($exec, "ANSYS CFX refiner executable $exec") or return 0;

  my $rundir=$self->rundir();
  _check_defined $rundir, "working directory" or return 0 if $do;
  # No test, we just hope.
  # The following will create the directory.
  my $workdir;
  if ($do) {
    $workdir = $self->rundir_obj();
    return 0 unless (defined $workdir);
  }

  # StartOutFile does the requisite checks, but only under some
  # circumstances, and if we're just checking we don't really want to
  # create it anyway ...
  my $outfile=$self->outfile();
  if ($do){
    _check_defined $outfile, "solver output file" or return 0;
    CFX5::FileTests::datafile_w($outfile, "solver output file") or return 0;
    $self->StartOutFile() or return 0;
  }

  # we need a file 'res', and write a file 'def'.
  unless ($self->HaveFile('res')) {
    # if we are not running the solver, give a nicer error message about
    # not having a results file!  This is still an internal error,
    # though, it should never get here.
    unless ($self->do_solve()) {
      _error("No results file is available to adapt, and this run was not set up to run the ANSYS CFX Solver.  This either means that a command-line argument is missing, or that a previous part of the run has failed, in which case there should be further errors above.");
    } else {
      _error("No results file is available to adapt.  This probably means that something failed previously in this run, in which case there should be further errors above.");
    }
    return 0;
  }

  # set up the control file
  my $contfile=$self->adapt_control();
  if (defined $contfile) {
    CFX5::FileTests::datafile($contfile, "refiner control file $contfile")
      or return 0;
    if ($do) {
      $workdir->input_file('refiner.control' => $contfile);
      $workdir->mark_output_file_ignore('refiner.control');
    }
  }

  # if we have problem setup file file, we'll have to skip a lot of
  # checking in a minute
  my $initfile=$self->adapt_initfile();
  if (defined $initfile) {
    CFX5::FileTests::datafile($initfile, "refiner problem setup file $initfile")
      or return 0;
  }

  # what do we produce?
  $self->UnProvideFile('res');
  $self->ProvideFile('def');

  # if this is it, then we need to remove any existing definition
  # files, as they will be replaced soon.
  if ($do) {
    $workdir->remove_file('def');

    # also ignore the def file we produce; if we come back again, we
    # want to re-run the refiner.
    $workdir->creatable_file('def');
    $workdir->mark_output_file_ignore('def');
  }

  # make sure we have the environment variables we need
  CFX5::PDB::setup_environment();

  # if there's a problem setup file, then we can't do anything until
  # we've run the refiner in "update the definition file" mode, so
  # just pretend it's all OK.
  if (defined $initfile and !$do and !$skipinit) {
    _trace("Skipping remaining Adapt checks because we have an init file");
    return 1;
  }

  # OK, since there's no problem setup file we know what's going on
  # (hah!), so check that the rest of the settings are OK.

  # first off, make sure we've read the stuff from the def file
  my %info = $self->getinfo();

  # if we're adapting to geometry, we need a database
  if ($self->adapt_to_geometry()) {
    my $db=$self->adapt_db();
    _check_defined $db, "CFX-Build database for adaption to geometry"
      or return 0;
    CFX5::FileTests::datafile($db, "CFX-Build database %% for adaption to geometry");

    if ($do) {
      unless ($self->HaveFile('refiner.db')) {
	# make a copy of the database for the refiner unless we've
	# already got it, in which case we leave it alone
	$workdir->input_file_copy('refiner.db' => $self->adapt_db());
      }
    }
  }

  return 1 unless $do;

  if ($self->adapt_save_intermediate()) {
    # need outdir to put them in
    my $dir=$self->outdir();
    _check_defined $dir, "Directory for Additional Output Files"
      or return 0;
    CFX5::FileTests::datadir_cw($dir, "Directory $dir for additional output files")
      or return 0;

    if ($do) {
      # create the directory if it isn't already there
      unless (test_d $dir) {
	unless (create_directory($dir)) {
	  _error("Error creating directory $dir for additional output files: $!");
	  return 0;
	}
      }
      # we do the check again, just in case ... ?
      CFX5::FileTests::datadir_w($dir, "Directory $dir for additional output files")
	or return 0;
    }
  }

  # ... and I've already done the preparation too.

  1;
}

=head4 Init

  $adapt->Init();

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 Init {
  my $self=shift;

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

  # if there's no init file, InitAdapt is pointless, and we just skip
  # it.
  my $initfile=$self->adapt_initfile();
  return 1 unless defined $initfile;

  # we check the executable first, before we bother making directories
  # or anything
  my $exec=$Config{CFX5_REFINER_EXEC};
  _check_defined $exec, "ANSYS CFX refiner executable" or return 0;
  CFX5::FileTests::execfile($exec, "ANSYS CFX refiner executable $exec") or return 0;

  my $rundir=$self->rundir();
  _check_defined $rundir, "working directory" or return 0;
  # No test, we just hope.
  # The following will create the directory.
  my $workdir;
  if ($do) {
    $workdir = $self->rundir_obj();
    return 0 unless (defined $workdir);
  }

  # StartOutFile does the requisite checks, but only under some
  # circumstances, and if we're just checking we don't really want to
  # create it anyway ...
  my $outfile=$self->outfile();
  _check_defined $outfile, "solver output file" or return 0;
  CFX5::FileTests::datafile_w($outfile, "solver output file") or return 0;
  if ($do) {
    $self->StartOutFile() or return 0;
  }

  # conceptually, we edit the file 'def'.
  unless ($self->HaveFile('def')) {
    internal_error("There is no 'def' file in the working directory");
    return 0;
  }

  # set up the control file
  my $contfile=$self->adapt_control();
  if (defined $contfile) {
    CFX5::FileTests::datafile($contfile, "refiner control file $contfile")
      or return 0;
    if ($do) {
      $workdir->input_file('refiner.control' => $contfile);
      $workdir->mark_output_file_ignore('refiner.control');
    }
  }

  # what do we produce?  Nothing!!!

  # make sure we have the environment variables we need
  CFX5::PDB::setup_environment();

  # 
  # run the refiner in init mode
  #

  # first, we need to make the file 'def' available as 'res,' which is
  # what the refiner expects to find
  $workdir->rename_file('def','res')
    or return 0;

  # now run the refiner
  $self->RunningComponent('refiner');
  $workdir->enter();
  my $rv = parse_rv($self->_exec('-init',$initfile));
  $workdir->leave();
  $self->RunningComponent('');

  # and rename 'res' back to 'def'
  $workdir->rename_file('res','def')
    or return 0;

  if ($workdir->have_file('res.lck')) {
    _warn("Removing res.lck file left behind by the adaption code");
    $workdir->remove_file('res.lck');
  }

  unless ($rv->is_success()) {
    _error($rv->errstr("The ANSYS CFX Mesh Refiner"));
    return 0;
  }

  # in order to check, we assume we've got a good res file
  $self->StartProviding();
  $self->ProvideFile('res');
  $self->getinfo();
  $rv=$self->Check('init file processed');
  $self->DoneProviding();

  $rv;
}

=head4 Run

This runs the mesh adpation code after setting it up as above.

Running the mesh adaption will remove any existing file 'def' and
'par'

=cut

sub Run {
  my $self=shift;

  if ($self->Prepare()) {
    # ready to go!
    my $rundir=$self->rundir_obj();

    $self->OutFileHeader('Mesh Adaption');

    # if we've got a 'par' file, remove it now, we're about to
    # invalidate it anyway
    if ($self->HaveFile('par')) {
      $rundir->remove_file('par');
    }

    if ($self->adapt_save_intermediate()) {
      my $dir=$self->outdir();

      # need to get the next name for the file
      my $max=0;
      local *DIR;
      unless (opendir DIR, $dir) {
	_error("Cannot read directory $dir: $!");
	return 0;
      }
      while (defined (my $f=readdir DIR)) {
	if ($f =~ m/^mesh(\d+)\.(def|res)$/i) {
	  $max=$1 if $1 > $max;
	}
      }
      closedir DIR;

      $max++;
      my $dest=fullpath(join_file($dir,'mesh'.$max.'.res'));
      $self->outobj()->message("Saving intermediate results file as $dest");
      $rundir->output_file_update('res' => $dest);
      # record the destination in case the user stops the run
      $self->resfile_from($dest);
    } else {
      # we are not saving intermediate files.  If the refiner fails,
      # what are we going to do?  It would be nice to be able to give
      # them something back, so make a backup of the res file.
      my $refbak=$rundir->creatable_file('res.pre-adapt');
      unless (copy_file($rundir->file_intpath('res') => $refbak)) {
	_warn("Unable to make a backup of the results file before mesh refinement.\nThis should not be a problem in itself, but may indicate a lack of\nresources which risks making this run fail later.");
	$rundir->remove_file('res.pre-adapt')
	  if ($rundir->have_file('res.pre-adapt'));
      } else {
	$self->resfile_from($refbak);
      }
    }

    # set up the argument list
    my @args;
    if ($self->adapt_to_geometry()) {
      push @args, '-CAD_database', $rundir->file_intpath('refiner.db');
    } else {
      push @args, '-dont_adapt_to_geometry';
    }

    $self->RunningComponent('refiner');
    $rundir->enter();
    my $unprv;
    {
      # this isn't nice, but it fits in well with the rest of the
      # code.  We have to ensure that _exec doesn't try to
      # print any messages which should go to the screen.

      local (*SAVEDOUT,*SAVEDERR);
      if (CFX5::Output::have_console()
	  or CFX5::Output::have_stdout_file()) {
	# if we have any FDs, save them.
	open SAVEDOUT, ">&STDOUT" 
	  or _warn "Cannot dup STDOUT";
	open SAVEDERR, ">&STDERR"
	  or _warn "Cannot dup STDERR";
      }
      open STDOUT, ">>".$self->outfile()
	or _warn "Cannot redirect STDOUT";
      open STDERR, ">&STDOUT"
	or _warn "Cannot redirect STDERR";

      # it appears that (with Perl 5.8.0?) we have to send something
      # to the output file after messing around with FDs, to realign
      # the OS position with the Perl one, or something.  Otherwise,
      # output from the refiner overwrites the top of the output file.
      print " ";

      $unprv = $self->_exec(@args);

      if (CFX5::Output::have_console()
	  or CFX5::Output::have_stdout_file()) {
	open STDERR, ">&SAVEDERR"
	  or _warn "Cannot restore STDERR";
	open STDOUT, ">&SAVEDOUT"
	  or _warn "Cannot restore STDOUT";
	close SAVEDERR
	  or _warn "Cannot close saved STDERR";
	close SAVEDOUT
	  or _warn "Cannot close saved STDOUT";
      } else {
	# they were closed to start with, close them again
	close STDOUT
	  or _warn "Cannot close STDOUT";
	close STDERR
	  or _warn "Cannot close STDERR";
      }
    }
    $rundir->leave();
    $self->RunningComponent('');

    my $rv=parse_rv($unprv);

    # we leave checking for errors until we've saved the old results
    # file

    # first, get rid of any backups if we dbported the database
    $rundir->remove_file('refiner.db.bak') 
      if $rundir->have_file('refiner.db.bak');

    # now we've finished with the results file from the previous
    # solver run, we copy it out (if required) and then remove it.  We
    # do this before processing the return code of the refiner,
    # because conceptually it's actually done before the refiner runs,
    # except that doing it this way is more disc-space-efficient.
    #
    # Except that it doesn't actually work! :-( There seems to be a
    # problem in the IO library or the mesh refiner.  THIS IS
    # DISABLED!
    if (0 and $self->adapt_save_intermediate()) {
      my ($ressize);	# temporary settings

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

      _debug("After refinement, results file has",
	     -s $rundir->file_intpath('res'),
	     "bytes");

      copy_file($rundir->file_intpath('res'),
		'extra-results.res');

      #
      # first of all, we need to get back the right file.  The
      # refiner, when it is run, modifies the res file using the IO
      # library and then, at the end of the run, copies the new file
      # to 'def', discarding out-of-date datasets.  But because of the
      # way the io library works, the original data still exists at
      # the start of the results file, and so we can simply truncate
      # the file back to its original length to retrieve this data.
      #
      if (defined $ressize) {
	unless (truncate $rundir->file_intpath('res'), $ressize) {
	  _warn("Unable to truncate the results file to $ressize bytes after refinement:\n$!");
	}
      } else {
	_warn("A problem was encountered working out the initial size of the results file.\nThe information in the saved intermediate results file may not be correct.");
      }

      _debug("After truncation, results file has",
	     -s $rundir->file_intpath('res'),
	     "bytes");

      # need to get the next name for the file
      my $max=0;
      local *DIR;
      unless (opendir DIR, $dir) {
	_error("Cannot read directory $dir: $!");
	return 0;
      }
      while (defined (my $f=readdir DIR)) {
	if ($f =~ m/^mesh(\d+)\.(def|res)$/i) {
	  $max=$1 if $1 > $max;
	}
      }
      closedir DIR;

      $max++;
      my $dest=fullpath(join_file($dir,'mesh'.$max.'.res'));
      _info("Saving intermediate results file as $dest");
      $rundir->output_file_done('res' => $dest);
      $rundir->remove_file('res') if $rundir->have_file('res');
    } else {
      # just kill it
      $rundir->remove_file('res');
    }

    if (!$rv->is_success()) {
      _error($rv->errstr("The ANSYS CFX mesh refiner"));

      if ($rundir->have_file('res.pre-adapt')) {
	# we will only have this file if we are not saving
	# intermediate files
	my $errf=$self->resfile_err();
	if ($rundir->output_file_done('res.pre-adapt' => $errf)) {
	  _print("A copy of the results file from the most recent solver run has been placed in $errf\n\n");
	} else {
	  _warn("Unable to keep a backup copy of the results from the most recent solver run.  Try enabling saving of intermediate results files.");
	}
	$rundir->remove_file('res.pre-adapt') 
	  if $rundir->have_file('res.pre-adapt');
      }

      return 0;
    }

    $rundir->remove_file('res.pre-adapt') 
      if $rundir->have_file('res.pre-adapt');

    if ($rundir->have_file('res.lck')) {
      _warn("Removing res.lck file left behind by the adaption code");
      $rundir->remove_file('res.lck');
    }

    return $rv->is_success();
  } else {
    return 0;
  }
}

=head4 getinfo

  my %info = $self->getinfo();

  my %info = $self->getinfo($newdeffile);

This function probably belongs somewhere else in the documentation,
but there is nowhere obvious to put it, so I will put it here!  It
reads adaption information from the definition file, and returns it in
the form of a hash, caching the hash if necessary.

If the optional first argument is not specified, "the definition file"
is the file 'def' in the working directory, if there is one.  If there
is no working directory, the information is taken from the file
mentioned in the C<deffile> setting.  I<No use is made of the initial
values file> at this time.  If no definition file can be located, an
internal error is signalled.

Currently, this hash has the following members:

=over

=item adapting

Are we doing any adaption?  TRUE for yes, FALSE for no.

=item state

This can be set to 'intermediate' or 'final', and corresponds to the
ADAPTING_STATE dataset.

=item adapt_again

This member provides a translation of the state: if state is
'intermediate', it is TRUE, or if state is 'final' it is FALSE.

=item geometry

TRUE if the definition file was set up to adapt to geometry, or FALSE
otherwise.

=item save_intermediate

TRUE if the definition file requested saving of intermediate files, or
FALSE otherwise.

=item defaulted

TRUE if any of the above had to be defaulted, or FALSE if all were
successfully read from the definition file.  This should be TRUE for
all "real" CFX-5.4 definition files.

=item time

(For internal use) the file time of the definition file when this
information was read.

=item filename

(For internal use) the name of the definition file which this
information was read from.

=back

This information is cached, based on the date of the 'def' file.  The
settings are defaulted based on the defaults set out in Richard
Myhills adaption detailed design document.

=cut

# _getai is used by info to get a dataset from the def file
sub _getai {
  my ($self,$def,$ai,$key,$dsname,$default)=@_;

  my %tof = (
	     # true-or-false
	     TRUE => 1, FALSE => '',
	     YES  => 1, NO    => '',
	     # and some other translations we want to make
	     INTERMEDIATE => 'intermediate',
	     FINAL => 'final',
	     # anything else is defaulted
	    );

  delete $ai->{$key};

  #
  # if we're suppressing this check, always return the default
  #
  if ($self->suppress_adapt()) {
    $ai->{$key} = $default;
    $ai->{defaulted}++;
    return 1;
  }

  #
  # try to read from the definition file
  #
  my @ds=$def->have_dataset(WHAT => "G/ADAPT/$dsname",
			    WHERE => 'EVERY',
			    WHEN => 0);
  my $ds;
  if (@ds > 1) {
    # oops!
    internal_error("Found more than one dataset: ",
		   map { "$_->[0]:$_->[1]:$_->[2]" } @ds);
  } elsif (@ds) {
    $ds=$def->read_dataset(WHAT => "G/ADAPT/$dsname",
			   WHERE => 'EVERY',
			   WHEN => 0);
    if (defined $ds) {
      my ($str)=$ds->data();
      $ai->{$key} = $tof{$str};
      if (!defined $ai->{$key}) {
	_warn("Unrecognised value", $ds->data(), 
	      "read from dataset", $ds->short_name(),
	      "assuming default");
      }
    } else {
      _warn("Error reading dataset", map { "$_->[0]:$_->[1]:$_->[2]" } @ds);
    }
  }

  unless(defined $ai->{$key}) {
    $ai->{$key} = $default;
    $ai->{defaulted}++;
  }
  1;
}

sub getinfo {
  my $self=shift;
  my $deffile=shift;

  my $adaptinfo = $self->adaptinfo() || {};
  
  # find a definition file to read
  unless (defined $deffile) {
    if ($self->have_setting('rundir_obj')) {
      my $workdir=$self->rundir_obj();
      if ($workdir->have_file('def')) {
	$deffile=$workdir->file_intpath('def');
      }
    }
    if (!defined $deffile) {
      if (defined $self->deffile() and test_f $self->deffile()) {
	$deffile=$self->deffile();
      }
    }
  }

  unless (defined $deffile) {
    internal_error("Unable to locate a definition file to read");
  }

  CFX5::FileTests::datafile($deffile, "ANSYS CFX definition file $deffile")
    or return ();

  # make an object to represent it, and get the filetime, to see if we
  # need to re-read it.
  my $def = new CFX5::DefFile ($deffile);
  if (!defined $def) {
    _error("Error reading ANSYS CFX definition file $deffile");
    return ();
  }

  # see if we can return what we already have
  {
    my $time=$def->file_time();
    if (defined $time
        and exists $adaptinfo->{time}
	and $adaptinfo->{time} == $time
	and exists $adaptinfo->{filename} 
	and $adaptinfo->{filename} eq $deffile) {
      # the cached copy is OK
      _debug "Using cached copy of $deffile, time $time";
      return %$adaptinfo;
    }
    _debug("Re-reading def file $deffile, time", defined $time ? $time : '(unknown)');
    $adaptinfo->{time}=$time;
    $adaptinfo->{filename}=$deffile;
  }

  #
  # no, we need to read it.  Read each dataset individually, and
  # default them if necessary.
  #

  delete $adaptinfo->{defaulted};

  $self->_getai($def,$adaptinfo,'adapting','ADAPTING',0);
  $self->_getai($def,$adaptinfo,'state','ADAPTING_STATE','final');
  $self->_getai($def,$adaptinfo,'geometry','ADAPTING_TO_GEOMETRY',0);
  $self->_getai($def,$adaptinfo,'save_intermediate','SAVE_INTERMEDIATE_FILES',0);

  # set adapt_again from the state
  $adaptinfo->{adapt_again} = ($adaptinfo->{state} eq 'intermediate' ? 1 : 0);

  # write a note saying what we found, in case somebody is watching
  _debug(join(", ", map { 
    "$_ => " . (defined $adaptinfo->{$_} ? $adaptinfo->{$_} : '(undef)')
  } keys %$adaptinfo));

  # now store this ...
  $self->adaptinfo($adaptinfo);

  # ... and return the hash
  return %$adaptinfo;
}

=head4 QueryAdaptNow

  if ($self->QueryAdaptNow()) {
    # do some adaption
  }

This internal function, used by the C<Run> and C<Check> methods,
checks the adaption information in the definition file and returns
TRUE if the refiner should be run at the next appropriate point, or
FALSE if no adaption should be done.

It is important to follow the "advice" of this routine when doing
actual runs, as the solver will be expecting this to be done when it
chooses the target residual to converge to.  See the comments under
L</ADAPTION> for more discussion.

This routine finds and reads the definition file as specified under
C<info>, and so may be called at any time after a definition
file has been set via the C<deffile> call.  In particular, it may be
called when doing a checking run to ask the question "will I be doing
any adaption on this run?"  Note that if a refiner problem
initialisation file is specified via the C<adapt_init> call, the
information returned by this routine may not be correct until after
the C<CombDef> component has run.

If no arguments are specified, and there is a working directory
object, C<QueryAdaptNow> (as distinct from C<AdaptInfo>) will first
look in the working directory for a file 'res'.  This is because we
except to be called between the solver and refiner in the adaption
loop.

If the solver is not set up to be run (ie, C<do_solve> is FALSE), this
call will always return FALSE, as adaption makes no sense in this
context.

=cut

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

  return 0 unless $self->do_solve();

  unless (defined $file) {
    if ($self->have_setting('rundir_obj')) {
      my $rd=$self->rundir_obj();
      if ($rd->have_file('res')) {
	$file=$rd->file_intpath('res');
      }
    }
  }
  
  # read the info from the def file
  my %ai = $self->getinfo($file,@_);

  # that was the hard bit.  Now, do we need to do some adaption?
  return 0 unless $ai{adapting};
  return $ai{adapt_again};
}

=head4 _exec

  $job->_exec();

=cut

sub _exec {
  my $self=shift;
  my @args=@_;

  unless ($self->lets_pretend()) {
    # this is the easy case
    #
    # Ish.
    if (CFX5::Output::have_console()) {
      return do_cmd_quiet($Config{CFX5_REFINER_EXEC},@args);
    }

    # OK, we don't have a console.  This means we need to run the
    # solver without one too, get its output, and make it available
    # somewhere.
    my $out=get_cmd_output($Config{CFX5_REFINER_EXEC},@args);
    my $rv=$?;
    # unlike the solver, the output of the refiner goes straight into
    # the out file
    print $out;
    return $rv;
  }

  # this won't show up on-screen, but it will appear in the tjob file.
  $self->TraceLog('refiner-args',@args);

  # we need to create a file 'def' based on 'res', in this case we
  # just copy it
  copy_file('res','def');

  # but we need to say we've done all our adaption now, or we get an
  # infinite loop!
  {
    my $def=CFX5::DefFile->new('def');
    my $ds=$def->read_dataset(WHAT => "G/ADAPT/ADAPTING_STATE",
                              WHERE => 'EVERY',
                              WHEN => 0);
    $ds->data('FINAL');
    $def->write_dataset($ds);
  }

  # give some refiner output
  {
    my $refout=$self.' '.rand();
    print "This is some output from the refiner:\n  $refout\nHonest.\n";
    $self->TraceLog('%output-file-check', $refout);
  }

  # success
  return 0;
}


1;
