=head1 NAME

  CFX5::Simulation::Configuration::Indirect - Indirect Start Method support

=head1 SYNOPSIS

This object is used by L<CFX5::Simulation::Configuration>, to run the solver via a an indirect start method (an external system)

=head1 DESCRIPTION

$Id$

=cut

package CFX5::Simulation::Configuration::Indirect;

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

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

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::Config qw(%Config export_conf);

use CFX5::CCLI;

use CFX5::Simulation::Util '_check_defined';

use CFX5::PkgLoader qw(cfx5_require);

use Carp;


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 or 2") 
    unless defined $do and ($do eq 0 or $do eq 1 or $do eq 2); 

  if($do){
    return run_indirect_job($self);
  } else {
    # Are there any checks we can do??
    return 1;
  }

}

sub can {
  my $self;
  return 1;
}

sub run_indirect_job {
  my $job=shift;
  my @args=@_;

  my $sm=$job->start_method_obj();
  my $ppe=$job->ppe_obj();

  # first the output file
  $job->StartOutFile();

  # Display CCL update log
  my $cclupdatelogobj = $job->cclupdatelog();
  if ($cclupdatelogobj->have_data()) {
    $job->OutFileHeader('CFX Command Language Upgrade');
    while (my $text = $cclupdatelogobj->get_next()) {
      _trace('Individual CCL Upgrade for Indirect');
      $job->outobj->print($text);
    }
  }

  $job->OutFileHeader("Solver Startup: " . $sm->name());

  my $workdir=$job->rundir_obj();
  my $resfile=$job->resfile();
  _check_defined $resfile, "ANSYS CFX results file" or return 0;
  CFX5::FileTests::datafile_w($resfile, "ANSYS CFX results file") or return 0;

  $job->outobj()->message("Using start-method options: ".join(" ", @{$sm->{smoptions}}) ) if defined $sm->{smoptions};


  # write the CCL, that we probably read from the Solver Manager, into
  # the working directory.
  {
    my $f=$job->rundir_obj()->creatable_file('startup.ccl');
    my @ccl=$job->get_current_ccl();

    # Fix up multiconfig initial values CCL if necessary
    # cannot refer to a configuration, must be the real file name
    # for path mapping to succeed
    # NB should move this up to the simulation level
    # should be possible to run a configuration in isolation
    if( not $job->parent()->singleconfig() and (my @inifiles=$job->GetIniFiles()) ){
      my $tmpapi=CFX5::CCLI::new_api_object();
      my @newccl;
      $tmpapi->parseCCLData(\@ccl);
      my $ivc=$tmpapi->havePath('/SIMULATION CONTROL/EXECUTION CONTROL/RUN DEFINITION/INITIAL VALUES SPECIFICATION/INITIAL VALUES CONTROL');
      $tmpapi->deleteObj('/SIMULATION CONTROL/EXECUTION CONTROL/RUN DEFINITION/INITIAL VALUES SPECIFICATION');
      push @newccl,     "SIMULATION CONTROL:",
                        "  EXECUTION CONTROL:",
                        "    RUN DEFINITION:",
                        "      INITIAL VALUES SPECIFICATION:";
      if($ivc){
        push @newccl,   "        INITIAL VALUES CONTROL:";
        if($job->continue_history()){
          push @newccl, "          Continue History From = Initial Values 1";
        }
        push @newccl,   "          Use Mesh From = ".$job->use_mesh_from();
        push @newccl,   "        END";
      }

      my $j=1;
      foreach my $i (@inifiles){
        push @newccl,   "        INITIAL VALUES: Initial Values $j",
                        "          File Name = $i",
                        "          Option = Results File",
                        "        END";
        $j+=1;
      }
      push @newccl,     "      END",
                        "    END",
                        "  END",
                        "END";
      $tmpapi->parseCCLData(\@newccl);
      $tmpapi->getFormattedCCL("/", 78, \@ccl);
    }

    # what if the file already exists?  We unlink it just in case,
    # lacking a write_file call.
    unlink($f);
    append_to_file($f,map { "$_\n" } @ccl);
  }

  #
  # this is largely taken from CFX5::Parallel::do_cmd, it sets up the
  # prerequisites and the command, and then runs it.
  #

  for my $req ($sm->prerequisites()) {
    $ppe->setup_req($req)
      or return CFX5::Commands::Result->new_not_run("Setup of $req failed");
  }

  my $master=CFX5::Parallel::Host->new($job, $sm->remote_host());

  $sm->use_solver_args(@args);

  my $command=$sm->indirect_command();

  if ($command !~ m!indirect_args!) {
    _warn("The indirect startup command does not contain the \%{indirect_args} substitution, and probably will not work.");
  }

  $command=$sm->fill_shared_template($command, $ppe);
  $command=$sm->fill_host_template($command, $ppe, 1, $master, 'this is the master');

  my @indirect_args = ('-indirect-startup-path' => $job->fullname());

  if ($job->parent()->indirect_options()) {
    my @options = split('\s+', $job->parent()->indirect_options());

    for my $option (@options) {
      next unless ($option);
      my ($arg,$value) = split('=',$option,2);
      next unless (defined $arg and defined $value);
      push @indirect_args, '-'.$arg, $value;
    }
  }

  $command=$sm->fill_indirect_template($command, $job, @indirect_args);

  $sm->assert_fully_replaced($command)
    or return CFX5::Commands::Result->new_not_run("start command template error");

  $job->outobj()->message("Starting the ANSYS CFX Solver on", $master->descname(),
                          "using the command\n\n"
                          . "  $command\n\n"
                          . "Please wait.");

  if (defined (my $shlib=$sm->shared_library_path())) {
    _debug("Adding $shlib to the library path");
    cfx5_require('CFX5::Config');
    $shlib=$sm->fill_shared_template($shlib);
    $shlib=$sm->fill_host_template($shlib, $ppe, 1, $master, 'this is the master');
    $sm->assert_fully_replaced($shlib)
      or return CFX5::Commands::Result->new_not_run("start command template error");
    CFX5::Config::prepend_to_libpath($shlib);
  }

  my @cmd=CFX5::Commands::split_cmd($command);
  if (not @cmd) {
    # the command contains shell metacharacters, or was otherwise too
    # difficult to split up into its constituent pieces: we'd better
    # pass it through the shell.
    @cmd = $command;

    # However, we do want to see if the command being run exists;
    # since this will prevent do_cmd from making the check, we'll do
    # it ourselves here.
    my $exec=CFX5::Commands::split_cmd_exec($command);
    if (defined $exec) {
      CFX5::Commands::check_for_executable($exec)
          or return CFX5::Commands::Result->new_not_run("Executable $exec not found");
    } else {
      # we can't even figure out what the leading executable is!
      _debug("Complicated shell command $command, executable not checked");
    }
  }

  _debug("Running parallel solver:", @cmd);

  # we used to use do_termerr_cmd.  Host->do_batch_cmd uses
  # do_output_cmd on remote hosts.  We don't worry about this too much,
  # because the solver (which is the most common use here) redirects
  # its own stdout anyway!
  my $rv=$master->do_batch_cmd(@cmd);

  # check for additional output
  unless ($rv->is_success()) {
    my @errs=$rv->term_error();
    push @errs, $sm->gather_errors();
    $rv->set_term_error(@errs);
    _error("Indirect start method returned non-zero exit code");
  }

  1;

}

1;
