/*
 *  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.jikesrvm.compilers.baseline;

import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.LineNumberReader;
import java.io.PrintStream;
import java.util.StringTokenizer;
import org.jikesrvm.VM;
import org.jikesrvm.Callbacks;
import org.jikesrvm.classloader.MemberReference;
import org.jikesrvm.classloader.NormalMethod;
import org.jikesrvm.runtime.Magic;
import org.vmmagic.pragma.Entrypoint;

/**
 * A repository of edge counters for bytecode-level edge conditional branches.
 */
public final class EdgeCounts implements Callbacks.ExitMonitor {
  /**
   * Adjustment to offset in data from the bytecode index for taken
   * branch counts
   */
  public static final int TAKEN = 0;
  /**
   * Adjustment to offset in data from the bytecode index for not
   * taken branch counts
   */
  public static final int NOT_TAKEN = 1;

  /** For a non-adaptive system, have we registered the exit call back yet? */
  private static boolean registered = false;

  /**
   * Array of edge count data. The first index is the ID of the
   * method, the second index is the bytecode index within the method
   * plus either TAKEN or NOT_TAKEN. The value is the count of the
   * number of times a particular branch event occurs.
   */
  @Entrypoint
  private static int[][] data;

  public void notifyExit(int value) { dumpCounts(); }

  public static void boot(String inputFileName) {
    if (inputFileName != null) {
      readCounts(inputFileName);
    }
  }

  public static synchronized void allocateCounters(NormalMethod m, int numEntries) {
    if (numEntries == 0) return;
    if (!VM.BuildForAdaptiveSystem && !registered) {
      // Assumption: If edge counters were enabled in a non-adaptive system
      //             then the user must want us to dump them when the system
      //             exits.  Otherwise why would they have enabled them...
      registered = true;
      Callbacks.addExitMonitor(new EdgeCounts());
    }
    allocateCounters(m.getId(), numEntries);
  }

  private static synchronized void allocateCounters(int id, int numEntries) {
    if (data == null) {
      data = new int[id + 500][];
    }
    if (id >= data.length) {
      int newSize = data.length * 2;
      if (newSize <= id) newSize = id + 500;
      int[][] tmp = new int[newSize][];
      System.arraycopy(data, 0, tmp, 0, data.length);
      Magic.sync();
      data = tmp;
    }
    data[id] = new int[numEntries];
  }

  public static BranchProfiles getBranchProfiles(NormalMethod m) {
    int id = m.getId();
    if (data == null || id >= data.length) return null;
    if (data[id] == null) return null;
    return new BranchProfiles(m, data[id]);
  }

  /**
   * Dump all the profile data to the file BaselineCompiler.options.EDGE_COUNTER_FILE
   */
  public static void dumpCounts() {
    dumpCounts(BaselineCompiler.options.EDGE_COUNTER_FILE);
  }

  /**
   * Dump all profile data to the given file
   * @param fn output file name
   */
  public static void dumpCounts(String fn) {
    PrintStream f;
    try {
      f = new PrintStream(new FileOutputStream(fn));
    } catch (IOException e) {
      VM.sysWrite("\n\nEdgeCounts.dumpCounts: Error opening output file!!\n\n");
      return;
    }
    if (data == null) return;
    for (int i = 0; i < data.length; i++) {
      if (data[i] != null) {
        NormalMethod m =
            (NormalMethod) MemberReference.getMemberRef(i).asMethodReference().peekResolvedMethod();
        new BranchProfiles(m, data[i]).print(f);
      }
    }
  }

  public static void readCounts(String fn) {
    LineNumberReader in = null;
    try {
      in = new LineNumberReader(new FileReader(fn));
    } catch (IOException e) {
      e.printStackTrace();
      VM.sysFail("Unable to open input edge counter file " + fn);
    }
    try {
      int[] cur = null;
      int curIdx = 0;
      for (String s = in.readLine(); s != null; s = in.readLine()) {
        StringTokenizer parser = new StringTokenizer(s, " \t\n\r\f,{}");
        String firstToken = parser.nextToken();
        if (firstToken.equals("M")) {
          int numCounts = Integer.parseInt(parser.nextToken());
          MemberReference key = MemberReference.parse(parser);
          int id = key.getId();
          allocateCounters(id, numCounts);
          cur = data[id];
          curIdx = 0;
        } else {
          String type = parser.nextToken(); // discard bytecode index, we don't care.
          if (type.equals("switch")) {
            parser.nextToken(); // discard '<'
            for (String nt = parser.nextToken(); !nt.equals(">"); nt = parser.nextToken()) {
              cur[curIdx++] = Integer.parseInt(nt);
            }
          } else if (type.equals("forwbranch") || type.equals("backbranch")) {
            parser.nextToken(); // discard '<'
            cur[curIdx + TAKEN] = Integer.parseInt(parser.nextToken());
            cur[curIdx + NOT_TAKEN] = Integer.parseInt(parser.nextToken());
            curIdx += 2;
          } else {
            VM.sysFail("Format error in edge counter input file");
          }
        }
      }
      in.close();
    } catch (IOException e) {
      e.printStackTrace();
      VM.sysFail("Error parsing input edge counter file" + fn);
    }

    // Enable debug of input by dumping file as we exit the VM.
    if (false) {
      Callbacks.addExitMonitor(new EdgeCounts());
      BaselineCompiler.processCommandLineArg("-X:base:", "edge_counter_file=DebugEdgeCounters");
    }
  }

}
