=head1 NAME

CFX5::Config - Access to configuration records

=head1 SYNOPSIS

This package provides functions for working out and accessing the
ANSYS CFX configuration variables from standard scripts.

=cut

package CFX5::Config;

#use Carp qw(cluck);
#cluck "Loading CFX5::Config ...";

use strict;

use vars qw($CVSVersion);
$CVSVersion='$Id$ ';

use Exporter;
use vars qw(@ISA @EXPORT_OK %EXPORT_TAGS);
@ISA=qw(Exporter);
@EXPORT_OK=qw(read_config getconf getconf_default setconf 
	      export_conf unexport_conf unsetconf query_conf
	      append_to_execpath prepend_to_execpath
	      append_to_libpath prepend_to_libpath
	      %Config %config %CFX5Config
	      set_debug check_gui_support);
%EXPORT_TAGS=('all' => [@EXPORT_OK],
	      'cmdline' => [qw(set_debug)]
	     );

use CFX5::ParseConfig;
use CFX5::CurrentConfig qw(:all %Config %config %CFX5Config);

use CFX5::PkgLoader qw(cfx5_require);

use CFX5::PathManip qw(:all);

use CFX5::Output qw(:all);

push @ISA, qw(CFX5::CurrentConfig);

=head1 FUNCTION LISTING

=head2 getconf, setconf

These functions are inherited from L<CFX5::CurrentConfig>, and should
be accessible via this package; that is the normal way of doing
things. Only the modules C<use>d by this module should use
L<CFX5::CurrentConfig> directly.

=cut

=head2 read_config

Sets up the configuration, and reads the standard configuration
files. Calling this function is like sourcing cfx5setup.

=cut

{
  my $alreadyread;
sub read_config {
  return if $alreadyread;
  $alreadyread=1;

  # set up things to start with
  unset_env_vars();

  # this would be done later in set_licfile anyway, but we'll do it
  # now in case the user overrides it in cfx5rc
  {
    my $lmlf=getconf("LM_LICENSE_FILE");
    setconf(LM_LICENSE_FILE_ORIG => (defined $lmlf ? $lmlf : '(unset)'));
  }

  # add the windows system dir to the PATH now, so it can be
  # overridden by the user if required
  if ($Config{windows_nt} and exists $ENV{SYSTEMROOT}) {
    prepend_to_path(\$Config{PATH}, join_file($ENV{SYSTEMROOT},'system32'));
  }

  my @messages;
  my $msg;

  # read the release definition file.  This one is mandatory, so give
  # an error if it cannot be found.
  my $relfile=getconf("CFX_RELEASE_FILE");
  if (defined $relfile and CFX5::FileTests::datafile
      ($relfile,
       "ANSYS CFX release definition file $relfile"
      )) {
    my @messages=CFX5::ParseConfig::read_file($relfile);
    if (@messages) {
      _abort("A syntax error was encountered while parsing the release definition file $relfile:\n".map { "  $_\n" } (@messages)."Please ensure that ANSYS CFX is correctly installed on your system");
    }
  } else {
    exit_if_error();
    # didn't signal an error above.  Should never get here, really, as
    # this variable is set up in CFX5::DefaultConfig.
    _abort("Unable to read the ANSYS CFX release definition file.  Please ensure that ANSYS CFX is correctly installed on your system.");
  }

  # now read the site and user config files
  my $sitefile=getconf("CFX_SITEFILE");
  @messages=CFX5::ParseConfig::read_file($sitefile) if defined $sitefile;
  for $msg (@messages) { print STDERR "$msg\n"; }
  @messages=();

  my $userfile=getconf("CFX_USERFILE");
  @messages=CFX5::ParseConfig::read_file($userfile) if defined $userfile;
  for $msg (@messages) { print STDERR "$msg\n"; }

  # check for an expiry date, and handle it if found
  check_expiry() or return 0;

  # we don't explicitly read a default config file, as the defaults
  # are encoded in DefaultConfig anyway.

  # do the things which can't be done with the stuff in DefaultConfig
  set_licfile();
  set_rld_path();
  set_exec_path();

  # now we explicitly read and export some variables
  export_std_conf();

  # finally, show a README if there is one
  show_readme();
}
}

=head3 check_expiry

  check_expiry();

Checks for an expiry.txt file in the installation, and if one is
found, reads it and checks the expiry date of this installation.  If
it has expired, aborts.  If we are within 3 days of the expiration, a
warning is given.

=cut

