/* Javascript Object Oriented Timer, version 1.0

 * --------------------------------------------------------

 * Copyright (C) 2008 zp bappi | zpbappi (at) gmail (dot) com

 *

 * Details and latest version at:

 * http://abcoder.com/javascript/core_javascript/javascript_timer

 *

 *

 * This script is distributed under the GNU Lesser General Public License (Version 3, 29 June 2007 or later).

 *

 * ================================ IMPORTANT ===========================================

 * This library is free software; you can redistribute it and/or modify it under the terms

 * of the GNU Lesser General Public License as published by the Free Software Foundation; 

 * either version 2.1 of the License, or (at your option) any later version.



 * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; 

 * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 

 * See the GNU Lesser General Public License for more details.

 * =======================================================================================

 *

 * Read the entire license text here: http://www.gnu.org/licenses/lgpl.html

 */

 

 

/* just declaring the class and pointing the real constructor */

var Timer = function(millis, callback){

	return this._init(millis, callback);

}



Timer.prototype = {

	//*******************************************************

	//Private Block starts here *****************************

	//

	//funcations and variables listed bellow

	//should not be accessed from outside

	//*******************************************************

	VERSION: 1.0,

	

	/* constructor for Timer object */

	_init: function(millis, callback){

		

		/* private variables */

		this._interval = 1000;

		this._timer = null;

		this._cbs = [];

		this._multipliers = [];

		this._tickCounts = [];

		this._canRun = [];

		this._stoppedThreads = 0;

		this._runOnce = false;

		this._startedAt = -1;

		this._pausedAt = -1;

		

		if(typeof(millis)=='number') this._interval = millis;

		

		this.addCallback(callback);

		

		return this;

	},

	

	

	/* some preset operation, called from start() */

	_preset: function(){

		this._stoppedThreads = 0;

		this._startedAt = -1;

		this._pausedAt = -1;

		for(var i=0; i<this._cbs.length; i++){

			this._canRun[i] = true;

			this._tickCounts[i] = 0;

		}

	},

	

	

	/* private clock pulse handler */

	/* The kernel */

	// this method actually makes things work

	_ticks: function(initInterval){

		

		/* process callback here */

		var me = this;

		for(var i=0; i<this._cbs.length; i++){

			if(typeof(this._cbs[i])=='function' && this._canRun[i]){

				this._tickCounts[i]++;

				if(this._tickCounts[i] == this._multipliers[i]){

					this._tickCounts[i] = 0;

					

					if(this.runOnce()){

						//in runOnce(true) mode, none is allowed to run more once

						this._canRun[i] = false;

						

						//count number of callback operations finished

						this._stoppedThreads++;

					}

					

					/* actually executing callback functions here */

					// trying to achieve parallelism for each function

					window.setTimeout(me._cbs[i], 0);

				}

			}

		}

		

		/* detect ending pulse for runOnce(true) mode */

		//all callback functions must be called exactly once when running in runOnce(true) mode

		if(this.runOnce() && this._stoppedThreads == this._cbs.length)

			this.stop();

		

		

		/* resume logic */

		if(typeof(initInterval)=='number'){

			//when resuming, after the first pulse, 

			//start clock with assigned interval and without presetting

			this.stop().start(null, true);

		}

	},

	

	//**********************************************************************

	// Private block ends here *********************************************

	//**********************************************************************

	// bellow is public block, i.e., listed function are available to use.

	// further detail about functions can be found just above the function 

	//**********************************************************************

	

	

	

	

	/* get/set mode of running */

	//Parameters:

	//	isRunOnce(optional)-> 	accepts true or false for setting the mode. default is false.

	//							if false, then timer runs untill each callback executes exactly once.

	//							if true, then timer runs forever until stop() or pause() is called.

	//							also, calling runOnce() without parameter returns the current mode of timer (true or false)

	//Returns: current Timer object(this) or boolean specifying current mode.

	

	runOnce: function(isRunOnce){

		if(typeof(isRunOnce)=='undefined') return this._runOnce;

		else if(typeof(isRunOnce)=='boolean') this._runOnce = isRunOnce;

		else alert("Invalid argument for runOnce(...).\n\nUsage: runOnce(true | false) /*Default value: false*/\nor, runOnce() to get status");

		return this;

	},

	

	

	/* get/set timer clock interval in milli sec */

	//Parameters:

	//	millis(optional)-> 	accepts integer number only. default is 1000 (1 sec).

	//						also, calling runOnce() without parameter returns the current interval of timer in milli sec

	//						using any other variation will have no effect, you may try...

	//Returns: current Timer object(this) or integer specifying current interval of the timer

	

	interval: function(millis){

		if(typeof(millis)=='undefined') return this._interval;

		else if(typeof(millis)=='number') this._interval = Math.floor(millis);

		return this;

	},

	

	

	/* stops the timer */

	//Returns: current Timer object(this)

	//CAUTION: DO NOT pass any patameter when using stop. this may have the same destructive effect (or a little less) described for start method.

	

	stop: function(isPausing){

		if(this._timer){

			if(!isPausing) this._pausedAt = -1;

			try{

				window.clearInterval(this._timer);

			}

			catch(ex){

				//i dont know if this line will be executed ever...

			}

			this._timer = null;

		}

		return this;

	},

	

	

	/* checks if timer is stopped */

	//Returns:	true if timer is stopped, false otherwise

	//Note:		paused and stopped are different states

	

	isStopped: function(){

		return ((this._timer == null) && !this.isPaused());

	},

	

	

	/* starts the timer */

	//DO NOT pass the parameters when using the timer. these parameters are for internal use only.

	//just use start(). this works fine :)

	//CAUTION:	passing the parameters may cause you diarrhoea or reveal your secret credentials in

	// 			world wide web or have significant desructive effect on your browser!

	//			the outcome is unpredictable in nature.

	//Returns:  current Timer object(this)

	

	start: function(_initialInterval, _withoutPreset){

		//when timer is paused, calling start behaves same as calling resume

		//but i do not recomment it

		//use resume when timer is paused

		if(this.isPaused())

			return this.resume();

		

		//prevent unnecessary calls to start

		if(!this.isStopped())

			return this;

		

		//when resuming, after one pulse, start is called to restore default default attitude of the timer.

		//hence, preset is not to be called

		if(!_withoutPreset)

			this._preset();

		

		var tmpInterval = this._interval;



		

		//when resuming, before the very first pulse,

		//start is called from resume with calculated interval

		//to behave like actually what resume means.

		//in all other cases, it is undefined or null

		if(typeof(_initialInterval)=='number') tmpInterval = _initialInterval;

		

		//what is this?

		//this is me.

		var me = this;

		

		

		//initializing the timer

		//looks familiar, eh!

		this._timer = window.setInterval(function(){me._ticks(_initialInterval);}, tmpInterval);



		//keeps track when the timer starts

		this._startedAt = (new Date()).getTime();

		//needed when resume() then pause() then again resume() is called
		//just track back one step before
		this._startedAt -= (this._interval - tmpInterval);

		return this;

	},

	

	

	

	/* pauses the timer, i.e., freezes execution */

	//it actually works like freezing the timer, dont be fooled by the code.

	//Returns:  current Timer object(this)

	

	pause: function(){

		if(this._timer){

			this._pausedAt = (new Date()).getTime();

			this.stop(true);

		}

		return this;

	},

	

	

	/* checks if timer is paused */

	//Returns:	true if timer is paused, false otherwise.

	//Note:		paused and stopped are different stages

	

	isPaused: function(){

		return (this._pausedAt >= 0);	

	},

	

	

	/* resumes the timer from paused state */

	//if timer is paused, it is resumed from the state it was (before pause)

	//if timer was not paused, it has no effect

	//Returns:	current Timer object(this)

	

	resume: function(){

		if(this.isPaused()){

			var tempInterval = this._interval - ((this._pausedAt - this._startedAt)%this._interval);

			this._pausedAt = -1;

			this.start(tempInterval, true);

		}

		return this;

	},

	

	

	/* restarts the timer */

	//just a shortcut for stop() and then start()

	//Returns:	current Timer object(this)

	restart: function(){

		return this.stop().start();

	},

	

	

	/* adds a callback function to be called */

	//Parameters:

	//	callback(mandatory)-> 	accepts only a function to be called at each Nth pulse of timer clock

	//							if not a function, the call to this function has no effect.

	//	N(optional)->			accept only integer value (takes floor if floating number is passed). default value is 1.

	//							calls the callback function at each Nth pulsh of the timer clock.

	//Returns: current Timer object(this)

	

	addCallback: function(callback, N){

		if(typeof(callback)=='function'){

			this._cbs.push(callback);

			if(typeof(N)=='number'){

				N = Math.floor(N)

				this._multipliers.push((N < 1 ? 1 : N));

			}

			else

				this._multipliers.push(1);

			

			this._tickCounts.push(0);

			this._canRun.push(true);

		}

		return this;

	},

	

	

	/* removes all callback functions added previously */

	//Returns: current Timer object(this)

	

	clearCallbacks: function(){

		this._cbs.length = 0;

		this._multipliers.length = 0;

		this._canRun.length = 0;

		this._tickCounts.length = 0;

		this._stoppedThreads = 0;

		return this;

	}

};
