// BEGIN FLOCK GPL
//
// Copyright Flock Inc. 2005-2009
// http://flock.com
//
// This file may be used under the terms of the
// GNU General Public License Version 2 or later (the "GPL"),
// http://www.gnu.org/licenses/gpl.html
//
// Software distributed under the License is distributed on an "AS IS" basis,
// WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
// for the specific language governing rights and limitations under the
// License.
//
// END FLOCK GPL

const CC = Components.classes;
const CI = Components.interfaces;
const CR = Components.results;

var EXPORTED_SYMBOLS = ["FlockScheduler"];

/**
 * FlockScheduler.schedule() - A cheap scheduler for javascript tasks.
 *
 * @param object[] aTimers (in) (optional)
 *      If provided, any timers created will be stored in this array
 *      so the caller can cancel them early.
 * @param float aSlice (in)
 *      Maximum timeslice in seconds. 0.5 or less is a good value.
 * @param float aPercent (in)
 *      Percent of wall-clock time that should be spent executing the task.
 *      For example, 20 = one fifth of the time.
 * @param function aTask (in)
 *      A generator taking a function that evaluates to true if it should
 *      yield, like this:
 *
 *        function task(should_yield) {
 *          while (some_condition) {
 *            do some work...
 *            if (should_yield()) {
 *              yield;
 *            }
 *          }
 *        }
 *
 * @return nothing
 *
 * NOTE: should_yield() is fairly cheap but in a tight inner loop it might
 *       make sense to call it every N cycles
 */
var FlockScheduler = {
  schedule: function schedule(aTimers, aSlice, aPercent, aTask) {

    // convert percent to ratio
    var ratio = aPercent / 100.0;

    // convert slice to milliseconds
    var slice = aSlice * 1000.0;

    var wall_start = Date.now();
    var process_time = 0;
    var process_start = 0;

    // A function the task can use to decide whether to yield its timeslice.
    function should_yield() {
      var now = Date.now();
      var so_far = now - process_start;
      var wall_time = now - wall_start;
      return (so_far >= slice) ||
             ((wall_time * ratio) < (process_time + so_far));
    }

    // A generator is (among other things) a function that calls yield.
    // To resume execution at the point of suspension by yield, call the
    // generator's .next() method.
    // See https://bugzilla.mozilla.org/show_bug.cgi?id=326466
    var generator = aTask(should_yield);
    var timer = CC["@mozilla.org/timer;1"].createInstance(CI.nsITimer);

    var callback = {
      notify: function FlockScheduler_callback_notify(aTimer) {
        var wall_time = Date.now() - wall_start;

        if (process_time > (wall_time * ratio)) {
          // We've spent more time than we're supposed to; skip this cycle.
          return;
        }

        process_start = Date.now();
        try {
          // Resume execution where the function yield'ed.
          generator.next();
        } catch (err if err instanceof StopIteration) {
          // The task is complete; stop timer and destroy it.
          if (aTimers) {
            FlockScheduler.cancel(aTimers, aTimers.indexOf(aTimer));
          }
          else {
            aTimer.cancel();
          }
        }
        process_time += (Date.now() - process_start);
      }
    };

    timer.initWithCallback(callback,
                           Math.round(slice/ratio),
                           CI.nsITimer.TYPE_REPEATING_SLACK);
    if (aTimers) {
      aTimers.push(timer);
    }
  },      
  
  cancel: function cancel(aTimers, i) {
    aTimers[i].cancel();
    aTimers.splice(i, 1);
  }
};
