/*
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.imagepipeline.memory;

import android.util.Log;
import com.facebook.common.internal.Preconditions;
import java.io.Closeable;
import java.nio.ByteBuffer;
import javax.annotation.Nullable;

/**
 * Wrapper around chunk using a direct ByteBuffer in native memory. A direct ByteBuffer is composed
 * of a Java {@link ByteBuffer} object allocated on the Java heap and the underlying buffer which is
 * in native memory.
 *
 * <p>The buffer in native memory will be released when the Java object gets garbage collected.
 */
public class BufferMemoryChunk implements MemoryChunk, Closeable {
  private static final String TAG = "BufferMemoryChunk";

  /** Internal representation of the chunk */
  private ByteBuffer mBuffer;

  /** Size of the ByteBuffer */
  private final int mSize;

  /** Unique identifier of the chunk */
  private final long mId;

  public BufferMemoryChunk(final int size) {
    mBuffer = ByteBuffer.allocateDirect(size);
    mSize = size;
    mId = System.identityHashCode(this);
  }

  @Override
  public synchronized void close() {
    mBuffer = null;
  }

  @Override
  public synchronized boolean isClosed() {
    return mBuffer == null;
  }

  @Override
  public int getSize() {
    return mSize;
  }

  @Override
  public synchronized int write(
      final int memoryOffset, final byte[] byteArray, final int byteArrayOffset, final int count) {
    Preconditions.checkNotNull(byteArray);
    Preconditions.checkState(!isClosed());
    final int actualCount = MemoryChunkUtil.adjustByteCount(memoryOffset, count, mSize);
    MemoryChunkUtil.checkBounds(
        memoryOffset, byteArray.length, byteArrayOffset, actualCount, mSize);
    mBuffer.position(memoryOffset);
    mBuffer.put(byteArray, byteArrayOffset, actualCount);
    return actualCount;
  }

  @Override
  public synchronized int read(
      final int memoryOffset, final byte[] byteArray, final int byteArrayOffset, final int count) {
    Preconditions.checkNotNull(byteArray);
    Preconditions.checkState(!isClosed());
    final int actualCount = MemoryChunkUtil.adjustByteCount(memoryOffset, count, mSize);
    MemoryChunkUtil.checkBounds(
        memoryOffset, byteArray.length, byteArrayOffset, actualCount, mSize);
    mBuffer.position(memoryOffset);
    mBuffer.get(byteArray, byteArrayOffset, actualCount);
    return actualCount;
  }

  @Override
  public synchronized byte read(final int offset) {
    Preconditions.checkState(!isClosed());
    Preconditions.checkArgument(offset >= 0);
    Preconditions.checkArgument(offset < mSize);
    return mBuffer.get(offset);
  }

  @Override
  public void copy(
      final int offset, final MemoryChunk other, final int otherOffset, final int count) {
    Preconditions.checkNotNull(other);
    // This implementation acquires locks on this and other objects and then delegates to
    // doCopy which does actual copy. In order to avoid deadlocks we have to establish some linear
    // order on all BufferMemoryChunks and acquire locks according to this order. In order
    // to do that, we use unique ids.
    // So we have to address 3 cases:

    // Case 1: other buffer equals this buffer, id comparison
    if (other.getUniqueId() == getUniqueId()) {
      // we do not allow copying to the same address
      // lets log warning and not copy
      Log.w(
          TAG,
          "Copying from BufferMemoryChunk "
              + Long.toHexString(getUniqueId())
              + " to BufferMemoryChunk "
              + Long.toHexString(other.getUniqueId())
              + " which are the same ");
      Preconditions.checkArgument(false);
    }

    // Case 2: Other memory chunk id < this memory chunk id
    if (other.getUniqueId() < getUniqueId()) {
      synchronized (other) {
        synchronized (this) {
          doCopy(offset, other, otherOffset, count);
        }
      }
      return;
    }

    // Case 3: Other memory chunk id > this memory chunk id
    synchronized (this) {
      synchronized (other) {
        doCopy(offset, other, otherOffset, count);
      }
    }
  }

  @Override
  public long getNativePtr() {
    throw new UnsupportedOperationException("Cannot get the pointer of a BufferMemoryChunk");
  }

  @Override
  @Nullable
  public synchronized ByteBuffer getByteBuffer() {
    return mBuffer;
  }

  @Override
  public long getUniqueId() {
    return mId;
  }

  /**
   * This does actual copy. It should be called only when we hold locks on both this and other
   * objects
   */
  private void doCopy(
      final int offset, final MemoryChunk other, final int otherOffset, final int count) {
    if (!(other instanceof BufferMemoryChunk)) {
      throw new IllegalArgumentException("Cannot copy two incompatible MemoryChunks");
    }
    Preconditions.checkState(!isClosed());
    Preconditions.checkState(!other.isClosed());
    MemoryChunkUtil.checkBounds(offset, other.getSize(), otherOffset, count, mSize);
    mBuffer.position(offset);
    // ByteBuffer can't be null at this point
    other.getByteBuffer().position(otherOffset);
    // Recover the necessary part to be copied as a byte array.
    // This requires a copy, for now there is not a more efficient alternative.
    byte[] b = new byte[count];
    mBuffer.get(b, 0, count);
    other.getByteBuffer().put(b, 0, count);
  }
}

@@ -302,16 +302,63 @@
 itions;%0A
+import com.facebook.infer.annotation.Nullsafe;%0A
 import j
@@ -375,16 +375,16 @@
 seable;%0A
-
 import j
@@ -767,16 +767,47 @@
 ed.%0A */%0A
+@Nullsafe(Nullsafe.Mode.LOCAL)%0A
 public c
@@ -979,16 +979,26 @@
 private 
+@Nullable 
 ByteBuff
@@ -1738,32 +1738,73 @@
 e(!isClosed());%0A
+    Preconditions.checkNotNull(mBuffer);%0A
     final int ac
@@ -2336,24 +2336,65 @@
 sClosed());%0A
+    Preconditions.checkNotNull(mBuffer);%0A
     final in
@@ -2902,24 +2902,65 @@
 t %3C mSize);%0A
+    Preconditions.checkNotNull(mBuffer);%0A
     return m
@@ -5222,24 +5222,65 @@
 sClosed());%0A
+    Preconditions.checkNotNull(mBuffer);%0A
     MemoryCh
@@ -5385,19 +5385,16 @@
 et);%0A   
- //
  ByteBuf
@@ -5401,61 +5401,96 @@
 fer 
-can't be null at this point%0A    other.getByteBuffer()
+otherByteBuffer = Preconditions.checkNotNull(other.getByteBuffer());%0A    otherByteBuffer
 .pos
@@ -5720,28 +5720,24 @@
 );%0A    other
-.get
 ByteBuffer()
@@ -5734,18 +5734,16 @@
 teBuffer
-()
 .put(b, 

Mark BufferMemoryChunk