/*
 * 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.jackrabbit.oak.plugins.index.lucene;

import org.apache.jackrabbit.oak.commons.collections.ListUtils;
import org.apache.jackrabbit.oak.plugins.index.search.FieldNames;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.apache.lucene.document.StringField;
import org.apache.lucene.index.DirectoryReader;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.IndexWriterConfig;
import org.apache.lucene.index.Term;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.RAMDirectory;
import org.junit.After;
import org.junit.Test;

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

import static org.apache.jackrabbit.oak.plugins.index.lucene.LuceneIndexStatistics.SYNTHETICALLY_FALLIABLE_FIELD;
import static org.apache.jackrabbit.oak.plugins.index.lucene.LuceneIndexConstants.VERSION;
import static org.junit.Assert.assertEquals;

public class IndexStatisticsTest {
    @After
    public void resetFailFlags() {
        LuceneIndexStatistics.failReadingFields = false;
        LuceneIndexStatistics.failReadingSyntheticallyFalliableField = false;
    }

    @Test
    public void numDocs() throws Exception {
        Directory d = createSampleDirectory(2);
        LuceneIndexStatistics stats = getStats(d);

        assertEquals(2, stats.numDocs());
    }

    @Test
    public void numDocsWithDelele() throws Exception {
        Directory d = createSampleDirectory(2);
        {
            IndexWriter writer = getWriter(d);
            writer.deleteDocuments(new Term("foo", "bar1"));
            writer.close();
        }

        LuceneIndexStatistics stats = getStats(d);
        assertEquals(1, stats.numDocs());
    }

    @Test
    public void getSimpleFieldDocCnt() throws Exception {
        Directory d = createSampleDirectory(2);
        LuceneIndexStatistics stats = getStats(d);

        assertEquals(2, stats.getDocCountFor("foo"));
    }

    @Test
    public void getSimpleFieldDocCntWithDelete() throws Exception {
        Directory d = createSampleDirectory(2);
        {
            IndexWriter writer = getWriter(d);
            writer.deleteDocuments(new Term("foo", "bar1"));
            writer.close();
        }

        LuceneIndexStatistics stats = getStats(d);
        assertEquals("Stats don't need to get accurate result which might require reading more",
            2, stats.getDocCountFor("foo"));
    }

    @Test
    public void absentFields() throws Exception {
        Directory d = createSampleDirectory(1);
        LuceneIndexStatistics stats = getStats(d);

        assertEquals(1, stats.getDocCountFor("foo"));
        assertEquals(0, stats.getDocCountFor("absent"));
        assertEquals(-1, stats.getDocCountFor(":someHiddenField"));
        assertEquals(-1, stats.getDocCountFor(FieldNames.ANALYZED_FIELD_PREFIX + "foo"));
        assertEquals(-1, stats.getDocCountFor(FieldNames.FULLTEXT_RELATIVE_NODE + "foo"));
        assertEquals(-1, stats.getDocCountFor("foo_facet"));
    }

    @Test
    public void onlyPropertyFields() throws Exception {
        Document document = new Document();
        document.add(new StringField("foo", "manualBar", Field.Store.NO));
        document.add(new StringField(":someHiddenField", "manualBar", Field.Store.NO));
        document.add(new StringField(FieldNames.ANALYZED_FIELD_PREFIX + "foo", "manualBar", Field.Store.NO));
        document.add(new StringField(FieldNames.FULLTEXT_RELATIVE_NODE + "foo", "manualBar", Field.Store.NO));
        document.add(new StringField("foo_facet", "manualBar", Field.Store.NO));
        Directory d = createSampleDirectory(document);
        LuceneIndexStatistics stats = getStats(d);

        assertEquals(3, stats.getDocCountFor("foo"));
        assertEquals(0, stats.getDocCountFor("absent"));
        assertEquals(-1, stats.getDocCountFor(":someHiddenField"));
        assertEquals(-1, stats.getDocCountFor(FieldNames.ANALYZED_FIELD_PREFIX + "foo"));
        assertEquals(-1, stats.getDocCountFor(FieldNames.FULLTEXT_RELATIVE_NODE + "foo"));
        assertEquals(-1, stats.getDocCountFor("foo_facet"));
    }

    @Test
    public void unableToIterateFields() throws Exception {
        LuceneIndexStatistics.failReadingFields = true;

        LuceneIndexStatistics stats = getStats(createSampleDirectory(100));

        assertEquals(100, stats.numDocs());
        assertEquals(-1, stats.getDocCountFor("foo"));
        assertEquals(-1, stats.getDocCountFor("bar"));
    }

    @Test
    public void unableToReadCountForJcrTitle() throws Exception {
        LuceneIndexStatistics.failReadingSyntheticallyFalliableField = true;

        Document doc = new Document();
        doc.add(new StringField("foo1", "bar1", Field.Store.NO));
        doc.add(new StringField(SYNTHETICALLY_FALLIABLE_FIELD, "title", Field.Store.NO));
        LuceneIndexStatistics stats = getStats(createSampleDirectory(doc));

        assertEquals(3, stats.numDocs());
        assertEquals(2, stats.getDocCountFor("foo"));
        assertEquals(1, stats.getDocCountFor("foo1"));
        assertEquals(-1, stats.getDocCountFor(SYNTHETICALLY_FALLIABLE_FIELD));
        assertEquals(0, stats.getDocCountFor("bar"));
    }

    private static Directory createSampleDirectory(long numOfDocs) throws IOException {
        return createSampleDirectory(numOfDocs, Collections.emptyList());
    }

    private static Directory createSampleDirectory(Document moreDoc) throws IOException {
        return createSampleDirectory(2, Collections.singleton(moreDoc));
    }

    private static Directory createSampleDirectory(long numOfDocs, Iterable<Document> moreDocs) throws IOException {
        List<Document> docs = ListUtils.toList(moreDocs);
        for (int i = 0; i < numOfDocs; i++) {
            Document doc = new Document();
            doc.add(new StringField("foo", "bar" + i, Field.Store.NO));
            docs.add(doc);
        }

        return createSampleDirectory(docs);
    }

    private static Directory createSampleDirectory(Iterable<Document> docs) throws IOException {
        Directory dir = new RAMDirectory();
        IndexWriter writer = null;
        try {
            writer = getWriter(dir);
            for (Document doc : docs) {
                writer.addDocument(doc);
            }
            return dir;
        } finally {
            if (writer != null) {
                writer.close();
            }
        }
    }

    private static IndexWriter getWriter(Directory d) throws IOException {
        IndexWriterConfig config = new IndexWriterConfig(VERSION, LuceneIndexConstants.ANALYZER);
        return new IndexWriter(d, config);
    }

    private static LuceneIndexStatistics getStats(Directory d) throws IOException {
        IndexReader reader = DirectoryReader.open(d);
        // no more reads
        d.close();

        LuceneIndexStatistics stats = new LuceneIndexStatistics(reader);
        //close reader... Index stats would read numDocs right away
        reader.close();

        return stats;
    }
}
