#! perl -w

##
## Name:
##
## cfx5interp
##
## Purpose:
##
## Interpolate ANSYS CFX solutions onto new meshes.
##
## Synopsis:
##
#S New Run Using One or Multiple Results Files:
#S cfx5interp -results <file> -mesh <file> [<arguments>]
#S cfx5interp -results <file> -results <file> -mesh <file> [<arguments>]
#S
#S Continue Run Using One or Multiple Results Files:
#S cfx5interp -continue-from <file> -mesh <file> [<arguments>]
#S cfx5interp -continue-from <file> -results <file> -mesh <file> [<arguments>]
#S
#S Difference Mode:
#S cfx5interp -difference -from <file> -destination <file> [<arguments>]
##
## []  denotes an optional argument.
## |   separates mutually exclusive arguments.
## ... following an argument indicates that it may be repeated.
## <>  and # denote that substitution of a suitable value is required. All 
##     other arguments are keywords, some of which may have a short form.
##
## Arguments for both interpolators:
##
## -help
## -h
##     Print this message.
##
#A -mesh <file>
##     <file> should be a ANSYS CFX Definition file containing the mesh 
##     onto which the solution should be interpolated. 
##     N.B. This file will be modified by cfx5interp.
##
#A -destination <file>
#A -dest <file>
##     When used with -difference, sets the ANSYS CFX results file which
##     contains the "new values" of the variables, and into which the
##     differencing information will be written.  NB this file will be
##     modified by cfx5interp.
##
#A -difference
#A -diff
##     Calculates the differences between an original set of fields,
##     specified with the -from switch, and a new set, specified with
##     -destination.
##
#A -from <file>
##     When used with -difference, gives the ANSYS CFX results file
##     containing the original values which are to be compared.
##
## -results <file>
#A -res <file>
##     <file> should be a ANSYS CFX Results file containing a solution.
##     This will be unchanged by cfx5interp. The solver-based 
##     interpolator can interpolate from multiple results files 
##     using multiple -results switches with each containing
##     one results file.
##
## Arguments for the new interpolator only:
##
## -continue-from <file>
#A -cont-from <file>
##     <file> should be a ANSYS CFX Results file containing a solution.
##     The transient information and particle database if available
##     in this file will be used to continue the run in the solver.
##     This file will be unchanged by cfx5interp.
##
#A -double
##     Use the double precision solver instead of the standard solver.
##
#A -large
##     Use the large problem interpolator instead of the standard executable.
##
#A -priority <level>
#A -pri <level>
##     Sets the default priority to run the solver-based interpolator at.
##     <level> should be one of
##       Idle
##       Low (1)
##       Standard (2)
##       High (3 or 4)
##     To be consistent with ANSYS CFX solver, a numeric setting is
##     also accepted where indicated above.
##
#A -size <factor>
#A -s <factor>
##     Change the memory estimates used by the solver-based interpolator
##     by a factor of <factor>.  By default the memory estimates contained
##     in the ANSYS CFX Solver Input File are used.  Sometimes these are
##     inaccurate and this option needs to be used to increase the
##     memory allocated.
##
#A -size-cat <size>
#A -size-nr <size>
#A -size-ni <size>
#A -size-nd <size>
#A -size-nc <size>
#A -size-nl <size>
#A -scat <size>
#A -nr <size>
#A -ni <size>
#A -nd <size>
#A -nc <size>
#A -nl <size>
##     These flags are for advanced users to change the memory allocation
##     parameters for the solver-based interpolator. Usually, you
##     should use the -size argument instead. <size> is the desired 
##     memory allocation in words, and may have K or M appended for
##     kilo- or mega-. If the suffix is 'x', the number is treated
##     as a multiplier rather than an explicit factor.
##
## Arguments for the old interpolator only:
##
#A -interpolate-old
#A -interp-old
##     Use this switch to use the old interpolator from ANSYS CFX 10.0 
##     release or earlier to perform the interpolation.
##
#A -mcl <size>
#A -mxl <size>
##     These options allow advanced users to change the memory
##     allocation for certain stages of the interpolation process
##     when using the old interpolator.
##
#A -vertex <file>
#A -vtx <file>
##     <file> should contain a list of vertices. If this option is
##     specified a new file will be created in the current directory
##     with a same name as the results file but a suffix .inn, where
##     nn is chosen to make a unique file name. Please does not use 
##     this option with the solver-based interpolator.
##
#U ****************************************************************************
#U *                                                                          *
#U *     The following features are supported only within CFDS and are not    *
#U *     shown by the -help option when the command is run elsewhere.         *
#U *                                                                          *
#U ****************************************************************************
#U
#U Unsupported Features:
#U
#U Arguments:
#U
#U -ccl <file>
#U
#U  Reads Command Language from the named file using the solver-based
#U  interpolator, and includes it in the setup of the interpolation.
#U
#U -executable <file>
#U -exec <file>
#U
#U Use <file> instead of the standard version of interp.exe
#U
#U -norun
#U     Set up the directory, but stop before running the interpolator.
#U
#U -parameters <file>
#U -params <file>
#U
#U  Use <file> as an input file containing extra data for interp.exe
#U
#U -out <file>
#U
#U  Send output to the named file rather than stdout/stderr.  For use
#U  by the Solver Manager only.
#U
#U -save
#U     Do not delete the working directory at the end of the run.
#U
#U Environment Variables:
#U
#U If the environment variable CFX_INTERP_EXEC is set and the -exec
#U option is not used on the command line its value will be treated
#U as the name of an executable program to be run instead of the 
#U standard version of interp.exe.
#U
#U Files:
#U
#U If a file called interp.params exists in the directory where cfx5interp
#U is run it will be treated as an input file containing extra data for 
#U interp.exe unless a different file was specified on the command line using
#U the -parameters option.
#U
#------------------------------------------------------------------------------

