package com.appdivision.manager.idlemanager
{
    import com.appdivision.manager.idlemanager.event.IdleEvent;
    
    import flash.events.EventDispatcher;
    import flash.events.KeyboardEvent;
    import flash.events.MouseEvent;
    
    import mx.collections.ArrayCollection;
    import mx.events.FlexEvent;
    import mx.managers.ISystemManager;
    
    [Event(name="unidle", type="com.appdivision.manager.idlemanager.event.IdleEvent")]
    [Mixin]
    public class IdleManager extends EventDispatcher
    {
        private static var _instance:IdleManager;
        private static var systemManager:ISystemManager;
        private var idleCollection:ArrayCollection;
        private var timeStamp:Number = 0;
        private var idleMilliseconds:Number = 0;
        private var idle:Boolean = false;
        
        public function IdleManager(singletonEnforcer:SingletonEnforcer)
        {
            if (singletonEnforcer == null) {
                    throw new Error("IdleManager is a singleton class, use getInstance() instead");
            }
            _instance = this;
            idleCollection = new ArrayCollection();
            addEventListeners();
        }
        
        public static function getInstance():IdleManager{
            if (_instance == null)_instance = new IdleManager(new SingletonEnforcer());
            return _instance;
        }
        
        /**
         * called due to the fact that i've used the [Mixin] metatag
         * makes sure event listeners have been added to the system manager even if the user doesnt
         * @param systemManager
         * 
         */        
        public static function init (systemManager:ISystemManager):void{
            IdleManager.systemManager = systemManager;            
        }
        
        private function addEventListeners():void{
            systemManager.addEventListener(FlexEvent.IDLE, onIdle);
            systemManager.addEventListener(MouseEvent.MOUSE_MOVE, onMouse);
            systemManager.addEventListener(MouseEvent.MOUSE_DOWN, onMouse);
            systemManager.addEventListener(KeyboardEvent.KEY_DOWN, onKeyboard, true);
        }
        
        private function onMouse(e:MouseEvent):void{
            if(idle)dispatchEvent(new IdleEvent(IdleEvent.UNIDLE, 0));
            idle = false;
        }
        
        private function onKeyboard(e:KeyboardEvent):void{
            if(idle)dispatchEvent(new IdleEvent(IdleEvent.UNIDLE, 0));
            idle = false;
        }
        
        /**
         * Happens when the application begins idling
         * @param e
         * 
         */        
        private function onIdle(e:FlexEvent):void{
            idle = true;
            idleMilliseconds += 100;
            
            //gets a new timeStamp
            var d:Date = new Date();
            var curr:Number = Date.UTC(d.getFullYear(), d.month, d.date, d.hours, d.minutes, d.seconds, d.milliseconds);
            
            //if  some time has passed by we need to reset the idle count
            if(curr > timeStamp + 200)idleMilliseconds = 1000;
                //Application.application.tracer.text += curr - timeStamp + "\n";
            
            //resets the timeStamp
            timeStamp = curr;
            
            //loops through idle objects in collection
            for(var i:Number = 0; i < idleCollection.length; i ++){
                //retrieves object
                var obj:Object = idleCollection.getItemAt(i);
                //if the remaineder of the idle seconds and the recurring milliseconds is 0 
                if(idleMilliseconds % obj.milliseconds == 0){
                    //if this is to fire one time
                    if(obj.isOneTime){
                        //build the event
                        var evt:IdleEvent = new IdleEvent(IdleEvent.ONE_TIME_IDLE, idleMilliseconds);
                        evt.data = obj.data;
                        //dispatch the event
                        obj.listener( evt );
                        //remove it so it wont fire again
                        removeIdleListener(obj.milliseconds, obj.listener);
                    }else
                    //if this is to fire one time every time it goes idle
                    if(obj.isFirstTime){
                        if(obj.milliseconds == idleMilliseconds){
                            //build the event
                            var evt:IdleEvent = new IdleEvent(IdleEvent.FIRST_TIME_IDLE, idleMilliseconds);
                            evt.data = obj.data;
                            //dispatch the event
                            obj.listener( evt );
                        }        
                    }else
                    //this is a recurring idle
                    {
                        //build the event
                        var evt:IdleEvent = new IdleEvent(IdleEvent.RECURRING_IDLE, idleMilliseconds);
                        evt.data = obj.data;
                        //dispatch the event
                        obj.listener( evt );

                    }
                } 
                
            }
        }
        
        private function checkForExistence(milliseconds:Number, listener:Function, isOneTime:Boolean = false, isFirstTime:Boolean = false):Boolean{
            for(var i:Number = 0; i < idleCollection.length; i ++){
                var obj:Object = idleCollection.getItemAt(i);
                if(obj.milliseconds == milliseconds && obj.listener == listener && obj.isOneTime == isOneTime && obj.isFirstTime == isFirstTime){
                    return true;
                }
            }
            return false;
        }
        
        /**
         * Removes an idle listener
         * @param milliseconds
         * @param listener
         * 
         */        
        public function removeIdleListener(milliseconds:Number, listener:Function):void{
            for(var i:Number = 0; i < idleCollection.length; i ++){
                var obj:Object = idleCollection.getItemAt(i);
                if(obj.milliseconds == milliseconds && obj.listener == listener){
                    idleCollection.removeItemAt(i);
                }
            }
        }
        
        /**
         * Will add an event that will be fired recurringly every N number of millseconds when an app is idle
         * @param milliseconds Will be rounded to the nearest 100 millisecond
         * @param listener listener function to be called with event
         * @param data optional data argument to be passed with event
         * 
         */                 
        public function addRecurringIdleEventListener(milliseconds:Number, listener:Function, data:Object = null):void{
            milliseconds = milliseconds < 100 ? 100 : Math.round(milliseconds/100) * 100;
            if(checkForExistence(milliseconds, listener, false, false))throw new Error("A listener for this exact time and function have already been added");
            var obj:Object = new Object();
            obj.milliseconds = milliseconds;
            obj.listener = listener;
            obj.data = data;
            idleCollection.addItem(obj);
        } 
        
        /**
         * Will add an event that will be fired one time the next time the application goes idle and reaches that number of milliseconds - then deletes it
         * @param milliseconds Will be rounded to the nearest 100 millisecond
         * @param listener listener function to be called with event
         * @param data optional data argument to be passed with event
         * 
         */        
        public function addOneTimeIdleEventListener(milliseconds:Number, listener:Function, data:Object = null):void{
            milliseconds = milliseconds < 100 ? 100 : Math.round(milliseconds/100) * 100;
            if(checkForExistence(milliseconds, listener, true, false))throw new Error("A listener for this exact time and function have already been added");
            var obj:Object = new Object();
            obj.isOneTime = true;
            obj.milliseconds = milliseconds;
            obj.listener = listener;
            obj.data = data;
            idleCollection.addItem(obj);
        }
        
        /**
         * Will add an event that will be fired one time every time the application goes idle and reaches that number of milliseconds
         * @param milliseconds Will be rounded to the nearest 100 millisecond
         * @param listener listener function to be called with event
         * @param data optional data argument to be passed with event
         * 
         */        
        public function addFirstTimeIdleEventListener(milliseconds:Number, listener:Function, data:Object = null):void{
            milliseconds = milliseconds < 100 ? 100 : Math.round(milliseconds/100) * 100;
            if(checkForExistence(milliseconds, listener, false, true))throw new Error("A listener for this exact time and function have already been added");
            var obj:Object = new Object();
            obj.isFirstTime = true;
            obj.milliseconds = milliseconds;
            obj.listener = listener;
            obj.data = data;
            idleCollection.addItem(obj);
        }
    }
}

 /**
 * SingletonEnforcer enables access of a Singleton class to be 
 * restricted to internal
 */
class SingletonEnforcer {}