#!/usr/bin/env perl
#-----------------------------------------------------------------------------------------------
#
# build-namelist
#
# This is the build-namelist script for the CESM datm (Data ATmosphere Model).
# This is normally run under the covers as part of the CESM scripts.
#
# Date        Contributor      Modification
# -------------------------------------------------------------------------------------------
# 2010-05-04  Kluzek           Original version
# 2012-03-10  Vertenstein      Updated for cesm1.1
#--------------------------------------------------------------------------------------------

use strict;
#use warnings;
#use diagnostics;
use Cwd qw(getcwd abs_path);
use English;
use Getopt::Long;
use IO::File;
#-----------------------------------------------------------------------------------------------

sub usage {
    die <<EOF;
SYNOPSIS
     build-namelist [options]

     Build the datm namelist 
OPTIONS
     -debug                        Run script using default values for env variable settings
                                   for debugging.
     -help [or -h]            	   Print usage to STDOUT.					    
     -infile "filepath"       	   Specify a file or list of files (comma delimited) 	    
                              	   containing namelists to read values from.			    
     -namelist "namelist"     	   Specify namelist settings directly on the commandline by 	    
                              	   supplying a string containing FORTRAN namelist syntax, e.g.,  
                              	      -namelist "&datm_exp taxmode='extend'/"		    
     -print "level"           	   Print level for debugging:				    
                              	      0 = silent						    
                              	      1 = regular						    
                              	      2 = verbose						    
     -test                    	   Enable checking that input datasets exist on local filesystem.
     -user_xml_dir "directory"     Directory of where to look for user versions of 
                                      namelist XML files (usually your SourceMods/src.datm directory)
                                      (such as namelist_definition_datm.xml, or namelist_defaults_datm.xml)
     -caseroot                     CASEROOT directory variable
     -scriptsroot                  SCRIPTSROOT directory variable
     -inst_string                  INST_STRING variable

Note: The precedence for setting the values of namelist variables is (highest to lowest):
      0. namelist values set by specific command-line options
      1. values set on the command-line using the -namelist option,
      2. values read from the file specified by -infile,
      3. values from the namelist defaults file.

NOTE: The following xml variables are REQUIRED (they are already set by scripts in the env_*.xml files)
         RUN_TYPE    DIN_LOC_ROOT  ATM_DOMAIN_FILE   ATM_DOMAIN_PATH
	 DATM_MODE   DATM_PRESAERO DATM_CO2_TSERIES  ATM_GRID          GRID                     
EOF
}

#-----------------------------------------------------------------------------------------------
# Set the directory that contains the DATM configuration scripts.  If the 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 $cmdline = "@ARGV";                 # Command line arguments to script
my $cwd = getcwd();                    # current working directory
my $cfgdir;                            # absolute pathname of directory that contains this script
my $nm = "ProgName::";                 # name to use if script dies
if ($ProgDir) { 
    $cfgdir = abs_path($ProgDir);
} else {
    $cfgdir = $cwd;
}

my $cfg_cache = "config_cache.xml";       # Default name of configuration cache file
my $outdirname = "$cwd";                  # Default name of output directory name

#-----------------------------------------------------------------------------------------------
# Process command-line options.
#
my %opts = ( help        => 0,
	     silent      => 0,
             debug       => 0,
	     test        => 0,
	     caseroot    => undef,
	     scriptsroot => abs_path( "$cfgdir/../../../../scripts" ),
	     inst_string => undef,
             user_xml_dir=> undef,
	    );

GetOptions(
    "h|help"         => \$opts{'help'},
    "infile=s"       => \$opts{'infile'},
    "namelist=s"     => \$opts{'namelist'},
    "print=i"        => \$opts{'print'},
    "debug"          => \$opts{'debug'},
    "test"           => \$opts{'test'},
    "caseroot=s"     => \$opts{'caseroot'},
    "scriptsroot=s"  => \$opts{'scriptsroot'},
    "inst_string=s"  => \$opts{'inst_string'},	   
    "user_xml_dir=s" => \$opts{'user_xml_dir'},
)  or usage();

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

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

# Define print levels:
#   0 - only issue fatal error messages
#   1 - only informs what files are created (default)
#   2 - verbose
my $print = $opts{'print'};
my $eol = "\n";
# Set caseroot for Debug mode
if ( $opts{'debug'} && ! defined($opts{'caseroot'}) ) {
   $opts{'caseroot'} = "$cwd/unit_testers";
   if ( $print > 1 ) { print "caseroot = $opts{'caseroot'}\n"; }
}

