/*
 *  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.LargeObjectSpace;
import org.mmtk.utility.gcspy.Color;
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.vm.gcspy.ServerInterpreter;
import org.mmtk.utility.Conversions;
import org.mmtk.utility.Log;
import org.mmtk.vm.VM;

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


/**
 * This class implements a simple driver for the MMTk LargeObjectSpace.
 */
@Uninterruptible public class TreadmillDriver extends AbstractDriver {

  private static final boolean DEBUG = false;

  // The streams
  protected IntStream   usedSpaceStream;
  protected ShortStream objectsStream;
  protected ShortStream rootsStream;
  protected ShortStream refFromImmortalStream;

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

  // Overall statistics
  protected int totalObjects         = 0; // total number of objects allocated
  protected int totalUsedSpace       = 0; // total space used
  protected int totalRoots           = 0; // total of roots
  protected int totalRefFromImmortal = 0; // total direct references from the immortal space
  protected Address maxAddr;              // the largest address seen
  protected int threshold;


  /**
   * Create a new driver for this collector
   *
   * @param server The name of the GCspy server that owns this space
   * @param spaceName The name of this driver
   * @param lospace the large object space for this allocator
   * @param blockSize The tile size
   * @param threshold the size threshold of the LOS
   * @param mainSpace Is this the main space?
   */
  public TreadmillDriver(
                         ServerInterpreter server,
                         String spaceName,
                         LargeObjectSpace lospace,
                         int blockSize,
                         int threshold,
                         boolean mainSpace) {
    //TODO blocksize should be a multiple of treadmill granularity
    super(server, spaceName, lospace, blockSize, mainSpace);

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

    this.threshold = threshold;

    // Initialise a subspace and 2 Streams
    subspace = createSubspace(lospace);
    allTileNum = 0;
    maxAddr = lospace.getStart();
    usedSpaceStream       = createUsedSpaceStream();
    objectsStream         = createObjectsStream();
    rootsStream           = createRootsStream();
    refFromImmortalStream = createRefFromImmortalStream();
    serverSpace.resize(0); // the collector must call resize() before gathering data

    // Initialise the statistics
    resetData();
  }

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

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

  @Interruptible
  private ShortStream createObjectsStream() {
    return VM.newGCspyShortStream(
                     this,
                     "Objects stream",
                     (short)0,
                     (short)(blockSize/threshold),
                     (short)0,
                     (short)0,
                     "No. of objects = ",
                     " objects",
                     StreamConstants.PRESENTATION_PLUS,
                     StreamConstants.PAINT_STYLE_ZERO,
                     0,
                     Color.Green,
                     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 tile stats for all streams, including values used for summaries
   */
  public void resetData() {
    super.resetData();

    // Reset all the streams
    usedSpaceStream.resetData();
    objectsStream.resetData();
    refFromImmortalStream.resetData();

    // Reset the summary counts
    totalUsedSpace = 0;
    totalObjects = 0;
    totalRefFromImmortal = 0;
  }

  /**
   * Update the tile statistics
   * In this case, we are accounting for super-page objects, rather than
   * simply for the objects they contain.
   *
   * @param addr The address of the superpage
   */
  public void scan(Address addr) {

    int index = subspace.getIndex(addr);
    int length = ((LargeObjectSpace)mmtkSpace).getSize(addr).toInt();

    if (DEBUG) {
      Log.write("TreadmillDriver: super=", addr);
      Log.write(", index=", index);
      Log.write(", pages=", length);
      Log.write(", bytes=", Conversions.pagesToBytes(length).toInt());
      Log.writeln(", max=", usedSpaceStream.getMaxValue());
    }

    totalObjects++;
    totalUsedSpace += length;
    objectsStream.increment(index, (short)1);
    int remainder = subspace.spaceRemaining(addr);
    usedSpaceStream.distribute(index, remainder, blockSize, length);

    Address tmp = addr.plus(length);
    if (tmp.GT(maxAddr)) maxAddr = tmp;
  }

  /**
   * Transmit the data if this event is of interest to the client
   * @param event The event, either BEFORE_COLLECTION, SEMISPACE_COPIED
   * or AFTER_COLLECTION
   */
  public void transmit(int event) {
    if (!isConnected(event))
      return;

    // At this point, we've filled the tiles with data,
    // however, we don't know the size of the space
    // Calculate the highest indexed tile used so far, and update the subspace
    Address start = subspace.getStart();
    int required = countTileNum(start, maxAddr, blockSize);
    int current = subspace.getBlockNum();
    if (required > current || maxAddr != subspace.getEnd()) {
      subspace.reset(start, maxAddr, 0, required);
      allTileNum = required;
      serverSpace.resize(allTileNum);
      setTilenames(subspace, allTileNum);
    }

    // Set the summaries
    setupSummaries();

    // set the control info: all of space is USED
    controlValues(CONTROL_USED,
                  subspace.getFirstIndex(), subspace.getBlockNum());

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

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

  /**
   * Setup summaries part of the <code>transmit</code> method.<p>
   * Override this method to setup summaries of additional streams in subclasses.
   */
  protected void setupSummaries() {
    usedSpaceStream.setSummary(totalUsedSpace,
                               subspace.getEnd().diff(subspace.getStart()).toInt());
    objectsStream.setSummary(totalObjects);
    rootsStream.setSummary(totalRoots);
    refFromImmortalStream.setSummary(totalRefFromImmortal);
  }


  /**
   * 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. <br>
   * 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;
    }
  }
}
