| /**
 * LineWalker.js
 *
 * Released under LGPL License.
 * Copyright (c) 1999-2017 Ephox Corp. All rights reserved
 *
 * License: http://www.tinymce.com/license
 * Contributing: http://www.tinymce.com/contributing
 */
/**
 * This module lets you walk the document line by line
 * returing nodes and client rects for each line.
 *
 * @private
 * @class tinymce.caret.LineWalker
 */
define(
  'tinymce.core.caret.LineWalker',
  [
    "tinymce.core.util.Fun",
    "tinymce.core.util.Arr",
    "tinymce.core.dom.Dimensions",
    "tinymce.core.caret.CaretCandidate",
    "tinymce.core.caret.CaretUtils",
    "tinymce.core.caret.CaretWalker",
    "tinymce.core.caret.CaretPosition",
    "tinymce.core.geom.ClientRect"
  ],
  function (Fun, Arr, Dimensions, CaretCandidate, CaretUtils, CaretWalker, CaretPosition, ClientRect) {
    var curry = Fun.curry;
    var findUntil = function (direction, rootNode, predicateFn, node) {
      while ((node = CaretUtils.findNode(node, direction, CaretCandidate.isEditableCaretCandidate, rootNode))) {
        if (predicateFn(node)) {
          return;
        }
      }
    };
    var walkUntil = function (direction, isAboveFn, isBeflowFn, rootNode, predicateFn, caretPosition) {
      var line = 0, node, result = [], targetClientRect;
      var add = function (node) {
        var i, clientRect, clientRects;
        clientRects = Dimensions.getClientRects(node);
        if (direction == -1) {
          clientRects = clientRects.reverse();
        }
        for (i = 0; i < clientRects.length; i++) {
          clientRect = clientRects[i];
          if (isBeflowFn(clientRect, targetClientRect)) {
            continue;
          }
          if (result.length > 0 && isAboveFn(clientRect, Arr.last(result))) {
            line++;
          }
          clientRect.line = line;
          if (predicateFn(clientRect)) {
            return true;
          }
          result.push(clientRect);
        }
      };
      targetClientRect = Arr.last(caretPosition.getClientRects());
      if (!targetClientRect) {
        return result;
      }
      node = caretPosition.getNode();
      add(node);
      findUntil(direction, rootNode, add, node);
      return result;
    };
    var aboveLineNumber = function (lineNumber, clientRect) {
      return clientRect.line > lineNumber;
    };
    var isLine = function (lineNumber, clientRect) {
      return clientRect.line === lineNumber;
    };
    var upUntil = curry(walkUntil, -1, ClientRect.isAbove, ClientRect.isBelow);
    var downUntil = curry(walkUntil, 1, ClientRect.isBelow, ClientRect.isAbove);
    var positionsUntil = function (direction, rootNode, predicateFn, node) {
      var caretWalker = new CaretWalker(rootNode), walkFn, isBelowFn, isAboveFn,
        caretPosition, result = [], line = 0, clientRect, targetClientRect;
      var getClientRect = function (caretPosition) {
        if (direction == 1) {
          return Arr.last(caretPosition.getClientRects());
        }
        return Arr.last(caretPosition.getClientRects());
      };
      if (direction == 1) {
        walkFn = caretWalker.next;
        isBelowFn = ClientRect.isBelow;
        isAboveFn = ClientRect.isAbove;
        caretPosition = CaretPosition.after(node);
      } else {
        walkFn = caretWalker.prev;
        isBelowFn = ClientRect.isAbove;
        isAboveFn = ClientRect.isBelow;
        caretPosition = CaretPosition.before(node);
      }
      targetClientRect = getClientRect(caretPosition);
      do {
        if (!caretPosition.isVisible()) {
          continue;
        }
        clientRect = getClientRect(caretPosition);
        if (isAboveFn(clientRect, targetClientRect)) {
          continue;
        }
        if (result.length > 0 && isBelowFn(clientRect, Arr.last(result))) {
          line++;
        }
        clientRect = ClientRect.clone(clientRect);
        clientRect.position = caretPosition;
        clientRect.line = line;
        if (predicateFn(clientRect)) {
          return result;
        }
        result.push(clientRect);
      } while ((caretPosition = walkFn(caretPosition)));
      return result;
    };
    return {
      upUntil: upUntil,
      downUntil: downUntil,
      /**
       * Find client rects with line and caret position until the predicate returns true.
       *
       * @method positionsUntil
       * @param {Number} direction Direction forward/backward 1/-1.
       * @param {DOMNode} rootNode Root node to walk within.
       * @param {function} predicateFn Gets the client rect as it's input.
       * @param {DOMNode} node Node to start walking from.
       * @return {Array} Array of client rects with line and position properties.
       */
      positionsUntil: positionsUntil,
      isAboveLine: curry(aboveLineNumber),
      isLine: curry(isLine)
    };
  }
);
 |