#!/usr/bin/env perl 
#-----------------------------------------------------------------------------------------------
#
# configure
#
# This utility allows the CCSM utilities user to specify configuration
# options via a commandline interface.
#
#-----------------------------------------------------------------------------------------------

use strict;
#use warnings;
#use diagnostics;

use Cwd;
use English;
use Getopt::Long;
use IO::File;
use IO::Handle;


#-----------------------------------------------------------------------------------------------
# Setting autoflush (an IO::Handle method) on STDOUT helps in debugging.  It forces the test
# descriptions to be printed to STDOUT before the error messages start.

*STDOUT->autoflush();                  

#-----------------------------------------------------------------------------------------------
# Set the directory that contains the CCSM configuration scripts.  If the create_newcase command was
# issued using a relative or absolute path, that path is in $ProgDir.  Otherwise assume the
# command was issued from the current working directory.

(my $ProgName = $0) =~ s!(.*)/!!;      # name of this script
my $ProgDir = $1;                      # name of directory containing this script -- may be a
                                       # relative or absolute path, or null if the script is in
                                       # the user's PATH


#-----------------------------------------------------------------------------------------------
if ($#ARGV == -1) {
    usage();
}

#-----------------------------------------------------------------------------------------------

sub usage {
    die <<EOF;
SYNOPSIS
     configure [options]
OPTIONS
     User supplied values are denoted in angle brackets (<>).  Any value that contains
     white-space must be quoted.  Long option names may be supplied with either single
     or double leading dashes.  A consequence of this is that single letter options may
     NOT be bundled.

     -cimeroot            Specify the toplevel cime directory.
                          default: use CIMEROOT environment variable
     -mach <name>         Specify a CCSM machine (required).
     -compiler <name>     Specify a compiler for the target machine (optional)
                          default: default compiler for the target machine
     -mpilib <name>       Specify a mpi librar for the target machine (optional)
                          default: default mpi library for the target machine
     -mach_dir <path>     Specify the locations of the Machines directory (optional).

     -output_dir <path>   default: current working directory
     -output_format <value> Output format can be make or cmake.
                          default: make
     -help [or -h]        Print usage to STDOUT (optional).
     -list                Only list valid values for machines and compilers (optional).
     -silent [or -s]      Turns on silent mode - only fatal messages issued (optional).
     -verbose [or -v]     Turn on verbose echoing of settings made by create_newcase (optional).
     

     The following arguments are required for a generic machine. Otherwise, they will be ignored. 

     -scratchroot <name>           cesm executable directory (EXEROOT will be scratchroot/CASE) (char)
     -din_loc_root <name>          cesm input data root directory (char)
     -max_tasks_per_node <value>   maximum mpi tasks per machine node (integer)

EXAMPLES

  ./configure -mach bluefire -compiler ibm
  ./configure -mach generic_CNL -compiler cray

EOF
}

#-----------------------------------------------------------------------------------------------
# Save commandline
my $commandline = "configure @ARGV";

#-----------------------------------------------------------------------------------------------
# Parse command-line options.
my %opts = (
              max_tasks_per_node => 1,
	    );
GetOptions(
    "cimeroot=s"                => \$opts{'cimeroot'},
    "compiler=s"                => \$opts{'compiler'},  
    "mpilib=s"                  => \$opts{'mpilib'},  
    "h|help"                    => \$opts{'help'},
    "list"                      => \$opts{'list'},
    "mach=s"                    => \$opts{'mach'},
    "mach_dir=s"                => \$opts{'mach_dir'},
    "output_dir=s"              => \$opts{'output_dir'},
    "output_format=s"           => \$opts{'output_format'},
    "s|silent"                  => \$opts{'silent'},
    "v|verbose"                 => \$opts{'verbose'},
    "scratchroot=s"             => \$opts{'scratchroot'},
    "din_loc_root=s"            => \$opts{'din_loc_root'},
    "max_tasks_per_node=i"      => \$opts{'max_tasks_per_node'},
)  or usage();

# Give usage message.
usage() if $opts{'help'};


# Check for unparsed argumentss
if (@ARGV) {
    print "ERROR: unrecognized arguments: @ARGV\n";
    usage();
}

# Check for manditory case input if not just listing valid values

