use v5.18;
use autodie;
use utf8;
use Carp qw(confess);
use Test::More;
use Test::Exception;
use FindBin qw($Bin);
use File::Glob qw(bsd_glob);
use File::Spec;
use JSON qw(decode_json);
use Data::Dumper;
use JSONLD;

use Moo;
use Attean;
use Type::Tiny::Role;

our $debug	= 0;
$JSONLD::debug	= $debug;
our $PATTERN;
if ($debug) {
	$PATTERN = qr/t0114/;
# 	$PATTERN = qr/gtw/;
} else {
	$PATTERN	= qr/./;
}

package MyJSONLD {
	use v5.18;
	use autodie;
	use Moo;
	use Attean::RDF;
	extends 'JSONLD';
	
	sub default_graph {
		return iri('http://attean.example.org/default-graph');
	}

	sub add_quad {
		my $self	= shift;
		my $triple	= shift;
		my $ds		= shift;
		$ds->add_quad($triple);
	}

	sub new_dataset {
		my $self	= shift;
		my $store	= Attean->get_store('Memory')->new();
		return $store;
	}
	
	sub new_triple {
		my $self	= shift;
		foreach my $v (@_) {
			Carp::confess "not a term object" unless (ref($v));
		}
		return triple(@_);
	}
	
	sub new_quad {
		my $self	= shift;
		foreach my $v (@_) {
			Carp::confess "not a term object: $v" unless (ref($v));
		}
		return quad(@_);
	}
	
	sub skolem_prefix {
		my $self	= shift;
		return 'tag:gwilliams@cpan.org,2019-12:JSONLD:skolem:';
	}
	sub new_graphname {
		my $self	= shift;
		my $value	= shift;
		if ($value =~ /^_:(.+)$/) {
			$value	= $self->skolem_prefix() . $1;
		}
		return $self->new_iri($value);
	}

	sub new_iri {
		my $self	= shift;
		return iri(shift);
	}
	
	sub new_blank {
		my $self	= shift;
		return blank(@_);
	}
	
	sub new_lang_literal {
		my $self	= shift;
		my $value	= shift;
		my $lang	= shift;
		return langliteral($value, $lang);
	}
	
	sub new_dt_literal {
		my $self	= shift;
		my $value	= shift;
		my $dt		= shift;
		return dtliteral($value, $dt);
	}
}

sub load_nq {
	my $file	= shift;
	open(my $fh, '<', $file);
	my $parser	= Attean->get_parser('nquads')->new();
	my $iter	= $parser->parse_iter_from_io($fh);
	my $miter	= $iter->materialize;
	foreach my $st ($miter->elements) {
		say $st->as_string;
	}
	return $miter;
}

sub load_json {
	my $file	= shift;
	open(my $fh, '<', $file);
	my $j	= JSON->new();
	$j->boolean_values(0, 1);
	return $j->decode(do { local($/); <$fh> });
}

my $path	= File::Spec->catfile( $Bin, 'data', 'json-ld-api-w3c' );
my $manifest	= File::Spec->catfile($path, 'toRdf-manifest.jsonld');
my $d		= load_json($manifest);
my $tests	= $d->{'sequence'};
my $base	= IRI->new(value => $d->{'baseIri'} // 'http://example.org/');
foreach my $t (@$tests) {
	my $id		= $t->{'@id'};
	next unless ($id =~ $PATTERN);

	my $input	= $t->{'input'};
	my $expect	= $t->{'expect'};
	my $name	= $t->{'name'};
	my $purpose	= $t->{'purpose'};
	my $options	= $t->{'option'} // {};
	my $_base	= $options->{'base'};
	my $spec_v	= $options->{'specVersion'} // '';
	my @types	= @{ $t->{'@type'} };
	my %types	= map { $_ => 1 } @types;

	my $test_base;
	if (defined($_base)) {
		$test_base	= IRI->new(value => $_base, base => $base)->abs;
	} else {
		$test_base	= IRI->new(value => $input, base => $base)->abs;
	}
	my $j		= JSON->new->canonical(1)->allow_nonref(1);
	$j->boolean_values(0, 1);
	if ($spec_v eq 'json-ld-1.0') {
		diag("IGNORING JSON-LD-1.0-only test $id\n");
	} elsif ($types{'jld:PositiveEvaluationTest'}) {
		note($id);
		my $jld			= MyJSONLD->new(base_iri => IRI->new($test_base));
		my $infile		= File::Spec->catfile($path, $input);
		my $outfile		= File::Spec->catfile($path, $expect);
		my $data		= load_json($infile);
		my $expected	= load_nq($outfile);
		if ($debug) {
			warn "Input file: $infile\n";
			warn "Output file: $outfile\n";
			warn "INPUT:\n===============\n" . JSON->new->pretty->encode($data);
		}
		my $default_graph	= $jld->default_graph();
		my $got		= eval {
			my $qiter	= $jld->to_rdf($data)->get_quads()->materialize();
			my $iter	= Attean::CodeIterator->new(generator => sub {
				my $q	= $qiter->next;
				return unless ($q);
				my $g		= $q->graph;
				my $prefix	= $jld->skolem_prefix();
				if ($g->equals($default_graph)) {
					return $q->as_triple;
				} elsif (substr($g->value, 0, length($prefix)) eq $prefix) {
					my $gb		= $jld->new_blank(substr($g->value, length($prefix)));
					my @terms	= $q->values;
					$terms[3]	= $gb;
					return $jld->new_quad(@terms);
				} else {
					return $q;
				}
			}, item_type => 'Attean::API::TripleOrQuad')->materialize;
		};
		if ($@) {
			diag("Died: $@");
		}

		eval {
			my $eqtest	= Attean::BindingEqualityTest->new();
			my $ok	= ok($eqtest->equals($got, $expected), "$id: $name");
			if ($debug) {
				my @data	= (
					['EXPECTED', $expected],
					['OUTPUT__', $got],
				);
				my @files;
				my $ser	= Attean->get_serializer('nquads')->new();
				foreach my $d (@data) {
					my ($name, $data)	= @$d;
					$data->reset;
					warn "=======================\n";
					my $filename	= "/tmp/json-ld-$$-$name.out";
					open(my $fh, '>', $filename) or die $!;
					push(@files, $filename);
					print {$fh} "# $name\n";
					$ser->serialize_iter_to_io($fh, $data);
					close($fh);
				}
				unless ($ok) {
					system('/usr/local/bin/bbdiff', '--wait', '--resume', @files);
				}
			}

		};
		if ($@) {
			fail($@);
			next;
		}
	} elsif ($types{'jld:NegativeEvaluationTest'}){
		diag("IGNORING NegativeEvaluationTest $id\n");
	} else {
		diag("Not a recognized evaluation test: " . Dumper(\@types));
		next;
	}
}

done_testing();
