=head1 NAME

  CFX5::SolControl - Dynamic Control of the ANSYS CFX Solver

=head1 SYNOPSIS

Usage goes something like "create, set up, run":

  # make a new object for a new job
  $cont = new CFX5::SolControl('StaticMixer_002.dir');

  $cont->stop();

=head1 DESCRIPTION

This class implements an object representing a run of the solver, from
the point of view of the cfx5control script, to dynamically control
the execution of the solver process.

$Id$

=cut

package CFX5::SolControl;

use strict;

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

use CFX5::PkgLoader qw(cfx5_require);

use CFX5::PathManip qw(fullpath join_file get_cwd change_cwd); #dirname basename join_file

use CFX5::FileOps qw(create_empty_file set_file_contents); # 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
                    prepend_to_libpath);

#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::CCLI;
#use CFX5::Parallel;

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

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

=head2 CONSTRUCTORS

=head3 new

  $cont=new CFX5::SolControl($dir);

Creates a new solver control object.  The argument should be the path
to a directory containing a running solver.

=cut

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

  # now set up any new settings
  $self->initialize(@_)
    or return undef;

  return $self;
}

sub initialize {
  my $self=shift;
  my $dir=shift;

  CFX5::FileTests::datadir_w($dir, "ANSYS CFX Solver Run Directory $dir")
      or return 0;

      
  unless ($dir=~m /(.*)(_[0-9][0-9][0-9])$/) {
    for my $f (qw(def)) {
      unless (test_f join_file($dir,$f)) {
        _error("The directory $dir does not look like a ANSYS CFX Solver run directory: the file $f is missing.");
        return 0;
      }
    }
  }
  
  $self->{directory} = fullpath($dir);

  # Load files from custom data directory (if specified)
  cfx5_require('CFX5::CCLI');

  unless ( defined $Config{CFX_CUSTOM_DATA_DIR} ) {
    my $deffile=join_file($self->directory(), 'def');
    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();
    } 
  }

  1;
}

=head2 BASIC INFORMATION

=head3 directory

  my $dir=$cont->directory();

Returns the full path to the directory to which this object refers.

=cut

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

=head3 ok

  if (!$dir->ok()) {
    _warn("The run directory has disappeared while you were editing, nothing more I can do!");
    return '';
  }

Returns TRUE if the directory still appears to be OK (ie, is still there), or FALSE if it has gone.

=cut

sub ok {
  my $self=shift;
  return test_d($self->directory());
}

=head3 prefix

Returns a unique prefix (including the directory) for files owned by
this object inside the solver directory.

This file name will always start "tmp.", which will cause the solver
script to throw it away if it exits while the file exists.

=cut

sub prefix {
  my $self=shift;
  return join_file($self->directory(), "tmp.sc.$$");
}

=head2 SIMPLE CONTROL: STOP AND BACKUP

=head3 stop

  $cont->stop();

=cut

sub stop {
  my $self=shift;

  _trace("Requesting stop");

  my $fdir=$self->directory();

  if (-f join_file($fdir,'stp')) {
    _error("A stop request has already been sent to the ANSYS CFX Solver",
           "process running in the specified directory:",
           $fdir);
    return 0;
  }

  unless (create_empty_file(join_file($fdir,'stp'))) {
    _error("Error writing to file ", join_file($fdir,'stp'), ": $!");
    return 0;
  }

  _note("A stop request has been sent to the ANSYS CFX Solver\n",
        "process running in the following directory:\n",
        $fdir, "\n",
        "The process will stop shortly.\n");

  1;
}

=head3 renice

  $cont->renice();
  
  Writes a file with the new priority level for a solver run into the run directory

=cut

