require 'rdf/redland'
require 'rdf/redland/uri'

module Redland
  
  class Node
    attr_reader :node
    
    @@node_types = {0 =>'NODE_TYPE_UNKNOWN',
                    1 =>'NODE_TYPE_RESOURCE',
                    2 =>'NODE_TYPE_LITERAL',
                    4 =>'NODE_TYPE_BLANK'}

    # Create an RDF Node
    # 
    # Resource or property node creation
    #   n1 = Node.new(Uri.new("http://example.com/foo"))
    #
    # String literal node creation
    #   n2 = Node.new("foo")
    def initialize(arg=nil)
      if arg.class == String
        temp = arg
        arg = {}
        arg[:literal]= temp
        @node = self.node_from_hash(arg)
      elsif arg.class == Hash
        @node = node_from_hash(arg)
      else
        @node = Node.ensure(arg)
      end
      raise RedlandError.new("Node construction failed") if not @node
      ObjectSpace.define_finalizer(self,Node.create_finalizer(@node))
    end

    # sets this node's value using a blank or URI extracted from a Hash
    # (?? confirm)
    def node_from_hash(hash)
      if hash.key?(:blank)
        node = Redland.librdf_new_node_from_blank_identifier($world.world,hash[:blank])
      end
      if hash.key?(:from_object)
        if hash.key?(:do_not_copy)
          node = hash[:from_object]
        else
          node = Redland.librdf_new_node_from_node(hash[:from_object])
        end
      end
      
      if hash.key?(:uri_string)
        node = Redland.librdf_new_node_from_uri_string($world.world,hash[:uri_string])
      end
      if hash.key?(:literal)
        node = node_from_literal(hash)
      end
      return node
    end

    # sets this node's value using a literal value extracted from a Hash
    # (?? confirm)
    def node_from_literal(hash)
      xml_language = hash.key?(:xml_language) ? hash[:xml_language] : ""
      wf_xml = hash.key?(:wf_xml?) ? 1 : 0
      if hash.key?(:datatype)
        datatype = hash[:datatype]
        node = Redland.librdf_new_node_from_typed_literal($world.world,hash[:literal],xml_language,datatype.uri)
      else
        node = Redland.librdf_new_node_from_literal($world.world,hash[:literal],xml_language,wf_xml)
      end
      return node
    end

    # Sets this node's value to a blank node with the specified ID (?? confirm)
    def bnode(id=nil)
      @node = Redland.librdf_new_node_from_blank_identifier($world.world,id)
    end

    # You shouldn't use this. Used internally for cleanup.
    def Node.create_finalizer(node)
      proc {|id| Redland::librdf_free_node(node) }
    end

    # Creates a new blank node with the specified ID (?? confirm)
    def Node.anon(id)
      n = Node.new('')
      n.bnode(id)
      n
    end

    # Return true if node is a literal
    def literal?
      return (Redland.librdf_node_is_literal(self.node) !=0)
    end

    # Return true if node is a resource with a URI
    def resource?
      return (Redland.librdf_node_is_resource(self.node) !=0)
    end

    # Return true if node is a blank node
    def blank?
      return (Redland.librdf_node_is_blank(self.node) !=0)
    end

    # Equivalency. Only works for comparing two Nodes
    def ==(other)
      return (Redland.librdf_node_equals(self.node,other.node) != 0)
    end

    # Convert this to a string
    def to_s
      if self.literal?
        Redland.librdf_node_get_literal_value(self.node)
      elsif self.blank?
        Redland.librdf_node_get_blank_identifier(self.node)
      elsif self.resource?
        uri = Redland.librdf_node_get_uri(self.node)
        Redland.librdf_uri_to_string(uri) unless uri.nil?
      else
        nil
      end
    end

    # Get the type of this node as a string (Node.node_types)
    def node_type()
      return @@node_types[Redland.librdf_node_get_type(self.node)]
    end
    
    # return a copy of the internal uri
    def uri
      if self.resource?
        return Uri.new(Redland.librdf_node_get_uri(self.node))
      else
        raise NodeTypeError.new("Can't get URI for node type %s" % self.node_type() )
      end
    end

    # returns a Literal if the node is a literal type 
    def literal
      unless self.literal?
        raise NodeTypeError.new("Can't get literal value for node type %s" % self.node_type)
      end
      return Literal.from_node(self.node)
    end

    # returns the blank identifier for this node if the internal model is
    # a blank node
    def blank_identifier()
      unless self.blank?
        raise NodeTypeError.new("Can't get blank identifier for node type %s" % self.node_type)
      else
        return Redland.librdf_node_get_blank_identifier(self.node)
      end
    end

    # Ensures that the argument is a node by constructing one or returning nil
    # (?? confirm)
    def Node.ensure(node)
      case node
      when Node
        my_node = Redland.librdf_new_node_from_node(node.node)
      when SWIG::TYPE_p_librdf_node_s
        my_node = Redland.librdf_new_node_from_node(node)
      when Uri
        my_node = Redland.librdf_new_node_from_uri($world.world,node.uri)
      when URI
        my_node = Redland.librdf_new_node_from_uri_string($world.world,node.to_s)
      else
        my_node = nil
      end
      return my_node
    end

  end

  # A class to make Blank Node creation easier
  #  model = Model.new
  #  dom = BNode.new('dom') # create with optional id
  #
  # I will probably depreciate this and just use Resource.new()
  # to generate blank nodes.
  class BNode < Node

    def initialize(id=nil)
      @node = Redland.librdf_new_node_from_blank_identifier($world.world,id)
      if not @node then raise RedlandError.new("Node construction failed")end
      ObjectSpace.define_finalizer(self,Node.create_finalizer(@node))
    end
    
  end

  # A helper class for namespace.  Caches the nodes for a given namespaces
  #  foaf = Namespace.new('http://xmlns.com/0.1/foaf/')
  #  model.find(nil,foaf['name'],'Dominic')
  #  puts foaf['name'] => http://xmlns.com/0.1/foaf/name
  class Namespace < Node

    def initialize(namespace)
      super(:uri_string=>namespace)
      @nodecache = {}
    end

    def [](item)
      return the_node(item)
    end

    def the_node(localName)
      if not @nodecache.member? localName
        @nodecache[localName] = Node.new(:uri_string=>self.uri.to_s + localName)
      end
      return @nodecache[localName]
    end

    private :the_node
    
  end

  # A literal node
  class Literal < Node
    include Redland
    
    # the string value of the literal
    attr_reader :value
    
    # the language of the literal
    attr_reader :language

    # create a new Literal node
    #  literal = Literal.new('Dominic')
    #  label = Literal.new('Name','en')
    def initialize(str,lang=nil,uri=nil,is_xml=false)
      @value = str
      @language = lang
      is_xml = is_xml==true ? 1 : 0
      if uri
        @node = Redland.librdf_new_node_from_typed_literal($world.world,value,lang,uri.uri)
      else
        @node = Redland.librdf_new_node_from_literal($world.world,value,lang,is_xml)
      end

      raise RedlandError.new("Node construction failed") if not @node
      ObjectSpace.define_finalizer(self,Node.create_finalizer(@node))
    end

    # create a literal from another Node
    def Literal.from_node(node)
      lang = Redland.librdf_node_get_literal_value_language(node) if Redland.librdf_node_get_literal_value_language(node)
      str = Redland.librdf_node_get_literal_value(node)
      hash_uri = Redland.librdf_node_get_literal_value_datatype_uri(node)
      hash_uri = Uri.new(Redland.librdf_uri_to_string(hash_uri)) if hash_uri
      return Literal.new(str,lang,hash_uri)
    end

    # create a literal from and xml string
    #  literal = Literal.from_xml('<em>This is emphasized</em>')
    def Literal.from_xml(str,lang=nil)
      return Literal.new(str,lang,nil,true)
    end
  end
end #Redland