my $mach;
my $compiler;
my $mpilib;
my $output_format='make';
my $cimeroot;
my $machdir;

if (!$opts{'list'}) {
    # Check for manditory machine input
    if ($opts{'mach'}) {
	$mach = $opts{'mach'};
        if ($mach =~ "generic") {
           if (!$opts{'scratchroot'}) {
              die "ERROR: configure must include the argument -scratchroot for a generic machines \n";
           }
           if (!$opts{'din_loc_root'}) {
	       die "ERROR: configure must include the argument -din_loc_root for a generic machines \n";
	   }
           if (!$opts{'max_tasks_per_node'}) {
              die "ERROR: configure must include the argument -max_tasks_per_node for a generic machines \n";
           }
	   my $din_loc_root = $opts{'din_loc_root'};
	   (-d "$din_loc_root")  or  die "Cannot find din_loc_root directory $din_loc_root";
       }
    } else {
	die "ERROR: configure must include the input argument, -mach \n";
    }

    # Check if machine compiler option is given
    if ($opts{'compiler'}) {
	$compiler = $opts{'compiler'};
    }

    # Check if machine mpilib option is given
    if ($opts{'mpilib'}) {
	$mpilib = $opts{'mpilib'};
    }
    if($opts{'output_format'}){
	$output_format = $opts{'output_format'};
    }
    if($opts{'mach_dir'}){
	$machdir = $opts{'mach_dir'};
    }
}
if($opts{'cimeroot'}){
$cimeroot = $opts{'cimeroot'};
}
$cimeroot = absolute_path($ENV{CIMEROOT}) unless defined($cimeroot);
(-d "$cimeroot")  or  die <<"EOF";
** Cannot find cimeroot directory \"$cimeroot\"  
EOF
my $scriptdir = "$cimeroot/scripts";
$machdir   = "$cimeroot/machines-acme" unless defined($machdir);
(-d "$machdir")  or  die <<"EOF";
** Cannot find machines directory \"$machdir\"  
EOF

# Check for the configuration definition file.
my $config_def_file = "config_definition.xml";
(-f "$scriptdir/Tools/$config_def_file")  or  die <<"EOF";
** Cannot find configuration definition file \"$config_def_file\" in directory 
    \"$scriptdir/Tools/$config_def_file\" **
EOF

my $output_dir = ".";
$output_dir = $opts{output_dir} if defined( $opts{output_dir} );
#
# Make sure the output_dir exists or can be created
# 
if(! -d "$output_dir") {
  mkdir $output_dir, "755" or die "Could not find or create $output_dir";
}
 
# Define 3 print levels:
# 0 - only issue fatal error messages
# 1 - only informs what files are created (default)
# 2 - verbose
my $print = 1;
if ($opts{'silent'})  { $print = 0; }
if ($opts{'verbose'}) { $print = 2; }
my $eol = "\n";

my %cfg = ();           # build configuration

#
# Make sure we can find the Machines directory
# 

(-d "$machdir")   or  die <<"EOF";
** Cannot find cesm Machines directory \"$machdir\" **
EOF

#-----------------------------------------------------------------------------------------------
# Make sure we can find required perl modules and configuration files.
# Look for them in the directory that contains the configure script.

# Machines definition file.
my $machine_file = 'config_machines.xml';
(-f "$machdir/$machine_file")  or  die <<"EOF";
** Cannot find machine parameters file \"$machine_file\" in directory 
    \"$machdir\" **
EOF

# Compiler definition file.
my $compiler_file = 'config_compilers.xml';
(-f "$machdir/$compiler_file")  or  die <<"EOF";
** Cannot find compiler parameters file \"$compiler_file\" in directory 
    \"$machdir\" **
EOF

if ($print>=2) { print "Setting configuration directory to $scriptdir$eol"; }

#-----------------------------------------------------------------------------------------------
my @dirs = ( "$scriptdir/Tools", "$cimeroot/utils/perl5lib" );

unshift @INC, @dirs;
require ConfigCase;
require SetupTools;

#-----------------------------------------------------------------------------------------------
# If just listing valid values then exit after completion of lists
if ($opts{'list'}) {
    ConfigCase::print_machines("$machdir/$machine_file");
# to do - add print_compilers
    if ($print>=2) { print "finished listing valid values, now exiting $eol"; }
    exit;
}

