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

import java.io.IOException;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.apache.lucene.document.FieldType;
import org.apache.lucene.document.StoredField;
import org.apache.lucene.document.TextField;
import org.apache.lucene.store.Directory;
import org.apache.lucene.tests.analysis.MockAnalyzer;
import org.apache.lucene.tests.util.FailOnNonBulkMergesInfoStream;
import org.apache.lucene.tests.util.LuceneTestCase;
import org.junit.Test;

public class TestConsistentFieldNumbers extends LuceneTestCase {

  @Test
  public void testSameFieldNumbersAcrossSegments() throws Exception {
    for (int i = 0; i < 2; i++) {
      Directory dir = newDirectory();
      IndexWriter writer =
          new IndexWriter(
              dir,
              newIndexWriterConfig(new MockAnalyzer(random()))
                  .setMergePolicy(NoMergePolicy.INSTANCE));

      Document d1 = new Document();
      d1.add(new TextField("f1", "first field", Field.Store.YES));
      d1.add(new TextField("f2", "second field", Field.Store.YES));
      writer.addDocument(d1);

      if (i == 1) {
        writer.close();
        writer =
            new IndexWriter(
                dir,
                newIndexWriterConfig(new MockAnalyzer(random()))
                    .setMergePolicy(NoMergePolicy.INSTANCE));
      } else {
        writer.commit();
      }

      Document d2 = new Document();
      d2.add(new TextField("f2", "second field", Field.Store.NO));
      d2.add(new TextField("f1", "first field", Field.Store.YES));
      d2.add(new TextField("f3", "third field", Field.Store.NO));
      d2.add(new TextField("f4", "fourth field", Field.Store.NO));
      writer.addDocument(d2);

      writer.close();

      SegmentInfos sis = SegmentInfos.readLatestCommit(dir);
      assertEquals(2, sis.size());

      FieldInfos fis1 = IndexWriter.readFieldInfos(sis.info(0));
      FieldInfos fis2 = IndexWriter.readFieldInfos(sis.info(1));

      assertEquals("f1", fis1.fieldInfo(0).name);
      assertEquals("f2", fis1.fieldInfo(1).name);
      assertEquals("f1", fis2.fieldInfo(0).name);
      assertEquals("f2", fis2.fieldInfo(1).name);
      assertEquals("f3", fis2.fieldInfo(2).name);
      assertEquals("f4", fis2.fieldInfo(3).name);

      writer = new IndexWriter(dir, newIndexWriterConfig(new MockAnalyzer(random())));
      writer.forceMerge(1);
      writer.close();

      sis = SegmentInfos.readLatestCommit(dir);
      assertEquals(1, sis.size());

      FieldInfos fis3 = IndexWriter.readFieldInfos(sis.info(0));

      assertEquals("f1", fis3.fieldInfo(0).name);
      assertEquals("f2", fis3.fieldInfo(1).name);
      assertEquals("f3", fis3.fieldInfo(2).name);
      assertEquals("f4", fis3.fieldInfo(3).name);

      dir.close();
    }
  }

  @Test
  public void testAddIndexes() throws Exception {
    Directory dir1 = newDirectory();
    Directory dir2 = newDirectory();
    IndexWriter writer =
        new IndexWriter(
            dir1,
            newIndexWriterConfig(new MockAnalyzer(random()))
                .setMergePolicy(NoMergePolicy.INSTANCE));

    Document d1 = new Document();
    d1.add(new TextField("f1", "first field", Field.Store.YES));
    d1.add(new TextField("f2", "second field", Field.Store.YES));
    writer.addDocument(d1);

    writer.close();
    writer =
        new IndexWriter(
            dir2,
            newIndexWriterConfig(new MockAnalyzer(random()))
                .setMergePolicy(NoMergePolicy.INSTANCE));

    Document d2 = new Document();
    d2.add(new TextField("f2", "second field", Field.Store.YES));
    d2.add(new TextField("f1", "first field", Field.Store.YES));
    d2.add(new TextField("f3", "third field", Field.Store.YES));
    d2.add(new TextField("f4", "fourth field", Field.Store.YES));
    writer.addDocument(d2);

    writer.close();

    writer =
        new IndexWriter(
            dir1,
            newIndexWriterConfig(new MockAnalyzer(random()))
                .setMergePolicy(NoMergePolicy.INSTANCE));
    writer.addIndexes(dir2);
    writer.close();

    SegmentInfos sis = SegmentInfos.readLatestCommit(dir1);
    assertEquals(2, sis.size());

    FieldInfos fis1 = IndexWriter.readFieldInfos(sis.info(0));
    FieldInfos fis2 = IndexWriter.readFieldInfos(sis.info(1));

    assertEquals("f1", fis1.fieldInfo(0).name);
    assertEquals("f2", fis1.fieldInfo(1).name);
    // make sure the ordering of the "external" segment is preserved
    assertEquals("f2", fis2.fieldInfo(0).name);
    assertEquals("f1", fis2.fieldInfo(1).name);
    assertEquals("f3", fis2.fieldInfo(2).name);
    assertEquals("f4", fis2.fieldInfo(3).name);

    dir1.close();
    dir2.close();
  }

