require 'sqlite3'
require File.join(File.dirname(__FILE__), 'abstract_sql_store')

module RdfContext
  # SQLite3 store context-ware and formula-aware implementation.
  # It stores it's triples in the following partitions:
  # - Asserted non rdf:type statements
  # - Asserted rdf:type statements (in a table which models Class membership). The motivation for this partition is primarily query speed and scalability as most graphs will always have more rdf:type statements than others
  # - All Quoted statements
  #
  #  In addition it persists namespace mappings in a seperate table
  #
  # Based on Python RdfLib SQLite
  class SQLite3Store < AbstractSQLStore
    def initialize(identifier = nil, configuration = {})
      super(identifier, configuration)

      @autocommit_default = false
      @path = configuration[:path] || File.join(Dir.getwd, "#{@internedId}.db")
      @db = open(:path => @path) unless @db
    end
    
    # Opens the store specified by the configuration hash. If
    # create is True a store will be created if it does not already
    # exist. If create is False and a store does not already exist
    # an exception is raised. An exception is also raised if a store
    # exists, but there is insufficient permissions to open the
    # store.
    #
    # @param(Hash) options: Configuration hash
    # <em>_path_:: Path to database file defaults to a file in the current directory based on a hash of the store identifier
    def open(options)
      unless File.exist?(options[:path])
        @db = SQLite3::Database.new(options[:path])
        executeSQL(CREATE_ASSERTED_STATEMENTS_TABLE % @internedId)
        executeSQL(CREATE_ASSERTED_TYPE_STATEMENTS_TABLE % @internedId)
        executeSQL(CREATE_QUOTED_STATEMENTS_TABLE % @internedId)
        executeSQL(CREATE_NS_BINDS_TABLE % @internedId)
        executeSQL(CREATE_LITERAL_STATEMENTS_TABLE % @internedId)
        
        # Create indicies
        {
          asserted_table => {
            "#{@internedId}_A_termComb_index" => %w(termComb),
            "#{@internedId}_A_s_index" => %w(subject),
            "#{@internedId}_A_p_index" => %w(predicate),
            "#{@internedId}_A_o_index" => %w(object),
            "#{@internedId}_A_c_index" => %w(context),
          },
          asserted_type_table => {
            "#{@internedId}_T_termComb_index" => %w(termComb),
            "#{@internedId}_T_member_index" => %w(member),
            "#{@internedId}_T_klass_index" => %w(klass),
            "#{@internedId}_T_c_index" => %w(context),
          },
          literal_table => {
            "#{@internedId}_L_termComb_index" => %w(termComb),
            "#{@internedId}_L_s_index" => %w(subject),
            "#{@internedId}_L_p_index" => %w(predicate),
            "#{@internedId}_L_c_index" => %w(context),
          },
          quoted_table => {
            "#{@internedId}_Q_termComb_index" => %w(termComb),
            "#{@internedId}_Q_s_index" => %w(subject),
            "#{@internedId}_Q_p_index" => %w(predicate),
            "#{@internedId}_Q_o_index" => %w(object),
            "#{@internedId}_Q_c_index" => %w(context),
          },
          namespace_binds => {
            "#{@internedId}_uri_index" => %w(uri),
          }
        }.each_pair do |tablename, indicies|
          indicies.each_pair do |index, columns|
            executeSQL("CREATE INDEX #{index} on #{tablename} ('#{columns.join(', ')}')")
          end
        end
      end

      raise StoreException.new("Attempt to open missing database file #{options[:path]}") unless File.exist?(options[:path])
      @db = SQLite3::Database.new(options[:path])
    end

    # Destroy databse
    #
    # @param(Hash) options: Configuration hash
    # <em>_path_:: Path to database file
    def destroy(options = {})
      File.delete(@path)
    end

    protected

    # Escape backslashes and single quotes
    def escapeQuotes(qstr)
      qstr.nil? ? "" : qstr.gsub("\\","\\\\").gsub("'", "\\'").gsub('"', '""')
    end

    # Where clase utility functions
    def buildSubjClause(subject, tableName)
  #    case subject
  #    when REGEXTerm
  #    when Array
  #    when QuotedGraph
  #    else
        ["#{tableName}.subject=?", subject] if subject
  #    end
    end

    def buildPredClause(predicate, tableName)
  #    case predicate
  #    when REGEXTerm
  #    when Array
  #    else
        ["#{tableName}.predicate=?", predicate] if predicate
  #    end
    end

    # Where clase utility functions
    def buildObjClause(object, tableName)
  #    case object
  #    when REGEXTerm
  #    when Array
  #    when QuotedGraph
  #    else
        ["#{tableName}.object=?", object] if object
  #    end
    end

    # Where clase utility functions
    def buildContextClause(context, tableName)
      context = normalizeTerm(context) if context

  #    case context
  #    when REGEXTerm
  #    when Array
  #    else
        ["#{tableName}.context=?", context] if context
  #    end
    end

    # Where clase utility functions
    def buildTypeMemberClause(subject, tableName)
  #    case context
  #    when REGEXTerm
  #    when Array
  #    else
        ["#{tableName}.member=?", subject] if subject
  #    end
    end

    # Where clase utility functions
    def buildTypeClassClause(object, tableName)
  #    case context
  #    when REGEXTerm
  #    else
        ["#{tableName}.klass=?", object] if object
  #    end
    end

    # This takes the query string and parameters and (depending on the SQL implementation) either fill in
    # the parameter in-place or pass it on to the DB impl (if it supports this).
    # The default (here) is to fill the parameters in-place surrounding each param with quote characters
    #
    # Yields each row
    def executeSQL(qStr, *params, &block)
      @statement_cache ||= {}
      #@statement_cache[qStr] ||= @db.prepare(qStr)
      @statement_cache[qStr] ||= qStr

      puts "executeSQL: '#{qStr}', #{params.join(", ")}" if $DEBUG
      if block_given?
        @db.execute(@statement_cache[qStr], *params) do |row|
          puts "executeSQL res: #{row.inspect}" if $DEBUG
          yield(row)
        end
      else
        @db.execute(@statement_cache[qStr], *params)
      end
    end

    CREATE_ASSERTED_STATEMENTS_TABLE = %(
    CREATE TABLE %s_asserted_statements (
        subject       text not NULL,
        predicate     text not NULL,
        object        text not NULL,
        context       text not NULL,
        termComb      tinyint unsigned not NULL))

    CREATE_ASSERTED_TYPE_STATEMENTS_TABLE = %(
    CREATE TABLE %s_type_statements (
        member        text not NULL,
        klass         text not NULL,
        context       text not NULL,
        termComb      tinyint unsigned not NULL))

    CREATE_LITERAL_STATEMENTS_TABLE = %(
    CREATE TABLE %s_literal_statements (
        subject       text not NULL,
        predicate     text not NULL,
        object        text,
        context       text not NULL,
        termComb      tinyint unsigned not NULL,
        objLanguage   varchar(3),
        objDatatype   text))

    CREATE_QUOTED_STATEMENTS_TABLE = %(
    CREATE TABLE %s_quoted_statements (
        subject       text not NULL,
        predicate     text not NULL,
        object        text,
        context       text not NULL,
        termComb      tinyint unsigned not NULL,
        objLanguage   varchar(3),
        objDatatype   text))

    CREATE_NS_BINDS_TABLE = %(
    CREATE TABLE %s_namespace_binds (
        prefix        varchar(20) UNIQUE not NULL,
        uri           text,
        PRIMARY KEY (prefix)))

  end
end