# Copyright (C) 2001-2005, Parrot Foundation.

=pod

=head1 NAME

Parrot::Configure::Data - Configuration data container

=head1 SYNOPSIS

    use Parrot::Configure::Data;

    my $data = Parrot::Configure::Data->new;
    my @values = $data->get(@keys);
    $data->set($key1 => $value1, $key2 => $value2);
    $data->add($delimiter, $key1 => $value1, $key2 => $value2);
    my @keys = $data->keys;
    my $serialized = $data->dump(q{c}, q{*PConfig});
    $data->clean;
    $data->settrigger($key, $trigger, $cb);
    $data->gettriggers($key);
    $data->gettrigger($key, $trigger);
    $data->deltrigger($key, $trigger);

=head1 DESCRIPTION

This module provides methods by which other Parrot::Configure::* modules
can access configuration data.

The module supplies a constructor for Parrot::Configure::Data objects
and three kinds of accessors:

=over 4

=item 1  Main configuration data

=item 2  Triggers

=item 3  Data read from Perl 5's C<%Config> or Perl 5 special variables.

=back

=head1 USAGE

=cut

package Parrot::Configure::Data;

use strict;
use warnings;

use Data::Dumper ();

=head2 Constructor

=over 4

=item * C<new()>

=over 4

=item * Purpose

Basic object constructor.

=item * Arguments

None.

=item * Return Value

Parrot::Configure::Data object.

=back

=back

=cut

sub new {
    my $class = shift;

    my $self = {
        c        => {},
        triggers => {},
        p5       => {},
    };

    bless $self, ref $class || $class;
    return $self;
}

=head2 Methods for Main Configuration Data

=over 4

=item * C<get($key, ...)>

=over 4

=item * Purpose

Provides access to the values assigned to elements in the
Parrot::Configure object's main data structure.

=item * Arguments

List of elements found in the Parrot::Configure object's main data
structure.

=item * Return Value

List of values associated with corresponding arguments.

=back

=cut

sub get {
    my $self = shift;

    my $c = $self->{c};

    return @$c{@_};
}

=item * C<< set($key => $val, ...) >>

=over 4

=item * Purpose

Modifies or creates new values in the main part of the Parrot::Configure
object's data structure..

=item * Arguments

List of C<< key => value >> pairs.

=item * Return Value

Parrot::Configure::Data object.

=back

=cut

sub set {
    my $self = shift;

    my $verbose = defined $self->get('verbose') && $self->get('verbose') == 2;

    print "\nSetting Configuration Data:\n(\n" if $verbose;

    while ( my ( $key, $val ) = splice @_, 0, 2 ) {
        print "\t$key => ", defined($val) ? "'$val'" : 'undef', ",\n"
            if $verbose;
        $self->{c}{$key} = $val;

        foreach my $trigger ( $self->gettriggers($key) ) {
            print "\tcalling trigger $trigger for $key\n" if $verbose;
            my $cb = $self->gettrigger( $key, $trigger );

            &$cb( $key, $val );
        }
    }

    print ");\n" if $verbose;

    return $self;
}

=item * C<< add($delim, $key => $val, ...) >>

=over 4

=item * Purpose

Either creates a new key or appends to an existing key, with the previous/new
values joined together by C<$delim>.

=item * Arguments

Delimiter value followed by a list of C<< key => value >> pairs.

=item * Return Value

Parrot::Configure::Data object.

=back

=cut

sub add {
    my $self  = shift;
    my $delim = shift;

    while ( my ( $key, $val ) = splice @_, 0, 2 ) {
        my ($old) = $self->{c}{$key};
        if ( defined $old ) {
            $self->set( $key, "$old$delim$val" );
        }
        else {
            $self->set( $key, $val );
        }
    }

    return $self;
}

=item * C<keys()>

=over 4

=item * Purpose

Provides a list of names of elements in the Parrot::Configure object's
main data structure.

=item * Arguments

None.

=item * Return Value

List of elements in the Parrot::Configure object's main data structure.

=back

=cut

sub keys {
    my $self = shift;

    return keys %{ $self->{c} };
}

=item * C<get_PConfig()>

=over 4

=item * Purpose

Slurps in L<Parrot::Config> data from previous run of I<Configure.pl>.

=item * Arguments

None.

=item * Return Value

Reference to hash holding main Parrot::Configure data structure.

=back

=cut