# get the standard ANSYS CFX library directory
BEGIN {
  if ($0 =~ m!^([a-z]:)?[\\/]!i) {
    # it's an absolute path, use it as-is
    (my $nd=$0) =~ s!(perllib[\\/])?[^\\/]*$!!i;
    unshift @INC, "$nd/perllib";
  } else {
    # wrap this in an eval, and if it fails, pray ... for example,
    # this can fail on Windows if we're running off a share.
    eval {
      require FindBin;
      FindBin->import();
      unshift @INC, "$FindBin::Bin/perllib";
      my $dummy=$FindBin::Bin; # kill used only once warning
    };
    warn $@ if $@;
  }
  if (defined $ENV{CFX_LOCAL_PERLLIB}) {
     unshift @INC, $ENV{CFX_LOCAL_PERLLIB};
  }

}

use CFX5::Init;
CFX5::Init::init();

use vars qw($CVSVersion);
sub BEGIN {
  $CVSVersion='@(#) $Id$\n';
}

use strict;

use CFX5::Output qw(:all);
use CFX5::Config qw(%Config export_conf append_to_libpath prepend_to_libpath);
use CFX5::PrintConfig;

use CFX5::CmdLine qw(:util parse_cmdline);

#use CFX5::FileTests;

use CFX5::PathManip qw(join_file basename dirname fullpath);
use CFX5::Commands qw(do_cmd handle_rv do_cmd_quiet);
use CFX5::Job;

use CFX5::FileOps qw(move_file_to_backup set_file_contents append_to_file);

use CFX5::WorkDir;

use CFX5::Parallel::MPI;
use CFX5::PatchInfo;
use CFX5::TimeDate qw(timedatestamp);

#
# read the configuration
#
CFX5::Config::read_config();

#
# Parse the command line
#

use vars qw($meshfile $resfile @resfiles $fromfile @fromfiles $contfile $destfile $vtxfile $paramfile $cclfile @nonopt $outfile $mcl $mxl $save $intmode $contmode $diffmode $diffswitch $norun $olddef $interp_old @passdown_args $exec);

