#!/usr/bin/perl
#
# All start times and end times are assumed to be in UTC and expected
# in be one of these forms (with optional trailing Z):
#   YYYY-MM-DD[T ]HH:MM:SS.ssssss
#   YYYY-MM-DD[T ]HH:MM:SS
#   YYYY-MM-DD
#
# ChangeLog:
#
# 2013.345: Initial version - S. Zuzlewski - NCEDC
# 2014.321: Empty location codes are now represented by ""
#           instead of "--".
# 2017.257: Added dynamic SQL

use strict;
use Getopt::Long;
use File::Basename;
use Config;
use DBI;

my $version  = "2017.257";

my $scriptname = basename($0);

my $verbose  = undef;
my $usage    = undef;
my $pretend  = undef;

my $starttime = undef;
my $endtime   = undef;
my $startbefore = undef;
my $startafter = undef;
my $endbefore = undef;
my $endafter  = undef;

my $network   = undef;
my $station   = undef;
my $location  = undef;
my $channel   = undef;

my $minlatitude = undef;
my $maxlatitude = undef;
my $minlongitude = undef;
my $maxlongitude = undef;

my $latitude = undef;
my $longitude = undef;
my $minradius = undef;
my $maxradius = undef;

my $level = "station";
my $format = "text";
my $includeavailability = undef;

my $updatedafter = undef;

my $flag_header = 0;

my $oracle_home = "/share/apps/lib/Oracle/current";
$ENV{ORACLE_HOME} = $oracle_home if ($ENV{ORACLE_HOME} eq "");
$ENV{NLS_LANG} = "american_america.US7ASCII";
$ENV{NLS_DATE_FORMAT} = "yyyy/mm/dd hh24:mi:ss";

require "/share/apps/lib/dbsetup.pl";

# The WSS will always translate URI parameters to command line options
# prefixed with a double-dash (--), e.g. 'station=ANMO' becomes '--station ANMO'
#
# This characteristic is leveraged to allow this handler script to
# support options specified with a single dash (for diagnostics and
# testing) that can never be specified by a remote call via the WSS.
#
# This is enforced by limiting the arguments that can be called
# using double-dashes to a subset needed to match the web service
# request paramters and the special case '--STDIN' option.
# All such options should be in the @doubledash list.

my @doubledash = ('starttime', 'start', 'endtime', 'end',
		  'startbefore', 'startafter', 'endbefore', 'endafter',
		  'network', 'net', 'station', 'sta',
		  'location', 'loc', 'channel', 'cha',
		  'minlatitude', 'minlat', 'maxlatitude', 'maxlat',
		  'minlongitude', 'minlon', 'maxlongitude', 'maxlon',
		  'latitude', 'lat', 'longitude', 'lon',
		  'minradius', 'maxradius',
		  'level', 'format', 'includeavailability', 'updatedafter');

foreach my $idx ( 0..$#ARGV ) {
  if ( $ARGV[$idx] =~ /^--.+/ )
    {
      my $arg = substr $ARGV[$idx], 2;

      if ( ! grep (/^${arg}$/, @doubledash) ) {
	print STDERR "Unrecognized option: $ARGV[$idx]\n";
	exit (3);
      }
    }
}

# Parse command line arguments
Getopt::Long::Configure (qw{ bundling_override no_auto_abbrev no_ignore_case_always });
my $getoptsret = GetOptions ( 'v+'           => \$verbose,
                              'h'            => \$usage,
                              'p'            => \$pretend,

                              'starttime|start|ts=s' => \$starttime,
                              'endtime|end|te=s' => \$endtime,
			      'startbefore=s'    => \$startbefore,
			      'startafter=s'     => \$startafter,
			      'endbefore=s'      => \$endbefore,
			      'endafter=s'       => \$endafter,

                              'network|net|N=s'  => \$network,
                              'station|sta|S=s'  => \$station,
                              'location|loc|L=s' => \$location,
                              'channel|cha|C=s'  => \$channel,

			      'minlatitude|minlat=s' => \$minlatitude,
			      'maxlatitude|maxlat=s' => \$maxlatitude,
			      'minlongitude|minlon=s' => \$minlongitude,
			      'maxlongitude|maxlon=s' => \$maxlongitude,

			      'latitude|lat=s'   => \$latitude,
			      'longitude|lon=s'  => \$longitude,
			      'minradius=s'      => \$minradius,
			      'maxradius=s'      => \$maxradius,

			      'level=s'          => \$level,
			      'format=s'	 => \$format,
			      'includeavailability=s' => \$includeavailability,
			      'updatedafter=s'   => \$updatedafter,
                              );

exit (3) if ( ! $getoptsret );

if ( defined $usage )
  {
    my $name = basename ($0);
    print STDERR "Output station metadata in text format ($version)\n\n";
    print STDERR "Usage: $name [-v] \n\n";
    print STDERR " -h             Help\n";
    print STDERR " -v             Verbose mode\n";
    print STDERR " -p             Pretend, do everything except requesting data\n";
    print STDERR "\n";
    print STDERR " --starttime    Specify start time (YYYY-MM-DDTHH:MM:SS.ssssss)\n";
    print STDERR " --endtime      Specify end time (YYYY-MM-DDTHH:MM:SS.ssssss)\n";
    print STDERR " --startbefore  Specify start before time (YYYY-MM-DDTHH:MM:SS.ssssss)\n";
    print STDERR " --startafter   Specify start after time (YYYY-MM-DDTHH:MM:SS.ssssss)\n";
    print STDERR " --endbefore    Specify end before time (YYYY-MM-DDTHH:MM:SS.ssssss)\n";
    print STDERR " --endafter     Specify end after time (YYYY-MM-DDTHH:MM:SS.ssssss)\n";
    print STDERR "\n";
    print STDERR " --network      Network code, list and wildcards (* and ?) accepted\n";
    print STDERR " --station      Station code, list and wildcards (* and ?) accepted\n";
    print STDERR " --location     Location ID, list and wildcards (* and ?) accepted\n";
    print STDERR " --channel      Channel codes, list and wildcards (* and ?) accepted\n";
    print STDERR "\n";
    print STDERR " --minlatitude  Specify minimum latitude in degrees\n";
    print STDERR " --maxlatitude  Specify maximum latitude in degrees\n";
    print STDERR " --minlongitude Specify minimum longitude in degrees\n";
    print STDERR " --maxlongitude Specify maximum longitude in degrees\n";
    print STDERR "\n";
    print STDERR " --latitude     Specify latitude for radius search\n";
    print STDERR " --longitude    Specify longitude for radius search\n";
    print STDERR " --minradius    Specify minimum radius from latitude:longitude\n";
    print STDERR " --maxradius    Specify maximum radius from latitude:longitude\n";
    print STDERR "\n";
    print STDERR " --level        Specify level of text results [network,station,channel]\n";
    print STDERR " --updatedafter Limit results to metadata updated after time\n";
    print STDERR "\n";

    exit (1);
}

# Validate global request parameters, exit if needed
my $retval = &ValidateRequest();

if ( $retval ) {
  exit ($retval);
}

# Database connection
# Parse DB_CONNECT environment variable for database access.
my ( $dbuser, $dbpass, $dbname );
if ($ENV{DB_CONNECT} ne "") {
        ($dbuser,$dbpass,$dbname) = $ENV{DB_CONNECT} =~ m|^(.+)/(.+)\@(.+)$|;
        die ("Error: invalid DB_CONNECT environment variable: $ENV{DB_CONNECT}\n")
                if ($dbuser eq "" || $dbpass eq "" || $dbname eq "");
}

my $dbh = DBI->connect(
        "dbi:Oracle:" . $dbname, $dbuser, $dbpass,
        { RaiseError => 1, AutoCommit => 0 }
) || die "Database connection not made: $DBI::errstr";


# Handle/fullfill data request
my $retval = &HandleRequest();

$dbh->disconnect;

if ( $retval ) {
  exit ($retval);
}

# Return with success code
exit (0);

## End of main


######################################################################
# HandleRequest:
#
# Process validated request and return selected data on STDOUT.  On
# errors this routine should return either an appropriate error code
# or exit within the routine with the appropriate message and error
# code.  The request parameters are available from global variables.
#
# Name parameter lists: each of $network, $station, $location and
# $channel may contain a list of comma separated values, which may
# futher contain wildcards.  For example, the channel value may
# contain "LHE,LHN,LHZ" or "LH?,BH?".
#
# When a fatal error is reached in this routine be sure to use the
# appropriate exit code:
#
#   Exit Status = Description
#   1  =  General error. An error description may be provided on stderr
#   2  =  No data. Request was successful but results in no data
#   3  =  Invalid or unsupported argument/parameter
#   4  =  Too much data requested
#
# Returns 0 on success, otherwise an appropriate exit code.
######################################################################
sub HandleRequest ()
{
  my @net_list;
  my @sta_list;
  my $ilevel = 1;

  my $sql_stmt = "SELECT DISTINCT c.net,c.sta,c.location,c.seedchan,c.lat,c.lon,c.elev,c.edepth,c.azimuth,c.dip,c.samprate,c.ondate,c.offdate,a.description, u.name, s.sensitivity, s.frequency FROM Channel_Data c, D_Abbreviation a, D_Unit u, Sensitivity s WHERE c.inid = a.id AND c.unit_signal = u.id AND s.stage_seq(+) = 0 AND c.net = s.net(+) AND c.sta = s.sta(+) AND c.seedchan = s.seedchan(+) AND c.location = s.location(+) AND c.ondate = s.ondate(+) ";

  my $tmp1 = substr $starttime,0,19;
  my $tmp2 = substr $endtime,0,19;
  $tmp1 =~ s/T/ /g;
  $tmp2 =~ s/T/ /g;
  if ( defined $starttime && defined $endtime ) {
  $sql_stmt .= " AND ((c.ondate < '$tmp1' AND c.offdate > '$tmp2') OR (c.ondate BETWEEN '$tmp1' AND '$tmp2') OR (c.offdate BETWEEN '$tmp1' AND '$tmp2')) ";
  }

  $tmp1 = substr $startbefore,0,19;
  $tmp1 =~ s/T/ /g;
  $sql_stmt .= " AND c.ondate < '$tmp1' " if ( defined $startbefore );

  $tmp1 = substr $startafter,0,19;
  $tmp1 =~ s/T/ /g;
  $sql_stmt .= " AND c.ondate > '$tmp1' " if ( defined $startafter );

  $tmp1 = substr $endbefore,0,19;
  $tmp1 =~ s/T/ /g;
  $sql_stmt .= " AND c.offdate < '$tmp1' " if ( defined $endbefore );

  $tmp1 = substr $endafter,0,19;
  $tmp1 =~ s/T/ /g;
  $sql_stmt .= " AND c.offdate > '$tmp1' " if ( defined $endafter );

  if ( defined $network ) {
	my $fflag = 0;
        $sql_stmt .= " AND (";
	$network =~ s/\?/_/g;
	$network =~ s/\*/%/g;

	my @unet = split(/,/,$network);
	foreach my $v (@unet) {
		if ($fflag == 0) {
			$sql_stmt .= " c.net LIKE ('$v') ";
			$fflag = 1;
		} else {
			$sql_stmt .= " OR c.net LIKE ('$v') ";
		}
  	}

	$sql_stmt .= ") ";
  }

  if ( defined $station ) {
	my $fflag = 0;
	$sql_stmt .= " AND (";
        $station =~ s/\?/_/g;
        $station =~ s/\*/%/g;

	my @usta = split(/,/,$station);
	foreach my $v (@usta) {
		if ($fflag == 0) {
			$sql_stmt .= " c.sta LIKE ('$v') ";
			$fflag = 1;
		} else {
			$sql_stmt .= " OR c.sta LIKE ('$v') ";
		}
	}

	$sql_stmt .= ") ";
  }

  if ( defined $location ) {
	my $fflag = 0;
        $sql_stmt .= " AND (";
	$location =~ s/-/ /g;
        $location =~ s/\?/_/g;
        $location =~ s/\*/%/g;

	my @uloc = split(/,/,$location);
	foreach my $v (@uloc) {
		if ($fflag == 0) {
			$sql_stmt .= " c.location LIKE ('$v') ";
			$fflag = 1;
		} else {
			$sql_stmt .= " OR c.location LIKE ('$v') ";
		}
	}

	$sql_stmt .= ") ";
  }

  if ( defined $channel ) {
	my $fflag = 0;
	$sql_stmt .= " AND (";
        $channel =~ s/\?/_/g;
        $channel =~ s/\*/%/g;

	my @ucha = split(/,/,$channel);
	foreach my $v (@ucha) {
		if ($fflag == 0) {
			$sql_stmt .= " c.seedchan LIKE ('$v') ";
			$fflag = 1;
		} else {
			$sql_stmt .= " OR c.seedchan LIKE ('$v') ";
		}
	}

	$sql_stmt .= ") ";
  }

  $sql_stmt .= " AND c.lat >= $minlatitude " if ( defined $minlatitude );
  $sql_stmt .= " AND c.lat <= $maxlatitude " if ( defined $maxlatitude );
  $sql_stmt .= " AND c.lon >= $minlongitude " if ( defined $minlongitude );
  $sql_stmt .= " AND c.lon <= $maxlongitude " if ( defined $maxlongitude );

  $tmp1 = substr $updatedafter,0,19;
  $tmp1 =~ s/T/ /g;
  $sql_stmt .= " AND c.lddate >= '$tmp1' " if ( defined $updatedafter );

  $sql_stmt .= " ORDER BY c.net,c.sta,c.location,c.seedchan,c.ondate";

  if ( $verbose ) {
    print STDERR "SQL Statement\n";
    print STDERR "-----------\n";
    print STDERR "${sql_stmt}\n";
    print STDERR "-----------\n";
  }

  # Stop here if pretending
  return 0 if $pretend;

  if ( $level eq "network" ) {
        $ilevel = 0;
  }

  if ( $level eq "station" ) {
        $ilevel = 1;
  }

  if ( $level eq "channel" ) {
        $ilevel = 2;
  }

  # Query DB
  my ($cursor,$net,$sta,$location,$seedchan,$lat,$lon,$elev,$depth,$azimuth,$dip,$samprate,$ondate,$offdate,$description,$name,$sensitivity,$frequency);
  unless ($cursor = $dbh->prepare("$sql_stmt")) {
	print ("Error: unable to prepare cursor\n");
	$dbh->disconnect;
	exit;
  }

  unless ($cursor->execute) {
	print ("Error: unable to execute cursor\n");
	$dbh->disconnect;
	exit;
  }

  while (($net,$sta,$location,$seedchan,$lat,$lon,$elev,$depth,$azimuth,$dip,$samprate,$ondate,$offdate,$description,$name,$sensitivity,$frequency) = $cursor->fetchrow_array) {
	$ondate  =~ s/ /T/g;
	$ondate	 =~ s/\//-/g;
	$offdate =~ s/ /T/g;
	$offdate =~ s/\//-/g;
	$location =~ s/ //g;

	my $flagI = 1;

	# Delta
	if (defined $latitude) {
		my $delta_sql_stmt = "SELECT tools.distance ($lat, $lon, $latitude, $longitude) FROM DUAL";

                # Query DB
                my ($delta,$delta_cursor);
                unless ($delta_cursor = $dbh->prepare("$delta_sql_stmt")) {
                        print ("Error: unable to prepare cursor\n");
                        $dbh->disconnect;
                        exit;
                }

                unless ($delta_cursor->execute) {
                        print ("Error: unable to execute cursor\n");
                        $dbh->disconnect;
                        exit;
                }

                while (($delta) = $delta_cursor->fetchrow_array) {
			if (($delta < $minradius*111.195) || ($delta > $maxradius*111.195)) {
				$flagI = 0;
			}
                }

                unless ($delta_cursor->finish) {
                        print ("Error: unable to finish cursor\n");
                        $dbh->disconnect;
                        exit;
                }
	}

	if ($flagI == 1) {
		# Network level
		if ($ilevel == 0) {
			push(@net_list, $net);
		}
		# Station level
		elsif ($ilevel == 1) {
			push(@sta_list, "$net.$sta");
		}
		# Channel level
		elsif ($ilevel == 2) {
			if ($flag_header == 0) {
				$flag_header = 1;
				print ("#Network | Station | Location | Channel | Latitude | Longitude | Elevation | Depth | Azimuth | Dip | Instrument | Scale | ScaleFreq | ScaleUnits | SampleRate | StartTime | EndTime\n");
			}

			print ("$net|$sta|$location|$seedchan|$lat|$lon|$elev|$depth|$azimuth|$dip|$description|$sensitivity|$frequency|$name|$samprate|$ondate|$offdate\n");
		}
	}
  }

  unless ($cursor->finish) {
	print ("Error: unable to finish cursor\n");
	$dbh->disconnect;
	exit;
  }

  # Network level
  if ($ilevel == 0) {
	# Remove duplicates
	my @net_unique = ();
	my %net_seen   = ();

	foreach my $elem ( @net_list ) {
		next if $net_seen{ $elem }++;
		push @net_unique, $elem;
	}

	my ($net_cursor,$net,$net_ondate,$net_offdate,$net_description,$nbsta);
	# Prepare sql statement
	my $net_sql_stmt = "SELECT DISTINCT s.net,MIN(s.ondate),MAX(s.offdate),a.description, COUNT(DISTINCT sta) nbsta FROM Station_Data s, D_Abbreviation a WHERE s.net_id = a.id AND s.net = ? GROUP BY s.net,a.description";
	unless ($net_cursor = $dbh->prepare("$net_sql_stmt")) {
	    print ("Error: unable to prepare cursor\n");
	    $dbh->disconnect;
	    exit;
	}

	foreach my $p (@net_unique) {
		if ($flag_header == 0) {
			$flag_header = 1;
			print ("#Network | Description | StartTime | EndTime | TotalStations\n");
		}


		if ( $verbose ) {
			print STDERR "SQL Statement\n";
			print STDERR "-----------\n";
			print STDERR "${net_sql_stmt}\n";
			print STDERR "-----------\n";
		}

		# Query DB
		unless ($net_cursor->execute($p)) {
			print ("Error: unable to execute cursor\n");
			$dbh->disconnect;
			exit;
		}

		while (($net,$net_ondate,$net_offdate,$net_description,$nbsta) = $net_cursor->fetchrow_array) {
			$net_ondate  =~ s/ /T/g;
			$net_ondate  =~ s/\//-/g;
			$net_offdate =~ s/ /T/g;
			$net_offdate =~ s/\//-/g;
			print "$net|$net_description|$net_ondate|$net_offdate|$nbsta\n";
		}

		unless ($net_cursor->finish) {
			print ("Error: unable to finish cursor\n");
			$dbh->disconnect;
			exit;
		}
	}
  }
  # Station level
  elsif ($ilevel == 1) {
	# Remove duplicates
	my @sta_unique = ();
	my %sta_seen   = ();

	foreach my $elem ( @sta_list ) {
		next if $sta_seen{ $elem }++;
		push @sta_unique, $elem;
	}

	my ($sta_cursor,$net,$sta,$sta_lat,$sta_lon,$sta_elev,$staname,$sta_ondate,$sta_offdate);
	# Prepare sql statement
	my $sta_sql_stmt = "SELECT DISTINCT net,sta,lat,lon,elev,staname,MIN(ondate),MAX(offdate) FROM Station_Data WHERE net = ? AND sta = ? GROUP BY net,sta,lat,lon,elev,staname";
	unless ($sta_cursor = $dbh->prepare("$sta_sql_stmt")) {
	    print ("Error: unable to prepare cursor\n");
	    $dbh->disconnect;
	    exit;
	}

	foreach my $p (@sta_unique) {
		if ($flag_header == 0) {
			$flag_header = 1;
			print ("#Network | Station | Latitude | Longitude | Elevation | SiteName | StartTime | EndTime\n");
		}

		my ($rnet,$rsta) = split(/\./,$p);

                if ( $verbose ) {
                        print STDERR "SQL Statement\n";
                        print STDERR "-----------\n";
                        print STDERR "${sta_sql_stmt}\n";
                        print STDERR "-----------\n";
                }

                # Query DB
                unless ($sta_cursor->execute($rnet, $rsta)) {
                        print ("Error: unable to execute cursor\n");
                        $dbh->disconnect;
                        exit;
                }

                while (($net,$sta,$sta_lat,$sta_lon,$sta_elev,$staname,$sta_ondate,$sta_offdate) = $sta_cursor->fetchrow_array) {
                        $sta_ondate  =~ s/ /T/g;
                        $sta_ondate  =~ s/\//-/g;
                        $sta_offdate =~ s/ /T/g;
                        $sta_offdate =~ s/\//-/g;
                        print "$net|$sta|$sta_lat|$sta_lon|$sta_elev|$staname|$sta_ondate|$sta_offdate\n";
                }

                unless ($sta_cursor->finish) {
                        print ("Error: unable to finish cursor\n");
                        $dbh->disconnect;
                        exit;
                }
	}
  }

  return 0;
} # End of HandleRequest()