sub get_PConfig {
    my $self = shift;
    my $res  = eval <<EVAL_CONFIG;
no strict;
use Parrot::Config;
\\%PConfig;
EVAL_CONFIG

    if ( not defined $res ) {
        die "You cannot use --step until you have completed the full configure process\n";
    }
    $self->{c} = $res;
}

=item * C<get_PConfig_Temp()>

=over 4

=item * Purpose

Slurps in L<Parrot::Config> temporary data from previous run of
Configure.pl.  Only to be used when running C<gen::makefiles> plugin.

=item * Arguments

None.

=item * Return Value

Reference to hash holding that part of the main Parrot::Configure data
structure holding temporary data.

=back

=cut

sub get_PConfig_Temp {
    my $self = shift;
    my $res  = eval <<EVAL_CONFIG_TEMP;
no strict;
use Parrot::Config::Generated;
\\%PConfig_Temp;
EVAL_CONFIG_TEMP

    if ( not defined $res ) {
        die "You cannot use --step until you have completed the full configure process\n";
    }
    $self->{c}{$_} = $res->{$_} for CORE::keys %$res;
}

=item * C<dump()>

=over 4

=item * Purpose

Provides a L<Data::Dumper> serialized string of the objects key/value pairs
suitable for being C<eval>ed.

=item * Arguments

Two scalar arguments:

=over 4

=item 1

Key in Parrot::Configure object's data structure which is being dumped.

=item 2

Name of the dumped structure.

=back

Example:

    $conf->data->dump(q{c}, q{*PConfig});
    $conf->data->dump(q{c_temp}, q{*PConfig_Temp});

=item * Return Value

String.

=back

=cut

# Data::Dumper supports Sortkeys since 2.12
# older versions will work but obviously not sorted
{
    if ( defined eval { Data::Dumper->can('Sortkeys') } ) {
        *dump = sub {
            my $self = shift;
            my ( $key, $structure ) = @_;
            Data::Dumper->new( [ $self->{$key} ], [$structure] )->Sortkeys(1)->Dump();
        };
    }
    else {
        *dump = sub {
            my $self = shift;
            my ( $key, $structure ) = @_;
            Data::Dumper->new( [ $self->{$key} ], [$structure] )->Dump();
        };
    }
}

=item * C<clean()>

=over 4

=item * Purpose

Deletes keys matching C</^TEMP_/> from the internal configuration store,
and copies them to a special store for temporary keys.
Keys using this naming convention are intended to be used only temporarily,
I<e.g.>  as file lists for Makefile generation.
Temporary keys are used B<only> to regenerate makefiles after configuration.

=item * Arguments

None.

=item * Return Value

Parrot::Configure::Data object.

=back

=back

=cut

sub clean {
    my $self = shift;

    $self->{c_temp}{$_} = delete $self->{c}{$_} for grep { /^TEMP_/ } CORE::keys %{ $self->{c} };

    return $self;
}

=head2 Triggers

=over 4

=item * C<settrigger($key, $trigger, $cb)>

=over 4

=item * Purpose

Set a callback on C<$key> named C<$trigger>.  Multiple triggers can be set on a
given key.  When the key is set via C<set> or C<add> then all callbacks that
are defined will be called.  Triggers are passed the key and value that was set
after it has been changed.

=item * Arguments

Accepts a key name, a trigger name, & a C<CODE> ref.

=item * Return Value

Parrot::Configure::Data object.

=back

=cut

sub settrigger {
    my ( $self, $key, $trigger, $cb ) = @_;

    return unless defined $key and defined $trigger and defined $cb;

    my $verbose = defined $self->get('verbose') && $self->get('verbose') == 2;

    print "Setting trigger $trigger on configuration key $key\n",
        if $verbose;

    $self->{triggers}{$key}{$trigger} = $cb;

    return $self;
}

=item * C<gettriggers($key)>

=over 4

=item * Purpose

Get the names of all triggers set for C<$key>.

=item * Arguments

String holding single key name.

=item * Return Value

List of triggers set for that key.

=back

=cut

sub gettriggers {
    my ( $self, $key ) = @_;

    return unless defined $self->{triggers}{$key};

    my $verbose = defined $self->get('verbose') && $self->get('verbose') == 2;

    print "Looking up all triggers on configuration key $key\n"
        if $verbose;

    return CORE::keys %{ $self->{triggers}{$key} };
}

=item * C<gettrigger($key, $trigger)>

=over 4

=item * Purpose