sub renice {
  my $self=shift;
  my $newpriority=shift;
  my $fdir=$self->directory();
  
  _trace("Reseting priority");

  my %ok = (
            Idle => 1,
            Low => 1,
            Standard => 1,
            High => 1,
           );

  unless ($ok{$newpriority}) {
    _error("$newpriority is an invalid priority setting.\n");
    return 0;
  }

  my %inewpr = (
                Idle => 0,
                Low => 1,
                Standard => 2,
                High => 3,
               ); 
               
  my $file=join_file($fdir,'renice');
  unless (set_file_contents($file,"$inewpr{$newpriority}\n")) {
    _error("Error creating renice file $file");
    return 0;
  }

  _note("The run time priority level of the ANSYS CFX Solver\n",
        "process running in the following directory:\n",
        $fdir, "\n",
        "will be reset to $newpriority priority at the end of the next iteration.\n");
	
  1;
}

=head3 backup

  $cont->backup();

=cut

sub backup {
  my $self=shift;

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

  _trace("Requesting backup");

  # for some reason, it looks like to get the solver to write a backup
  # file, we create a file called 'trn' ...
  my $file=join_file($dir,'trn');
  unless (test_f $file or create_empty_file($file)) {
    _error("Error writing to file $file: $!");
    return 0;
  }

  1;
}

=head2 RUNTIME SETTINGS

Various settings which control the operation of the other tasks.

=head3 suppress_backup

  $cont->suppress_backup(1);

Set to TRUE to avoid doing a backup before injecting new CCL.

=cut

sub suppress_backup {
  my $self=shift;
  if (@_) {
    my $new=shift;
    $self->{suppress_backup}=$new;
  }
  return $self->{suppress_backup};
}

=head2 OUTPUT CONTROL

For the dynamic reread, we need to be able to catch any output --
error messages and so on -- so we can insert them into the reread file
to be seen in the output file.

=cut

sub catch_output {
  my $self=shift;
  unless (exists $self->{output_handler}) {
    cfx5_require('CFX5::Output::Array');
    $self->{output_handler} = CFX5::Output::Array->new();
  }
  add_output_object($self->{output_handler});
}

=head2 CCL MANIPULATION

=head3 get_current_ccl

  my @ccl=$cont->get_current_ccl();

Reads the current CCL information from the named run directory, and
returns it.

TODO: what should happen in the case that there is a reread.def file
(whatever it is called) with new CCL?

On error, the error is signalled with _error, and an empty list is
returned.

=cut

sub get_current_ccl {
  my $self=shift;
  my $dir=$self->directory();

  # the CCL is left in a file called "ccl" by the solver script
  my $file=join_file($dir, 'ccl');
  unless (test_f $file) {
    _error("Unable to find current CCL in the run directory $dir");
    return ();
  }
  local *CCL;
  unless (open CCL, "<$file") {
    _error("Unable to read current CCL from $file: $!");
    return ();
  }
  my @rv;
  while (defined (my $l=<CCL>)) {
    chomp $l;
    push @rv, $l;
  }
  close CCL;

  return @rv;
}

=head3 inject_ccl

  unless ($cont->inject_ccl(@new_ccl)) {
    exit(1);
  }

Given a (complete) new physics CCL file, this will put it through the
ccl2flow/cclsetup process and write the new information into the
reread file.  On success, the return value will be true.

On error, the error is signalled with _error, and false is returned.
The directory will be left in a consistent state.  If the errors might
be user errors (eg, invalid CCL), they will also be written into the
reread file if possible so the solver can signal them in the output
file.

=cut

