#!/usr/bin/env ruby

$LOAD_PATH.unshift '../../lib/' # because we're testing dev version

# Exploring a pastiche of the Cwm RDF API
# taking http://www.w3.org/2000/10/swap/pim/toIcal.py as basis
# $Id: pastiche.rb,v 1.5 2003/05/06 02:39:20 danbri Exp $ 
# Dan Brickley <danbri@w3.org>
#
#      Current Cwm-like RDF API (llyn.py/thing.py) functionality:
#
#      def each(st)       # iterator for simple statement matcher
#      def statementsMatching(st) # ditto but returns immutable statements
#      def add(st)	  # works for RDFGraph, no impl yet in SimpleSQLGraph 
#      def any(st)        # returns the missing bit for a blanked out node
#      def the(st)        # ditto but complains if multiple answers
#      def Namespace(n)   # haven't sorted out interning, scopes etc though
# 
# TODO: currently all supporting code is here w/ the tests, maybe -> ../lib/ ?
#
# see http://www.w3.org/2000/10/swap/llyn.py and thing.py
# 
# note that the SQL/stored tests require a local DBI rdfstore, see tiny.rb
#
# The Cwm-ish api explored below echoes this from Cwm:
#  for cal in sts.each(pred = RDF.type, obj = ICAL.Vcalendar):
#  still TODO:
#  if sts.statementsMatching(RDF.type, comp, ICAL.Vevent):
#  sts.any(subj, ICAL.sym(pn)) -> ???, returns at most one?
#
# NOTE: we have *lots* of issues, eg. which things should be class vs
# instance vs factoried, interned, etc etc. Ignoring all that for now...
#
# we annotate the SimpleSQLGraph class with mixin
# so all our SimpleSQLGraph instances have Cwm-ish behaviours.
# also instances (see below). See further below for health warning and 
# ideas for a different approach. 
#
###################################################################

#require 'test/unit'     # unit testing


# tests see tests/scutter/sqlgraph.rb amongst others

require 'basicrdf'      # main RubyRdf impl and api
require 'squish' 	# for SimpleSQLGraph impl

module RDF4R
  module Pastiche
    module SWAP
      class TheWorld
        attr_accessor :namespaces
        @@ns=[]
        def initialize
          @@ns=[]
        end
        def TheWorld.namespaces
          return @@ns
        end
      end
      
      # load some RDF/XML from URI, returning a Cwm Store
      # load() isn't a property of a Cwm instance.
      # ...though we allow it there as a static too.
      # how about as an instance method? would that be an add() variant?
      def load(uri)
        return Formula.new(Loader.get_rdf_from_uri(uri,uri))
      end

      def Namespace(n)
          # puts "NS:"+@namespaces.inspect
        TheWorld.namespaces.push n
        return n.to_s # what is a 'Namespace' in cwm? string?
        # all manner of interning would normally happen here
        # however we've not decided how to deal with that yet.
        # Oh, and it should be a class method.
      end

      # A Formula is a set of triples.
      class Formula                         #<Graph                      
	# bad name? Llyn? Formula? Cwm? what rel'n w/ Graph makes sense?
        
        # This is functionality we attach to RDFGraph and SimpleSQLGraph
        # the @kb field works for locally held data, not encapsulated 
        # services like SQL-backed stores, SOAP etc. The each() iterator
        # allows data to be accessed regardless. Need to think about 
        # cleaner design for allowing different types of stores, and the
        # classes they fall into.
        
        attr_accessor :namespaces
        
        def initialize(graph)
          #puts "Cwm pastiche initializing with graph "+graph.to_s
          @kb=graph
        end

#	def method_missing(m)
#	  @kb.method(m) # testing this as wrapping trick
#	end
        
        def to_s
          return @kb.toNtriples # for now...
        end

        def add(st)
          g=@kb.tell(Statement.new(st['subj'], st['pred'], st['obj'])) if st.class==Hash
          raise("Unknown invocation of add()") # can we add graphs?
        end      
        

        # return the blanked out bit that matches
        def any(st)
          one(st,false)
        end

        def the(st)
          return one(st,true)
        end

        # return the blanked out bit that matches
        def one(st,the_mode=false)
          results=[]
          self.each(st) do |r|
          results.push r
          end
          return nil if results==nil
          if ((results.size>1) & the_mode==true)
            raise("There should only be one match. got: #{results.inspect}") 
          end
	  return nil if results.size==0
