/****************************************************************
 * 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   *
 *                                                              *
 *   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.james.mime4j.util;

import java.util.ArrayList;
import java.util.Optional;

/**
 * This is a small utility class, whose main functionality is to allow
 * simple reuse of raw byte/char buffers. It is usually used through
 * <code>ThreadLocal</code> member of the owning class pointing to
 * instance of this class through a <code>SoftReference</code>. The
 * end result is a low-overhead GC-cleanable recycling: hopefully
 * ideal for use by stream readers.
 *<p>
 * Rewritten in 2.10 to be thread-safe (see [jackson-core#479] for details),
 * to not rely on {@code ThreadLocal} access.
 */
public class BufferRecycler {
    static final boolean ENABLED = Optional.ofNullable(System.getProperty("james.mime4j.buffer.recycling.enabled"))
        .map(Boolean::parseBoolean)
        .orElse(true);

    protected final ArrayList<byte[]>[] _byteBuffers;
    protected final ArrayList<char[]>[] _charBuffers;
    protected final ArrayList<int[]> _intBuffers;

    /**
     * Default constructor used for creating instances of this default
     * implementation.
     */
    public BufferRecycler() {
        this(4, 4);
    }

    /**
     * Alternate constructor to be used by sub-classes, to allow customization
     * of number of low-level buffers in use.
     *
     * @param bbCount Number of {@code byte[]} buffers to allocate
     * @param cbCount Number of {@code char[]} buffers to allocate
     *
     * @since 2.4
     */
    protected BufferRecycler(int bbCount, int cbCount) {
        _byteBuffers = new ArrayList[bbCount];
        for (int i = 0; i < bbCount; i++) {
            _byteBuffers[i] = new ArrayList<>();
        }
        _charBuffers = new ArrayList[cbCount];
        for (int i = 0; i < cbCount; i++) {
            _charBuffers[i] = new ArrayList<>();
        }
        _intBuffers = new ArrayList<>();
    }
    
    /**
     * @param ix One of <code>READ_IO_BUFFER</code> constants.
     *
     * @return Buffer allocated (possibly recycled)
     */
    public final byte[] allocByteBuffer(int ix) {
        return allocByteBuffer(ix, 0);
    }

    public final int[] allocintBuffer(int minSize) {
        final int DEF_SIZE = 256;
        if (minSize < DEF_SIZE) {
            minSize = DEF_SIZE;
        }
        final ArrayList<int[]> buffers = _intBuffers;
        int[] buffer = null;
        if (buffers.size() > 0) {
            buffer = buffers.remove(buffers.size() -1);
        }
        if (buffer == null || buffer.length < minSize) {
            buffer = new int[minSize];
        }
        return buffer;
    }

    public byte[] allocByteBuffer(int ix, int minSize) {
        final int DEF_SIZE = 4000;
        if (minSize < DEF_SIZE) {
            minSize = DEF_SIZE;
        }
        final ArrayList<byte[]> buffers = _byteBuffers[ix];
        byte[] buffer = null;
        if (buffers.size() > 0) {
            buffer = buffers.remove(buffers.size() -1);
        }
        if (buffer == null || buffer.length < minSize) {
            buffer = balloc(minSize);
        }
        return buffer;
    }

    public void releaseByteBuffer(int ix, byte[] buffer) {
        if (buffer == null) {
            return;
        }
        _byteBuffers[ix].add(buffer);
    }

    public void releaseIntBuffer(int[] buffer) {
        if (buffer == null) {
            return;
        }
        if (ENABLED) {
            _intBuffers.add(buffer);
        }
    }
    
    public final char[] allocCharBuffer(int ix) {
        return allocCharBuffer(ix, 0);
    }

    public char[] allocCharBuffer(int ix, int minSize) {
        final int DEF_SIZE = 4000;
        if (minSize < DEF_SIZE) {
            minSize = DEF_SIZE;
        }
        final ArrayList<char[]> buffers = _charBuffers[ix];
        char[] buffer = null;
        if (buffers.size() > 0) {
            buffer = buffers.remove(buffers.size() -1);
        }
        if (buffer == null || buffer.length < minSize) {
            buffer = calloc(minSize);
        }
        return buffer;
    }

    public void releaseCharBuffer(int ix, char[] buffer) {
        if (ENABLED) {
            _charBuffers[ix].add(buffer);
        }
    }

    protected byte[] balloc(int size) {
        return new byte[size];
    }

    protected char[] calloc(int size) {
        return new char[size];
    }
}
