package PROP::Conf;

use strict;
use base qw/Exporter/;
no strict 'refs';
use vars qw/%rdbms/;
our @EXPORT = qw/get_rdbms get_cnxn_conf set_rdbms set_cnxn_conf/;
use PROP::Constants;
use Hash::Util qw/lock_keys/;
use PROP::Exception::Configuration;
use PROP::Exception::IllegalArgument;

use PROP::Conf::MySQL;

my %param_names;

my $rdbms = undef;
my $cnxn_conf = undef;

sub set_rdbms {
    $rdbms = $_[0];
}

sub set_cnxn_conf {
    $cnxn_conf = $_[0];
}

sub get_rdbms {
    return $rdbms || $ENV{'PROP_RDBMS'} or
	die new PROP::Exception::Configuration("RDBMS is unascertainable");
}

sub get_cnxn_conf {
    return $cnxn_conf || $ENV{'PROP_CNXNCONF'} ||
	die new PROP::Exception::Configuration('connection configuration is unascertainable');
}

BEGIN {
    my @params = ('user', 'password', 'host', 'port', 'database');
    %param_names = map { ($_, 1) } @params;

    foreach my $param (@params) {
	*{'get_' . $param} =
	    sub {
		my ($self) = @_;
		return $self->get_param($param);
	    };
    }
}

sub new {
    my ($invocant, $rdbms, $cnxn_conf) = @_;

    unless($rdbms and $cnxn_conf) {
	$rdbms = get_rdbms();
	$cnxn_conf = get_cnxn_conf();
    }

    unless($rdbms) {
	my $msg = "PROP::Conf could not determine the RDBMS";
	die new PROP::Exception::Configuration($msg);
    }

    unless($cnxn_conf) {
	my $msg = "PROP::Conf could not determine a uri for configuration data";
	die new PROP::Exception::Configuration($msg);
    }

    my $subclass = 'PROP::Conf::' . $rdbms;

    my $self = bless({}, $subclass);
    $self->{-params} = {};
    $self->_read_configuration($cnxn_conf);

    lock_keys(%$self) if DEBUG;

    return $self;
}

sub _read_configuration {
    my ($self, $cnxn_conf) = @_;
    my $class = ref($self);
    my $msg = "class '$class' should have provided a _read_configuration method";
    die new PROP::Exception($msg);
}

sub get_dbd_name {
    my ($self) = @_;
    my $class = ref($self);
    my $msg = "class '$class' should have provided a get_dbd_name method";
    die new DBI::NIST::Exception($msg);
}

sub get_param {
    my ($self, $name) = @_;

    unless($param_names{$name}) {
	my $msg = "unknown param '$name'";
	die new PROP::Exception::IllegalArgument($msg);
    }

    return $self->{-params}{$name};
}

sub _set_param {
    my ($self, $name, $value) = @_;

    unless($param_names{$name}) {
	my $msg = "unknown param '$name'";
	die new PROP::Exception::IllegalArgument($msg);
    }

    $self->{-params}{$name} = $value;
}

1;

=head1 Name

PROP::Conf

=head1 Description

The PROP::Conf class is an abstract base class for various subclasses,
each of which acts as a configuration parser for a different RDBMS.
The subclass is responsible for all actual parsing, and the base class
provides the parsed information via the get_param method.

When writing a PROP::Conf subclass, the subclass must provide the
following methods: _read_configuration, get_dbd_name.  The
_read_configuration method must accept a single argument, from which
configuration information can be drawn.  The get_dbd_name method takes
no argument and returns the name of the DBD driver to be used,
e.g. 'mysql' in the case of the MySQL database.

=head1 Methods

=over

=item new

 $conf = PROP::Conf::Foo->new($cnxn_conf);

Presuming that PROP::Conf::Foo is a subclass of PPO::Conf, this method
creates an instance of class PROP::Conf::Foo, and parses the
configuration information specified by $cnxn_conf, extracting all of
the configuration information that was available to be used later for
establishing a database connection.  A user is not, however, apt to
ever directly instantiate or use a subclass of PROP::Conf.  Rather, one
will be created behind the scenes by the PROP::DBH class when a handle
is requested.

=item get_param

 $conf->get_param($name);

This method returns the value of the parameter specified by $name.  As
additional syntactic sugar, the following methods are also provided:
get_user, get_password, get_host, get_port, get_database.

=item set_rdbms

 PROP::Conf::set_rdbms($rdbms);

This static method is used to indicate which RDBMS is to be used, and
consequently dictates which subclass of PROP::Conf that PPO::DBH will
instantiate.

=item set_cnxn_conf

 PROP::Conf::set_cnxn_conf($cnxn_conf);

This static method is used to indicate from where connection
configuration information will be drawn.  It can be anything that the
subclass that corresponds to the argument passed to set_rdbms will
accept.

=back

=head1 Author

Andrew Gibbs (awgibbs@awgibbs.com,andrew.gibbs@nist.gov)

=head1 Legalese

This software was developed at the National Institute of Standards and
Technology by employees of the Federal Government in the course of
their official duties. Pursuant to title 17 Section 105 of the United
States Code this software is not subject to copyright protection and
is in the public domain. PROP is an experimental system. NIST
assumes no responsibility whatsoever for its use by other parties, and
makes no guarantees, expressed or implied, about its quality,
reliability, or any other characteristic. We would appreciate
acknowledgement if the software is used.  This software can be
redistributed and/or modified freely provided that any derivative
works bear some notice that they are derived from it, and any modified
versions bear some notice that they have been modified.
