/* 
 * 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 Lucene.Net.Support;
using NUnit.Framework;

using WhitespaceAnalyzer = Lucene.Net.Analysis.WhitespaceAnalyzer;
using Document = Lucene.Net.Documents.Document;
using Field = Lucene.Net.Documents.Field;
using IndexReader = Lucene.Net.Index.IndexReader;
using IndexWriter = Lucene.Net.Index.IndexWriter;
using Term = Lucene.Net.Index.Term;
using NIOFSIndexInput = Lucene.Net.Store.NIOFSDirectory.NIOFSIndexInput;
using SimpleFSIndexInput = Lucene.Net.Store.SimpleFSDirectory.SimpleFSIndexInput;
using ArrayUtil = Lucene.Net.Util.ArrayUtil;
using IndexSearcher = Lucene.Net.Search.IndexSearcher;
using ScoreDoc = Lucene.Net.Search.ScoreDoc;
using TermQuery = Lucene.Net.Search.TermQuery;
using LuceneTestCase = Lucene.Net.Util.LuceneTestCase;
using _TestUtil = Lucene.Net.Util._TestUtil;

namespace Lucene.Net.Store
{
	
	[TestFixture]
	public class TestBufferedIndexInput:LuceneTestCase
	{
		
		private static void  WriteBytes(System.IO.FileInfo aFile, long size)
		{
			System.IO.Stream stream = null;
			try
			{
				stream = new System.IO.FileStream(aFile.FullName, System.IO.FileMode.Create);
				for (int i = 0; i < size; i++)
				{
					stream.WriteByte((byte) Byten(i));
				}
				stream.Flush();
			}
			finally
			{
				if (stream != null)
				{
					stream.Close();
				}
			}
		}
		
		private const long TEST_FILE_LENGTH = 1024 * 1024;
		
		// Call readByte() repeatedly, past the buffer boundary, and see that it
		// is working as expected.
		// Our input comes from a dynamically generated/ "file" - see
		// MyBufferedIndexInput below.
		[Test]
		public virtual void  TestReadByte()
		{
			MyBufferedIndexInput input = new MyBufferedIndexInput();
			for (int i = 0; i < BufferedIndexInput.BUFFER_SIZE * 10; i++)
			{
				Assert.AreEqual(input.ReadByte(), Byten(i));
			}
		}
		
		// Call readBytes() repeatedly, with various chunk sizes (from 1 byte to
		// larger than the buffer size), and see that it returns the bytes we expect.
		// Our input comes from a dynamically generated "file" -
		// see MyBufferedIndexInput below.
		[Test]
		public virtual void  TestReadBytes()
		{
			System.Random r = NewRandom();
			
			MyBufferedIndexInput input = new MyBufferedIndexInput();
			RunReadBytes(input, BufferedIndexInput.BUFFER_SIZE, r);
			
			// This tests the workaround code for LUCENE-1566 where readBytesInternal
			// provides a workaround for a JVM Bug that incorrectly raises a OOM Error
			// when a large byte buffer is passed to a file read.
			// NOTE: this does only test the chunked reads and NOT if the Bug is triggered.
			//final int tmpFileSize = 1024 * 1024 * 5;
			int inputBufferSize = 128;
			
            System.String tempDirectory = System.IO.Path.GetTempPath();

			System.IO.FileInfo tmpInputFile = new System.IO.FileInfo(System.IO.Path.Combine(tempDirectory, "IndexInput.tmpFile"));
			System.IO.File.Delete(tmpInputFile.FullName);
			WriteBytes(tmpInputFile, TEST_FILE_LENGTH);
			
			// run test with chunk size of 10 bytes
			RunReadBytesAndClose(new SimpleFSIndexInput(tmpInputFile, inputBufferSize, 10), inputBufferSize, r);
			// run test with chunk size of 100 MB - default
			RunReadBytesAndClose(new SimpleFSIndexInput(tmpInputFile, inputBufferSize, FSDirectory.DEFAULT_READ_CHUNK_SIZE), inputBufferSize, r);
            Assert.Pass("Supressing Sub-Tests on NIOFSIndexInput classes");
			// run test with chunk size of 10 bytes
			//RunReadBytesAndClose(new NIOFSIndexInput(tmpInputFile, inputBufferSize, 10), inputBufferSize, r);    // {{Aroush-2.9}} suppressing this test since NIOFSIndexInput isn't ported
            System.Console.Out.WriteLine("Suppressing sub-test: 'RunReadBytesAndClose(new NIOFSIndexInput(tmpInputFile, inputBufferSize, 10), inputBufferSize, r);' since NIOFSIndexInput isn't ported");
			// run test with chunk size of 100 MB - default
			//RunReadBytesAndClose(new NIOFSIndexInput(tmpInputFile, inputBufferSize), inputBufferSize, r);     // {{Aroush-2.9}} suppressing this test since NIOFSIndexInput isn't ported
            System.Console.Out.WriteLine("Suppressing sub-test: 'RunReadBytesAndClose(new NIOFSIndexInput(tmpInputFile, inputBufferSize), inputBufferSize, r);' since NIOFSIndexInput isn't ported");
		}
		
		private void  RunReadBytesAndClose(IndexInput input, int bufferSize, System.Random r)
		{
			try
			{
				RunReadBytes(input, bufferSize, r);
			}
			finally
			{
				input.Close();
			}
		}
		
		private void  RunReadBytes(IndexInput input, int bufferSize, System.Random r)
		{
			
			int pos = 0;
			// gradually increasing size:
			for (int size = 1; size < bufferSize * 10; size = size + size / 200 + 1)
			{
				CheckReadBytes(input, size, pos);
				pos += size;
				if (pos >= TEST_FILE_LENGTH)
				{
					// wrap
					pos = 0;
					input.Seek(0L);
				}
			}
			// wildly fluctuating size:
			for (long i = 0; i < 1000; i++)
			{
				int size = r.Next(10000);
				CheckReadBytes(input, 1 + size, pos);
				pos += 1 + size;
				if (pos >= TEST_FILE_LENGTH)
				{
					// wrap
					pos = 0;
					input.Seek(0L);
				}
			}
			// constant small size (7 bytes):
			for (int i = 0; i < bufferSize; i++)
			{
				CheckReadBytes(input, 7, pos);
				pos += 7;
				if (pos >= TEST_FILE_LENGTH)
				{
					// wrap
					pos = 0;
					input.Seek(0L);
				}
			}
		}
		
		private byte[] buffer = new byte[10];
		
		private void  CheckReadBytes(IndexInput input, int size, int pos)
		{
			// Just to see that "offset" is treated properly in readBytes(), we
			// add an arbitrary offset at the beginning of the array
			int offset = size % 10; // arbitrary
			buffer = ArrayUtil.Grow(buffer, offset + size);
			Assert.AreEqual(pos, input.FilePointer);
			long left = TEST_FILE_LENGTH - input.FilePointer;
			if (left <= 0)
			{
				return ;
			}
			else if (left < size)
			{
				size = (int) left;
			}
			input.ReadBytes(buffer, offset, size);
			Assert.AreEqual(pos + size, input.FilePointer);
			for (int i = 0; i < size; i++)
			{
				Assert.AreEqual(Byten(pos + i), buffer[offset + i], "pos=" + i + " filepos=" + (pos + i));
			}
		}
		
		// This tests that attempts to readBytes() past an EOF will fail, while
		// reads up to the EOF will succeed. The EOF is determined by the
		// BufferedIndexInput's arbitrary length() value.
		[Test]
		public virtual void  TestEOF()
		{
			MyBufferedIndexInput input = new MyBufferedIndexInput(1024);
			// see that we can read all the bytes at one go:
			CheckReadBytes(input, (int) input.Length(), 0);
			// go back and see that we can't read more than that, for small and
			// large overflows:
			int pos = (int) input.Length() - 10;

			input.Seek(pos);
			CheckReadBytes(input, 10, pos);

			input.Seek(pos);
            Assert.Throws<System.IO.IOException>(() => CheckReadBytes(input, 11, pos), "Block read past end of file");

			input.Seek(pos);
            Assert.Throws<System.IO.IOException>(() => CheckReadBytes(input, 50, pos), "Block read past end of file");

			input.Seek(pos);
            Assert.Throws<System.IO.IOException>(() => CheckReadBytes(input, 100000, pos), "Block read past end of file");
		}
		
		// byten emulates a file - byten(n) returns the n'th byte in that file.
		// MyBufferedIndexInput reads this "file".
		private static byte Byten(long n)
		{
			return (byte) (n * n % 256);
		}
		private class MyBufferedIndexInput:BufferedIndexInput
		{
			private long pos;
			private long len;
			public MyBufferedIndexInput(long len)
			{
				this.len = len;
				this.pos = 0;
			}
			public MyBufferedIndexInput():this(System.Int64.MaxValue)
			{
			}
			public override void  ReadInternal(byte[] b, int offset, int length)
			{
				for (int i = offset; i < offset + length; i++)
					b[i] = Lucene.Net.Store.TestBufferedIndexInput.Byten(pos++);
			}
			
			public override void  SeekInternal(long pos)
			{
				this.pos = pos;
			}

            protected override void Dispose(bool disposing)
            {
                // Do nothing
            }
			
			public override long Length()
			{
				return len;
			}
		}
		
		[Test]
		public virtual void  TestSetBufferSize()
		{
			System.IO.DirectoryInfo indexDir = new System.IO.DirectoryInfo(System.IO.Path.Combine(AppSettings.Get("tempDir", ""), "testSetBufferSize"));
			MockFSDirectory dir = new MockFSDirectory(indexDir, NewRandom());
			try
			{
				IndexWriter writer = new IndexWriter(dir, new WhitespaceAnalyzer(), true, IndexWriter.MaxFieldLength.LIMITED);
				writer.UseCompoundFile = false;
				for (int i = 0; i < 37; i++)
				{
					Document doc = new Document();
					doc.Add(new Field("content", "aaa bbb ccc ddd" + i, Field.Store.YES, Field.Index.ANALYZED));
					doc.Add(new Field("id", "" + i, Field.Store.YES, Field.Index.ANALYZED));
					writer.AddDocument(doc);
				}
				writer.Close();
				
				dir.allIndexInputs.Clear();
				
				IndexReader reader = IndexReader.Open(dir, false);
				Term aaa = new Term("content", "aaa");
				Term bbb = new Term("content", "bbb");
				Term ccc = new Term("content", "ccc");
				Assert.AreEqual(37, reader.DocFreq(ccc));
				reader.DeleteDocument(0);
				Assert.AreEqual(37, reader.DocFreq(aaa));
				dir.tweakBufferSizes();
				reader.DeleteDocument(4);
				Assert.AreEqual(reader.DocFreq(bbb), 37);
				dir.tweakBufferSizes();
				
				IndexSearcher searcher = new IndexSearcher(reader);
				ScoreDoc[] hits = searcher.Search(new TermQuery(bbb), null, 1000).ScoreDocs;
				dir.tweakBufferSizes();
				Assert.AreEqual(35, hits.Length);
				dir.tweakBufferSizes();
				hits = searcher.Search(new TermQuery(new Term("id", "33")), null, 1000).ScoreDocs;
				dir.tweakBufferSizes();
				Assert.AreEqual(1, hits.Length);
				hits = searcher.Search(new TermQuery(aaa), null, 1000).ScoreDocs;
				dir.tweakBufferSizes();
				Assert.AreEqual(35, hits.Length);
				searcher.Close();
				reader.Close();
			}
			finally
			{
				_TestUtil.RmDir(indexDir);
			}
		}
		
		private class MockFSDirectory:Directory
		{
			
			internal System.Collections.IList allIndexInputs = new System.Collections.ArrayList();
			
			internal System.Random rand;
			
			private Directory dir;
		    private bool isDisposed;
			
			public MockFSDirectory(System.IO.DirectoryInfo path, System.Random rand)
			{
				this.rand = rand;
				interalLockFactory = new NoLockFactory();
				dir = new SimpleFSDirectory(path, null);
			}
			
			public override IndexInput OpenInput(System.String name)
			{
				return OpenInput(name, BufferedIndexInput.BUFFER_SIZE);
			}
			
			public virtual void  tweakBufferSizes()
			{
				System.Collections.IEnumerator it = allIndexInputs.GetEnumerator();
				//int count = 0;
				while (it.MoveNext())
				{
					BufferedIndexInput bii = (BufferedIndexInput) it.Current;
					int bufferSize = 1024 + (int) System.Math.Abs(rand.Next() % 32768);
					bii.SetBufferSize(bufferSize);
					//count++;
				}
				//System.out.println("tweak'd " + count + " buffer sizes");
			}
			
			public override IndexInput OpenInput(System.String name, int bufferSize)
			{
				// Make random changes to buffer size
				bufferSize = 1 + (int) System.Math.Abs(rand.Next() % 10);
				IndexInput f = dir.OpenInput(name, bufferSize);
				allIndexInputs.Add(f);
				return f;
			}
			
			public override IndexOutput CreateOutput(System.String name)
			{
				return dir.CreateOutput(name);
			}

            protected override void Dispose(bool disposing)
            {
                if (isDisposed) return;

                if (disposing)
                {
                    if (dir != null)
                    {
                        dir.Close();
                    }
                }

                dir = null;
                isDisposed = true;
            }
			
			public override void  DeleteFile(System.String name)
			{
				dir.DeleteFile(name);
			}
			public override void  TouchFile(System.String name)
			{
				dir.TouchFile(name);
			}
			public override long FileModified(System.String name)
			{
				return dir.FileModified(name);
			}
			public override bool FileExists(System.String name)
			{
				return dir.FileExists(name);
			}
			public override System.String[] ListAll()
			{
				return dir.ListAll();
			}
			
			public override long FileLength(System.String name)
			{
				return dir.FileLength(name);
			}
		}
	}
}