package org.jcon.util;

import java.util.Vector;

/**
 * Simple benchmark related methods. All static methods.
 * Handles timings up to 999 seconds.
 * <p>
 * Supports multiple time series. Each method should refer
 * to the desired series. Currently these are 0 to 2.
 * <p>
 *      A use example is: <pre>
 * BenchMark.startTimeSeries(0);
 * runVariation1();
 * System.out.println("Variation1 time = " + BenchMark.getTimeElapsed(0));
 * runVariation2();
 * System.out.println("Variation2 time = " + BenchMark.getTimeElapsed(0));
 * System.out.println("Total time = " + BenchMark.getTimePoint(0));
 * </pre> <p>
 * Also supports "runs" via the methods with "run" in them.
 * This replaces the time series methods.
 * See runUnitTest() for a full run example.
 * A simple example is: <p>
 *      <pre>
 * BenchMark.startRun();
 * doStep1();
 * BenchMark.endStep("Step 1");
 * doStep2();
 * BenchMark.endStep("Step 2");
 * BenchMark.endRun();
 * System.out.println(BenchMark.listRun());
 *      </pre> <p>
 *      A run output example is: <p> <pre>
 * MenuFramePD.actionPerformed() - BenchMark run results are:
 * ParamStoreResource pre readParam()   0.06
 * ParamStoreResource Read File         0.05
 * ParamStoreResource Convert           0.11
 * DataEditor initSources()             0.00
 * DataEditor initViews()               7.20
 * DataEditor startSources()            0.44
 * DataEditor showViews()               1.87
 *                                    ------
 * Total                                9.73
 * </pre>
 * @author Jack Harich
 */

public class BenchMark { // java org.jcon.util.BenchMark

//---------- Private Fields ------------------------------
private static BenchMark bmark = new BenchMark();

// General
private static Time[]  times = new Time[3];
// Run related
private static long startRunTime;
private static long endRunTime;
private static Vector steps = new Vector();
private static int maxStepNameLength;

//---------- Initialization ------------------------------
static {
    //for (int i = 0; i < times.length; i++) {
    for (int i = times.length; --i >= 0; ) {
        times[i] = bmark.new Time();
    }
}
public static void main(String args[]) {
    new BenchMark().runUnitTest();
}
//---------- Public Methods ------------------------------
//----- General time series methods
/**
 * Starts the time point series by setting it to zero.
 * Subsequent getTimePoint() calls will be relative to
 * the starting time point. Returns "  0.00".
 */
public static String startTimeSeries(int series) {
    times[series].startTime = System.currentTimeMillis();
    return "  0.00";
}
/**
 * Returns currentTimeMillis() time point formatted to
 * "  9.99" for use in simple optimization timing. If
 * startTimeSeries() has been called then this returns
 * relative values.
 */
public static String getTimePoint(int series) {
    long timePoint = getTimeInternalPoint(series);
    times[series].previousPoint = timePoint;
    return formatTimePoint(timePoint);
}
/**
 * Returns the formatted elapsed time since the last call
 * to getTimePoint() or getTimeElapsed().
 */
 public static String getTimeElapsed(int series) {
    long timePoint = getTimeInternalPoint(series);
    String text = formatTimePoint(timePoint - times[series].previousPoint);
    times[series].previousPoint = timePoint;
    return text;
 }
//----- Run related methods
/**
 * Starts a benchmark run. This must be called before
 * calling the other run methods, such as endStep().
 */
public static void startRun() {
    startRunTime = System.currentTimeMillis();
    steps.removeAllElements();
    maxStepNameLength = 0;
    endRunTime = -1;
}
/**
 * Ends a named run step. This name will later be used
 * to list the results of the run. This method can be called
 * as many times as desired between startRun() and endRun().
 */
public static void endStep(String stepName) {
    Step step = bmark.new Step(stepName, System.currentTimeMillis());
    addStep(step);
}
/**
 * Ends a benchmark run. This must be called after the last
 * endStep() and before listRun(). If the step times do not
 * add up to the total run time then an "Unaccounted" step
 * is automatically added.
 */
public static void endRun() {
    endRunTime = System.currentTimeMillis();

    // Add Unaccounted step to handle "missing" time
    // after last step. This avoids false data.
    long lastStepTime = 0;
    if (! steps.isEmpty()) {
        Step lastStep = (Step)steps.lastElement();
        lastStepTime = lastStep.time;
    }
    if (endRunTime - lastStepTime != 0) {
        Step extraStep = bmark.new Step("Unaccounted",
            endRunTime);
        addStep(extraStep);
    }
}
/**
 * Returns a String containing a list of the run results.
 * The step names are on the left and step times are on
 * the right. The grand total is at the bottom.
 * <p>
 * To guarentee correct data an "Unaccounted" step will be
 * added if the last step time is different from the end
 * time. This method may be called as many times as needed,
 * though once is the norm.
 */
public static String listRun() {
    StringBuffer text = new StringBuffer();
    long previousStepTime = startRunTime;
    Step step = null;

    // Add steps to text
    for (int i = 0; i < steps.size(); i++) {
        step = (Step)steps.elementAt(i);
        addStep(text, previousStepTime, step);
        previousStepTime = step.time;
    }
    // Add total footer
    text.append(getPaddedStepName("") + "------" + "\n");
    // Add total as a step
    step = bmark.new Step("Total", endRunTime);
    addStep(text, startRunTime, step);

    return text.toString();
}
public void runUnitTest() {
    BenchMark.startRun();
    // Step1
    for (int i = 0; i < 900; i++) {
        String test = "Red " + i;
    }
    BenchMark.endStep("Step 1");
    // Step2
    for (int i = 0; i < 500; i++) {
        String test = "Blue " + i;
    }
    BenchMark.endStep("Step 2");
    // Unaccounted
    for (int i = 0; i < 300; i++) {
        String test = "Unaccounted " + i;
    }
    // Done
    BenchMark.endRun();
    System.out.println(BenchMark.listRun());
}
//---------- Private Methods -----------------------------
private static void addStep(Step step) {
    steps.addElement(step);
    if (step.name.length() > maxStepNameLength) maxStepNameLength = step.name.length();
}
// Adds a line to the text. Line format is:
// Step Name       9.99
private static void addStep(StringBuffer text,
        long previousStepTime, Step step) {

    String line = getPaddedStepName(step.name);
    line += formatTimePoint(step.time - previousStepTime);
    text.append(line + "\n");
}
private static String getPaddedStepName(String name) {
    return DataLib.rightPad(name, maxStepNameLength + 1);
}
private static long getTimeInternalPoint(int series) {
    long timePoint = System.currentTimeMillis();
    long startTime = times[series].startTime;
    if (startTime > 0) timePoint = timePoint - startTime;
    return timePoint;
}
// Returns "  0.00" format
private static String formatTimePoint(long timePoint) {
    String time = "00000" + String.valueOf(timePoint);
    time = time.substring(time.length() - 6);
    time = time.substring(0, 3) + "." + time.substring(3, 5);
    if (time.startsWith("0")) time = " " + time.substring(1);
    if (time.startsWith(" 0")) time = "  " + time.substring(2);
    return time;
}
//--- Std
private static void print(String text) {
    System.out.println("BMark" + text);
}
//========== Inner Classes ===============================
private class Time {

    long startTime     = -1;
    long previousPoint = -1;

} // End inner class
private class Step {

    String name;
    long time;

    Step(String name, long time) {
        this.name = name;
        this.time = time;
    }

} // End inner class

} // End outer class
