use v5.14;
use warnings;

=head1 NAME

Attean::API::Expression - SPARQL expressions

=head1 VERSION

This document describes Attean::API::Expression version 0.030

=head1 DESCRIPTION

The Attean::API::Expression role defines a common API for SPARQL expressions
consisting of logical, numeric, and function operators, constant terms, and
variables. Expressions may be evaluated in the context of a
L<Attean::API::Result> object, and either return a L<Attean::API::Term> object
or throw a type error exception.

=head1 ROLES

This role consumes the L<Attean::API::DirectedAcyclicGraph> role which provide the following methods:

=over 4

=item C<< is_leaf >>

=item C<< walk( prefix => \&pre_cb, postfix => \&pre_cb ) >>

=item C<< cover( prefix => \&pre_cb, postfix => \&pre_cb ) >>

=back

and the following attributes:

=over 4

=item C<< children >>

=back

=head1 ATTRIBUTES

The following attributes exist:

=over 4

=item C<< operator >>

A string indicating the expression operator (e.g. C<'+'> or C<'||'>).

=back

=head1 REQUIRED METHODS

The following methods are required by the L<Attean::API::Expression> role:

=over 4

=item C<< as_string >>

Returns a string serialization of the expression object.

=back

=cut

package Attean::API::Expression 0.030 {
	use Types::Standard qw(Str);

	use Moo::Role;

	with 'Attean::API::DirectedAcyclicGraph', 'Attean::API::UnionScopeVariables';
	
	has 'operator' => (is => 'ro', isa => Str, required => 1);
	requires 'is_stable'; # is stable for sorting (won't change across evaluations)
	requires 'unaggregated_variables';
	requires 'as_string';
	requires 'as_sparql';
	
	sub BUILD {}
	if ($ENV{ATTEAN_TYPECHECK}) {
		around 'BUILD' => sub {
			my $orig	= shift;
			my $self	= shift;
			$self->$orig(@_);
			my $name	= ref($self);
			$name		=~ s/^.*://;
			if ($self->can('arity')) {
				my $arity		= $self->arity;
				if (defined($arity)) {
					my $children	= $self->children;
					my $size		= scalar(@$children);
					unless ($size == $arity) {
						die "${name} expression construction with bad number of children (expected $arity, but got $size)";
					}
				}
			}
		}
	}
}

package Attean::API::UnaryExpression 0.030 {
	use AtteanX::SPARQL::Constants;
	use AtteanX::SPARQL::Token;

	use Moo::Role;
	
	with 'Attean::API::Expression', 'Attean::API::UnaryQueryTree';
	with 'Attean::API::SPARQLSerializable';
	
	sub as_string {
		my $self	= shift;
		my ($data)	= @{ $self->children };
		return sprintf("%s(%s)", $self->operator, $data->as_string);
	}

	my %ops	= (
		'!'	=> AtteanX::SPARQL::Token->fast_constructor( BANG, -1, -1, -1, -1, ['!'] ),
		'-'	=> AtteanX::SPARQL::Token->fast_constructor( MINUS, -1, -1, -1, -1, ['-'] ),
		'+'	=> AtteanX::SPARQL::Token->fast_constructor( PLUS, -1, -1, -1, -1, ['+'] ),
	);

	sub unaggregated_variables {
		my $self	= shift;
		my ($child)	= @{ $self->children };
		return $child->unaggregated_variables;
	}
	
	sub sparql_tokens {
		my $self	= shift;
		my $op		= $ops{$self->operator} // die "No operator found in Attean::API::UnaryExpression->sparql_tokens";

		my @tokens;
		push(@tokens, $op);
		foreach my $t (@{ $self->children }) {
			push(@tokens, $t->sparql_tokens->elements);
		}
		return Attean::ListIterator->new( values => \@tokens, item_type => 'AtteanX::SPARQL::Token' );
	}
}

