# RDF::Query::Expression::Binary
# -----------------------------------------------------------------------------

=head1 NAME

RDF::Query::Expression::Binary - Algebra class for binary expressions

=head1 VERSION

This document describes RDF::Query::Expression::Binary version 2.919.

=cut

package RDF::Query::Expression::Binary;

use strict;
use warnings;
no warnings 'redefine';
use base qw(RDF::Query::Expression);

use Data::Dumper;
use Log::Log4perl;
use Scalar::Util qw(blessed);
use Carp qw(carp croak confess);

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

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

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

=head1 METHODS

Beyond the methods documented below, this class inherits methods from the
L<RDF::Query::Expression> class.

=over 4

=cut

=item C<< sse >>

Returns the SSE string for this algebra expression.

=cut

sub sse {
	my $self	= shift;
	my $context	= shift;
	
	return sprintf(
		'(%s %s %s)',
		$self->op,
		map { $_->sse( $context ) } $self->operands,
	);
}

=item C<< as_sparql >>

Returns the SPARQL string for this algebra expression.

=cut

sub as_sparql {
	my $self	= shift;
	my $context	= shift;
	my $indent	= shift;
	my $op		= $self->op;
 	$op			= '=' if ($op eq '==');
	return sprintf("(%s $op %s)", map { $_->as_sparql( $context, $indent ) } $self->operands);
}

=item C<< evaluate ( $query, \%bound ) >>

Evaluates the expression using the supplied bound variables.
Will return a RDF::Query::Node object.

=cut

sub evaluate {
	my $self	= shift;
	my $query	= shift;
	my $bound	= shift;
	my $l		= Log::Log4perl->get_logger("rdf.query.expression.binary");
	my $op		= $self->op;
	my @operands	= $self->operands;
	my ($lhs, $rhs)	= map {
						throw RDF::Query::Error::ExecutionError ( -text => "error in evaluating operands to binary $op" )
							unless (blessed($_));
						$_->isa('RDF::Query::Algebra')
							? $_->evaluate( $query, $bound, @_ )
							: ($_->isa('RDF::Trine::Node::Variable'))
								? $bound->{ $_->name }
								: $_
	} @operands;
	
	$l->debug("Binary Operator '$op': " . Dumper($lhs, $rhs));
	
### This does overloading of infix<+> on literal values to perform string concatenation
# 	if ($op eq '+') {
# 		if (blessed($lhs) and $lhs->isa('RDF::Query::Node::Literal') and blessed($rhs) and $rhs->isa('RDF::Query::Node::Literal')) {
# 			if (not($lhs->has_datatype) and not($rhs->has_datatype)) {
# 				my $value	= $lhs->literal_value . $rhs->literal_value;
# 				return RDF::Query::Node::Literal->new( $value );
# 			}
# 		}
# 	}
	
	if ($op =~ m#^[-+/*]$#) {
		if (blessed($lhs) and blessed($rhs) and $lhs->isa('RDF::Query::Node::Literal') and $rhs->isa('RDF::Query::Node::Literal') and $lhs->is_numeric_type and $rhs->is_numeric_type) {
			my $type	= $self->promote_type( $op, $lhs->literal_datatype, $rhs->literal_datatype );
			my $value;
			if ($op eq '+') {
				my $lhsv	= $lhs->numeric_value;
				my $rhsv	= $rhs->numeric_value;
				if (defined($lhsv) and defined($rhsv)) {
					$value		= $lhsv + $rhsv;
				} else {
					throw RDF::Query::Error::ComparisonError -text => "Cannot evaluate infix:<+> on non-numeric types";
				}
			} elsif ($op eq '-') {
				my $lhsv	= $lhs->numeric_value;
				my $rhsv	= $rhs->numeric_value;
				if (defined($lhsv) and defined($rhsv)) {
					$value		= $lhsv - $rhsv;
				} else {
					throw RDF::Query::Error::ComparisonError -text => "Cannot evaluate infix:<-> on non-numeric types";
				}
			} elsif ($op eq '*') {
				my $lhsv	= $lhs->numeric_value;
				my $rhsv	= $rhs->numeric_value;
				if (defined($lhsv) and defined($rhsv)) {
					$value		= $lhsv * $rhsv;
				} else {
					throw RDF::Query::Error::ComparisonError -text => "Cannot evaluate infix:<*> on non-numeric types";
				}
			} elsif ($op eq '/') {
				my $lhsv	= $lhs->numeric_value;
				my $rhsv	= $rhs->numeric_value;
				
				my ($lt, $rt)	= ($lhs->literal_datatype, $rhs->literal_datatype);
				if ($lt eq $rt and $lt eq 'http://www.w3.org/2001/XMLSchema#integer') {
					$type	= 'http://www.w3.org/2001/XMLSchema#decimal';
				}
				
				if (defined($lhsv) and defined($rhsv)) {
					if ($rhsv == 0) {
						throw RDF::Query::Error::FilterEvaluationError -text => "Illegal division by zero";
					}
					$value		= $lhsv / $rhsv;
				} else {
					throw RDF::Query::Error::ComparisonError -text => "Cannot evaluate infix:</> on non-numeric types";
				}
			} else {
				throw RDF::Query::Error::ExecutionError -text => "Unrecognized binary operator '$op'";
			}
			return RDF::Query::Node::Literal->new( $value, undef, $type, 1 );
		} else {
			throw RDF::Query::Error::ExecutionError -text => "Numeric binary operator '$op' with non-numeric data";
		}
	} elsif ($op =~ m#^([<>]=?)|!?=$#) {
		my @types	= qw(RDF::Query::Node::Literal RDF::Query::Node::Resource RDF::Query::Node::Blank);
		
		if ($op =~ /[<>]/) {
			# if it's a relational operation other than equality testing,
			# the two nodes must be of the same type.
			my $ok		= 0;
			foreach my $type (@types) {
				$ok	||= 1 if ($lhs->isa($type) and $rhs->isa($type));
			}
			if (not($ok) and not($RDF::Query::Node::Literal::LAZY_COMPARISONS)) {
				throw RDF::Query::Error::TypeError -text => "Attempt to compare two nodes of different types.";
			}
		}
		
		my $bool;
		if ($op eq '<') {
			$bool	= ($lhs < $rhs);
		} elsif ($op eq '<=') {
			$bool	= ($lhs <= $rhs);
		} elsif ($op eq '>') {
			$bool	= ($lhs > $rhs);
		} elsif ($op eq '>=') {
			$bool	= ($lhs >= $rhs);
		} elsif ($op eq '==') {
			$bool	= ($lhs == $rhs);
		} elsif ($op eq '!=') {
			$bool	= ($lhs != $rhs);
		} else {
			throw RDF::Query::Error::ExecutionError -text => "Unrecognized binary operator '$op'";
		}
		
		my $value	= ($bool) ? 'true' : 'false';
		$l->debug("-> $value");
		return RDF::Query::Node::Literal->new( $value, undef, 'http://www.w3.org/2001/XMLSchema#boolean' );
	} else {
		$l->logdie("Unknown operator: $op");
	}
}

