/*
 * 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 System;
using NUnit.Framework;
using Analyzer = Lucene.Net.Analysis.Analyzer;
using SimpleAnalyzer = Lucene.Net.Analysis.SimpleAnalyzer;
using StandardAnalyzer = Lucene.Net.Analysis.Standard.StandardAnalyzer;
using Document = Lucene.Net.Documents.Document;
using Field = Lucene.Net.Documents.Field;
using Directory = Lucene.Net.Store.Directory;
using FSDirectory = Lucene.Net.Store.FSDirectory;
using RAMDirectory = Lucene.Net.Store.RAMDirectory;

namespace Lucene.Net.Index
{
	
	/// <summary> Tests for the "IndexModifier" class, including accesses from two threads at the
	/// same time.
	/// 
	/// </summary>
	/// <author>  Daniel Naber
	/// </author>
	[TestFixture]
    public class TestIndexModifier
	{
		
		private int docCount = 0;
		
		private Term allDocTerm = new Term("all", "x");
		
		[Test]
        public virtual void  TestIndex()
		{
			Directory ramDir = new RAMDirectory();
			IndexModifier i = new IndexModifier(ramDir, new StandardAnalyzer(), true);
			i.AddDocument(GetDoc());
			Assert.AreEqual(1, i.DocCount());
			i.Flush();
			i.AddDocument(GetDoc(), new SimpleAnalyzer());
			Assert.AreEqual(2, i.DocCount());
			i.Optimize();
			Assert.AreEqual(2, i.DocCount());
			i.Flush();
			i.DeleteDocument(0);
			Assert.AreEqual(1, i.DocCount());
			i.Flush();
			Assert.AreEqual(1, i.DocCount());
			i.AddDocument(GetDoc());
			i.AddDocument(GetDoc());
			i.Flush();
			Assert.AreEqual(3, i.DocCount());
			i.DeleteDocuments(allDocTerm);
			Assert.AreEqual(0, i.DocCount());
			i.Optimize();
			Assert.AreEqual(0, i.DocCount());
			
			//  Lucene defaults:
			Assert.IsNull(i.GetInfoStream());
			Assert.IsTrue(i.GetUseCompoundFile());
			Assert.AreEqual(10, i.GetMaxBufferedDocs());
			Assert.AreEqual(10000, i.GetMaxFieldLength());
			Assert.AreEqual(10, i.GetMergeFactor());
			// test setting properties:
			i.SetMaxBufferedDocs(100);
			i.SetMergeFactor(25);
			i.SetMaxFieldLength(250000);
			i.AddDocument(GetDoc());
			i.SetUseCompoundFile(false);
			i.Flush();
			Assert.AreEqual(100, i.GetMaxBufferedDocs());
			Assert.AreEqual(25, i.GetMergeFactor());
			Assert.AreEqual(250000, i.GetMaxFieldLength());
			Assert.IsFalse(i.GetUseCompoundFile());
			
			// test setting properties when internally the reader is opened:
			i.DeleteDocuments(allDocTerm);
			i.SetMaxBufferedDocs(100);
			i.SetMergeFactor(25);
			i.SetMaxFieldLength(250000);
			i.AddDocument(GetDoc());
			i.SetUseCompoundFile(false);
			i.Optimize();
			Assert.AreEqual(100, i.GetMaxBufferedDocs());
			Assert.AreEqual(25, i.GetMergeFactor());
			Assert.AreEqual(250000, i.GetMaxFieldLength());
			Assert.IsFalse(i.GetUseCompoundFile());
			
			i.Close();
			try
			{
				i.DocCount();
				Assert.Fail();
			}
			catch (System.SystemException e)
			{
				// expected exception
			}
		}
		
		[Test]
        public virtual void  TestExtendedIndex()
		{
			Directory ramDir = new RAMDirectory();
			PowerIndex powerIndex = new PowerIndex(this, ramDir, new StandardAnalyzer(), true);
			powerIndex.AddDocument(GetDoc());
			powerIndex.AddDocument(GetDoc());
			powerIndex.AddDocument(GetDoc());
			powerIndex.AddDocument(GetDoc());
			powerIndex.AddDocument(GetDoc());
			powerIndex.Flush();
			Assert.AreEqual(5, powerIndex.DocFreq(allDocTerm));
			powerIndex.Close();
		}
		
		private Lucene.Net.Documents.Document GetDoc()
		{
			Lucene.Net.Documents.Document doc = new Lucene.Net.Documents.Document();
			doc.Add(new Field("body", ((System.Int32) docCount).ToString(), Field.Store.YES, Field.Index.UN_TOKENIZED));
			doc.Add(new Field("all", "x", Field.Store.YES, Field.Index.UN_TOKENIZED));
			docCount++;
			return doc;
		}
		
		[Test]
        public virtual void  TestIndexWithThreads()
		{
			_TestIndexInternal(0);
			_TestIndexInternal(10);
			_TestIndexInternal(50);
		}
		
        private void  _TestIndexInternal(int maxWait)
		{
			bool create = true;
			//Directory rd = new RAMDirectory();
			// work on disk to make sure potential lock problems are tested:
			System.String tempDir = System.IO.Path.GetTempPath();
			if (tempDir == null)
				throw new System.IO.IOException("java.io.tmpdir undefined, cannot run test");
			System.IO.FileInfo indexDir = new System.IO.FileInfo(System.IO.Path.Combine(tempDir, "lucenetestindex"));
			Directory rd = FSDirectory.GetDirectory(indexDir, create);
			IndexThread.id = 0;
			IndexThread.idStack.Clear();
			IndexModifier index = new IndexModifier(rd, new StandardAnalyzer(), create);
			IndexThread thread1 = new IndexThread(index, maxWait, 1);
			thread1.Start();
			IndexThread thread2 = new IndexThread(index, maxWait, 2);
			thread2.Start();
			while (thread1.IsAlive || thread2.IsAlive)
			{
				try
				{
					System.Threading.Thread.Sleep(100);
				}
				catch (System.Threading.ThreadInterruptedException e)
				{
					throw new System.SystemException(e.Message);
				}
			}
			index.Optimize();
			int added = thread1.added + thread2.added;
			int deleted = thread1.deleted + thread2.deleted;
			Assert.AreEqual(added - deleted, index.DocCount());
			index.Close();
			
			try
			{
				index.Close();
				Assert.Fail();
			}
			catch (System.SystemException e)
			{
				// expected exception
			}
			RmDir(indexDir);
		}
		
		private void  RmDir(System.IO.FileInfo dir)
		{
			System.IO.FileInfo[] files = SupportClass.FileSupport.GetFiles(dir);
			for (int i = 0; i < files.Length; i++)
			{
				bool tmpBool;
				if (System.IO.File.Exists(files[i].FullName))
				{
					System.IO.File.Delete(files[i].FullName);
					tmpBool = true;
				}
				else if (System.IO.Directory.Exists(files[i].FullName))
				{
					System.IO.Directory.Delete(files[i].FullName);
					tmpBool = true;
				}
				else
					tmpBool = false;
				bool generatedAux = tmpBool;
			}
			bool tmpBool2;
			if (System.IO.File.Exists(dir.FullName))
			{
				System.IO.File.Delete(dir.FullName);
				tmpBool2 = true;
			}
			else if (System.IO.Directory.Exists(dir.FullName))
			{
				System.IO.Directory.Delete(dir.FullName);
				tmpBool2 = true;
			}
			else
				tmpBool2 = false;
			bool generatedAux2 = tmpBool2;
		}
		
		private class PowerIndex : IndexModifier
		{
			private void  InitBlock(TestIndexModifier enclosingInstance)
			{
				this.enclosingInstance = enclosingInstance;
			}
			private TestIndexModifier enclosingInstance;
			public TestIndexModifier Enclosing_Instance
			{
				get
				{
					return enclosingInstance;
				}
				
			}
			public PowerIndex(TestIndexModifier enclosingInstance, Directory dir, Analyzer analyzer, bool create):base(dir, analyzer, create)
			{
				InitBlock(enclosingInstance);
			}
			public virtual int DocFreq(Term term)
			{
				lock (directory)
				{
					AssureOpen();
					CreateIndexReader();
					return indexReader.DocFreq(term);
				}
			}
		}
	}
	
	class IndexThread : SupportClass.ThreadClass
	{
		
		private const int ITERATIONS = 500; // iterations of thread test
		
		internal static int id = 0;
		internal static System.Collections.ArrayList idStack = new System.Collections.ArrayList();
		
		internal int added = 0;
		internal int deleted = 0;
		
		private int maxWait = 10;
		private IndexModifier index;
		private int threadNumber;
		private System.Random random;
		
		internal IndexThread(IndexModifier index, int maxWait, int threadNumber)
		{
			this.index = index;
			this.maxWait = maxWait;
			this.threadNumber = threadNumber;
			// TODO: test case is not reproducible despite pseudo-random numbers:
			random = new System.Random((System.Int32) (101 + threadNumber)); // constant seed for better reproducability
		}
		
		override public void  Run()
		{
			try
			{
				for (int i = 0; i < ITERATIONS; i++)
				{
					int rand = random.Next(101);
					if (rand < 5)
					{
						index.Optimize();
					}
					else if (rand < 60)
					{
						Lucene.Net.Documents.Document doc = GetDocument();
						index.AddDocument(doc);
						idStack.Add(doc.Get("id"));
						added++;
					}
					else
					{
						// we just delete the last document added and remove it
						// from the id stack so that it won't be removed twice:
						System.String delId = null;
						try
						{
                            delId = idStack[idStack.Count - 1] as System.String;
                            idStack.RemoveAt(idStack.Count - 1);
						}
						catch (System.ArgumentOutOfRangeException e)
						{
							continue;
						}
						Term delTerm = new Term("id", System.Int32.Parse(delId).ToString());
						int delCount = index.DeleteDocuments(delTerm);
						if (delCount != 1)
						{
							throw new System.SystemException("Internal error: " + threadNumber + " deleted " + delCount + " documents, term=" + delTerm);
						}
						deleted++;
					}
					if (maxWait > 0)
					{
						try
						{
							rand = random.Next(maxWait);
							//System.out.println("waiting " + rand + "ms");
							System.Threading.Thread.Sleep(rand);
						}
						catch (System.Threading.ThreadInterruptedException e)
						{
							throw new System.SystemException(e.Message);
						}
					}
				}
			}
			catch (System.IO.IOException e)
			{
				throw new System.SystemException(e.Message);
			}
		}
		
		private Lucene.Net.Documents.Document GetDocument()
		{
			Lucene.Net.Documents.Document doc = new Lucene.Net.Documents.Document();
			lock (GetType())
			{
				doc.Add(new Field("id", ((System.Int32) id).ToString(), Field.Store.YES, Field.Index.UN_TOKENIZED));
				id++;
			}
			// add random stuff:
			doc.Add(new Field("content", ((System.Int32) random.Next(1000)).ToString(), Field.Store.YES, Field.Index.TOKENIZED));
			doc.Add(new Field("content", ((System.Int32) random.Next(1000)).ToString(), Field.Store.YES, Field.Index.TOKENIZED));
			doc.Add(new Field("all", "x", Field.Store.YES, Field.Index.TOKENIZED));
			return doc;
		}
	}
}