'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 _randomSeed = require('random-seed');

var _randomSeed2 = _interopRequireDefault(_randomSeed);

var _iterator = require('./iterator');

var _iterator2 = _interopRequireDefault(_iterator);

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

var LineTopIndex = (function () {
  function LineTopIndex() {
    var params = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0];

    _classCallCheck(this, LineTopIndex);

    this.setDefaultLineHeight(params.defaultLineHeight || 0);
    this.randomGenerator = new _randomSeed2['default'](params.seed || Date.now());
    this.root = null;
    this.iterator = this.buildIterator();
    this.blockEndNodesById = {};
    this.blockHeightsById = {};
    this.followingBlockIds = new Set();
  }

  _createClass(LineTopIndex, [{
    key: 'setDefaultLineHeight',
    value: function setDefaultLineHeight(lineHeight) {
      this.defaultLineHeight = lineHeight;
    }
  }, {
    key: 'buildIterator',
    value: function buildIterator() {
      return new _iterator2['default'](this);
    }
  }, {
    key: 'insertBlock',
    value: function insertBlock(id, row, blockHeight) {
      var isAfterRow = arguments.length <= 3 || arguments[3] === undefined ? false : arguments[3];

      var node = this.iterator.insertNode(row);
      if (node.priority == null) {
        node.priority = this.generateRandom();
        this.bubbleNodeUp(node);
      }

      this.adjustNodeBlockHeight(node, +blockHeight, isAfterRow);

      node.blockIds.add(id);
      this.blockEndNodesById[id] = node;
      this.blockHeightsById[id] = blockHeight;
      if (isAfterRow) this.followingBlockIds.add(id);
    }
  }, {
    key: 'removeBlock',
    value: function removeBlock(id) {
      var node = this.blockEndNodesById[id];
      var blockHeight = this.blockHeightsById[id];
      var isAfterRow = this.followingBlockIds.has(id);

      this.adjustNodeBlockHeight(node, -blockHeight, isAfterRow);
      node.blockIds['delete'](id);
      if (node.blockIds.size === 0) {
        this.deleteNode(node);
      }

      delete this.blockEndNodesById[id];
      delete this.blockHeightsById[id];
      this.followingBlockIds['delete'](id);
    }
  }, {
    key: 'resizeBlock',
    value: function resizeBlock(id, newBlockHeight) {
      var node = this.blockEndNodesById[id];
      var blockHeight = this.blockHeightsById[id];
      var delta = newBlockHeight - blockHeight;
      var isAfterRow = this.followingBlockIds.has(id);

      this.adjustNodeBlockHeight(node, delta, isAfterRow);

      this.blockHeightsById[id] = newBlockHeight;
    }
  }, {
    key: 'moveBlock',
    value: function moveBlock(id, newRow) {
      var blockHeight = this.blockHeightsById[id];
      var isAfterRow = this.followingBlockIds.has(id);
      this.removeBlock(id);
      this.insertBlock(id, newRow, blockHeight, isAfterRow);
    }
  }, {
    key: 'splice',
    value: function splice(start, oldExtent, newExtent) {
      var _this = this;

      if (oldExtent === 0 && newExtent === 0) return new Set();

      var oldEnd = start + oldExtent;
      var newEnd = start + newExtent;

      var isInsertion = oldExtent === 0;
      var startNode = this.iterator.insertNode(start);
      var endNode = this.iterator.insertNode(oldEnd, !isInsertion);

      var touchedBlocks = new Set();
      var blocksIdsToMove = new Set();

      startNode.priority = -1;
      this.bubbleNodeUp(startNode);
      endNode.priority = -2;
      this.bubbleNodeUp(endNode);

      startNode.blockIds.forEach(function (id) {
        startNode.blockIds['delete'](id);
        startNode.blockHeight -= _this.blockHeightsById[id];
        startNode.distanceFromLeftAncestor.pixels -= _this.blockHeightsById[id];
        if (_this.followingBlockIds.has(id)) startNode.followingBlockHeight -= _this.blockHeightsById[id];

        blocksIdsToMove.add(id);
      });

      if (startNode.right) {
        this.blockIdsForSubtree(startNode.right).forEach(function (id) {
          blocksIdsToMove.add(id);
        });

        startNode.right = null;
      }

      blocksIdsToMove.forEach(function (id) {
        endNode.blockIds.add(id);
        endNode.blockHeight += _this.blockHeightsById[id];
        if (_this.followingBlockIds.has(id)) endNode.followingBlockHeight += _this.blockHeightsById[id];
        _this.blockEndNodesById[id] = endNode;
      });

      endNode.distanceFromLeftAncestor.row = newEnd;
      endNode.blockIds.forEach(function (id) {
        return touchedBlocks.add(id);
      });

      if (startNode.distanceFromLeftAncestor.row === endNode.distanceFromLeftAncestor.row) {
        endNode.blockIds.forEach(function (id) {
          startNode.blockIds.add(id);
          startNode.blockHeight += _this.blockHeightsById[id];
          startNode.distanceFromLeftAncestor.pixels += _this.blockHeightsById[id];
          if (_this.followingBlockIds.has(id)) startNode.followingBlockHeight += _this.blockHeightsById[id];
          _this.blockEndNodesById[id] = startNode;
        });

        this.deleteNode(endNode);
      } else if (endNode.blockIds.size > 0) {
        endNode.priority = this.generateRandom();
        this.bubbleNodeDown(endNode);
      } else {
        this.deleteNode(endNode);
      }

      if (startNode.blockIds.size > 0) {
        startNode.priority = this.generateRandom();
        this.bubbleNodeDown(startNode);
      } else {
        this.deleteNode(startNode);
      }

      return touchedBlocks;
    }
  }, {
    key: 'pixelPositionAfterBlocksForRow',
    value: function pixelPositionAfterBlocksForRow(row) {
      return row * this.defaultLineHeight + this.iterator.inclusiveTotalBlockPixelsPrecedingRow(row);
    }
  }, {
    key: 'pixelPositionBeforeBlocksForRow',
    value: function pixelPositionBeforeBlocksForRow(row) {
      return row * this.defaultLineHeight + this.iterator.exclusiveTotalBlockPixelsPrecedingRow(row);
    }
  }, {
    key: 'rowForPixelPosition',
    value: function rowForPixelPosition(pixelPosition) {
      return this.iterator.rowForPixelPosition(pixelPosition, this.defaultLineHeight);
    }
  }, {
    key: 'deleteNode',
    value: function deleteNode(node) {
      node.priority = Infinity;
      this.bubbleNodeDown(node);
      if (node.parent) {
        if (node.parent.left === node) {
          node.parent.left = null;
        } else {
          node.parent.right = null;
        }
      } else {
        this.root = null;
      }
    }
  }, {
    key: 'bubbleNodeUp',
    value: function bubbleNodeUp(node) {
      while (node.parent && node.priority < node.parent.priority) {
        if (node === node.parent.left) {
          this.rotateNodeRight(node);
        } else {
          this.rotateNodeLeft(node);
        }
      }
    }
  }, {
    key: 'bubbleNodeDown',
    value: function bubbleNodeDown(node) {
      while (true) {
        var leftChildPriority = node.left ? node.left.priority : Infinity;
        var rightChildPriority = node.right ? node.right.priority : Infinity;

        if (leftChildPriority < rightChildPriority && leftChildPriority < node.priority) {
          this.rotateNodeRight(node.left);
        } else if (rightChildPriority < node.priority) {
          this.rotateNodeLeft(node.right);
        } else {
          break;
        }
      }
    }
  }, {
    key: 'rotateNodeLeft',
    value: function rotateNodeLeft(pivot) {
      var root = pivot.parent;

      if (root.parent) {
        if (root === root.parent.left) {
          root.parent.left = pivot;
        } else {
          root.parent.right = pivot;
        }
      } else {
        this.root = pivot;
      }
      pivot.parent = root.parent;

      root.right = pivot.left;
      if (root.right) {
        root.right.parent = root;
      }

      pivot.left = root;
      pivot.left.parent = pivot;

      pivot.distanceFromLeftAncestor = (0, _logicalPositionHelpers.add)(root.distanceFromLeftAncestor, pivot.distanceFromLeftAncestor);
    }
  }, {
    key: 'rotateNodeRight',
    value: function rotateNodeRight(pivot) {
      var root = pivot.parent;

      if (root.parent) {
        if (root === root.parent.left) {
          root.parent.left = pivot;
        } else {
          root.parent.right = pivot;
        }
      } else {
        this.root = pivot;
      }
      pivot.parent = root.parent;

      root.left = pivot.right;
      if (root.left) {
        root.left.parent = root;
      }

      pivot.right = root;
      pivot.right.parent = pivot;

      root.distanceFromLeftAncestor = (0, _logicalPositionHelpers.subtract)(root.distanceFromLeftAncestor, pivot.distanceFromLeftAncestor);
    }
  }, {
    key: 'adjustNodeBlockHeight',
    value: function adjustNodeBlockHeight(node, delta, isAfterRow) {
      if (isAfterRow) node.followingBlockHeight += delta;
      node.blockHeight += delta;
      node.distanceFromLeftAncestor.pixels += delta;
      while (node.parent) {
        if (node.parent.left === node) {
          node.parent.distanceFromLeftAncestor.pixels += delta;
        }
        node = node.parent;
      }
    }
  }, {
    key: 'blockIdsForSubtree',
    value: function blockIdsForSubtree(node) {
      var blockIds = arguments.length <= 1 || arguments[1] === undefined ? new Set() : arguments[1];

      node.blockIds.forEach(function (id) {
        blockIds.add(id);
      });
      if (node.left) this.blockIdsForSubtree(node.left, blockIds);
      if (node.right) this.blockIdsForSubtree(node.right, blockIds);

      return blockIds;
    }
  }, {
    key: 'generateRandom',
    value: function generateRandom() {
      return this.randomGenerator.random();
    }
  }]);

  return LineTopIndex;
})();

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