# allow us to give a descriptive description (!)
use CFX5::Config::def 
  CFX_EXPIRY_FILE => sub { join_file($Config{CFX_DATA_DIR}, 'expiry.txt'); },
  CFX_EXPIRY_DESC => sub {
    # only have a default here if we have an expiry; although it doesn't
    # matter for the stuff here, it looks silly in the Show>Variables.
    if ($Config{CFX_EXPIRY_TIME}) {
      return 'time-limited version';
    } else {
      return undef;
    }
  };

sub check_expiry {
  # the expiry file just sets a variable CFX_EXPIRY_TIME
  my $expfile=getconf("CFX_EXPIRY_FILE");

  # read the file iff we can find it; the following code is unlikely
  # to trigger otherwise, but it's useful for testing to try anyway in
  # case CFX_EXPIRY_TIME is defined.
  if (-f $expfile) {
    my @messages=CFX5::ParseConfig::read_file($expfile) if defined $expfile;
    for my $msg (@messages) { print STDERR "$msg\n"; }
  }

  my $exptime = getconf('CFX_EXPIRY_TIME');
  return 1 unless defined $exptime;

  # the CFX_EXPIRY_TIME will be a time() value -- number of seconds
  # since the epoch -- after which the software will not run.

  # check it's a number and give a sensible error
  unless ($exptime =~ m!^\s*[0-9]+\s*$!) {
    _abort("Bad (unparsable) expiry time $exptime");
  }

  # and finally, the real check
  if (time() > $exptime) {
    _abort("This $Config{CFX_EXPIRY_DESC} of $Config{CFX_PRODUCT_FULLNAME} has expired.  Please contact your CFX Support Representative for an updated copy.");
  } elsif (time() + 3*24*60*60 > $exptime) {
    # give a warning if less than 3 days to go.
    my $days = int(($exptime - time()) / (24*60*60));
    if ($days == 0) {
      $days = 'today';
    } elsif ($days == 1) {
      $days = 'tomorrow';
    } else {
      $days = "in $days days";
    }
    _warn("This $Config{CFX_EXPIRY_DESC} of $Config{CFX_PRODUCT_FULLNAME} expires $days.  Please contact your CFX Support Representative for an updated copy.");
  }

  1;
}

sub check_gui_support {
  return 1 if $Config{CFX_DISABLE_GUI_CHECK};
  return $Config{gui_support};
}  

=head3 unset_env_vars

Unset any environment variables that we explicitly do not want to pick
up from the environment. Typically, these are variables which are used
by third-party packages which the user may have installed centrally.

We also unset a whole slew of C<CFX*> environment variables, as a
workaround to make running from the scripts easier under NT. Users can
still make changes to the config in their F<.cfx5rc> files, or (in
future) via the registry on NT.

=cut

