using J2N.Threading;
using J2N.Threading.Atomic;
using Lucene.Net.Analysis;
using Lucene.Net.Documents;
using Lucene.Net.Index.Extensions;
using NUnit.Framework;
using System;
using System.Collections.Generic;
using System.IO;
using Console = Lucene.Net.Support.SystemConsole;

namespace Lucene.Net.Index
{
    /*
     * 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.
     */

    using Directory = Lucene.Net.Store.Directory;
    using Document = Documents.Document;
    using Field = Field;
    using IndexSearcher = Lucene.Net.Search.IndexSearcher;
    using LuceneTestCase = Lucene.Net.Util.LuceneTestCase;
    using MockDirectoryWrapper = Lucene.Net.Store.MockDirectoryWrapper;
    using ScoreDoc = Lucene.Net.Search.ScoreDoc;
    using TermQuery = Lucene.Net.Search.TermQuery;
    using TestUtil = Lucene.Net.Util.TestUtil;

    [TestFixture]
    public class TestIndexWriterCommit : LuceneTestCase
    {
        private static readonly FieldType StoredTextType = new FieldType(TextField.TYPE_NOT_STORED);

        /*
         * Simple test for "commit on close": open writer then
         * add a bunch of docs, making sure reader does not see
         * these docs until writer is closed.
         */

        [Test]
        public virtual void TestCommitOnClose()
        {
            Directory dir = NewDirectory();
            IndexWriter writer = new IndexWriter(dir, NewIndexWriterConfig(TEST_VERSION_CURRENT, new MockAnalyzer(Random)));
            for (int i = 0; i < 14; i++)
            {
                AddDoc(writer);
            }
            writer.Dispose();

            Term searchTerm = new Term("content", "aaa");
            DirectoryReader reader = DirectoryReader.Open(dir);
            IndexSearcher searcher = NewSearcher(reader);
            ScoreDoc[] hits = searcher.Search(new TermQuery(searchTerm), null, 1000).ScoreDocs;
            Assert.AreEqual(14, hits.Length, "first number of hits");
            reader.Dispose();

            reader = DirectoryReader.Open(dir);

            writer = new IndexWriter(dir, NewIndexWriterConfig(TEST_VERSION_CURRENT, new MockAnalyzer(Random)));
            for (int i = 0; i < 3; i++)
            {
                for (int j = 0; j < 11; j++)
                {
                    AddDoc(writer);
                }
                IndexReader r = DirectoryReader.Open(dir);
                searcher = NewSearcher(r);
                hits = searcher.Search(new TermQuery(searchTerm), null, 1000).ScoreDocs;
                Assert.AreEqual(14, hits.Length, "reader incorrectly sees changes from writer");
                r.Dispose();
                Assert.IsTrue(reader.IsCurrent(), "reader should have still been current");
            }

            // Now, close the writer:
            writer.Dispose();
            Assert.IsFalse(reader.IsCurrent(), "reader should not be current now");

            IndexReader ir = DirectoryReader.Open(dir);
            searcher = NewSearcher(ir);
            hits = searcher.Search(new TermQuery(searchTerm), null, 1000).ScoreDocs;
            Assert.AreEqual(47, hits.Length, "reader did not see changes after writer was closed");
            ir.Dispose();
            reader.Dispose();
            dir.Dispose();
        }

        /*
         * Simple test for "commit on close": open writer, then
         * add a bunch of docs, making sure reader does not see
         * them until writer has closed.  Then instead of
         * closing the writer, call abort and verify reader sees
         * nothing was added.  Then verify we can open the index
         * and add docs to it.
         */

