/* 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;
        }
};