#-----------------------------------------------------------------------------------------------
# Create new config object if not just listing valid values
my $cfg_ref = ConfigCase->new("$scriptdir/Tools/$config_def_file"); 

#if ($print>=2) { print "A new config reference object was created$eol";}

#-----------------------------------------------------------------------------------------------
# (4) Machine parameters
if($mach =~ /generic_(.*)/){
  $cfg_ref->set('OS',$1);
  $cfg_ref->set('MACH','generic');
}else{
  $cfg_ref->set_machine("$machdir/$machine_file", $mach, $print);
}

$cfg_ref->set('CCSM_MACHDIR', "$machdir");

if ($mach =~ "generic") {
    my $scratchroot  = "$opts{'scratchroot'}";
    $cfg_ref->set('DIN_LOC_ROOT'        ,  $opts{'din_loc_root'});  
    $cfg_ref->set('MAX_TASKS_PER_NODE'  ,  $opts{'max_tasks_per_node'});  
    $cfg_ref->set('GMAKE_J'             ,  "1");
}

# Check that compiler request for target machine matches a supported value
# Or set default compiler - if not provided compiler request

my $compilers = $cfg_ref->get('COMPILERS');
my @compilers = split ",", $compilers, -1;
if ($compiler) {
  if (! ($mach =~ "generic")){
     my $found = 0;
     foreach my $comp (@compilers) {
 	if ($compiler eq $comp) {
 	    $found = 1;
 	}
     }
     if (!$found) {
 	my $sysmod = "rm -rf $output_dir";
 	system($sysmod) == 0 or die "ERROR: $sysmod failed: $?\n";
 	die "ERROR: compiler setting of $compiler does not match supported values of $compilers \n";
     }
    }
    $cfg_ref->set('COMPILER', "$compiler");
    if ($print>=2) { print "Machine compiler specifier: $compiler.$eol"; }
} else {
    $compiler = $compilers[0];   
    $cfg_ref->set('COMPILER', "$compiler");
    if ($print>=2) { print "Machine compiler specifier: $compiler.$eol"; }
}



my $mpilibs = $cfg_ref->get('MPILIBS');
my @mpilibs = split ",", $mpilibs, -1;
if ($mpilib) {
    # check that mpilib request for target machine matches a supported value
    my $found = 0;
    foreach my $mpi (@mpilibs) {
	if ($mpilib eq $mpi) {
	    $found = 1;
	}
    }
    if (!$found) {
	my $sysmod = "rm -rf $output_dir";
	system($sysmod) == 0 or die "ERROR: $sysmod failed: $?\n";
	die "ERROR: mpilib setting of $mpilib does not match supported values of $mpilibs \n";
    }
    $cfg_ref->set('MPILIB', "$mpilib");
    if ($print>=2) { print "Machine mpilib specifier: $mpilib.$eol"; }
} else {
    $mpilib = $mpilibs[0];
    $cfg_ref->set('MPILIB', "$mpilib");
    if ($print>=2) { print "Machine mpilib specifier: $mpilib.$eol"; }
}    

if ($print>=2) { print "Machine specifier: $mach.$eol"; }

my $repotag;
if (-f "$cimeroot/ChangeLog") { 
    $repotag =`cat $cimeroot/ChangeLog | grep 'Version:' | head -1`;
}
my @repotag = split(/ /,$repotag); 
$repotag = $repotag[2]; 
chomp($repotag);
$cfg_ref->set('CIME_REPOTAG', $repotag);

SetupTools::set_compiler($cfg_ref->get('OS'),"$machdir/$compiler_file",$compiler, $mach, $mpilib, $print, "$output_dir/Macros", $output_format);

my $sysmod;
# Create machine specific environment file (env_mach_specific)
if(-e "$machdir/env_mach_specific.$mach" ) {
  $sysmod = "cp $machdir/env_mach_specific.$mach $output_dir/env_mach_specific";
}else{
  $sysmod = "touch $output_dir/env_mach_specific";
}
system($sysmod) == 0 or die "ERROR: $sysmod failed: $?\n";

