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

namespace Lucene.Net.Store
{
	
	/// <summary>Base implementation class for buffered {@link IndexInput}. </summary>
	public abstract class BufferedIndexInput : IndexInput, System.ICloneable
	{
		internal static readonly int BUFFER_SIZE;
		
		private byte[] buffer;
		
		private long bufferStart = 0; // position in file of buffer
		private int bufferLength = 0; // end of valid bytes
		private int bufferPosition = 0; // next byte to read
		
		public override byte ReadByte()
		{
			if (bufferPosition >= bufferLength)
				Refill();
			return buffer[bufferPosition++];
		}
		
		public override void  ReadBytes(byte[] b, int offset, int len)
		{
			if (len < BUFFER_SIZE)
			{
				for (int i = 0; i < len; i++)
				    // read byte-by-byte
					b[i + offset] = (byte) ReadByte();
			}
			else
			{
				// read all-at-once
				long start = GetFilePointer();
				SeekInternal(start);
				ReadInternal(b, offset, len);
				
				bufferStart = start + len; // adjust stream variables
				bufferPosition = 0;
				bufferLength = 0; // trigger refill() on read
			}
		}
		
		private void  Refill()
		{
			long start = bufferStart + bufferPosition;
			long end = start + BUFFER_SIZE;
			if (end > Length())
			    // don't read past EOF
				end = Length();
			bufferLength = (int) (end - start);
			if (bufferLength <= 0)
				throw new System.IO.IOException("read past EOF");
			
			if (buffer == null)
				buffer = new byte[BUFFER_SIZE]; // allocate buffer lazily
			ReadInternal(buffer, 0, bufferLength);
			
			bufferStart = start;
			bufferPosition = 0;
		}
		
		/// <summary>Expert: implements buffer refill.  Reads bytes from the current position
		/// in the input.
		/// </summary>
		/// <param name="b">the array to read bytes into
		/// </param>
		/// <param name="offset">the offset in the array to start storing bytes
		/// </param>
		/// <param name="length">the number of bytes to read
		/// </param>
		public abstract void  ReadInternal(byte[] b, int offset, int length);
		
		public override long GetFilePointer()
		{
			return bufferStart + bufferPosition;
		}
		
		public override void  Seek(long pos)
		{
			if (pos >= bufferStart && pos < (bufferStart + bufferLength))
				bufferPosition = (int) (pos - bufferStart);
			    // seek within buffer
			else
			{
				bufferStart = pos;
				bufferPosition = 0;
				bufferLength = 0; // trigger refill() on read()
				SeekInternal(pos);
			}
		}
		
		/// <summary>Expert: implements seek.  Sets current position in this file, where the
		/// next {@link #ReadInternal(byte[],int,int)} will occur.
		/// </summary>
		/// <seealso cref="ReadInternal(byte[],int,int)">
		/// </seealso>
		public abstract void  SeekInternal(long pos);
		
		public override System.Object Clone()
		{
			BufferedIndexInput clone = (BufferedIndexInput) base.Clone();
			
			if (buffer != null)
			{
				clone.buffer = new byte[BUFFER_SIZE];
				Array.Copy(buffer, 0, clone.buffer, 0, bufferLength);
			}
			
			return clone;
		}

        static BufferedIndexInput()
		{
			BUFFER_SIZE = BufferedIndexOutput.BUFFER_SIZE;
		}
	}
}