=head1 NAME

  CFX5::Simulation::Configuration::Remeshing - External remeshing wrapper for CFX5::Simulation::Configuration

=head1 SYNOPSIS

This module is used by L<CFX5::Simulation::Configuration>, to manage the process of 
external remeshing.

=head1 DESCRIPTION

$Id$

=cut

package CFX5::Simulation::Configuration::Remeshing;

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

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

use CFX5::PathManip qw(dirname basename fullpath join_file split_mult_files
                       normalize_filename is_absolute findbin_by_path);
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 getconf export_conf);

use CFX5::CCLI qw(getcclds);

use CFX5::Commands qw(get_cmd_output);

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

use CFX5::PkgLoader qw(cfx5_require);

use CFX5::Simulation::Configuration::Remeshing::Remesh ;
use CFX5::Simulation::Configuration::Remeshing::Remesh::Validation qw(Validate);
use CFX5::Simulation::Configuration::Remeshing::Remesh::Preprocessing qw(Preprocess);
use CFX5::Simulation::Configuration::Remeshing::Remesh::GeometryMod qw(GeomMod);
use CFX5::Simulation::Configuration::Remeshing::Remesh::MeshMod qw(MeshMod);
use CFX5::Simulation::Configuration::Remeshing::Remesh::MeshUpdate ;
use CFX5::Output::Utils 'wraptext';

use CFX5::DefFile ; 

use Carp;

=head3 Remeshing

"Remeshing" is responsible for orchestrating all of the remeshing tasks. 
This involves extracting any necessary information from the definition 
and results files, and calling the remeshing application

=cut

#
################################################################################
## Prepare - create array of remesh objects
################################################################################
#
sub Prepare {
  my $self=shift ;

  # Delete and reconstruct the array of remesh objects
  my $remeshObjs = $self->remeshObjs() ;
  if (!defined $remeshObjs) {
    _error "Remeshing: Array of object definitions does not exist!" ;
    return 0; 
  }

  # Validate all remeshing objects to detect any problems before starting
  unless ($self->run_mode() eq 'Partitioner Only') {
    my $rv = 1;
    foreach my $remeshObj (@{$remeshObjs}) {
      $rv &= Validate($remeshObj);
    }

    unless ($rv) {
      _error "Remeshing: Failed to validate one or more remesh objects.";
      return 0;
    }
  } 
  1 ; 
}

#
################################################################################
## QueryRemeshNow - Determine if remeshing is required
################################################################################
#
sub QueryRemeshNow {
  my $self = shift ;
  my ($condList, $typeList) = @_ ;
  
  return 0 unless (defined $$condList[0]); 

  # See if any remeshing is active
  my $remeshNow = 0; 
  my $remeshObjs = $self->remeshObjs();
  my @actCond ; 
  foreach my $remeshObj (@{$remeshObjs}) {
    my @actCondObj = $remeshObj->IsActive($condList,$typeList) ;
    push (@actCond, @actCondObj) if (defined $actCondObj[0]) ; 
  }
    
  # Reduce condition and type lists after processing all remesh object defintions
  for (my $ii=0; $ii<scalar(@actCond); $ii++) {
    for (my $jj=0; $jj<scalar(@$condList); $jj++) {
      if ($actCond[$ii] eq $$condList[$jj]) {
        splice (@$condList,$jj,1); 
        splice (@$typeList,$jj,1);
        last ;
      }
    }
  }

  # Returns number of conditions conditions that satisfy remesh object requirments
  # --> returns zero if no remeshing is active
  return scalar(@actCond); 
}

#
################################################################################
## Run Remeshing
################################################################################
#

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

#
# Deal with Check mode
#
  return 0 unless $do ; 
#
# Now proceed...
#
  my $workdir = $self->rundir_obj();
  my $fname = shift ;
  my $remeshingFile = $workdir->file_intpath('res') ; 
#
#===============================================================================
# Setup
#===============================================================================
#
  $self->Setup() ; 
#
# Pick up information applicable to all remeshing definitions
#
  my @transient = GetDataSet($remeshingFile,"G/TRANSIENT","ZN1","-2") ;
  unless (ref $transient[0]) {
    _error("Remeshing: Could not read G/TRANSIENT dataset from $remeshingFile");
    return 0;
  }
  my $ATStep = $transient[0][2] ;
#
# Connect to text output files
# --> Direct script output to regular .out file
# --> Direct geometry/mesh modification output to ###_remesh.out file
#
  my $outfile=$self->outobj();
  unless (defined $outfile) {
    _error "Remeshing: Could not connect to out file" ;
    return 0;
  }
  my $outfile_Remesh = sprintf("%d_remesh.out",$ATStep) ;
  $workdir->creatable_file($outfile_Remesh);
#
# Create the mesh update object and corresponding Pre session file
# --> Get rid of the file if it exists
#  
  my ($meshUpdate_pre,$meshUpdate,$meshUpdateFile); 
  $meshUpdateFile = "meshUpdate.pre" ;
  if ($self->HaveFile($meshUpdateFile)) {
    $workdir->remove_file($meshUpdateFile);
  }
  $meshUpdate_pre = $workdir->file_intpath($meshUpdateFile) ;
  $workdir->creatable_file($meshUpdateFile);
  $meshUpdate = CFX5::Simulation::Configuration::Remeshing::Remesh::MeshUpdate->new($meshUpdate_pre) ;  
  $meshUpdate->WriteHeader($remeshingFile);   