my $Attrs=CFX5::Attr->new();

# 0 => unset; we need some value for mcl as 
$mcl=0; $mxl=0;

@nonopt = parse_cmdline
  (\@ARGV,
   'INCLUDE_REQ',
   'ALLOW_NON_OPTION',

   # B/C
   '-def+'        => sub { 
     conf_datafile_w(\$olddef,undef,@_);
     _warn("The -def switch is deprecated and will be removed shortly, please use the -results and -mesh switches for interpolation or the -difference, -from and -dest switches for differencing.");
   },

   # interpolation
   # --> conf_datafile and conf_datafile_w are used for -mesh option because we need
   #     to ensure that the file is readable, writable and exists
   '-continue-from+' => sub { $contmode++; conf_datafile(\$contfile,undef,@_); },
   '-cont-from+'     => '-continue-from',
   '-mesh+'          => sub { $intmode++; conf_datafile(\$meshfile,undef,@_); conf_datafile_w(\$meshfile,undef,@_); },
   '-res+'           => sub { $intmode++; conf_datafile_m(\@resfiles,undef,@_); },
   '-results+'       => '-res',
   '-vtx+'           => sub { $intmode++; conf_datafile(\$vtxfile,undef,@_); },
   '-vertex+'        => '-vtx',

   # differencing, require -difference
   # --> conf_datafile and conf_datafile_w are used for -dest option because we need
   #     to ensure that the file is readable, writable and exists
   -difference    => sub { $diffmode++; $diffswitch++; },
   -diff          => '-difference',
   '-from+'       => sub { $diffmode++; conf_datafile_m(\@fromfiles,undef,@_); },
   '-destination+' => sub { $diffmode++; conf_datafile(\$destfile,undef,@_); conf_datafile_w(\$destfile,undef,@_); },
   '-dest+'       => '-destination',

   # other misc parameters
   '-mcl+'        => \$mcl,
   '-mxl+'        => \$mxl,
   '-ccl+'     => sub { conf_datafile(\$cclfile,undef,@_); },
   '-params+'     => sub { conf_datafile(\$paramfile,undef,@_); },
   '-parameters+' => '-params',
   '-double' => sub { $Attrs->set_attr('double'); },
   '-priority+' => sub {
     my $priority=$_[1];
     if ($priority =~ m!^[01234]$!) {
       # 'Idle' is also accepted, but has no numeric equivalent, as
       # I'm trying to deprecate the numerics!
       my %map=(
                0 => 'Idle',
                1 => 'Low',
                2 => 'Standard',
                3 => 'High',
                4 => 'High',
               );
       $priority=$map{$priority};
     }
     my %pri=(
               Idle => 0,
               Low => 1,
               Standard => 2,
               High => 3,
              );
      if (defined $pri{$priority}) {
        push @passdown_args, '-pri', $pri{$priority};
      }
   },
   '-pri+' => '-priority',
   '-exec+'       => \$exec,
   '-executable+' => '-exec',
   '-norun'       => \$norun,
   '-out+'        => sub { conf_datafile_w(\$outfile,undef,@_); },
   '-save'        => \$save,
   '-debug'       => \&CFX5::CmdLine::handle_std_arg,
   '-large'       => sub { $Attrs->set_attr('int64'); },
   # Reverse back to the old interpolation method
   '-interpolate-old' => \$interp_old,
   '-interp-old'      => '-interpolate-old',
   # memory switches for the solver interpolation method
   "-size+" => sub {
     my $f = $_[1] ;
     if (defined $f) {
       if ($f =~ m/^([0-9]+|[0-9]*\.[0-9]+|[0-9]+\.[0-9]*)$/) {
         push @passdown_args, '-s', $f;
       };
     };
   },
   '-s+' => '-size',
   '-size-cat+' => sub { cat_factor($_[1]) ; },
   '-size-nr+' => sub { memory_factor('nr',$_[1]) ; },
   '-size-ni+' => sub { memory_factor('ni',$_[1]) ; },
   '-size-nd+' => sub { memory_factor('nd',$_[1]) ; },
   '-size-nc+' => sub { memory_factor('nc',$_[1]) ; },
   '-size-nl+' => sub { memory_factor('nl',$_[1]) ; },
   '-scat+' => '-size-cat',
   '-nr+' => '-size-nr',
   '-ni+' => '-size-ni',
   '-nd+' => '-size-nd',
   '-nc+' => '-size-nc',
   '-nl+' => '-size-nl',
  );

