| define(
  'tinymce.plugins.imagetools.test.ImageOps',
  [
    'ephox.agar.api.Chain',
    'ephox.agar.api.Guard',
    'ephox.agar.api.Mouse',
    'ephox.agar.api.Pipeline',
    'ephox.agar.api.Step',
    'ephox.agar.api.UiFinder',
    'ephox.agar.mouse.Clicks',
    'ephox.katamari.api.Fun',
    'ephox.katamari.api.Result',
    'ephox.mcagar.api.TinyDom',
    'ephox.mcagar.api.TinyUi',
    'ephox.sugar.api.events.DomEvent',
    'ephox.sugar.api.properties.Attr',
    'ephox.sugar.api.search.PredicateFilter',
    'ephox.sugar.api.search.Selectors',
    'ephox.sugar.api.view.Visibility'
  ],
  function (Chain, Guard, Mouse, Pipeline, Step, UiFinder, Clicks, Fun, Result, TinyDom, TinyUi, DomEvent, Attr, PredicateFilter, Selectors, Visibility) {
    // IMPORTANT: we match buttons by their aria-label
    return function (editor) {
      var ui = TinyUi(editor);
      var cHasState = function (predicate) {
        return Chain.binder(function (element) {
          return predicate(element) ? Result.value(element) : Result.error("Predicate didn't match.");
        });
      };
      var cWaitForState = function (predicate) {
        return Chain.control(
          cHasState(predicate),
          Guard.tryUntil("Predicate has failed.", 10, 3000)
        );
      };
      var cWaitForChain = function (chain) {
        return Chain.control(
          chain,
          Guard.tryUntil("Chain has failed.", 10, 3000)
        );
      };
      var cFindChildWithState = function (selector, predicate) {
        return Chain.on(function (scope, next, die) {
          var children = PredicateFilter.descendants(scope, function (element) {
            return Selectors.is(element, selector) && predicate(element);
          });
          children.length ? next(Chain.wrap(children[0])) : die();
        });
      };
      var cDragSlider = Chain.fromChains([
        UiFinder.cFindIn('div[role="slider"]'),
        Chain.on(function (element, next, die) {
          var pos = editor.dom.getPos(element.dom());
          var unbindMouseMove = DomEvent.bind(element, 'mousemove', function (e) {
            Clicks.mouseup(element);
            unbindMouseMove();
            next(Chain.wrap(element));
          }).unbind;
          var unbindMouseDown = DomEvent.bind(element, 'mousedown', function (e) {
            Clicks.mousemove(element, pos.x + 10, pos.y); // not sure if xy actually matters here
            unbindMouseDown();
          }).unbind;
          Clicks.mousedown(element);
        })
      ]);
      var cExecCommandFromDialog = function (label) {
        var cInteractWithUi;
        switch (label) {
          case 'Rotate counterclockwise':
          case 'Rotate clockwise':
          case 'Flip vertically':
          case 'Flip horizontally':
            // Orientation operations, like Flip or Rotate are grouped in a sub-panel
            cInteractWithUi = cClickToolbarButton(label);
            label = 'Orientation';
            break;
          case 'Brightness':
          case 'Contrast':
          case 'Color levels':
          case 'Gamma':
            cInteractWithUi = cDragSlider;
            break;
          default:
            cInteractWithUi = Chain.wait(1);
        }
        return Chain.fromChains([
          cClickToolbarButton('Edit image'),
          Chain.fromParent(ui.cWaitForPopup('wait for Edit Image dialog', 'div[aria-label="Edit image"][role="dialog"]'), [
            ui.cWaitForUi('wait for canvas', '.mce-imagepanel > img'),
            cClickToolbarButton(label),
            Chain.fromParent(cWaitForChain(cFindChildWithState('.mce-container.mce-form', Visibility.isVisible)), [
              Chain.fromChains([
                cInteractWithUi
              ]),
              cClickButton('Apply')
            ]),
            ui.cWaitForUi('wait for Save button to become enabled', 'div[role="button"]:contains(Save):not(.mce-disabled)'),
            cClickButton('Save')
          ])
        ]);
      };
      var cWaitForUi = function (label, selector) {
        return UiFinder.cWaitForState(label, selector, Fun.constant(true));
      };
      var cClickButton = function (text) {
        return Chain.fromChains([
          cWaitForUi('wait for ' + text + ' button', 'div[role="button"]:contains(' + text + '):not(.mce-disabled)'),
          Mouse.cClick
        ]);
      };
      var cClickToolbarButton = function (label) {
        return Chain.fromChains([
          UiFinder.cFindIn('div[aria-label="' + label + '"][role="button"]'),
          Mouse.cClick
        ]);
      };
      var sWaitForUrlChange = function (imgEl, origUrl) {
        return Chain.asStep(imgEl, [
          cWaitForState(function (el) {
            return Attr.get(el, 'src') !== origUrl;
          })
        ]);
      };
      var sExec = function (execFromToolbar, label) {
        return Step.async(function (next, die) {
          var imgEl = TinyDom.fromDom(editor.selection.getNode());
          var origUrl = Attr.get(imgEl, 'src');
          Pipeline.async({}, [
            Chain.asStep(imgEl, [
              Mouse.cClick,
              ui.cWaitForPopup('wait for Imagetools toolbar', 'div[aria-label="Inline toolbar"][role="dialog"]'),
              execFromToolbar ? cClickToolbarButton(label) : cExecCommandFromDialog(label)
            ]),
            sWaitForUrlChange(imgEl, origUrl)
          ], function () {
            next();
          }, die);
        });
      };
      return {
        sExecToolbar: Fun.curry(sExec, true),
        sExecDialog: Fun.curry(sExec, false)
      };
    };
  }
);
 |