/*
 *  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.plan.refcount.cd;

import org.mmtk.plan.Phase;
import org.mmtk.plan.refcount.RCBase;
import org.mmtk.plan.refcount.RCHeader;
import org.mmtk.utility.Log;
import org.mmtk.utility.deque.SharedDeque;
import org.mmtk.utility.options.Options;

import org.mmtk.vm.VM;

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

/**
 * This class implements the global state of a trial deletion cycle detector.

 */
@Uninterruptible public final class TrialDeletion extends CD {

  /****************************************************************************
   *
   * Class variables
   */
 // Collection phases
  public static final short CD_PREPARE_FILTER   = Phase.createSimple("td.prepare-filter");
  public static final short CD_PREPARE_COLLECT  = Phase.createSimple("td.prepare-collect");
  public static final short CD_FILTER_PURPLE    = Phase.createSimple("td.filter-purple");
  public static final short CD_FREE_FILTERED    = Phase.createSimple("td.free-filtered");
  public static final short CD_FILTER_MATURE    = Phase.createSimple("td.filter-mature");
  public static final short CD_MARK_GREY        = Phase.createSimple("td.mark-grey");
  public static final short CD_SCAN             = Phase.createSimple("td.scan");
  public static final short CD_COLLECT          = Phase.createSimple("td.collect");
  public static final short CD_FREE             = Phase.createSimple("td.free");
  public static final short CD_FLUSH_FILTERED   = Phase.createSimple("td.flush-filtered");
  public static final short CD_PROCESS_DECS     = Phase.createSimple("td.process-decs");
  public static final short CD_RELEASE          = Phase.createSimple("td.release");

  // CHECKSTYLE:OFF

  /* Cycle detection */
  private static final short cdPhase = Phase.createComplex("trial deletion",
      Phase.scheduleGlobal   (CD_PREPARE_FILTER),
      Phase.scheduleCollector(CD_FILTER_PURPLE),
      Phase.scheduleCollector(CD_FREE_FILTERED),
      Phase.scheduleGlobal   (CD_PREPARE_COLLECT),
      Phase.scheduleCollector(CD_FILTER_MATURE),
      Phase.scheduleCollector(CD_MARK_GREY),
      Phase.scheduleCollector(CD_SCAN),
      Phase.scheduleCollector(CD_COLLECT),
      Phase.scheduleCollector(CD_FREE),
      Phase.scheduleCollector(CD_FLUSH_FILTERED),
      Phase.scheduleCollector(CD_PROCESS_DECS),
      Phase.scheduleGlobal   (CD_RELEASE));

  // CHECKSTYLE:ON

  public static final int NO_PROCESSING   = 0;
  public static final int FILTER_PURPLE   = 1;
  public static final int FULL_COLLECTION = 2;

  /****************************************************************************
   *
   * Instance variables
   */
  public final SharedDeque workPool;
  public final SharedDeque blackPool;
  public final SharedDeque unfilteredPurplePool;
  public final SharedDeque maturePurplePool;
  public final SharedDeque filteredPurplePool;
  public final SharedDeque cyclePoolA;
  public final SharedDeque cyclePoolB;
  public final SharedDeque freePool;
  public int cdMode;
  private long startNanos;

  /****************************************************************************
   *
   * Initialization
   */

  public TrialDeletion(RCBase global) {
    workPool = new SharedDeque("workPool",RCBase.metaDataSpace, 1);
    blackPool = new SharedDeque("blackPool",RCBase.metaDataSpace, 1);
    unfilteredPurplePool = new SharedDeque("unfilteredPurplePool",RCBase.metaDataSpace, 1);
    maturePurplePool = new SharedDeque("maturePurplePool",RCBase.metaDataSpace, 1);
    filteredPurplePool = new SharedDeque("filteredPurplePool",RCBase.metaDataSpace, 1);
    cyclePoolA = new SharedDeque("cyclePoolA",RCBase.metaDataSpace, 1);
    cyclePoolB = new SharedDeque("cyclePoolB",RCBase.metaDataSpace, 1);
    freePool = new SharedDeque("freePool",RCBase.metaDataSpace, 1);
    cdMode = NO_PROCESSING;
    global.insertPhaseAfter(Phase.scheduleGlobal(RCBase.RELEASE), Phase.scheduleComplex(cdPhase));
  }

