#!/usr/bin/perl

#######################################################################
package Local::Database::ToRDF;

use 5.008;
use strict;

use DBI;
use RDF::Trine qw[statement iri blank literal];
use RDF::Trine::Namespace qw[rdf rdfs owl xsd];

sub new
{
	my ($class, %mappings) = @_;
	bless {%mappings}, $class; 
}

sub namespaces
{
	my ($self) = @_;
	my %NS;
	
	%NS = %{ $self->{-namespaces} }
		if ref $self->{-namespaces} eq 'HASH';
		
	%NS = (
		owl  => "$owl",
		rdf  => "$rdf",
		rdfs => "$rdfs",
		xsd  => "$xsd",
		) unless %NS;
	
	return %NS;
}

sub process
{
	my ($self, $dbh, $model) = @_;
	$model = RDF::Trine::Model->temporary_model unless defined $model;
	my $parsers = {};
	my %NS = $self->namespaces;
	
	TABLE: while (my ($table, $tmap) = each %$self)
	{
		next if $table =~ /^-/;
		
		# ->{select}
		my $select = $tmap->{select} || '*';
		my $sql    = "SELECT $select FROM $table";
		my $sth    = $dbh->prepare($sql);
		
		$sth->execute;
		
		ROW: while (my $row = $sth->fetchrow_hashref)
		{
			my %row = %$row;
			
			# ->{about}
			my $subject;
			if ($tmap->{about})
			{
				$tmap->{about} = [$tmap->{about}] unless ref $tmap->{about} eq 'ARRAY';
				my ($template, @cols) = @{ $tmap->{about} };
				$subject = iri(sprintf($template, @row{@cols}));
			}
			$subject ||= RDF::Trine::Node::Blank->new;
			
			# ->{typeof}
			foreach (@{ $tmap->{typeof} })
			{
				$_ = iri($_) unless ref $_;
				$model->add_statement(statement($subject, $rdf->type, $_));
			}

			# ->{columns}
			my %columns = %{ $tmap->{columns} };
			COLUMN: while (my ($column, $list) = each %columns)
			{
				MAP: foreach my $map (@$list)
				{
					my $predicate;
					my $value = $row{$column};
					
					if (defined $map->{parse} and uc $map->{parse} eq 'TURTLE')
					{
						next MAP unless length $value;
						
						my $turtle = join '', map { sprintf("\@prefix %s: <%s>.\n", $_, $NS{$_}) } keys %NS;
						$turtle .= sprintf("\@base <%s>.\n", $subject->uri);
						$turtle .= "$value\n";
						eval {
							$parsers->{ $map->{parse} } = RDF::Trine::Parser->new($map->{parse});
							$parsers->{ $map->{parse} }->parse_into_model($subject, $turtle, $model);
						};
						next MAP;
					}
										
					if ($map->{rev} || $map->{rel})
					{
						if ($map->{resource})
						{
							$value = sprintf($map->{resource}, $value);
						}
						$predicate = $map->{rev} || $map->{rel};
						$value     = iri($value);
					}
					
					elsif ($map->{property})
					{
						if ($map->{content})
						{
							$value = sprintf($map->{content}, $value);
						}
						$predicate = $map->{property};
						$value     = literal($value, $map->{lang}, $map->{datatype});
					}
					
					if (defined $predicate)
					{
						$predicate = iri($predicate) unless ref $predicate;

						my $lsubject = $subject;
						if ($map->{about})
						{
							$map->{about} = [$map->{about}] unless ref $map->{about} eq 'ARRAY';
							my ($template, @cols) = @{ $map->{about} };
							$lsubject = iri(sprintf($template, @row{@cols}));
						}

						if ($map->{rev})
						{
							$model->add_statement(statement($value, $predicate, $lsubject));
						}
						else
						{
							$model->add_statement(statement($lsubject, $predicate, $value));
						}
					}
				}
			}
		}
	}

	return $model;
}


#######################################################################
package main;

use 5.008;
use strict;

use DBI;
use RDF::Trine;
use RDF::Trine::Namespace qw[rdf rdfs owl xsd];

my $foaf = RDF::Trine::Namespace->new('http://xmlns.com/foaf/0.1/');
my $bibo = RDF::Trine::Namespace->new('http://purl.org/ontology/bibo/');
my $dc   = RDF::Trine::Namespace->new('http://purl.org/dc/terms/');
my $skos = RDF::Trine::Namespace->new('http://www.w3.org/2004/02/skos/core#');

my $mapping = Local::Database::ToRDF->new(
	-namespaces => {
		bibo  => "$bibo",
		dc    => "$dc",
		foaf  => "$foaf",
		rdfs  => "$rdfs",
		skos  => "$skos",
		},
	books => {
		about     => ['http://example.net/id/book/%d', 'book_id'],
		typeof    => [$bibo->Book],
		columns   => {
			title    => [{property => $rdfs->label, lang=>'en'}, {property => $dc->title, lang=>'en'}],
			turtle   => [{parse => 'Turtle'}],
			},
		},
	authors => {
		select    => "*, forename||' '||surname AS fullname",
		about     => ['http://example.net/id/author/%d', 'author_id'],
		typeof    => [$foaf->Person],
		columns   => {
			forename => [{property => $foaf->givenName}],
			surname  => [{property => $foaf->familyName}],
			fullname => [{property => $rdfs->label}, {property => $foaf->name}],
			turtle   => [{parse => 'Turtle'}],
			},
		},
	topics => {
		about     => ['http://example.net/id/topic/%d', 'topic_id'],
		typeof    => [$skos->Concept],
		columns   => {
			label    => [{property => $rdfs->label, lang=>'en'}, {property => $skos->prefLabel, lang=>'en'}],
			turtle   => [{parse => 'Turtle'}],
			},
		},
	book_authors => {
		about     => ['http://example.net/id/book/%d', 'book_id'],
		columns   => {
			author_id=> [{rel => $dc->creator, resource => 'http://example.net/id/author/%d'}, {rel => $foaf->maker, resource => 'http://example.net/id/author/%d'}, {rev => $foaf->made, resource => 'http://example.net/id/author/%d'}, {rel => $bibo->author, resource => 'http://example.net/id/author/%d'}],
			},
		},
	book_topics => {
		about     => ['http://example.net/id/book/%d', 'book_id'],
		columns   => {
			topic_id => [{rel => $dc->subject, resource => 'http://example.net/id/topic/%d'}],
			},
		},
	);

my $file  = shift @ARGV;
my $dbh   = DBI->connect("dbi:SQLite:dbname=$file");
my $model = $mapping->process($dbh);

print RDF::Trine::Serializer
	->new('Turtle', namespaces => { $mapping->namespaces })
	->serialize_model_to_string($model);

