'use strict';

Object.defineProperty(exports, '__esModule', {
  value: true
});

var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })();

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }

function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }

var _node = require('./node');

var _node2 = _interopRequireDefault(_node);

var _logicalPositionHelpers = require('./logical-position-helpers');

var Iterator = (function () {
  function Iterator(tree) {
    _classCallCheck(this, Iterator);

    this.tree = tree;
  }

  _createClass(Iterator, [{
    key: 'reset',
    value: function reset() {
      this.leftAncestor = null;
      this.leftAncestorPosition = _logicalPositionHelpers.ZERO_POSITION;
      this.leftAncestorStack = [null];
      this.leftAncestorPositionStack = [_logicalPositionHelpers.ZERO_POSITION];
      this.currentPosition = _logicalPositionHelpers.ZERO_POSITION;
      this.setCurrentNode(this.tree.root);
    }
  }, {
    key: 'insertNode',
    value: function insertNode(row) {
      var matchNodesAtSameRow = arguments.length <= 1 || arguments[1] === undefined ? true : arguments[1];

      this.reset();

      if (!this.currentNode) {
        this.tree.root = new _node2['default'](null, { row: row, pixels: 0 });
        return this.tree.root;
      }

      while (true) {
        if (row < this.currentPosition.row) {
          if (this.currentNode.left) {
            this.descendLeft();
          } else {
            return this.insertLeftChild(row);
          }
        } else if (row === this.currentPosition.row && matchNodesAtSameRow) {
          return this.currentNode;
        } else {
          // row > this.currentPosition.row
          if (this.currentNode.right) {
            this.descendRight();
          } else {
            return this.insertRightChild(row);
          }
        }
      }
    }
  }, {
    key: 'inclusiveTotalBlockPixelsPrecedingRow',
    value: function inclusiveTotalBlockPixelsPrecedingRow(row) {
      this.reset();

      if (!this.currentNode) return 0;

      while (true) {
        if (row < this.currentPosition.row) {
          if (this.currentNode.left) {
            this.descendLeft();
          } else {
            return this.leftAncestorPosition.pixels;
          }
        } else if (row === this.currentPosition.row) {
          return this.currentPosition.pixels - this.currentNode.followingBlockHeight;
        } else {
          // row > this.currentPosition.row
          if (this.currentNode.right) {
            this.descendRight();
          } else {
            return this.currentPosition.pixels;
          }
        }
      }
    }
  }, {
    key: 'exclusiveTotalBlockPixelsPrecedingRow',
    value: function exclusiveTotalBlockPixelsPrecedingRow(row) {
      this.reset();

      if (!this.currentNode) return 0;

      while (true) {
        if (row <= this.currentPosition.row) {
          if (this.currentNode.left) {
            this.descendLeft();
          } else {
            return this.leftAncestorPosition.pixels;
          }
        } else {
          // row > this.currentPosition.row
          if (this.currentNode.right) {
            this.descendRight();
          } else {
            return this.currentPosition.pixels;
          }
        }
      }
    }
  }, {
    key: 'rowForPixelPosition',
    value: function rowForPixelPosition(pixelPosition, lineHeight) {
      this.reset();

      if (!this.currentNode) return Math.floor(pixelPosition / lineHeight);

      var blockStart = undefined,
          blockEnd = undefined;
      while (true) {
        blockEnd = this.currentPosition.row * lineHeight + this.currentPosition.pixels;
        blockStart = blockEnd - this.currentNode.blockHeight;

        if (blockStart <= pixelPosition && pixelPosition <= blockEnd) {
          return this.currentPosition.row;
        } else if (pixelPosition < blockStart) {
          if (this.currentNode.left) {
            this.descendLeft();
          } else {
            var previousBlockEnd = this.leftAncestorPosition.row * lineHeight + this.leftAncestorPosition.pixels;
            var overshoot = pixelPosition - previousBlockEnd;
            return this.leftAncestorPosition.row + Math.floor(overshoot / lineHeight);
          }
        } else {
          // pixelPosition > blockEnd
          if (this.currentNode.right) {
            this.descendRight();
          } else {
            var overshoot = pixelPosition - blockEnd;
            return this.currentPosition.row + Math.floor(overshoot / lineHeight);
          }
        }
      }
    }
  }, {
    key: 'setCurrentNode',
    value: function setCurrentNode(node) {
      this.currentNode = node;
      if (this.currentNode) {
        this.currentPosition = (0, _logicalPositionHelpers.add)(this.leftAncestorPosition, this.currentNode.distanceFromLeftAncestor);
      }
    }
  }, {
    key: 'descendLeft',
    value: function descendLeft() {
      this.pushToAncestorStacks();
      this.setCurrentNode(this.currentNode.left);
    }
  }, {
    key: 'descendRight',
    value: function descendRight() {
      this.pushToAncestorStacks();
      this.leftAncestor = this.currentNode;
      this.leftAncestorPosition = this.currentPosition;
      this.setCurrentNode(this.currentNode.right);
    }
  }, {
    key: 'pushToAncestorStacks',
    value: function pushToAncestorStacks() {
      this.leftAncestorStack.push(this.leftAncestor);
      this.leftAncestorPositionStack.push(this.leftAncestorPosition);
    }
  }, {
    key: 'insertLeftChild',
    value: function insertLeftChild(row) {
      var extent = {
        row: row - this.leftAncestorPosition.row,
        pixels: 0
      };
      var newNode = new _node2['default'](this.currentNode, extent);
      this.currentNode.left = newNode;
      return newNode;
    }
  }, {
    key: 'insertRightChild',
    value: function insertRightChild(row) {
      var extent = {
        row: row - this.currentPosition.row,
        pixels: 0
      };
      var newNode = new _node2['default'](this.currentNode, extent);
      this.currentNode.right = newNode;
      return newNode;
    }
  }]);

  return Iterator;
})();

exports['default'] = Iterator;
module.exports = exports['default'];