Get the callback set for C<$key> under the name C<$trigger>

=item * Arguments

Accepts a key name & a trigger name.

=item * Return Value

C<CODE> ref.

=back

=cut

sub gettrigger {
    my ( $self, $key, $trigger ) = @_;

    return
        unless defined $self->{triggers}{$key}
            and defined $self->{triggers}{$key}{$trigger};

    my $verbose = defined $self->get('verbose') && $self->get('verbose') == 2;

    print "Looking up trigger $trigger on configuration key $key\n"
        if $verbose;

    return $self->{triggers}{$key}{$trigger};
}

=item * C<deltrigger($key, $trigger)>

=over 4

=item * Purpose

Removes the trigger on C<$key> named by C<$trigger>

=item * Arguments

Accepts a key name & a trigger name.

=item * Return Value

Parrot::Configure::Data object.

=back

=cut

sub deltrigger {
    my ( $self, $key, $trigger ) = @_;

    return
        unless defined $self->{triggers}{$key}
            and defined $self->{triggers}{$key}{$trigger};

    my $verbose = defined $self->get('verbose') && $self->get('verbose') == 2;

    print "Removing trigger $trigger on configuration key $key\n"
        if $verbose;

    delete $self->{triggers}{$key}{$trigger};

    return $self;
}

=back

=head2 Methods for Perl 5 Data


=over 4

=item * C<get_p5($key, ...)>

=over 4

=item * Purpose

Retrieve data originally derived from the Perl 5 environment during
configuration step C<init::defaults> and stored in a special part of the
Parrot::Configure::Data object.

=item * Arguments

List of elements found in the Perl 5-related part of the
Parrot::Configure object's data structure.

=item * Return Value

List of values associated with corresponding arguments.

=item * Note

Once data from Perl 5's C<%Config> or special variables has been stored
in configuration step C<init::defaults>, C<%Config> and the special
variables should not be further accessed.  Use this method instead.

=back

=cut

sub get_p5 {
    my $self = shift;

    my $p5 = $self->{p5};

    return @$p5{@_};
}

=item * C<< set_p5($key => $val, ...) >>

=over 4

=item * Purpose

Looks up values from either (a) the C<%Config>, located in Config.pm
and imported via C<use Config;>, associated with the instance of Perl
(C<$^X>) used to run I<Configure.pl> and assigns those values to a
special part of the Parrot::Configure::Data object.

=item * Arguments

List of C<< key => value >> pairs.  If the key being set is from
C<%Config>, the corresponding value should have the same name.  If,
however, the key being set is a Perl 5 special variable (I<e.g.>,
C<%^O>), the corresponding value should be the 'English' name of that
special variable as documented in L<perlvar> (less the initial C<$>, of
course).

=item * Return Value

Parrot::Configure::Data object.

=item * Examples

=item * Note

This method should B<only> be used in configuration step
C<init::defaults>.  It is B<not> the method used to assign values to the
main Parrot::Configure data structure; use C<set()> (above) instead.

=back

=cut

sub set_p5 {
    my $self = shift;

    my $verbose = defined $self->get('verbose') && $self->get('verbose') == 2;

    print "\nSetting Configuration Data:\n(\n" if $verbose;

    while ( my ( $key, $val ) = splice @_, 0, 2 ) {
        print "\t$key => ", defined($val) ? "'$val'" : 'undef', ",\n"
            if $verbose;
        $self->{p5}{$key} = $val;

    }

    print ");\n" if $verbose;

    return $self;
}

=item * C<keys_p5()>

=over 4

=item * Purpose

Provides a list of names of elements in the Parrot::Configure object's
main data structure.

=item * Arguments

None.

=item * Return Value

List of elements in the part of the Parrot::Configure object's data
structure storing Perl 5 configuration data.

=back

=back

=cut

sub keys_p5 {
    my $self = shift;

    return CORE::keys %{ $self->{p5} };
}

=head1 CREDITS

Based largely on code written by Brent Royal-Gordon C<brent@brentdax.com>.

=head1 AUTHOR

Joshua Hoblitt C<jhoblitt@cpan.org>

=head1 SEE ALSO

F<docs/configuration.pod>, L<Parrot::Configure>, L<Parrot::Configure::Step>,
L<Parrot::Configure::Step>

=cut

1;

# Local Variables:
#   mode: cperl
#   cperl-indent-level: 4
#   fill-column: 100
# End:
# vim: expandtab shiftwidth=4:
