/**
 * Copyright 2007-2016, Kaazing Corporation. All rights reserved.
 *
 * Licensed 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.util.byteaccess;


import java.nio.ByteOrder;
import java.util.Collections;

import org.apache.mina.core.buffer.IoBuffer;


/**
 * A <code>ByteArray</code> backed by a <code>IoBuffer</code>. This class
 * is abstract. Subclasses need to override the <code>free()</code> method. An
 * implementation backed by a heap <code>IoBuffer</code> can be created with
 * a <code>SimpleByteArrayFactory</code>.
 *
 * @author <a href="http://mina.apache.org">Apache MINA Project</a>
 */
public abstract class BufferByteArray extends AbstractByteArray
{

    /**
     * The backing <code>IoBuffer</code>.
     */
    protected IoBuffer bb;

    /**
     * 
     * Creates a new instance of BufferByteArray and uses the supplied
     * {@link IoBuffer} to back this class
     *
     * @param bb
     *  The backing buffer
     */
    public BufferByteArray( IoBuffer bb )
    {
        this.bb = bb;
    }


    /**
     * @inheritDoc
     */
    public Iterable<IoBuffer> getIoBuffers()
    {
        return Collections.singletonList( bb );
    }


    /**
     * @inheritDoc
     */
    public IoBuffer getSingleIoBuffer()
    {
        return bb;
    }


    /**
     * @inheritDoc
     * 
     * Calling <code>free()</code> on the returned slice has no effect.
     */
    public ByteArray slice( int index, int length )
    {
        int oldLimit = bb.limit();
        bb.position( index );
        bb.limit( index + length );
        IoBuffer slice = bb.slice();
        bb.limit( oldLimit );
        return new BufferByteArray( slice )
        {

            @Override
            public void free()
            {
                // Do nothing.
            }
        };
    }


    /**
     * @inheritDoc
     */
    public abstract void free();


    /**
     * @inheritDoc
     */
    public Cursor cursor()
    {
        return new CursorImpl();
    }


    /**
     * @inheritDoc
     */
    public Cursor cursor( int index )
    {
        return new CursorImpl( index );
    }


    /**
     * @inheritDoc
     */
    public int first()
    {
        return 0;
    }


    /**
     * @inheritDoc
     */
    public int last()
    {
        return bb.limit();
    }


    /**
     * @inheritDoc
     */
    public ByteOrder order()
    {
        return bb.order();
    }


    /**
     * @inheritDoc
     */
    public void order( ByteOrder order )
    {
        bb.order( order );
    }


    /**
     * @inheritDoc
     */
    public byte get( int index )
    {
        return bb.get( index );
    }


    /**
     * @inheritDoc
     */
    public void put( int index, byte b )
    {
        bb.put( index, b );
    }


    /**
     * @inheritDoc
     */
    public void get( int index, IoBuffer other )
    {
        bb.position( index );
        other.put( bb );
    }


    /**
     * @inheritDoc
     */
    public void put( int index, IoBuffer other )
    {
        bb.position( index );
        bb.put( other );
    }


    /**
     * @inheritDoc
     */
    public short getShort( int index )
    {
        return bb.getShort( index );
    }


    /**
     * @inheritDoc
     */
    public void putShort( int index, short s )
    {
        bb.putShort( index, s );
    }


    /**
     * @inheritDoc
     */
    public int getInt( int index )
    {
        return bb.getInt( index );
    }


    /**
     * @inheritDoc
     */
    public void putInt( int index, int i )
    {
        bb.putInt( index, i );
    }


    /**
     * @inheritDoc
     */
    public long getLong( int index )
    {
        return bb.getLong( index );
    }


    /**
     * @inheritDoc
     */
    public void putLong( int index, long l )
    {
        bb.putLong( index, l );
    }


    /**
     * @inheritDoc
     */
    public float getFloat( int index )
    {
        return bb.getFloat( index );
    }


    /**
     * @inheritDoc
     */
    public void putFloat( int index, float f )
    {
        bb.putFloat( index, f );
    }


    /**
     * @inheritDoc
     */
    public double getDouble( int index )
    {
        return bb.getDouble( index );
    }


    /**
     * @inheritDoc
     */
    public void putDouble( int index, double d )
    {
        bb.putDouble( index, d );
    }


    /**
     * @inheritDoc
     */
    public char getChar( int index )
    {
        return bb.getChar( index );
    }


    /**
     * @inheritDoc
     */
    public void putChar( int index, char c )
    {
        bb.putChar( index, c );
    }

    private class CursorImpl implements Cursor
    {

        private int index;


        public CursorImpl()
        {
            // This space intentionally blank.
        }


        public CursorImpl( int index )
        {
            setIndex( index );
        }


        /**
         * @inheritDoc
         */
        public int getRemaining()
        {
            return last() - index;
        }


        /**
         * @inheritDoc
         */
        public boolean hasRemaining()
        {
            return getRemaining() > 0;
        }


        /**
         * @inheritDoc
         */
        public int getIndex()
        {
            return index;
        }


        /**
         * @inheritDoc
         */
        public void setIndex( int index )
        {
            if ( index < 0 || index > last() )
            {
                throw new IndexOutOfBoundsException();
            }
            this.index = index;
        }


        public void skip( int length )
        {
            setIndex( index + length );
        }


        public ByteArray slice( int length )
        {
            ByteArray slice = BufferByteArray.this.slice( index, length );
            index += length;
            return slice;
        }


        /**
         * @inheritDoc
         */
        public ByteOrder order()
        {
            return BufferByteArray.this.order();
        }


        /**
         * @inheritDoc
         */
        public byte get()
        {
            byte b = BufferByteArray.this.get( index );
            index += 1;
            return b;
        }


        /**
         * @inheritDoc
         */
        public void put( byte b )
        {
            BufferByteArray.this.put( index, b );
            index += 1;
        }


        /**
         * @inheritDoc
         */
        public void get( IoBuffer bb )
        {
            int size = Math.min( getRemaining(), bb.remaining() );
            BufferByteArray.this.get( index, bb );
            index += size;
        }


        /**
         * @inheritDoc
         */
        public void put( IoBuffer bb )
        {
            int size = bb.remaining();
            BufferByteArray.this.put( index, bb );
            index += size;
        }


        /**
         * @inheritDoc
         */
        public short getShort()
        {
            short s = BufferByteArray.this.getShort( index );
            index += 2;
            return s;
        }


        /**
         * @inheritDoc
         */
        public void putShort( short s )
        {
            BufferByteArray.this.putShort( index, s );
            index += 2;
        }


        /**
         * @inheritDoc
         */
        public int getInt()
        {
            int i = BufferByteArray.this.getInt( index );
            index += 4;
            return i;
        }


        /**
         * @inheritDoc
         */
        public void putInt( int i )
        {
            BufferByteArray.this.putInt( index, i );
            index += 4;
        }


        /**
         * @inheritDoc
         */
        public long getLong()
        {
            long l = BufferByteArray.this.getLong( index );
            index += 8;
            return l;
        }


        /**
         * @inheritDoc
         */
        public void putLong( long l )
        {
            BufferByteArray.this.putLong( index, l );
            index += 8;
        }


        /**
         * @inheritDoc
         */
        public float getFloat()
        {
            float f = BufferByteArray.this.getFloat( index );
            index += 4;
            return f;
        }


        /**
         * @inheritDoc
         */
        public void putFloat( float f )
        {
            BufferByteArray.this.putFloat( index, f );
            index += 4;
        }


        /**
         * @inheritDoc
         */
        public double getDouble()
        {
            double d = BufferByteArray.this.getDouble( index );
            index += 8;
            return d;
        }


        /**
         * @inheritDoc
         */
        public void putDouble( double d )
        {
            BufferByteArray.this.putDouble( index, d );
            index += 8;
        }


        /**
         * @inheritDoc
         */
        public char getChar()
        {
            char c = BufferByteArray.this.getChar( index );
            index += 2;
            return c;
        }


        /**
         * @inheritDoc
         */
        public void putChar( char c )
        {
            BufferByteArray.this.putChar( index, c );
            index += 2;
        }
    }
}