        [Test]
        public virtual void TestCommitOnCloseAbort()
        {
            Directory dir = NewDirectory();
            IndexWriter writer = new IndexWriter(dir, (IndexWriterConfig)NewIndexWriterConfig(TEST_VERSION_CURRENT, new MockAnalyzer(Random)).SetMaxBufferedDocs(10));
            for (int i = 0; i < 14; i++)
            {
                AddDoc(writer);
            }
            writer.Dispose();

            Term searchTerm = new Term("content", "aaa");
            IndexReader reader = DirectoryReader.Open(dir);
            IndexSearcher searcher = NewSearcher(reader);
            ScoreDoc[] hits = searcher.Search(new TermQuery(searchTerm), null, 1000).ScoreDocs;
            Assert.AreEqual(14, hits.Length, "first number of hits");
            reader.Dispose();

            writer = new IndexWriter(dir, (IndexWriterConfig)NewIndexWriterConfig(TEST_VERSION_CURRENT, new MockAnalyzer(Random)).SetOpenMode(OpenMode.APPEND).SetMaxBufferedDocs(10));
            for (int j = 0; j < 17; j++)
            {
                AddDoc(writer);
            }
            // Delete all docs:
            writer.DeleteDocuments(searchTerm);

            reader = DirectoryReader.Open(dir);
            searcher = NewSearcher(reader);
            hits = searcher.Search(new TermQuery(searchTerm), null, 1000).ScoreDocs;
            Assert.AreEqual(14, hits.Length, "reader incorrectly sees changes from writer");
            reader.Dispose();

            // Now, close the writer:
            writer.Rollback();

            TestIndexWriter.AssertNoUnreferencedFiles(dir, "unreferenced files remain after rollback()");

            reader = DirectoryReader.Open(dir);
            searcher = NewSearcher(reader);
            hits = searcher.Search(new TermQuery(searchTerm), null, 1000).ScoreDocs;
            Assert.AreEqual(14, hits.Length, "saw changes after writer.abort");
            reader.Dispose();

            // Now make sure we can re-open the index, add docs,
            // and all is good:
            writer = new IndexWriter(dir, (IndexWriterConfig)NewIndexWriterConfig(TEST_VERSION_CURRENT, new MockAnalyzer(Random)).SetOpenMode(OpenMode.APPEND).SetMaxBufferedDocs(10));

            // On abort, writer in fact may write to the same
            // segments_N file:
            if (dir is MockDirectoryWrapper)
            {
                ((MockDirectoryWrapper)dir).PreventDoubleWrite = false;
            }

            for (int i = 0; i < 12; i++)
            {
                for (int j = 0; j < 17; j++)
                {
                    AddDoc(writer);
                }
                IndexReader r = DirectoryReader.Open(dir);
                searcher = NewSearcher(r);
                hits = searcher.Search(new TermQuery(searchTerm), null, 1000).ScoreDocs;
                Assert.AreEqual(14, hits.Length, "reader incorrectly sees changes from writer");
                r.Dispose();
            }

            writer.Dispose();
            IndexReader ir = DirectoryReader.Open(dir);
            searcher = NewSearcher(ir);
            hits = searcher.Search(new TermQuery(searchTerm), null, 1000).ScoreDocs;
            Assert.AreEqual(218, hits.Length, "didn't see changes after close");
            ir.Dispose();

            dir.Dispose();
        }

        /*
         * Verify that a writer with "commit on close" indeed
         * cleans up the temp segments created after opening
         * that are not referenced by the starting segments
         * file.  We check this by using MockDirectoryWrapper to
         * measure max temp disk space used.
         */