sub cat_factor {
     my $f = shift ;
     if (defined $f) {
       if ($f =~ s!x$!!i) {
       # "1.5x" actually maps to a different interpolator argument
         push @passdown_args, "-smms", $f;
       } else {
         if ($f =~ m/^\d+[KkMm]?$/) {
            push @passdown_args, "-nmms", $f;
         } else {
            _error("Invalid syntax for parameter /SIMULATION CONTROL/EXECUTION CONTROL/INTERPOLATOR STEP CONTROL/MEMORY CONTROL/Catalogue Size Override = $f");
         }

       }
     }
}

sub memory_factor {
     my $t = shift ;
     my $f = shift ;
     if (defined $f) {

       # Initialize the memory flags for error message printout.
       my %pairs=(
                  nr => 'Real',
                  ni => 'Integer',
                  nd => 'Double Precision',
                  nc => 'Character',
                  nl => 'Logical',
                 );

       if ($f =~ s!x$!!i) {
       # "1.5x" actually maps to a different interpolator argument
         (my $a=$t) =~ s!^n!s!;
         push @passdown_args, "-$a", $f;
       } else {
         if ($f =~ m/^\d+[KkMm]?$/) {
            push @passdown_args, "-$t", $f;
         } else {
            _error("Invalid syntax for parameter /SIMULATION CONTROL/EXECUTION CONTROL/INTERPOLATOR STEP CONTROL/MEMORY CONTROL/$pairs{$t} Memory Override = $f");
         }
       }
     }
}

if (defined $outfile) {
  # the output file is used by the (old only?) solver manager to read
  # the output, so it must be opened ASAP.
  open STDOUT, ">>$outfile" or _abort "Cannot write to output file: $!";
  open STDERR, ">&STDOUT" or _abort "Cannot write to output file: $!";
}

if ($olddef and @nonopt) {
  unless (defined $resfile) {
    $resfile=shift @nonopt;
  }
}

if (@nonopt) {
  _error("Unexpected non-option arguments", @nonopt, "were found on the command line.");
}

exit_if_error();

if ($interp_old and $contmode) {
  _error("Incompatible command line switches used, you cannnot use the continue-from switch for the old interpolator.");
}

if ($diffmode and $contmode) {
  _error("Incompatible command line switches used, you cannnot use the continue-from switch for differencing.");
}

if ($diffmode and $intmode) {
  _error("Incompatible command line switches used, you must use only switches for interpolation or for differencing.");
}

if (not $diffswitch and not $intmode and not $olddef and not $contmode) {
  _error("You must give either -results or -difference switch.");
}

if ($diffmode and not $diffswitch) {
  _error("You must use the -difference switch with the -from and -destination switches.");
}

exit_if_error();

if ($olddef) {
  $meshfile=$olddef;
}

if ($diffmode) {
  unless (defined $fromfiles[0]) {
    _error("You must give a source file for differencing (-from).");
  } else{
    unless (scalar(@fromfiles) == 1) {
      _error("You must give only one ANSYS CFX results file from which to compare the difference (-from).");
    }
  }
  unless (defined $destfile) {
    _error("You must give a destination file for differencing (-destination).");
  }
}
if ($intmode or $olddef) {
  if ($contmode) {
    unless (defined $contfile) {
      _error("You must give one ANSYS CFX results file from which to continue the run (-continue-from).");
    }
    unless ($contmode == 1) {
      _error("You must give only one ANSYS CFX results file from which to continue the run (-continue-from).");
    }
    unless (defined $meshfile) {
      _error("You must give a ANSYS CFX definition file, onto which to interpolate the results to continue the run (-mesh or -vertex).");
    }
  } else {
    unless (@resfiles) {
      _error("You must give one or more ANSYS CFX results files from which to start a new run (-results).");
    }
    unless (defined $meshfile or defined $vtxfile) {
      _error("You must give a ANSYS CFX definition file or vertex file, onto which to interpolate the results to start a new run (-mesh or -vertex).");
    }
  }
}