use vars qw(@VarsToUnset);
sub unset_env_vars {
  unless (@VarsToUnset) {
    # do this so we can put comments in it
    @VarsToUnset=(
                  grep { $_ ne '' }         # remove empty words
                  map { split(/\s+/,$_) }   # split on whitespace
                  grep { $_ !~ m/^\s*\#/ }  # remove comments
                  split("\n",               # split into lines
                        q(
		       # these ones are taken from the shell scripts
		       PVM_ROOT PVM_ARCH PVM_VERSION 
		       XENVIRONMENT
		       P3_HOME P3_VERSION P3_USER_MESSAGES P3_HELP_DIRECTORY
		       QTDIR
		       
		       #
		       # the CFX variables
		       #
		       # one should be able to individually justify why any of
		       # these variables should be unset ...
		       #
		       
		       # definitely unset these
		       CFX_SITEFILE
		       CFX_USERFILE
		       CFX_STDFILE
		       CFX_RELEASE_FILE
		       
		       # I guess we need /some/ way to override these ...
		       #CFX_PROFILE_DIR
		       #CFX_USER_CONFIG_DIR
		       
		       # this is mine!
		       CFXBUILDID
		       
		       # unset all the arch strings as the Perl stuff
		       # can calculate them differently to the shell
		       # scripts, and we want to make sure things are
		       # consistent.  We do not unset CFX5ARCH any
		       # more, as it has a special meaning.
		       CFX_OS
		       CFX5OS
		       CFX5_HOST_ARCH
		       ASC_MACH
		       MSC_M
		       MSCv7_M
		       LM_ARCH
		       CFX5DEBUG
		       
		       # we want to unset most of the directories, that is the
		       # initial driver for doing this at all!
		       CFX_ROOT
		       CFX5ROOT
		       CFX_CMD_DIR
		       CFX_CONFIG_DIR
		       CFX_DATA_DIR
		       CFX_EG_DIR
		       CFX_ICON_DIR
		       CFX_INCLUDE_DIR
		       CFX5BINDIR
		       CFX5EXECDIR
		       CFX5LIBDIR
		       CFX5OBJDIR
		       CFX5SCCS
		       CFX5TESTDIR
		       CFX_TCLPROCS
		       CFX_TOOLS_ROOT
		       ANSYS_COMMON_ROOT
		       ANSYS_COMMON_IMAGES_DIR
		       
		       CFX_BUILD_ROOT
		       CFX_BUILD_DIR
		       P3_HOME
		       P3_VERSION
		       P3_HELP_DIRECTORY
		       P3_USER_MESSAGES
		       BLDTMPDIR
		       
		       CFX_MANAGER_DIR
		       CFX_MANAGER_ROOT
		       CFX_SOLVER_OUTPUT_FILE
		       PVM_ROOT
		       PVM_VERSION
		       PVM_ARCH
		       
		       CFX_VIS_ROOT
		       CFX_VIS_SUPPORT_DIR
		       CFX_VIS_INST_DIR
		       # the following are internals for building
		       XP_ROOT
		       XP_VERSION
		       XP_TYPE
		       XP_ARGS
		       AVS_TYPE
		       AVS_ARGS
		       AVS_UI_GAMMA
		       
		       CUE_DIR
		       ANSYSLIC_DIR
		       CFX_HELP_DIR
		       
		       # don't let them do silly things
		       CFX_HOST
		       CFX_FULLHOST
		      )));
  }

  my $v;
  for $v (@VarsToUnset) {
    if (exists $ENV{$v}) {
      _debug "Will not pick up $v from the environment.";
      delete $ENV{$v};
    }
  }

  # remove *any* variables from other releases which we've inherited.
  # This is needed for the MFR technology demonstrator release, to
  # allow running the CFX-5.5.1 solver from within 5.5 SM, but it's a
  # hack, so it's protected by the variable which will be used to
  # enable it.
  if (exists $ENV{CUESM_DEVEL_SCRIPT_PATH}) {
    _debug("It looks like we might be running under the CFX-5.5 solver manager, looking for variables to unset ...");
    for my $var (keys %ENV) {
      if ($ENV{$var} =~ m![\\/]5\.5(?:[\\/]|$)!) {
        if ($var ne "PATH") {
          _debug("Removing variable $var=$ENV{$var} from environment");
          delete $ENV{$var};
        }
      }
    }
  }
}

=head3 set_licfile

  set_licfile();

Set LM_LICENSE_FILE to include the standard ANSYS CFX license file, if found.

=cut

sub set_licfile {
  #
  # the new lm.o should do "all this" automatically.  Allow the user
  # to switch on the old code if desired, just in case the new doesn't
  # work ...
  #
  if ($Config{CFX5_EXPLICIT_LICENSE_FILE}) {
    # where do the LIST tools think it should be?
    cfx5_require('CFX5::LIST');
    my $lf=CFX5::LIST::license_file();

    # and where do we think it should be?
    my $root=getconf("CFX5ROOT");
    internal_error("Unable to get CFX5ROOT") unless defined $root;
    my $licf=join_file($root,"license","license.dat");

    if (-f $licf) {
      # both have some idea where it lives, so we have to pick one; for
      # development purposes, it's better to go with ANSYS CFX's idea if it
      # is workable, but we will give a warning.
      if (defined $lf and $lf ne $licf) {
        _note "Overriding LM_LICENSE_FILE of $lf for local license daemon";
      }
    }

    my @newpath;
    push @newpath, $licf if (-f $licf);
    push @newpath, $lf if defined $lf;
    {
      my $lmlf=getconf("LM_LICENSE_FILE");
      if (defined $lmlf) {
        setconf(LM_LICENSE_FILE_ORIG => (defined $lmlf ? $lmlf : '(unset)'))
          unless defined getconf('LM_LICENSE_FILE_ORIG');
        push @newpath, path_to_list($lmlf);
      }
    }
    @newpath=path_list_remove_dups(@newpath);
    setconf("LM_LICENSE_FILE",list_to_path(@newpath)) if @newpath;

    if (defined getconf('LM_LICENSE_FILE_FORCE')) {
      _note "Overriding LM_LICENSE_FILE setting from LM_LICENSE_FILE_FORCE";
      setconf('LM_LICENSE_FILE',getconf('LM_LICENSE_FILE_FORCE'));
    }
  }

  #
  # for the new lm.o, we export CFX_LICENSING_ROOT if we have one so
  # we make sure we're using the same thing.
  #
  export_conf('CFX_LICENSING_ROOT');

  #
  # we don't need to do anything for the SLM
  #
}

=head3 set_rld_path

  set_rld_path()

Add our directories to the C<LD_LIBRARY_PATH> variable, or whatever it
is called on this machine.

This would probably be better done when the variables are actually
calculated, so (for instance) P3_HOME is not calculated unless it's
actually necessary. Some things still need doing here, though.

=cut '

sub set_rld_path {
  my $ldvar=$Config{ld_path_var};
  return unless defined $ldvar;

  my $os=$Config{CFX5OS};

  if ($os eq "osf") {
    $ENV{_RLD_LIST} = "/usr/shlib/libc.so:DEFAULT";
  }

  my $path=$Config{$ldvar};

  if (defined $Config{CFX5_RESTORE_LIBPATH}) {
    # this variable is set by the Build script when it starts, so we
    # don't get loads of extra crap which confuses Vis on IBM.  In
    # general, I guess it seems to make sense, though.
    $path=$Config{CFX5_RESTORE_LIBPATH};
    unsetconf('CFX5_RESTORE_LIBPATH');
  }

  if ($os eq "solaris") {
    append_to_path(\$path, 
		   "/usr/dt/lib",
		   (defined $Config{OPENWINHOME}
		    ? "$Config{OPENWINHOME}/lib" : ()),
		   "/opt/SUNWits/Graphics-sw/xgl-3.0/lib", "/usr/sfw/lib/sparcv9");
  } 

  if ($os eq "aix" or $os eq "hpux") {
    append_to_path(\$path, "/usr/lib");
  }

  # Need to add the gcc runtimes to the library path if they are not installed
  # If it's already set then don't override it
  if( ($os eq "linux" or $os eq "linux-amd64") and not defined $Config{CFX_NEED_RUNTIMES}) {
    my $dir;
    for $dir ($Config{CFX5LIBDIR}) {
      if( -d "$dir/runtimes" ){
        foreach my $i (glob(join_file($dir,"runtimes", "*"))) {
          use DynaLoader qw(dl_findfile);
	  setconf(CFX_NEED_RUNTIMES => 1) if not DynaLoader::dl_findfile(basename($i));
        }
      }
    }
  }

  my $dir;
  for $dir ($Config{CFX5LIBDIR}) {
    prepend_to_path(\$path, $dir)
      if -d $dir;
    if (defined $Config{CFX_NEED_RUNTIMES} and $Config{CFX_NEED_RUNTIMES}) {
      prepend_to_path(\$path, join_file($dir, "runtimes"))
       if -d "$dir/runtimes";
    }
  }

  if ($os eq "hpux") {
    append_to_path(\$path, "/opt/graphics/common/lib");
  }

  $Config{$ldvar}=$path;
  export_conf($ldvar);

  #
  # need to override / replace some of the above for 64-bit programs.
  # LD_LIBRARY_PATH_64 is used on Solaris; similar things may need
  # doing for other platforms, basically if any shared libraries are
  # used from this for any 64-bit execs.
  #

  #if ($os eq 'solaris') {
  #  for my $dir (join_file($Config{CFX5LIBDIR}, '64bit')) {
  #    append_to_path(\$Config{LD_LIBRARY_PATH_64}, $dir)
  #      if -d $dir;
  #  }
  #  export_conf('LD_LIBRARY_PATH_64');
  #}

}

=head3 set_exec_path

  set_exec_path()

Add our directories to the C<PATH> variable.

=cut

sub set_exec_path {
  my $os=$Config{CFX5OS};

  my $path=$Config{PATH};
  
  # We always want the bin directory.
  my @dirs = ($Config{CFX5BINDIR});
  
  # For each platform specific folder in bin we want to add the relevant
  # directory in tools.
  for my $dir ($Config{CFX5BINDIR},
    join_file($Config{CFX_TOOLS_ROOT},$Config{CFX_OS},"bin")) {
    push (@dirs, $dir) if -d $dir;
  }

  # On Windows we also should add the Framework directory and the 
  # Perl lib directory.
  if ($Config{windows_nt}) {
    if ($Config{FRAMEWORKINSTALLROOT}) {
        my $framework_root = join_file($Config{FRAMEWORKINSTALLROOT},"bin","Win64");
        push(@dirs, $framework_root);
    }
    unshift(@dirs, $Config{CFX_PERL_LIBDIR});
  } 
  
  # We now set the PATH to:
  #     {CFX_PERL_LIBDIR};{CFX5BINDIR};{platform specific TOOLS directories};
  #     {FRAMEWORK bin directory};{PATH}
  for my $dir (@dirs) {
    prepend_to_path(\$path, $dir);
  }

  # Now set the configuration PATH and export it.
  $Config{PATH}=$path;
  export_conf("PATH");
}

=head3 export_std_conf

Export various "standard" variables, needed by everything; this
includes things like the LD_LIBRARY_PATH (or whatever) and the
XENVIRONMENT. 

=cut

sub export_std_conf {
  export_conf(qw(
                 CFX_FORCE_WOW64
                 PATH
                 LM_LICENSE_FILE
                 ANSYSLIC_DIR
                 CFX_CMD_DIR
                 CFXBUILDID
                 CFX_FORMAT
                 LM_PROJECT
                 CFX5MONO
                 CFX_HELP_DIR
                 CFX_HELP_JARDIR
                 CLASSPATH
                 CFX_CCS_SUBMIT_SCRIPT
                 CFX5_IMPI_DISALLOW
                 CFX_SOLVE_USE_REMOTE_HELPER
                 PVM_ROOT
                 CFX_AWP_ROOT
		));
}

=head3 show_readme

Shows the README file from the etc directory, if it exists.  It also
sets an environment variable, C<CFX5_SUPPRESS_README>, which will stop
it being shown again.  This call is also responsible for showing the
terms and conditions the first time ANSYS CFX is run.

=cut

sub show_readme {
  return 0 if $Config{CFX_SUPPRESS_README};

  my $ddir=$Config{CFX_DATA_DIR};
  local *FILE;
  local $/=undef;

  if (-f join_file($ddir,'README')) {
    open FILE, "<".join_file($ddir,'README')
      and print STDERR <FILE>
	and close FILE;
  }

  $Config{CFX_SUPPRESS_README}=1;
  export_conf('CFX_SUPPRESS_README');
  1;
}

=head2 Standard Paths

Functions to add directories to the "standard" paths.

=head3 append_to_execpath

  append_to_execpath join_file($Config{P3_HOME}, "bin");

Add the directories named to the end of the executable path $PATH.

=cut

sub append_to_execpath {
  $Config{PATH}='' unless defined $Config{PATH};
  append_to_path(\$Config{PATH},@_);
  export_conf('PATH');
}

=head3 prepend_to_execpath

  prepend_to_execpath join_file($Config{P3_HOME}, "bin");

Add the directories named to the beginnning of the executable path $PATH.

=cut

sub prepend_to_execpath {
  $Config{PATH}='' unless defined $Config{PATH};
  prepend_to_path(\$Config{PATH},@_);
  export_conf('PATH');
}

=head3 append_to_libpath

  append_to_libpath join_file($Config{P3_HOME}, "lib");

Add the directories named to the end of the library path. This is LD_LIBRARY_PATH on
some UNIXes or something else on the others, and PATH on NT.

=cut

sub append_to_libpath {
  my $ldvar=$Config{ld_path_var};
  $Config{$ldvar}='' unless defined $Config{$ldvar};
  append_to_path(\$Config{$ldvar},@_);
  export_conf($ldvar);
}

=head3 prepend_to_libpath

  prepend_to_libpath join_file($Config{P3_HOME}, "lib");

Add the directories named to the end of the library path. This is LD_LIBRARY_PATH on
some UNIXes or something else on the others, and PATH on NT.

=cut

sub prepend_to_libpath {
  my $ldvar=$Config{ld_path_var};
  $Config{$ldvar}='' unless defined $Config{$ldvar};
  prepend_to_path(\$Config{$ldvar},@_);
  export_conf($ldvar);
}


=head2 export_conf

  export_conf("variable")

Export a variable (or a list of variables) from the configuration
database into the environment, to be picked up by other processes.  If
the configuration variable has the value C<undef>, it will not be
exported.

=cut

sub export_conf {
  my @vars=@_;

  my $v;

  for $v (@_) {
    my $val=getconf($v);
    if (defined $val) {
      $ENV{$v}=$val;
    }
  }
}

=head2 unexport_conf

  unexport_conf("variable")

Removes a variable from the environment, undoing the effect of an
export_conf call.  This does not unset the configuration variable
itself, or lose any modifications to it.

=cut

sub unexport_conf {
  my @vars=@_;

  my $v;

  for $v (@_) {
    delete $ENV{$v};
  }
}

1;