# Copy Depends files if they exist
if( -e "$machdir/Depends.$mach.$compiler" ) {
  $sysmod = "cp $machdir/Depends.$mach.$compiler $output_dir/";
  system($sysmod) == 0 or die "ERROR: $sysmod failed: $?\n";
}else{
if( -e "$machdir/Depends.$mach" ) {
  $sysmod = "cp $machdir/Depends.$mach $output_dir/";
  system($sysmod) == 0 or die "ERROR: $sysmod failed: $?\n";
}
if( -e "$machdir/Depends.$compiler" ) {
  $sysmod = "cp $machdir/Depends.$compiler $output_dir/";
  system($sysmod) == 0 or die "ERROR: $sysmod failed: $?\n";
}
}

# Finally include a csh wrapper to load the modules and run the make

open(F,">$output_dir/build.csh") or die "Could not open file $output_dir/build.csh";
print F "#!/usr/bin/env csh\n";

print F "#\n# Use this script to assure that modules are properly loaded before compiling\n#\n";

print F "setenv MPILIB ".$cfg_ref->get('MPILIB')."\n";
print F "setenv COMPILER $compiler\n";
print F "source env_mach_specific\n";
print F "gmake \$*\n";
close(F);
chmod(0755,"$output_dir/build.csh");

print "Successfully created auxilary build files for $mach \n\n";

exit;

#-----------------------------------------------------------------------------------------------
# FINNISHED ####################################################################################
#-----------------------------------------------------------------------------------------------

sub absolute_path {
#
# Convert a pathname into an absolute pathname, expanding any . or .. characters.
# Assumes pathnames refer to a local filesystem.
# Assumes the directory separator is "/".
#
  my $path = shift;
  my $cwd = getcwd();  # current working directory
  my $abspath;         # resulting absolute pathname

# Strip off any leading or trailing whitespace.  (This pattern won't match if
# there's embedded whitespace.
  $path =~ s!^\s*(\S*)\s*$!$1!;

# Convert relative to absolute path.

  if ($path =~ m!^\.$!) {          # path is "."
      return $cwd;
  } elsif ($path =~ m!^\./!) {     # path starts with "./"
      $path =~ s!^\.!$cwd!;
  } elsif ($path =~ m!^\.\.$!) {   # path is ".."
      $path = "$cwd/..";
  } elsif ($path =~ m!^\.\./!) {   # path starts with "../"
      $path = "$cwd/$path";
  } elsif ($path =~ m!^[^/]!) {    # path starts with non-slash character
      $path = "$cwd/$path";
  }

  my ($dir, @dirs2);
  my @dirs = split "/", $path, -1;   # The -1 prevents split from stripping trailing nulls
                                     # This enables correct processing of the input "/".

  # Remove any "" that are not leading.
  for (my $i=0; $i<=$#dirs; ++$i) {
      if ($i == 0 or $dirs[$i] ne "") {
	  push @dirs2, $dirs[$i];
      }
  }
  @dirs = ();

  # Remove any "."
  foreach $dir (@dirs2) {
      unless ($dir eq ".") {
	  push @dirs, $dir;
      }
  }
  @dirs2 = ();

  # Remove the "subdir/.." parts.
  foreach $dir (@dirs) {
    if ( $dir !~ /^\.\.$/ ) {
        push @dirs2, $dir;
    } else {
        pop @dirs2;   # remove previous dir when current dir is ..
    }
  }
  if ($#dirs2 == 0 and $dirs2[0] eq "") { return "/"; }
  $abspath = join '/', @dirs2;
  return( $abspath );
}

#-------------------------------------------------------------------------------

sub subst_env_path {
#
# Substitute for any environment variables contained in a pathname.
# Assumes the directory separator is "/".
#
  my $path = shift;
  my $newpath;         # resulting pathname

# Strip off any leading or trailing whitespace.  (This pattern won't match if
# there's embedded whitespace.
  $path =~ s!^\s*(\S*)\s*$!$1!;

  my ($dir, @dirs2);
  my @dirs = split "/", $path, -1;   # The -1 prevents split from stripping trailing nulls
                                     # This enables correct processing of the input "/".

  foreach $dir (@dirs) {
    if ( $dir =~ /^\$(.+)$/ ) {
        push @dirs2, $ENV{$1};
    } else {
        push @dirs2, $dir;
    }
  }
  $newpath = join '/', @dirs2;
  return( $newpath );
}

#-------------------------------------------------------------------------------



#-------------------------------------------------------------------------------