        [Test]
        public virtual void TestCommitOnCloseDiskUsage()
        {
            // MemoryCodec, since it uses FST, is not necessarily
            // "additive", ie if you add up N small FSTs, then merge
            // them, the merged result can easily be larger than the
            // sum because the merged FST may use array encoding for
            // some arcs (which uses more space):

            string idFormat = TestUtil.GetPostingsFormat("id");
            string contentFormat = TestUtil.GetPostingsFormat("content");
            AssumeFalse("this test cannot run with Memory codec", idFormat.Equals("Memory", StringComparison.Ordinal) || contentFormat.Equals("Memory", StringComparison.Ordinal));
            MockDirectoryWrapper dir = NewMockDirectory();
            Analyzer analyzer;
            if (Random.NextBoolean())
            {
                // no payloads
                analyzer = new AnalyzerAnonymousInnerClassHelper(this);
            }
            else
            {
                // fixed length payloads
                int length = Random.Next(200);
                analyzer = new AnalyzerAnonymousInnerClassHelper2(this, length);
            }

            IndexWriter writer = new IndexWriter(dir, NewIndexWriterConfig(TEST_VERSION_CURRENT, analyzer).SetMaxBufferedDocs(10).SetReaderPooling(false).SetMergePolicy(NewLogMergePolicy(10)));
            for (int j = 0; j < 30; j++)
            {
                AddDocWithIndex(writer, j);
            }
            writer.Dispose();
            dir.ResetMaxUsedSizeInBytes();

            dir.TrackDiskUsage = true;
            long startDiskUsage = dir.MaxUsedSizeInBytes;
            writer = new IndexWriter(dir, NewIndexWriterConfig(TEST_VERSION_CURRENT, analyzer).SetOpenMode(OpenMode.APPEND).SetMaxBufferedDocs(10).SetMergeScheduler(new SerialMergeScheduler()).SetReaderPooling(false).SetMergePolicy(NewLogMergePolicy(10)));
            for (int j = 0; j < 1470; j++)
            {
                AddDocWithIndex(writer, j);
            }
            long midDiskUsage = dir.MaxUsedSizeInBytes;
            dir.ResetMaxUsedSizeInBytes();
            writer.ForceMerge(1);
            writer.Dispose();

            DirectoryReader.Open(dir).Dispose();

            long endDiskUsage = dir.MaxUsedSizeInBytes;

            // Ending index is 50X as large as starting index; due
            // to 3X disk usage normally we allow 150X max
            // transient usage.  If something is wrong w/ deleter
            // and it doesn't delete intermediate segments then it
            // will exceed this 150X:
            // System.out.println("start " + startDiskUsage + "; mid " + midDiskUsage + ";end " + endDiskUsage);
            Assert.IsTrue(midDiskUsage < 150 * startDiskUsage, "writer used too much space while adding documents: mid=" + midDiskUsage + " start=" + startDiskUsage + " end=" + endDiskUsage + " max=" + (startDiskUsage * 150));
            Assert.IsTrue(endDiskUsage < 150 * startDiskUsage, "writer used too much space after close: endDiskUsage=" + endDiskUsage + " startDiskUsage=" + startDiskUsage + " max=" + (startDiskUsage * 150));
            dir.Dispose();
        }

        private class AnalyzerAnonymousInnerClassHelper : Analyzer
        {
            private readonly TestIndexWriterCommit OuterInstance;

            public AnalyzerAnonymousInnerClassHelper(TestIndexWriterCommit outerInstance)
            {
                this.OuterInstance = outerInstance;
            }

            protected internal override TokenStreamComponents CreateComponents(string fieldName, TextReader reader)
            {
                return new TokenStreamComponents(new MockTokenizer(reader, MockTokenizer.WHITESPACE, true));
            }
        }

        private class AnalyzerAnonymousInnerClassHelper2 : Analyzer
        {
            private readonly TestIndexWriterCommit OuterInstance;

            private int Length;

            public AnalyzerAnonymousInnerClassHelper2(TestIndexWriterCommit outerInstance, int length)
            {
                this.OuterInstance = outerInstance;
                this.Length = length;
            }

            protected internal override TokenStreamComponents CreateComponents(string fieldName, TextReader reader)
            {
                Tokenizer tokenizer = new MockTokenizer(reader, MockTokenizer.WHITESPACE, true);
                return new TokenStreamComponents(tokenizer, new MockFixedLengthPayloadFilter(Random, tokenizer, Length));
            }
        }

        /*
         * Verify that calling forceMerge when writer is open for
         * "commit on close" works correctly both for rollback()
         * and close().
         */