######################################################################
# ValidateRequest:
#
# Validate the data selection parameter values and print specific
# errors to STDERR.
#
# Expected date-time formats are one of:
#
# YYYY-MM-DD[T ]HH:MM:SS.ssssss
# YYYY-MM-DD[T ]HH:MM:SS
# YYYY-MM-DD
#
# Month, day, hour, min and second values may be single digits.
#
# If specified, network, station, location and channel are
# validated for length and characters allowed in the SEED 2.4
# specification or wildcards.
#
# Name parameter lists: each of network, station, location and channel
# may contain a list of comma separated values, which may futher
# contain wildcards.  For example, the channel value may contain
# "LHE,LHN,LHZ" or "LH?,BH?".
#
# Returns 0 on success, otherwise an appropriate exit code.
######################################################################
sub ValidateRequest () {
  my $retval = 0;

  if ( defined $starttime && ! defined $endtime ) {
	$endtime   = "3000-01-01T00:00:00";
  }

  if ( ! defined $starttime && defined $endtime ) {
        $starttime = "1900-01-01T00:00:00";
  }

  if ( defined $starttime && ! ValidDateTime ($starttime) ) {
    print STDERR "Unrecognized start time [YYYY-MM-DDTHH:MM:SS.ssssss]: '$starttime'\n";
    $retval = 3;
  }

  if ( defined $endtime && ! ValidDateTime ($endtime) ) {
    print STDERR "Unrecognized end time [YYYY-MM-DDTHH:MM:SS.ssssss]: '$endtime'\n";
    $retval = 3;
  }

  if ( defined $startbefore && ! ValidDateTime ($startbefore) ) {
    print STDERR "Unrecognized start before time [YYYY-MM-DDTHH:MM:SS.ssssss]: '$startbefore'\n";
    $retval = 3;
  }

  if ( defined $startafter && ! ValidDateTime ($startafter) ) {
    print STDERR "Unrecognized start after time [YYYY-MM-DDTHH:MM:SS.ssssss]: '$startafter'\n";
    $retval = 3;
  }

  if ( defined $endbefore && ! ValidDateTime ($endbefore) ) {
    print STDERR "Unrecognized end before time [YYYY-MM-DDTHH:MM:SS.ssssss]: '$endbefore'\n";
    $retval = 3;
  }

  if ( defined $endafter && ! ValidDateTime ($endafter) ) {
    print STDERR "Unrecognized end after time [YYYY-MM-DDTHH:MM:SS.ssssss]: '$endafter'\n";
    $retval = 3;
  }

  if ( defined $updatedafter && ! ValidDateTime ($updatedafter) ) {
    print STDERR "Unrecognized updated after time [YYYY-MM-DDTHH:MM:SS.ssssss]: '$updatedafter'\n";
    $retval = 3;
  }

  if ( defined $network ) {
    foreach my $net ( split (/,/, $network) ) {
      if ( $net !~ /^[A-Za-z0-9\*\?]{1,2}$/ ) {
	print STDERR "Unrecognized network code [1-2 characters]: '$net'\n";
	$retval = 3;
      }
    }
  }

  if ( defined $station ) {
    foreach my $sta ( split (/,/, $station) ) {
      if ( $sta !~ /^[A-Za-z0-9\*\?]{1,5}$/ ) {
	print STDERR "Unrecognized station code [1-5 characters]: '$sta'\n";
	$retval = 3;
      }
    }
  }

  if ( defined $location ) {
    foreach my $loc ( split (/,/, $location) ) {
      if ( $loc !~ /^[A-Za-z0-9\-\*\?]{1,2}$/ ) {
	print STDERR "Unrecognized location ID [1-2 characters]: '$loc'\n";
	$retval = 3;
      }
    }
  }

  if ( defined $channel ) {
    foreach my $chan ( split (/,/, $channel) ) {
      if ( $chan !~ /^[A-Za-z0-9\*\?]{1,3}$/ ) {
	print STDERR "Unrecognized channel codes [1-3 characters]: '$chan'\n";
	$retval = 3;
      }
    }
  }

  # Validate minlatitude option, must be a decimal value between -90 and 90
  if ( defined $minlatitude ) {
    if ( $minlatitude !~ /^[+-]?\d+(\.\d+)?$/ ) {
      print STDERR "Unrecognized value for minlatitude [decimal degrees]: '$minlatitude'\n";
      $retval = 3;
    }
    elsif ( $minlatitude < -90.0 || $minlatitude > 90.0 ) {
      print STDERR "Value for minlatitude out of range [-90 to 90]: '$minlatitude'\n";
      $retval = 3;
    }
  }

  # Validate maxlatitude option, must be a decimal value between -90 and 90
  if ( defined $maxlatitude ) {
    if ( $maxlatitude !~ /^[+-]?\d+(\.\d+)?$/ ) {
      print STDERR "Unrecognized value for maxlatitude [decimal degrees]: '$maxlatitude'\n";
      $retval = 3;
    }
    elsif ( $maxlatitude < -90.0 || $maxlatitude > 90.0 ) {
      print STDERR "Value for maxlatitude out of range [-90 to 90]: '$maxlatitude'\n";
      $retval = 3;
    }
  }

  # Test latitude interval
  if ( defined $minlatitude && defined $maxlatitude ) {
        if ( $minlatitude > $maxlatitude ) {
                print STDERR "Minimum latitude is greater than maximum latitude: '$minlatitude','$maxlatitude'\n";
                $retval = 3;
        }
  }

  # Validate minlongitude option, must be a decimal value between -180 and 180
  if ( defined $minlongitude ) {
    if ( $minlongitude !~ /^[+-]?\d+(\.\d+)?$/ ) {
      print STDERR "Unrecognized value for minlongitude [decimal degrees]: '$minlongitude'\n";
      $retval = 3;
    }
    elsif ( $minlongitude < -180.0 || $minlongitude > 180.0 ) {
      print STDERR "Value for minlongitude out of range [-180 to 180]: '$minlongitude'\n";
      $retval = 3;
    }
  }

  # Validate maxlongitude option, must be a decimal value between -180 and 180
  if ( defined $maxlongitude ) {
    if ( $maxlongitude !~ /^[+-]?\d+(\.\d+)?$/ ) {
      print STDERR "Unrecognized value for maxlongitude [decimal degrees]: '$maxlongitude'\n";
      $retval = 3;
    }
    elsif ( $maxlongitude < -180.0 || $maxlongitude > 180.0 ) {
      print STDERR "Value for maxlongitude out of range [-180 to 180]: '$maxlongitude'\n";
      $retval = 3;
    }
  }

  # Test longitude interval
  if ( defined $minlongitude && defined $maxlongitude ) {
        if ( $minlongitude > $maxlongitude ) {
                print STDERR "Minimum longitude is greater than maximum longitude: '$minlongitude','$maxlongitude'\n";
                $retval = 3;
        }
  }

  # Validate latitude option, must be a decimal value between -90 and 90
  if ( defined $latitude ) {
    if ( $latitude !~ /^[+-]?\d+(\.\d+)?$/ ) {
      print STDERR "Unrecognized value for latitude [decimal degrees]: '$latitude'\n";
      $retval = 3;
    }
    elsif ( $latitude < -90.0 || $latitude > 90.0 ) {
      print STDERR "Value for latitude out of range [-90 to 90]: '$latitude'\n";
      $retval = 3;
    }
  }

  # Validate longitude option, must be a decimal value between -180 and 180
  if ( defined $longitude ) {
    if ( $longitude !~ /^[+-]?\d+(\.\d+)?$/ ) {
      print STDERR "Unrecognized value for longitude [decimal degrees]: '$longitude'\n";
      $retval = 3;
    }
    elsif ( $longitude < -180.0 || $longitude > 180.0 ) {
      print STDERR "Value for longitude out of range [-180 to 180]: '$longitude'\n";
      $retval = 3;
    }
  }

  # Validate minradius option, must be a decimal value between 0 and 180
  if ( defined $minradius ) {
    if ( $minradius !~ /^[+-]?\d+(\.\d+)?$/ ) {
      print STDERR "Unrecognized value for minradius [decimal degrees]: '$minradius'\n";
      $retval = 3;
    }
    elsif ( $minradius < 0.0 || $minradius > 180.0 ) {
      print STDERR "Value for minradius out of range [0 to 180]: '$minradius'\n";
      $retval = 3;
    }
    if (!defined $maxradius ) {
	$maxradius = 180;
    }
  }

  # Validate maxradius option, must be a decimal value between 0 and 180
  if ( defined $maxradius ) {
    if ( $maxradius !~ /^[+-]?\d+(\.\d+)?$/ ) {
      print STDERR "Unrecognized value for maxradius [decimal degrees]: '$maxradius'\n";
      $retval = 3;
    }
    elsif ( $maxradius < 0.0 || $maxradius > 180.0 ) {
      print STDERR "Value for maxradius out of range [0 to 180]: '$maxradius'\n";
      $retval = 3;
    }
    if (!defined $minradius ) {
	$minradius = 0;
    }
  }

  # Check that latitude & longitude are specified if minradius or maxradius is specified
  if ( (defined $minradius || defined $maxradius) &&
       ! (defined $latitude && defined $longitude) ) {
    print STDERR "latitude and longitude must be specified if minradius and/or maxradius is used\n";
    $retval = 3;
  }

  # Check that min/max lat/lon is not combined with lat/lon/radius
  if ( (defined $minlatitude || defined $maxlatitude || defined $minlongitude || defined $maxlongitude) &&
       (defined $minradius || defined $maxradius) ) {
    print STDERR "[min|max]latitude and [min|max]longitude cannot be combined with latitude, longitude [min|max]radius\n";
    $retval = 3;
  }

  # Test radius interval
  if ( defined $minradius && defined $maxradius ) {
        if ( $minradius > $maxradius ) {
                print STDERR "Minimum radius is greater than maximum radius: '$minradius','$maxradius'\n";
                $retval = 3;
        }
  }

  # Validate level option, must be "network", "station" or "channel"
  if ( defined $level && $level !~ /^(network|station|channel)$/i ) {
    print STDERR "Unrecognized level selection [network, station, channel]: '$level'\n";
    $retval = 3;
  }

  return $retval;
}  # End of ValidateRequest()


######################################################################
# ValidateDateTime:
#
# Validate a string to match one of these date-time formats:
#
# YYYY-MM-DD[T ]HH:MM:SS.ssssss
# YYYY-MM-DD[T ]HH:MM:SS
# YYYY-MM-DD
#
# Returns 1 on match and 0 on non-match.
######################################################################
sub ValidDateTime () {
  my $string = shift;

  return 0 if ( ! $string );

  return 1 if ( $string =~ /^\d{4}-[01]\d-[0-3]\d[T ][0-2]\d:[0-5]\d:[0-5]\d\.\d+(Z)?$/ ||
		$string =~ /^\d{4}-[01]\d-[0-3]\d[T ][0-2]\d:[0-5]\d:[0-5]\d(Z)?$/ ||
		$string =~ /^\d{4}-[01]\d-[0-3]\d(Z)?$/ );

  return 0;
}  # End of ValidDateTime
