/*
 *  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.gcspy.drivers;

import org.mmtk.policy.Space;
import org.mmtk.utility.gcspy.Color;
import org.mmtk.utility.gcspy.LinearScan;
import org.mmtk.utility.gcspy.StreamConstants;
import org.mmtk.utility.gcspy.Subspace;
import org.mmtk.vm.gcspy.IntStream;
import org.mmtk.vm.gcspy.ShortStream;

import org.mmtk.utility.Log;
import org.mmtk.vm.gcspy.ServerInterpreter;
import org.mmtk.vm.VM;

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

/**
 * GCspy driver for the MMTk ContigousSpace.
 *
 * This class implements a simple driver for contiguous MMTk spaces
 * such as CopySpace and ImmortalSpace.
 */
@Uninterruptible public class LinearSpaceDriver extends AbstractDriver {

  // The GCspy streams
  protected IntStream   scalarUsedSpaceStream;
  protected IntStream   arrayUsedSpaceStream;
  protected ShortStream scalarObjectsStream;
  protected ShortStream arrayObjectsStream;
  protected ShortStream arrayPrimitiveStream;
  protected ShortStream rootsStream;
  protected ShortStream refFromImmortalStream;

  protected Subspace subspace;               // A subspace for all of this space
  protected int allTileNum;                  // total number of tiles

  // Overall statistics
  protected int totalScalarObjects   = 0;    // total number of objects allocated
  protected int totalArrayObjects    = 0;
  protected int totalPrimitives      = 0;
  protected int totalScalarUsedSpace = 0;    // total space used
  protected int totalArrayUsedSpace  = 0;
  protected int totalRoots           = 0;
  protected int totalRefFromImmortal = 0;

  private final LinearScan scanner;          // A scanner to trace objects

  // Debugging
  protected Address lastAddress = Address.zero();
  protected int lastSize = 0;
  private static final boolean DEBUG = false;


  /**
   * Create a new driver for a contiguous MMTk space.
   *
   * @param server The GCspy ServerInterpreter
   * @param spaceName The name of this GCspy space
   * @param mmtkSpace The MMTk space
   * @param blockSize The tile size
   * @param mainSpace Is this the main space?
   */
  public LinearSpaceDriver(ServerInterpreter server,
                           String spaceName,
                           Space mmtkSpace,
                           int blockSize,
                           boolean mainSpace) {

    super(server, spaceName, mmtkSpace, blockSize, mainSpace);

    if (DEBUG) {
      Log.write("LinearSpaceDriver for "); Log.write(spaceName);
      Log.write(", blocksize="); Log.write(blockSize);
      Log.write(", start="); Log.write(mmtkSpace.getStart());
      Log.write(", extent="); Log.write(mmtkSpace.getExtent());
      Log.write(", maxTileNum="); Log.writeln(maxTileNum);
    }

    // Initialise a subspace and 4 Streams
    subspace = createSubspace(mmtkSpace);
    allTileNum = 0;
    scalarUsedSpaceStream = createScalarUsedSpaceStream();
    arrayUsedSpaceStream  = createArrayUsedSpaceStream();
    scalarObjectsStream   = createScalarObjectsStream();
    arrayPrimitiveStream  = createArrayPrimitiveStream();
    arrayObjectsStream    = createArrayObjectsStream();
    rootsStream           = createRootsStream();
    refFromImmortalStream = createRefFromImmortalStream();
    serverSpace.resize(0); // the collector must call resize() before gathering data

    // Initialise the statistics
    resetData();
    scanner = new LinearScan(this);
  }

  /**
   * Get the name of this driver type.
   * @return The name of this driver.
   */
  protected String getDriverName() { return "MMTk LinearSpaceDriver"; }