#	  puts "RESULTS: #{results.inspect} #{results.size}" #zzz
          return results[0] if st['subj']==nil
          return results[0] if st['pred']==nil
          return results[0] if st['obj']==nil
          raise ("You must give one wildcard")
        end
        
        # we use 'yield', unlike Cwm (though there is an @@todo in llyn.py)
        # hmm is this use of 'each' a clash w/ Ruby conventions?
        #
        # OK this was my atempt at 'each', but it was wrong so renamed.
        def =~ (st=nil)
          #puts "Query was: "+st.inspect+" type: "+st.class.to_s
          g=@kb.ask st if st.class==Statement
          g=@kb.ask(Statement.new(st['subj'], st['pred'], st['obj'])) if st.class==Hash
	  g=@kb.ask(Statement.new(nil,nil,nil)) if st==nil # default 
          g=Graph.new([]) if g==nil
          g.statements.each do |s|
            yield s
          end
        end


        # Cwm-like each() method, can be called as an iterator
        # but if not, returns an array.
        def each(st={})
          res=[]
          g=@kb.ask st if st.class==Statement
          g=@kb.ask(Statement.new(st['subj'], st['pred'], st['obj'])) if st.class==Hash
	  g=@kb.ask(Statement.new(nil,nil,nil)) if st==nil # default 
          #puts "Results: #{st.inspect}"
          g=Graph.new([]) if g==nil
          ## TODO: check if multi blanks
          g.statements.each do |s|
            res.push s.predicate.to_s if st['pred']==nil 
            res.push s.subject.to_s if st['subj']==nil 
            res.push s.object.to_s if st['obj']==nil 
            #puts "Result list: #{res.inspect}"
          end
          return res if !block_given?
          res.each do |i| 
            yield i 
          end
        end
        #end
        

        # READ ONLY results
        # todo: test this!
        def statementsMatching(st)
          #puts "Store query: #{st.inspect}"
          g=@kb.ask st if st.class==Statement
          g=@kb.ask(Statement.new(st['subj'], st['pred'], st['obj'])) if st.class==Hash
          g=Graph.new([]) if g==nil
          return g if !block_given?
          g.statements.each do |s|
            s=s.clone.freeze # copy and freeze each statement (deep? Nodes?)
            yield s
          end
        end
        
        # RDFLib-ish stuff for completeness, but is this a diff pastiche?)
	def triples(st)
	  each(st)
	end	

	def remove(st)
	  raise "Remove method unimplemented."
        end

	# these take a Hash here, so don't need to memorise arg order.

        def subjects(opts)
 	  any(opts)
	end
	
	def predicates(opts)
	  any(opts)
	end

	def objects(opts)
	  any(opts)
	end

	def subject_predicates(opts)
	  any(opts)
	end

	def subject_objects(opts)
	  any(opts)
	end

	def predicate_objects(opts)
	  any(opts)
	end


        # todo: write a test for this
	def contains?(st)
	  # barf if st has nil'd fields
	  any(st).size==0
	end

      end

    end
  end
end

###########################################################################
# Experimental class annotation; see ../../lib/squish.rb for core class def.
#
class SimpleSQLGraph
  require 'dbi'
  include RDF4R::Pastiche::SWAP 
  def kb #needed for Cwm-ish stuff only
    #puts "Help, someone wants to dump the KB. (SimpleSQLGraph.kb())"
    return kb_from_sql
  end
  def kb_from_sql
    dump = Formula.new self.ask(Statement.new(nil,nil,nil))
    #puts "Got dump: #{dump.type}"
    return dump
  end
  #  TODO: implement tell() in this class via store_graph()
end

# same again re ../../lib/basicrdf.rb
#
class Graph #<Array
  include RDF4R::Pastiche::SWAP

  # this is an experiment, not needed yet:
  def each
    statements.each do |s| yield s end
  end
end



## NOTES:
#
#
# Should Node subclass String (see timbl/eikeon discussion?)
#
# todo: puts "My local db is: "+local.kb.kb_from_sql 
# OK lots of issues here. Mixings / multi-inheritance confusion w 'kb', 
# which is also an attribute of the SimpleSQL class.
# local.kb calls SimpleSQLGraph's kb method

# we basically are splashing about in an is-a vs has-a mess.
# sometimes RDF4R::Pastiche::Cwm has as instances RDFGraph,
# SimpleSQLGraph etc., other times it sorta wraps them, via the 'kb' field.
# Maybe drop the inheritance thing and do it all with wrapper, less 
# scope for confusion?

#<danbri> I'm apeing the outward form of Cwms main apis, which means when i get around to learning Python DBI I should be able to pick up where i left off talking to my SQL store and to Cwm...
#<DanC> the dbview code has pointers to python DBI docs, btw
