module Neo4j
  module Batch
    class Indexer

      delegate :field_types, :entity_type, :indexer_for, :indexed_value_for, :lucene_config, :via_relationships, :to => :@wrapped_indexer

      def initialize(wrapped_indexer)
        @wrapped_indexer = wrapped_indexer
        @parent_indexers = wrapped_indexer.parent_indexers.collect{|i| Indexer.new(i)}
      end

      def indexer_for_field(field, rel_type)
        dsl = via_relationships[field]
        return nil if dsl.nil?
        return nil if dsl.rel_type != rel_type

        target_class = dsl.target_class
        self.class.instance_for(target_class)
      end

      def index_node_via_rel(rel_type, other, node_props) #:nodoc:
        return if node_props.empty? || via_relationships.empty?
        props_copy = node_props.clone

        while !props_copy.empty?
          indexer     = indexer_for_field(props_copy.keys.first, rel_type)

          # put all other fields that are not of this index type in a new hash
          other_index = {}
          # delete all fields that are not of this index
          props_copy.delete_if { |k, v| indexer != indexer_for_field(k, rel_type) && other_index[k] = v }
          # add all those properties for this index
          indexer && indexer.index_entity(other, props_copy)

          # continue with the remaining fields
          props_copy = other_index
        end
      end

      def index_entity(entity_id, props)
        filter_props = props.keys.inject({}) { |memo, field| memo[field] = indexed_value_for(field, props[field]) if field_types.has_key?(field); memo }

        while !filter_props.empty?
          # pick one index type
          index       = batch_index_for_field(filter_props.keys[0])
          # put all other fields that are not of this index type in a new hash
          other_index = {}
          # delete all fields that are not of this index
          filter_props.delete_if { |k, v| index != batch_index_for_field(k) && other_index[k] = v }
          # add all those properties for this index
          index.add(entity_id, filter_props)
          # continue with the remaining fields
          filter_props = other_index
        end

        @parent_indexers.each { |i| i.index_entity(entity_id, props) }
      end

      def index_flush
        return nil if @batch_indexes.nil?
        @batch_indexes.values.each {|index| index.flush}
      end

      def index_get(key, value, index_type)
        index = @batch_indexes && @batch_indexes[index_type]
        return nil if index.nil?
        index.get(key,value)
      end

      def index_query(query, index_type)
        index = @batch_indexes && @batch_indexes[index_type]
        return nil if index.nil?
        index.query(query)
      end


      def batch_index_for_field(field)
        type                 = field_types[field]
        @batch_indexes       ||= {}
        @batch_indexes[type] ||= create_batch_index_with(type)
      end

      def create_batch_index_with(type)
        index_config = lucene_config(type)

        if entity_type == :node
          self.class.index_provider.node_index(indexer_for.index_names[type], index_config)
        else
          self.class.index_provider.relationship_index(indexer_for.index_names[type], index_config)
        end
        
      end

      class << self
        attr_accessor :index_provider
      
        def instance_for(clazz)
          @instances ||= {}
          @instances[clazz.to_s] ||= Indexer.new(clazz._indexer)
        end

        # Mostly for testing
        def clear_all_instances
          @instances = nil
        end
      end
    end
  end
end