  /**
   * Perform a (global) collection phase.
   *
   * @param phaseId Collection phase to execute.
   */
  @Inline
  public boolean collectionPhase(int phaseId) {
    if (phaseId == CD_PREPARE_FILTER) {
      cdMode = NO_PROCESSING;
      if (shouldFilterPurple()) {
        cdMode = FILTER_PURPLE;
      }
      workPool.prepareNonBlocking();
      blackPool.prepareNonBlocking();
      filteredPurplePool.prepareNonBlocking();
      cyclePoolA.prepareNonBlocking();
      cyclePoolB.prepareNonBlocking();

      maturePurplePool.prepare();
      unfilteredPurplePool.prepare();
      freePool.prepare();
      return true;
    }

    if (phaseId == CD_PREPARE_COLLECT) {
      if (cdMode == FILTER_PURPLE) {
        if (shouldCollectCycles()) {
          cdMode = FULL_COLLECTION;
          startNanos = VM.statistics.nanoTime();
          if (Options.verbose.getValue() > 0) {
            Log.write("(CD-TD ");
            Log.flush();
          }
        }
      }
      return true;
    }

    if (phaseId == CD_RELEASE) {
      if (cdMode != NO_PROCESSING) {
        unfilteredPurplePool.reset();
      }
      if (cdMode == FULL_COLLECTION) {
        workPool.reset();
        blackPool.reset();
        maturePurplePool.reset();
        filteredPurplePool.reset();
        cyclePoolA.reset();
        cyclePoolB.reset();
        freePool.reset();

        if (Options.verbose.getValue() > 0) {
          Log.write(VM.statistics.nanosToMillis(VM.statistics.nanoTime() - startNanos));
          Log.write(" ms)");
        }
      }
      return true;
    }

    return false;
  }

  /*****************************************************************************
   *
   * Collection
   */

  /**
   * Update the CD section of the RC word when an increment is performed
   *
   * @param rcWord The refcount word after the increment.
   * @return The updated status after CD modification
   */
  public int notifyIncRC(int rcWord) {
    return (rcWord & ~RCHeader.PURPLE);
  }

  /**
   * If the reported decrement succeeds, should we buffer the object?
   *
   * @param rcWord The refcount work post decrement.
   * @return The updated status after CD modification
   */
  public boolean shouldBufferOnDecRC(int rcWord) {
    return ((rcWord & RCHeader.COLOR_MASK) < RCHeader.PURPLE) &&
           ((rcWord & RCHeader.BUFFERED_MASK) == 0);
  }


  /**
   * Allow a free of this object, or is it in a CD data structure
   *
   * @param object The object to check
   * @return True if free is safe
   */
  public boolean allowFree(ObjectReference object) {
    return !RCHeader.isBuffered(object);
  }

  /**
   * Update the header on a buffered dec to non-zero RC
   *
   * @param rcWord The refcount work post decrement.
   * @return The updated status after CD modification
   */
  public int updateHeaderOnBufferedDec(int rcWord) {
    return (rcWord & ~RCHeader.COLOR_MASK) | RCHeader.PURPLE | RCHeader.BUFFERED_MASK;
  }

  /**
   * Update the header on a non-buffered dec to non-zero RC
   *
   * @param rcWord The refcount work post decrement.
   * @return The updated status after CD modification
   */
  public int updateHeaderOnUnbufferedDec(int rcWord) {
    if ((rcWord & RCHeader.GREEN) != 0) {
      return rcWord;
    }
    return (rcWord & ~RCHeader.COLOR_MASK) | RCHeader.PURPLE;
  }

  /**
   * Perform any cycle detector header initialization.
   *
   * @param typeRef Type information for the object.
   * @param rcWord The refcount work post decrement.
   * @return The updated status after CD modification
   */
  public int initializeHeader(ObjectReference typeRef, int rcWord) {
    if (VM.objectModel.isAcyclic(typeRef)) {
      rcWord |= RCHeader.GREEN;
    }
    return rcWord;
  }
}
