=head1 NAME

  CFX5::Simulation::SettingsBase - Setting handling for CFX5::Simulation

=head1 SYNOPSIS

This object is inherited by ConfigSettings and SimSetting, and is used solely as
a way of splitting up the code.

=head1 DESIGN DOCUMENTS

  IPRODUCT.SCRIPTS.SOLVER.DESIGN.5.4

=head1 DESCRIPTION

=cut

package CFX5::Simulation::SettingsBase;

use strict;
use CFX5::Output qw(:all error_text);
use CFX5::FileTests;
use CFX5::FileStat;

use CFX5::PathManip qw(dirname basename fullpath join_file normalize_filename);
use CFX5::FileOps qw(select_execarch);

use CFX5::Config qw(setconf %Config export_conf getconf_default getconf
                    append_to_libpath);

use CFX5::CCLI;
use CFX5::Files::Partition;
use CFX5::PkgLoader qw(cfx5_require);
use CFX5::ResInfo ; 

use Carp;

sub initialise {
  my $self=shift;

  $self->{settings} = {};
  $self->{cclapi} = {};
  $self->{SettingInfo}={};

  1;
}

=pod

The settings functions are all called as follows.  To set a setting:

  $job->setting("new-value");
  $job->setting(undef);

to get the current value of a setting:

  $job->setting();

The return value is the (previous in the first case) value of the
setting, which can be the default value if it has not been explicitly
set.  Errors in the arguments are indicated by called C<_error>, so
one may do something like:

  $job->setting1($s1);
  $job->setting2($s2);
  exit_if_error();

See C<exit_if_error> and C<error_pending>.  If a setting succeeds,
this does not mean that there are no problems, just no obvious ones.
For example, if one is doing a solver run with CCL the definition file
must currently be writable (this will change before realease, though),
but the C<deffile> function will quite happily accept it; it will not
accept it if the file does not exist.

Each setting I<setting> also has an associated function
C<_ok_I<setting>>, which is responsible for checking that a putative
value for a setting is OK; if not, it should signal an error and
return FALSE.  There is also a function C<_def_I<setting>>, which
returns the default value for a setting.  These functions are I<not>
designed to be called from outside the module.

=cut

=head2 SETTING SETUP AND CONFIGURATION

This section contains routines for setting up and checking information
about the settings (as opposed to the actual settings).

=head4 _setting_info

Gets or sets the information about a particular setting.  This is the
nub of the code which maps CCL to and from the in-memory hash.  The
primary key is the CCL path, or if there is no CCL path, the setting
name.  The setting name should be provided as an alias if there is
one.

To set:

  _setting_info('/SIMULATION CONTROL/EXECUTION CONTROL/RUN DEFINITION/Solver Input File',
    alias => 'deffile',
    default => \&_def_deffile,
    check => \&_ok_deffile,
  );

To read the setting information:

  my $fcheck=_setting_info('/SIMULATION CONTROL/EXECUTION CONTROL/RUN DEFINITION/Solver Input File',
                           'check');

This information is static, and is not specific to any particular
CFX5::Job object.

=cut

{
  my %ValidSettings = map { $_ => 1 } qw(alias default check);

sub _setting_info {
  my $self=shift;
  my $setting=shift;

  if (@_ == 1) {
    # read a setting
    my $type=shift;
    unless (exists $self->{SettingInfo}{$setting}) {
      # something we don't know about, assume we don't know (!)
      return undef;
    }
    unless ($ValidSettings{$type}) {
      internal_error("Unknown information type $type");
    }
    return $self->{SettingInfo}{$setting}->{$type};
  } elsif (not @_) {
    # erm ... ?
    internal_error("Insufficient arguments in call to _setting_info");
  } else {
    # set the stuff
    while (@_) {
      my $k=shift;
      my $v=shift;
      unless ($ValidSettings{$k}) {
        internal_error("Unknown information type $k");
      }
      $self->{SettingInfo}{$setting}->{$k}=$v;
    }
    if ($self->{SettingInfo}{$setting}{alias}) {
      # make an alias
      $self->{SettingInfo}{$self->{SettingInfo}{$setting}{alias}}=$self->{SettingInfo}{$setting};
    }
  }

  1;
}
}

=head4 _setting_status

This call is a counterpart of C<_setting_info>, but works on an
individual object.

To set:

  $self->_setting_status('deffile', defaulted => 1);

To read the setting information:

  my $isdefault=_setting_status('deffile', 'defaulted');

=cut

{
  my %ValidSettings = map { $_ => 1 } qw(isdefault);

sub _setting_status {
  my $self=shift;
  my $setting=shift;

  if (@_ == 1) {
    # read a setting
    my $type=shift;
    unless ($ValidSettings{$type}) {
      internal_error("Unknown information type $type");
    }
    return $self->{cclapi}{settings}{$setting}->{$type};
  } elsif (not @_) {
    # erm ... ?
    internal_error("Insufficient arguments in call to _setting_info");
  } else {
    # set the stuff
    while (@_) {
      my $k=shift;
      my $v=shift;
      unless ($ValidSettings{$k}) {
        internal_error("Unknown information type $k");
      }
      $self->{cclapi}{settings}{$setting}->{$k}=$v;
    }
    if ($self->{cclapi}{settings}{$setting}{alias}) {
      # make an alias
      $self->{cclapi}{settings}{$self->{cclapi}{settings}{$setting}{alias}}=$self->{cclapi}{settings}{$setting};
    }
  }

  1;
}
}