exit_if_error();

sub find_outfile_name {
  my $resfile=shift;
  my $c=0;
  (my $base=basename($resfile)) =~ s!\.res!!i;
  $base .= '.i';
  while (1) {
    $c=sprintf("%02d",$c);
    my $file=$base.$c;
    return $file unless -e $file;
    $c++;
  }
  undef;
}

#
# go to!
#

CFX5::PrintConfig::show_vars_by_verbosity();

# find a name for the working directory
my $dir;
{
  my $wrkname="\#cfx.".$$;
  $dir=new CFX5::WorkDir($wrkname);
  exit_if_error();
  # this should never trigger
  _abort "Error creating working directory"
    unless defined $dir;
}

# parameters file if there is one
if (defined $paramfile) {
  $dir->input_file('interp.params' => $paramfile);
} elsif (-f 'interp.params') {
  $dir->input_file('interp.params' => 'interp.params');
}

# if we need to, create a memory.dat file
if ($mcl or $mxl) {
  my $name=$dir->creatable_file('memory.dat');
  set_file_contents($name, "$mcl\n$mxl\n");
  # discard at the end
  $dir->mark_output_file_ignore('memory.dat');
}

#
# what are we doing?  There are two choices:
#
#  * Interpolation from a results file onto the mesh in a definition
#    file, modifying the original definition file as we go.
#
#  * Interpolation from a results file using data in a vertex file,
#    writing results to a new file.
#
if ($intmode or $olddef) {
  if ($meshfile) {
    # input mesh from the definition file, and write output to it too
    $dir->input_file('cfx5.def' => $meshfile);
    $dir->mark_output_file('cfx5.def');
  } elsif (defined $vtxfile) {
    $dir->input_file('vertex.dat' => $vtxfile);

    $resfile  = $resfiles[0] ;
    my $vtxout=find_outfile_name($resfile);

    $dir->creatable_file('vertex.dat.new' => $vtxout);
    $dir->mark_output_file('vertex.dat.new');

    _info("Writing output to $vtxout");
  } else {
    internal_error("Should never get here!");
  }

  # Process single results file case. 
  if ($interp_old) {
    if (scalar(@resfiles) == 1) {
      $resfile = $resfiles[0] ;
      $dir->input_file('cfx5.ini',$resfile) ;
    } else {
      _error("You must provide only one ANSYS CFX results files to the old interpolator.");
    }
  # Process multiple results file case. 
  } else {
    # Initialize the counter for results files.
    my $count = 0 ;
    # Get number of results files.
    my $ninifiles = scalar(@resfiles) ; 
    # Process continue-from file (always the first one). 
    # Increase the file counter and number of results files by 1.
    if ($contmode) {
      push @passdown_args, '-rest' ; 
      $dir->input_file('cfx5_001.ini',$contfile);
      $count = 1 ;
      $ninifiles++ ; 
    }
    # push down the number of input results files.
    push @passdown_args, '-nini', $ninifiles ; 
    if ($contmode) { $count = 1 ; }
    foreach my $resfile (@resfiles) {
      # increase the file count.
      $count++ ;
      $count=sprintf("%03d",$count);
      # set the initial file name.
      my $inifile='cfx5_'.$count.'.ini' ;
      # input the results file.  Will not be modified.
      $dir->input_file($inifile,$resfile);
    }
  }

  # skip if we got the -def argument. TODO: remove all this!
  if ($intmode) {
    my $name=$dir->creatable_file('mode.dat');
    set_file_contents($name, "interpolate\n");
    # discard at the end
    $dir->mark_output_file_ignore('mode.dat');
  }

} elsif ($diffmode) {
  # input the results file.  Will not be modified.
  $fromfile = $fromfiles[0] ;
  if ($interp_old) {
    $dir->input_file('cfx5.ini',$fromfile);
  } else {
    $dir->input_file('cfx5_001.ini',$fromfile);
  }

  # input mesh from the definition file, and write output to it too
  $dir->input_file('cfx5.def' => $destfile);
  $dir->mark_output_file('cfx5.def');

  my $name=$dir->creatable_file('mode.dat');
  set_file_contents($name, "difference\n");
  # discard at the end
  $dir->mark_output_file_ignore('mode.dat');

} else {
  internal_error("Should never get here!");
}