        [Test]
        public virtual void TestCommitOnCloseForceMerge()
        {
            Directory dir = NewDirectory();
            // Must disable throwing exc on double-write: this
            // test uses IW.rollback which easily results in
            // writing to same file more than once
            if (dir is MockDirectoryWrapper)
            {
                ((MockDirectoryWrapper)dir).PreventDoubleWrite = false;
            }
            IndexWriter writer = new IndexWriter(dir, NewIndexWriterConfig(TEST_VERSION_CURRENT, new MockAnalyzer(Random)).SetMaxBufferedDocs(10).SetMergePolicy(NewLogMergePolicy(10)));
            for (int j = 0; j < 17; j++)
            {
                AddDocWithIndex(writer, j);
            }
            writer.Dispose();

            writer = new IndexWriter(dir, NewIndexWriterConfig(TEST_VERSION_CURRENT, new MockAnalyzer(Random)).SetOpenMode(OpenMode.APPEND));
            writer.ForceMerge(1);

            // Open a reader before closing (commiting) the writer:
            DirectoryReader reader = DirectoryReader.Open(dir);

            // Reader should see index as multi-seg at this
            // point:
            Assert.IsTrue(reader.Leaves.Count > 1, "Reader incorrectly sees one segment");
            reader.Dispose();

            // Abort the writer:
            writer.Rollback();
            TestIndexWriter.AssertNoUnreferencedFiles(dir, "aborted writer after forceMerge");

            // Open a reader after aborting writer:
            reader = DirectoryReader.Open(dir);

            // Reader should still see index as multi-segment
            Assert.IsTrue(reader.Leaves.Count > 1, "Reader incorrectly sees one segment");
            reader.Dispose();

            if (VERBOSE)
            {
                Console.WriteLine("TEST: do real full merge");
            }
            writer = new IndexWriter(dir, NewIndexWriterConfig(TEST_VERSION_CURRENT, new MockAnalyzer(Random)).SetOpenMode(OpenMode.APPEND));
            writer.ForceMerge(1);
            writer.Dispose();

            if (VERBOSE)
            {
                Console.WriteLine("TEST: writer closed");
            }
            TestIndexWriter.AssertNoUnreferencedFiles(dir, "aborted writer after forceMerge");

            // Open a reader after aborting writer:
            reader = DirectoryReader.Open(dir);

            // Reader should see index as one segment
            Assert.AreEqual(1, reader.Leaves.Count, "Reader incorrectly sees more than one segment");
            reader.Dispose();
            dir.Dispose();
        }

        // LUCENE-2095: make sure with multiple threads commit
        // doesn't return until all changes are in fact in the
        // index
        [Test]
        public virtual void TestCommitThreadSafety()
        {
            const int NUM_THREADS = 5;
            const double RUN_SEC = 0.5;
            var dir = NewDirectory();
            var w = new RandomIndexWriter(Random, dir, NewIndexWriterConfig(TEST_VERSION_CURRENT, new MockAnalyzer(Random)).SetMergePolicy(NewLogMergePolicy()));
            TestUtil.ReduceOpenFiles(w.IndexWriter);
            w.Commit();
            var failed = new AtomicBoolean();
            var threads = new ThreadJob[NUM_THREADS];
            long endTime = Environment.TickCount + ((long)(RUN_SEC * 1000));
            for (int i = 0; i < NUM_THREADS; i++)
            {
                int finalI = i;
                threads[i] = new ThreadAnonymousInnerClassHelper(dir, w, failed, endTime, finalI, NewStringField);
                threads[i].Start();
            }
            for (int i = 0; i < NUM_THREADS; i++)
            {
                threads[i].Join();
            }
            Assert.IsFalse(failed);
            w.Dispose();
            dir.Dispose();
        }

        private class ThreadAnonymousInnerClassHelper : ThreadJob
        {
            private readonly Func<string, string, Field.Store, Field> NewStringField;
            private Directory Dir;
            private RandomIndexWriter w;
            private AtomicBoolean Failed;
            private long EndTime;
            private int FinalI;

            /// <param name="newStringField">
            /// LUCENENET specific
            /// This is passed in because <see cref="LuceneTestCase.NewStringField(string, string, Field.Store)"/>
            /// is no longer static.
            /// </param>
            public ThreadAnonymousInnerClassHelper(Directory dir, RandomIndexWriter w, AtomicBoolean failed, long endTime, int finalI, Func<string, string, Field.Store, Field> newStringField)
            {
                NewStringField = newStringField;
                this.Dir = dir;
                this.w = w;
                this.Failed = failed;
                this.EndTime = endTime;
                this.FinalI = finalI;
            }

