package com.jmatio.io.stream;

import java.io.IOException;
import java.nio.ByteBuffer;

/**
 * From https://gist.github.com/manzke/985007
 *
 */
public class ByteBufferedOutputStream extends BufferedOutputStream
{
    private ByteBuffer buffer;

    private boolean onHeap;

    private float increasing = DEFAULT_INCREASING_FACTOR;

    public static final float DEFAULT_INCREASING_FACTOR = 1.5f;

    public ByteBufferedOutputStream( int size )
    {
        this( size, DEFAULT_INCREASING_FACTOR, false );
    }

    public ByteBufferedOutputStream( int size, boolean onHeap )
    {
        this( size, DEFAULT_INCREASING_FACTOR, onHeap );
    }

    public ByteBufferedOutputStream( int size, float increasingBy )
    {
        this( size, increasingBy, false );
    }

    public ByteBufferedOutputStream( int size, float increasingBy, boolean onHeap )
    {
        if ( increasingBy <= 1 )
        {
            throw new IllegalArgumentException( "Increasing Factor must be greater than 1.0" );
        }
        if ( onHeap )
        {
            buffer = ByteBuffer.allocate( size );
        }
        else
        {
            buffer = ByteBuffer.allocateDirect( size );
        }
        this.onHeap = onHeap;
    }

    @Override
    public void write( byte[] b, int off, int len ) throws IOException
    {
        int position = buffer.position();
        int limit = buffer.limit();

        long newTotal = position + len;
        if ( newTotal > limit )
        {
            int capacity = (int) ( buffer.capacity() * increasing );
            while ( capacity <= newTotal )
            {
                capacity = (int) ( capacity * increasing );
            }

            increase( capacity );
        }

        buffer.put( b, 0, len );
    }

    @Override
    public void write( int b ) throws IOException
    {
        if ( !buffer.hasRemaining() )
        {
            increase( (int) ( buffer.capacity() * increasing ) );
        }
        buffer.put( (byte) b );
    }

    protected void increase( int newCapacity )
    {
        buffer.limit( buffer.position() );
        buffer.rewind();

        ByteBuffer newBuffer;
        if ( onHeap )
        {
            newBuffer = ByteBuffer.allocate( newCapacity );
        }
        else
        {
            newBuffer = ByteBuffer.allocateDirect( newCapacity );
        }

        newBuffer.put( buffer );
        buffer.clear();
        buffer = newBuffer;
    }
    
    @Override
    public long size()
    {
        return buffer.position();
    }

    public long capacity()
    {
        return buffer.capacity();
    }

    public ByteBuffer buffer()
    {
        return buffer;
    }
}
