/*
 * Copyright 2021 The Netty Project
 *
 * The Netty Project 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 io.net5.buffer.api;

import io.net5.buffer.api.pool.PooledBufferAllocator;

import java.util.function.Supplier;

/**
 * Interface for allocating {@link Buffer}s.
 */
public interface BufferAllocator extends AutoCloseable {
    /**
     * Produces a {@link BufferAllocator} that allocates unpooled, on-heap buffers.
     * On-heap buffers have a {@code byte[]} internally, and their
     * {@linkplain ReadableComponent#readableNativeAddress() readable} and
     * {@linkplain WritableComponent#writableNativeAddress() writable} native addresses are zero.
     * <p>
     * The concrete {@link Buffer} implementation is chosen by {@link MemoryManager#instance()}.
     *
     * @return A non-pooling allocator of on-heap buffers
     */
    static BufferAllocator onHeapUnpooled() {
        return new ManagedBufferAllocator(MemoryManager.instance(), false);
    }

    /**
     * Produces a {@link BufferAllocator} that allocates unpooled, off-heap buffers.
     * Off-heap buffers a native memory pointer internally, which can be obtained from their
     * {@linkplain ReadableComponent#readableNativeAddress() readable} and
     * {@linkplain WritableComponent#writableNativeAddress() writable} native address methods.
     * <p>
     * The concrete {@link Buffer} implementation is chosen by {@link MemoryManager#instance()}.
     *
     * @return A non-pooling allocator of on-heap buffers
     */
    static BufferAllocator offHeapUnpooled() {
        return new ManagedBufferAllocator(MemoryManager.instance(), true);
    }

    /**
     * Produces a pooling {@link BufferAllocator} that allocates and recycles on-heap buffers.
     * On-heap buffers have a {@code byte[]} internally, and their
     * {@linkplain ReadableComponent#readableNativeAddress() readable} and
     * {@linkplain WritableComponent#writableNativeAddress() writable} native addresses are zero.
     * <p>
     * The concrete {@link Buffer} implementation is chosen by {@link MemoryManager#instance()}.
     *
     * @return A pooling allocator of on-heap buffers
     */
    static BufferAllocator onHeapPooled() {
        return new PooledBufferAllocator(MemoryManager.instance(), false);
    }

    /**
     * Produces a pooling {@link BufferAllocator} that allocates and recycles off-heap buffers.
     * Off-heap buffers a native memory pointer internally, which can be obtained from their
     * {@linkplain ReadableComponent#readableNativeAddress() readable} and
     * {@linkplain WritableComponent#writableNativeAddress() writable} native address methods.
     * <p>
     * The concrete {@link Buffer} implementation is chosen by {@link MemoryManager#instance()}.
     *
     * @return A pooling allocator of on-heap buffers
     */
    static BufferAllocator offHeapPooled() {
        return new PooledBufferAllocator(MemoryManager.instance(), true);
    }

    /**
     * Allocate a {@link Buffer} of the given size in bytes. This method may throw an {@link OutOfMemoryError} if there
     * is not enough free memory available to allocate a {@link Buffer} of the requested size.
     * <p>
     * The buffer will use big endian byte order.
     *
     * @param size The size of {@link Buffer} to allocate.
     * @return The newly allocated {@link Buffer}.
     * @throws IllegalStateException if this allocator has been {@linkplain #close() closed}.
     */
    Buffer allocate(int size);

    /**
     * Create a supplier of "constant" {@linkplain Buffer Buffers} from this allocator, that all have the given
     * byte contents. The buffer has the same capacity as the byte array length, and its write offset is placed at the
     * end, and its read offset is at the beginning, such that the entire buffer contents are readable.
     * <p>
     * The buffers produced by the supplier will each have their own independent life-cycle, and closing them will
     * make them {@linkplain Buffer#isAccessible() inaccessible}, just like normally allocated buffers.
     * <p>
     * The buffers produced are "constants", in the sense that they are {@linkplain Buffer#readOnly() read-only}.
     * <p>
     * It can generally be expected, but is not guaranteed, that the returned supplier is more resource efficient than
     * allocating and copying memory with other available APIs. In such optimised implementations, the underlying memory
     * baking the buffers will be shared among all the buffers produced by the supplier.
     * <p>
     * The primary use case for this API, is when you need to repeatedly produce buffers with the same contents, and
     * you perhaps wish to keep a {@code static final} field with these contents. The supplier-based API enforces
     * that each usage get their own distinct buffer instance. Each of these instances cannot interfere with each other,
     * so bugs like closing, or modifying the contents, of a shared buffer cannot occur.
     *
     * @param bytes The byte contents of the buffers produced by the returned supplier.
     * @return A supplier of read-only buffers with the given contents.
     * @throws IllegalStateException if this allocator has been {@linkplain #close() closed}, but any supplier obtained
     * prior to closing the allocator will continue to work.
     */
    Supplier<Buffer> constBufferSupplier(byte[] bytes);

    /**
     * Close this allocator, freeing all of its internal resources.
     * <p>
     * Existing (currently in-use) allocated buffers will not be impacted by calling this method.
     * If this is a pooling or caching allocator, then existing buffers will be immediately freed when they are closed,
     * instead of being pooled or cached.
     * <p>
     * The allocator can no longer be used to allocate more buffers after calling this method.
     * Attempting to allocate from a closed allocator will cause {@link IllegalStateException}s to be thrown.
     */
    @Override
    void close();
}