            public override void Run()
            {
                try
                {
                    Document doc = new Document();
                    DirectoryReader r = DirectoryReader.Open(Dir);
                    Field f = NewStringField("f", "", Field.Store.NO);
                    doc.Add(f);
                    int count = 0;
                    do
                    {
                        if (Failed)
                        {
                            break;
                        }
                        for (int j = 0; j < 10; j++)
                        {
                            string s = FinalI + "_" + Convert.ToString(count++);
                            f.SetStringValue(s);
                            w.AddDocument(doc);
                            w.Commit();
                            DirectoryReader r2 = DirectoryReader.OpenIfChanged(r);
                            Assert.IsNotNull(r2);
                            Assert.IsTrue(!r2.Equals(r));
                            r.Dispose();
                            r = r2;
                            Assert.AreEqual(1, r.DocFreq(new Term("f", s)), "term=f:" + s + "; r=" + r);
                        }
                    } while (Environment.TickCount < EndTime);
                    r.Dispose();
                }
                catch (Exception t)
                {
                    Failed.Value = (true);
                    throw new Exception(t.Message, t);
                }
            }
        }

        // LUCENE-1044: test writer.Commit() when ac=false
        [Test]
        public virtual void TestForceCommit()
        {
            Directory dir = NewDirectory();

            IndexWriter writer = new IndexWriter(dir, NewIndexWriterConfig(TEST_VERSION_CURRENT, new MockAnalyzer(Random)).SetMaxBufferedDocs(2).SetMergePolicy(NewLogMergePolicy(5)));
            writer.Commit();

            for (int i = 0; i < 23; i++)
            {
                AddDoc(writer);
            }

            DirectoryReader reader = DirectoryReader.Open(dir);
            Assert.AreEqual(0, reader.NumDocs);
            writer.Commit();
            DirectoryReader reader2 = DirectoryReader.OpenIfChanged(reader);
            Assert.IsNotNull(reader2);
            Assert.AreEqual(0, reader.NumDocs);
            Assert.AreEqual(23, reader2.NumDocs);
            reader.Dispose();

            for (int i = 0; i < 17; i++)
            {
                AddDoc(writer);
            }
            Assert.AreEqual(23, reader2.NumDocs);
            reader2.Dispose();
            reader = DirectoryReader.Open(dir);
            Assert.AreEqual(23, reader.NumDocs);
            reader.Dispose();
            writer.Commit();

            reader = DirectoryReader.Open(dir);
            Assert.AreEqual(40, reader.NumDocs);
            reader.Dispose();
            writer.Dispose();
            dir.Dispose();
        }

        [Test]
        public virtual void TestFutureCommit()
        {
            Directory dir = NewDirectory();

            IndexWriter w = new IndexWriter(dir, NewIndexWriterConfig(TEST_VERSION_CURRENT, new MockAnalyzer(Random)).SetIndexDeletionPolicy(NoDeletionPolicy.INSTANCE));
            Document doc = new Document();
            w.AddDocument(doc);

            // commit to "first"
            IDictionary<string, string> commitData = new Dictionary<string, string>();
            commitData["tag"] = "first";
            w.SetCommitData(commitData);
            w.Commit();

            // commit to "second"
            w.AddDocument(doc);
            commitData["tag"] = "second";
            w.SetCommitData(commitData);
            w.Dispose();

            // open "first" with IndexWriter
            IndexCommit commit = null;
            foreach (IndexCommit c in DirectoryReader.ListCommits(dir))
            {
                if (c.UserData["tag"].Equals("first", StringComparison.Ordinal))
                {
                    commit = c;
                    break;
                }
            }

            Assert.IsNotNull(commit);

            w = new IndexWriter(dir, NewIndexWriterConfig(TEST_VERSION_CURRENT, new MockAnalyzer(Random)).SetIndexDeletionPolicy(NoDeletionPolicy.INSTANCE).SetIndexCommit(commit));

            Assert.AreEqual(1, w.NumDocs);

            // commit IndexWriter to "third"
            w.AddDocument(doc);
            commitData["tag"] = "third";
            w.SetCommitData(commitData);
            w.Dispose();

            // make sure "second" commit is still there
            commit = null;
            foreach (IndexCommit c in DirectoryReader.ListCommits(dir))
            {
                if (c.UserData["tag"].Equals("second", StringComparison.Ordinal))
                {
                    commit = c;
                    break;
                }
            }

            Assert.IsNotNull(commit);

            dir.Dispose();
        }

