// Copyright (c) 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

'use strict';

base.require('base.utils');

base.exportTo('base', function() {
  // Setting this to true will cause stack traces to get dumped into the
  // tasks. When an exception happens the original stack will be printed.
  //
  // NOTE: This should never be set committed as true.
  var recordRAFStacks = false;

  var pendingPreAFs = [];
  var pendingRAFs = [];
  var pendingIdleCallbacks = [];
  var currentRAFDispatchList = undefined;

  var rafScheduled = false;

  function scheduleRAF() {
    if (rafScheduled)
      return;
    rafScheduled = true;
    if (window.requestAnimationFrame) {
      window.requestAnimationFrame(processRequests);
    } else {
      var delta = Date.now() - window.performance.now();
      window.webkitRequestAnimationFrame(function(domTimeStamp) {
        processRequests(domTimeStamp - delta);
      });
    }
  }

  function onAnimationFrameError(e, opt_stack) {
    if (opt_stack)
      console.log(opt_stack);

    if (e.message)
      console.error(e.message, e.stack);
    else
      console.error(e);
  }

  function runTask(task, frameBeginTime) {
    try {
      task.callback.call(task.context, frameBeginTime);
    } catch (e) {
      base.onAnimationFrameError(e, task.stack);
    }
  }

  function processRequests(frameBeginTime) {
    // We assume that we want to do a maximum of 10ms optional work per frame.
    // Hopefully rAF will eventually pass this in for us.
    var rafCompletionDeadline = frameBeginTime + 10;

    rafScheduled = false;

    var currentPreAFs = pendingPreAFs;
    currentRAFDispatchList = pendingRAFs;
    pendingPreAFs = [];
    pendingRAFs = [];

    for (var i = 0; i < currentPreAFs.length; i++)
      runTask(currentPreAFs[i], frameBeginTime);

    while (currentRAFDispatchList.length > 0)
      runTask(currentRAFDispatchList.shift(), frameBeginTime);
    currentRAFDispatchList = undefined;

    while (pendingIdleCallbacks.length > 0 &&
           window.performance.now() < rafCompletionDeadline)
      runTask(pendingIdleCallbacks.shift());

    if (pendingIdleCallbacks.length > 0)
      scheduleRAF();
  }

  function getStack_() {
    if (!recordRAFStacks)
      return '';

    var stackLines = base.stackTrace();
    // Strip off getStack_.
    stackLines.shift();
    return stackLines.join('\n');
  }

  function requestPreAnimationFrame(callback, opt_this) {
    pendingPreAFs.push({
      callback: callback,
      context: opt_this || window,
      stack: getStack_()});
    scheduleRAF();
  }

  function requestAnimationFrameInThisFrameIfPossible(callback, opt_this) {
    if (!currentRAFDispatchList) {
      requestAnimationFrame(callback, opt_this);
      return;
    }
    currentRAFDispatchList.push({
      callback: callback,
      context: opt_this || window,
      stack: getStack_()});
    return;
  }

  function requestAnimationFrame(callback, opt_this) {
    pendingRAFs.push({
      callback: callback,
      context: opt_this || window,
      stack: getStack_()});
    scheduleRAF();
  }

  function requestIdleCallback(callback, opt_this) {
    pendingIdleCallbacks.push({
      callback: callback,
      context: opt_this || window,
      stack: getStack_()});
    scheduleRAF();
  }

  function forcePendingRAFTasksToRun(frameBeginTime) {
    if (!rafScheduled)
      return;
    processRequests(frameBeginTime);
  }

  return {
    onAnimationFrameError: onAnimationFrameError,
    requestPreAnimationFrame: requestPreAnimationFrame,
    requestAnimationFrame: requestAnimationFrame,
    requestAnimationFrameInThisFrameIfPossible:
        requestAnimationFrameInThisFrameIfPossible,
    requestIdleCallback: requestIdleCallback,
    forcePendingRAFTasksToRun: forcePendingRAFTasksToRun
  };
});
