#!/usr/bin/env perl
#-----------------------------------------------------------------------------------------------
#
# configure
#
#
# This utility allows the CICE user to specify compile-time configuration
# options via a commandline interface.  The output from configure is a
# Makefile and a cache file that contains all configuration parameters
# required to produce the Makefile.  A subsequent invocation of configure
# can use the cache file as input (via the -defaults argument) to reproduce
# the CICE configuration contained in it.  Note that when a cache file is
# used to set default values only the model parameters are used.  The
# parameters that are platform dependent (e.g., compiler options, library
# locations, etc) are ignored.
#
# As the build time configurable options of CICE are changed, this script
# must also be changed.  Thus configure is maintained under revision
# control in the CICE source tree and it is assumed that only the version of
# configure in the source tree will be used to build CICE.  Thus we assume
# that the root of the source tree can be derived from the location of this
# script.
#
# configure has an optional test mode to check that the Fortran90 compiler
# works and that external references to the netCDF and MPI libraries can be
# resolved at link time.
#
# Date        Contributor           Modification
# -----------------------------------------------------------------------------------------------------
# 2009-01-18  Mariana Vertenstein   Original version
#-----------------------------------------------------------------------------------------------

use strict;
#use warnings;
#use diagnostics;

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

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.

     ***************************************************** 
     To obtain the cice decompsition, can do any of the following
     Set all of 
          -bsizex, -bsizey, -maxblocks, -decomptype 
           explicitly sets the cice decomposition
     OR
          -ntasks, -nthreads 
           used to determine defaults for the cice decomposition.
     OR
          -nodecomp
           the cice decompsition is determined external to configure
     ******************************************************

     -cache <file>       Name of output cache file (default: config_cache.xml).
     -cachedir <file>    Name of directory where output cache file is written (default: CICE build directory).
     -cice_mode <mode>   Mode [prognostic | thermo_only | prescribed ]. (default: prognostic)
     -bsizex <n>         Size of cice block in first horizontal dimension.
     -bsizey <n>         Size of cice block in second horizontal dimension.
     -maxblocks <n>      Max number of cice blocks per processor.
     -decomptype <name>  Decomposition type ( valid values are [cartesian | spacecurve])
     -help [or -h]       Print usage to STDOUT.
     -hgrid <name>       Specify horizontal grid.  Use nlatxnlon for spectral grids;
                         dlatxdlon for fv grids (dlat and dlon are the grid cell size
			 in degrees for latitude and longitude respectively); 
     -ntasks <n>         Number of MPI tasks.  
     -nthreads <n>       Number of OMP threads per process.  Setting nthreads > 0 implies smp.
     -nodecomp           Turns off the generation of cice decomposition (default: not defined - decomp is generated)
     -ntr_aero <n>       Turns on aerosal tracers (valid values are 0,1,2,3,4,5,6, default is 3)
     -silent [or -s]     Turns on silent mode - only fatal messages issued.
     -verbose [or -v]    Turn on verbose echoing of settings made by configure.
     -version            Echo the CVS tag name used to check out this CICE distribution.
EOF
}

#-----------------------------------------------------------------------------------------------
# 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 CICE configuration scripts.  If the configure 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
my $cwd = getcwd();                    # current working directory
my $cfgdir;                            # absolute pathname of directory that contains this script
if ($ProgDir) { 
    $cfgdir = absolute_path($ProgDir);
} else {
    $cfgdir = $cwd;
}

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

#-----------------------------------------------------------------------------------------------
# Parse command-line options.
my %opts = (
	    cache     => "config_cache.xml",
	    );
