package com.srbenoit.buffer;

import java.nio.BufferOverflowException;
import java.nio.BufferUnderflowException;
import java.nio.CharBuffer;
import java.nio.ReadOnlyBufferException;
import com.srbenoit.pool.AbstractPoolObject;

/**
 * A buffer that holds character data and that can be stored in a <code>Pool</code>. Each buffer
 * contains a pointer to a subsequent buffer (null for the last buffer in a chain), allowing
 * buffers to be assembled into a linked list.
 */
public class PooledCharBuffer extends AbstractPooledBuffer {

    /** the next buffer in a linked list */
    private transient PooledCharBuffer next;

    /**
     * Constructs a new <code>PooledCharBuffer</code>.
     *
     * @param  capacity  the capacity of a buffer, in characters
     */
    public PooledCharBuffer(final int capacity) {

        super(CharBuffer.allocate(capacity));
        this.next = null;
    }

    /**
     * Gets the buffer.
     *
     * @return  the buffer
     */
    @Override public CharBuffer getBuffer() {

        return (CharBuffer) super.getBuffer();
    }

    /**
     * Allocates a new buffer of a specified capacity;
     *
     * @param  capacity  the capacity of the new buffer
     */
    @Override public void newBuffer(final int capacity) {

        setBuffer(CharBuffer.allocate(capacity));
    }

    /**
     * Sets the next buffer in the linked list.
     *
     * @param  nextBuffer  the next buffer
     */
    public void setNext(final PooledCharBuffer nextBuffer) {

        synchronized (this.synch) {
            this.next = nextBuffer;
        }
    }

    /**
     * Gets the next buffer in the linked list.
     *
     * @return  the next buffer
     */
    public PooledCharBuffer getNext() {

        synchronized (this.synch) {
            return this.next;
        }
    }

    /**
     * Reads the character at the backing buffer's current position, and then increments that
     * position.
     *
     * @return  the character at the backing buffer's current position
     * @throws  BufferUnderflowException  if the backing buffer's current position is not smaller
     *                                    than its limit
     */
    public char get() throws BufferUnderflowException {

        synchronized (this.synch) {
            return getBuffer().get();
        }
    }

    /**
     * Writes the given character into the backing buffer at the current position, and then
     * increments that position.
     *
     * @param   data  the character to be written
     * @throws  BufferOverflowException  if the backing buffer's current position is not smaller
     *                                   than its limit
     * @throws  ReadOnlyBufferException  if the backing buffer is read-only
     */
    public void put(final char data) throws BufferOverflowException, ReadOnlyBufferException {

        synchronized (this.synch) {
            getBuffer().put(data);
        }
    }

    /**
     * Reads the character from the backing buffer at the given index.
     *
     * @param   index  the index from which the character will be read
     * @return  the character at the given index
     * @throws  IndexOutOfBoundsException  if <code>index</code> is negative or not smaller than
     *                                     the backing buffer's limit
     */
    public char get(final int index) {

        synchronized (this.synch) {
            return getBuffer().get(index);
        }
    }

    /**
     * Writes the given character into the backing buffer at the given index.
     *
     * @param   index  the index at which the character will be written
     * @param   data   the character value to be written
     * @throws  IndexOutOfBoundsException  if <code>index</code> is negative or not smaller than
     *                                     the buffer's limit
     * @throws  ReadOnlyBufferException    if this buffer is read-only
     */
    public void put(final int index, final char data) throws IndexOutOfBoundsException,
        ReadOnlyBufferException {

        synchronized (this.synch) {
            getBuffer().put(index, data);
        }
    }

