# RDF::Trine::VariableBindings
# -----------------------------------------------------------------------------

=head1 NAME

RDF::Trine::VariableBindings - Variable bindings

=head1 VERSION

This document describes RDF::Trine::VariableBindings version 1.017

=head1 SYNOPSIS

  use RDF::Trine qw(literal);
  use RDF::Trine::VariableBindings;
  my $vb = RDF::Trine::VariableBindings->new( {} );
  $vb->set( foo => literal("bar") );
  $vb->set( baz => literal("blee") );
  $vb->variables; # qw(foo baz)
  
  my $x = RDF::Trine::VariableBindings->new( { foo => literal("bar") } );
  $x->set( greeting => literal("hello") );

  my $j = $vb->join( $x ); # { foo => "bar", baz => "blee", greeting => "hello" }

  my @keys = qw(baz greeting);
  my $p = $j->project( @keys ); # { baz => "blee", greeting => "hello" }
  print $p->{greeting}->literal_value; # "hello"

=head1 DESCRIPTION

RDF::Trine::VariableBindings objects provide a mapping from variable names to
RDF::Trine::Node objects. The objects may be used as a hash reference, with
variable names used as hash keys.

=head1 METHODS

=over 4

=cut

package RDF::Trine::VariableBindings;

use strict;
use warnings;
use overload	'""'	=> sub { $_[0]->as_string };

my %VB_LABELS;

use Scalar::Util qw(blessed refaddr);

######################################################################

our ($VERSION);
BEGIN {
	$VERSION	= '1.017';
}

######################################################################

=item C<< new ( \%bindings ) >>

=cut

sub new {
	my $class		= shift;
	my $bindings	= shift;
	my $self		= bless( { %$bindings }, $class );
	
	if (blessed($bindings) and $bindings->isa('RDF::Trine::VariableBindings')) {
		my $addr	= refaddr($bindings);
		if (ref($VB_LABELS{ $addr })) {
			$VB_LABELS{ refaddr($self) }	= { %{ $VB_LABELS{ $addr } } };
		}
	}
	
	return $self;
}

=item C<< set ( $variable_name => $node ) >>

=cut

sub set {
	my $self	= shift;
	my $name	= shift;
	my $node	= shift;
	$self->{ $name }	= $node;
}

=item C<< join ( $row ) >>

Returns a new VariableBindings object based on the join of this object and C<< $row >>.
If the two variable binding objects cannot be joined, returns undef.

=cut

sub join {
	my $self	= shift;
	my $class	= ref($self);
	my $rowb	= shift;
	
	my %keysa;
	my @keysa	= keys %$self;
	@keysa{ @keysa }	= (1) x scalar(@keysa);
	my @shared	= grep { exists $keysa{ $_ } } (keys %$rowb);
	foreach my $key (@shared) {
		my $val_a	= $self->{ $key };
		my $val_b	= $rowb->{ $key };
		next unless (defined($val_a) and defined($val_b));
		my $equal	= (refaddr($val_a) == refaddr($val_b)) || ($val_a == $val_b) || $val_a->equal( $val_b );
		unless ($equal) {
			return;
		}
	}
	
	my $row	= { (map { $_ => $self->{$_} } grep { defined($self->{$_}) } keys %$self), (map { $_ => $rowb->{$_} } grep { defined($rowb->{$_}) } keys %$rowb) };
	my $joined	= $class->new( $row );
	$joined->copy_labels_from( $self );
	$joined->copy_labels_from( $rowb );
	
	return $joined;
}

=item C<< variables >>

=cut

sub variables {
	my $self	= shift;
	return (keys %$self);
}

=item C<< project ( @keys ) >>

Returns a new binding with values for only the keys listed.

=cut

sub project {
	my $self	= shift;
	my $class	= ref($self);
	my @keys	= @_;
	my %data	= map { $_ => $self->{ $_ } } @keys;
	my $p		= $class->new( \%data );
	
	my $addr	= refaddr($self);
	if (ref($VB_LABELS{ $addr })) {
		$VB_LABELS{ refaddr($p) }	= { %{ $VB_LABELS{ $addr } } };
	}
	
	return $p;
}

=item C<< as_string >>

Returns a string representation of the variable bindings.

=cut

sub as_string {
	my $self	= shift;
	my @keys	= sort keys %$self;
	my $string	= sprintf('{ %s }', CORE::join(', ', map { CORE::join('=', $_, ($self->{$_}) ? $self->{$_}->as_string : '()') } (@keys)));
	return $string;
}

=item C<< label ( $label => $value ) >>

Sets the named C<< $label >> to C<< $value >> for this variable bindings object.
If no C<< $value >> is given, returns the current label value, or undef if none
exists.

=cut

sub label {
	my $self	= shift;
	my $addr	= refaddr($self);
	my $label_name	= shift;
	if (@_) {
		my $value	= shift;
		$VB_LABELS{ $addr }{ $label_name }	= $value;
	}
	
	my $labels	= $VB_LABELS{ $addr };
	if (ref($labels)) {
		my $value	= $labels->{ $label_name };
		return $value;
	} else {
		return;
	}
}

=item C<< copy_labels_from ( $vb ) >>

Copies the labels from C<< $vb >>, adding them to the labels for this object.

=cut

sub copy_labels_from {
	my $self		= shift;
	my $rowa		= shift;
	my $self_labels	= $VB_LABELS{ refaddr($self) };
	my $a_labels	= $VB_LABELS{ refaddr($rowa) };
	if ($self_labels or $a_labels) {
		$self_labels	||= {};
		$a_labels		||= {};
		my %new_labels	= ( %$self_labels, %$a_labels );
		
		if (exists $new_labels{'origin'}) {
			my %origins;
			foreach my $o (@{ $self_labels->{'origin'} || [] }) {
				$origins{ $o }++;
			}
			foreach my $o (@{ $a_labels->{'origin'} || [] }) {
				$origins{ $o }++;
			}
			$new_labels{'origin'}	= [ keys %origins ];
		}
		
		$VB_LABELS{ refaddr($self) }	= \%new_labels;
	}
}

sub _labels {
	my $self	= shift;
	my $addr	= refaddr($self);
	my $labels	= $VB_LABELS{ $addr };
	return $labels;
}

sub DESTROY {
	my $self	= shift;
	my $addr	= refaddr( $self );
	delete $VB_LABELS{ $addr };
	return;
}

1;

__END__

=back

=head1 BUGS

Please report any bugs or feature requests to through the GitHub web interface
at L<https://github.com/kasei/perlrdf/issues>.

=head1 AUTHOR

 Gregory Todd Williams <gwilliams@cpan.org>

=head1 COPYRIGHT

Copyright (c) 2006-2012 Gregory Todd Williams. This
program is free software; you can redistribute it and/or modify it under
the same terms as Perl itself.

=cut