# user_xml_dir
my $opt = 'user_xml_dir';
if (defined $opts{$opt}) {
   my $dir = $opts{$opt};
   if ( ! -d "$dir" ) {
      die << "EOF";
** $ProgName - $opt: $dir does NOT exist
EOF
   }
   my @files = glob("$dir/*.xml");
   if ( $#files == -1 && $print > 0 ) {
      print << "EOF";
** $ProgName - Warning NO XML files exist in $opt directory $dir
EOF
   }
}

my $CASEROOT    = $opts{'caseroot'};
my $SCRIPTSROOT = $opts{'scriptsroot'};
my $INST_STRING = $opts{'inst_string'}; 

#-----------------------------------------------------------------------------------------------
my $bldconfdir = "$CASEROOT/Buildconf/datmconf";
if ( $opts{'debug'} ) {
   my $cmd = "mkdir -p $bldconfdir";
   print "Execute: $cmd\n";
   system( "$cmd" );
   chdir( "$bldconfdir" );
}

# build empty config_cache.xml file (needed below)  
my $config_cache = "$bldconfdir/$cfg_cache";
my  $fh = new IO::File;
$fh->open(">$config_cache") or die "** can't open file: $config_cache\n";
print $fh  <<"EOF";
<?xml version="1.0"?>
<config_definition>
</config_definition>
EOF
$fh->close;
if ($print>=1) { print "Wrote file $config_cache $eol"; }
(-f "$config_cache")  or  die <<"EOF";
** $ProgName - Cannot find configuration cache file: $config_cache\" **
EOF

#-----------------------------------------------------------------------------------------------
# Make sure we can find required perl modules, definition, and defaults files.
# Look for them under the directory that contains the configure script.

# The root directory for the input data files must be specified.

#The root directory to cesm utils Tools
my $cimeroot   = abs_path( "${SCRIPTSROOT}/../" );
my $perl5lib   = "$cimeroot/utils/perl5lib";

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

# The Build::Config module provides utilities to access the configuration information
# in the config_cache.xml file
(-f "$perl5lib/Build/Config.pm")  or  die <<"EOF";
** $ProgName - Cannot find perl module \"Build/Config.pm\" in directory 
    \"$perl5lib\" **
EOF

# The namelist definition file contains entries for all namelist variables that
# can be output by build-namelist.
my $nl_definition_file = "$cfgdir/namelist_files/namelist_definition_datm.xml";
# If user has user namelist definition files
if (defined $opts{'user_xml_dir'}) {
    my $filename = $nl_definition_file;
    $filename    =~ s!(.*)/!!;
    my $newfile  = "$opts{'user_xml_dir'}/$filename";
    if ( -f "$newfile" ) {
       $nl_definition_file = $newfile;
    }
}
(-f "$nl_definition_file")  or  die <<"EOF";
** $ProgName - Cannot find namelist definition file \"$nl_definition_file\" **
EOF
if ($print>=2) { print "Using namelist definition file $nl_definition_file$eol"; }

# The Build::NamelistDefinition module provides utilities to validate that the output
# namelists are consistent with the namelist definition file
(-f "$perl5lib/Build/NamelistDefinition.pm")  or  die <<"EOF";
** $ProgName - Cannot find perl module \"Build/NamelistDefinition.pm\" in directory 
    \"$perl5lib\" **
EOF

# The namelist defaults file contains default values for all required namelist variables.
my @nl_defaults_files = ( "$cfgdir/namelist_files/namelist_defaults_datm.xml"); 
# If user has user namelist definition files
if (defined $opts{'user_xml_dir'}) {
  my @filelist = @nl_defaults_files;
  foreach my $file  ( @filelist ) {
    $file =~ s!(.*)/!!;
    my $newfile = "$opts{'user_xml_dir'}/$file";
    if ( -f "$newfile" ) {
       unshift @nl_defaults_files, $newfile;
    }
  }
}
foreach my $nl_defaults_file ( @nl_defaults_files ) {
  (-f "$nl_defaults_file")  or  die <<"EOF";
** $ProgName - Cannot find namelist defaults file \"$nl_defaults_file\" **
EOF
  if ($print>=2) { print "Using namelist defaults file $nl_defaults_file$eol"; }
}

# The Build::NamelistDefaults module provides a utility to obtain default values of namelist
# variables based on finding a best fit with the attributes specified in the defaults file.
(-f "$perl5lib/Build/NamelistDefaults.pm")  or  die <<"EOF";
** $ProgName - Cannot find perl module \"Build/NamelistDefaults.pm\" in directory 
    \"$perl5lib\" **
EOF

# The Build::Namelist module provides utilities to parse input namelists, to query and modify
# namelists, and to write output namelists.
(-f "$perl5lib/Build/Namelist.pm")  or  die <<"EOF";
** $ProgName - Cannot find perl module \"Build/Namelist.pm\" in directory 
    \"$perl5lib\" **
EOF

#-----------------------------------------------------------------------------------------------
# Add $cfgdir/perl5lib to the list of paths that Perl searches for modules
my @dirs = ( "$cfgdir/..", "$perl5lib", "$CASEROOT/Tools");
unshift @INC, @dirs;

require XML::Lite;
require Build::Config;
require Build::NamelistDefinition;
require Build::NamelistDefaults;
require Build::Namelist;
require Streams::TemplateGeneric;
require SetupTools;

#-----------------------------------------------------------------------------------------------
# Create a configuration object from the DATM config_cache.xml file. 
my $cfg = Build::Config->new( $config_cache );


# Create a namelist definition object.  This object provides a method for verifying that the
# output namelist variables are in the definition file, and are output in the correct
# namelist groups.
my $definition = Build::NamelistDefinition->new( $nl_definition_file );

# Create a namelist defaults object.  This object provides default values for variables
# contained in the input defaults file.  The configuration object provides attribute
# values that are relevent for the DATM for which the namelist is being produced.
my $defaults = Build::NamelistDefaults->new( shift( @nl_defaults_files ), $cfg);
foreach my $nl_defaults_file ( @nl_defaults_files ) {
   $defaults->add( "$nl_defaults_file" );
}

# Create an empty namelist object.  Add values to it in order of precedence.
my $nl = Build::Namelist->new();

#-----------------------------------------------------------------------------------------------
# Some regular expressions...
my $TRUE  = '\.true\.';
my $FALSE = '\.false\.';

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

# Process the user input in order of precedence.  At each point we'll only add new
# values to the namelist and not overwrite previously specified specified values which
# have higher precedence.

# Process the commandline args that provide specific namelist values.

# Process the -namelist arg.

if (defined $opts{'namelist'}) {
    # Parse commandline namelist
    my $nl_arg = Build::Namelist->new($opts{'namelist'});

    # Validate input namelist -- trap exceptions
    my $nl_arg_valid;
    eval { $nl_arg_valid = $definition->validate($nl_arg); };
    if ($@) {
	die "$ProgName - ERROR: Invalid namelist variable in commandline arg '-namelist'.\n $@";
    }

    # Merge input values into namelist.  Previously specified values have higher precedence
    # and are not overwritten.
    $nl->merge_nl($nl_arg_valid);
}

# Process the -infile arg.

if (defined $opts{'infile'}) {
   foreach my $infile ( split( /,/, $opts{'infile'} ) ) {
      # Parse namelist input from a file
      my $nl_infile = Build::Namelist->new($infile);

      # Validate input namelist -- trap exceptions
      my $nl_infile_valid;
      eval { $nl_infile_valid = $definition->validate($nl_infile); };
      if ($@) {
         die "$ProgName - ERROR: Invalid namelist variable in '-infile' $infile.\n $@";
      }

      # Merge input values into namelist.  Previously specified values have higher precedence
      # and are not overwritten.
      $nl->merge_nl($nl_infile_valid);
   }
}

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

####################################
# Required xml variables           #
####################################

my %xmlvars = ();
SetupTools::getxmlvars(${CASEROOT},\%xmlvars);
foreach my $attr (keys %xmlvars) {
    $xmlvars{$attr} = SetupTools::expand_xml_var($xmlvars{$attr}, \%xmlvars); 
}
foreach my $var ( "RUN_TYPE", "DIN_LOC_ROOT", "ATM_DOMAIN_FILE", "ATM_DOMAIN_PATH", 
		  "DATM_MODE", "DATM_PRESAERO", "DATM_CO2_TSERIES", "ATM_GRID", "GRID" ) {
    if ( $print > 1 ) { print "$var = $xmlvars{$var}\n"; }
    if ( ! defined($xmlvars{$var})  || $xmlvars{$var} =~ /^(UNSET|)$/ ) {
	die "** $ProgName - $var is NOT set  ** \n"
    }
}

my $RUN_TYPE        = $xmlvars{'RUN_TYPE'}; 
my $DIN_LOC_ROOT    = $xmlvars{'DIN_LOC_ROOT'}; 
my $ATM_DOMAIN_FILE = $xmlvars{'ATM_DOMAIN_FILE'};
my $ATM_DOMAIN_PATH = $xmlvars{'ATM_DOMAIN_PATH'};
my $DATM_MODE       = $xmlvars{'DATM_MODE'};   
my $DATM_PRESAERO   = $xmlvars{'DATM_PRESAERO'};    
my $DATM_CO2_TSERIES= $xmlvars{'DATM_CO2_TSERIES'};
my $ATM_GRID        = $xmlvars{'ATM_GRID'};    
my $GRID            = $xmlvars{'GRID'};     
my $CLM_USRDAT_NAME = $xmlvars{'CLM_USRDAT_NAME'};     

