package util;

import java.util.ArrayList;

/**
 * @author Krisi
 *
 * Timerklasse mit verschiedenen Zeitmessungsmöglichkeiten.
 * Für Beispiel siehe "main"
 */

public class Timer {

	public double 	compensationStartStop=0.0, 	// compensation in [ms] for starting and stopping the measurement
					compensationFSB=1.0;		// factor for measurement errors (nano time) at FSB changed at runtime
	private long startNS=0; 					// Time of measurement start
	private ArrayList<Double> times;	// saved measurement values, double, [ms]
	
	
	/** String representing an Overview of the measured values **/
	public String toStringNew(){
		float avg = ((int)getTimesSum())/1000f;
		float min = ((int)getMinimumTime())/1000f;
		float max = ((int)getMaximumTime())/1000f;
		int nrOf  = getNrOFMeasuredTimes();
		
		String ret = "Average = " + avg + " sec ["+min+" to "+max+"], in "+nrOf+" measurements.";
		
		return ret;
	}
	
	
	
	
	/** 
	 * Simple constructor:
	 * No time compensation or expected measurements.
	 * **/
	public Timer(){
		this(0);
	}
	/** Constructor for many measurements **/
	public Timer(int expectedMeasurements){
		times = new ArrayList<Double>(expectedMeasurements);
		start();		// first time is object make time
	}
	/** constructor for copying compensations and configurations **/
	public Timer(Timer t){
		this(t.times.size());
		compensationStartStop = t.compensationStartStop;
		compensationFSB       = t.compensationFSB;
	}
	
	
	/**
	 * Using the timer: Example
	 */
	public static void main2(String[] args) {
		
		Timer t = new Timer();
		System.out.println( "init time:  " + t.INIT(0) + "" );
		
		for(int i=0; i<1; i++)
			for(int j=0; j<10; j++){
				
				System.out.print(".");
				
				t.start();
				for(int x=0; x<Integer.MAX_VALUE/100; x++){}
				t.stop();
			}
		System.out.println();
		System.out.println( t );
		System.out.println();
		for(double d : t.times){
			System.out.println("Time x = " + d);
		}
	}
	
	/** String representing an Overview of the measured values **/
	public String toString(){
		StringBuffer sb = new StringBuffer(100);
		double av = getTimesAverage();
		
		sb.append( "Measurements   = " + getNrOFMeasuredTimes() 	+ "\n");
		sb.append( "Average Value  = " + av 						+ " ms \n" );
		sb.append( "Minimum        = " + getMinimumTime()				+ " ms \n" );
		sb.append( "Maximum        = " + getMaximumTime()				+ " ms \n" );
		sb.append( "Below/Above Av = " + below(av)+"/"+above(av)	+ " \n" );
		
		return sb.toString();
	}
	
	/** delete measured values **/
	public void smallReset(){
		startNS=0;
		times = new ArrayList<Double>();
	}

	/**
	 * INIT(0): Initialize the timer compensations.
	 * Needs about 1 second of time.
	 * @return String with information regarding compensations.
	 */
	public String INIT(){
		return INIT(0);
	}
	
	/** Initialize the timer compensations 
	 * 		compensationStartStop - compensation in [ms] for starting and stopping the measurement
	 *		compensationFSB		  - factor for measurement errors (nano time) at FSB changed at runtime
	 * 
	 * @param speed: -2...ultra (10++ sec), -1...slow (4++ sec) 0...normal (1+ sec), 1...fast (0.5+ sec), 2...very fast (0.25+ sec)
	 * @return String with information regarding compensations.
	 * **/
	public String INIT(int speed){
		
		compensationStartStop = getCompensationStartStop(speed);
		compensationFSB 	  = getCompensationFSB(speed);
		
		return "Start-Stop = " + compensationStartStop + "ms, FSB-Factor = " + compensationFSB;
	}
	/** 
	 * Copy timer initialization parameters.
	 * 
	 * @param t - timer to copy configuration from
	 */
	public void INIT_COPY(Timer t){
		compensationStartStop = t.compensationStartStop;
		compensationFSB       = t.compensationFSB;
	}
	
	 /** @return compensation time for start and stop (minimum) 
	  * 
	  * @param speed: -2...ultra, -1...slow, 0...normal, 1...fast, 2...very fast
	  */
	public static double getCompensationStartStop(int speed){
		
		Timer tim = new Timer();
		int limit;
		
		if(speed<=-2)
			limit = 15000;		// 15000 test runs
		else if(speed==-1)
			limit = 5000;		// 5000 test runs
		else if(speed==0)
			limit = 500;		// 500 test runs
		else if(speed==1)
			limit = 100;		// 100 test runs
		else
			limit = 25;			// 25 test runs
		
		for(int i=0; i<limit; i++){
			tim.start();
			tim.stop();
		}
		
		double compensation = tim.getMinimumTime();
		return compensation;
	}
	
