/*
 * JBoss, Home of Professional Open Source.
 * See the COPYRIGHT.txt file distributed with this work for information
 * regarding copyright ownership.  Some portions may be licensed
 * to Red Hat, Inc. under one or more contributor license agreements.
 * 
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 * 
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 * 02110-1301 USA.
 */

package org.teiid.common.buffer.impl;

import java.nio.ByteBuffer;

/**
 * Provides buffer slices or blocks off of a central
 * set of buffers.
 */
public class BlockByteBuffer {
	
	private static class ByteBufferHolder {
		int size;
		volatile ByteBuffer buffer;
		
		public ByteBufferHolder(int size) {
			this.size = size;
		}

		public ByteBuffer duplicate(boolean direct) {
			if (buffer == null) {
				synchronized (this) {
					if (buffer == null) {
						this.buffer = allocate(size, direct);
					}
				}
			}
			return buffer.duplicate();
		}
	}

	private static class BlockByteBufferData {
		int blockAddressBits;
		int segmentAddressBits;
		int segmentSize;
		int blockSize;
		int blockCount;
		boolean direct;
		ByteBufferHolder[] origBuffers;
	}
	
	private BlockByteBufferData data;
	private ByteBuffer[] buffers;
	
	/**
	 * Creates a new {@link BlockByteBuffer} where each buffer segment will be
	 * 1 << segmentAddressBits (max of 30), and a total size of (1 << blockAddressBits)*blockCount.
	 * @param segmentAddressBits
	 * @param blockCount
	 * @param blockAddressBits
	 * @param direct
	 */
	public BlockByteBuffer(int segmentAddressBits, int blockCount, int blockAddressBits, boolean direct) {
		this.data = new BlockByteBufferData();
		this.data.segmentAddressBits = segmentAddressBits;
		this.data.blockAddressBits = blockAddressBits;
		this.data.blockSize = 1 << blockAddressBits;
		this.data.segmentSize = 1 << this.data.segmentAddressBits;
		this.data.blockCount = blockCount;
		long size = ((long)blockCount)<<blockAddressBits;
		int fullSegments = (int)(size>>segmentAddressBits);
		int lastSegmentSize = (int) (size&(data.segmentSize-1));
		int segments = fullSegments;
		if (lastSegmentSize > 0) {
			segments++;
		}
		this.data.origBuffers = new ByteBufferHolder[segments];
		this.data.direct = direct;
		buffers = new ByteBuffer[segments];
		for (int i = 0; i < fullSegments; i++) {
			this.data.origBuffers[i] = new ByteBufferHolder(data.segmentSize);
		}
		if (lastSegmentSize > 0) {
			this.data.origBuffers[fullSegments] = new ByteBufferHolder(lastSegmentSize);
		}
	}
	
	public ByteBuffer[] getBuffers() {
		return buffers;
	}
	
	private BlockByteBuffer() {
		
	}

	public static ByteBuffer allocate(int size, boolean direct) {
		if (direct) {
			ByteBuffer newBuffer = ByteBuffer.allocateDirect(size);
			int longsPerSegment = size>>3;
			//manually initialize until java 7 when it's mandated (although this may already have been performed)
			for (int j = 0; j < longsPerSegment; j++) {
				newBuffer.putLong(0);
			}
			return newBuffer;
		}
		return ByteBuffer.allocate(size);
	}
	
	public BlockByteBuffer duplicate() {
		BlockByteBuffer dup = new BlockByteBuffer();
		dup.data = data;
		dup.buffers = new ByteBuffer[buffers.length];
		return dup;
	}
	
	/**
	 * Return a buffer positioned at the given start byte.
	 * It is assumed that the caller will handle blocks in
	 * a thread safe manner.
	 * @param startIndex
	 * @return
	 */
	public ByteBuffer getByteBuffer(int block) {
		if (block < 0 || block >= data.blockCount) {
			throw new IndexOutOfBoundsException("Invalid block " + block); //$NON-NLS-1$
		}
		int segment = block>>(data.segmentAddressBits-data.blockAddressBits);
		ByteBuffer bb = buffers[segment];
		if (bb == null) {
			bb = buffers[segment] = data.origBuffers[segment].duplicate(data.direct);
		} else {
			bb.rewind();	
		}
		int position = (block<<data.blockAddressBits)&(data.segmentSize-1);
		bb.limit(position + data.blockSize);
		bb.position(position);
		return bb;
	}
		
}
