| define(
  'tinymce.themes.mobile.ios.scroll.IosScrolling',
  [
    'ephox.katamari.api.Fun',
    'ephox.katamari.api.Future',
    'ephox.sugar.api.properties.Attr',
    'ephox.sugar.api.properties.Classes',
    'ephox.sugar.api.properties.Css',
    'ephox.sugar.api.search.Traverse',
    'global!Math',
    'tinymce.themes.mobile.ios.smooth.SmoothAnimation',
    'tinymce.themes.mobile.ios.view.IosViewport',
    'tinymce.themes.mobile.style.Styles',
    'tinymce.themes.mobile.util.DataAttributes'
  ],
  function (Fun, Future, Attr, Classes, Css, Traverse, Math, SmoothAnimation, IosViewport, Styles, DataAttributes) {
    var animator = SmoothAnimation.create();
    var ANIMATION_STEP = 15;
    var NUM_TOP_ANIMATION_FRAMES = 10;
    var ANIMATION_RATE = 10;
    var lastScroll = 'data-' + Styles.resolve('last-scroll-top');
    var getTop = function (element) {
      var raw = Css.getRaw(element, 'top').getOr(0);
      return parseInt(raw, 10);
    };
    var getScrollTop = function (element) {
      return parseInt(element.dom().scrollTop, 10);
    };
    var moveScrollAndTop = function (element, destination, finalTop) {
      return Future.nu(function (callback) {
        var getCurrent = Fun.curry(getScrollTop, element);
        var update = function (newScroll) {
          element.dom().scrollTop = newScroll;
          Css.set(element, 'top', (getTop(element) + ANIMATION_STEP) + 'px');
        };
        var finish = function (/* dest */) {
          element.dom().scrollTop = destination;
          Css.set(element, 'top', finalTop + 'px');
          callback(destination);
        };
        animator.animate(getCurrent, destination, ANIMATION_STEP, update, finish, ANIMATION_RATE);
      });
    };
    var moveOnlyScroll = function (element, destination) {
      return Future.nu(function (callback) {
        var getCurrent = Fun.curry(getScrollTop, element);
        Attr.set(element, lastScroll, getCurrent());
        var update = function (newScroll, abort) {
          var previous = DataAttributes.safeParse(element, lastScroll);
          // As soon as we detect a scroll value that we didn't set, assume the user
          // is scrolling, and abort the scrolling.
          if (previous !== element.dom().scrollTop) {
            abort(element.dom().scrollTop);
          } else {
            element.dom().scrollTop = newScroll;
            Attr.set(element, lastScroll, newScroll);
          }
        };
        var finish = function (/* dest */) {
          element.dom().scrollTop = destination;
          Attr.set(element, lastScroll, destination);
          callback(destination);
        };
        // Identify the number of steps based on distance (consistent time)
        var distance = Math.abs(destination - getCurrent());
        var step = Math.ceil(distance / NUM_TOP_ANIMATION_FRAMES);
        animator.animate(getCurrent, destination, step, update, finish, ANIMATION_RATE);
      });
    };
    var moveOnlyTop = function (element, destination) {
      return Future.nu(function (callback) {
        var getCurrent = Fun.curry(getTop, element);
        var update = function (newTop) {
          Css.set(element, 'top', newTop + 'px');
        };
        var finish = function (/* dest */) {
          update(destination);
          callback(destination);
        };
        var distance = Math.abs(destination - getCurrent());
        var step = Math.ceil(distance / NUM_TOP_ANIMATION_FRAMES);
        animator.animate(getCurrent, destination, step, update, finish, ANIMATION_RATE);
      });
    };
    var updateTop = function (element, amount) {
      var newTop = (amount + IosViewport.getYFixedData(element)) + 'px';
      Css.set(element, 'top', newTop);
    };
    // Previously, we moved the window scroll back smoothly with the SmoothAnimation concept.
    // However, on tinyMCE, we seemed to get a lot more cursor flickering as the window scroll
    // was changing. Therefore, until tests prove otherwise, we are just going to jump to the
    // destination in one go.
    var moveWindowScroll = function (toolbar, viewport, destY) {
      var outerWindow = Traverse.owner(toolbar).dom().defaultView;
      return Future.nu(function (callback) {
        updateTop(toolbar, destY);
        updateTop(viewport, destY);
        outerWindow.scrollTo(0, destY);
        callback(destY);
      });
    };
    return {
      moveScrollAndTop: moveScrollAndTop,
      moveOnlyScroll: moveOnlyScroll,
      moveOnlyTop: moveOnlyTop,
      moveWindowScroll: moveWindowScroll
    };
  }
);
 |