#!/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);