/*
 *   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
 *
 *     https://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.directory.api.asn1.util;

/**
 * A buffer used to store an encoding PDU. It's auto-extended, and
 * filled by the end.
 *
 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
 */
public class Asn1Buffer2
{
    /** The buffer default size */
    private static final int DEFAULT_SIZE = 1024;

    /** The current position in the buffer */
    private int pos = 0;

    /** The current size */
    private int size = DEFAULT_SIZE;

    /** The internal buffer storage */
    private final class InternalBuffer
    {
        /**
         * private constructor
         */
        InternalBuffer()
        {
            // Nothing to do
        }

        /** A buffer to store the encoded PDU */
        private byte[] buffer = new byte[DEFAULT_SIZE];

        /** The next buffer, if any */
        private InternalBuffer next;
    }

    /** The current internal buffer */
    private InternalBuffer currentBuffer;


    /**
     * Create a new instance of Asn1Buffer2
     */
    public Asn1Buffer2()
    {
        currentBuffer = new InternalBuffer();
    }


    /**
     * Get the current position in the buffer
     * 
     * @return The current position in the buffer
     */
    public int getPos()
    {
        return pos;
    }


    /**
     * Store a byte at the current position in the buffer
     *
     * @param b The byte to store
     */
    public void put( byte b )
    {
        if ( pos == size )
        {
            // The buffer needs to be reallocated, its too small
            extend();
        }

        currentBuffer.buffer[size - pos - 1] = b;
        pos++;
    }


    /**
     * Store some bytes at the current position in the buffer
     *
     * @param bytes The bytes to store
     */
    public void put( byte[] bytes )
    {
        int dataLength = bytes.length;

        while ( true )
        {
            int room = size - pos;

            if ( dataLength > room )
            {
                // First fulfill the current buffer
                System.arraycopy(
                    bytes,
                    dataLength - room,
                    currentBuffer,
                    0,
                    room );

                dataLength -= room;
                pos += room;

                extend();
            }
            else
            {
                // Last bytes are copied in the current buffer
                System.arraycopy(
                    bytes,
                    0,
                    currentBuffer,
                    room - dataLength,
                    dataLength );

                pos += dataLength;

                break;
            }
        }
    }


    /**
     * Extend the buffer
     */
    private void extend()
    {
        InternalBuffer newCurrentBuffer = new InternalBuffer();

        newCurrentBuffer.next = currentBuffer;
        currentBuffer = newCurrentBuffer;
        size += DEFAULT_SIZE;
    }


    /**
     * Get the interned bytes
     * 
     * @return The stored encoded PDU.
     */
    public byte[] getBytes()
    {
        byte[] result = new byte[pos];

        InternalBuffer bufferPtr = currentBuffer;
        int currentPos = 0;
        int dataPos = size - pos;
        int dataLength = DEFAULT_SIZE - dataPos;

        while ( bufferPtr.next != null )
        {
            System.arraycopy(
                bufferPtr,
                dataPos,
                result,
                currentPos,
                dataLength );

            currentPos += dataLength;
            dataPos = 0;
            dataLength = DEFAULT_SIZE;
        }

        return result;
    }


    /**
     * Get the buffer size
     * 
     * @return The buffer size (ie the maximum number of bytes that can be
     * added to this buffer before it gets extended).
     */
    public int getSize()
    {
        return size;
    }


    /**
     * Clear the position, emptying the buffer. If it has grown, reallocate it
     * to its initial size.
     */
    public void clear()
    {
        pos = 0;
        size = DEFAULT_SIZE;

        // Un-reference the extended buffer. They will be garbage collected.
        while ( currentBuffer.next != null )
        {
            currentBuffer = currentBuffer.next;
        }
    }


    /**
     * {@inheritDoc}
     */
    @Override
    public String toString()
    {
        StringBuilder sb = new StringBuilder();
        sb.append( "[" ).append( size ).append( ", " ).append( pos ).append( "] )" );

        InternalBuffer bufferPtr = currentBuffer;

        while ( bufferPtr.next != null )
        {
            sb.append( "\n    " ).append( Asn1StringUtils.dumpBytes( bufferPtr.buffer ) );
        }

        return sb.toString();
    }
}