=head4 _newsetting

Defines a new setting, including the callbacks, and registers it in
various places.

  _newsetting 'deffile' => '/SIMULATION CONTROL/EXECUTION CONTROL/RUN DEFINITION/Solver Input File';
  sub _ok_deffile { ... }
  sub _def_deffile { ... }

This call expects two routines to be defined, _ok_I<setting> to check
a value for correctness, and _def_I<setting> to get a default value.

=cut

# we could use AUTOLOAD here except that the compiler doesn't like
# eval "".
sub _newsetting {
  my $_self=shift;
  my ($s,$cp)=@_;
  my $f = '';
  unless( defined &$s ) {
     $f .= qq(
           sub $s {
                   my \$self=shift;
                   my \$c;
                  );
    if (defined $cp) {
      $f .= q(
        my $rv;
       );
    }

    if (defined $cp) {
      # we have a CCL database path, so we use that as the canonical
      # location

      $f .= qq(
               \$c=\$self->read_ccl_parm("$cp");
              );
    } else {
      # just store it
      $f .= qq(
               if (exists \$self->{settings}->{$s}) {
                 \$c=\$self->{settings}->{$s};
               } else {
                 \$c=\$self->_def_$s();
               }
              );
    }

    # now the error checking if we're in set mode, which is common
    $f .= qq(
           if (scalar \@_) {
             if (scalar(\@_) > 1) {
               internal_error("Too many arguments to $s");
             }
             my \$new=shift;
             if (\$self->_ok_$s(\$new)) {
    );

    # set differently, depending on whether or not we have a CCL path
    if (defined $cp) {
      $f .= qq(
                 \$self->set_ccl_parm("$cp",\$new);
              );
    } else {
      $f .= qq(
                 \$self->{settings}->{$s}=\$new;
      );
    }

    # finish off the error checking
    $f .= qq(
               } else {
                 _error("Invalid setting \$new for $s")
                   unless error_pending();
               }
             }
            );

    # and finally, end the function by returning the previous setting.
    $f .= qq(
             \$c;
           }
          );

    #warn "Setting $s from ", (defined $cp ? $cp : "(nowhere)"), ":\n$f";
    eval "$f";
    if ($@) {
      die "Error compiling code for $s: $@\n\nCode was:\n$f";
    }
  }

  # now, let's remember all this
  {
    my ($sc,$sa)=($s,'');
    if (defined $cp) {
      $sc=$cp;
      $sa="alias => \"$s\", ";
    }
    # it appears that the compiler stringifies coderefs, so the
    # previous try
    #     default => \\&_def_$s
    # fails when compiled, trying to call a routine
    # "UNKNOWN(0xsomething)".  It would be good to revert to this
    # behaviour in the future.
    eval qq{
            \$_self->_setting_info("$sc",
                          $sa
                          default => "_def_$s",
                          check => "_ok_$s"
                         );
           };
    if ($@) {
      die "Error recording information for $s: $@\n";
    }
  }

  1;
}


=head4 ccl_api_obj

This call returns the CCL API object which corresponds to the CCL
behind this job.  If used before this object has been set up, it will
give an error.  Note that different jobs use different instances of
the API.

=cut

sub ccl_api_obj {
  my $self=shift;

  unless (exists $self->{cclapi}->{apiobj}) {
    $self->{cclapi}->{apiobj} = CFX5::CCLI::new_api_object();
  }

  if (defined $self->{cclapi}->{apiobj}) {
    my $api=$self->{cclapi}->{apiobj};
    return $api;
  }
  internal_error("Use of internal CCL API object before it is defined");
}

=head4 set_validation_level

Sets the validation level in the CCL API instance.

=cut

sub set_validation_level {
  my $self=shift;
  my $api=$self->ccl_api_obj();
  my $lev=shift;

  $api->setValidationLevel($lev);
}

=head4 have_ccl_api_obj

  if ($self->have_ccl_api_obj()) {
    my $api=$self->ccl_api_obj();
    # use $api
  }

This is purely a performance tweak, to avoid instantiating the CCL API
if we only want to look for a setting in it.  It returns TRUE if the
object has already been instantiated, or FALSE if it has never been
set up, and therefore nothing can ever have been set in it.

=cut

sub have_ccl_api_obj {
  my $self=shift;
  return exists $self->{cclapi}->{apiobj} ? 1 : '';
}

=head4 have_setting

This function queries whether or not a given setting has been
specified.  Normally, one would simply use the function for the given
setting, but some of the defaults have side-effects (for example, the
C<rundir_obj>), so this can be useful.  Where possible, avoid the use
of this call, though.

Note that no checking is done on the validity of the requested
setting.

  if ($run->have_setting('workdir_obj')) {
    # do something with the run directory
  }

=cut

sub have_setting {
  my $self=shift;
  my $s=shift;
  (exists($self->{settings}{$s}) ? 1 : '')
    or $self->{cclapi}->{index}->{$s};
}

sub format_ccl_error {
  my $self=shift;
  CFX5::CCLI::format_ccl_error(@_);
}

1;
