The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
package RDF::Closure::RestrictedDatatype;

BEGIN {
	$RDF::Closure::RestrictedDatatype::AUTHORITY = 'cpan:TOBYINK';
	$RDF::Closure::RestrictedDatatype::VERSION   = '0.001';
}

use 5.008;
use strict;
use utf8;

use Error qw':try';
use RDF::Closure::DatatypeHandling;
use RDF::Trine qw[iri];
use RDF::Trine::Namespace qw[XSD RDF RDFS OWL];
use Scalar::Util qw[blessed];

#: Constant for datatypes using min, max (inclusive and exclusive):
use constant MIN_MAX               => 'MIN_MAX';
#: Constant for datatypes using length, minLength, and maxLength (and nothing else)
use constant LENGTH                => 'LENGTH';
#: Constant for datatypes using length, minLength, maxLength, and pattern
use constant LENGTH_AND_PATTERN    => 'LENGTH_AND_PATTERN';
#: Constat for datatypes using length, minLength, maxLength, pattern, and lang range
use constant LENGTH_PATTERN_LRANGE => 'LENGTH_PATTERN_LRANGE';

#: Dictionary of all the datatypes, keyed by category
our %Datatypes_per_facets = (
	MIN_MAX => [
		$OWL->rational, $XSD->decimal, $XSD->integer,
		$XSD->nonNegativeInteger, $XSD->nonPositiveInteger,
		$XSD->positiveInteger, $XSD->negativeInteger,
		$XSD->long, $XSD->short, $XSD->byte,
		$XSD->unsignedLong, $XSD->unsignedInt, $XSD->unsignedShort, $XSD->unsignedByte,
		$XSD->double, $XSD->float,
		$XSD->dateTime, $XSD->dateTimeStamp, $XSD->time, $XSD->date,
		],
	LENGTH => [ $XSD->hexBinary, $XSD->base64Binary ],
	LENGTH_AND_PATTERN => [
		$XSD->anyURI, $XSD->string, $XSD->NMTOKEN, $XSD->Name, $XSD->NCName,
		$XSD->language, $XSD->normalizedString,
		],
	LENGTH_PATTERN_LRANGE => [ $RDF->PlainLiteral ],
	);

our %facet_to_method = (
	MIN_MAX               => [qw(_check_max_exclusive _check_min_exclusive _check_max_inclusive _check_min_inclusive)],
	LENGTH                => [qw(_check_min_length _check_max_length _check_length)],
	LENGTH_AND_PATTERN    => [qw(_check_min_length _check_max_length _check_length _check_pattern)],
	LENGTH_PATTERN_LRANGE => [qw(_check_min_length _check_max_length _check_length _check_lang_range)],
	);

our @facetable_datatypes = map { @$_ } values %Datatypes_per_facets;

sub new
{
	my ($class, @args) = @_;
	my $self = bless {}, $class;
	$self->__init__(@args);
	return $self;
}

sub extract_from_graph
{
	my ($class, $graph, $dth) = @_;
	my @retval;
	
	$graph->subjects($RDF->type, $RDFS->Datatype)->each(sub{
		my $dtype = shift;
		my $base_type;
		my @facets;
		eval
		{
			my @base_types = $graph->objects($dtype, $OWL->onDatatype);
			if (@base_types)
			{
				if (exists $base_types[1])
				{
					die(sprintf("Several base datatype for the same restriction %s", $dtype));
				}
				else
				{
					$base_type = $base_types[0];
					if (grep { $base_type->equal($_) } @facetable_datatypes)
					{
						my @rlists = $graph->objects($dtype, $OWL->withRestrictions);
						if (exists $rlists[1])
						{
							die(sprintf("More than one facet lists for the same restriction %s", $dtype));
						}
						elsif (@rlists)
						{
							my @final_facets;
							foreach my $r ($graph->get_list(@rlists))
							{
								$graph->get_statements($r, undef, undef)->each(sub{
									my (undef, $facet, $lit) = (shift)->nodes;
									push @final_facets, [$facet, $lit];
								});
							}
							# We do have everything we need:
							my $new_datatype = $class->new($dtype, $base_type, \@final_facets, $dth);
							push @retval, $new_datatype;
						}
					}
				}
			}
		};
	});
	
	return @retval;
}