  /**
   * Private creator methods to create the Streams.
   */
  @Interruptible
  private IntStream createScalarUsedSpaceStream() {
    return VM.newGCspyIntStream(
                     this,
                     "Scalar Used Space stream",            // stream name
                     0,                                     // min. data value
                     blockSize,                             // max. data value
                     0,                                     // zero value
                     0,                                     // default value
                    "Scalars and primitive arrays: ",       // value prefix
                    " bytes",                               // value suffix
                     StreamConstants.PRESENTATION_PERCENT,  // presentation style
                     StreamConstants.PAINT_STYLE_ZERO,      // paint style
                     0,                                     // index of max stream (only needed if the presentation is *_VAR)
                     Color.Red,                             // tile colour
                     true);                                 // summary enabled
  }

  @Interruptible
  private IntStream createArrayUsedSpaceStream() {
    return VM.newGCspyIntStream(
                     this,
                     "Array Used Space stream",
                     0,
                     blockSize,
                     0,
                     0,
                    "Reference arrays: ",
                    " bytes",
                     StreamConstants.PRESENTATION_PERCENT,
                     StreamConstants.PAINT_STYLE_ZERO,
                     0,
                     Color.Blue,
                     true);
  }

  @Interruptible
  private ShortStream createScalarObjectsStream() {
    return VM.newGCspyShortStream(
                     this,
                     "Scalar Objects stream",
                     (short)0,
                     // Say, max value = 50% of max possible
                     (short)(maxObjectsPerBlock(blockSize)/2),
                     (short)0,
                     (short)0,
                     "Scalars: ",
                     " objects",
                     StreamConstants.PRESENTATION_PLUS,
                     StreamConstants.PAINT_STYLE_ZERO,
                     0,
                     Color.Green,
                     true);
  }

  @Interruptible
  private ShortStream createArrayPrimitiveStream() {
    return VM.newGCspyShortStream(
                     this,
                     "Array Primitive stream",
                     (short)0,
                     // Say, typical primitive array size = 4 * typical scalar size?
                     (short)(maxObjectsPerBlock(blockSize)/8),
                     (short)0,
                     (short)0,
                     "Primitive arrays: ",
                     " objects",
                     StreamConstants.PRESENTATION_PLUS,
                     StreamConstants.PAINT_STYLE_ZERO,
                     0,
                     Color.Yellow,
                     true);
  }

  @Interruptible
  private ShortStream createArrayObjectsStream() {
    return VM.newGCspyShortStream(
                     this,
                     "Array Objects stream",
                     (short)0,
                     // Say, typical ref array size = 4 * typical scalar size?
                     (short)(maxObjectsPerBlock(blockSize)/8),
                     (short)0,
                     (short)0,
                     "Reference arrays: ",
                     " objects",
                     StreamConstants.PRESENTATION_PLUS,
                     StreamConstants.PAINT_STYLE_ZERO,
                     0,
                     Color.Cyan,
                     true);
  }

  @Interruptible
  private ShortStream createRootsStream() {
    return VM.newGCspyShortStream(
                     this,
                     "Roots stream",
                     (short)0,
                     // Say, typical size = 4 * typical scalar size?
                     (short)(maxObjectsPerBlock(blockSize)/8),
                     (short)0,
                     (short)0,
                     "Roots: ",
                     " objects",
                     StreamConstants.PRESENTATION_PLUS,
                     StreamConstants.PAINT_STYLE_ZERO,
                     0,
                     Color.Blue,
                     true);
  }

  @Interruptible
  private ShortStream createRefFromImmortalStream() {
    return VM.newGCspyShortStream(
                     this,
                     "References from immortal stream",
                     (short)0,
                     // Say, typical size = 4 * typical scalar size?
                     (short)(maxObjectsPerBlock(blockSize)/8),
                     (short)0,
                     (short)0,
                     "References from immortal space: ",
                     " references",
                     StreamConstants.PRESENTATION_PLUS,
                     StreamConstants.PAINT_STYLE_ZERO,
                     0,
                     Color.Blue,
                     true);
  }

