package org.apache.lucene.facet.search;

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

import org.apache.lucene.index.Term;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.TermQuery;
import org.junit.Before;
import org.junit.Test;

import org.apache.lucene.facet.FacetTestBase;
import org.apache.lucene.facet.search.FacetsAccumulator;
import org.apache.lucene.facet.search.ScoredDocIDs;
import org.apache.lucene.facet.search.ScoredDocIDsIterator;
import org.apache.lucene.facet.search.ScoredDocIdCollector;
import org.apache.lucene.facet.search.StandardFacetsAccumulator;
import org.apache.lucene.facet.search.params.CountFacetRequest;
import org.apache.lucene.facet.search.params.FacetSearchParams;
import org.apache.lucene.facet.search.params.ScoreFacetRequest;
import org.apache.lucene.facet.search.results.FacetResult;
import org.apache.lucene.facet.search.results.FacetResultNode;
import org.apache.lucene.facet.taxonomy.CategoryPath;

/**
 * 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.
 */

/** Test ScoredDocIdCollector. */
public class TestScoredDocIdCollector extends FacetTestBase {

  @Override
  @Before
  public void setUp() throws Exception {
    super.setUp();
    initIndex();
  }

  @Override
  public void tearDown() throws Exception {
    closeAll();
    super.tearDown();
  }

  @Test
  public void testConstantScore() throws Exception {
    // test that constant score works well

    Query q = new TermQuery(new Term(CONTENT_FIELD, "white"));
    if (VERBOSE) {
      System.out.println("Query: " + q);
    }
    float constScore = 17.0f;
    ScoredDocIdCollector dCollector = ScoredDocIdCollector.create(indexReader
        .maxDoc(), false); // scoring is disabled
    dCollector.setDefaultScore(constScore);
    searcher.search(q, dCollector);

    // verify by doc scores at the level of doc-id-iterator
    ScoredDocIDs scoredDocIDs = dCollector.getScoredDocIDs();
    assertEquals("Wrong number of matching documents!", 2, scoredDocIDs.size());
    ScoredDocIDsIterator docItr = scoredDocIDs.iterator();
    while (docItr.next()) {
      assertEquals("Wrong score for doc " + docItr.getDocID(), constScore,
          docItr.getScore(), Double.MIN_VALUE);
    }

    // verify by facet values
    List<FacetResult> countRes = findFacets(scoredDocIDs, getFacetedSearchParams());
    List<FacetResult> scoreRes = findFacets(scoredDocIDs, sumScoreSearchParams());

    assertEquals("Wrong number of facet count results!", 1, countRes.size());
    assertEquals("Wrong number of facet score results!", 1, scoreRes.size());

    FacetResultNode parentCountRes = countRes.get(0).getFacetResultNode();
    FacetResultNode parentScoreRes = scoreRes.get(0).getFacetResultNode();

    assertEquals("Wrong number of top count aggregated categories!", 3,
        parentCountRes.getNumSubResults());
    assertEquals("Wrong number of top score aggregated categories!", 3,
        parentScoreRes.getNumSubResults());

    // rely on that facet value is computed as doc-score, and
    // accordingly compare values of the two top-category results.

    FacetResultNode[] countResNodes = resultNodesAsArray(parentCountRes);
    FacetResultNode[] scoreResNodes = resultNodesAsArray(parentScoreRes);

    for (int i = 0; i < scoreResNodes.length; i++) {
      assertEquals("Ordinals differ!", 
          countResNodes[i].getOrdinal(), scoreResNodes[i].getOrdinal());
      assertEquals("Wrong scores!", 
          constScore * countResNodes[i].getValue(),
          scoreResNodes[i].getValue(), 
          Double.MIN_VALUE);
    }
  }

  // compute facets with certain facet requests and docs
  private List<FacetResult> findFacets(ScoredDocIDs sDocids,
      FacetSearchParams facetSearchParams) throws IOException {
    FacetsAccumulator fAccumulator = new StandardFacetsAccumulator(
        facetSearchParams, indexReader, taxoReader);
    List<FacetResult> res = fAccumulator.accumulate(sDocids);

    // Results are ready, printing them...
    int i = 0;
    for (FacetResult facetResult : res) {
      if (VERBOSE) {
        System.out.println("Res " + (i++) + ": " + facetResult);
      }
    }

    return res;
  }

  @Test
  public void testOutOfOrderCollectionScoringEnabled() throws Exception {
    assertFalse(
        "when scoring enabled, out-of-order collection should not be supported",
        ScoredDocIdCollector.create(1, true).acceptsDocsOutOfOrder());
  }

  @Test
  public void testOutOfOrderCollectionScoringDisabled() throws Exception {
    // This used to fail, because ScoredDocIdCollector.acceptDocsOutOfOrder
    // returned true, even when scoring was enabled.
    final int[] docs = new int[] { 1, 0, 2 }; // out of order on purpose

    ScoredDocIdCollector sdic = ScoredDocIdCollector.create(docs.length, false);
    assertTrue(
        "when scoring disabled, out-of-order collection should be supported",
        sdic.acceptsDocsOutOfOrder());
    for (int i = 0; i < docs.length; i++) {
      sdic.collect(docs[i]);
    }

    assertEquals("expected 3 documents but got " + sdic.getScoredDocIDs().size(), 3, sdic.getScoredDocIDs().size());
    ScoredDocIDsIterator iter = sdic.getScoredDocIDs().iterator();
    Arrays.sort(docs);
    for (int i = 0; iter.next(); i++) {
      assertEquals("expected doc " + docs[i], docs[i], iter.getDocID());
    }
  }

  /* use a scoring aggregator */
  private FacetSearchParams sumScoreSearchParams() {
    // this will use default faceted indexing params, not altering anything about indexing
    FacetSearchParams res = super.getFacetedSearchParams();
    res.addFacetRequest(new ScoreFacetRequest(new CategoryPath("root", "a"), 10));
    return res;
  }

  @Override
  protected FacetSearchParams getFacetedSearchParams() {
    FacetSearchParams res = super.getFacetedSearchParams();
    res.addFacetRequest(new CountFacetRequest(new CategoryPath("root","a"), 10));
    return res;
  }

}