GetOptions(
    "cache=s"                   => \$opts{'cache'},
    "cachedir=s"                => \$opts{'cachedir'},
    "cice_mode=s"               => \$opts{'cice_mode'},
    "bsizex=s"                  => \$opts{'bsizex'},
    "bsizey=s"                  => \$opts{'bsizey'},
    "maxblocks=s"               => \$opts{'maxblocks'},
    "decomptype=s"              => \$opts{'decomptype'},
    "h|help"                    => \$opts{'help'},
    "hgrid=s"                   => \$opts{'hgrid'},
    "ntr_aero=s"                => \$opts{'ntr_aero'},
    "ntasks=s"                  => \$opts{'ntasks'},
    "nthreads=s"                => \$opts{'nthreads'},
    "nodecomp!"	                => \$opts{'nodecomp'},
    "s|silent"                  => \$opts{'silent'},
    "v|verbose"                 => \$opts{'verbose'},
    "version"                   => \$opts{'version'},
)  or usage();

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

# Echo version info.
version($cfgdir) if $opts{'version'};    

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

# 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 required perl modules and configuration files.
# Look for them in the directory that contains the configure script.

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

# The configuration definition file specifies generic defaults.
#
# Horizontal grid.
my $horiz_grid_file = 'config_grid.xml';
(-f "$cfgdir/../../../../scripts/ccsm_utils/Case.template/$horiz_grid_file") or  die <<"EOF";
** Cannot find horizonal grid parameters file \"$horiz_grid_file\" in directory \"$cfgdir/../../../../scripts/ccsm_utils/Case.template/\" **
EOF

# The XML::Lite module is required to parse the XML configuration files.
(-f "$cfgdir/../../../../scripts/ccsm_utils/Tools/perl5lib/XML/Lite.pm") or  die <<"EOF";
** Cannot find perl module \"XML/Lite.pm\" in directory \"$cfgdir/../../../../scripts/ccsm_utils/Tools/perl5lib\" **
EOF

# The Build::Config module provides utilities to store and manipulate the configuration.
(-f "$cfgdir/../../../../scripts/ccsm_utils/Tools/perl5lib/Build/Config.pm") or  die <<"EOF";
** Cannot find perl module \"Build/Config.pm\" in directory \"$cfgdir/../../../../scripts/ccsm_utils/Tools/perl5lib\" **
EOF

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

#-----------------------------------------------------------------------------------------------
# Add $cfgdir/perl5lib to the list of paths that Perl searches for modules
unshift @INC, "$cfgdir/../../../../scripts/ccsm_utils/Tools/perl5lib";
unshift @INC, "$cfgdir";
require XML::Lite;
require Build::Config;

# Initialize the configuration.  The $config_def_file provides the definition of a CICE
# configuration. $cfg_ref is a reference to the new configuration object.
my $cfg_ref = Build::Config->new("$cfgdir/$config_def_file");

#-----------------------------------------------------------------------------------------------
# CICE root directory.  
my $cice_root = absolute_path("$cfgdir/../../../..");

if (-d "$cice_root/models/ice/cice/src") {
    $cfg_ref->set('cice_root', $cice_root);
} else {
    die <<"EOF";
** Invalid CICE root directory: $cice_root
** 
** The CICE root directory must contain the subdirectory models/ice/cice/src/.
** It is derived from "config_dir/../../../.." where config_dir is the
** directory in the CICE distribution that contains the configuration
** scripts.
EOF
}

if ($print>=2) { print "Setting CICE root directory to $cice_root$eol"; }

#-----------------------------------------------------------------------------------------------
# CICE build directory.
my $cice_bld;
if (defined $opts{'cice_bld'}) {
    $cice_bld = absolute_path($opts{'cice_bld'});
}
else { # use default value
    $cice_bld = absolute_path($cfg_ref->get('cice_bld'));
}

if (-d $cice_bld or mkdirp($cice_bld)) {
    # If the build directory exists or can be made then set the value...
    $cfg_ref->set('cice_bld', $cice_bld);
}
else {
    die <<"EOF";
** Could not create the specified CICE build directory: $cice_bld
EOF
}

if ($print>=2) { print "Setting CICE build directory to $cice_bld$eol"; }