  /**
   * Reset the statistics for all the streams, including totals used for summaries
   */
  public void resetData() {
    super.resetData();

    // Reset all the streams
    scalarUsedSpaceStream.resetData();
    arrayUsedSpaceStream.resetData();
    scalarObjectsStream.resetData();
    arrayObjectsStream.resetData();
    arrayPrimitiveStream.resetData();
    refFromImmortalStream.resetData();

    // Reset the summary counts
    totalScalarObjects   = 0;
    totalArrayObjects    = 0;
    totalPrimitives      = 0;
    totalScalarUsedSpace = 0;
    totalArrayUsedSpace  = 0;
    totalRefFromImmortal = 0;
  }

  /**
   * BumpPointer.linearScan needs a LinearScan object, which we provide here.
   * @return the scanner for this driver
   */
   public LinearScan getScanner() { return scanner; }

  /**
   * Set the current address range of a contiguous space
   * @param start the start of the contiguous space
   * @param end the end of the contiguous space
   */
  public void setRange(Address start, Address end) {
    int current = subspace.getBlockNum();
    int required = countTileNum(start, end, subspace.getBlockSize());

    // Reset the subspace
    if(required != current)
      subspace.reset(start, end, 0, required);

    if (DEBUG) {
      Log.write("\nContiguousSpaceDriver.setRange for contiguous space: ");
      Log.write(subspace.getFirstIndex()); Log.write("-", subspace.getBlockNum());
      Log.write(" (", start); Log.write("-", end); Log.write(")");
    }

    // Reset the driver
    // Note release() only resets a CopySpace's  cursor (and optionally zeroes
    // or mprotects the pages); it doesn't make the pages available to other
    // spaces. If pages were to be released, change the test here to
    //     if (allTileNum != required) {
    if (allTileNum < required) {
      if (DEBUG) { Log.write(", resize from ", allTileNum); Log.write(" to ", required); }
      allTileNum = required;
      serverSpace.resize(allTileNum);
      setTilenames(subspace, allTileNum);
    }
    if (DEBUG) Log.writeln();
  }


  /**
   * Update the tile statistics
   * @param obj The current object
   */
  public void  scan(ObjectReference obj) {
    scan(obj, true);
  }

  /**
   * Update the tile statistics
   * @param obj The current object
   * @param total Whether to accumulate the values
   */
  public void scan(ObjectReference obj, boolean total) {
    boolean isArray = VM.objectModel.isArray(obj);
    int length = VM.objectModel.getCurrentSize(obj);
    Address addr = obj.toAddress();

    if (VM.VERIFY_ASSERTIONS) {
      if(addr.LT(lastAddress.plus(lastSize))) {
        Log.write("\nContiguousSpaceDriver finds addresses going backwards: ");
        Log.write("last="); Log.write(lastAddress);
        Log.write(" last size="); Log.write(lastSize);
        Log.writeln(" current=", addr);
      }
      lastAddress = addr;
      lastSize = length;
    }

    // Update the stats
    if (subspace.addressInRange(addr)) {
      int index = subspace.getIndex(addr);
      int remainder = subspace.spaceRemaining(addr);
      if (isArray) {
        arrayObjectsStream.increment(index, (short)1);
        arrayUsedSpaceStream.distribute(index, remainder, blockSize, length);
        if (total) {
          totalArrayObjects++;
          totalArrayUsedSpace += length;
        }
      } else {
        if(!this.scanCheckPrimitiveArray(obj, index, total, length)) {
          // real object
          scalarObjectsStream.increment(index, (short)1);
          if (total) {
            totalScalarObjects++;
            totalScalarUsedSpace += length;
          }
        }
        scalarUsedSpaceStream.distribute(index, remainder, blockSize, length);
      }
    }
  }

  /**
   * Check if this Object is an array of primitives.<br>
   * Part of the public scan() method.
   *
   * @param obj The Object to check
   * @param index Index of the tile
   * @param total Increment summary
   * @param length Current size of the Object, will be added to array space summary.
   * @return True if this Object is an array of primitives.
   */
  protected boolean scanCheckPrimitiveArray(ObjectReference obj, int index, boolean total, int length) {
    if(VM.objectModel.isPrimitiveArray(obj)) {
      arrayPrimitiveStream.increment(index, (short)1);
      if (total) {
        totalPrimitives++;
        totalScalarUsedSpace += length;
      }
      return true;
    } else {
      return false;
    }
  }