if ($norun) {
  _warn("Terminating run prematurely at user request.");
  exit(0);
}

# Add MPI library paths to the library path
CFX5::Parallel::MPI::export_conf();

# Set the path to the executable
my $interp_exec;
if ($interp_old) {
  $interp_exec = $Config{CFX_INTERP_EXEC};
} else {
  if ($exec) {
    $interp_exec = $exec;
  } elsif ($Config{CFX_SOLVER_INTERP_EXEC}) {
    $interp_exec = $Config{CFX_SOLVER_INTERP_EXEC};
  } else {
    $interp_exec = join_file($Config{CFX5EXECDIR}, scalar($Attrs->partial_attr_list()), 'solver-mpi.exe');
  }

  push @passdown_args, '-interp', '-stdout' ; 
}

CFX5::FileTests::execfile($interp_exec,"ANSYS CFX Interpolation program $interp_exec");

exit_if_error();

# Create Job object
my $job = new CFX5::Job();
$job->text_command([$0,@ARGV]);

# Create cfx5interp.ccl for the complete CCL used for the run
if ($dir->have_file('cfx5.def')) {
  # Read CCL from def file
  $job->deffile(join_file($dir->dir(),'cfx5.def'));
  $job->read_def_ccl();

  # Merge supplementary CCL, if specified
  if (defined $cclfile and (-f $cclfile)) {
    $job->read_ccl($cclfile);
  }
  my $file=$dir->creatable_file('cfx5interp.ccl');
  my @ccl=$job->get_current_ccl();
  # what if the file already exists?  We unlink it just in case,
  # lacking a write_file call.
  unlink($file);
  append_to_file($file,map { "$_\n" } @ccl);
}

# Show time, date, user, host, architecture and command used
my $tdstamp = timedatestamp();
my $text = "This run of the CFX-$Config{CFXBUILDID} Interpolator started $tdstamp\n".
           "by user $Config{CFX_USER} on $Config{CFX_HOST}".
           " ($Config{CFX5_HOST_ARCH})";

if (defined $job->text_command()) {
  if (ref $job->text_command() eq 'ARRAY') {
    my $col=0;
    my $width=70;
    my $margin='   ';
    $text .= " using the command:\n\n ";
    for my $l (@{$job->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" . $job->text_command() . "\n\n";
  }
} else {
  $text .= ".\n\n";
}
print $text;

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

print "Setting up CFX Interpolator run ...\n\n";

if ($dir->have_file('cfx5.def')) {

  print " +--------------------------------------------------------------------+\n";
  print " |                                                                    |\n";
  print " |                    CFX Command Language for Run                    |\n";
  print " |                                                                    |\n";
  print " +--------------------------------------------------------------------+\n\n";
  print  map { " $_\n" } ($job->get_current_ccl(), '');
}

# now we're ready to rumble!
$dir->enter();
my $rv=handle_rv(do_cmd_quiet($interp_exec,@passdown_args));
$dir->leave();

# now tidy up
$dir->output_marked_files_done();
$dir->junk_directory() unless $save;

if ($rv) {
  _error("The ANSYS CFX Interpolation Program exited with return code $rv");
  exit_if_error();
}
exit(0);