my $xsd				= 'http://www.w3.org/2001/XMLSchema#';
my %integer_types	= map { join('', $xsd, $_) => 1 } qw(nonPositiveInteger nonNegativeInteger positiveInteger negativeInteger short unsignedShort byte unsignedByte long unsignedLong);
my %rel	= (
	"${xsd}integer"				=> 0,
	"${xsd}int"					=> 1,
	"${xsd}unsignedInt"			=> 2,
	"${xsd}nonPositiveInteger"	=> 3,
	"${xsd}nonNegativeInteger"	=> 4,
	"${xsd}positiveInteger"		=> 5,
	"${xsd}negativeInteger"		=> 6,
	"${xsd}short"				=> 7,
	"${xsd}unsignedShort"		=> 8,
	"${xsd}byte"				=> 9,
	"${xsd}unsignedByte"		=> 10,
	"${xsd}long"				=> 11,
	"${xsd}unsignedLong"		=> 12,
	"${xsd}decimal"				=> 13,
	"${xsd}float"				=> 14,
	"${xsd}double"				=> 15,
);

=item C<< promote_type ( $op, $lhs_datatype, $rhs_datatype ) >>

Returns the XSD type URI (as a string) for the resulting value of performing the
supplied operation on arguments of the indicated XSD types.

=cut

sub promote_type {
	my $self	= shift;
	my $op		= shift;
	no warnings 'uninitialized';
	my @types	= sort { $rel{$b} <=> $rel{$a} } @_;
	
	my $type	= $types[0];
	$type		= "${xsd}integer" if ($integer_types{ $type });
	return $type;
}

1;

__END__

=back

=head1 AUTHOR

 Gregory Todd Williams <gwilliams@cpan.org>

=cut
