package scr_param;
use strict;

# reads in an SCR hash file and translates it
# into a perl hash object

my $sysconf = "/etc/scr/scr.conf";

sub new
{
  my $type = shift;

  my $self = {};

  $self->{prog} = 'scr_param';
  $self->{usrconf} = undef;
  $self->{sysconf} = undef;
  $self->{compile} = undef;
  $self->{no_user} = undef;

  # define prefix directory
  my $prefix = undef;
  if (defined $ENV{SCR_PREFIX}) {
    # use SCR_PREFIX if set
    $prefix = $ENV{SCR_PREFIX};
  } else {
    # otherwise, use the current working dir
    my $cwd = `pwd`;
    chomp $cwd;
    $prefix = $cwd;
  }

  # define user config file
  my $usrfile = undef;
  if (defined $ENV{SCR_CONF_FILE}) {
    # use SCR_CONF_FILE if set
    $usrfile = $ENV{SCR_CONF_FILE};
  } elsif (defined $prefix) {
    # otherwise, look in the prefix directory
    $usrfile = "$prefix/.scrconf";
  }

  # read in the user configuration file, if specified
  if (defined $usrfile and -r $usrfile) {
    $self->{usrconf} = read_config_file($self, $usrfile);
  }

  # read in the system configuration file
  if (defined $sysconf and -r $sysconf) {
    $self->{sysconf} = read_config_file($self, $sysconf);
  }

  # set our compile time constants
  $self->{compile} = {};
  $self->{compile}{"CNTLDIR"}{"/tmp"} = {};
  $self->{compile}{"CACHEDIR"}{"/tmp"} = {};
  $self->{compile}{"SCR_CNTL_BASE"}{"/tmp"} = {};
  $self->{compile}{"SCR_CACHE_BASE"}{"/tmp"} = {};
  $self->{compile}{"SCR_CACHE_SIZE"}{"1"} = {};

  # set our restricted parameters,
  # these can't be set via env vars or user conf file
  $self->{no_user} = {};
  $self->{no_user}{"SCR_CNTL_BASE"} = {};

  # NOTE: At this point we could scan the environment and user config file
  # for restricted parameters to print a warning.  However, in this case
  # printing the extra messages from a perl script whose output is used by
  # other scripts as input may do more harm than good.  Printing the
  # warning in the library should be sufficient.

  # if CACHE_BASE and CACHE_SIZE are set and the user didn't set CACHEDESC,
  # create a single CACHEDESC in the user hash
  my $cache_base = get($self, "SCR_CACHE_BASE");
  my $cache_size = get($self, "SCR_CACHE_SIZE");
  if (not defined $self->{usrconf}{"CACHE"} and
      defined $cache_base and
      defined $cache_size)
  {
    $self->{usrconf}{"CACHE"}{$cache_base}{"SIZE"}{$cache_size} = {};
  }

  return bless $self, $type;
}

sub read_config_file
{
  my $self = shift @_;
  my $file = shift @_;

  my $h = {};

  if (-r $file) {
    if (open(IN, $file)) {
      while (my $line = <IN>) {
        chomp $line;
        $line =~ s/^\s*//g; # strip any leading whitespace from line
        $line =~ s/\s*$//g; # strip any trailing whitespace from line
        $line =~ s/=/ /g;   # replace '=' with spaces
        my @parts = split(/\s+/, $line);
        my $master_key   = undef;
        my $master_value = undef;
        while (@parts > 0) {
          my $part = shift @parts;
          if ($part =~ /^#/) {
            @parts = ();
          } else {
            # convert the key to upper case
            my $key = uc($part);

            # read in the value (should have at least one more item in the list)
            if (@parts == 0) {
              print "$self->{prog}: ERROR: Invalid key=value pair detected in $file.\n";
              exit 1;
            }

            # expand any embedded environment variable names as in
            # key=$ENVVAR or key=/home/${USER}/${SLURM_JOBID}
            # replaces variable with its value from the environment
            my $value = '';
            my $raw_value = shift @parts;
            my @value_parts = split(/(\$\w+)|(\$\{\w+\})/, $raw_value);
            foreach my $seg (@value_parts)
            {
              next if (not defined($seg));
              $seg = ($ENV{$1} || '') if ($seg =~ m/\$\{?(\w+)\}?/);
              $value .= $seg;
            }

            # record the key value pair in the hash
            if (defined $master_key) {
              $$h{$master_key}{$master_value}{$key}{$value} = {};
            } else {
              $master_key   = $key;
              $master_value = $value;
              $$h{$master_key}{$master_value} = {};
            }
          }
        }
      }
    }

    # done reading, close the file
    close(IN);
  } else {
    print "$self->{prog}: ERROR: Could not open file: $file\n";
  }

  return $h;
}

