    package flare.animate
    {
        import flash.display.Shape;
        import flash.events.Event;
        import flash.events.TimerEvent;
        import flash.utils.Timer;

        /**
         * Scheduler that oversees animation and time-based processing. Uses an
         * internal timer to regularly invoke the current set of scheduled
         * objects. Typically, interaction with the scheduler is automatically
         * handled by Transition classes. However, custom implmentations of
         * the ISchedulable interface will need to be scheduled. Use the
         * <tt>Scheduler.instance</tt> property, and not the constructor, to get
         * a reference to the active scheduler.
         *
         * <p>By default, the Scheduler issues updates to all scheduled items each
         * time the Flash Player advances to the next frame, as reported by the
         * <code>Event.ENTER_FRAME</code> event. To instead set the update interval
         * manually, see the <code>timerInterval</code> property.</p>
         */
        public class Scheduler
        {
            private static const _instance:Scheduler = new Scheduler(_Lock);
            /** The default Scheduler instance. */
            public static function get instance():Scheduler { return _instance; }

            private var _scheduled:Array; // list of all currently scheduled items
            private var _ids:Object;      // map of all named items
            private var _timer:Timer;     // timer for interval-based scheduling
            private var _obj:Shape;       // shape for getting ENTER_FRAME events

            /**
             * Sets the timer interval (in milliseconds) at which the scheduler
             * should process events. If this value is greater than zero, a
             * <code>Timer</code> instance will be used to trigger scheduler
             * updates at the given interval. If this value is less than or equal
             * to zero (the default), scheduler updates will be issued with each
             * time the Flash Player advances to the next frame according to the
             * <code>Event.ENTER_FRAME</code> event.
             */
            public function get timerInterval():Number { return _timer.delay; }
            public function set timerInterval(t:Number):void {
                pause(); _timer.delay = (t>0 ? t : 0); play();
            }

            /**
             * Creates a new Scheduler--this constructor should be not used;
             * instead use the <code>instance</code> property.
             * @param lock a lock object to emulate a private constructor
             */
            public function Scheduler(lock:Class) {
                if (lock == _Lock) {
                    _scheduled = [];
                    _ids = {};
                    _obj = new Shape();
                    _timer = new Timer(0);
                    _timer.addEventListener(TimerEvent.TIMER, tick);
                } else {
                    throw new Error("Invalid constructor. Use Scheduler.instance.");
                }
            }

            /**
             * Plays the scheduler, allowing it to process events.
             */
            private function play():void
            {
                if (timerInterval <= 0) {
                    if (!_obj.hasEventListener(Event.ENTER_FRAME))
                        _obj.addEventListener(Event.ENTER_FRAME, tick);
                } else if (!_timer.running) {
                    _timer.start();
                }
            }

            /**
             * Pauses the scheduler, so that events are not processed.
             */
            private function pause():void
            {
                if (timerInterval <= 0) {
                    _obj.removeEventListener(Event.ENTER_FRAME, tick);
                } else {
                    _timer.stop();
                }
            }

            /**
             * Adds an object to the scheduling list.
             * @param item a schedulable object to add
             */
            public function add(item:ISchedulable) : void
            {
                if (item.id && _ids[item.id] != item) {
                    cancel(item.id);
                    _ids[item.id] = item;
                }
                _scheduled.push(item);
                play();
            }

            /**
             * Removes an object from the scheduling list.
             * @param item the object to remove
             * @return true if the object was found and removed, false otherwise
             */
            public function remove(item:ISchedulable) : Boolean
            {
                var idx:int = _scheduled.indexOf(item);
                if (idx >= 0) {
                    _scheduled.splice(idx,1);
                    if (item.id && _ids[item.id] == item) {
                        if (_scheduled.indexOf(item) < 0)
                            delete _ids[item.id];
                    }
                }
                return (idx >= 0);
            }

            /**
             * Indicates if an object with the given id is currently in the
             * scheduler queue.
             * @param id the id to check for
             * @return true if an object with that id is currently scheduled,
             *  false otherwise
             */
            public function isScheduled(id:String) : Boolean
            {
                return _ids[id] != undefined;
            }

            /**
             * Looks up the scheduled object indicated by the given id, if any.
             * @param id the id to lookup
             * @return the scheduled object with matching id, of null if none
             */
            public function lookup(id:String) : ISchedulable
            {
                return id==null ? null : _ids[id];
            }

            /**
             * Cancels any scheduled object with a matching id.
             * @param id the id to cancel
             * @return true if an object was found and cancelled, false otherwise
             */
            public function cancel(id:String) : Boolean
            {
                var s:ISchedulable = _ids[id];
                if (s != null) {
                    remove(s);
                    s.cancelled();
                    return true;
                } else {
                    return false;
                }
            }

            /**
             * Frame/timer callback that invokes each scheduled object.
             * @param event the event that triggered the callback
             */
            public function tick(event:Event) : void
            {
                // all events will see the same timestamp
                var time:Number = new Date().time;

                for each (var s:ISchedulable in _scheduled) {
                    if (s.evaluate(time))
                        remove(s);
                }
                if (_scheduled.length == 0) {
                    pause();
                }
            }

        } // end of class Scheduler
    }

    // scheduler lock class to enforce singleton pattern
    class _Lock { }