package edu.uprm.walsaip.vte.core.util;

import java.text.NumberFormat;

import edu.uprm.walsaip.vte.core.messagebus.Message;
import edu.uprm.walsaip.vte.core.messagebus.MessageBus;
import edu.uprm.walsaip.vte.core.messagebus.MessageListener;
import edu.uprm.walsaip.vte.core.model.TerrainModel;


/**
 * This behavior calculates the frame rate and average frame rate of a Java3D
 * application. The behavior sets itself up to wakeup every time a new frame is
 * rendered.
 * 
 * <p>
 * The HotSpot(tm) compiler performs some initial optimizations before running
 * at optimal speed. Frame rates measured during this warmup period will be
 * inaccurate and not indicative of the true performance of the the application.
 * Therefore, before beginning the frame rate computation, the frame counter
 * waits for a fixed time period to allow the HotSpot(tm) compiler to
 * stablilize.
 * 
 * <p>
 * To avoid computing the frame rate too frequently (which would also hamper
 * rendering performance), the frame counter only computes the frame rate at
 * fixed time intervals. The default sampling duration is 10 seconds. After
 * waiting for the warmup period, the frame counter needs to calibrate itself.
 * It computes the number of frames rendered during the sampling period. After
 * doing this calibration, the frame counter reports the frame rate after these
 * many frames are rendered. It also reports the average frame rate after a
 * fixed number of sampling intervals (the default is 5).
 * 
 * <p>
 * The frame counter can be set up to run for a fixed number of sampling
 * intervals or to run indefinitely. The default is to run indefinitely.
 */

public class StatCounter implements MessageListener {
	
  private static StatCounter instance = new StatCounter();
	
  // Do calibration for these many millisec
  private static final long testduration = 1000;

  // Report frame rate after every sampleduration milliseconds
  private static final long sampleduration = 3000;

  
  private boolean enabled = false;
  // Flag to indicate that it is time to (re)calibrate
  private boolean doCalibration = true;

  // Flag to indicate the counter has started
  private boolean startup = true;

  // Wait for HotSpot compiler to perform optimizations
  private boolean warmup = true;

  // Time to wait for HotSpot compiler to stabilize (in milliseconds)
  private long warmupTime = 5000;

  // Counter for number of frames rendered
  private int numframes = 0;
  private int numtris = 0;
  // Report frame rate after maxframe number of frames have been rendered
  private int maxframes = 1;

  // Variables to keep track of elapsed time
  private long startuptime = 0;

  private long currtime = 0;

  private long lasttime = 0;

  private long deltatime;


  // No. of sampling intervals run for so far
  private long numLoops = 0;

  // Total number of frames rendered so far
  private int sumFrames = 0;
  //Total number of triangles rendered so far
  
  private int sumTris = 0; 
  // Total time since last reporting of average frame rate
  private long sumTimes = 0;

  // Counts no. of sampling intervals
  private int loop = 0;
  private double avgFps = 0.0;
  private double ravgFps = 0.0;
  private double avgTps = 0.0;
  private double ravgTps = 0.0;
  // Average frame rate is reported after loopCount number of sampling intervals
  private int loopCount = 5;

  private double sumFps = 0.0;
  private double sumTps = 0.0;
  private int totalMeasures = 0;

  private String filename = null;
  private NumberFormat nf = null;
  // int index = 0;
  protected StatCounter() {
   
    nf = NumberFormat.getNumberInstance();
    MessageBus.addListener(this, TerrainModel.ID);
  }

  // Called to init the behavior
 public static StatCounter getInstance() {
	 return instance;
 }