sub inject_ccl {
  my $self=shift;
  my $dir=$self->directory();

  my $cclapi;
  my @ccltext;

  if (ref $_[0]) {
    # if it's a reference, it's a CCL API object, which we'll use
    # directly rather than re-parsing it all ourselves -- mostly, this
    # allows us to skip re-reading the RULES
    $cclapi=shift;
  } else {
    @ccltext = @_;
  }

  $self->catch_output();

  _trace("Validating new CCL");

  # create a backup file.  It doesn't matter whether there are CCL
  # errors or not, as we're going to do some sort of update anyway, so
  # we still need a backup.
  unless ($self->suppress_backup()) {
    $self->backup();
  }

  cfx5_require('CFX5::CCLI');

  # update and validate the new CCL
  unless ($cclapi) {
    CFX5::CCLI::export_parser_conf();

    my $api=CFX5::CCLI->new_api_object();
    my $rv;

    _debug("Loading standard RULES ...");
    $rv=$api->readLibraries();
    if ($rv) {
      _error("Error reading the standard Command Language libraries:\n",
             CFX5::CCLI::format_ccl_error($rv));
      $self->record_errors();
      return '';
    }
    _debug("Loaded standard libraries.");

    $rv=$api->parseCCLData(\@ccltext);
    if ($rv) {
      _error("Error parsing the new Command Language data:\n",
             CFX5::CCLI::format_ccl_error($rv));
      $self->record_errors();
      return '';
    }

    # this shouldn't really get triggered, but just in case ...
    $rv=$api->updateSolverCCL();
    if ($rv) {
      _print(CFX5::CCLI::format_ccl_error($rv), "\n");
    }

    _debug("Validating ...");
    $rv=$api->validateCCLData();
    if ($rv) {
      _error("Error in validation of the new Command Language data:\n",
             CFX5::CCLI::format_ccl_error($rv));
      $self->record_errors();
      return '';
    }
    _debug("Done validating.");

    my @newtext;
    $rv=$api->getExpandedCCL(\@newtext);
    if ($rv) {
      _error("Error retrieving the updated form of the new Command Language data:\n\n",
             CFX5::CCLI::format_ccl_error($rv));
      # but continue anyway?
    } else {
      @ccltext=@newtext;
    }
  } else {
    # have an api, so we know it's validated, just get the CCL from it
    my @newtext;
    my $rv=$cclapi->getExpandedCCL(\@newtext);
    if ($rv) {
      _error("Error retrieving the updated form of the new Command Language data:\n\n",
             CFX5::CCLI::format_ccl_error($rv));
      # but continue anyway?
    } else {
      @ccltext=@newtext;
    }
  }

  # we need to record the current CCL so we can produce diffs etc
  my @origccl = $self->get_current_ccl();

  # Check there is no external solver coupling
  my $api = CFX5::CCLI->new_api_object();
  my $rv=$api->parseCCLData(\@origccl);
  unless ($rv) {
    my @extcpl_list;
    $api->getObjectsByType("EXTERNAL SOLVER COUPLING",\@extcpl_list);
    for my $extcpl (@extcpl_list) {
      my $opt = undef;
      $api->getStringValue("$extcpl/Option",\$opt) ;
      if (defined $opt && $opt ne 'None') {
        _error("Edit Run In Progress is not supported in simulations with external solver coupling.");
      }
    }
  }

  # generate diffs.  Should we maybe output them to the screen to give
  # people something to look at here?
  my @ccldiffs;
  _debug("Differencing CCL");
  cfx5_require('CFX5::CCLDiff');
  @ccldiffs = CFX5::CCLDiff->compareCCL(\@origccl, \@ccltext);
  exit_if_error();
  unless (@ccldiffs) {
    _warn("The requested CCL is the same as that already in use by this solver run.  Continuing anyway.");
  } else {
    # we remove the API's line-wrapping, as we're going to do our own
    # (non-CCL-compatible, but this is just diffs) anyway.
    my @newdiffs;
    for my $l (@ccldiffs) {
      if (@newdiffs and $newdiffs[$#newdiffs] =~ s!\\[\n\r]*$!!) {
        $newdiffs[$#newdiffs] .= $l;
      } else {
        push @newdiffs, $l;
      }
    }

    _print("Updating Command Language with the following changes:\n\n");
    _print map { " $_\n" } @newdiffs, "\n";
  }

  # Create dummy simulation and configuration and
  # read in current CCL, which determines executable selection
  cfx5_require('CFX5::Simulation');
  my $sim = CFX5::Simulation->new();
  $sim->add_configuration(1, 0, "Default");
  my $config = $sim->createConfig(1, 0);
  $config->read_ccl_nodef(\@origccl);

  # Set ccl2flow and cclsetup executables
  setconf('CFX5_CCL2FLOW_EXEC', $config->ccl2flow_exec());
  setconf('CFX5_CCLSETUP_EXEC', $config->cclsetup_exec());

  _trace("About to inject new CCL");

  # write the new CCL text to a temporary ccl file
  my $prefix=$self->prefix();
  set_file_contents("$prefix.ccl" => map { "$_\n" } @ccltext);

  # now we run ccl2flow, cclsetup, etc, to produce mms.setup (but
  # don't write the information into the def file OR THE REREAD FILE
  # yet)
  {
    my @out;
    my $rv=CFX5::CCLI::ccl2flow(join_file($dir, 'def'),
                                "$prefix.mms",
                                "$prefix.ccl",
                                \@out);
    if (!$rv) {
      _error("An error was encountered preprocessing the new Command Language:\n\n" . join("\n", @out));
      $self->record_errors();
      return '';
    }
    if (@out) {
      _print map { "$_\n" } @out;
    }
    if ($rv eq 'CFX-5.3') {
      # the user is weird
      _error("Dynamic update of solver Command Language cannot be used with solver runs which do not use the Command Language.");
      $self->record_errors();
      return '';
    }
  }

  unless ($self->ok()) {
    _abort("The ANSYS CFX Solver Run Directory", $self->directory(),
           "is no longer valid.  This probably means the solver run",
           "has ended during processing of your new data.");
  }

  {
    # cclsetup uses hardwired file names, so we have to run in this
    # directory
    my $cwd=get_cwd();
    unless (change_cwd($self->directory())) {
      _error("Unable to change to directory", $self->directory() . ": $!");
      return '';
    }

    my @out;
    my $rv=CFX5::CCLI::cclsetup("$prefix.mms",
                                "$prefix.setup",
                                \@out);

    unless (change_cwd($cwd)) {
      _warn("Unable to reset current directory to $cwd: $!");
    }

    if (!$rv) {
      if (@out) {
        _error("An error was encountered converting the new Command Language for the Solver:\n\n" . join("\n", @out));
      } else {
        # if there's no output, we assume we already got an error,
        # unless we didn't
        if (!error_pending()) {
          _error("An error was encountered converting the new Command Language for the Solver.");
        }
      }
      $self->record_errors();
      return '';
    }
    if (@out) {
      _print map { "$_\n" } @out;
    }
  }

  local *MMSFILE;
  unless (open MMSFILE, "<$prefix.setup") {
    _error("Error opening generated intermediate file $prefix.setup: $!");
    $self->record_errors();
    return '';
  }
  my @mmstext = map { s/\n\r*//g; $_ } <MMSFILE>;
  close MMSFILE;

  # anything else we need to do to prepare?

  # before going any further, check that the directory is still
  # reasonable.  If not, we'll abort here.
  unless ($self->ok()) {
    _abort("The ANSYS CFX Solver Run Directory", $self->directory(),
           "is no longer valid.  This probably means the solver run",
           "has ended during processing of your new data.");
  }

  $self->write_reread_file(\@ccltext, \@mmstext);

  return 1;
}

=head3 merge_ccl

Merges new CCL with the current CCL from the run, and injects that
into the run.

=cut

sub merge_ccl {
  my $self=shift;
  my @ccltext = @_;

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

  $self->catch_output();

  my $prefix=$self->prefix();

  my @origccl=$self->get_current_ccl();

  cfx5_require('CFX5::CCLI');

  # update and validate the new CCL
  CFX5::CCLI::export_parser_conf();

  my $api=CFX5::CCLI->new_api_object();
  my $rv;

  _debug("Loading standard RULES ...");
  $rv=$api->readLibraries();
  if ($rv) {
    _error("Error reading the standard Command Language libraries:\n",
           CFX5::CCLI::format_ccl_error($rv));
    $self->record_errors();
    return '';
  }
  _debug("Loaded standard libraries.");

  $rv=$api->parseCCLData(\@origccl);
  if ($rv) {
    _error("Error parsing the existing Command Language data:\n",
           CFX5::CCLI::format_ccl_error($rv));
    $self->record_errors();
    return '';
  }

  $rv=$api->parseCCLData(\@ccltext);
  if ($rv) {
    _error("Error parsing the additional Command Language data:\n",
           CFX5::CCLI::format_ccl_error($rv));
    $self->record_errors();
    return '';
  }

  # this shouldn't really get triggered, but just in case ...
  $rv=$api->updateSolverCCL();
  if ($rv) {
    _print(CFX5::CCLI::format_ccl_error($rv), "\n");
  }

  _debug("Validating ...");
  $rv=$api->validateCCLData();
  if ($rv) {
    _error("Error in validation of the new Command Language data:\n",
           CFX5::CCLI::format_ccl_error($rv));
    $self->record_errors();
    return '';
  }
  _debug("Done validating.");

  # now inject it following the normal procedure
  return $self->inject_ccl($api);
}

=head2 THE CCL EDITOR

These routines are stored in this file as, although their direct
function is not specific to the dynamic control, the modes of
operation and the error messages are.

=head3 edit_ccl

Edits the CCL for the given run, and then injects it as normal.

=cut

sub edit_ccl {
  my $self=shift;

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

  $self->catch_output();

  my $prefix=$self->prefix();

  my @origccl=$self->get_current_ccl();

  # first, write the old CCL text to a temporary ccl file
  set_file_contents("$prefix.ccl" => map { "$_\n" } @origccl)
    or return '';

  # run the editor
  unless ($self->edit_ccl_file("$prefix.ccl")) {
    return '';
  }

  # we have new CCL in the file
  local *CCLFILE;
  if ((! open CCLFILE, "<$prefix.ccl") && (-d "${dir}"))
  {
      _error("Error opening $prefix.ccl: $!");
      return '';
  }
  
  #
  # If the directory is present, the solver manager run is still in
  # progress.
  #
  my @ccltext = map { s/\n\r*//g; $_ } <CCLFILE>;
  close CCLFILE;

  # if the CCL hasn't changed in the editor -- ie the user has not
  # saved -- cancel the edit.  This is different from the explicit
  # injection case at the command line, where we go ahead anyway.
  # Note also that we don't try to compare the actual content of the
  # CCL, just the lines, so if the user saved a null change it may go
  # ahead and update it anyway.
  my $diff=0;
  # compare array lengths, save time if the change is obvious
  if (@origccl != @ccltext)
  {
    $diff++;
  }
  else
  {
    for (my $i=0; defined $origccl[$i] or defined $ccltext[$i]; ++$i)
    {
      if (!defined $origccl[$i] or !defined $ccltext[$i] or $origccl[$i] ne $ccltext[$i])
	{
        $diff++;
      }
    }
  }
  unless ($diff)
  {
    _note "No changes have been made to the Command Language.";
    return 1;
  }

  # now inject it following the normal procedure
  if (-d "${dir}")
  {
    return $self->inject_ccl(@ccltext);
  }
  else
  {
    return 1;
  }
}

=head3 edit_ccl_file

  $cont->edit_ccl_file('file.ccl');

Basic file-editing.  Runs the editor in safe mode on the specified
file.

=cut

sub edit_ccl_file {
  my $self=shift;
  my $cclfile=shift;

  cfx5_require('CFX5::FileTests');
  CFX5::FileTests::datafile_w($cclfile)
      or return '';

  cfx5_require('CFX5::CCLI');

  prepend_to_libpath($Config{CFX_QT_LIBDIR});
  CFX5::CCLI::export_parser_conf();

  export_conf(qw(CFX5_PHYSICS_EXEC CFX5ROOT CFXBUILDID CFX_NO_FRAME_VIEWER));

  export_conf(qw(CFX_DATA_DIR));

  # this is needed for the physics validator on NT
  if ($Config{windows_nt}) {
    prepend_to_libpath($Config{CFX_STL_LIBDIR});
  }

  cfx5_require('CFX5::FrameServ', 'CFX5::System');

  #CFX5::FrameServ::start_server();

  CFX5::System::lang_fixup();

  cfx5_require('CFX5::Commands');

  my $rv = CFX5::Commands::parse_rv(CFX5::Commands::do_cmd_quiet($Config{CFX5_CCLEDITOR_EXEC}, '-safe', $cclfile));
  if ($rv->is_success()) {
    return 1;
  } else {
    _error($rv->errstr("The CFX Command Language editor"));
    return '';
  }
}

=head2 REREAD FILE MANIPULATION

Routines for reading and writing the IO-module-controlled F<reread> file.

=head3 write_reread_file

  $cont->write_reread_file(\@ccltext, \@mmstext)
    or die;

Goes through the entire process of writing information into the reread
file.  The inputs are the new CCL and the new MMS.

This call will also write the ccl and mms.setup files in the run
directory, if the reread file is successfully written.  It will
correctly handle errors by calling C<write_reread_error> to explicitly
re-introduce the old settings where necessary.

=cut

sub write_reread_file {
  my $self=shift;
  my $rccltext=shift;
  my $rmmstext=shift;

  # now we write the reread.def file!
  $self->open_reread()
    or return '';

  # if one of these fails, it will signal an error.  In this case, we
  # don't want to record the current state, and we do want to attempt
  # to revert to the original by writing an error reread file if
  # possible.  Of course, if one of these failed, there's a good
  # chance the error file will fail too, but it may not -- for
  # example, an overlong string in the new CCL.
  unless ($self->write_reread_ds('G/COMMANDS', @$rccltext)
          and $self->write_reread_ds('G/MMS', @$rmmstext)) {
    return $self->write_reread_error();
  }

  # if we got this far, update the ccl file in the run directory
  # too. We do this while the reread file is open so we get the
  # benefit of its locking.  If one of these fail, we are basically
  # screwed; we'll just ignore it and hope things improve.  This could
  # give problems with differences in later runs, but apart from that
  # I don't think it'll cause too much hassle.
  set_file_contents(join_file($self->directory(), 'ccl'), map { "$_\n" } @$rccltext);

  # for now, we have to save somewhere the MMS too, as we need to write
  # it again for a dummy dynamic restart.
  set_file_contents(join_file($self->directory(), 'mms.setup'), map { "$_\n" } @$rmmstext);

  # if we have any output, write that in too, so any errors or
  # warnings are recorded along with the actual differences.  If this
  # fails, there's no point in trying an error file, as that's just
  # going to do the exact same thing again.
  $self->write_reread_output();

  $self->close_reread()
    or return '';

  return 1;
}

=head3 write_reread_error

  $cont->write_reread_error(\@ccltext, \@mmstext)
    or die;

Writes the original information from the ccl and mms.setup files into
the reread file, along with the output history.  This is for use on
errors, where we wish to record the error in the solver output file,
but not update the run.

=cut

sub write_reread_error {
  my $self=shift;

  # now we write the reread.def file!
  $self->open_reread()
    or return '';

  local *CCLFILE;
  unless (open CCLFILE, "<".join_file($self->directory(),"ccl")) {
    _error("Error opening old file ccl: $!");
    $self->close_reread();
    return '';
  }
  my @ccltext = map { s/\n\r*//g; $_ } <CCLFILE>;
  close CCLFILE;

  local *MMSFILE;
  unless (open MMSFILE, "<".join_file($self->directory(),"mms.setup")) {
    _error("Error opening old file mms.setup: $!");
    $self->close_reread();
    return '';
  }
  my @mmstext = map { s/\n\r*//g; $_ } <MMSFILE>;
  close MMSFILE;

  # if one of these fails, it will signal an error.  We still want to
  # go through and close down properly, though ... I think.
  $self->write_reread_ds('G/COMMANDS', @ccltext);
  $self->write_reread_ds('G/MMS', @mmstext);

  # if we have any output, write that in too, so any errors or
  # warnings are recorded along with the actual differences.
  $self->write_reread_output();

  $self->close_reread()
    or return '';

  1;
}

=head3 record_errors

  $cont->record_errors();
  return '';

In some cases, if relevant, this will write a reread file containing
the old information from before this run started and the current set
of error messages, using C<write_reread_error>.

If for whatever reason that seems not to be sensible, it will not do
it.  This could be because the directory no longer exists, or because
we are running from the command line and so the user can see the error
anyway. 

=cut

sub record_errors {
  my $self=shift;
  return unless $self->ok();

  # for now, we always do it.  This could/should change later.
  return $self->write_reread_error();
}

=head3 open_reread

  $cont->open_reread();

Opens the reread file using the IO module.  If the reread file is
already open, does nothing.

=cut

sub open_reread {
  my $self=shift;
  unless (defined $self->{reread}) {
    my $filename=join_file($self->directory(), 'reread');
    # we do not use CFX5::DefFile, as we wish to have more control
    # over the process.  Particularly, we want to hold the file open
    # while we write to it.  However, we'll use the code from
    # CFX5::DefFile to find it!
    cfx5_require('CFX5::DefFile');
    CFX5::DefFile::init_xs();
    $self->{reread}=CFX5::IO::Base->new();
    if ($self->{reread}->open_primaryfile($filename)) {
      _error("Error from IO module while opening $filename:\n",
	     $self->{reread}->errstr());
      return '';
    }
  }
  1;
}

=head3 write_reread_ds

  $cont->write_reread_ds('G/NAME' => @lines);

Writes the given lines of data into the named dataset, with
WHERE=EVERY, WHEN=0.

=cut

sub write_reread_ds {
  my $self=shift;
  my $dsname=shift;
  my @contents=map { split("\n\r*", $_) } @_;

  my $io=$self->{reread};

  my $hdr=CFX5::IO::Header->new();
  $hdr->what($dsname);
  $hdr->where('EVERY');
  $hdr->when(0);
  $hdr->istrct('STRI');
  $hdr->nstrct(scalar @contents);

  my $data=CFX5::IO::Data->new();
  $data->numstring(scalar @contents);
  for (my $i=0; $i < @contents; ++$i) {
    $data->stringelt($i => $contents[$i]);
  }

  if ($io->write_dataset_hdrdata($hdr, $data)) {
    _error("Error writing dataset $dsname:\n",
           $io->errstr());
    return '';
  }

  1;
}

=head3 write_reread_output

Writes the current stashed output into the reread file, in the
G/COMMANDS/UPDATEMSG dataset.

=cut

sub write_reread_output {
  my $self=shift;
  my @data=$self->{output_handler}->in_box();
  return $self->write_reread_ds('G/COMMANDS/UPDATEMSG' => @data);
}

=head3 close_reread

  $cont->close_reread();

Closes the reread file using the IO module.  If the reread file is
already closed, does nothing.

=cut

sub close_reread {
  my $self=shift;

  my $io=$self->{reread};
  $self->{reread}=undef;

  if (defined $io) {
    if ($io->close_primaryfile()) {
      _error("Error from IO module while closing reread file:\n",
             $io->errstr());
      return '';
    }
  }

  1;
}

1;