# Validate some of the env values.
if ( $DATM_MODE =~ /CLM/ && $DATM_PRESAERO eq "none" ) {
   die "${nm}:: A DATM_MODE for CLM is incompatible with DATM_PRESAERO=none\n";
}

if ( $GRID eq "CLM_USRDAT" && $CLM_USRDAT_NAME =~ /^(UNSET|)$/ ) {
   die <<"EOF";
** $ProgName - GRID=CLM_USRDAT and CLM_USRDAT_NAME is NOT set  **
EOF
}

(-d $DIN_LOC_ROOT)  or mkdir $DIN_LOC_ROOT;
if ($print>=2) { print "CESM inputdata root directory: $DIN_LOC_ROOT$eol"; }

if ($opts{'test'}) {
    (-d $DIN_LOC_ROOT)  or  die <<"EOF";
** $ProgName - CESM inputdata root is not a directory: \"$DIN_LOC_ROOT\" **
EOF
}
my $test_files = $opts{'test'};

my $var   = "DATM_MODE";
my $group = $definition->get_group_name($var);
$nl->set_variable_value( $group, $var, "\'$xmlvars{$var}\'" );
my $var   = "DATM_PRESAERO";
my $group = $definition->get_group_name($var);
$nl->set_variable_value( $group, $var, "\'$xmlvars{$var}\'" );

####################################
# Streams file(s)                  #
####################################
  
# Get defaults for data manipulation options associated with 
# each stream (mapping, filling, time-interp etc.)

if ($print>=1) { print "  datm mode is $DATM_MODE \n"; }
if ($print>=1) { print "  datm presaero mode is $DATM_PRESAERO \n"; }
if ($print>=1) { print "  datm cesm grid is $GRID \n"; }
if ($print>=1) { print "  datm atm grid is $ATM_GRID \n" };

# Create input data list (written to later)
my $fh_out = new IO::File;
$fh_out->open(">$CASEROOT/Buildconf/datm.input_data_list") or
    die "** can't open filepath file: datm.input_data_list\n";

# Hash for parsing default_namelist_datm.xml
my %default_namelist_opts;
$default_namelist_opts{'grid'}             = $GRID;
$default_namelist_opts{'datm_mode'}        = $DATM_MODE;
$default_namelist_opts{'presaero_mode'}    = $DATM_PRESAERO;
$default_namelist_opts{'datm_co2_tseries'} = $DATM_CO2_TSERIES;

# Create streams template file(s) - loop over streams
my $streams = $defaults->get_value( "streamslist", \%default_namelist_opts );
$streams    = SetupTools::expand_xml_var( $streams, \%xmlvars );
my @streams = split ",", $streams, -1;
if ($DATM_PRESAERO ne "none") {
    if ($DATM_PRESAERO eq "pt1_pt1") {
	push (@streams, "presaero.$DATM_PRESAERO.$ATM_GRID");
    } else {
	push (@streams, "presaero.$DATM_PRESAERO");
    }
}

if ($DATM_CO2_TSERIES ne "none") {
    push (@streams, "co2tseries.$DATM_CO2_TSERIES");
}

