/*
 *  This file is part of the Jikes RVM project (http://jikesrvm.org).
 *
 *  This file is licensed to You under the Common Public License (CPL);
 *  You may not use this file except in compliance with the License. You
 *  may obtain a copy of the License at
 *
 *      http://www.opensource.org/licenses/cpl1.0.php
 *
 *  See the COPYRIGHT.txt file distributed with this work for information
 *  regarding copyright ownership.
 */
package org.mmtk.utility.deque;

import org.mmtk.plan.Plan;
import org.mmtk.utility.Constants;

import org.mmtk.vm.VM;

import org.vmmagic.unboxed.*;
import org.vmmagic.pragma.*;

/**
 * Note this may perform poorly when being used as a FIFO structure with
 * insertHead and pop operations operating on the same buffer.  This
 * only uses the fields inherited from <code>LocalQueue</code>, but adds
 * the ability for entries to be added to the head of the deque and popped
 * from the rear.
 */
@Uninterruptible public class LocalDeque extends LocalQueue
  implements Constants {

  /****************************************************************************
   *
   * Public instance methods
   */

  /**
   * Constructor
   *
   * @param queue The shared deque to which this local deque will append
   * its buffers (when full or flushed).
   */
  LocalDeque(SharedDeque queue) {
    super(queue);
  }

  /**
   * Flush the buffer to the shared deque (this will make any entries
   * in the buffer visible to any other consumer associated with the
   * shared deque).
   */
  @Override
  public final void flushLocal() {
    super.flushLocal();
    if (head.NE(Deque.HEAD_INITIAL_VALUE)) {
      closeAndInsertHead(queue.getArity());
      head = Deque.HEAD_INITIAL_VALUE;
    }
  }

  /****************************************************************************
   *
   * Protected instance methods
   */

  /**
   * Check whether there is space in the buffer for a pending insert.
   * If there is not sufficient space, allocate a new buffer
   * (dispatching the full buffer to the shared deque if not null).
   *
   * @param arity The arity of the values stored in this deque: the
   * buffer must contain enough space for this many words.
   */
  @Inline
  protected final void checkHeadInsert(int arity) {
    if (bufferOffset(head).EQ(bufferSentinel(arity)) ||
        head.EQ(HEAD_INITIAL_VALUE))
      headOverflow(arity);
    else if (VM.VERIFY_ASSERTIONS) VM.assertions._assert(bufferOffset(head).sLE(bufferLastOffset(arity)));
  }

  /**
   * Insert a value at the front of the deque (and buffer).  This is
   * <i>unchecked</i>.  The caller must first call
   * <code>checkHeadInsert()</code> to ensure the buffer can accommodate
   * the insertion.
   *
   * @param value the value to be inserted.
   */
  @Inline
  protected final void uncheckedHeadInsert(Address value) {
      if (VM.VERIFY_ASSERTIONS) VM.assertions._assert(bufferOffset(head).sLT(bufferSentinel(queue.getArity())));
    head.store(value);
    head = head.plus(BYTES_IN_ADDRESS);
    // if (Interface.VerifyAssertions) enqueued++;
  }

  /****************************************************************************
   *
   * Private instance methods and fields
   */

  /**
   * Buffer space has been exhausted, allocate a new buffer and enqueue
   * the existing buffer (if any).
   *
   * @param arity The arity of this buffer (used for sanity test only).
   */
  private void headOverflow(int arity) {
    if (VM.VERIFY_ASSERTIONS) VM.assertions._assert(arity == queue.getArity());
    if (head.NE(Deque.HEAD_INITIAL_VALUE))
      closeAndInsertHead(arity);

    head = queue.alloc();
    Plan.checkForAsyncCollection(); // possible side-effect of alloc()
  }

  /**
   * Close the head buffer and enqueue it at the front of the
   * shared buffer deque.
   *
   *  @param arity The arity of this buffer.
   */
  @Inline
  private void closeAndInsertHead(int arity) {
    queue.enqueue(head, arity, false);
  }

  /**
   * The tail is empty (or null), and the shared deque has no buffers
   * available.  If the head has sufficient entries, consume the head.
   * Otherwise try wait on the shared deque until either all other
   * clients of the reach exhaustion or a buffer becomes
   * available.
   *
   * @param arity The arity of this buffer
   * @return True if the consumer has eaten all of the entries
   */
  @SuppressWarnings("unused")
  private boolean tailStarved(int arity) {
    if (VM.VERIFY_ASSERTIONS) VM.assertions._assert(arity == queue.getArity());
    // entries in tail, so consume tail
    if (!bufferOffset(head).isZero()) {
      tailBufferEnd = head;
      tail = bufferStart(tailBufferEnd);
      head = Deque.HEAD_INITIAL_VALUE;
      return false;
    }

    // Wait for another entry to materialize...
    tailBufferEnd = queue.dequeueAndWait(arity, true);
    tail = bufferStart(tail);

    // return true if a) there is not a tail buffer or b) it is empty
    return (tail.EQ(Deque.TAIL_INITIAL_VALUE) || tail.EQ(tailBufferEnd));
  }
}
