package Types::Standard::CycleTuple;

use 5.006001;
use strict;
use warnings;

BEGIN {
	$Types::Standard::CycleTuple::AUTHORITY = 'cpan:TOBYINK';
	$Types::Standard::CycleTuple::VERSION   = '1.002001';
}

use Type::Tiny ();
use Types::Standard ();
use Types::TypeTiny ();

sub _croak ($;@) { require Error::TypeTiny; goto \&Error::TypeTiny::croak }

my $_Optional = Types::Standard::Optional;
my $_arr      = Types::Standard::ArrayRef;

no warnings;

my $cycleuniq = 0;

sub __constraint_generator
{
	my @params = map {
		ref($_) eq 'HASH' and exists($_->{slurpy})
			and _croak("Parameters to CycleTuple[`a] cannot be slurpy");
		my $param = Types::TypeTiny::to_TypeTiny($_);
		Types::TypeTiny::TypeTiny->check($param)
			or _croak("Parameter to CycleTuple[`a] expected to be a type constraint; got $param");
		$param;
	} @_;
	my $count  = @params;
	my $tuple  = Types::Standard::Tuple()->of(@params);
	
	_croak("Parameters to CycleTuple[`a] cannot be optional")
		if grep !!$_->is_strictly_a_type_of($_Optional), @params;
	
	sub {
		my $value = shift;
		return unless $_arr->check($value);
		return if @$value % $count;
		my $i = 0;
		while ($i < $#$value) {
			my $tmp = [@$value[ $i .. $i+$count-1 ]];
			return unless $tuple->check($tmp);
			$i += $count;
		}
		!!1;
	}
}

sub __inline_generator
{
	my @params = map {
		my $param = Types::TypeTiny::to_TypeTiny($_);
		Types::TypeTiny::TypeTiny->check($param)
			or _croak("Parameter to CycleTuple[`a] expected to be a type constraint; got $param");
		$param;
	} @_;
	my $count  = @params;
	my $tuple  = Types::Standard::Tuple()->of(@params);
	
	return unless $tuple->can_be_inlined;
	
	sub {
		$cycleuniq++;
		
		my $v = $_[1];
		my @checks = $_arr->inline_check($v);
		push @checks, sprintf(
			'not(@%s %% %d)',
			($v=~/\A\$[a-z0-9_]+\z/i ? $v : "{$v}"),
			$count,
		);
		push @checks, sprintf(
			'do { my $cyclecount%d = 0; my $cycleok%d = 1; while ($cyclecount%d < $#{%s}) { my $cycletmp%d = [@{%s}[$cyclecount%d .. $cyclecount%d+%d]]; unless (%s) { $cycleok%d = 0; last; }; $cyclecount%d += %d; }; $cycleok%d; }',
			$cycleuniq,
			$cycleuniq,
			$cycleuniq,
			$v,
			$cycleuniq,
			$v,
			$cycleuniq,
			$cycleuniq,
			$count - 1,
			$tuple->inline_check("\$cycletmp$cycleuniq"),
			$cycleuniq,
			$cycleuniq,
			$count,
			$cycleuniq,
		) if grep { $_->inline_check('$xyz') ne '(!!1)' } @params;
		join(' && ', @checks);
	}
}