sub __init__
{
	my ($self, $type_uri, $base_type, $facets, $dt_handler) = @_;
	
	$dt_handler ||= RDF::Closure::DatatypeHandling->new;
	
	$self->{datatype}   = $type_uri;
	$self->{base_type}  = $base_type;
	$self->{dt_handler} = $dt_handler;
	
	my $converter = $dt_handler->mapping("$base_type");
	unless (defined $converter)
	{
		throw Error::Simple("No facet is implemented for datatype %s", $base_type);
	}
	$self->{converter}  = $converter;
	
	$self->{minExclusive} = undef;
	$self->{maxExclusive} = undef;
	$self->{minInclusive} = undef;
	$self->{maxInclusive} = undef;
	$self->{length}       = undef;
	$self->{maxLength}    = undef;
	$self->{minLength}    = undef;
	$self->{pattern}      = [];
	$self->{langRange}    = [];
	
	foreach my $pair (@$facets)
	{
		my ($facet, $value) = @$pair;
		$value = $self->{dt_handler}->literal_to_perl($value) if ref $value;
		
		if ($facet->equal($XSD->minInclusive) and (!defined $self->{minInclusive} or $self->{minInclusive} < $value))
		{
			$self->{minInclusive} = $value;
		}
		elsif ($facet->equal($XSD->maxInclusive) and (!defined $self->{maxInclusive} or $self->{maxInclusive} > $value))
		{
			$self->{maxInclusive} = $value;
		}
		elsif ($facet->equal($XSD->minExclusive) and (!defined $self->{minExclusive} or $self->{minExclusive} < $value))
		{
			$self->{minExclusive} = $value;
		}
		elsif ($facet->equal($XSD->maxExclusive) and (!defined $self->{maxExclusive} or $self->{maxExclusive} > $value))
		{
			$self->{maxExclusive} = $value;
		}
		elsif ($facet->equal($XSD->minLength) and (!defined $self->{minLength} or $self->{minLength} < $value))
		{
			$self->{minLength} = $value;
		}
		elsif ($facet->equal($XSD->maxLength) and (!defined $self->{maxLength} or $self->{maxLength} > $value))
		{
			$self->{maxLength} = $value;
		}
		elsif ($facet->equal($XSD->length))
		{
			$self->{length} = $value;
		}
		elsif ($facet->equal($XSD->pattern))
		{
			push @{$self->{pattern}}, qr($value)so;
		}
		elsif ($facet->equal($RDF->langRange))
		{
			push @{$self->{langRange}}, $value;
		}
	}
	
	$self->{check_methods} = [];
	
	LOOP: foreach my $cat (keys %Datatypes_per_facets)
	{
		if (grep {$_->equal($base_type)} @{$Datatypes_per_facets{$cat}})
		{
			$self->{category}      = $cat;
			$self->{check_methods} = $facet_to_method{$cat};
			last LOOP;
		}
	}
}

sub datatype  { return $_[0]->{datatype}; }
sub base_type { return $_[0]->{base_type}; }

sub check
{
	my ($self, $value, $dt) = @_;
	
	if (blessed($value) and $value->isa('RDF::Trine::Node'))
	{
		$dt  ||= $value->literal_datatype;
		$value = $self->{dt_handler}->literal_to_perl($value);
	}
	
	foreach my $method (@{$self->{check_methods}})
	{
		return unless $self->$method($value, $dt);
	}
	
	return $self;
}

sub _check_min_exclusive
{
	my ($self, $value) = @_;
	return $self unless defined $self->{minExclusive};
	return ($self->{minExclusive} < $value);
}

sub _check_max_exclusive
{
	my ($self, $value) = @_;
	return $self unless defined $self->{maxExclusive};
	return ($self->{maxExclusive} > $value);
}

sub _check_min_inclusive
{
	my ($self, $value) = @_;
	return $self unless defined $self->{minInclusive};
	return ($self->{minInclusive} <= $value);
}

sub _check_max_inclusive
{
	my ($self, $value) = @_;
	return $self unless defined $self->{maxInclusive};
	return ($self->{maxInclusive} >= $value);
}

sub _check_min_length
{
	my ($self, $value) = @_;
	return $self unless defined $self->{minLength};
	return ($self->{minLength} <= length($value));
}

sub _check_max_length
{
	my ($self, $value) = @_;
	return $self unless defined $self->{maxLength};
	return ($self->{maxLength} >= length($value));
}

sub _check_length
{
	my ($self, $value) = @_;
	return $self unless defined $self->{length};
	return ($self->{length} == length($value));
}

sub _check_pattern
{
	my ($self, $value) = @_;
	
	foreach my $pattern (@{$self->{pattern}})
	{
		return unless $value =~ $pattern;
	}
	
	return $self;
}

sub _check_lang_range
{
	my ($self, $value) = @_;
	
	return unless blessed($value) && $value->can('lang_range_check');
	
	foreach my $r (@{$self->{langRange}})
	{
		return unless $value->lang_range_check($r);
	}
	
	return $self;
}

1;