/**
 * 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 org.apache.mina.core.buffer.IoBuffer;


/**
 * Provides restricted, relative, write-only access to the bytes in a
 * <code>CompositeByteArray</code>.
 *
 * Using this interface has the advantage that it can be automatically
 * determined when a component <code>ByteArray</code> can no longer be written
 * to, and thus components can be automatically flushed. This makes it easier to
 * use pooling for underlying <code>ByteArray</code>s.
 *
 * By providing an appropriate <code>Expander</code> it is also possible to
 * automatically add more backing storage as more data is written.
 *<br/><br/>
 * TODO: Get flushing working.
 * 
 * @author <a href="http://mina.apache.org">Apache MINA Project</a>
 */
public class CompositeByteArrayRelativeWriter extends CompositeByteArrayRelativeBase implements IoRelativeWriter
{

    /**
     * An object that knows how to expand a <code>CompositeByteArray</code>.
     */
    public interface Expander
    {
        void expand( CompositeByteArray cba, int minSize );
    }

    /**
     * No-op expander.  The overridden method does nothing.
     * 
     */
    public static class NopExpander implements Expander
    {
        public void expand( CompositeByteArray cba, int minSize )
        {
            // Do nothing.
        }
    }

    /**
     * Expands the supplied {@link CompositeByteArray} by the number of
     * bytes provided in the constructor
     * 
     */
    public static class ChunkedExpander implements Expander
    {

        private final ByteArrayFactory baf;

        private final int newComponentSize;


        public ChunkedExpander( ByteArrayFactory baf, int newComponentSize )
        {
            this.baf = baf;
            this.newComponentSize = newComponentSize;
        }


        public void expand( CompositeByteArray cba, int minSize )
        {
            int remaining = minSize;
            while ( remaining > 0 )
            {
                ByteArray component = baf.create( newComponentSize );
                cba.addLast( component );
                remaining -= newComponentSize;
            }
        }

    }

    /**
     * An object that knows how to flush a <code>ByteArray</code>.
     */
    public interface Flusher
    {
        // document free() behaviour
        void flush( ByteArray ba );
    }

    /**
     * The expander to use when the array underflows.
     */
    private final Expander expander;

    /**
     * The flusher to use when flushing component <code>ByteArray</code>s.
     */
    private final Flusher flusher;

    /**
     * Whether or not to automatically flush a component once the cursor moves
     * past it.
     */
    private final boolean autoFlush;


    /**
     * 
     * Creates a new instance of CompositeByteArrayRelativeWriter.
     *
     * @param cba
     *  The CompositeByteArray to use to back this class
     * @param expander
     *  The expander.  Will increase the size of the internal ByteArray
     * @param flusher
     *  Flushed the ByteArray when necessary
     * @param autoFlush
     *  Should this class automatically flush?
     */
    public CompositeByteArrayRelativeWriter( CompositeByteArray cba, Expander expander, Flusher flusher,
        boolean autoFlush )
    {
        super( cba );
        this.expander = expander;
        this.flusher = flusher;
        this.autoFlush = autoFlush;
    }


    private void prepareForAccess( int size )
    {
        int underflow = cursor.getIndex() + size - last();
        if ( underflow > 0 )
        {
            expander.expand( cba, underflow );
        }
    }


    /**
     * Flush to the current index.
     */
    public void flush()
    {
        flushTo( cursor.getIndex() );
    }


    /**
     * Flush to the given index.
     */
    public void flushTo( int index )
    {
        ByteArray removed = cba.removeTo( index );
        flusher.flush( removed );
    }


    /**
     * @inheritDoc
     */
    public void skip( int length )
    {
        cursor.skip( length );
    }


    @Override
    protected void cursorPassedFirstComponent()
    {
        if ( autoFlush )
        {
            flushTo( cba.first() + cba.getFirst().length() );
        }
    }


    /**
     * @inheritDoc
     */
    public void put( byte b )
    {
        prepareForAccess( 1 );
        cursor.put( b );
    }


    /**
     * @inheritDoc
     */
    public void put( IoBuffer bb )
    {
        prepareForAccess( bb.remaining() );
        cursor.put( bb );
    }


    /**
     * @inheritDoc
     */
    public void putShort( short s )
    {
        prepareForAccess( 2 );
        cursor.putShort( s );
    }


    /**
     * @inheritDoc
     */
    public void putInt( int i )
    {
        prepareForAccess( 4 );
        cursor.putInt( i );
    }


    /**
     * @inheritDoc
     */
    public void putLong( long l )
    {
        prepareForAccess( 8 );
        cursor.putLong( l );
    }


    /**
     * @inheritDoc
     */
    public void putFloat( float f )
    {
        prepareForAccess( 4 );
        cursor.putFloat( f );
    }


    /**
     * @inheritDoc
     */
    public void putDouble( double d )
    {
        prepareForAccess( 8 );
        cursor.putDouble( d );
    }


    /**
     * @inheritDoc
     */
    public void putChar( char c )
    {
        prepareForAccess( 2 );
        cursor.putChar( c );
    }
}