        [Test]
        public virtual void TestZeroCommits()
        {
            // Tests that if we don't call commit(), the directory has 0 commits. this has
            // changed since LUCENE-2386, where before IW would always commit on a fresh
            // new index.
            Directory dir = NewDirectory();
            IndexWriter writer = new IndexWriter(dir, NewIndexWriterConfig(TEST_VERSION_CURRENT, new MockAnalyzer(Random)));
            try
            {
                DirectoryReader.ListCommits(dir);
                Assert.Fail("listCommits should have thrown an exception over empty index");
            }
#pragma warning disable 168
            catch (IndexNotFoundException e)
#pragma warning restore 168
            {
                // that's expected !
            }
            // No changes still should generate a commit, because it's a new index.
            writer.Dispose();
            Assert.AreEqual(1, DirectoryReader.ListCommits(dir).Count, "expected 1 commits!");
            dir.Dispose();
        }

        // LUCENE-1274: test writer.PrepareCommit()
        [Test]
        public virtual void TestPrepareCommit()
        {
            Directory dir = NewDirectory();

            IndexWriter writer = new IndexWriter(dir, NewIndexWriterConfig(TEST_VERSION_CURRENT, new MockAnalyzer(Random)).SetMaxBufferedDocs(2).SetMergePolicy(NewLogMergePolicy(5)));
            writer.Commit();

            for (int i = 0; i < 23; i++)
            {
                AddDoc(writer);
            }

            DirectoryReader reader = DirectoryReader.Open(dir);
            Assert.AreEqual(0, reader.NumDocs);

            writer.PrepareCommit();

            IndexReader reader2 = DirectoryReader.Open(dir);
            Assert.AreEqual(0, reader2.NumDocs);

            writer.Commit();

            IndexReader reader3 = DirectoryReader.OpenIfChanged(reader);
            Assert.IsNotNull(reader3);
            Assert.AreEqual(0, reader.NumDocs);
            Assert.AreEqual(0, reader2.NumDocs);
            Assert.AreEqual(23, reader3.NumDocs);
            reader.Dispose();
            reader2.Dispose();

            for (int i = 0; i < 17; i++)
            {
                AddDoc(writer);
            }

            Assert.AreEqual(23, reader3.NumDocs);
            reader3.Dispose();
            reader = DirectoryReader.Open(dir);
            Assert.AreEqual(23, reader.NumDocs);
            reader.Dispose();

            writer.PrepareCommit();

            reader = DirectoryReader.Open(dir);
            Assert.AreEqual(23, reader.NumDocs);
            reader.Dispose();

            writer.Commit();
            reader = DirectoryReader.Open(dir);
            Assert.AreEqual(40, reader.NumDocs);
            reader.Dispose();
            writer.Dispose();
            dir.Dispose();
        }