  public void testFieldNumberGaps() throws IOException {
    int numIters = atLeast(13);
    for (int i = 0; i < numIters; i++) {
      Directory dir = newDirectory();
      {
        IndexWriter writer =
            new IndexWriter(
                dir,
                newIndexWriterConfig(new MockAnalyzer(random()))
                    .setMergePolicy(NoMergePolicy.INSTANCE));
        Document d = new Document();
        d.add(new TextField("f1", "d1 first field", Field.Store.YES));
        d.add(new TextField("f2", "d1 second field", Field.Store.YES));
        writer.addDocument(d);
        writer.close();
        SegmentInfos sis = SegmentInfos.readLatestCommit(dir);
        assertEquals(1, sis.size());
        FieldInfos fis1 = IndexWriter.readFieldInfos(sis.info(0));
        assertEquals("f1", fis1.fieldInfo(0).name);
        assertEquals("f2", fis1.fieldInfo(1).name);
      }

      {
        IndexWriter writer =
            new IndexWriter(
                dir,
                newIndexWriterConfig(new MockAnalyzer(random()))
                    .setMergePolicy(NoMergePolicy.INSTANCE));
        Document d = new Document();
        d.add(new TextField("f1", "d2 first field", Field.Store.YES));
        d.add(new StoredField("f3", new byte[] {1, 2, 3}));
        writer.addDocument(d);
        writer.close();
        SegmentInfos sis = SegmentInfos.readLatestCommit(dir);
        assertEquals(2, sis.size());
        FieldInfos fis1 = IndexWriter.readFieldInfos(sis.info(0));
        FieldInfos fis2 = IndexWriter.readFieldInfos(sis.info(1));
        assertEquals("f1", fis1.fieldInfo(0).name);
        assertEquals("f2", fis1.fieldInfo(1).name);
        assertEquals("f1", fis2.fieldInfo(0).name);
        assertNull(fis2.fieldInfo(1));
        assertEquals("f3", fis2.fieldInfo(2).name);
      }

      {
        IndexWriter writer =
            new IndexWriter(
                dir,
                newIndexWriterConfig(new MockAnalyzer(random()))
                    .setMergePolicy(NoMergePolicy.INSTANCE));
        Document d = new Document();
        d.add(new TextField("f1", "d3 first field", Field.Store.YES));
        d.add(new TextField("f2", "d3 second field", Field.Store.YES));
        d.add(new StoredField("f3", new byte[] {1, 2, 3, 4, 5}));
        writer.addDocument(d);
        writer.close();
        SegmentInfos sis = SegmentInfos.readLatestCommit(dir);
        assertEquals(3, sis.size());
        FieldInfos fis1 = IndexWriter.readFieldInfos(sis.info(0));
        FieldInfos fis2 = IndexWriter.readFieldInfos(sis.info(1));
        FieldInfos fis3 = IndexWriter.readFieldInfos(sis.info(2));
        assertEquals("f1", fis1.fieldInfo(0).name);
        assertEquals("f2", fis1.fieldInfo(1).name);
        assertEquals("f1", fis2.fieldInfo(0).name);
        assertNull(fis2.fieldInfo(1));
        assertEquals("f3", fis2.fieldInfo(2).name);
        assertEquals("f1", fis3.fieldInfo(0).name);
        assertEquals("f2", fis3.fieldInfo(1).name);
        assertEquals("f3", fis3.fieldInfo(2).name);
      }

      {
        IndexWriter writer =
            new IndexWriter(
                dir,
                newIndexWriterConfig(new MockAnalyzer(random()))
                    .setMergePolicy(NoMergePolicy.INSTANCE));
        writer.deleteDocuments(new Term("f1", "d1"));
        // nuke the first segment entirely so that the segment with gaps is
        // loaded first!
        writer.forceMergeDeletes();
        writer.close();
      }

      IndexWriter writer =
          new IndexWriter(
              dir,
              newIndexWriterConfig(new MockAnalyzer(random()))
                  .setMergePolicy(new LogByteSizeMergePolicy())
                  .setInfoStream(new FailOnNonBulkMergesInfoStream()));
      writer.forceMerge(1);
      writer.close();

      SegmentInfos sis = SegmentInfos.readLatestCommit(dir);
      assertEquals(1, sis.size());
      FieldInfos fis1 = IndexWriter.readFieldInfos(sis.info(0));
      assertEquals("f1", fis1.fieldInfo(0).name);
      assertEquals("f2", fis1.fieldInfo(1).name);
      assertEquals("f3", fis1.fieldInfo(2).name);
      dir.close();
    }
  }

