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

#ifndef _DECAF_NIO_BUFFER_H_
#define _DECAF_NIO_BUFFER_H_

#include <decaf/lang/exceptions/IllegalArgumentException.h>
#include <decaf/nio/InvalidMarkException.h>

namespace decaf {
namespace nio {

    /**
     * A container for data of a specific primitive type.
     *
     * A buffer is a linear, finite sequence of elements of a specific primitive
     * type. Aside from its content, the essential properties of a buffer are
     * its capacity, limit, and position:
     *
     * A buffer's capacity is the number of elements it contains. The capacity of a
     * buffer is never negative and never changes.
     *
     * A buffer's limit is the index of the first element that should not be read
     * or written. A buffer's limit is never negative and is never greater than its
     * capacity.
     *
     * A buffer's position is the index of the next element to be read or written.
     * A buffer's position is never negative and is never greater than its limit.
     *
     * There is one subclass of this class for each non-boolean primitive type.
     *
     * Transferring data:
     *   Each subclass of this class defines two categories of get and put operations:
     *   * Relative operations read or write one or more elements starting at the
     *     current position and then increment the position by the number of
     *     elements transferred. If the requested transfer exceeds the limit then a
     *     relative get operation throws a BufferUnderflowException and a relative
     *     put operation throws a BufferOverflowException; in either case, no data
     *     is transferred.
     *   * Absolute operations take an explicit element index and do not affect the
     *     position. Absolute get and put operations throw an IndexOutOfBoundsException
     *     if the index argument exceeds the limit.
     *
     *  Data may also, of course, be transferred in to or out of a buffer by the I/O
     *  operations of an appropriate channel, which are always relative to the current
     *  position.
     *
     * Marking and resetting:
     *
     *   A buffer's mark is the index to which its position will be reset when the
     *   reset method is invoked. The mark is not always defined, but when it is
     *   defined it is never negative and is never greater than the position. If the
     *   mark is defined then it is discarded when the position or the limit is
     *   adjusted to a value smaller than the mark. If the mark is not defined then
     *   invoking the reset method causes an InvalidMarkException to be thrown.
     *
     * Invariants:
     *
     *   The following invariant holds for the mark, position, limit, and capacity values:
     *     0 <= mark <= position <= limit <= capacity
     *
     *   A newly-created buffer always has a position of zero and a mark that is
     *   undefined.  The initial limit may be zero, or it may be some other value that
     *   depends upon the type of the buffer and the manner in which it is constructed.
     *   The initial content of a buffer is, in general, undefined.
     *
     * Clearing, flipping, and rewinding:
     *
     *   In addition to methods for accessing the position, limit, and capacity values
     *   and for marking and resetting, this class also defines the following operations
     *   upon buffers:
     *
     *    clear() makes a buffer ready for a new sequence of channel-read or relative
     *    put operations: It sets the limit to the capacity and the position to zero.
     *
     *    flip() makes a buffer ready for a new sequence of channel-write or relative
     *    get operations: It sets the limit to the current position and then sets the
     *    position to zero.
     *
     *    rewind() makes a buffer ready for re-reading the data that it already
     *    contains: It leaves the limit unchanged and sets the position to zero.
     *
     * Read-only buffers:
     *
     *   Every buffer is readable, but not every buffer is writable. The mutation
     *   methods of each buffer class are specified as optional operations that will
     *   throw a ReadOnlyBufferException when invoked upon a read-only buffer. A
     *   read-only buffer does not allow its content to be changed, but its mark,
     *   position, and limit values are mutable. Whether or not a buffer is read-only
     *   may be determined by invoking its isReadOnly method.
     *
     * Thread safety:
     *
     *   Buffers are not safe for use by multiple concurrent threads. If a buffer is to
     *   be used by more than one thread then access to the buffer should be controlled
     *   by appropriate synchronization.
     *
     * Invocation chaining:
     *
     *   Methods in this class that do not otherwise have a value to return are specified
     *   to return the buffer upon which they are invoked. This allows method invocations
     *   to be chained; for example, the sequence of statements
     *
     *     b.flip();
     *     b.position(23);
     *     b.limit(42);
     *
     *   can be replaced by the single, more compact statement
     *     b.flip().position(23).limit(42);
     */
    class DECAF_API Buffer {
    protected:

        mutable int _position;
        int _capacity;
        int _limit;
        int _mark;
        bool _markSet;

    public:

        Buffer(int capactiy);
        Buffer(const Buffer& other);
        virtual ~Buffer() {}

    public:

        /**
         * @return this buffer's capacity.
         */
        virtual int capacity() const {
            return this->_capacity;
        }

        /**
         * @return the current position in the buffer
         */
        virtual int position() const {
            return this->_position;
        }

        /**
         * Sets this buffer's position. If the mark is defined and larger than the
         * new position then it is discarded.
         *
         * @param newPosition
         *      The new postion in the buffer to set.
         *
         * @return a reference to This buffer.
         *
         * @throws IllegalArgumentException if preconditions on the new pos don't hold.
         */
        virtual Buffer& position(int newPosition);

        /**
         * @return this buffers Limit
         */
        virtual int limit() const {
            return this->_limit;
        }

        /**
         * Sets this buffer's limit. If the position is larger than the new limit then
         * it is set to the new limit. If the mark is defined and larger than the new
         * limit then it is discarded.
         *
         * @param newLimit
         *      The new limit value; must be no larger than this buffer's capacity.
         *
         * @return A reference to This buffer
         *
         * @throws IllegalArgumentException if preconditions on the new pos don't hold.
         */
        virtual Buffer& limit(int newLimit);

        /**
         * Sets this buffer's mark at its position.
         *
         * @return a reference to this buffer.
         */
        virtual Buffer& mark();

        /**
         * Resets this buffer's position to the previously-marked position.
         *
         * @return a reference to this buffer.
         *
         * @throws InvalidMarkException - If the mark has not been set
         */
        virtual Buffer& reset();

        /**
         * Clears this buffer. The position is set to zero, the limit is set to the
         * capacity, and the mark is discarded.
         *
         * Invoke this method before using a sequence of channel-read or put operations
         * to fill this buffer. For example:
         *
         *    buf.clear();     // Prepare buffer for reading
         *    in.read(buf);    // Read data
         *
         * This method does not actually erase the data in the buffer, but it is named
         * as if it did because it will most often be used in situations in which that
         * might as well be the case.
         *
         * @return a reference to this buffer.
         */
        virtual Buffer& clear();

        /**
         * Flips this 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.
         *
         * After a sequence of channel-read or put operations, invoke this method to
         * prepare for a sequence of channel-write or relative get operations. For
         * example:
         *
         *     buf.put(magic);    // Prepend header
         *     in.read(buf);      // Read data into rest of buffer
         *     buf.flip();        // Flip buffer
         *     out.write(buf);    // Write header + data to channel
         *
         * This method is often used in conjunction with the compact method when
         * transferring data from one place to another.
         *
         * @return a reference to this buffer.
         */
        virtual Buffer& flip();

        /**
         * Rewinds this buffer. The position is set to zero and the mark is discarded.
         *
         * Invoke this method before a sequence of channel-write or get operations,
         * assuming that the limit has already been set appropriately. For example:
         *
         *    out.write(buf);    // Write remaining data
         *    buf.rewind();      // Rewind buffer
         *    buf.get(array);    // Copy data into array
         *
         * @return a reference to this buffer.
         */
        virtual Buffer& rewind();

        /**
         * Returns the number of elements between the current position and the limit.
         *
         * @return The number of elements remaining in this buffer
         */
        virtual int remaining() const {
            return _limit - _position;
        }

        /**
         * Tells whether there are any elements between the current position and the limit.
         *
         * @return true if, and only if, there is at least one element remaining in
         *          this buffer.
         */
        virtual bool hasRemaining() const {
            return remaining() != 0;
        }

        /**
         * Tells whether or not this buffer is read-only.
         *
         * @return true if, and only if, this buffer is read-only.
         */
        virtual bool isReadOnly() const = 0;

    };

}}

#endif /*_DECAF_NIO_BUFFER_H_*/
