class ThinkingSphinx::FacetSearch
  include Enumerable

  attr_reader   :options
  attr_accessor :query

  def initialize(query = nil, options = {})
    query, options   = nil, query if query.is_a?(Hash)
    @query, @options = query, options
    @hash             = {}
  end

  def [](key)
    populate

    @hash[key]
  end

  def each(&block)
    populate

    @hash.each(&block)
  end

  def for(facet_values)
    filter_facets = facet_values.keys.collect { |key|
      facets.detect { |facet| facet.name == key.to_s }
    }

    ThinkingSphinx::Search.new query, options.merge(
      :indices => index_names_for(*filter_facets)
    ).merge(Filter.new(facets, facet_values).to_hash)
  end

  def populate
    return if @populated

    batch = ThinkingSphinx::BatchedSearch.new
    facets.each do |facet|
      batch.searches << ThinkingSphinx::Search.new(query, options_for(facet))
    end

    batch.populate ThinkingSphinx::Middlewares::RAW_ONLY

    facets.each_with_index do |facet, index|
      @hash[facet.name.to_sym] = facet.results_from batch.searches[index].raw
    end

    @hash[:class] = @hash[:sphinx_internal_class]

    @populated = true
  end

  def populated?
    @populated
  end

  def to_hash
    populate

    @hash
  end

  private

  def configuration
    ThinkingSphinx::Configuration.instance
  end

  def facets
    @facets ||= properties.group_by(&:name).collect { |name, matches|
      ThinkingSphinx::Facet.new name, matches
    }
  end

  def properties
    properties = indices.collect(&:facets).flatten
    if options[:facets].present?
      properties = properties.select { |property|
        options[:facets].include? property.name.to_sym
      }
    end
    properties
  end

  def index_names_for(*facets)
    facet_names(
      indices.select do |index|
        facets.all? { |facet| facet_names(index.facets).include?(facet.name) }
      end
    )
  end

  def facet_names(facets)
    facets.collect(&:name)
  end

  def indices
    @indices ||= configuration.index_set_class.new(
      options.slice(:classes, :indices)
    )
  end

  def max_matches
    configuration.settings['max_matches'] || 1000
  end

  def limit
    limit = options[:limit] || options[:per_page] || max_matches
  end

  def options_for(facet)
    options.merge(
      :select      => [(options[:select] || '*'),
        "#{ThinkingSphinx::SphinxQL.group_by[:select]}",
        "#{ThinkingSphinx::SphinxQL.count[:select]}"
      ].join(', '),
      :group_by    => facet.name,
      :indices     => index_names_for(facet),
      :max_matches => max_matches,
      :limit       => limit
    )
  end

  class Filter
    def initialize(facets, hash)
      @facets, @hash = facets, hash
    end

    def to_hash
      @hash.keys.inject({}) { |options, key|
        type = @facets.detect { |facet| facet.name == key.to_s }.filter_type
        options[type] ||= {}
        options[type][key] = @hash[key]
        options
      }
    end
  end
end
