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


/* Class that Posting and PostingVector use to write byte
* streams into shared fixed-size byte[] arrays.  The idea
* is to allocate slices of increasing lengths For
* example, the first slice is 5 bytes, the next slice is
* 14, etc.  We start by writing our bytes into the first
* 5 bytes.  When we hit the end of the slice, we allocate
* the next slice and then write the address of the new
* slice into the last 4 bytes of the previous slice (the
* "forwarding address").
*
* Each slice is filled with 0's initially, and we mark
* the end with a non-zero byte.  This way the methods
* that are writing into the slice don't need to record
* its length and instead allocate a new slice once they
* hit a non-zero byte. */

using System;
using System.Collections.Generic;
using Lucene.Net.Support;

namespace Lucene.Net.Index
{
	
	sealed public class ByteBlockPool
	{
		private void  InitBlock()
		{
			byteUpto = DocumentsWriter.BYTE_BLOCK_SIZE;
		}
		
		public /*internal*/ abstract class Allocator
		{
			public /*internal*/ abstract void  RecycleByteBlocks(byte[][] blocks, int start, int end);
            public /*internal*/ abstract void RecycleByteBlocks(IList<byte[]> blocks);
			public /*internal*/ abstract byte[] GetByteBlock(bool trackAllocations);
		}
		
		public byte[][] buffers = new byte[10][];
		
		internal int bufferUpto = - 1; // Which buffer we are upto
		public int byteUpto; // Where we are in head buffer
		
		public byte[] buffer; // Current head buffer
		public int byteOffset = - DocumentsWriter.BYTE_BLOCK_SIZE; // Current head offset
		
		private readonly bool trackAllocations;
		private readonly Allocator allocator;
		
		public ByteBlockPool(Allocator allocator, bool trackAllocations)
		{
			InitBlock();
			this.allocator = allocator;
			this.trackAllocations = trackAllocations;
		}
		
		public void  Reset()
		{
			if (bufferUpto != - 1)
			{
				// We allocated at least one buffer
				
				for (int i = 0; i < bufferUpto; i++)
				// Fully zero fill buffers that we fully used
					System.Array.Clear(buffers[i], 0, buffers[i].Length);
				
				// Partial zero fill the final buffer
				System.Array.Clear(buffers[bufferUpto], 0, byteUpto);
				
				if (bufferUpto > 0)
				// Recycle all but the first buffer
					allocator.RecycleByteBlocks(buffers, 1, 1 + bufferUpto);
				
				// Re-use the first buffer
				bufferUpto = 0;
				byteUpto = 0;
				byteOffset = 0;
				buffer = buffers[0];
			}
		}
		
		public void  NextBuffer()
		{
			if (1 + bufferUpto == buffers.Length)
			{
				var newBuffers = new byte[(int) (buffers.Length * 1.5)][];
				Array.Copy(buffers, 0, newBuffers, 0, buffers.Length);
				buffers = newBuffers;
			}
			buffer = buffers[1 + bufferUpto] = allocator.GetByteBlock(trackAllocations);
			bufferUpto++;
			
			byteUpto = 0;
			byteOffset += DocumentsWriter.BYTE_BLOCK_SIZE;
		}
		
		public int NewSlice(int size)
		{
			if (byteUpto > DocumentsWriter.BYTE_BLOCK_SIZE - size)
				NextBuffer();
			int upto = byteUpto;
			byteUpto += size;
			buffer[byteUpto - 1] = 16;
			return upto;
		}
		
		// Size of each slice.  These arrays should be at most 16
		// elements (index is encoded with 4 bits).  First array
		// is just a compact way to encode X+1 with a max.  Second
		// array is the length of each slice, ie first slice is 5
		// bytes, next slice is 14 bytes, etc.
		internal static readonly int[] nextLevelArray = new int[]{1, 2, 3, 4, 5, 6, 7, 8, 9, 9};
		internal static readonly int[] levelSizeArray = new int[]{5, 14, 20, 30, 40, 40, 80, 80, 120, 200};
		internal static readonly int FIRST_LEVEL_SIZE = levelSizeArray[0];
        public readonly static int FIRST_LEVEL_SIZE_For_NUnit_Test = levelSizeArray[0];
		
		public int AllocSlice(byte[] slice, int upto)
		{
			
			int level = slice[upto] & 15;
			int newLevel = nextLevelArray[level];
			int newSize = levelSizeArray[newLevel];
			
			// Maybe allocate another block
			if (byteUpto > DocumentsWriter.BYTE_BLOCK_SIZE - newSize)
				NextBuffer();
			
			int newUpto = byteUpto;
			int offset = newUpto + byteOffset;
			byteUpto += newSize;
			
			// Copy forward the past 3 bytes (which we are about
			// to overwrite with the forwarding address):
			buffer[newUpto] = slice[upto - 3];
			buffer[newUpto + 1] = slice[upto - 2];
			buffer[newUpto + 2] = slice[upto - 1];
			
			// Write forwarding address at end of last slice:
			slice[upto - 3] = (byte) (Number.URShift(offset, 24));
			slice[upto - 2] = (byte) (Number.URShift(offset, 16));
			slice[upto - 1] = (byte) (Number.URShift(offset, 8));
			slice[upto] = (byte) offset;
			
			// Write new level:
			buffer[byteUpto - 1] = (byte) (16 | newLevel);
			
			return newUpto + 3;
		}

        public static int FIRST_LEVEL_SIZE_ForNUnit
        {
            get { return FIRST_LEVEL_SIZE; }
        }
	}
}