sub get
{
  my $self = shift;
  my $name = shift;

  # if param is set in environment, return that value
  if (not defined $self->{no_user}{$name} and defined $ENV{$name}) {
    return $ENV{$name};
  }

  # otherwise, check whether we have it defined in our user config file
  if (not defined $self->{no_user}{$name} and defined $self->{usrconf}{$name}) {
    my $first = (keys %{$self->{usrconf}{$name}})[0];
    return $first;
  }

  # otherwise, check whether we have it defined in our system config file
  if (defined $self->{sysconf}{$name}) {
    my $first = (keys %{$self->{sysconf}{$name}})[0];
    return $first;
  }

  # otherwise, check whether its a compile time constant
  if (defined $self->{compile}{$name}) {
    my $first = (keys %{$self->{compile}{$name}})[0];
    return $first;
  }

  return undef;
}

sub get_hash
{
  my $self = shift;
  my $name = shift;

  # if param is set in environment, return that value
  if (not defined $self->{no_user}{$name} and defined $ENV{$name}) {
    my $h = {};
    %$h = $ENV{$name};
    return $h;
    #return $ENV{$name};
  }

  # otherwise, check whether we have it defined in our user config file
  if (not defined $self->{no_user}{$name} and defined $self->{usrconf}{$name}) {
    my $h = {};
    %$h = %{$self->{usrconf}{$name}};
    return $h;
  }

  # otherwise, check whether we have it defined in our system config file
  if (defined $self->{sysconf}{$name}) {
    my $h = {};
    %$h = %{$self->{sysconf}{$name}};
    return $h;
  }

  # otherwise, check whether its a compile time constant
  if (defined $self->{compile}{$name}) {
    my $h = {};
    %$h = %{$self->{compile}{$name}};
    return $h;
  }

  return undef;
}

# convert byte string like 2kb, 1.5m, 200GB, 1.4T to integer value
sub abtoull
{
  my $self = shift;
  my $str  = shift;

  # break string into number and units
  my $number = undef;
  my $units  = undef;
  if ($str =~ /^(\d+\.\d+)(\D+)$/) {
    # got something like "2.5GB"
    $number = $1;
    $units  = $2;
  } elsif ($str =~ /^(\d+)(\D+)$/) {
    # got something like "2GB"
    $number = $1;
    $units  = $2;
  } else {
    # just take what the user has verbatim
    $number = $str;
  }

  # parse units if we have them
  my $factor = undef;
  if (defined $units) {
    if ($units =~ /^[bB]$/) {
      $factor = 1;
    } elsif ($units =~ /^[kK][bB]?$/) {
      $factor = 1024;
    } elsif ($units =~ /^[mM][bB]?$/) {
      $factor = 1024 * 1024;
    } elsif ($units =~ /^[gG][bB]?$/) {
      $factor = 1024 * 1024 * 1024;
    } elsif ($units =~ /^[tT][bB]?$/) {
      $factor = 1024 * 1024 * 1024 * 1024;
    } elsif ($units =~ /^[pP][bB]?$/) {
      $factor = 1024 * 1024 * 1024 * 1024 * 1024;
    } elsif ($units =~ /^[eE][bB]?$/) {
      $factor = 1024 * 1024 * 1024 * 1024 * 1024 * 1024;
    } else {
      # TODO: print error?
      # unknown unit string
    }
  }

  # set value to number portion
  my $val = $number;
  if (defined $factor) {
    # multiply by factor if we have one
    $val *= $factor;
  } elsif (defined $units) {
    # got a units string, but couldn't parse it
    $val = "0";
  }

  # chop off any trailing decimals
  $val = int($val);

  return $val;
}

1;