    /**
     * Transfers characters from the backing buffer into the given destination array. If there are
     * fewer characters remaining in the buffer than are required to satisfy the request, that is,
     * if <code>length &gt; remaining()</code>, then no characters are transferred and a <code>
     * BufferUnderflowException</code> is thrown.
     *
     * <p>Otherwise, this method copies <code>length</code> characters from this buffer into the
     * given array, starting at the current position of this buffer and at the given offset in the
     * array. The position of this buffer is then incremented by <code>length</code>.
     *
     * @param   dst     the array into which characters are to be written
     * @param   offset  the offset within the array of the first byte to be written; must be
     *                  non-negative and no larger than <code>dst.length</code>
     * @param   length  the maximum number of characters to be written to the given array; must be
     *                  non-negative and no larger than <code>dst.length - offset</code>
     * @throws  BufferUnderflowException   if there are fewer than <code>length</code> characters
     *                                     remaining in the backing buffer
     * @throws  IndexOutOfBoundsException  if the preconditions on the <code>offset</code> and
     *                                     <code>length</code> parameters do not hold
     */
    public void get(final char[] dst, final int offset, final int length) {

        synchronized (this.synch) {
            getBuffer().get(dst, offset, length);
        }
    }

    /**
     * Transfers characters from the backing buffer into the given destination array.
     *
     * @param   dst  the array into which characters are to be written
     * @throws  BufferUnderflowException  if there are fewer than <code>length</code> characters
     *                                    remaining in the backing buffer
     */
    public void get(final char[] dst) {

        synchronized (this.synch) {
            getBuffer().get(dst);
        }
    }

    /**
     * Transfers characters into the backing buffer from the given source array. If there are more
     * characters to be copied from the array than remain in this buffer, that is, if <code>length
     * &gt; remaining()</code>, then no bytes are transferred and a <code>
     * BufferOverflowException</code> is thrown.
     *
     * <p>Otherwise, this method copies <code>length</code> characters from the given array into
     * the backing buffer, starting at the given offset in the array and at the current position of
     * the backing buffer. The position of this buffer is then incremented by <code>length</code>.
     *
     * @param   src     the array from which characters are to be read
     * @param   offset  the offset within the array of the first character to be read; must be
     *                  non-negative and no larger than <code>array.length</code>
     * @param   length  the number of characters to be read from the given array; must be
     *                  non-negative and no larger than <code>array.length - offset</code>
     * @throws  BufferOverflowException    if there is insufficient space in this buffer
     * @throws  IndexOutOfBoundsException  if the preconditions on the <code>offset</code> and
     *                                     <code>length</code> parameters do not hold
     * @throws  ReadOnlyBufferException    if this buffer is read-only
     */
    public void put(final char[] src, final int offset, final int length)
        throws BufferOverflowException, IndexOutOfBoundsException, ReadOnlyBufferException {

        synchronized (this.synch) {
            getBuffer().put(src, offset, length);
        }
    }

    /**
     * This method transfers the entire content of the given source character array into the
     * backing buffer.
     *
     * @param   src  the array from which characters are to be read
     * @throws  BufferOverflowException  if there is insufficient space in the backing buffer
     * @throws  ReadOnlyBufferException  if this buffer is read-only
     */
    public void put(final char[] src) {

        synchronized (this.synch) {
            getBuffer().put(src);
        }
    }

    /**
     * Creates a copy of the object, but with an independent backing buffer - used to create new
     * objects when the pool is empty. This is more efficient than creating a new object.
     *
     * @return  the copy
     */
    @Override public AbstractPoolObject copy() {

        PooledCharBuffer copy;

        synchronized (this.synch) {

            try {
                copy = (PooledCharBuffer) super.clone();
                copy.newBuffer(capacity());
            } catch (CloneNotSupportedException e) {
                copy = new PooledCharBuffer(capacity());
            }
        }

        return copy;
    }

    /**
     * Resets this buffer to a virgin state (used before the buffer is returned to a pool).
     */
    @Override public void toVirginState() {

        synchronized (this.synch) {
            this.next = null;
            super.toVirginState();
        }
    }

    /**
     * Informs the pool object that it is being released for garbage collection and should free any
     * internal resources. The object may not be reused after this method is called.
     */
    @Override public void die() {

        synchronized (this.synch) {
            this.next = null;
            super.die();
        }
    }
}
