/* Copyright (c) 2010, Carl Burch. License information is located in the
 * com.cburch.logisim.Main source code and at www.cburch.com/logisim/. */

package com.cburch.logisim.gui.main;

import com.cburch.logisim.circuit.Simulator;
import com.cburch.logisim.circuit.SimulatorEvent;
import com.cburch.logisim.circuit.SimulatorListener;

class TickCounter implements SimulatorListener {
	private static final int QUEUE_LENGTH = 1000;
	
	private long[] queueTimes;
	private double[] queueRates;
	private int queueStart;
	private int queueSize;
	private double tickFrequency;
	
	public TickCounter() {
		queueTimes = new long[QUEUE_LENGTH];
		queueRates = new double[QUEUE_LENGTH];
		queueSize = 0;
	}

	public void clear() {
		queueSize = 0;
	}
	
	public void propagationCompleted(SimulatorEvent e) {
		Simulator sim = e.getSource();
		if (!sim.isTicking()) {
			queueSize = 0;
		}
	}

	public void simulatorStateChanged(SimulatorEvent e) {
		propagationCompleted(e);
	}

	public void tickCompleted(SimulatorEvent e) {
		Simulator sim = e.getSource();
		if (!sim.isTicking()) {
			queueSize = 0;
		} else {
			double freq = sim.getTickFrequency();
			if (freq != tickFrequency) {
				queueSize = 0;
				tickFrequency = freq;
			}
			
			int curSize = queueSize;
			int maxSize = queueTimes.length;
			int start = queueStart;
			int end;
			if (curSize < maxSize) { // new sample is added into queue
				end = start + curSize;
				if (end >= maxSize) {
					end -= maxSize;
				}
				curSize++;
				queueSize = curSize;
			} else { // new sample replaces oldest value in queue
				end = queueStart;
				if (end + 1 >= maxSize) {
					queueStart = 0;
				} else {
					queueStart = end + 1;
				}
			}
			long startTime = queueTimes[start];
			long endTime = System.currentTimeMillis();
			double rate;
			if (startTime == endTime || curSize <= 1) {
				rate = Double.MAX_VALUE;
			} else {
				rate = 1000.0 * (curSize - 1) / (endTime - startTime);
			}
			queueTimes[end] = endTime;
			queueRates[end] = rate; 
		}
	}

	public String getTickRate() {
		int size = queueSize;
		if (size <= 1) {
			return "";
		} else {
			int maxSize = queueTimes.length;
			int start = queueStart;
			int end = start + size - 1;
			if (end >= maxSize) {
				end -= maxSize;
			}
			double rate = queueRates[end];
			if (rate <= 0 || rate == Double.MAX_VALUE) {
				return "";
			} else {
				// Figure out the minimum over the previous 100 readings, and
				// base our rounding off of that. This is meant to provide some
				// stability in the rounding - we don't want the result to
				// oscillate rapidly between 990 Hz and 1 KHz - it's better for
				// it to oscillate between 990 Hz and 1005 Hz.
				int baseLen = size;
				if (baseLen > 100) baseLen = 100;
				int baseStart = end - baseLen + 1;
				double min = rate;
				if (baseStart < 0) {
					baseStart += maxSize;
					for (int i = baseStart + maxSize; i < maxSize; i++) {
						double x = queueRates[i];
						if (x < min) min = x;
					}
					for (int i = 0; i < end; i++) {
						double x = queueRates[i];
						if (x < min) min = x;
					}
				} else {
					for (int i = baseStart; i < end; i++) {
						double x = queueRates[i];
						if (x < min) min = x;
					}
				}
				if (min < 0.9 * rate) min = rate;

				if (min >= 1000.0) {
					return Strings.get("tickRateKHz",
							roundString(rate / 1000.0, min / 1000.0));
				} else {
					return Strings.get("tickRateHz", roundString(rate, min));
				}
			}
		}
	}
	
	private String roundString(double val, double min) {
		// round so we have only three significant digits
		int i = 0; // invariant: a = 10^i
		double a = 1.0; // invariant: a * bm == min, a is power of 10
		double bm = min;
		double bv = val;
		if (bm >= 1000) {
			while (bm >= 1000) {
				i++;
				a *= 10;
				bm /= 10;
				bv /= 10;
			}
		} else {
			while (bm < 100) {
				i--;
				a /= 10;
				bm *= 10;
				bv *= 10;
			}
		}

		// Examples:
		// 2.34: i = -2, a = .2, b = 234
		// 20.1: i = -1, a = .1, b = 201
		
		if (i >= 0) { // nothing after decimal point
			return "" + (int) Math.round(a * Math.round(bv));
		} else { // keep some after decimal point
			return String.format("%." + (-i) + "f", Double.valueOf(a * bv));
		}
	}
}
