Timer.timers = [];
Timer.defaultLifetime = Number.POSITIVE_INFINITY; // measured in milliseconds
Timer.listenerNotifiedMethodName = "timeout"; // name of listener method invoked when lifetime ms has passed
Timer.debug = (location.hostname.indexOf(".") == -1);

function Timer(delay, lifetime) {
	if (typeof delay != "number" && typeof lifetime != "number") {
		return;
	}
	lifetime = parseInt(lifetime, 10);
	this.lifetime = (isNaN(lifetime) || lifetime <= 0) ? Timer.defaultLifetime : lifetime; // measured in milliseconds
	delay = parseInt(delay, 10);
	this.delay = (isNaN(delay) || delay <= 0 || delay > this.lifetime) ? this.lifetime : delay; // measured in milliseconds
	this.initialDelay = this.delay; // measured in milliseconds
	this.listeners = new Array(); // array of listener objects which will be notified between each delay
	this.timerId = NaN;
	this.runningTime = 0; // measured in milliseconds
	this.index = Timer.timers.length;
	Timer.timers.push(this);
}

Timer.removeAll = function() {
	for (var i = 0, l = Timer.timers.length; i < l; i++) {
		var timer = Timer.timers[i];
		if (timer != null) {
			timer.stop();
			timer = null;
		}
	}
	Timer.timers = [];
}

Timer.prototype.toString = function() {
	return "[object Timer]: index " + this.index + "\nlifetime: " + this.lifetime + "\ndelay: " + this.delay + "\ninitial delay: " + this.initialDelay + "\nrunning time: " + this.runningTime + "\ntimerId: " + this.timerId;
}

Timer.prototype.addListeners = function(listeners) {
	if (listeners != null && listeners.constructor == Array) {
		for (var i = 0, l = listeners.length; i < l; i++) {
			this.addListener(listeners[i]);
		}
	}
}

Timer.prototype.addListener = function(listener) {
	if (listener != null && typeof listener[Timer.listenerNotifiedMethodName] == "function") {
		this.listeners.push(listener);
	}
}

Timer.prototype.setLifetime = function(lifetime) {
	if (typeof lifetime == "number" && isNaN(lifetime) && lifetime > 0) {
		this.lifetime = lifetime;
	}
}

Timer.prototype.setDelay = function(delay) {
	delay = parseInt(delay, 10);
	if (!isNaN(delay) && delay > 0 && delay <= this.lifetime) {
		this.delay = delay;
	}
}

Timer.prototype.setInitialDelay = function(initialDelay) {
	initialDelay = parseInt(initialDelay, 10);
	if (!isNaN(initialDelay) && initialDelay > 0 && initialDelay <= this.lifetime) {
		this.initialDelay = initialDelay;
	}
}

Timer.prototype.start = function() {
	if (!this.isRunning()) {
		this.setNextTimeout(this.initialDelay);
	}
}

Timer.prototype.stop = function() {
	if (!isNaN(this.timerId)) {
		window.clearTimeout(this.timerId);
		this.timerId = NaN;
	}
	this.runningTime = 0;
}

Timer.prototype.reset = function(delay, initialDelay, lifetime) {
	this.stop();
	this.setDelay(delay);
	this.setInitialDelay(initialDelay);
	this.setLifetime(lifetime);
	this.start();
}

Timer.prototype.isRunning = function() {
	return !isNaN(this.timerId);
}

Timer.prototype.restart = function(delay) {
	delay = parseInt(delay, 10);
	if (!isNaN(delay) && delay > 0) { 
		this.stop();
		window.setTimeout("Timer.timers[" + this.index + "].start();", delay);
	}
}

Timer.prototype.pause = function(pause) {
	pause = parseInt(pause, 10);
	if (!isNaN(pause) && pause > 0) {
		if (!isNaN(this.timerId)) {
			window.clearTimeout(this.timerId);
		}
		this.timerId = window.setTimeout("Timer.timers[" + this.index + "].run();", pause);
	}
}

Timer.prototype.run = function() {
	this.showStatus();
	if (this.runningTime >= this.lifetime) {
		this.stop();
		this.timeout();
		this.notifyListeners();
	} else {
		if (this.keepRunning()) {
			this.setNextTimeout(this.delay);
		} else {
			this.stop();
		}
	}
}

Timer.prototype.setNextTimeout = function(delay) {
	delay = parseInt(delay, 10);
	if (!isNaN(delay) && delay > 0) {
		this.runningTime += delay;
		this.timerId = window.setTimeout("Timer.timers[" + this.index + "].run();", delay);
	}
}

Timer.prototype.notifyListeners = function() {
	for (var i = 0, l = this.listeners.length; i < l; i++) {
		var listener = this.listeners[i];
		if (typeof listener[Timer.listenerNotifiedMethodName] == "function") {
			listener[Timer.listenerNotifiedMethodName](this);
		}
	}
}

/*	Override this method for customized behavior.
	Must return a boolean.
	True means the timer continues, false means it stops (without calling timeout and without notifying listeners).	*/
Timer.prototype.keepRunning = function() {
	// note that you can accelerate the timer like this (lifetime should be positive infinity):
	//this.delay -= 250; // proportional
	// or like this:
	//this.delay *= 0.9 // exponential
	return true;
}

// override this method for customized behavior
Timer.prototype.timeout = function() {
}

// override this method for customized behavior
Timer.prototype.showStatus = function() {
	if (Timer.debug) {
		window.status = this.runningTime + " ms " + new Date().toString();
	}
}
