use v5.14;
use warnings;

=head1 NAME

Attean::API - Utility package for loading all Attean role packages.

=head1 VERSION

This document describes Attean::API version 0.035

=head1 SYNOPSIS

  use v5.14;
  use Attean;

=head1 DESCRIPTION

This is a utility package that will load all the Attean-related Moo roles
in the Attean::API namespace.

=head1 METHODS

=over 4

=cut

package Attean::API::ResultOrTerm 0.035 {
	use Moo::Role;
}

package Attean::API::BlankOrIRI 0.035 {
	use Moo::Role;
	with 'Attean::API::Term', 'Attean::API::BlankOrIRIOrTriple';
}

package Attean::API::BlankOrIRIOrTriple 0.035 {
	use Moo::Role;
}

package Attean::API::TermOrTriple 0.035 {
	use Moo::Role;
}

package Attean::API::TermOrVariable 0.035 {
	use Scalar::Util qw(blessed);
	use Sub::Install;
	use Sub::Util qw(set_subname);

	use Moo::Role;

	with 'Attean::API::SPARQLSerializable';

	sub equals {
		my ($a, $b)	= @_;
		return ($a->as_string eq $b->as_string);
	}

	sub is_bound {
	  my $self = shift;
	  return (! $self->does('Attean::API::Variable'));
	}
	
	sub apply_binding {
		my $self	= shift;
		my $class	= ref($self);
		my $bind	= shift;
		if ($self->does('Attean::API::Variable')) {
			my $name	= $self->value;
			my $replace	= $bind->value($name);
			if (defined($replace) and blessed($replace)) {
				return $replace;
			} else {
				return $self;
			}
		} else {
			return $self;
		}
	}

	BEGIN {
		my %types	= (
			variable	=> 'Variable',
			blank		=> 'Blank',
			literal		=> 'Literal',
			resource	=> 'IRI',
			iri			=> 'IRI',
		);
		while (my ($name, $role) = each(%types)) {
			my $method	= "is_$name";
			my $code	= sub { return shift->does("Attean::API::$role") };
			Sub::Install::install_sub({
				code	=> set_subname($method, $code),
				as		=> $method
			});
		}
	}
}

package Attean::API::TermOrVariableOrTriplePattern 0.035 {
	use Scalar::Util qw(blessed);
	use Sub::Install;
	use Sub::Util qw(set_subname);

	use Moo::Role;

	with 'Attean::API::SPARQLSerializable';

	sub is_bound {
	  my $self = shift;
	  return (! $self->does('Attean::API::Variable'));
	}
	
	sub apply_binding {
		my $self	= shift;
		my $class	= ref($self);
		my $bind	= shift;
		if ($self->does('Attean::API::Variable')) {
			my $name	= $self->value;
			my $replace	= $bind->value($name);
			if (defined($replace) and blessed($replace)) {
				return $replace;
			} else {
				return $self;
			}
		} else {
			return $self;
		}
	}

	BEGIN {
		my %types	= (
			variable	=> 'Variable',
			blank		=> 'Blank',
			literal		=> 'Literal',
			resource	=> 'IRI',
			iri			=> 'IRI',
			pattern		=> 'TriplePattern'
		);
		while (my ($name, $role) = each(%types)) {
			my $method	= "is_$name";
			my $code	= sub { return shift->does("Attean::API::$role") };
			Sub::Install::install_sub({
				code	=> set_subname($method, $code),
				as		=> $method
			});
		}
	}
}

package Attean::Mapper 0.035 {
	use Moo::Role;
	requires 'map'; # my $that = $object->map($this)
}

package Attean::API::Variable 0.035 {
	use AtteanX::SPARQL::Constants;
	use AtteanX::SPARQL::Token;

	use Moo::Role;

	with 'Attean::API::TermOrVariable';

=item C<< as_string >>

Returns a string representation of the variable.'

=cut

	sub as_string {
		my $self	= shift;
		return '?' . $self->value;
	}

	sub sparql_tokens {
		my $self	= shift;
		my $t	= AtteanX::SPARQL::Token->fast_constructor( VAR, -1, -1, -1, -1, [$self->value] );
		return Attean::ListIterator->new( values => [$t], item_type => 'AtteanX::SPARQL::Token' );
	}
	
}