  @Test
  public void testManyFields() throws Exception {
    final int NUM_DOCS = atLeast(200);
    final int MAX_FIELDS = atLeast(50);

    int[][] docs = new int[NUM_DOCS][4];
    for (int i = 0; i < docs.length; i++) {
      for (int j = 0; j < docs[i].length; j++) {
        docs[i][j] = random().nextInt(MAX_FIELDS);
      }
    }

    Directory dir = newDirectory();
    IndexWriter writer = new IndexWriter(dir, newIndexWriterConfig(new MockAnalyzer(random())));

    for (int i = 0; i < NUM_DOCS; i++) {
      Document d = new Document();
      for (int j = 0; j < docs[i].length; j++) {
        d.add(getField(docs[i][j]));
      }

      writer.addDocument(d);
    }

    writer.forceMerge(1);
    writer.close();

    SegmentInfos sis = SegmentInfos.readLatestCommit(dir);
    for (SegmentCommitInfo si : sis) {
      FieldInfos fis = IndexWriter.readFieldInfos(si);

      for (FieldInfo fi : fis) {
        Field expected = getField(Integer.parseInt(fi.name));
        assertEquals(expected.fieldType().indexOptions(), fi.getIndexOptions());
        assertEquals(expected.fieldType().storeTermVectors(), fi.hasTermVectors());
      }
    }

    dir.close();
  }

  private Field getField(int number) {
    int mode = number % 16;
    String fieldName = "" + number;
    FieldType customType = new FieldType(TextField.TYPE_STORED);

    FieldType customType2 = new FieldType(TextField.TYPE_STORED);
    customType2.setTokenized(false);

    FieldType customType3 = new FieldType(TextField.TYPE_NOT_STORED);
    customType3.setTokenized(false);

    FieldType customType4 = new FieldType(TextField.TYPE_NOT_STORED);
    customType4.setTokenized(false);
    customType4.setStoreTermVectors(true);
    customType4.setStoreTermVectorOffsets(true);

    FieldType customType5 = new FieldType(TextField.TYPE_NOT_STORED);
    customType5.setStoreTermVectors(true);
    customType5.setStoreTermVectorOffsets(true);

    FieldType customType6 = new FieldType(TextField.TYPE_STORED);
    customType6.setTokenized(false);
    customType6.setStoreTermVectors(true);
    customType6.setStoreTermVectorOffsets(true);

    FieldType customType7 = new FieldType(TextField.TYPE_NOT_STORED);
    customType7.setTokenized(false);
    customType7.setStoreTermVectors(true);
    customType7.setStoreTermVectorOffsets(true);

    FieldType customType8 = new FieldType(TextField.TYPE_STORED);
    customType8.setTokenized(false);
    customType8.setStoreTermVectors(true);
    customType8.setStoreTermVectorPositions(true);

    FieldType customType9 = new FieldType(TextField.TYPE_NOT_STORED);
    customType9.setStoreTermVectors(true);
    customType9.setStoreTermVectorPositions(true);

    FieldType customType10 = new FieldType(TextField.TYPE_STORED);
    customType10.setTokenized(false);
    customType10.setStoreTermVectors(true);
    customType10.setStoreTermVectorPositions(true);

    FieldType customType11 = new FieldType(TextField.TYPE_NOT_STORED);
    customType11.setTokenized(false);
    customType11.setStoreTermVectors(true);
    customType11.setStoreTermVectorPositions(true);

    FieldType customType12 = new FieldType(TextField.TYPE_STORED);
    customType12.setStoreTermVectors(true);
    customType12.setStoreTermVectorOffsets(true);
    customType12.setStoreTermVectorPositions(true);

    FieldType customType13 = new FieldType(TextField.TYPE_NOT_STORED);
    customType13.setStoreTermVectors(true);
    customType13.setStoreTermVectorOffsets(true);
    customType13.setStoreTermVectorPositions(true);

    FieldType customType14 = new FieldType(TextField.TYPE_STORED);
    customType14.setTokenized(false);
    customType14.setStoreTermVectors(true);
    customType14.setStoreTermVectorOffsets(true);
    customType14.setStoreTermVectorPositions(true);

    FieldType customType15 = new FieldType(TextField.TYPE_NOT_STORED);
    customType15.setTokenized(false);
    customType15.setStoreTermVectors(true);
    customType15.setStoreTermVectorOffsets(true);
    customType15.setStoreTermVectorPositions(true);

    switch (mode) {
      case 0:
        return new Field(fieldName, "some text", customType);
      case 1:
        return new TextField(fieldName, "some text", Field.Store.NO);
      case 2:
        return new Field(fieldName, "some text", customType2);
      case 3:
        return new Field(fieldName, "some text", customType3);
      case 4:
        return new Field(fieldName, "some text", customType4);
      case 5:
        return new Field(fieldName, "some text", customType5);
      case 6:
        return new Field(fieldName, "some text", customType6);
      case 7:
        return new Field(fieldName, "some text", customType7);
      case 8:
        return new Field(fieldName, "some text", customType8);
      case 9:
        return new Field(fieldName, "some text", customType9);
      case 10:
        return new Field(fieldName, "some text", customType10);
      case 11:
        return new Field(fieldName, "some text", customType11);
      case 12:
        return new Field(fieldName, "some text", customType12);
      case 13:
        return new Field(fieldName, "some text", customType13);
      case 14:
        return new Field(fieldName, "some text", customType14);
      case 15:
        return new Field(fieldName, "some text", customType15);
      default:
        return null;
    }
  }
}