  // Called every time the behavior is activated
  public void count(int triangles, int frames) {
    // Apply calibration algorithm to determine number of frames to
    // wait before computing frames per second.
    // sampleduration = 10000 -> to run test, pass for 10 seconds.

	if (!enabled)
		return;
	  
  	if (doCalibration) { // start calibration
      		if (startup) {
        		// Record time at which the behavior was first invoked
        		startuptime = System.currentTimeMillis();
        		startup = false;
        		System.out.print("FPSCounter warming up...");
      		} 
      		else if (warmup) { // Wait for the system to stabilize.
        		//System.out.print("\rFPSCounter warming up..."+ symbol[(index++) % symbol.length]);
        		currtime = System.currentTimeMillis();
        		deltatime = currtime - startuptime;
        		if (deltatime > warmupTime) {
          			// Done waiting for warmup
          			warmup = false;
          			lasttime = System.currentTimeMillis();
          			System.out.println("Done\n");
          			System.out.println(filename+":");
        		}
      		} 
      		else {
       			numframes += frames;
       		
        		// Wait till at least maxframe no. of frames have been rendered
        		if (numframes >= maxframes) {
          			currtime = System.currentTimeMillis();
          			deltatime = currtime - lasttime;
          			// Do the calibration for testduration no. of millisecs
          			if (deltatime > testduration) {
           				// Compute total no. of frames rendered so far in the
            				// current sampling duration
            				maxframes = (int) Math.ceil((double) numframes* ((double) sampleduration / (double) deltatime));

           				 // Done with calibration
            				doCalibration = false;
            				// reset the value for the measurement
            				numframes = 0;
            				lasttime = System.currentTimeMillis();
          			} else {
            				// Need to run the calibration routine for some more
            				// time. Increase the no. of frames to be rendered
            				maxframes *= 2;
          			}
        		}
      		}
	} 
	else { // do the measurement
      		numframes += frames;
      		numtris += triangles;
      		if (numframes >= maxframes) {
        		currtime = System.currentTimeMillis();
        		deltatime = currtime - lasttime;
        		// time is in millisec, so multiply by 1000 to get frames/sec
        		double fps = (double) numframes / ((double) deltatime / 1000.0);
        		double tps = (double) numtris / ((double) deltatime / 1000.0);	
        		//System.out.println("Frame Rate : \n\tNo. of frames : "+ numframes + "\n\tTime : "+ ((double) deltatime / 1000.0) + " sec."  + "\n\tFrames/sec : " + nf.format(fps));
        		//System.out.println("Triangle Rate : \n\tNo. of triangles : "+ numtris + "\n\tTime : "+ ((double) deltatime / 1000.0) + " sec."  + "\n\tTriangles/sec : " + nf.format(tps));
		        // Calculate average frame rate
        		sumFrames += numframes;
        		sumTris	+= numtris;
        		sumTimes += deltatime;
        		sumFps += fps;
        		sumTps += tps;
        		loop++;
        		if (loop >= loopCount) {
        			totalMeasures++;
        			
          			avgFps = (double) sumFrames * 1000.0 / (double) sumTimes;
			        ravgFps = sumFps / (double) loopCount;
          			
			        avgTps = (double) sumTris * 1000.0 / (double) sumTimes;
			        ravgTps = sumTps / (double) loopCount;
          			
			        System.out.println("Measurement # "+(totalMeasures) +"\nAggregate frame rate "+ nf.format(avgFps) + " frames/sec\n"+ 
			        				   "Average frame rate "  + nf.format(ravgFps) + " frames/sec\n"+
          			                   "Aggregate triangle rate "+ nf.format(avgTps) + " triangles/sec\n"+
          			                   "Average triangle rate "  + nf.format(ravgTps) + " triangles/sec\n");
          			
          			numLoops++;
//					if (finiteLoop && numLoops >= maxLoops) {
//            				System.out.println("************** The End **************\n");
//            				//setEnable(false);
//          		}
          			loop = 0;
          			sumFps = 0;
          			sumTps = 0;
        		}
        		numframes = 0;
        		numtris = 0;
        		lasttime = System.currentTimeMillis();
        
      		}
    	}
    // Set the trigger for the behavior
    //wakeupOn(FPSwakeup);
  }

  /**
   * The frame counter waits for some time before computing the frame rate.
   * This allows the HotSpot compiler to perform initial optimizations. The
   * amount of time to wait for is set by this method. The default is 20000
   * (20 sec)
   * 
   * @param Amount
   *            of time to wait for before computing frame rate (specified in
   *            milliseconds)
   */
  public void setWarmupTime(long wt) {
    warmupTime = wt;
  }

  /**
   * Sets the number of sampling intervals to wait for before computing the
   * average frame rate. The default is 5.
   * 
   * @param No.
   *            of sampling intervals over which to compute frame rate. A
   *            value of 0 implies the average frame rate is computed over one
   *            sampling interval
   */
  public void setLoopCount(int lc) {
    loopCount = lc;
  }


private void reset() {
	  doCalibration = true;
	  startup = true;
	  warmup = true;
	  numframes = 0;
	  numtris = 0;
	  maxframes = 1;
	  startuptime = 0;
	  currtime = 0;
	  lasttime = 0;
	  numLoops = 0;
	  sumFrames = 0;
	  sumTris = 0; 
	  sumTimes = 0;
	  loop = 0;
	  avgFps = 0.0;
	  ravgFps = 0.0;
	  avgTps = 0.0;
	  ravgTps = 0.0;
	  loopCount = 5;	
	  sumFps = 0.0;
	  sumTps = 0.0;
	  totalMeasures = 0;
}

	public void processEvent(Message<?> m) {
		String name = m.getName();
		Object value = m.getValue();

		if (name.equals(TerrainModel.INIT)) {
			filename = String.class.cast(value);
			reset();
			enabled = true;
		}
		

	}


}