package org.apache.lucene.facet.range;

/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import org.apache.lucene.facet.params.FacetSearchParams;
import org.apache.lucene.facet.search.FacetRequest;
import org.apache.lucene.facet.search.FacetResult;
import org.apache.lucene.facet.search.FacetResultNode;
import org.apache.lucene.facet.search.FacetsAccumulator;
import org.apache.lucene.facet.search.FacetsAggregator;
import org.apache.lucene.facet.search.FacetsCollector.MatchingDocs;
import org.apache.lucene.facet.taxonomy.CategoryPath;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.NumericDocValues;

/** Uses a {@link NumericDocValues} and accumulates
 *  counts for provided ranges.  This is dynamic (does not
 *  use the taxonomy index or anything from the index
 *  except the NumericDocValuesField). */

public class RangeAccumulator extends FacetsAccumulator {

  static class RangeSet {
    final Range[] ranges;
    final String field;

    public RangeSet(Range[] ranges, String field) {
      this.ranges = ranges;
      this.field = field;
    }
  }

  final List<RangeSet> requests = new ArrayList<RangeSet>();

  public RangeAccumulator(FacetSearchParams fsp, IndexReader reader) {
    super(fsp, reader, null, null);

    for(FacetRequest fr : fsp.facetRequests) {

      if (!(fr instanceof RangeFacetRequest)) {
        throw new IllegalArgumentException("only RangeFacetRequest is supported; got " + fsp.facetRequests.get(0).getClass());
      }

      if (fr.categoryPath.length != 1) {
        throw new IllegalArgumentException("only flat (dimension only) CategoryPath is allowed");
      }

      RangeFacetRequest<?> rfr = (RangeFacetRequest<?>) fr;

      requests.add(new RangeSet(rfr.ranges, rfr.categoryPath.components[0]));
    }
  }

  @Override
  public FacetsAggregator getAggregator() {
    throw new UnsupportedOperationException();
  }

  @Override
  public List<FacetResult> accumulate(List<MatchingDocs> matchingDocs) throws IOException {

    // TODO: test if this is faster (in the past it was
    // faster to do MachingDocs on the inside) ... see
    // patches on LUCENE-4965):
    List<FacetResult> results = new ArrayList<FacetResult>();
    for(int i=0;i<requests.size();i++) {
      RangeSet ranges = requests.get(i);

      int[] counts = new int[ranges.ranges.length];
      for (MatchingDocs hits : matchingDocs) {
        NumericDocValues ndv = hits.context.reader().getNumericDocValues(ranges.field);
        if (ndv == null) {
          continue; // no numeric values for this field in this reader
        }
        final int length = hits.bits.length();
        int doc = 0;
        while (doc < length && (doc = hits.bits.nextSetBit(doc)) != -1) {
          long v = ndv.get(doc);
          // TODO: use interval tree instead of linear search:
          for(int j=0;j<ranges.ranges.length;j++) {
            if (ranges.ranges[j].accept(v)) {
              counts[j]++;
            }
          }

          doc++;
        }
      }

      List<FacetResultNode> nodes = new ArrayList<FacetResultNode>(ranges.ranges.length);
      for(int j=0;j<ranges.ranges.length;j++) {
        nodes.add(new RangeFacetResultNode(ranges.field, ranges.ranges[j], counts[j]));
      }

      FacetResultNode rootNode = new FacetResultNode(-1, 0);
      rootNode.label = new CategoryPath(ranges.field);
      rootNode.subResults = nodes;

      results.add(new FacetResult(searchParams.facetRequests.get(i), rootNode, nodes.size()));
    }

    return results;
  }

  @Override
  public boolean requiresDocScores() {
    return false;
  }
}

@@ -3592,16 +3592,202 @@
 t(doc);%0A
+          // TODO: if all ranges are non-overlapping, we%0A          // should instead do a bin-search up front%0A          // (really, a specialized case of the interval%0A          // tree)%0A
         

add TODO