/*
 *  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.utility.Log;
import org.mmtk.utility.TracingConstants;
import org.mmtk.vm.VM;
import org.mmtk.utility.Constants;

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

/**
 * This supports <i>unsynchronized</i> enqueuing and dequeuing of tracing data
 * and bulk processing of the buffer.
 */
@Uninterruptible public class TraceBuffer extends LocalQueue
  implements Constants, TracingConstants {

  /***********************************************************************
   *
   * Class based constants
   */
  private static final Word TRACE_NEW_RECORD = Word.fromIntSignExtend(3);
  private static final Word TRACE_ALLOC_SIZE = Word.fromIntSignExtend(5);
//  private static final Word TRACE_ALLOC_NAME = Word.fromIntSignExtend(6);
  private static final Word TRACE_ALLOC_FP = Word.fromIntSignExtend(7);
  private static final Word TRACE_ALLOC_THREAD = Word.fromIntSignExtend(9);
  private static final Word TRACE_TIB_VALUE = Word.fromIntSignExtend(10);
  private static final Word TRACE_DEATH_TIME = Word.fromIntSignExtend(11);
  private static final Word TRACE_FIELD_TARGET = Word.fromIntSignExtend(12);
  private static final Word TRACE_ARRAY_TARGET = Word.fromIntSignExtend(13);
  private static final Word TRACE_FIELD_SLOT = Word.fromIntSignExtend(14);
  private static final Word TRACE_ARRAY_ELEMENT = Word.fromIntSignExtend(15);
  private static final Word TRACE_STATIC_TARGET = Word.fromIntSignExtend(17);
  private static final Word TRACE_BOOT_ALLOC_SIZE = Word.fromIntSignExtend(18);

  /*
   * Debugging and trace reducing constants
   */
  public static final boolean OMIT_ALLOCS=false;
  public static final boolean OMIT_UPDATES=false;
  public static final boolean OMIT_BOOTALLOCS=false;
  public static final boolean OMIT_UNREACHABLES=false;
  public static final boolean OMIT_OTHERS=false;
  public static final boolean OMIT_OUTPUT=OMIT_ALLOCS && OMIT_UPDATES &&
                                          OMIT_OTHERS;


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

  /**
   * Constructor
   *
   * @param pool The shared queue to which this queue will append its
   * buffers (when full or flushed) and from which it will aquire new
   * buffers when it has exhausted its own.
   */
  public TraceBuffer(SharedDeque pool) {
    super(pool);
  }

  /**
   * Push word onto the tracing queue.
   *
   * @param i The data to be pushed onto the tracing queue
   */
  @Inline
  public final void push(Word i) {
    checkTailInsert(1);
    uncheckedTailInsert(i.toAddress());
  }

  /**
   * Process the data in the tracing buffer, output information as needed.
   */
  public final void process() {
    Word traceState = TRACE_NEW_RECORD;
    int entriesNotFlushed = 0;
    boolean loggedRecord = false;
    /* First we must flush any remaining data */
    if (!OMIT_OUTPUT) Log.writeln();

    /* Process through the entire buffer. */
    while (checkDequeue(1)) {
      /* For speed and efficiency, we will actually process the data buffer by
         buffer and not by dequeue-ing each entry. */
      while (!bufferOffset(head).isZero()) {
        head = head.minus(BYTES_IN_ADDRESS);
        Word val = head.loadWord();
        if (traceState.EQ(TRACE_NEW_RECORD)) {
          loggedRecord = false;
          if (val.EQ(TRACE_GCSTART)) {
            if (!OMIT_OTHERS) {
              Log.write('G');
              Log.write('C');
              Log.writeln('B', true);
            }
          } else if (val.EQ(TRACE_GCEND)) {
            if (!OMIT_OTHERS) {
              Log.write('G');
              Log.write('C');
              Log.writeln('E', true);
            }
          } else {
            traceState = val;
          }
        } else {
          if (traceState.EQ(TRACE_EXACT_ALLOC) ||
              traceState.EQ(TRACE_ALLOC)) {
            if (!OMIT_ALLOCS) {
              Log.write((traceState.EQ(TRACE_EXACT_ALLOC)) ? 'A' : 'a');
              Log.write(' ');
              Log.write(val);
              loggedRecord = true;
            }
            traceState = TRACE_ALLOC_SIZE;
          } else if (traceState.EQ(TRACE_EXACT_IMMORTAL_ALLOC) ||
                     traceState.EQ(TRACE_IMMORTAL_ALLOC)) {
            if (!OMIT_ALLOCS) {
              Log.write((traceState.EQ(TRACE_EXACT_IMMORTAL_ALLOC)) ? 'I' : 'i');
              Log.write(' ');
              Log.write(val);
              loggedRecord = true;
            }
            traceState = TRACE_ALLOC_SIZE;
          } else if (traceState.EQ(TRACE_BOOT_ALLOC)) {
            if (!OMIT_BOOTALLOCS) {
              Log.write('B');
              Log.write(' ');
              Log.write(val);
              loggedRecord = true;
            }
            traceState = TRACE_BOOT_ALLOC_SIZE;
          } else if (traceState.EQ(TRACE_DEATH)) {
            if (!OMIT_UNREACHABLES) {
              Log.write('D');
              Log.write(' ');
              Log.write(val);
              loggedRecord = true;
            }
            traceState = TRACE_DEATH_TIME;
          } else if (traceState.EQ(TRACE_BOOT_ALLOC_SIZE)) {
            if (!OMIT_BOOTALLOCS)
              Log.write(val);
            traceState = TRACE_NEW_RECORD;
          } else if (traceState.EQ(TRACE_ALLOC_SIZE)) {
            if (!OMIT_ALLOCS)
              Log.write(val);
            traceState = TRACE_ALLOC_FP;
          } else if (traceState.EQ(TRACE_ALLOC_FP)) {
            if (!OMIT_ALLOCS)
              Log.write(val);
            traceState = TRACE_ALLOC_THREAD;
          } else if (traceState.EQ(TRACE_ALLOC_THREAD)) {
            if (!OMIT_ALLOCS)
              Log.write(val);
            traceState = TRACE_NEW_RECORD;
          } else if (traceState.EQ(TRACE_TIB_SET)) {
            if (!OMIT_UPDATES) {
              Log.write('T');
              Log.write(' ');
              Log.write(val);
              loggedRecord = true;
            }
            traceState = TRACE_TIB_VALUE;
          } else if (traceState.EQ(TRACE_STATIC_SET)) {
            if (!OMIT_UPDATES) {
              Log.write('S');
              Log.write(' ');
              Log.write(val);
              loggedRecord = true;
            }
            traceState = TRACE_STATIC_TARGET;
          } else if (traceState.EQ(TRACE_TIB_VALUE) ||
                     traceState.EQ(TRACE_STATIC_TARGET)) {
            if (!OMIT_UPDATES)
              Log.write(val);
            traceState = TRACE_NEW_RECORD;
          } else if (traceState.EQ(TRACE_DEATH_TIME)) {
            if (!OMIT_UNREACHABLES)
              Log.write(val);
            traceState = TRACE_NEW_RECORD;
          } else if (traceState.EQ(TRACE_FIELD_SET) ||
                     traceState.EQ(TRACE_ARRAY_SET)) {
            if (!OMIT_UPDATES) {
              Log.write('U');
              Log.write(' ');
              Log.write(val);
              loggedRecord = true;
            }
            traceState = TRACE_FIELD_SLOT;
          } else if (traceState.EQ(TRACE_FIELD_TARGET) ||
                     traceState.EQ(TRACE_ARRAY_TARGET)) {
            if (!OMIT_UPDATES)
              Log.write(val);
            traceState = TRACE_NEW_RECORD;
          } else if (traceState.EQ(TRACE_FIELD_SLOT) ||
                     traceState.EQ(TRACE_ARRAY_ELEMENT)) {
            if (!OMIT_UPDATES)
              Log.write(val);
            traceState = TRACE_FIELD_TARGET;
          } else {
            VM.assertions.fail("Cannot understand directive!\n");
          }
          if (traceState.EQ(TRACE_NEW_RECORD) && loggedRecord) {
            entriesNotFlushed++;
            Log.writeln();
          } else if (loggedRecord) {
              Log.write(' ');
          }
        }
        if (entriesNotFlushed == 10) {
          if (!OMIT_OUTPUT)
            Log.flush();
          entriesNotFlushed = 0;
        }
      }
    }
    resetLocal();
  }
}
