/*
 *  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. 
 *  
 */
package org.apache.mina.common.support;

import java.nio.ByteOrder;
import java.nio.CharBuffer;
import java.nio.DoubleBuffer;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.nio.LongBuffer;
import java.nio.ShortBuffer;

import org.apache.mina.common.ByteBuffer;
import org.apache.mina.common.ByteBufferAllocator;

/**
 * A base implementation of {@link ByteBuffer}.  This implementation
 * assumes that {@link ByteBuffer#buf()} always returns a correct NIO
 * {@link java.nio.ByteBuffer} instance.  Most implementations could
 * extend this class and implement their own buffer management mechanism.
 *
 * @author The Apache Directory Project (mina-dev@directory.apache.org)
 * @version $Rev: 555855 $, $Date: 2007-07-13 12:19:00 +0900 (Fri, 13 Jul 2007) $
 * @noinspection StaticNonFinalField
 * @see ByteBufferAllocator
 */
public abstract class BaseByteBuffer extends ByteBuffer {
    private boolean autoExpand;

    /**
     * We don't have any access to Buffer.markValue(), so we need to track it down,
     * which will cause small extra overhead.
     */
    private int mark = -1;

    protected BaseByteBuffer() {
    }

    public boolean isDirect() {
        return buf().isDirect();
    }

    public boolean isReadOnly() {
        return buf().isReadOnly();
    }

    public int capacity() {
        return buf().capacity();
    }

    public ByteBuffer capacity(int newCapacity) {
        if (newCapacity > capacity()) {
            // Allocate a new buffer and transfer all settings to it.
            int pos = position();
            int limit = limit();
            ByteOrder bo = order();

            capacity0(newCapacity);
            buf().limit(limit);
            if (mark >= 0) {
                buf().position(mark);
                buf().mark();
            }
            buf().position(pos);
            buf().order(bo);
        }

        return this;
    }

    /**
     * Implement this method to increase the capacity of this buffer.
     * <tt>newCapacity</tt> is always greater than the current capacity.
     */
    protected abstract void capacity0(int newCapacity);

    public boolean isAutoExpand() {
        return autoExpand;
    }

    public ByteBuffer setAutoExpand(boolean autoExpand) {
        this.autoExpand = autoExpand;
        return this;
    }

    public ByteBuffer expand(int pos, int expectedRemaining) {
        int end = pos + expectedRemaining;
        if (end > capacity()) {
            // The buffer needs expansion.
            capacity(end);
        }

        if (end > limit()) {
            // We call limit() directly to prevent StackOverflowError
            buf().limit(end);
        }
        return this;
    }

    public int position() {
        return buf().position();
    }

    public ByteBuffer position(int newPosition) {
        autoExpand(newPosition, 0);
        buf().position(newPosition);
        if (mark > newPosition) {
            mark = -1;
        }
        return this;
    }

    public int limit() {
        return buf().limit();
    }

    public ByteBuffer limit(int newLimit) {
        autoExpand(newLimit, 0);
        buf().limit(newLimit);
        if (mark > newLimit) {
            mark = -1;
        }
        return this;
    }

    public ByteBuffer mark() {
        buf().mark();
        mark = position();
        return this;
    }

    public int markValue() {
        return mark;
    }

    public ByteBuffer reset() {
        buf().reset();
        return this;
    }

    public ByteBuffer clear() {
        buf().clear();
        mark = -1;
        return this;
    }

    public ByteBuffer flip() {
        buf().flip();
        mark = -1;
        return this;
    }

    public ByteBuffer rewind() {
        buf().rewind();
        mark = -1;
        return this;
    }

    public byte get() {
        return buf().get();
    }

    public ByteBuffer put(byte b) {
        autoExpand(1);
        buf().put(b);
        return this;
    }

    public byte get(int index) {
        return buf().get(index);
    }

    public ByteBuffer put(int index, byte b) {
        autoExpand(index, 1);
        buf().put(index, b);
        return this;
    }

    public ByteBuffer get(byte[] dst, int offset, int length) {
        buf().get(dst, offset, length);
        return this;
    }

    public ByteBuffer put(java.nio.ByteBuffer src) {
        autoExpand(src.remaining());
        buf().put(src);
        return this;
    }

    public ByteBuffer put(byte[] src, int offset, int length) {
        autoExpand(length);
        buf().put(src, offset, length);
        return this;
    }

    public ByteBuffer compact() {
        buf().compact();
        mark = -1;
        return this;
    }

    public ByteOrder order() {
        return buf().order();
    }

    public ByteBuffer order(ByteOrder bo) {
        buf().order(bo);
        return this;
    }

    public char getChar() {
        return buf().getChar();
    }

    public ByteBuffer putChar(char value) {
        autoExpand(2);
        buf().putChar(value);
        return this;
    }

    public char getChar(int index) {
        return buf().getChar(index);
    }

    public ByteBuffer putChar(int index, char value) {
        autoExpand(index, 2);
        buf().putChar(index, value);
        return this;
    }

    public CharBuffer asCharBuffer() {
        return buf().asCharBuffer();
    }

    public short getShort() {
        return buf().getShort();
    }

    public ByteBuffer putShort(short value) {
        autoExpand(2);
        buf().putShort(value);
        return this;
    }

    public short getShort(int index) {
        return buf().getShort(index);
    }

    public ByteBuffer putShort(int index, short value) {
        autoExpand(index, 2);
        buf().putShort(index, value);
        return this;
    }

    public ShortBuffer asShortBuffer() {
        return buf().asShortBuffer();
    }

    public int getInt() {
        return buf().getInt();
    }

    public ByteBuffer putInt(int value) {
        autoExpand(4);
        buf().putInt(value);
        return this;
    }

    public int getInt(int index) {
        return buf().getInt(index);
    }

    public ByteBuffer putInt(int index, int value) {
        autoExpand(index, 4);
        buf().putInt(index, value);
        return this;
    }

    public IntBuffer asIntBuffer() {
        return buf().asIntBuffer();
    }

    public long getLong() {
        return buf().getLong();
    }

    public ByteBuffer putLong(long value) {
        autoExpand(8);
        buf().putLong(value);
        return this;
    }

    public long getLong(int index) {
        return buf().getLong(index);
    }

    public ByteBuffer putLong(int index, long value) {
        autoExpand(index, 8);
        buf().putLong(index, value);
        return this;
    }

    public LongBuffer asLongBuffer() {
        return buf().asLongBuffer();
    }

    public float getFloat() {
        return buf().getFloat();
    }

    public ByteBuffer putFloat(float value) {
        autoExpand(4);
        buf().putFloat(value);
        return this;
    }

    public float getFloat(int index) {
        return buf().getFloat(index);
    }

    public ByteBuffer putFloat(int index, float value) {
        autoExpand(index, 4);
        buf().putFloat(index, value);
        return this;
    }

    public FloatBuffer asFloatBuffer() {
        return buf().asFloatBuffer();
    }

    public double getDouble() {
        return buf().getDouble();
    }

    public ByteBuffer putDouble(double value) {
        autoExpand(8);
        buf().putDouble(value);
        return this;
    }

    public double getDouble(int index) {
        return buf().getDouble(index);
    }

    public ByteBuffer putDouble(int index, double value) {
        autoExpand(index, 8);
        buf().putDouble(index, value);
        return this;
    }

    public DoubleBuffer asDoubleBuffer() {
        return buf().asDoubleBuffer();
    }
}
