package com.fasterxml.jackson.databind.util;

/**
 * Base class for specialized primitive array builders.
 */
public abstract class PrimitiveArrayBuilder<T>
{
    /**
     * Let's start with small chunks; typical usage is for small arrays anyway.
     */
    final static int INITIAL_CHUNK_SIZE = 12;

    /**
     * Also: let's expand by doubling up until 64k chunks (which is 16k entries for
     * 32-bit machines)
     */
    final static int SMALL_CHUNK_SIZE = (1 << 14);

    /**
     * Let's limit maximum size of chunks we use; helps avoid excessive allocation
     * overhead for huge data sets.
     * For now, let's limit to quarter million entries, 1 meg chunks for 32-bit
     * machines.
     */
    final static int MAX_CHUNK_SIZE = (1 << 18);

    // // // Data storage

    protected T _freeBuffer;

    protected Node<T> _bufferHead;

    protected Node<T> _bufferTail;

    /**
     * Number of total buffered entries in this buffer, counting all instances
     * within linked list formed by following {@link #_bufferHead}.
     */
    protected int _bufferedEntryCount;

    // // // Recycled instances of sub-classes

    // // // Life-cycle

    protected PrimitiveArrayBuilder() { }

    /*
    /**********************************************************
    /* Public API
    /**********************************************************
     */

    public int bufferedSize() { return _bufferedEntryCount; }
    
    public T resetAndStart()
    {
        _reset();
        return (_freeBuffer == null) ?
            _constructArray(INITIAL_CHUNK_SIZE) : _freeBuffer;
    }

    /**
     * @return Length of the next chunk to allocate
     */
    public final T appendCompletedChunk(T fullChunk, int fullChunkLength)
    {
        Node<T> next = new Node<T>(fullChunk, fullChunkLength);
        if (_bufferHead == null) { // first chunk
            _bufferHead = _bufferTail = next;
        } else { // have something already
            _bufferTail.linkNext(next);
            _bufferTail = next;
        }
        _bufferedEntryCount += fullChunkLength;
        int nextLen = fullChunkLength; // start with last chunk size
        // double the size for small chunks
        if (nextLen < SMALL_CHUNK_SIZE) {
            nextLen += nextLen;
        } else { // but by +25% for larger (to limit overhead)
            nextLen += (nextLen >> 2);
        }
        return _constructArray(nextLen);
    }

    public T completeAndClearBuffer(T lastChunk, int lastChunkEntries)
    {
        int totalSize = lastChunkEntries + _bufferedEntryCount;
        T resultArray = _constructArray(totalSize);

        int ptr = 0;

        for (Node<T> n = _bufferHead; n != null; n = n.next()) {
            ptr = n.copyData(resultArray, ptr);
        }
        System.arraycopy(lastChunk, 0, resultArray, ptr, lastChunkEntries);
        ptr += lastChunkEntries;

        // sanity check (could have failed earlier due to out-of-bounds, too)
        if (ptr != totalSize) {
            throw new IllegalStateException("Should have gotten "+totalSize+" entries, got "+ptr);
        }
        return resultArray;
    }

    /*
    /**********************************************************
    /* Abstract methods for sub-classes to implement
    /**********************************************************
     */

    protected abstract T _constructArray(int len);

    /*
    /**********************************************************
    /* Internal methods
    /**********************************************************
     */

    protected void _reset()
    {
        // can we reuse the last (and thereby biggest) array for next time?
        if (_bufferTail != null) {
            _freeBuffer = _bufferTail.getData();
        }
        // either way, must discard current contents
        _bufferHead = _bufferTail = null;
        _bufferedEntryCount = 0;
    }

    /*
    /**********************************************************
    /* Helper classes
    /**********************************************************
     */

    /**
     * For actual buffering beyond the current buffer, we can actually
     * use shared class which only deals with opaque "untyped" chunks.
     * This works because {@link java.lang.System#arraycopy} does not
     * take type; hence we can implement some aspects of primitive data
     * handling in generic fashion.
     */
    final static class Node<T>
    {
        /**
         * Data stored in this node.
         */
        final T _data;

        /**
         * Number entries in the (untyped) array. Offset is assumed to be 0.
         */
        final int _dataLength;

        Node<T> _next;

        public Node(T data, int dataLen)
        {
            _data = data;
            _dataLength = dataLen;
        }

        public T getData() { return _data; }

        public int copyData(T dst, int ptr)
        {
            System.arraycopy(_data, 0, dst, ptr, _dataLength);
            ptr += _dataLength;
            return ptr;
        }

        public Node<T> next() { return _next; }

        public void linkNext(Node<T> next)
        {
            if (_next != null) { // sanity check
                throw new IllegalStateException();
            }
            _next = next;
        }
    }
}
