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

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CountDownLatch;
import org.apache.lucene.document.Document;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.MultiTerms;
import org.apache.lucene.index.Term;
import org.apache.lucene.index.Terms;
import org.apache.lucene.index.TermsEnum;
import org.apache.lucene.store.Directory;
import org.apache.lucene.tests.analysis.MockAnalyzer;
import org.apache.lucene.tests.index.RandomIndexWriter;
import org.apache.lucene.tests.util.LineFileDocs;
import org.apache.lucene.tests.util.LuceneTestCase;
import org.apache.lucene.tests.util.TestUtil;
import org.apache.lucene.util.BytesRef;

public class TestSameScoresWithThreads extends LuceneTestCase {

  public void test() throws Exception {
    final Directory dir = newDirectory();
    MockAnalyzer analyzer = new MockAnalyzer(random());
    analyzer.setMaxTokenLength(TestUtil.nextInt(random(), 1, IndexWriter.MAX_TERM_LENGTH));
    final RandomIndexWriter w = new RandomIndexWriter(random(), dir, analyzer);
    LineFileDocs docs = new LineFileDocs(random());
    int charsToIndex = atLeast(100000);
    int charsIndexed = 0;
    // System.out.println("bytesToIndex=" + charsToIndex);
    while (charsIndexed < charsToIndex) {
      Document doc = docs.nextDoc();
      charsIndexed += doc.get("body").length();
      w.addDocument(doc);
      // System.out.println("  bytes=" + charsIndexed + " add: " + doc);
    }
    IndexReader r = w.getReader();
    // System.out.println("numDocs=" + r.numDocs());
    w.close();

    final IndexSearcher s = newSearcher(r);
    Terms terms = MultiTerms.getTerms(r, "body");
    int termCount = 0;
    TermsEnum termsEnum = terms.iterator();
    while (termsEnum.next() != null) {
      termCount++;
    }
    assertTrue(termCount > 0);

    // Target ~10 terms to search:
    double chance = 10.0 / termCount;
    termsEnum = terms.iterator();
    final Map<BytesRef, TopDocs> answers = new HashMap<>();
    while (termsEnum.next() != null) {
      if (random().nextDouble() <= chance) {
        BytesRef term = BytesRef.deepCopyOf(termsEnum.term());
        answers.put(term, s.search(new TermQuery(new Term("body", term)), 100));
      }
    }

    if (!answers.isEmpty()) {
      final CountDownLatch startingGun = new CountDownLatch(1);
      int numThreads = TEST_NIGHTLY ? TestUtil.nextInt(random(), 2, 5) : 2;
      Thread[] threads = new Thread[numThreads];
      for (int threadID = 0; threadID < numThreads; threadID++) {
        Thread thread =
            new Thread() {
              @Override
              public void run() {
                try {
                  startingGun.await();
                  for (int i = 0; i < 20; i++) {
                    List<Map.Entry<BytesRef, TopDocs>> shuffled =
                        new ArrayList<>(answers.entrySet());
                    Collections.shuffle(shuffled, random());
                    for (Map.Entry<BytesRef, TopDocs> ent : shuffled) {
                      TopDocs actual = s.search(new TermQuery(new Term("body", ent.getKey())), 100);
                      TopDocs expected = ent.getValue();
                      assertEquals(expected.totalHits.value, actual.totalHits.value);
                      assertEquals(
                          "query=" + ent.getKey().utf8ToString(),
                          expected.scoreDocs.length,
                          actual.scoreDocs.length);
                      for (int hit = 0; hit < expected.scoreDocs.length; hit++) {
                        assertEquals(expected.scoreDocs[hit].doc, actual.scoreDocs[hit].doc);
                        // Floats really should be identical:
                        assertTrue(expected.scoreDocs[hit].score == actual.scoreDocs[hit].score);
                      }
                    }
                  }
                } catch (Exception e) {
                  throw new RuntimeException(e);
                }
              }
            };
        threads[threadID] = thread;
        thread.start();
      }
      startingGun.countDown();
      for (Thread thread : threads) {
        thread.join();
      }
    }
    docs.close();
    r.close();
    dir.close();
  }
}