# anomaly forcing: Check for bias correction streams
my $bias_correct = $nl->get_value( 'bias_correct' );
$bias_correct =~ s/[\'\"]//g;
if ( $bias_correct ) {
    push (@streams, $bias_correct); #from namelist_defaults_datm.xml
}

#Check for anomaly forcing streams
my $anomaly_forcing = $nl->get_value( 'anomaly_forcing' );
$anomaly_forcing =~ s/[\'\"]//g;
my @anomaly_forcing = split ",", $anomaly_forcing, -1;
if ( @anomaly_forcing ) {
    push (@streams, @anomaly_forcing);
}

my $ostreams  = undef;
my $omapalgo  = undef;
my $omapmask  = undef;
my $otintalgo = undef;
my $otaxmode  = undef;
my $ofillalgo = undef;
my $ofillmask = undef;
my $odtlimit  = undef;
foreach my $stream ( @streams ) {

    # Exit if there is no prescribed aerosol stream
    if ($stream eq "presaero" && $DATM_PRESAERO eq "none" ) {
	next;
    } 
    if ($print>=1) { print "  datm stream is $stream$INST_STRING \n";}

    # Create hash needed to parse namelist_defaults.xml file
    # Hash default_namelist_opts contains the attribute values needed for $defaults->get_value 
    $default_namelist_opts{'stream'}        = $stream;
    if ($DATM_PRESAERO ne 'none') {
	$default_namelist_opts{'presaero_flag'} = "active";
    } else {
	$default_namelist_opts{'presaero_flag'} = "none";
    }
    if ( $stream =~ /presaero/ ) {
	$default_namelist_opts{'ispresaerostream'} = "TRUE";
    } else {
	$default_namelist_opts{'ispresaerostream'} = "FALSE";
    }
    $default_namelist_opts{'grid'}          = $GRID;
    $default_namelist_opts{'presaero_mode'} = $DATM_PRESAERO;
	
    # Determine stream txt file
    my $outstream = "datm.streams.txt" . ".$stream" . "$INST_STRING";
    if (-e "$CASEROOT/user_$outstream") {

        if ( ! -w "$CASEROOT/user_$outstream" ) {
           print "Your user streams file is read-only: $CASEROOT/user_$outstream\n";
           die "Make it writable to continue\n";
        }
	my $command = "cp -p $CASEROOT/user_$outstream $bldconfdir/$outstream";
	system($command) == 0  or die "system $command failed: $? \n";

    } else {

	# Create hash to initialze streams object
	my %stream_template_opts;
	if ( $print == 0 ) {
	    $stream_template_opts{'printing'} = 0;
	} else {
	    $stream_template_opts{'printing'} = 1;
	}
	$stream_template_opts{'test'}       = $opts{'test'};
	$stream_template_opts{'ProgName'}   = $ProgName;
	$stream_template_opts{'ProgDir'}    = "$cfgdir";
	$stream_template_opts{'cmdline'}    = $cmdline;
	
	$stream_template_opts{'offset'}     = $defaults->get_value( "strm_offset"    , \%default_namelist_opts  );
	$stream_template_opts{'filepath'}   = $defaults->get_value( "strm_datdir"    , \%default_namelist_opts );
	$stream_template_opts{'filenames'}  = $defaults->get_value( "strm_datfil"    , \%default_namelist_opts );
	$stream_template_opts{'domainpath'} = $defaults->get_value( "strm_domdir"    , \%default_namelist_opts  );
	$stream_template_opts{'domain'}     = $defaults->get_value( "strm_domfil"    , \%default_namelist_opts  );
	$stream_template_opts{'datvarnames'}= $defaults->get_value( "strm_datvar"    , \%default_namelist_opts );
	$stream_template_opts{'domvarnames'}= $defaults->get_value( "strm_domvar"    , \%default_namelist_opts );
	$stream_template_opts{'yearfirst'}  = $defaults->get_value( "strm_year_start", \%default_namelist_opts );
	$stream_template_opts{'yearlast'}   = $defaults->get_value( "strm_year_end"  , \%default_namelist_opts );
	
	$stream_template_opts{'filepath'}   = SetupTools::expand_xml_var( $stream_template_opts{'filepath'}  , \%xmlvars );
	$stream_template_opts{'filenames'}  = SetupTools::expand_xml_var( $stream_template_opts{'filenames'} , \%xmlvars );
	$stream_template_opts{'domainpath'} = SetupTools::expand_xml_var( $stream_template_opts{'domainpath'}, \%xmlvars );
	$stream_template_opts{'domain'}     = SetupTools::expand_xml_var( $stream_template_opts{'domain'}    , \%xmlvars );
	$stream_template_opts{'yearfirst'}  = SetupTools::expand_xml_var( $stream_template_opts{'yearfirst'} , \%xmlvars );
	$stream_template_opts{'yearlast'}   = SetupTools::expand_xml_var( $stream_template_opts{'yearlast'}  , \%xmlvars );

        foreach my $item ( "yearfirst", "yearlast", "offset", "filepath", "filenames", "domainpath", "domain", "datvarnames", "domvarnames" ) {
           if ( $stream_template_opts{$item} =~ /^[ ]*$/ ) {
	       die "$ProgName ERROR:: bad $item for stream:  $stream\n";
           }
        }
	
	# Create the streams txt file for this stream (from a generic template)
	my $stream_template = Streams::TemplateGeneric->new( \%stream_template_opts );
	$stream_template->Read( "${CASEROOT}/Buildconf/datm.template.streams.xml" );
	$stream_template->Write( $outstream );
	
	# Append to datm.input_data_list
	my @filenames = $stream_template->GetDataFilenames( 'domain');
	my $i = 0;
	foreach my $file ( @filenames ) {
	    $i++; 
	    print $fh_out "domain${i} = $file\n";
	}
	
	my @filenames = $stream_template->GetDataFilenames( 'data');
	my $i = 0;
	foreach my $file ( @filenames ) {
	    $i++;
	    print $fh_out "file${i} = $file\n";
	}
    }

    # Stream specific namelist variables used below for $nl
    my $tintalgo   = $defaults->get_value( "strm_tintalgo"  , \%default_namelist_opts);
    my $mapalgo    = $defaults->get_value( 'strm_mapalgo'   , \%default_namelist_opts);
    my $mapmask    = $defaults->get_value( 'strm_mapmask'   , \%default_namelist_opts);
    my $taxmode    = $defaults->get_value( "strm_taxmode"   , \%default_namelist_opts);
    my $fillalgo   = $defaults->get_value( 'strm_fillalgo'  , \%default_namelist_opts);
    my $fillmask   = $defaults->get_value( 'strm_fillmask'  , \%default_namelist_opts);
    my $dtlimit    = $defaults->get_value( 'strm_dtlimit'   , \%default_namelist_opts);
    my $beg_year   = $defaults->get_value( 'strm_year_start', \%default_namelist_opts);
    my $end_year   = $defaults->get_value( 'strm_year_end'  , \%default_namelist_opts);
    my $align_year = $defaults->get_value( 'strm_year_align', \%default_namelist_opts);
    $beg_year      = SetupTools::expand_xml_var($beg_year,   \%xmlvars );
    $end_year      = SetupTools::expand_xml_var($end_year,   \%xmlvars );
    $align_year    = SetupTools::expand_xml_var($align_year, \%xmlvars );

    foreach my $year ( $beg_year, $end_year, $align_year ) {
       if ( $year eq "" || $year !~ /[0-9]+/ ) {
	   print "\n\nyear=$year is NOT set or NOT an integer\n";
	   die "$ProgName ERROR:: bad year to run stream over: $stream\n";
       }
    }
    if ( $beg_year > $end_year ) {
	print "\n\nbeg_year=$beg_year end_year=$end_year\n";
	die "$ProgName ERROR:: beg_year greater than end_year\n";
    }

    if ( ! defined($ostreams) ) {
	$ostreams  = "\"$outstream $align_year $beg_year $end_year\"";
	$omapalgo  = "\'$mapalgo\'";
	$omapmask  = "\'$mapmask\'";
	$otintalgo = "\'$tintalgo\'";
	$otaxmode  = "\'$taxmode\'";
	$ofillalgo = "\'$fillalgo\'";
	$ofillmask = "\'$fillmask\'";
	$odtlimit  = "$dtlimit";
    } else {
	$ostreams   = "$ostreams,\"$outstream $align_year $beg_year $end_year\"";
	$omapalgo  .= ",\'$mapalgo\'";
	$omapmask  .= ",\'$mapmask\'";
	$otintalgo .= ",\'$tintalgo\'";
	$otaxmode  .= ",\'$taxmode\'";
	$ofillalgo .= ",\'$fillalgo\'";
	$ofillmask .= ",\'$fillmask\'";
	$odtlimit  .= ",$dtlimit";
    }

}

$fh_out->close;
    
##########################################################
# namelist group: shr_strdata_nml  (in file datm_atm_in) #
##########################################################

my $datamode   = $defaults->get_value( "datamode", \%default_namelist_opts );
my $vectors    = $defaults->get_value( "vectors",  \%default_namelist_opts );
add_default($nl, 'datamode',   'val' => "$datamode" );
add_default($nl, 'vectors',    'val' => "$vectors"   );
add_default($nl, 'domainfile', 'val' => "${ATM_DOMAIN_PATH}/${ATM_DOMAIN_FILE}" );
add_default($nl, 'streams',    'val' => "$ostreams"  );
add_default($nl, 'mapalgo',    'val' => "$omapalgo"  );
add_default($nl, 'mapmask',    'val' => "$omapmask"  );
add_default($nl, 'tintalgo',   'val' => "$otintalgo" );
add_default($nl, 'taxmode',    'val' => "$otaxmode"  );
add_default($nl, 'fillalgo',   'val' => "$ofillalgo" );
add_default($nl, 'fillmask',   'val' => "$ofillmask" );
add_default($nl, 'dtlimit',    'val' => "$odtlimit"  );

##########################################################
# namelist group: datm_nml  (in file datm_in)            #
##########################################################

add_default($nl, 'atm_in' , 'val'=>"datm_atm_in${INST_STRING}");
add_default($nl, 'iradsw');
if ($DATM_MODE =~ /^CORE/) {
    my $factorfn = "$DIN_LOC_ROOT/atm/datm7/CORE2/COREv2.correction_factors.T62.121007.nc";
    add_default($nl, 'factorfn', 'val'=>"$factorfn" );
}
if ($DATM_PRESAERO eq "none" ) {
    add_default($nl, 'presaero', 'val'=>'.false.');
} else {
    add_default($nl, 'presaero', 'val'=>'.true.');
}
add_default($nl, 'decomp',    'val'=>'1d');
add_default($nl, 'force_prognostic_true', 'val'=>'.false.');
add_default($nl, 'restfilm',  'val'=>'undefined'); 
add_default($nl, 'restfils',  'val'=>'undefined'); 

if ( $RUN_TYPE =~ /branch/ ) {
   # The datm master restart file is currently unused
   #if (not defined $nl->get_value('restfils')) {
   #   die "$ProgName ERROR:: restfilm is required for a branch type.\n";
   #}
}

#-----------------------------------------------------------------------------------------------
# Validate that the entire resultant namelist is valid
#
$definition->validate($nl);

#-----------------------------------------------------------------------------------------------
# Write output files
#
my $note = "";

# datm_atm_in
my @groups = qw(shr_strdata_nml);
my $outfile = "./datm_atm_in";
$nl->write($outfile, 'groups'=>\@groups, 'note'=>"$note" );
if ($print>=2) { print "Writing datm_dshr namelist to $outfile $eol"; }

# datm_in
@groups = qw(datm_nml);
$outfile = "./datm_in";
$nl->write($outfile, 'groups'=>\@groups, 'note'=>"$note" );
if ($print>=2) { print "Writing datm_in namelist to $outfile $eol"; }

# atm_modelio
@groups = qw(modelio);
$outfile = "./atm_modelio.nml";
$nl->set_variable_value( "modelio", "logfile", "'atm.log'" );
$nl->write($outfile, 'groups'=>\@groups, 'note'=>"$note" );
if ($print>=2) { print "Writing atm_modelio.nml namelist to $outfile $eol"; }

# Test that input files exist locally.
check_input_files($nl, $DIN_LOC_ROOT, "$CASEROOT/Buildconf/datm.input_data_list");


# END OF MAIN SCRIPT
#===============================================================================================

sub add_default {

# Add a value for the specified variable to the specified namelist object.  The variables
# already in the object have the higher precedence, so if the specified variable is already
# defined in the object then don't overwrite it, just return.
#
# This method checks the definition file and adds the variable to the correct
# namelist group.
#
# The value can be provided by using the optional argument key 'val' in the
# calling list.  Otherwise a default value is obtained from the namelist
# defaults object.  If no default value is found this method throws an exception
# unless the 'nofail' option is set true.
#
# Example 1: Specify the default value $val for the namelist variable $var in namelist
#            object $nl:
#
#  add_default($nl, $var, 'val'=>$val)
#
# Example 2: Add a default for variable $var if an appropriate value is found.  Otherwise
#            don't add the variable
#
#  add_default($nl, $var, 'nofail'=>1)
#
#
# ***** N.B. ***** This routine assumes the following variables are in package main::
#  $definition        -- the namelist definition object
#  $defaults          -- the namelist defaults object
#  $DIN_LOC_ROOT      -- CESM inputdata root directory

    my $nl = shift;     # namelist object
    my $var = shift;    # name of namelist variable
    my %opts = @_;      # options

    # If variable has quotes around it
    if ( $var =~ /'(.+)'/ ) {
       $var = $1;
    }
    # Query the definition to find which group the variable belongs to.  Exit if not found.
    my $group = $definition->get_group_name($var);
    unless ($group) {
	my $fname = $definition->get_file_name();
	die "$ProgName - ERROR: variable \"$var\" not found in namelist definition file $fname.\n";
    }

    # check whether the variable has a value in the namelist object -- if so then skip to end
    my $val = $nl->get_variable_value($group, $var);
    if (! defined $val) {

       # Look for a specified value in the options hash

       if (defined $opts{'val'}) {
	   $val = $opts{'val'};
       }
       # or else get a value from namelist defaults object.
       # Note that if the 'val' key isn't in the hash, then just pass anything else
       # in %opts to the get_value method to be used as attributes that are matched
       # when looking for default values.
       else {
	   $val = $defaults->get_value($var, \%opts);

           # Truncate model_version appropriately
   
           if ( $var eq "model_version" ) {
               $val =~ /(URL: https:\/\/[a-zA-Z0-9._-]+\/)([a-zA-Z0-9\/._-]+)(\/bld\/.+)/;
               $val = $2;
           }
       }

       unless ( defined($val) ) {
	   unless ($opts{'nofail'}) {
	       die "$ProgName - No default value found for $var.\n" . 
                   "            Are defaults provided? \n";
	   }
	   else {
	       return;
	   }
       }


       # query the definition to find out if the variable is an input pathname
       my $is_input_pathname = $definition->is_input_pathname($var);

       # The default values for input pathnames are relative.  If the namelist
       # variable is defined to be an absolute pathname, then prepend
       # the CESM inputdata root directory.
       if (not defined $opts{'no_abspath'}) {
	   if (defined $opts{'set_abspath'}) {
	       $val = set_abs_filepath($val, $opts{'set_abspath'});
	   } else {
	       if ($is_input_pathname eq 'abs') {
		   $val = set_abs_filepath($val, $DIN_LOC_ROOT);
                   if ( $test_files ) {
                      if ($print>=2) { print "Test that $val exists$eol"; }
                      if ( ($val !~ /null/) and (! -f "$val") ) {
                         die "$ProgName - file not found: $var = $val\n";
                      }
                   }
	       }
	   }
       }

       # query the definition to find out if the variable takes a string value.
       # The returned string length will be >0 if $var is a string, and 0 if not.
       my $str_len = $definition->get_str_len($var);

       # If the variable is a string, then add quotes if they're missing
       if ($str_len > 0) {
	   $val = quote_string($val);
       }

       # set the value in the namelist
       $nl->set_variable_value($group, $var, $val);
    }

}

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

sub check_input_files {

# For each variable in the namelist which is an input dataset, check to see if it
# exists locally.
#
# ***** N.B. ***** This routine assumes the following variables are in package main::
#  $definition        -- the namelist definition object

    my $nl = shift;     # namelist object
    my $inputdata_rootdir = shift;    # if false prints test, else creates inputdata file
    my $outfile = shift;
    open(OUTFILE, ">>$outfile") if defined $inputdata_rootdir;

    # Look through all namelist groups
    my @groups = $nl->get_group_names();
    foreach my $group (@groups) {

	# Look through all variables in each group
	my @vars = $nl->get_variable_names($group);
	foreach my $var (@vars) {

	    # Is the variable an input dataset?
	    my $input_pathname_type = $definition->is_input_pathname($var);

	    # If it is, check whether it exists locally and print status
	    if ($input_pathname_type) {

		# Get pathname of input dataset
		my $pathname = $nl->get_variable_value($group, $var);
		# Need to strip the quotes
		$pathname =~ s/[\'\"]//g;

		if ($input_pathname_type eq 'abs') {
                    if ($inputdata_rootdir) {
                        print OUTFILE "$var = $pathname\n";
                    }
                    else {
		        if (-e $pathname) {  # use -e rather than -f since the absolute pathname
			                     # might be a directory
			    print "OK -- found $var = $pathname\n";
		        }
		        else {
			    print "NOT FOUND:  $var = $pathname\n";
		        }
                    }
		}
		elsif ($input_pathname_type =~ m/rel:(.+)/o) {
		    # The match provides the namelist variable that contains the
		    # root directory for a relative filename
		    my $rootdir_var = $1;
		    my $rootdir = $nl->get_variable_value($group, $rootdir_var);
		    $rootdir =~ s/[\'\"]//g;
                    if ($inputdata_rootdir) {
                        $pathname = "$rootdir/$pathname";
                        print OUTFILE "$var = $pathname\n";
                    }
                    else {
		        if (-f "$rootdir/$pathname") {
			    print "OK -- found $var = $rootdir/$pathname\n";
		        }
		        else {
			    print "NOT FOUND:  $var = $rootdir/$pathname\n";
		        }
                    }
		}
	    }
	}
    }
    close OUTFILE if defined $inputdata_rootdir;
    return 0 if defined $inputdata_rootdir;
}

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

sub set_abs_filepath {

# check whether the input filepath is an absolute path, and if it isn't then
# prepend a root directory

    my ($filepath, $rootdir) = @_;

    # strip any leading/trailing whitespace
    $filepath =~ s/^\s+//;
    $filepath =~ s/\s+$//;
    $rootdir  =~ s/^\s+//;
    $rootdir  =~ s/\s+$//;

    # strip any leading/trailing quotes
    $filepath =~ s/^['"]+//;
    $filepath =~ s/["']+$//;
    $rootdir =~ s/^['"]+//;
    $rootdir =~ s/["']+$//;

    my $out = $filepath;
    unless ( $filepath =~ /^\// ) {  # unless $filepath starts with a /
	$out = "$rootdir/$filepath"; # prepend the root directory
    }
    return $out;
}

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

sub valid_option {

    my ($val, @expect) = @_;
    my ($expect);

    $val =~ s/^\s+//;
    $val =~ s/\s+$//;
    foreach $expect (@expect) {
	if ($val =~ /^$expect$/i) { return $expect; }
    }
    return undef;
}

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

sub quote_string {
    my $str = shift;
    $str =~ s/^\s+//;
    $str =~ s/\s+$//;
    unless ($str =~ /^['"]/) {        #"'
        $str = "\'$str\'";
    }
    return $str;
}