#
# Additional data
# - record of the mesh files being replaced per remesh object
#
  my %meshFiles ; 
  my $nAutoNameMeshFile = 0 ;
#
#===============================================================================
# Review CCL -- Process active remeshing definitions in current configuration
#===============================================================================
#
# Loop over all defined remesh objects and see which are active
#  
  my $remeshObjs = $self->remeshObjs(); 
  foreach my $remeshObj (@{$remeshObjs}) {
  
    # Skip further processing if this object is not active
    next unless (defined $remeshObj->Get("ActCond")) ;
    
    # Create notification for current active remesh object
    $outfile->print("\nRemesh Object: ".$remeshObj->Get("Name")."\n") ; 
    $outfile->print("  Activated by condition: ".$remeshObj->Get("ActCond")."\n") ;
    
    # Transfer settings to remesh object
    $remeshObj->Set("ResFile",$remeshingFile) ;
    $remeshObj->Set("RemeshOutFile",$outfile_Remesh) ;
#
#-----------------------------------------------------------------------
# Figure out which mesh file is being created
#-----------------------------------------------------------------------
#  
    # Make up name for replacement mesh file if one doesn't already exist
    # as part of the CCL definition
    my $meshFile = $remeshObj->Get("Replacement Mesh File") ;
    unless (defined $meshFile) {
      $nAutoNameMeshFile++ ; 
      $meshFile = sprintf("%d_newmesh_%02d.mesh",$ATStep,$nAutoNameMeshFile) ;
    }

    # Process and store the name in the remesh object
    $remeshObj->Set("MeshFile",$meshFile) ;
#
#-----------------------------------------------------------------------
# Create new mesh file
#-----------------------------------------------------------------------
#  
    # Make sure we're not replacing a mesh file more than once
    # --> Just warn the user that we're not going to proceed.
    $workdir->creatable_file($meshFile);
    my $remeshObj_Name = $meshFiles{$meshFile} ; 
    if ( defined $remeshObj_Name ) {
			$outfile->print("  This Remesh object definition is attempting to replace the same ") ;
			$outfile->print("  mesh file as a previously processed Remesh object.  The previous") ; 
			$outfile->print("  mesh file will not be replaced.") ; 
			$outfile->print("    Mesh File    : ".basename($meshFile)) ;
			$outfile->print("    Prev. Object : $remeshObj_Name") ; 
    }
    else {
      $meshFiles{$meshFile} = $remeshObj->Get("Name") ; 
			unless (Validate($remeshObj)) {
				_error "Remeshing: Failed to validate remesh object" ;
				return 0 ; 
			}
			unless (Preprocess($remeshObj)) {
				_error "Remeshing: Failed to preprocess remesh object" ;
				return 0 ; 
			}
			unless (GeomMod($remeshObj)) {
				_error "Remeshing: Failed to modify geometry for remesh object" ;
				return 0 ; 
			}
			unless (MeshMod($remeshObj)) {
				_error "Remeshing: Failed to modify mesh for remesh object" ;
				return 0 ; 
			}
			unless ($meshUpdate->WriteRemeshContrib($remeshObj)) {
				_error "Remeshing: Failed to make remesh contribution to $meshUpdate_pre\n" ;
				return 0 ; 
			}
    }
#
#-----------------------------------------------------------------------
# Closure for this remeshing definition
#-----------------------------------------------------------------------
#  
#   Reset the activation condition(s)    
    $remeshObj->Set("ActCond",{}) ; 
  }
#
#===============================================================================
#  Insert New Meshes Into Def File
#===============================================================================
#
  my $meshfileNew = sprintf("%d_newmesh.def",$ATStep) ;

  $workdir->creatable_file($meshfileNew);
  my $meshUpdate_def = $workdir->file_intpath($meshfileNew) ;
  unless ($meshUpdate->WriteFooter($meshUpdate_def)) {
    _error "Remeshing: Failed writing mesh update footer" ;
    return 0 ; 
  }
  
  $workdir->enter() ;
  unless ($meshUpdate->DoUpdate($outfile_Remesh)) {
    _error "Remeshing: Failed executing mesh update" ;
    return 0 ; 
  }
  $workdir->leave() ;  
#
#===============================================================================
#  Closure
#===============================================================================
#
# Mark intermediate files for deletion or saving during run tidyup
#
  $workdir->mark_output_file_ignore($meshUpdateFile); 

  foreach my $meshFile (keys %meshFiles) {
    $workdir->mark_output_file_ignore($meshFile); 
  }
#
# General Cleanup 
#
  my $resfileNew = sprintf("%d_oldmesh.res",$ATStep);
  $self->Cleanup($meshfileNew,$resfileNew,$outfile_Remesh); 
#
 1;
}