sub __deep_explanation
{
	my ($type, $value, $varname) = @_;
	
	my @constraints =
		map Types::TypeTiny::to_TypeTiny($_), @{ $type->parameters };
	
	if (@$value % @constraints)
	{
		return [
			sprintf('"%s" expects a multiple of %d values in the array', $type, scalar(@constraints)),
			sprintf('%d values found', scalar(@$value)),
		];
	}
	
	for my $i (0 .. $#$value)
	{
		my $constraint = $constraints[$i % @constraints];
		next if $constraint->check($value->[$i]);
		
		return [
			sprintf('"%s" constrains value at index %d of array with "%s"', $type, $i, $constraint),
			@{ $constraint->validate_explain($value->[$i], sprintf('%s->[%s]', $varname, $i)) },
		];
	}
	
	# This should never happen...
	return;  # uncoverable statement
}

my $label_counter = 0;
sub __coercion_generator
{
	my ($parent, $child, @tuple) = @_;
	
	my $child_coercions_exist = 0;
	my $all_inlinable = 1;
	for my $tc (@tuple)
	{
		$all_inlinable = 0 if !$tc->can_be_inlined;
		$all_inlinable = 0 if $tc->has_coercion && !$tc->coercion->can_be_inlined;
		$child_coercions_exist++ if $tc->has_coercion;
	}

	return unless $child_coercions_exist;
	my $C = "Type::Coercion"->new(type_constraint => $child);

	if ($all_inlinable)
	{
		$C->add_type_coercions($parent => Types::Standard::Stringable {
			my $label  = sprintf("CTUPLELABEL%d", ++$label_counter);
			my $label2 = sprintf("CTUPLEINNER%d", $label_counter);
			my @code;
			push @code, 'do { my ($orig, $return_orig, $tmp, @new) = ($_, 0);';
			push @code,       "$label: {";
			push @code,       sprintf('(($return_orig = 1), last %s) if scalar(@$orig) %% %d != 0;', $label, scalar @tuple);
			push @code,         sprintf('my $%s = 0; while ($%s < @$orig) {', $label2, $label2);
			for my $i (0 .. $#tuple)
			{
				my $ct = $tuple[$i];
				my $ct_coerce   = $ct->has_coercion;
				
				push @code, sprintf(
					'do { $tmp = %s; (%s) ? ($new[$%s + %d]=$tmp) : (($return_orig=1), last %s) };',
					$ct_coerce
						? $ct->coercion->inline_coercion("\$orig->[\$$label2 + $i]")
						: "\$orig->[\$$label2 + $i]",
					$ct->inline_check('$tmp'),
					$label2,
					$i,
					$label,
				);
			}
			push @code, sprintf('$%s += %d;', $label2, scalar(@tuple));
			push @code,         '}';
			push @code,       '}';
			push @code,    '$return_orig ? $orig : \\@new';
			push @code, '}';
			"@code";
		});
	}
	
	else
	{
		$C->add_type_coercions(
			$parent => sub {
				my $value = @_ ? $_[0] : $_;
				
				if (scalar(@$value) % scalar(@tuple) != 0)
				{
					return $value;
				}
				
				my @new;
				for my $i (0 .. $#$value)
				{
					my $ct = $tuple[$i % @tuple];
					my $x  = $ct->has_coercion ? $ct->coerce($value->[$i]) : $value->[$i];
					
					return $value unless $ct->check($x);
					
					$new[$i] = $x;
				}
				
				return \@new;
			},
		);
	};
	
	return $C;
}

1;

__END__

=pod

=encoding utf-8

=head1 NAME

Types::Standard::CycleTuple - internals for the Types::Standard CycleTuple type constraint

=head1 STATUS

This module is considered part of Type-Tiny's internals. It is not
covered by the
L<Type-Tiny stability policy|Type::Tiny::Manual::Policies/"STABILITY">.

=head1 DESCRIPTION

This file contains some of the guts for L<Types::Standard>.
It will be loaded on demand. You may ignore its presence.

=head1 BUGS

Please report any bugs to
L<http://rt.cpan.org/Dist/Display.html?Queue=Type-Tiny>.

=head1 SEE ALSO

L<Types::Standard>.

=head1 AUTHOR

Toby Inkster E<lt>tobyink@cpan.orgE<gt>.

=head1 COPYRIGHT AND LICENCE

This software is copyright (c) 2017 by Toby Inkster.

This is free software; you can redistribute it and/or modify it under
the same terms as the Perl 5 programming language system itself.

=head1 DISCLAIMER OF WARRANTIES

THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED
WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.

