/*
 * 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.
 */
package org.apache.lucene.facet.taxonomy;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import org.apache.lucene.document.Document;
import org.apache.lucene.facet.FacetField;
import org.apache.lucene.facet.FacetTestCase;
import org.apache.lucene.facet.FacetsCollector;
import org.apache.lucene.facet.FacetsCollector.MatchingDocs;
import org.apache.lucene.facet.FacetsCollectorManager;
import org.apache.lucene.facet.FacetsConfig;
import org.apache.lucene.facet.taxonomy.directory.DirectoryTaxonomyReader;
import org.apache.lucene.facet.taxonomy.directory.DirectoryTaxonomyWriter;
import org.apache.lucene.index.IndexWriterConfig;
import org.apache.lucene.search.DocIdSetIterator;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.MatchAllDocsQuery;
import org.apache.lucene.store.Directory;
import org.apache.lucene.tests.index.RandomIndexWriter;
import org.apache.lucene.util.IOUtils;

public class TestTaxonomyFacetLabels extends FacetTestCase {

  private List<Document> prepareDocuments() {
    List<Document> docs = new ArrayList<>();

    Document doc = new Document();
    doc.add(new FacetField("Author", "Bob"));
    doc.add(new FacetField("Publish Date", "2010", "10", "15"));
    docs.add(doc);

    doc = new Document();
    doc.add(new FacetField("Author", "Lisa"));
    doc.add(new FacetField("Publish Date", "2010", "10", "20"));
    docs.add(doc);

    doc = new Document();
    doc.add(new FacetField("Author", "Tom"));
    doc.add(new FacetField("Publish Date", "2012", "1", "1"));
    docs.add(doc);

    doc = new Document();
    doc.add(new FacetField("Author", "Susan"));
    doc.add(new FacetField("Publish Date", "2012", "1", "7"));
    docs.add(doc);

    doc = new Document();
    doc.add(new FacetField("Author", "Frank"));
    doc.add(new FacetField("Publish Date", "1999", "5", "5"));
    docs.add(doc);

    return docs;
  }

  private List<Integer> allDocIds(MatchingDocs m, boolean decreasingDocIds) throws IOException {
    DocIdSetIterator disi = m.bits.iterator();
    List<Integer> docIds = new ArrayList<>();
    while (disi.nextDoc() != DocIdSetIterator.NO_MORE_DOCS) {
      docIds.add(disi.docID());
    }

    if (decreasingDocIds == true) {
      Collections.reverse(docIds);
    }
    return docIds;
  }

  private List<FacetLabel> lookupFacetLabels(
      TaxonomyFacetLabels taxoLabels, List<MatchingDocs> matchingDocs) throws IOException {
    return lookupFacetLabels(taxoLabels, matchingDocs, null, false);
  }

  private List<FacetLabel> lookupFacetLabels(
      TaxonomyFacetLabels taxoLabels, List<MatchingDocs> matchingDocs, String dimension)
      throws IOException {
    return lookupFacetLabels(taxoLabels, matchingDocs, dimension, false);
  }

  private List<FacetLabel> lookupFacetLabels(
      TaxonomyFacetLabels taxoLabels,
      List<MatchingDocs> matchingDocs,
      String dimension,
      boolean decreasingDocIds)
      throws IOException {
    List<FacetLabel> facetLabels = new ArrayList<>();

    for (MatchingDocs m : matchingDocs) {
      TaxonomyFacetLabels.FacetLabelReader facetLabelReader =
          taxoLabels.getFacetLabelReader(m.context);
      List<Integer> docIds = allDocIds(m, decreasingDocIds);
      FacetLabel facetLabel;
      for (Integer docId : docIds) {
        while (true) {
          if (dimension != null) {
            facetLabel = facetLabelReader.nextFacetLabel(docId, dimension);
          } else {
            facetLabel = facetLabelReader.nextFacetLabel(docId);
          }

          if (facetLabel == null) {
            break;
          }
          facetLabels.add(facetLabel);
        }
      }
    }

    return facetLabels;
  }

  public void testBasic() throws Exception {
    Directory dir = newDirectory();
    Directory taxoDir = newDirectory();

    // Writes facet ords to a separate directory from the main index:
    DirectoryTaxonomyWriter taxoWriter =
        new DirectoryTaxonomyWriter(taxoDir, IndexWriterConfig.OpenMode.CREATE);
    RandomIndexWriter writer = new RandomIndexWriter(random(), dir);

    FacetsConfig config = new FacetsConfig();
    config.setHierarchical("Publish Date", true);

    for (Document doc : prepareDocuments()) {
      writer.addDocument(config.build(taxoWriter, doc));
    }

    // NRT open
    IndexSearcher searcher = newSearcher(writer.getReader());
    // NRT open
    TaxonomyReader taxoReader = new DirectoryTaxonomyReader(taxoWriter);

    FacetsCollector fc = searcher.search(new MatchAllDocsQuery(), new FacetsCollectorManager());

    TaxonomyFacetLabels taxoLabels =
        new TaxonomyFacetLabels(taxoReader, FacetsConfig.DEFAULT_INDEX_FIELD_NAME);

    // Check labels for all dimensions
    List<FacetLabel> facetLabels = lookupFacetLabels(taxoLabels, fc.getMatchingDocs());
    assertEquals("Incorrect number of facet labels received", 10, facetLabels.size());

    // Check labels for all dimensions
    assertTrue(
        facetLabels.stream()
            .filter(l -> "Author".equals(l.components[0]))
            .map(l -> l.components[1])
            .collect(Collectors.toSet())
            .equals(Set.of("Bob", "Lisa", "Susan", "Frank", "Tom")));

    assertTrue(
        facetLabels.stream()
            .filter(l -> "Publish Date".equals(l.components[0]))
            .map(l -> String.join("/", l.components[1], l.components[2], l.components[3]))
            .collect(Collectors.toSet())
            .equals(Set.of("2010/10/15", "2010/10/20", "2012/1/1", "2012/1/7", "1999/5/5")));

    // Check labels for a specific dimension
    facetLabels = lookupFacetLabels(taxoLabels, fc.getMatchingDocs(), "Publish Date");
    assertEquals(
        "Incorrect number of facet labels received for 'Publish Date'", 5, facetLabels.size());

    assertTrue(
        facetLabels.stream()
            .map(l -> String.join("/", l.components[1], l.components[2], l.components[3]))
            .collect(Collectors.toSet())
            .equals(Set.of("2010/10/15", "2010/10/20", "2012/1/1", "2012/1/7", "1999/5/5")));

    try {
      facetLabels = lookupFacetLabels(taxoLabels, fc.getMatchingDocs(), null, true);
      fail("IllegalArgumentException was not thrown for using docIds supplied in decreasing order");
    } catch (IllegalArgumentException ae) {
      assertTrue(ae.getMessage().contains("docs out of order"));
    }

    try {
      facetLabels = lookupFacetLabels(taxoLabels, fc.getMatchingDocs(), "Publish Date", true);
      fail("Assertion error was not thrown for using docIds supplied in decreasing order");
    } catch (IllegalArgumentException ae) {
      assertTrue(ae.getMessage().contains("docs out of order"));
    }

    writer.close();
    IOUtils.close(taxoWriter, searcher.getIndexReader(), taxoReader, taxoDir, dir);
  }
}