  /**
   * Transmit the data if this event is of interest to the client.<p>
   * Implemented using the algorithm pattern, subclasses can override parts of it.
   * @param event The event, defined in the Plan
   */
  public void transmit(int event) {
    if (!server.isConnected(event))
      return;

    if (DEBUG) {
      Log.write("CONNECTED\n");
      Log.write(myClass);
      Log.write(".send: numTiles=", allTileNum);
      //Log.write("LinearSpaceDriver.transmit: numTiles=", allTileNum);
      Log.writeln(", control.length=", control.length);
      Log.flush();
    }

    // Setup the summaries
    setupSummaries();

    // Setup the control info
    setupControlInfo();

    // Setup the space info
    Offset size = subspace.getEnd().diff(subspace.getStart());
    setSpaceInfo(size);

    // Send the all streams
    send(event, allTileNum);

    // Debugging
    if (VM.VERIFY_ASSERTIONS) {
      lastAddress = Address.zero();
      lastSize = 0;
    }
  }

  /**
   * Setup summaries part of the <code>transmit</code> method.<p>
   * Override this method to setup summaries of additional streams in subclasses.
 */
  protected void setupSummaries() {
    scalarUsedSpaceStream.setSummary(totalScalarUsedSpace,
                                     subspace.getEnd().diff(subspace.getStart()).toInt());
    arrayUsedSpaceStream.setSummary(totalArrayUsedSpace,
                                    subspace.getEnd().diff(subspace.getStart()).toInt());
    scalarObjectsStream.setSummary(totalScalarObjects);
    arrayObjectsStream.setSummary(totalArrayObjects);
    arrayPrimitiveStream.setSummary(totalPrimitives);
    rootsStream.setSummary(totalRoots);
    refFromImmortalStream.setSummary(totalRefFromImmortal);
  }

  /**
   * Setup control info part of the <code>transmit</code> method.<p>
   * Override this method to change the controls for your own driver subclass.
 */
  protected void setupControlInfo() {
    int numBlocks = subspace.getBlockNum();
    controlValues(CONTROL_USED, subspace.getFirstIndex(), numBlocks);
    if (DEBUG) {
      Log.write("LinearSpaceDriver.transmitSetupControlInfo: allTileNum=", allTileNum);
      Log.writeln(", numBlocks=", numBlocks);
    }
    if (numBlocks < allTileNum)
      controlValues(CONTROL_UNUSED,
                    subspace.getFirstIndex() + numBlocks,
                    allTileNum - numBlocks);
  }

  /**
   * Handle a root address
   *
   * @param addr Root Address
   * @return true if the given Address is in this subspace.
   */
  public boolean handleRoot(Address addr) {
    if(subspace.addressInRange(addr)) {
      // increment tile
      int index = subspace.getIndex(addr);
      rootsStream.increment(index, (short)1);
      // increment summary
      this.totalRoots++;
      return true;
    } else {
      return false;
    }
  }

  /**
   * Reset the roots Stream
   * The roots Stream has to be reset seperately because we do not
   * gather data in the usual way using <code>scan()</code>.
 */
  public void resetRootsStream() {
    rootsStream.resetData();
    totalRoots = 0;
  }

  /**
   * Handle a direct reference from the immortal space.
   *
   * @param addr The Address
   * @return true if the given Address is in this subspace.
   */
  public boolean handleReferenceFromImmortalSpace(Address addr) {
    if(subspace.addressInRange(addr)) {
      // increment tile
      int index = subspace.getIndex(addr);
      refFromImmortalStream.increment(index, (short)1);
      // increment summary
      this.totalRefFromImmortal++;
      return true;
    } else {
      return false;
    }
  }

}
