package com.srbenoit.buffer;

import java.nio.Buffer;
import java.nio.InvalidMarkException;
import com.srbenoit.pool.AbstractPoolObject;

/**
 * A base class with common methods for buffers that can be stored in a <code>Pool</code>. Each
 * buffer contains a pointer to a subsequent buffer ( <code>null</code> for the last buffer in a
 * chain), allowing buffers to be assembled into a linked list.
 */
public abstract class AbstractPooledBuffer extends AbstractPoolObject {

    /** the backing buffer */
    private transient Buffer buf;

    /** flag indicating that this is the last buffer in a unit of data */
    private transient boolean terminal;

    /**
     * Constructs a new <code>AbstractPooledBuffer</code>.
     *
     * @param  buffer  the backing buffer
     */
    public AbstractPooledBuffer(final Buffer buffer) {

        super();

        this.buf = buffer;
        this.terminal = false;
    }

    /**
     * Gets the buffer.
     *
     * @return  the buffer
     */
    public Buffer getBuffer() {

        return this.buf;
    }

    /**
     * Replaces the current buffer with a new buffer.
     *
     * @param  newBuf  the new buffer
     */
    protected void setBuffer(final Buffer newBuf) {

        this.buf = newBuf;
    }

    /**
     * Allocates a new buffer of a specified capacity.
     *
     * @param  capacity  the capacity of the new buffer
     */
    public abstract void newBuffer(final int capacity);

    /**
     * Sets the flag that indicates that this buffer is the last buffer in a unit of data. This
     * could be used, for example, to tell a server to close a channel once this data has been
     * successfully sent.
     *
     * @param  isTerminal  <code>true</code> if this is the last buffer in the unit of data; <code>
     *                     false</code> otherwise
     */
    public void setTerminal(final boolean isTerminal) {

        synchronized (this.synch) {
            this.terminal = isTerminal;
        }
    }

    /**
     * Gets the flag that indicates that this buffer is the last buffer in a unit of data.
     *
     * @return  <code>true</code> if this is the last buffer in the unit of data; <code>
     *          false</code> otherwise
     */
    public boolean isTerminal() {

        synchronized (this.synch) {
            return this.terminal;
        }
    }

    /**
     * Resets this buffer to a virgin state (used before the buffer is returned to a pool).
     */
    @Override public void toVirginState() {

        synchronized (this.synch) {
            this.buf.clear();
            this.terminal = false;
        }
    }

    /**
     * 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() {

        toVirginState();
    }

    /**
     * Clears the backing byte buffer. The position is set to zero, the limit is set to the
     * capacity, and the mark is discarded.
     */
    public void clear() {

        synchronized (this.synch) {
            this.buf.clear();
        }
    }

    /**
     * Flips the backing buffer. The limit is set to the current position and then the position is
     * set to zero. If the mark is defined then it is discarded.
     */
    public void flip() {

        synchronized (this.synch) {
            this.buf.flip();
        }
    }

    /**
     * Rewinds the backing buffer. The position is set to zero and the mark is discarded.
     */
    public void rewind() {

        synchronized (this.synch) {
            this.buf.rewind();
        }
    }

    /**
     * Sets the backing buffer's mark at its position.
     */
    public void mark() {

        synchronized (this.synch) {
            this.buf.mark();
        }
    }

    /**
     * Resets this buffer's position to the previously-marked position.
     *
     * <p>Invoking this method neither changes nor discards the mark's value.
     *
     * @throws  InvalidMarkException  if the mark has not been set
     */
    public void reset() throws InvalidMarkException {

        synchronized (this.synch) {
            this.buf.reset();
        }
    }

    /**
     * Returns the backing buffer's position.
     *
     * @return  the position
     */
    public int position() {

        synchronized (this.synch) {
            return this.buf.position();
        }
    }

    /**
     * Sets the backing buffer's position. If the mark is defined and larger than the new position
     * then it is discarded.
     *
     * @param   offset  the new position value; must be non-negative and no larger than the current
     *                  limit
     * @throws  IllegalArgumentException  if the preconditions on <code>offset</code> do not hold
     */
    public void position(final int offset) throws IllegalArgumentException {

        synchronized (this.synch) {
            this.buf.position(offset);
        }
    }

    /**
     * Returns the number of elements between the current position and the limit.
     *
     * @return  the number of elements remaining
     */
    public int remaining() {

        synchronized (this.synch) {
            return this.buf.remaining();
        }
    }

    /**
     * Returns the backing buffer's capacity.
     *
     * @return  the capacity
     */
    public int capacity() {

        synchronized (this.synch) {
            return this.buf.capacity();
        }
    }

    /**
     * Returns the backing buffer's limit.
     *
     * @return  the limit
     */
    public int limit() {

        synchronized (this.synch) {
            return this.buf.limit();
        }
    }
}