#
################################################################################
## Setup
################################################################################
#
sub Setup() {
  my $self = shift;
#
#===============================================================================
# Create remeshing banner 
#===============================================================================
#
  $self->OutFileHeader("Remeshing") ;
#
#===============================================================================
#  File Cleanup
#===============================================================================
#
  my $workdir = $self->rundir_obj();
  return 0 unless (defined $workdir);
#
  if ($self->HaveFile('def')) {
    $workdir->remove_file('def');
  }
  
  1 ; 
}
#
################################################################################
## Cleanup
################################################################################
#
sub Cleanup() {
  my $self       = shift;
  my $deffileNew = shift; 
  my $resfileNew = shift; 
  my $outfileNew = shift; 
#
  my $workdir = $self->rundir_obj();
  return 0 unless (defined $workdir);
#
#===============================================================================
#  Process Def file (contains new mesh)
#===============================================================================
#
# Currently we're not keeping these files at the end of the run
  $workdir->mark_output_file_ignore($deffileNew); 
  $self->defRemesh($workdir->file_intpath($deffileNew)); 
#
#===============================================================================
#  Process Res File
#===============================================================================
#
# Rename res file using file prefix defined in res file dataset
  my @fileprefix = GetDataSet($workdir->file_intpath('res'),"G/FILE_PREFIX","EVERY") ;
  if (ref $fileprefix[0]) {
    my $ind = index($fileprefix[0][0],' ');
    $resfileNew = substr($fileprefix[0][0],0,$ind).".res";
  }
  $workdir->rename_file('res' => $resfileNew) ;
  $workdir->notice_file($resfileNew,
                        join_file($workdir->{basename},$resfileNew)) ; 
  $workdir->mark_output_file_leave($resfileNew); 
  $self->resRemesh($workdir->file_intpath($resfileNew)); 
#
#===============================================================================
#  Process Remeshing .out File
#===============================================================================
#
  $workdir->notice_file($outfileNew,
                        join_file($workdir->{basename},$outfileNew)) ; 
  $workdir->mark_output_file_leave($outfileNew); 
  $self->outRemesh($workdir->file_intpath($outfileNew)); 
#
#===============================================================================
#  Add entry in the G/RES_INFO dataset and write it to the file
#===============================================================================
#
  my $simStep  = $self->parent()->{simstep};
  my $confStep = $self->parent()->{configmap}{$self->parent()->{currentconfig}}{nextstep}-1;
  my $confName = $self->parent()->{configmap}{$self->parent()->{currentconfig}}{name};
  my $pathLevels = ($self->parent()->singleconfig() ? 1 : 2); 
  my $resInfoPath = join_file(basename($workdir->{basename},$pathLevels),
                              $resfileNew); 
  
  $self->resInfo()->AddEntry($simStep,$confStep,$confName,$resInfoPath) ;
                             
  $self->resInfo()->WriteToFile($workdir->file_intpath($resfileNew)) ;
#
#===============================================================================
#  Summary information
#===============================================================================
#
    my $outfile=$self->outobj();
    unless (defined $outfile) {
      _error "Remeshing Cleanup: Could not connect to out file" ;
      return 0;
    }
    
    my @text; 

    $outfile->print("\nCFX Solver Results generated before remeshing have been written to:\n");
    @text = wraptext(63,join_file($workdir->{basename},$resfileNew)) ;
    foreach my $line (@text) {
      $outfile->print("  $line"); 
    }
    
    $outfile->print("\nText output generated during remeshing has been written to:\n");
    @text = wraptext(63,join_file($workdir->{basename},$outfileNew)) ;
    foreach my $line (@text) {
      $outfile->print("  $line"); 
    }
    $outfile->print("\n\n");

  1; 
}
#
################################################################################
## Additional Support Routines
################################################################################
#
sub run_and_redirect_Remeshing {

	my $fname=shift; 
  my $exec=shift;
  my @args=@_;

  cfx5_require('CFX5::Commands') or return undef;

  # this isn't nice, but it fits i201 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 (defined $fname) {
    if (CFX5::Output::have_console()
        or CFX5::Output::have_stdout_file()) {
      # if we have any FDs, save them.
      open SAVEDOUT, ">&STDOUT" 
        or _warn "Remeshing: Cannot dup STDOUT";
      open SAVEDERR, ">&STDERR"
        or _warn "Remeshing: Cannot dup STDERR";
    }
    open STDOUT, ">>".$fname  
      or _warn "Remeshing: Cannot redirect STDOUT";
    open STDERR, ">&STDOUT"
      or _warn "Remeshing: 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 " ";
  }

  # If the command is a not a full path, prepend containing directory.
  # This allows operation of the indirect object system call in do_cmd()

  unless (is_absolute($exec))
  {
    my $fullname = join_file(findbin_by_path($exec),$exec);
    printf "External Command: $exec is not absolute. Executing as: $fullname\n";
    $exec = $fullname;
  }

  # Run the command

  my $rv=CFX5::Commands::parse_rv(CFX5::Commands::do_cmd_quiet($exec, @args));

  # Revert stdout/stderr

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

  $rv;
}


1;