        // LUCENE-1274: test writer.PrepareCommit()
        [Test]
        public virtual void TestPrepareCommitRollback()
        {
            Directory dir = NewDirectory();
            if (dir is MockDirectoryWrapper)
            {
                ((MockDirectoryWrapper)dir).PreventDoubleWrite = false;
            }

            IndexWriter writer = new IndexWriter(dir, NewIndexWriterConfig(TEST_VERSION_CURRENT, new MockAnalyzer(Random)).SetMaxBufferedDocs(2).SetMergePolicy(NewLogMergePolicy(5)));
            writer.Commit();

            for (int i = 0; i < 23; i++)
            {
                AddDoc(writer);
            }

            DirectoryReader reader = DirectoryReader.Open(dir);
            Assert.AreEqual(0, reader.NumDocs);

            writer.PrepareCommit();

            IndexReader reader2 = DirectoryReader.Open(dir);
            Assert.AreEqual(0, reader2.NumDocs);

            writer.Rollback();

            IndexReader reader3 = DirectoryReader.OpenIfChanged(reader);
            Assert.IsNull(reader3);
            Assert.AreEqual(0, reader.NumDocs);
            Assert.AreEqual(0, reader2.NumDocs);
            reader.Dispose();
            reader2.Dispose();

            writer = new IndexWriter(dir, NewIndexWriterConfig(TEST_VERSION_CURRENT, new MockAnalyzer(Random)));
            for (int i = 0; i < 17; i++)
            {
                AddDoc(writer);
            }

            reader = DirectoryReader.Open(dir);
            Assert.AreEqual(0, reader.NumDocs);
            reader.Dispose();

            writer.PrepareCommit();

            reader = DirectoryReader.Open(dir);
            Assert.AreEqual(0, reader.NumDocs);
            reader.Dispose();

            writer.Commit();
            reader = DirectoryReader.Open(dir);
            Assert.AreEqual(17, reader.NumDocs);
            reader.Dispose();
            writer.Dispose();
            dir.Dispose();
        }

        // LUCENE-1274
        [Test]
        public virtual void TestPrepareCommitNoChanges()
        {
            Directory dir = NewDirectory();

            IndexWriter writer = new IndexWriter(dir, NewIndexWriterConfig(TEST_VERSION_CURRENT, new MockAnalyzer(Random)));
            writer.PrepareCommit();
            writer.Commit();
            writer.Dispose();

            IndexReader reader = DirectoryReader.Open(dir);
            Assert.AreEqual(0, reader.NumDocs);
            reader.Dispose();
            dir.Dispose();
        }

        // LUCENE-1382
        [Test]
        public virtual void TestCommitUserData()
        {
            Directory dir = NewDirectory();
            IndexWriter w = new IndexWriter(dir, (IndexWriterConfig)NewIndexWriterConfig(TEST_VERSION_CURRENT, new MockAnalyzer(Random)).SetMaxBufferedDocs(2));
            for (int j = 0; j < 17; j++)
            {
                AddDoc(w);
            }
            w.Dispose();

            DirectoryReader r = DirectoryReader.Open(dir);
            // commit(Map) never called for this index
            Assert.AreEqual(0, r.IndexCommit.UserData.Count);
            r.Dispose();

            w = new IndexWriter(dir, (IndexWriterConfig)NewIndexWriterConfig(TEST_VERSION_CURRENT, new MockAnalyzer(Random)).SetMaxBufferedDocs(2));
            for (int j = 0; j < 17; j++)
            {
                AddDoc(w);
            }
            IDictionary<string, string> data = new Dictionary<string, string>();
            data["label"] = "test1";
            w.SetCommitData(data);
            w.Dispose();

            r = DirectoryReader.Open(dir);
            Assert.AreEqual("test1", r.IndexCommit.UserData["label"]);
            r.Dispose();

            w = new IndexWriter(dir, NewIndexWriterConfig(TEST_VERSION_CURRENT, new MockAnalyzer(Random)));
            w.ForceMerge(1);
            w.Dispose();

            dir.Dispose();
        }

        /// <summary>
        /// LUCENENET specific
        /// Copied from <see cref="TestIndexWriter.AddDoc(IndexWriter)"/>
        /// to remove inter-class dependency on <see cref="TestIndexWriter"/>
        /// </summary>
        private void AddDoc(IndexWriter writer)
        {
            Document doc = new Document();
            doc.Add(NewTextField("content", "aaa", Field.Store.NO));
            writer.AddDocument(doc);
        }

        /// <summary>
        /// LUCENENET specific
        /// Copied from <seealso cref="TestIndexWriter.AddDocWithIndex(IndexWriter, int)"/>
        /// to remove inter-class dependency on <see cref="TestIndexWriter"/>.
        /// </summary>
        private void AddDocWithIndex(IndexWriter writer, int index)
        {
            Document doc = new Document();
            doc.Add(NewField("content", "aaa " + index, StoredTextType));
            doc.Add(NewField("id", "" + index, StoredTextType));
            writer.AddDocument(doc);
        }

    }
}