/*
 * 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.geode.internal.tcp;

import java.lang.ref.SoftReference;
import java.nio.ByteBuffer;
import java.util.IdentityHashMap;
import java.util.Iterator;
import java.util.concurrent.ConcurrentLinkedQueue;

import org.apache.geode.distributed.internal.DMStats;
import org.apache.geode.internal.Assert;

/**
 *
 */
public class Buffers {
  /**
   * A list of soft references to byte buffers.
   */
  private static final ConcurrentLinkedQueue bufferQueue = new ConcurrentLinkedQueue();

  /**
   * Should only be called by threads that have currently acquired send permission.
   *
   * @return a byte buffer to be used for sending on this connection.
   */
  static ByteBuffer acquireSenderBuffer(int size, DMStats stats) {
    return acquireBuffer(size, stats, true);
  }

  static ByteBuffer acquireReceiveBuffer(int size, DMStats stats) {
    return acquireBuffer(size, stats, false);
  }

  static ByteBuffer acquireBuffer(int size, DMStats stats, boolean send) {
    ByteBuffer result;
    if (TCPConduit.useDirectBuffers) {
      IdentityHashMap<BBSoftReference, BBSoftReference> alreadySeen = null; // keys are used like a
                                                                            // set
      BBSoftReference ref = (BBSoftReference) bufferQueue.poll();
      while (ref != null) {
        ByteBuffer bb = ref.getBB();
        if (bb == null) {
          // it was garbage collected
          int refSize = ref.consumeSize();
          if (refSize > 0) {
            if (ref.getSend()) { // fix bug 46773
              stats.incSenderBufferSize(-refSize, true);
            } else {
              stats.incReceiverBufferSize(-refSize, true);
            }
          }
        } else if (bb.capacity() >= size) {
          bb.rewind();
          bb.limit(size);
          return bb;
        } else {
          // wasn't big enough so put it back in the queue
          Assert.assertTrue(bufferQueue.offer(ref));
          if (alreadySeen == null) {
            alreadySeen = new IdentityHashMap<BBSoftReference, BBSoftReference>();
          }
          if (alreadySeen.put(ref, ref) != null) {
            // if it returns non-null then we have already seen this item
            // so we have worked all the way through the queue once.
            // So it is time to give up and allocate a new buffer.
            break;
          }
        }
        ref = (BBSoftReference) bufferQueue.poll();
      }
      result = ByteBuffer.allocateDirect(size);
    } else {
      // if we are using heap buffers then don't bother with keeping them around
      result = ByteBuffer.allocate(size);
    }
    if (send) {
      stats.incSenderBufferSize(size, TCPConduit.useDirectBuffers);
    } else {
      stats.incReceiverBufferSize(size, TCPConduit.useDirectBuffers);
    }
    return result;
  }

  static void releaseSenderBuffer(ByteBuffer bb, DMStats stats) {
    releaseBuffer(bb, stats, true);
  }

  static void releaseReceiveBuffer(ByteBuffer bb, DMStats stats) {
    releaseBuffer(bb, stats, false);
  }

  /**
   * Releases a previously acquired buffer.
   */
  static void releaseBuffer(ByteBuffer bb, DMStats stats, boolean send) {
    if (TCPConduit.useDirectBuffers) {
      BBSoftReference bbRef = new BBSoftReference(bb, send);
      bufferQueue.offer(bbRef);
    } else {
      if (send) {
        stats.incSenderBufferSize(-bb.capacity(), false);
      } else {
        stats.incReceiverBufferSize(-bb.capacity(), false);
      }
    }
  }

  public static void initBufferStats(DMStats stats) { // fixes 46773
    if (TCPConduit.useDirectBuffers) {
      @SuppressWarnings("unchecked")
      Iterator<BBSoftReference> it = (Iterator<BBSoftReference>) bufferQueue.iterator();
      while (it.hasNext()) {
        BBSoftReference ref = it.next();
        if (ref.getBB() != null) {
          if (ref.getSend()) { // fix bug 46773
            stats.incSenderBufferSize(ref.getSize(), true);
          } else {
            stats.incReceiverBufferSize(ref.getSize(), true);
          }
        }
      }
    }
  }

  /**
   * A soft reference that remembers the size of the byte buffer it refers to. TODO Dan - I really
   * think this should be a weak reference. The JVM doesn't seem to clear soft references if it is
   * getting low on direct memory.
   */
  private static class BBSoftReference extends SoftReference<ByteBuffer> {
    private int size;
    private final boolean send;

    public BBSoftReference(ByteBuffer bb, boolean send) {
      super(bb);
      this.size = bb.capacity();
      this.send = send;
    }

    public int getSize() {
      return this.size;
    }

    public synchronized int consumeSize() {
      int result = this.size;
      this.size = 0;
      return result;
    }

    public boolean getSend() {
      return this.send;
    }

    public ByteBuffer getBB() {
      return (ByteBuffer) super.get();
    }
  }

}
