// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package org.chromium.mojo_base;

import org.chromium.mojo.system.Core;
import org.chromium.mojo.system.SharedBufferHandle;
import org.chromium.mojo.system.impl.CoreImpl;
import org.chromium.mojo_base.mojom.BigBuffer;
import org.chromium.mojo_base.mojom.BigBufferSharedMemoryRegion;

import java.nio.ByteBuffer;

/**
 * Static helper methods for working with the mojom BigBuffer type.
 */
public final class BigBufferUtil {
    public static final int MAX_INLINE_ARRAY_SIZE = 64 * 1024;

    /**
     * A mapping to a BigBuffer.
     *
     * If it is backed by shared memory, this will be a direct mapping which must be closed and will
     * be invalid thereafter. The simplest way to do this is by using try-with-resources.
     */
    public static class Mapping implements AutoCloseable {
        private final SharedBufferHandle mHandle;
        private final ByteBuffer mBuffer;

        Mapping(SharedBufferHandle handle, ByteBuffer buffer) {
            mHandle = handle;
            mBuffer = buffer;
        }

        public ByteBuffer getBuffer() {
            return mBuffer;
        }

        @Override
        public void close() {
            if (mHandle != null) {
                mHandle.unmap(mBuffer);
            }
        }
    }

    // Retrives a copy of the buffer's contents regardless of what type was backing it (i.e. array
    // or shared memory).
    public static byte[] getBytesFromBigBuffer(BigBuffer buffer) {
        if (buffer.which() == BigBuffer.Tag.Bytes) {
            return buffer.getBytes();
        } else {
            BigBufferSharedMemoryRegion region = buffer.getSharedMemory();
            ByteBuffer byteBuffer =
                    region.bufferHandle.map(0, region.size, SharedBufferHandle.MapFlags.NONE);
            byte[] bytes = new byte[region.size];
            byteBuffer.get(bytes);
            region.bufferHandle.unmap(byteBuffer);
            return bytes;
        }
    }

    // Opens a mapping to an existing buffer for direct reading, without a copy.
    // This must be used with a try-with-resources so that close is called to prevent a leak.
    // The direct buffer must not be used after close is called.
    public static Mapping map(BigBuffer buffer) {
        if (buffer.which() == BigBuffer.Tag.Bytes) {
            return new Mapping(null, ByteBuffer.wrap(buffer.getBytes()));
        } else {
            BigBufferSharedMemoryRegion region = buffer.getSharedMemory();
            ByteBuffer byteBuffer =
                    region.bufferHandle.map(0, region.size, SharedBufferHandle.MapFlags.NONE);
            return new Mapping(region.bufferHandle, byteBuffer);
        }
    }

    // Creates a new mojom.BigBuffer for IPC from a set of bytes. If the byte array is larger than
    // MAX_INLINE_ARRAY_SIZE, shared memory will be used instead of an inline array.
    public static BigBuffer createBigBufferFromBytes(byte[] bytes) {
        BigBuffer buffer = new BigBuffer();
        if (bytes.length <= MAX_INLINE_ARRAY_SIZE) {
            buffer.setBytes(bytes);
            return buffer;
        }
        Core core = CoreImpl.getInstance();
        BigBufferSharedMemoryRegion region = new BigBufferSharedMemoryRegion();
        region.bufferHandle =
                core.createSharedBuffer(new SharedBufferHandle.CreateOptions(), bytes.length);
        region.size = bytes.length;
        ByteBuffer mappedRegion =
                region.bufferHandle.map(0, bytes.length, SharedBufferHandle.MapFlags.NONE);
        mappedRegion.put(bytes);
        region.bufferHandle.unmap(mappedRegion);
        buffer.setSharedMemory(region);
        return buffer;
    }
}