package Attean::API::BinaryExpression 0.030 {
	use AtteanX::SPARQL::Constants;
	use AtteanX::SPARQL::Token;

	use Moo::Role;
	
	with 'Attean::API::Expression', 'Attean::API::BinaryQueryTree';
	with 'Attean::API::SPARQLSerializable';

	sub as_string {
		my $self	= shift;
		my ($lhs, $rhs)	= @{ $self->children };
		return sprintf("(%s %s %s)", $lhs->as_string, $self->operator, $rhs->as_string);
	}

	sub unaggregated_variables {
		my $self	= shift;
		return map { $_->unaggregated_variables } @{ $self->children };
	}
	
	my %ops	= (
		'-'		=> AtteanX::SPARQL::Token->fast_constructor( MINUS, -1, -1, -1, -1, ['-'] ),
		'+'		=> AtteanX::SPARQL::Token->fast_constructor( PLUS, -1, -1, -1, -1, ['+'] ),
		'*'		=> AtteanX::SPARQL::Token->fast_constructor( STAR, -1, -1, -1, -1, ['*'] ),
		'/'		=> AtteanX::SPARQL::Token->fast_constructor( SLASH, -1, -1, -1, -1, ['/'] ),
		'<'		=> AtteanX::SPARQL::Token->fast_constructor( LT, -1, -1, -1, -1, ['<'] ),
		'>'		=> AtteanX::SPARQL::Token->fast_constructor( GT, -1, -1, -1, -1, ['>'] ),
		'<='	=> AtteanX::SPARQL::Token->fast_constructor( LE, -1, -1, -1, -1, ['<='] ),
		'>='	=> AtteanX::SPARQL::Token->fast_constructor( GE, -1, -1, -1, -1, ['>='] ),
		'!='	=> AtteanX::SPARQL::Token->fast_constructor( NOTEQUALS, -1, -1, -1, -1, ['!='] ),
		'='		=> AtteanX::SPARQL::Token->fast_constructor( EQUALS, -1, -1, -1, -1, ['='] ),
		'&&'	=> AtteanX::SPARQL::Token->fast_constructor( ANDAND, -1, -1, -1, -1, ['&&'] ),
		'||'	=> AtteanX::SPARQL::Token->fast_constructor( OROR, -1, -1, -1, -1, ['||'] ),
	);
	
	sub sparql_tokens {
		my $self	= shift;
		my $op		= $ops{$self->operator} // die "No operator found in Attean::API::BinaryExpression->sparql_tokens";

		my @tokens;
		foreach my $t (@{ $self->children }) {
			push(@tokens, $t->sparql_tokens->elements);
			push(@tokens, $op);
		}
		pop(@tokens);
		return Attean::ListIterator->new( values => \@tokens, item_type => 'AtteanX::SPARQL::Token' );
	}
}

package Attean::API::NaryExpression 0.030 {
	use Moo::Role;
	with 'Attean::API::Expression', 'Attean::API::QueryTree';
	sub as_string {
		my $self	= shift;
		my @children	= map { $_->as_string } @{ $self->children };
		return sprintf("%s(%s)", $self->operator, join(', ', @children));
	}

	sub as_sparql {
		my $self	= shift;
		return $self->as_string;
	}

	sub unaggregated_variables {
		my $self	= shift;
		return map { $_->unaggregated_variables } @{ $self->children };
	}
}

package Attean::API::AggregateExpression 0.030 {
	use Moo::Role;
	requires 'operator';
	requires 'scalar_vars';
	with 'Attean::API::Expression', 'Attean::API::DirectedAcyclicGraph';

	sub as_string {
		my $self	= shift;
		my @children	= map { $_->as_string } @{ $self->children };
		return sprintf("%s(%s)", $self->operator, join(', ', @children));
	}

	sub as_sparql {
		my $self	= shift;
		return $self->as_string;
	}

	sub unaggregated_variables {
		return;
	}
}

1;

__END__

=head1 BUGS

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

=head1 SEE ALSO



=head1 AUTHOR

Gregory Todd Williams  C<< <gwilliams@cpan.org> >>

=head1 COPYRIGHT

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

=cut