#-----------------------------------------------------------------------------------------------
# configuration cache directory and file.
my $config_cache_dir;
my $config_cache_file;
if (defined $opts{'cachedir'}) {
    $config_cache_dir = absolute_path($opts{'cachedir'});
}
else {
    $config_cache_dir = $cfg_ref->get('cice_bld');
}

if (-d $config_cache_dir or mkdirp($config_cache_dir)) {
    $config_cache_file = "$config_cache_dir/$opts{'cache'}";
} else {
    die <<"EOF";
** Could not create the specified directory for configuration cache file: $config_cache_dir
EOF
}

if ($print>=2) { print "The configuration cache file will be created in $config_cache_file$eol"; }

#-----------------------------------------------------------------------------------------------
# Horizontal grid parameters
if (defined $opts{'hgrid'}) {
    $cfg_ref->set('hgrid', $opts{'hgrid'});
}
my $hgrid = $cfg_ref->get('hgrid');

# set_horiz_grid sets the parameters for specific hgrid combinations.
set_horiz_grid("$cfgdir/../../../../scripts/ccsm_utils/Case.template/$horiz_grid_file", $cfg_ref);

if ($print>=2) { print "Horizontal grid specifier: $hgrid.$eol"; }

#-----------------------------------------------------------------------------------------------
# Set the cice mode 
my $cice_mode;
if ($opts{'cice_mode'}) {
    $cice_mode = $opts{'cice_mode'}; 
    $cfg_ref->set('cice_mode', $cice_mode);
} else {
    $cice_mode = $cfg_ref->get('cice_mode');
}    
if ($print>=2) { print "cice : mode         is $cice_mode $eol";}

#-----------------------------------------------------------------------------------------------
# Set the number of ice categories
my $ncat;
if (($cice_mode eq 'prognostic') || ($cice_mode eq 'thermo_only')) {
    $ncat = 5; 
} elsif ($cice_mode eq 'prescribed') {
    $ncat = 1;
} else {
    die <<"EOF";
**  ERROR: following mode is not supported: $cice_mode
EOF
}
if ($print>=2) { print "cice : ncat         is $ncat $eol";}

# Tracers
if (defined $opts{'ntr_aero'}) {
    $cfg_ref->set('ntr_aero', $opts{'ntr_aero'});
} else {
    if ($cice_mode eq 'prescribed') {
	$cfg_ref->set('ntr_aero', 0);
    } else {
	$cfg_ref->set('ntr_aero', 3);
    }
}

#-----------------------------------------------------------------------------------------------
# Get the CICE grid
my $hgrid = $cfg_ref->get('hgrid');
my $nlon  = $cfg_ref->get('nlon');
my $nlat  = $cfg_ref->get('nlat');

if ($print>=2) { print "cice : grid         is $hgrid     $eol";}
if ($print>=2) { print "cice : nlon         is $nlon      $eol";}
if ($print>=2) { print "cice : nlat         is $nlat      $eol";}

#-----------------------------------------------------------------------------------------------
# If the cice decomposition is not to be specified, then set the cpp variables and stop

my $ntr_aero = $cfg_ref->get('ntr_aero');
if (defined $opts{'ntr_aero'}) {$ntr_aero = $opts{'ntr_aero'}};
if ($ntr_aero gt "6"){ 
    die <<"EOF";
**  ERROR: ntr_aero value of $ntr_aero is invalid, only values less than 6 are supported
EOF
}

my $cice_cppdefs = " -DCCSMCOUPLED -Dcoupled -Dncdf -DNCAT=$ncat -DNXGLOB=$nlon -DNYGLOB=$nlat -DNTR_AERO=$ntr_aero";

#-----------------------------------------------------------------------------------------------
# Determine if will set the cice decomposition
my $set_decomp = (defined $opts{'nodecomp'}) ? 0 : 1;

# Set the CICE decomposition
my $bsizex;
my $bsizey;
my $maxblocks;
my $decomptype;

if ($set_decomp) {

    # First, check to see if user has specified it.
    my $cice_decomp_params = 0;

    if (defined $opts{'bsizex'}) {
	$bsizex = $opts{'bsizex'};
	++$cice_decomp_params;
    }
    if (defined $opts{'bsizey'}) {
	$bsizey = $opts{'bsizey'};
	++$cice_decomp_params;
    }
    if (defined $opts{'maxblocks'}) {
	$maxblocks = $opts{'maxblocks'};
	++$cice_decomp_params;
    }
    if (defined $opts{'decomptype'}) {
	$decomptype = $opts{'decomptype'};
	++$cice_decomp_params;
    }

    # Check that if the user has specified any of the decomp parameters,
    # they must all be specified.
    if ($cice_decomp_params) {
	unless ($cice_decomp_params == 4) {
	    die <<"EOF";
**    ERROR: If any of the CICE decomposition parameters are specified, then they
**    must all be specified.  The settings were:
**    bsizex=$bsizex bsizey=$bsizey maxblocks=$maxblocks decomptype=$decomptype
EOF
       }
	unless (defined $opts{'decomptype'}) {
	    die <<"EOF";
**    ERROR: Whend the CICE decomposition parameters are specified, the
**    cice decomptype must also be specified as ant input argument.  
EOF
        }
	$cfg_ref->set('cice_decomptype', $decomptype);
    }

    if ($cice_decomp_params == 0) {

	# Check whether ntasks is specified.
	my $ntasks;
	if (defined $opts{'ntasks'}) {
	    $cfg_ref->set('ntasks', $opts{'ntasks'});
	    $ntasks = $opts{'ntasks'};
	    
	    # Check for legal ntasks value
	    if ($ntasks < 1) {
		die "ERROR: ntasks value < 1: ntasks= $ntasks $eol";
	    }
	}
	
	# Check whether nthreads is specified.
	my $nthreads;
	if (defined $opts{'nthreads'}) {
	    $cfg_ref->set('nthreads', $opts{'nthreads'});
	    $nthreads = $opts{'nthreads'};
	    
	    # Check for legal nthreads value
	    if ($nthreads < 1) {
		die "ERROR: nthreads value < 1: nthreads= $nthreads $eol";
	    }
	}
	
	# User hasn't specified CICE decomp so get default values.

	# The defaults depend on the total number of execution threads available.  These must
	# be specified by the user.
	my $cice_ntasks;
	my $cice_nthreads;
	
	# User must define both ntasks and nthreads
	if (defined $ntasks and defined $nthreads) {
	    $cice_ntasks   = $ntasks;
	    $cice_nthreads = $nthreads;
	} else {
	    die <<"EOF";
**    ERROR: If CICE decomposition parameters are not specified, then either
**    -ntasks and -nthreads must be specified to determine a default decomposition
**    OR -nodecomp must be set as an argument.
EOF
        }

	if ($cice_ntasks == 1 && $cice_nthreads ==1 ) {
	    
	    # serial case:
	    $bsizex        = $nlon;
	    $bsizey        = $nlat;
	    $maxblocks     = 1;
	    $decomptype    = 'cartesian';
	    $cfg_ref->set('cice_decomptype', $decomptype);
	    
	} else {
	    
	    # If not serial, use the script provided by CICE to generate the default decomposition
	    
	    my $cice_decomp_gen = "$cice_root/models/ice/cice/bld/generate_cice_decomp.pl";
	    my $cice_decomp = `$cice_decomp_gen -res $hgrid -nproc $cice_ntasks -thrds $cice_nthreads -output all`;
	    
	    # check for error
	    if ($cice_decomp =~ /No Decomp Created/) {
		die <<"EOF";
**    ERROR: CICE decomposition generator returns: $cice_decomp
**    May need to explicitly specify the decomposition using the arguments -bsizex, -bsizey, and -maxblocks
EOF

            } else {

		# output is blank separated values for nlons, nlats, bsizex, bsizey, maxblocks, decomptype
		my @tokens   = split " ", $cice_decomp;
		$bsizex      = $tokens[2];
		$bsizey      = $tokens[3];
		$maxblocks   = $tokens[4];
		$decomptype  = $tokens[5];
		$cfg_ref->set('cice_decomptype', $decomptype);
	    }
	}
    }
}
    
if ($print>=2) { print "cice : bsizex       is $bsizex      $eol";}
if ($print>=2) { print "cice : bsizey       is $bsizey      $eol";}
if ($print>=2) { print "cice : mxblcks      is $maxblocks   $eol";}
if ($print>=2) { print "cice : decomp type  is $decomptype  $eol";}

if ($set_decomp) {
    $cice_cppdefs = "$cice_cppdefs -DBLCKX=$bsizex -DBLCKY=$bsizey -DMXBLCKS=$maxblocks";
}

#-----------------------------------------------------------------------------------------------
# Makefile configuration #######################################################################
#-----------------------------------------------------------------------------------------------

# the CPP definitions that were explicitly set in the defaults 
my $cfg_cppdefs = ' ';

# CICE ice model
$cfg_cppdefs .= $cice_cppdefs;

# CPP defines to put on Makefile
my $make_cppdefs = "$cfg_cppdefs";

if ($print>=2) { print "CPP definitions set by configure: \'$cfg_cppdefs\'$eol"; }

#-----------------------------------------------------------------------------------------------
# Write configuration files ####################################################################
#-----------------------------------------------------------------------------------------------

my $fp_filename      = 'Filepath';             # name of output filepath file
my $cpp_filename     = 'CICE_cppdefs';         # name of output file for cice's cppdefs in ccsm

# Write the filepath file for ccsm.
write_filepath("$cice_bld/$fp_filename", $cfg_ref);
if ($print>=2) { print "creating $cice_bld/$fp_filename\n"; }

# Write the file for cice's cppdefs needed in ccsm.
write_cppdefs("$cice_bld/$cpp_filename", $make_cppdefs);
if ($print>=2) { print "creating $cice_bld/$cpp_filename\n"; }

# Write the configuration file.
$cfg_ref->write_file("$config_cache_file", $commandline);
if ($print>=2) { print "creating $cice_bld/$config_cache_file\n"; }

#-----------------------------------------------------------------------------------------------
# Finished unless testing requested ############################################################
#-----------------------------------------------------------------------------------------------

# Done testing.
chdir( $cwd ) || die <<"EOF";
** Trouble changing directory back to $cwd
**
EOF
if ($print) { print "configure done.\n"; }
exit;

#-----------------------------------------------------------------------------------------------
# REALLY FINNISHED #############################################################################
#-----------------------------------------------------------------------------------------------

sub write_filepath
{
    my ($file, $cfg_ref) = @_;
    my  $fh = new IO::File;

    $fh->open(">$file") or die "** can't open filepath file: $file\n";

    print $fh "\$CASEROOT/SourceMods/src.cice\n";
    print $fh "\$CODEROOT/ice/cice/src/drivers/cpl_\$comp\n";
    print $fh "\$CODEROOT/ice/cice/src/drivers/cpl_share\n";
    print $fh "\$CODEROOT/ice/cice/src/mpi\n";
    print $fh "\$CODEROOT/ice/cice/src/source\n";

    $fh->close;
}

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

sub write_cppdefs
{
    my ($file, $make_cppdefs) = @_;
    my  $fh = new IO::File;

    $fh->open(">$file") or die "** can't open cpp defs file: $file\n";

    print $fh "$make_cppdefs\n";

    $fh->close;
}

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

sub set_horiz_grid
{
    # Set the parameters for the specified horizontal grid.  The
    # parameters are read from an input file, and if no grid matches are
    # found then issue error message.
    # This routine uses the configuration defined at the package level ($cfg_ref).

    my ($hgrid_file, $cfg_ref) = @_;
    my $xml = XML::Lite->new( $hgrid_file );
    my $root = $xml->root_element();

    # Check for valid root node
    my $name = $root->get_name();
    $name eq "config_horiz_grid" or die
	"file $hgrid_file is not a horizontal grid parameters file\n";

    # Get grid from the package's configuration
    my $hgrid   = $cfg_ref->get('hgrid');

    # Read the grid parameters from $hgrid_file.
    my @e = $xml->elements_by_name( "horiz_grid" );
    my %a = ();

    # Search for matching grid.
    my $found = 0;
  HGRID:
    while ( my $e = shift @e ) {
	%a = $e->get_attributes();
	if ( $hgrid eq $a{'GLOB_GRID'} ) {
	    $found = 1;
	    last HGRID;
	}
    }

    # Die unless search was successful.
    unless ($found) { die "set_horiz_grid: no match for hgrid $hgrid\n"; }

    # Set parameter values
    $cfg_ref->set('nlat', $a{'ny'});
    $cfg_ref->set('nlon', $a{'nx'});

    # Override resolution settings to configure for SCAM mode.  The override is needed
    # because in SCAM mode the -hgrid option is used to specify the resolution of default
    # datasets from which single data columns are extracted.
    my $scam = $cfg_ref->get('scam');
    if ($scam) {
	$cfg_ref->set('nlat', 1);
	$cfg_ref->set('nlon', 1);
    }
}

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

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

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

sub mkdirp {
    my ($dir) = @_;
    my (@dirs) = split /\//, $dir;
    my (@subdirs, $path);

    # if $dir is absolute pathname then @dirs will start with ""
    if ($dirs[0] eq "") { push @subdirs, shift @dirs; }  

    while ( @dirs ) { # check that each subdir exists and mkdir if it doesn't
	push @subdirs, shift @dirs;
	$path = join '/', @subdirs;
	unless (-d $path or mkdir($path, 0777)) { return 0; }
    }
    return 1;
}

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

sub get_option {

    my ($mes, @expect) = @_;
    my ($ans, $expect, $max_tries);

    $max_tries = 5;
    print $mes;
    while ($max_tries) {
	$ans = <>; chomp $ans;
	--$max_tries;
	$ans =~ s/^\s+//;
	$ans =~ s/\s+$//;
	# Check for null response which indicates that default is accepted.
	unless ($ans) { return ""; }
	foreach $expect (@expect) {
	    if ($ans =~ /^$expect$/i) { return $expect; }
	}
	if ($max_tries > 1) {
	    print "$ans does not match any of the expected values: @expect\n";
	    print "Please try again: ";
	} elsif ($max_tries == 1) {
	    print "$ans does not match any of the expected values: @expect\n";
	    print "Last chance! ";
	}
    }
    die "Failed to get answer to question: $mes\n";
}

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

sub write_tests_filepath
{
    my ($test_dir) = @_;
    my  $fh = new IO::File;

    $fh->open(">Filepath") or die "** can't open file: $test_dir/Filepath\n";

    print $fh "$test_dir\n";

    $fh->close;
}

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

sub version {
# The version is found in CICE's ChangeLog file.
# $cfgdir is set by the configure script to the name of its directory.

    my ($cfgdir) = @_;

    my $logfile = "$cfgdir/../doc/ChangeLog";

    my $fh = IO::File->new($logfile, '<') or die "** can't open ChangeLog file: $logfile\n";

    while (my $line = <$fh>) {

	if ($line =~ /^Tag name:\s*(\w+)/ ) {
	    print "$1\n";
	    exit;
	}
    }

}


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

sub print_hash {
    my %h = @_;
    my ($k, $v);
    while ( ($k,$v) = each %h ) { print "$k => $v\n"; }
}

