module Neo4j
  module Wrapper
    module HasN

      # The object created by a has_n or has_one Neo4j::NodeMixin class method which enables creating and traversal of nodes.
      #
      # @see Neo4j::Wrapper::HasN::ClassMethods
      class Nodes
        include Enumerable
        include Neo4j::Core::ToJava

        def initialize(node, decl_rel, cypher_query_hash = nil, &cypher_block) # :nodoc:
          @node = node
          @decl_rel = decl_rel
          @cypher_block = cypher_block
          @cypher_query_hash = cypher_query_hash
          self.class.define_rule_methods_on(self, decl_rel)
        end

        def self.define_rule_methods_on(instance, decl_rel )
          rule_node = Neo4j::Wrapper::Rule::Rule.rule_node_for(decl_rel.target_class)

          singelton = class << instance;
            self;
          end

          rule_node && rule_node.rules.each do |rule|
            next if rule.rule_name == :all
            singelton.send(:define_method, rule.rule_name) do |*cypher_query_hash, &cypher_block|
              proc = Proc.new do |m|
                m.incoming(rule.rule_name)
                if cypher_block
                  self.instance_exec(m, &cypher_block)
                end
              end
              query(cypher_query_hash.first, &proc)
            end
          end
        end

        def to_s
          if @cypher_block || @cypher_query_hash
            query(@cypher_query_hash, &@cypher_block).to_s
          else
            "HasN::Nodes [#{@decl_rel.dir}, id: #{@node.neo_id} type: #{@decl_rel && @decl_rel.rel_type} decl_rel:#{@decl_rel}]"
          end
        end

        def query(cypher_query_hash = nil, &block)
          Neo4j::Core::Traversal::CypherQuery.new(@node.neo_id, @decl_rel.dir, [@decl_rel.rel_type], cypher_query_hash, &block)
        end

        # Traverse the relationship till the index position
        # @return [Neo4j::NodeMixin,Neo4j::Node,nil] the node at the given position
        def [](index)
          i = 0
          each { |x| return x if i == index; i += 1 }
          nil # out of index
        end

        # Pretend we are an array - this is necessarily for Rails actionpack/actionview/formhelper to work with this
        def is_a?(type)
          # ActionView requires this for nested attributes to work
          return true if Array == type
          super
        end

        # Required by the Enumerable mixin.
        def each
          if @cypher_block || @cypher_query_hash
            query(@cypher_query_hash, &@cypher_block).each { |i| yield i }
          else
            @decl_rel.each_node(@node) { |n| yield n } # Should use yield here as passing &block through doesn't always work (why?)
          end
        end

        # returns none wrapped nodes, you may get better performance using this method
        def _each
          @decl_rel._each_node(@node) { |n| yield n }
        end

        # Returns an real ruby array.
        def to_ary
          self.to_a
        end

        # Returns true if there are no node in this type of relationship
        def empty?
          first == nil
        end


        # Creates a relationship instance between this and the other node.
        # Returns the relationship object
        def new(other)
          @decl_rel.create_relationship_to(@node, other)
        end


        # Creates a relationship between this and the other node.
        #
        # @example Person includes the Neo4j::NodeMixin and declares a has_n :friends
        #
        #   p = Person.new # Node has declared having a friend type of relationship
        #   n1 = Node.new
        #   n2 = Node.new
        #
        #   p.friends << n2 << n3
        #
        # @return self
        def <<(other)
          @decl_rel.create_relationship_to(@node, other)
          self
        end
      end

    end
  end
end