	/**
	 * The nano time may be affected by an FSB changed at runtime.
	 * This is compensated with this factor. (1.0 if FSB is normal/like startupFSB)
	 * 
	 * @param speed: -2...ultra (10 sec), -1...slow (4 sec), 0...normal (1 sec), 1...fast (0.5 sec), 2...very fast (0.25 sec)
	 * @return compensation Factor for FSB difference
	 */
	public static double getCompensationFSB(int speed){
		
		long timeLimit;
		
		if(speed<=-2)
			timeLimit = 10000l * 1000l * 1000l;		//10 Sek Testzeit
		else if(speed==-1)
			timeLimit = 4000l * 1000l * 1000l;		//4 Sek Testzeit
		else if(speed==0)
			timeLimit = 1000l * 1000l * 1000l;		//1 Sek Testzeit
		else if(speed==1)
			timeLimit =  500l * 1000l * 1000l;		//0.5 Sek Testzeit
		else
			timeLimit =  250l * 1000l * 1000l;		//0.25 Sek Testzeit
			
		long startNS, startMS, stopNS, stopMS; 
		double nTime, mTime, compensation1, compensationOpt;
		boolean error=true;
		
		startMS = System.currentTimeMillis();					// start system time [ms]
		startNS = System.nanoTime();							// start system time [ns]
		
		Tools.sleep( (long)(timeLimit/(1000.0*1000.0)*0.85) );	// sleep most of the time
		timeLimit += startNS;									// calculate the total time limit [ns]
		
		while( (stopNS=System.nanoTime()) < timeLimit) {error=false;}	// full cpu usage time measurement, stop time [ns]
		stopMS = System.currentTimeMillis();					// stop system time [ms]
		
		nTime = (stopNS-startNS) / (1000.0*1000.0);				// stop system time [ns]
		mTime = (stopMS-startMS);								// stop system time [ms]
		compensation1 = mTime/nTime;							// calculate theoretical compensation
		
		// Measurement error (A time is zero or sleep was too long delayed) or FSB is normal:
		if(error || mTime==0 || nTime==0){
			System.err.println("Error in timer initialization! Using standart value instead of compensation "+compensation1);
			compensationOpt = 1.0;				// 
		}
		else if(Tools.within(1.0, 0.000002, compensation1)){
			compensationOpt = 1.0;				// normally unchanged FSB. No compensation - with tolerance.
		}
		else{
			compensationOpt = compensation1;	// use FSB compensation
		}
		//Out.pl("ms="+ms+" \t ns="+ns+" \t compensation= "+compensation1+" / "+compensationOpt);
		
		return compensationOpt;
	}
	
	/** starts the timer **/
	public void start(){
		startNS =System.nanoTime();
	}
	
	/** @return time since last 'start()' in milliseconds. Save it in list.  **/
	public double stop(){
		
		double time = getTime();	// ms
		times.add(time);
		
		return time;
	}
	
//	/** just returns actual time [ms] (since start), do not save value. see getTime() 
//	 * **/
//	public double stop2(){
//		return getTime();
//	}
	
	/** just returns actual time [ms] (since start), do not save value 
	 * @return time in ms
	 * **/
	public double getTime(){
		
		long stopNS = System.nanoTime();					// Stop time [ns]
		double time = (stopNS-startNS)/(1000.0*1000.0);		// Time in Double [ms]
		time = (time-compensationStartStop) * compensationFSB;							// Compensations
		return time;
	}
	
	public double getTimeSec(){
		return getTime()/1000.0;
	}
	
	/** @return minimum measured time **/
	public double getMinimumTime(){
		
		double min;
		if(times.size()>0)
			min = times.get(0);
		else
			return 0;
			
		for(double d : times){
			if(d<min)
				min=d;
		}
		return min;
	}
	
	/** @return maximum measured time [ms] **/
	public double getMaximumTime(){
		
		double max;
		if(times.size()>0)
			max = times.get(0);
		else
			return 0;
			
		for(double d : times){
			if(d>max)
				max=d;
		}
		
		return max;
	}
	
	/** @return sum of all measured times [ms] **/
	public double getTimesSum(){
		
		double sum = 0;
		
		for(double d : times){
			sum += d;
		}
		return sum;
	}
	
	/** @return average time [ms] **/
	public double getTimesAverage(){
		
		double sum  = getTimesSum();
		int    size = times.size();
		double res  = sum/size;
		return res;
	}
	
	/** @return number of values above the specified value **/
	public int above(double val){
		int count=0;
		for(double d : times){
			if(d>val)
				count++;
		}
		return count;
	}
	/** @return number of values below the specified value **/
	public int below(double val){
		int count=0;
		for(double d : times){
			if(d<val)
				count++;
		}
		return count;
	}
	
	/** @return measured time at index 'i' in [ms]**/
	public double getMeasuredTime(int i){
		return times.get(i);
	}
	/** @return last measured time [ms]**/
	public double getMeasuredTime(){
		return times.get(times.size()-1);
	}
	
	/** @return number of measured times **/
	public int getNrOFMeasuredTimes(){
		return times.size();
	}
	
	/** @return the list of measured times [ms] **/
	public ArrayList<Double> getTimesList(){
		return times;
	}
	

}