package Attean::API::CanonicalizingBindingSet 0.035 {
	use Attean::RDF;

	use Moo::Role;
	use Scalar::Util qw(blessed);
	use namespace::clean;

	with 'MooX::Log::Any';
	requires 'elements';

	sub canonical_set {
		my $self	= shift;
		my ($set)	= $self->canonical_set_with_mapping;
		return $set;
	}
	
	sub canonical_set_with_mapping {
		my $self	= shift;
		my @t		= $self->elements;
		my @tuples	= map { [ $_->tuples_string, $_, {} ] } @t;
		my $replacements	= 0;
		foreach my $p (@tuples) {
			my ($str, $t)	= @$p;
			foreach my $pos ($t->variables) {
				my $term	= $t->value($pos);
				my $tstr	= $term->ntriples_string;
				if ($term->does('Attean::API::Blank') or $term->does('Attean::API::Variable')) {
					$str	=~ s/\Q$tstr\E/~/;
					$str	.= "#$tstr";
					$p->[2]{$pos}	= $tstr;
					$replacements++;
					$p->[0]	= $str;
				}
			}
		}
	
		@tuples	= sort { $a->[0] cmp $b->[0] } @tuples;
		my $counter	= 1;
		my %mapping;
		foreach my $i (0 .. $#tuples) {
			my $p		= $tuples[$i];
			my ($str, $t)	= @$p;
			my $item_class	= ref($t);
			my ($next, $last)	= ('')x2;
			$last	= $tuples[$i-1][0] if ($i > 0);
			$next	= $tuples[$i+1][0] if ($i < $#tuples);
			next if ($str eq $last or $str eq $next);
			foreach my $pos (reverse $t->variables) {
				if (defined(my $tstr = $p->[2]{$pos})) {
					$tstr	=~ /^([?]|_:)([^#]+)$/;
					my $prefix	= $1;
					my $name	= $2;
					my $key		= "$prefix$name";
					delete $p->[2]{$pos};
					my $id		= (exists($mapping{$key})) ? $mapping{$key}{id} : sprintf("v%03d", $counter++);
					my $type	= ($prefix eq '?' ? 'variable' : 'blank');
					$mapping{ $key }	= { id => $id, prefix => $prefix, type => $type };
					my %t		= $p->[1]->mapping;
					$t{ $pos }	= ($type eq 'blank') ? Attean::Blank->new($id) : Attean::Variable->new($id);
					my $t	= $item_class->new( %t );
					$p->[1]	= $t;
					$p->[0]	= $t->tuples_string;
				}
			}
		}
	
		foreach my $p (@tuples) {
			my ($str, $t)	= @$p;
			my $item_class	= ref($t);
			foreach my $pos (reverse $t->variables) {
				if (defined(my $tstr = $p->[2]{$pos})) {
					$tstr	=~ /^([?]|_:)([^#]+)$/;
					my $prefix	= $1;
					my $name	= $2;
					my $key		= "$prefix$name";
					delete $p->[2]{$pos};
					unless (exists($mapping{$key})) {
						$self->error("Cannot canonicalize binding set");
						return;
					}
					my $id		= $mapping{$key}{id};
					my $type	= ($prefix eq '?' ? 'variable' : 'blank');
					$mapping{ $key }	= { id => $id, prefix => $prefix, type => $type };
					my %t		= $p->[1]->mapping;
					$t{ $pos }	= ($type eq 'blank') ? Attean::Blank->new($id) : Attean::Variable->new($id);
					my $t	= $item_class->new( %t );
					$p->[1]	= $t;
					$p->[0]	= $t->tuples_string;
				}
			}
		}
		
		my $parse_id	= '1';
		my %bnode_map	= map { $_ => Attean::Blank->new( value => $mapping{"_:$_"}{'id'} ) }
							map { substr($_,2) } # remove the leading _:
								grep { /^_:/ }
									(keys %mapping);
		foreach my $p (@tuples) {
			my ($str, $t)	= @$p;
			my $item_class	= ref($t);
			foreach my $pos (reverse $t->variables) {
				my $term	= $t->$pos();
				if (blessed($term) and $term->does('Attean::API::Literal')) {
					my $dt 		= $term->datatype();
					if (blessed($dt) and ($dt->value eq 'http://w3id.org/awslabs/neptune/SPARQL-CDTs/Map' or $dt->value eq 'http://w3id.org/awslabs/neptune/SPARQL-CDTs/List')) {
						my %t	= $p->[1]->mapping;
						my $newterm	= AtteanX::Functions::CompositeLists::rewrite_lexical($term, \%bnode_map, $parse_id);
						$t{ $pos }	= $newterm;
						my $newt	= $item_class->new( %t );
						$t			= $newt;
						$p->[1]	= $t;
					}
				}
			}
		}
		
		@tuples	= sort { $a->[0] cmp $b->[0] } @tuples;
		my $elements	= [ map { $_->[1] } @tuples ];
		return ($elements, \%mapping);
	}
}

package Attean::API 0.035 {
	use Attean::API::Term;
	use Attean::API::Store;
	use Attean::API::Model;
	use Attean::API::Iterator;
	use Attean::API::Parser;
	use Attean::API::Serializer;
	use Attean::API::Query;
	use Attean::API::Expression;
	use Attean::API::Plan;
	use Attean::API::QueryPlanner;
	
	use Attean::Variable;
	use Attean::Blank;
	use Attean::IRI;
}

1;

__END__

=back

=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--2022 Gregory Todd Williams.
This program is free software; you can redistribute it and/or modify it under
the same terms as Perl itself.

=cut
