module Neo4j


  # Represents a node in the Neo4j space.
  # 
  # Is a wrapper around a Java Neo4j::Node (org.neo4j.graphdb.Node)
  # The following methods are delegated to the Java Neo4j::Node
  #   []=, [], property?, props, update, neo_id, rels, rel?, to_param
  #   rel, del, list?, list, lists, print, add_rel, outgoing, incoming,
  #   next, prev, next=, prev=, head
  #
  # Those methods are defined in included mixins
  # This mixin also include the class method in the
  #
  # === Included Mixins
  # * Neo4j::JavaPropertyMixin - instance methods for properties
  # * Neo4j::JavaNodeMixin - instance methods
  # * Neo4j::JavaListMixin - instance methods for list methods
  # * Neo4j::RelClassMethods - class methods for generating relationship accessors
  # * Neo4j::PropertyClassMethods - class methods for generating property accessors
  #
  module NodeMixin
    extend Forwardable

    def_delegators :@_java_node, :[]=, :[], :property?, :props, :update, :neo_id, :rels, :rel?, :to_param,
                   :rel, :del, :list?, :list, :lists, :print, :print_sub, :add_rel, :outgoing, :incoming,
                   :add_list_item_methods, :next, :prev, :next=, :prev=, :head # used for has_list, defined in Neo4j::JavaListMixin


    # --------------------------------------------------------------------------
    # Initialization methods
    #


    # Initialize the the neo node for this instance.
    # Will create a new transaction if one is not already running.
    # 
    # Does
    # * sets the neo4j property '_classname' to self.class.to_s
    # * creates a neo4j node java object (in @_java_node)
    # * calls init_node if that is defined in the current class.
    #
    # If you want to provide your own initialize method you should instead implement the
    # method init_node method.
    #
    # === Example
    #
    #   class MyNode
    #     include Neo4j::NodeMixin
    #
    #     def init_node(name, age)
    #        self[:name] = name
    #        self[:age] = age
    #     end
    #   end
    #
    #   node = MyNode('jimmy', 23)
    #   # or also possible
    #   node = MyNode :name => 'jimmy', :age => 12
    #
    # The init_node is only called when the node is created in the database.
    # The initialize method is used both for to purposes:
    # loading an already existing node from the Neo4j database and creating a new node in the database.
    #
    def initialize(*args)
      # was a neo java node provided ?
      if args.length == 1 && args[0].kind_of?(org.neo4j.graphdb.Node)
        # yes, it was loaded from the database
        init_with_node(args[0])
      elsif self.respond_to?(:init_node)
        # does the class provide an initialization method ?
        init_without_node({})
        init_node(*args)
      else
        # no, but maybe it had a hash of properties to initialize it with, create node
        init_without_node(args[0] || {})
      end
      # was a block given in order to initialize the neo4j node ?
      yield self if block_given?
      # must call super with no arguments so that chaining of the initialize method works
      super()
    end


    # Inits this node with the specified java neo node
    #
    def init_with_node(java_node) # :nodoc:
      @_java_node = java_node
      java_node._wrapper=self
    end

    # Returns the org.neo4j.graphdb.Node wrapped object
    def _java_node
      @_java_node
    end

    # Creates a new node and initialize with given properties.
    #
    def init_without_node(props) # :nodoc:
      props[:_classname] = self.class.to_s
      @_java_node = Neo4j.create_node props
      update_index if props && !props.empty?
      @_java_node._wrapper = self
      Neo4j.event_handler.node_created(self)
    end


    # Since we sometimes don't know if we have a java node or a wrapped Ruby node we need this so that we
    # always can call this method
    def wrapper # :nodoc:
      self
    end


    # --------------------------------------------------------------------------
    # Property methods
    #


    # Creates a struct class containing all properties of this class.
    # This value object can be used from Ruby on Rails RESTful routing.
    #
    # ==== Example
    #
    # h = Person.value_object.new
    # h.name    # => nil
    # h.name='kalle'
    # h[:name]   # => 'kalle'
    #
    # ==== Returns
    # a value object struct
    #
    def value_object
      vo = self.class.value_object.new
      vo._update(props)
      vo
    end


    # --------------------------------------------------------------------------
    # Equal and hash methods
    #

    def equal?(o)
      eql?(o)
    end

    def eql?(o)
      o.kind_of?(NodeMixin) && o._java_node == @_java_node
    end

    def ==(o)
      eql?(o)
    end

    def hash
      @_java_node.hashCode
    end


    # --------------------------------------------------------------------------
    # Update and Delete methods
    #


    # Specifies which relationships should be ignored when trying to cascade delete a node.
    # If a node does not have any relationships (except those specified here to ignore) it will be cascade deleted
    #
    def ignore_incoming_cascade_delete?(relationship) # :nodoc:
      # ignore relationship with property _cascade_delete_incoming
      relationship.property?(:_cascade_delete_incoming)
    end

    # Updates the index for this node.
    # This method will be automatically called when needed
    # (a property changed or a relationship was created/deleted)
    #
    def update_index # :nodoc:
      self.class.indexer.index(self)
    end

    # --------------------------------------------------------------------------
    # Relationship methods
    #

    # Returns a Neo4j::Relationships::NodeTraverser object for traversing nodes from and to this node.
    # The Neo4j::Relationships::NodeTraverser is an Enumerable that returns Neo4j::NodeMixin objects.
    #
    # ==== Example
    #
    #   person_node.traverse.outgoing(:friends).each { ... }
    #   person_node.traverse.outgoing(:friends).raw.each { }
    #
    # The raw false parameter means that the ruby wrapper object will not be loaded, instead the raw Java Neo4j object will be used,
    # it might improve the performance.
    #
    def traverse(*args)
      if args.empty?
        Neo4j::Relationships::NodeTraverser.new(self)
      else
        @_java_node.traverse(*args)
      end

    end


    # --------------------------------------------------------------------------
    # Private methods
    #

    def _to_java_direction(dir) # :nodoc:
      case dir
        when :outgoing
          org.neo4j.graphdb.Direction::OUTGOING
        when :incoming
          org.neo4j.graphdb.Direction::INCOMING
        when :both
          org.neo4j.graphdb.Direction::BOTH
        else
          raise "Unknown parameter: '#{dir}', only accept :outgoing, :incoming or :both"
      end
    end


    # --------------------------------------------------------------------------
    # Hooks
    #


    # Adds class methods from
    #
    # * Neo4j::RelClassMethods
    # * Neo4j::PropertyClassMethods
    #
    def self.included(c) # :nodoc:
      c.instance_eval do
        # these constants are used in the Neo4j::RelClassMethods and Neo4j::PropertyClassMethods
        # they are defined here since they should only be defined once -
        # all subclasses share the same index, declared properties and index_updaters
        const_set(:ROOT_CLASS, self)
        const_set(:DECL_RELATIONSHIPS, {})
        const_set(:PROPERTIES_INFO, {})
      end unless c.const_defined?(:DECL_RELATIONSHIPS)

      c.extend Neo4j::RelClassMethods
      c.extend Neo4j::PropertyClassMethods
    end
  end
end