'use strict';

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

var _index = require('../index.js');

var _index2 = _interopRequireDefault(_index);

var _collection = require('../collection.js');

var _collection2 = _interopRequireDefault(_collection);

var _collectioneventtype = require('../collectioneventtype.js');

var _collectioneventtype2 = _interopRequireDefault(_collectioneventtype);

var _feature = require('../feature.js');

var _feature2 = _interopRequireDefault(_feature);

var _mapbrowsereventtype = require('../mapbrowsereventtype.js');

var _mapbrowsereventtype2 = _interopRequireDefault(_mapbrowsereventtype);

var _mapbrowserpointerevent = require('../mapbrowserpointerevent.js');

var _mapbrowserpointerevent2 = _interopRequireDefault(_mapbrowserpointerevent);

var _array = require('../array.js');

var _array2 = _interopRequireDefault(_array);

var _coordinate = require('../coordinate.js');

var _coordinate2 = _interopRequireDefault(_coordinate);

var _events = require('../events.js');

var _events2 = _interopRequireDefault(_events);

var _event = require('../events/event.js');

var _event2 = _interopRequireDefault(_event);

var _eventtype = require('../events/eventtype.js');

var _eventtype2 = _interopRequireDefault(_eventtype);

var _condition = require('../events/condition.js');

var _condition2 = _interopRequireDefault(_condition);

var _extent = require('../extent.js');

var _extent2 = _interopRequireDefault(_extent);

var _geometrytype = require('../geom/geometrytype.js');

var _geometrytype2 = _interopRequireDefault(_geometrytype);

var _point = require('../geom/point.js');

var _point2 = _interopRequireDefault(_point);

var _modifyeventtype = require('../interaction/modifyeventtype.js');

var _modifyeventtype2 = _interopRequireDefault(_modifyeventtype);

var _pointer = require('../interaction/pointer.js');

var _pointer2 = _interopRequireDefault(_pointer);

var _vector = require('../layer/vector.js');

var _vector2 = _interopRequireDefault(_vector);

var _vector3 = require('../source/vector.js');

var _vector4 = _interopRequireDefault(_vector3);

var _vectoreventtype = require('../source/vectoreventtype.js');

var _vectoreventtype2 = _interopRequireDefault(_vectoreventtype);

var _rbush = require('../structs/rbush.js');

var _rbush2 = _interopRequireDefault(_rbush);

var _style = require('../style/style.js');

var _style2 = _interopRequireDefault(_style);

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

/**
 * @classdesc
 * Interaction for modifying feature geometries.  To modify features that have
 * been added to an existing source, construct the modify interaction with the
 * `source` option.  If you want to modify features in a collection (for example,
 * the collection used by a select interaction), construct the interaction with
 * the `features` option.  The interaction must be constructed with either a
 * `source` or `features` option.
 *
 * By default, the interaction will allow deletion of vertices when the `alt`
 * key is pressed.  To configure the interaction with a different condition
 * for deletion, use the `deleteCondition` option.
 *
 * @constructor
 * @extends {ol.interaction.Pointer}
 * @param {olx.interaction.ModifyOptions} options Options.
 * @fires ol.interaction.Modify.Event
 * @api
 */
var _ol_interaction_Modify_ = function (options) {

  _pointer2.default.call(this, {
    handleDownEvent: _ol_interaction_Modify_.handleDownEvent_,
    handleDragEvent: _ol_interaction_Modify_.handleDragEvent_,
    handleEvent: _ol_interaction_Modify_.handleEvent,
    handleUpEvent: _ol_interaction_Modify_.handleUpEvent_
  });

  /**
   * @private
   * @type {ol.EventsConditionType}
   */
  this.condition_ = options.condition ? options.condition : _condition2.default.primaryAction;

  /**
   * @private
   * @param {ol.MapBrowserEvent} mapBrowserEvent Browser event.
   * @return {boolean} Combined condition result.
   */
  this.defaultDeleteCondition_ = function (mapBrowserEvent) {
    return _condition2.default.altKeyOnly(mapBrowserEvent) && _condition2.default.singleClick(mapBrowserEvent);
  };

  /**
   * @type {ol.EventsConditionType}
   * @private
   */
  this.deleteCondition_ = options.deleteCondition ? options.deleteCondition : this.defaultDeleteCondition_;

  /**
   * @type {ol.EventsConditionType}
   * @private
   */
  this.insertVertexCondition_ = options.insertVertexCondition ? options.insertVertexCondition : _condition2.default.always;

  /**
   * Editing vertex.
   * @type {ol.Feature}
   * @private
   */
  this.vertexFeature_ = null;

  /**
   * Segments intersecting {@link this.vertexFeature_} by segment uid.
   * @type {Object.<string, boolean>}
   * @private
   */
  this.vertexSegments_ = null;

  /**
   * @type {ol.Pixel}
   * @private
   */
  this.lastPixel_ = [0, 0];

  /**
   * Tracks if the next `singleclick` event should be ignored to prevent
   * accidental deletion right after vertex creation.
   * @type {boolean}
   * @private
   */
  this.ignoreNextSingleClick_ = false;

  /**
   * @type {boolean}
   * @private
   */
  this.modified_ = false;

  /**
   * Segment RTree for each layer
   * @type {ol.structs.RBush.<ol.ModifySegmentDataType>}
   * @private
   */
  this.rBush_ = new _rbush2.default();

  /**
   * @type {number}
   * @private
   */
  this.pixelTolerance_ = options.pixelTolerance !== undefined ? options.pixelTolerance : 10;

  /**
   * @type {boolean}
   * @private
   */
  this.snappedToVertex_ = false;

  /**
   * Indicate whether the interaction is currently changing a feature's
   * coordinates.
   * @type {boolean}
   * @private
   */
  this.changingFeature_ = false;

  /**
   * @type {Array}
   * @private
   */
  this.dragSegments_ = [];

  /**
   * Draw overlay where sketch features are drawn.
   * @type {ol.layer.Vector}
   * @private
   */
  this.overlay_ = new _vector2.default({
    source: new _vector4.default({
      useSpatialIndex: false,
      wrapX: !!options.wrapX
    }),
    style: options.style ? options.style : _ol_interaction_Modify_.getDefaultStyleFunction(),
    updateWhileAnimating: true,
    updateWhileInteracting: true
  });

  /**
  * @const
  * @private
  * @type {Object.<string, function(ol.Feature, ol.geom.Geometry)>}
  */
  this.SEGMENT_WRITERS_ = {
    'Point': this.writePointGeometry_,
    'LineString': this.writeLineStringGeometry_,
    'LinearRing': this.writeLineStringGeometry_,
    'Polygon': this.writePolygonGeometry_,
    'MultiPoint': this.writeMultiPointGeometry_,
    'MultiLineString': this.writeMultiLineStringGeometry_,
    'MultiPolygon': this.writeMultiPolygonGeometry_,
    'Circle': this.writeCircleGeometry_,
    'GeometryCollection': this.writeGeometryCollectionGeometry_
  };

  /**
   * @type {ol.source.Vector}
   * @private
   */
  this.source_ = null;

  var features;
  if (options.source) {
    this.source_ = options.source;
    features = new _collection2.default(this.source_.getFeatures());
    _events2.default.listen(this.source_, _vectoreventtype2.default.ADDFEATURE, this.handleSourceAdd_, this);
    _events2.default.listen(this.source_, _vectoreventtype2.default.REMOVEFEATURE, this.handleSourceRemove_, this);
  } else {
    features = options.features;
  }
  if (!features) {
    throw new Error('The modify interaction requires features or a source');
  }

  /**
   * @type {ol.Collection.<ol.Feature>}
   * @private
   */
  this.features_ = features;

  this.features_.forEach(this.addFeature_, this);
  _events2.default.listen(this.features_, _collectioneventtype2.default.ADD, this.handleFeatureAdd_, this);
  _events2.default.listen(this.features_, _collectioneventtype2.default.REMOVE, this.handleFeatureRemove_, this);

  /**
   * @type {ol.MapBrowserPointerEvent}
   * @private
   */
  this.lastPointerEvent_ = null;
};

_index2.default.inherits(_ol_interaction_Modify_, _pointer2.default);

/**
 * @define {number} The segment index assigned to a circle's center when
 * breaking up a cicrle into ModifySegmentDataType segments.
 */
_ol_interaction_Modify_.MODIFY_SEGMENT_CIRCLE_CENTER_INDEX = 0;

/**
 * @define {number} The segment index assigned to a circle's circumference when
 * breaking up a circle into ModifySegmentDataType segments.
 */
_ol_interaction_Modify_.MODIFY_SEGMENT_CIRCLE_CIRCUMFERENCE_INDEX = 1;

/**
 * @param {ol.Feature} feature Feature.
 * @private
 */
_ol_interaction_Modify_.prototype.addFeature_ = function (feature) {
  var geometry = feature.getGeometry();
  if (geometry && geometry.getType() in this.SEGMENT_WRITERS_) {
    this.SEGMENT_WRITERS_[geometry.getType()].call(this, feature, geometry);
  }
  var map = this.getMap();
  if (map && map.isRendered() && this.getActive()) {
    this.handlePointerAtPixel_(this.lastPixel_, map);
  }
  _events2.default.listen(feature, _eventtype2.default.CHANGE, this.handleFeatureChange_, this);
};

/**
 * @param {ol.MapBrowserPointerEvent} evt Map browser event
 * @private
 */
_ol_interaction_Modify_.prototype.willModifyFeatures_ = function (evt) {
  if (!this.modified_) {
    this.modified_ = true;
    this.dispatchEvent(new _ol_interaction_Modify_.Event(_modifyeventtype2.default.MODIFYSTART, this.features_, evt));
  }
};

/**
 * @param {ol.Feature} feature Feature.
 * @private
 */
_ol_interaction_Modify_.prototype.removeFeature_ = function (feature) {
  this.removeFeatureSegmentData_(feature);
  // Remove the vertex feature if the collection of canditate features
  // is empty.
  if (this.vertexFeature_ && this.features_.getLength() === 0) {
    this.overlay_.getSource().removeFeature(this.vertexFeature_);
    this.vertexFeature_ = null;
  }
  _events2.default.unlisten(feature, _eventtype2.default.CHANGE, this.handleFeatureChange_, this);
};

/**
 * @param {ol.Feature} feature Feature.
 * @private
 */
_ol_interaction_Modify_.prototype.removeFeatureSegmentData_ = function (feature) {
  var rBush = this.rBush_;
  var /** @type {Array.<ol.ModifySegmentDataType>} */nodesToRemove = [];
  rBush.forEach(
  /**
   * @param {ol.ModifySegmentDataType} node RTree node.
   */
  function (node) {
    if (feature === node.feature) {
      nodesToRemove.push(node);
    }
  });
  for (var i = nodesToRemove.length - 1; i >= 0; --i) {
    rBush.remove(nodesToRemove[i]);
  }
};

/**
 * @inheritDoc
 */
_ol_interaction_Modify_.prototype.setActive = function (active) {
  if (this.vertexFeature_ && !active) {
    this.overlay_.getSource().removeFeature(this.vertexFeature_);
    this.vertexFeature_ = null;
  }
  _pointer2.default.prototype.setActive.call(this, active);
};

/**
 * @inheritDoc
 */
_ol_interaction_Modify_.prototype.setMap = function (map) {
  this.overlay_.setMap(map);
  _pointer2.default.prototype.setMap.call(this, map);
};

/**
 * @param {ol.source.Vector.Event} event Event.
 * @private
 */
_ol_interaction_Modify_.prototype.handleSourceAdd_ = function (event) {
  if (event.feature) {
    this.features_.push(event.feature);
  }
};

/**
 * @param {ol.source.Vector.Event} event Event.
 * @private
 */
_ol_interaction_Modify_.prototype.handleSourceRemove_ = function (event) {
  if (event.feature) {
    this.features_.remove(event.feature);
  }
};

/**
 * @param {ol.Collection.Event} evt Event.
 * @private
 */
_ol_interaction_Modify_.prototype.handleFeatureAdd_ = function (evt) {
  this.addFeature_( /** @type {ol.Feature} */evt.element);
};

/**
 * @param {ol.events.Event} evt Event.
 * @private
 */
_ol_interaction_Modify_.prototype.handleFeatureChange_ = function (evt) {
  if (!this.changingFeature_) {
    var feature = /** @type {ol.Feature} */evt.target;
    this.removeFeature_(feature);
    this.addFeature_(feature);
  }
};

/**
 * @param {ol.Collection.Event} evt Event.
 * @private
 */
_ol_interaction_Modify_.prototype.handleFeatureRemove_ = function (evt) {
  var feature = /** @type {ol.Feature} */evt.element;
  this.removeFeature_(feature);
};

/**
 * @param {ol.Feature} feature Feature
 * @param {ol.geom.Point} geometry Geometry.
 * @private
 */
_ol_interaction_Modify_.prototype.writePointGeometry_ = function (feature, geometry) {
  var coordinates = geometry.getCoordinates();
  var segmentData = /** @type {ol.ModifySegmentDataType} */{
    feature: feature,
    geometry: geometry,
    segment: [coordinates, coordinates]
  };
  this.rBush_.insert(geometry.getExtent(), segmentData);
};

/**
 * @param {ol.Feature} feature Feature
 * @param {ol.geom.MultiPoint} geometry Geometry.
 * @private
 */
_ol_interaction_Modify_.prototype.writeMultiPointGeometry_ = function (feature, geometry) {
  var points = geometry.getCoordinates();
  var coordinates, i, ii, segmentData;
  for (i = 0, ii = points.length; i < ii; ++i) {
    coordinates = points[i];
    segmentData = /** @type {ol.ModifySegmentDataType} */{
      feature: feature,
      geometry: geometry,
      depth: [i],
      index: i,
      segment: [coordinates, coordinates]
    };
    this.rBush_.insert(geometry.getExtent(), segmentData);
  }
};

/**
 * @param {ol.Feature} feature Feature
 * @param {ol.geom.LineString} geometry Geometry.
 * @private
 */
_ol_interaction_Modify_.prototype.writeLineStringGeometry_ = function (feature, geometry) {
  var coordinates = geometry.getCoordinates();
  var i, ii, segment, segmentData;
  for (i = 0, ii = coordinates.length - 1; i < ii; ++i) {
    segment = coordinates.slice(i, i + 2);
    segmentData = /** @type {ol.ModifySegmentDataType} */{
      feature: feature,
      geometry: geometry,
      index: i,
      segment: segment
    };
    this.rBush_.insert(_extent2.default.boundingExtent(segment), segmentData);
  }
};

/**
 * @param {ol.Feature} feature Feature
 * @param {ol.geom.MultiLineString} geometry Geometry.
 * @private
 */
_ol_interaction_Modify_.prototype.writeMultiLineStringGeometry_ = function (feature, geometry) {
  var lines = geometry.getCoordinates();
  var coordinates, i, ii, j, jj, segment, segmentData;
  for (j = 0, jj = lines.length; j < jj; ++j) {
    coordinates = lines[j];
    for (i = 0, ii = coordinates.length - 1; i < ii; ++i) {
      segment = coordinates.slice(i, i + 2);
      segmentData = /** @type {ol.ModifySegmentDataType} */{
        feature: feature,
        geometry: geometry,
        depth: [j],
        index: i,
        segment: segment
      };
      this.rBush_.insert(_extent2.default.boundingExtent(segment), segmentData);
    }
  }
};

/**
 * @param {ol.Feature} feature Feature
 * @param {ol.geom.Polygon} geometry Geometry.
 * @private
 */
_ol_interaction_Modify_.prototype.writePolygonGeometry_ = function (feature, geometry) {
  var rings = geometry.getCoordinates();
  var coordinates, i, ii, j, jj, segment, segmentData;
  for (j = 0, jj = rings.length; j < jj; ++j) {
    coordinates = rings[j];
    for (i = 0, ii = coordinates.length - 1; i < ii; ++i) {
      segment = coordinates.slice(i, i + 2);
      segmentData = /** @type {ol.ModifySegmentDataType} */{
        feature: feature,
        geometry: geometry,
        depth: [j],
        index: i,
        segment: segment
      };
      this.rBush_.insert(_extent2.default.boundingExtent(segment), segmentData);
    }
  }
};

/**
 * @param {ol.Feature} feature Feature
 * @param {ol.geom.MultiPolygon} geometry Geometry.
 * @private
 */
_ol_interaction_Modify_.prototype.writeMultiPolygonGeometry_ = function (feature, geometry) {
  var polygons = geometry.getCoordinates();
  var coordinates, i, ii, j, jj, k, kk, rings, segment, segmentData;
  for (k = 0, kk = polygons.length; k < kk; ++k) {
    rings = polygons[k];
    for (j = 0, jj = rings.length; j < jj; ++j) {
      coordinates = rings[j];
      for (i = 0, ii = coordinates.length - 1; i < ii; ++i) {
        segment = coordinates.slice(i, i + 2);
        segmentData = /** @type {ol.ModifySegmentDataType} */{
          feature: feature,
          geometry: geometry,
          depth: [j, k],
          index: i,
          segment: segment
        };
        this.rBush_.insert(_extent2.default.boundingExtent(segment), segmentData);
      }
    }
  }
};

/**
 * We convert a circle into two segments.  The segment at index
 * {@link ol.interaction.Modify.MODIFY_SEGMENT_CIRCLE_CENTER_INDEX} is the
 * circle's center (a point).  The segment at index
 * {@link ol.interaction.Modify.MODIFY_SEGMENT_CIRCLE_CIRCUMFERENCE_INDEX} is
 * the circumference, and is not a line segment.
 *
 * @param {ol.Feature} feature Feature.
 * @param {ol.geom.Circle} geometry Geometry.
 * @private
 */
_ol_interaction_Modify_.prototype.writeCircleGeometry_ = function (feature, geometry) {
  var coordinates = geometry.getCenter();
  var centerSegmentData = /** @type {ol.ModifySegmentDataType} */{
    feature: feature,
    geometry: geometry,
    index: _ol_interaction_Modify_.MODIFY_SEGMENT_CIRCLE_CENTER_INDEX,
    segment: [coordinates, coordinates]
  };
  var circumferenceSegmentData = /** @type {ol.ModifySegmentDataType} */{
    feature: feature,
    geometry: geometry,
    index: _ol_interaction_Modify_.MODIFY_SEGMENT_CIRCLE_CIRCUMFERENCE_INDEX,
    segment: [coordinates, coordinates]
  };
  var featureSegments = [centerSegmentData, circumferenceSegmentData];
  centerSegmentData.featureSegments = circumferenceSegmentData.featureSegments = featureSegments;
  this.rBush_.insert(_extent2.default.createOrUpdateFromCoordinate(coordinates), centerSegmentData);
  this.rBush_.insert(geometry.getExtent(), circumferenceSegmentData);
};

/**
 * @param {ol.Feature} feature Feature
 * @param {ol.geom.GeometryCollection} geometry Geometry.
 * @private
 */
_ol_interaction_Modify_.prototype.writeGeometryCollectionGeometry_ = function (feature, geometry) {
  var i,
      geometries = geometry.getGeometriesArray();
  for (i = 0; i < geometries.length; ++i) {
    this.SEGMENT_WRITERS_[geometries[i].getType()].call(this, feature, geometries[i]);
  }
};

/**
 * @param {ol.Coordinate} coordinates Coordinates.
 * @return {ol.Feature} Vertex feature.
 * @private
 */
_ol_interaction_Modify_.prototype.createOrUpdateVertexFeature_ = function (coordinates) {
  var vertexFeature = this.vertexFeature_;
  if (!vertexFeature) {
    vertexFeature = new _feature2.default(new _point2.default(coordinates));
    this.vertexFeature_ = vertexFeature;
    this.overlay_.getSource().addFeature(vertexFeature);
  } else {
    var geometry = /** @type {ol.geom.Point} */vertexFeature.getGeometry();
    geometry.setCoordinates(coordinates);
  }
  return vertexFeature;
};

/**
 * @param {ol.ModifySegmentDataType} a The first segment data.
 * @param {ol.ModifySegmentDataType} b The second segment data.
 * @return {number} The difference in indexes.
 * @private
 */
_ol_interaction_Modify_.compareIndexes_ = function (a, b) {
  return a.index - b.index;
};

/**
 * @param {ol.MapBrowserPointerEvent} evt Event.
 * @return {boolean} Start drag sequence?
 * @this {ol.interaction.Modify}
 * @private
 */
_ol_interaction_Modify_.handleDownEvent_ = function (evt) {
  if (!this.condition_(evt)) {
    return false;
  }
  this.handlePointerAtPixel_(evt.pixel, evt.map);
  var pixelCoordinate = evt.map.getCoordinateFromPixel(evt.pixel);
  this.dragSegments_.length = 0;
  this.modified_ = false;
  var vertexFeature = this.vertexFeature_;
  if (vertexFeature) {
    var insertVertices = [];
    var geometry = /** @type {ol.geom.Point} */vertexFeature.getGeometry();
    var vertex = geometry.getCoordinates();
    var vertexExtent = _extent2.default.boundingExtent([vertex]);
    var segmentDataMatches = this.rBush_.getInExtent(vertexExtent);
    var componentSegments = {};
    segmentDataMatches.sort(_ol_interaction_Modify_.compareIndexes_);
    for (var i = 0, ii = segmentDataMatches.length; i < ii; ++i) {
      var segmentDataMatch = segmentDataMatches[i];
      var segment = segmentDataMatch.segment;
      var uid = _index2.default.getUid(segmentDataMatch.feature);
      var depth = segmentDataMatch.depth;
      if (depth) {
        uid += '-' + depth.join('-'); // separate feature components
      }
      if (!componentSegments[uid]) {
        componentSegments[uid] = new Array(2);
      }
      if (segmentDataMatch.geometry.getType() === _geometrytype2.default.CIRCLE && segmentDataMatch.index === _ol_interaction_Modify_.MODIFY_SEGMENT_CIRCLE_CIRCUMFERENCE_INDEX) {

        var closestVertex = _ol_interaction_Modify_.closestOnSegmentData_(pixelCoordinate, segmentDataMatch);
        if (_coordinate2.default.equals(closestVertex, vertex) && !componentSegments[uid][0]) {
          this.dragSegments_.push([segmentDataMatch, 0]);
          componentSegments[uid][0] = segmentDataMatch;
        }
      } else if (_coordinate2.default.equals(segment[0], vertex) && !componentSegments[uid][0]) {
        this.dragSegments_.push([segmentDataMatch, 0]);
        componentSegments[uid][0] = segmentDataMatch;
      } else if (_coordinate2.default.equals(segment[1], vertex) && !componentSegments[uid][1]) {

        // prevent dragging closed linestrings by the connecting node
        if ((segmentDataMatch.geometry.getType() === _geometrytype2.default.LINE_STRING || segmentDataMatch.geometry.getType() === _geometrytype2.default.MULTI_LINE_STRING) && componentSegments[uid][0] && componentSegments[uid][0].index === 0) {
          continue;
        }

        this.dragSegments_.push([segmentDataMatch, 1]);
        componentSegments[uid][1] = segmentDataMatch;
      } else if (this.insertVertexCondition_(evt) && _index2.default.getUid(segment) in this.vertexSegments_ && !componentSegments[uid][0] && !componentSegments[uid][1]) {
        insertVertices.push([segmentDataMatch, vertex]);
      }
    }
    if (insertVertices.length) {
      this.willModifyFeatures_(evt);
    }
    for (var j = insertVertices.length - 1; j >= 0; --j) {
      this.insertVertex_.apply(this, insertVertices[j]);
    }
  }
  return !!this.vertexFeature_;
};

/**
 * @param {ol.MapBrowserPointerEvent} evt Event.
 * @this {ol.interaction.Modify}
 * @private
 */
_ol_interaction_Modify_.handleDragEvent_ = function (evt) {
  this.ignoreNextSingleClick_ = false;
  this.willModifyFeatures_(evt);

  var vertex = evt.coordinate;
  for (var i = 0, ii = this.dragSegments_.length; i < ii; ++i) {
    var dragSegment = this.dragSegments_[i];
    var segmentData = dragSegment[0];
    var depth = segmentData.depth;
    var geometry = segmentData.geometry;
    var coordinates;
    var segment = segmentData.segment;
    var index = dragSegment[1];

    while (vertex.length < geometry.getStride()) {
      vertex.push(segment[index][vertex.length]);
    }

    switch (geometry.getType()) {
      case _geometrytype2.default.POINT:
        coordinates = vertex;
        segment[0] = segment[1] = vertex;
        break;
      case _geometrytype2.default.MULTI_POINT:
        coordinates = geometry.getCoordinates();
        coordinates[segmentData.index] = vertex;
        segment[0] = segment[1] = vertex;
        break;
      case _geometrytype2.default.LINE_STRING:
        coordinates = geometry.getCoordinates();
        coordinates[segmentData.index + index] = vertex;
        segment[index] = vertex;
        break;
      case _geometrytype2.default.MULTI_LINE_STRING:
        coordinates = geometry.getCoordinates();
        coordinates[depth[0]][segmentData.index + index] = vertex;
        segment[index] = vertex;
        break;
      case _geometrytype2.default.POLYGON:
        coordinates = geometry.getCoordinates();
        coordinates[depth[0]][segmentData.index + index] = vertex;
        segment[index] = vertex;
        break;
      case _geometrytype2.default.MULTI_POLYGON:
        coordinates = geometry.getCoordinates();
        coordinates[depth[1]][depth[0]][segmentData.index + index] = vertex;
        segment[index] = vertex;
        break;
      case _geometrytype2.default.CIRCLE:
        segment[0] = segment[1] = vertex;
        if (segmentData.index === _ol_interaction_Modify_.MODIFY_SEGMENT_CIRCLE_CENTER_INDEX) {
          this.changingFeature_ = true;
          geometry.setCenter(vertex);
          this.changingFeature_ = false;
        } else {
          // We're dragging the circle's circumference:
          this.changingFeature_ = true;
          geometry.setRadius(_coordinate2.default.distance(geometry.getCenter(), vertex));
          this.changingFeature_ = false;
        }
        break;
      default:
      // pass
    }

    if (coordinates) {
      this.setGeometryCoordinates_(geometry, coordinates);
    }
  }
  this.createOrUpdateVertexFeature_(vertex);
};

/**
 * @param {ol.MapBrowserPointerEvent} evt Event.
 * @return {boolean} Stop drag sequence?
 * @this {ol.interaction.Modify}
 * @private
 */
_ol_interaction_Modify_.handleUpEvent_ = function (evt) {
  var segmentData;
  var geometry;
  for (var i = this.dragSegments_.length - 1; i >= 0; --i) {
    segmentData = this.dragSegments_[i][0];
    geometry = segmentData.geometry;
    if (geometry.getType() === _geometrytype2.default.CIRCLE) {
      // Update a circle object in the R* bush:
      var coordinates = geometry.getCenter();
      var centerSegmentData = segmentData.featureSegments[0];
      var circumferenceSegmentData = segmentData.featureSegments[1];
      centerSegmentData.segment[0] = centerSegmentData.segment[1] = coordinates;
      circumferenceSegmentData.segment[0] = circumferenceSegmentData.segment[1] = coordinates;
      this.rBush_.update(_extent2.default.createOrUpdateFromCoordinate(coordinates), centerSegmentData);
      this.rBush_.update(geometry.getExtent(), circumferenceSegmentData);
    } else {
      this.rBush_.update(_extent2.default.boundingExtent(segmentData.segment), segmentData);
    }
  }
  if (this.modified_) {
    this.dispatchEvent(new _ol_interaction_Modify_.Event(_modifyeventtype2.default.MODIFYEND, this.features_, evt));
    this.modified_ = false;
  }
  return false;
};

/**
 * Handles the {@link ol.MapBrowserEvent map browser event} and may modify the
 * geometry.
 * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event.
 * @return {boolean} `false` to stop event propagation.
 * @this {ol.interaction.Modify}
 * @api
 */
_ol_interaction_Modify_.handleEvent = function (mapBrowserEvent) {
  if (!(mapBrowserEvent instanceof _mapbrowserpointerevent2.default)) {
    return true;
  }
  this.lastPointerEvent_ = mapBrowserEvent;

  var handled;
  if (!mapBrowserEvent.map.getView().getInteracting() && mapBrowserEvent.type == _mapbrowsereventtype2.default.POINTERMOVE && !this.handlingDownUpSequence) {
    this.handlePointerMove_(mapBrowserEvent);
  }
  if (this.vertexFeature_ && this.deleteCondition_(mapBrowserEvent)) {
    if (mapBrowserEvent.type != _mapbrowsereventtype2.default.SINGLECLICK || !this.ignoreNextSingleClick_) {
      handled = this.removePoint();
    } else {
      handled = true;
    }
  }

  if (mapBrowserEvent.type == _mapbrowsereventtype2.default.SINGLECLICK) {
    this.ignoreNextSingleClick_ = false;
  }

  return _pointer2.default.handleEvent.call(this, mapBrowserEvent) && !handled;
};

/**
 * @param {ol.MapBrowserEvent} evt Event.
 * @private
 */
_ol_interaction_Modify_.prototype.handlePointerMove_ = function (evt) {
  this.lastPixel_ = evt.pixel;
  this.handlePointerAtPixel_(evt.pixel, evt.map);
};

/**
 * @param {ol.Pixel} pixel Pixel
 * @param {ol.PluggableMap} map Map.
 * @private
 */
_ol_interaction_Modify_.prototype.handlePointerAtPixel_ = function (pixel, map) {
  var pixelCoordinate = map.getCoordinateFromPixel(pixel);
  var sortByDistance = function (a, b) {
    return _ol_interaction_Modify_.pointDistanceToSegmentDataSquared_(pixelCoordinate, a) - _ol_interaction_Modify_.pointDistanceToSegmentDataSquared_(pixelCoordinate, b);
  };

  var box = _extent2.default.buffer(_extent2.default.createOrUpdateFromCoordinate(pixelCoordinate), map.getView().getResolution() * this.pixelTolerance_);

  var rBush = this.rBush_;
  var nodes = rBush.getInExtent(box);
  if (nodes.length > 0) {
    nodes.sort(sortByDistance);
    var node = nodes[0];
    var closestSegment = node.segment;
    var vertex = _ol_interaction_Modify_.closestOnSegmentData_(pixelCoordinate, node);
    var vertexPixel = map.getPixelFromCoordinate(vertex);
    var dist = _coordinate2.default.distance(pixel, vertexPixel);
    if (dist <= this.pixelTolerance_) {
      var vertexSegments = {};

      if (node.geometry.getType() === _geometrytype2.default.CIRCLE && node.index === _ol_interaction_Modify_.MODIFY_SEGMENT_CIRCLE_CIRCUMFERENCE_INDEX) {

        this.snappedToVertex_ = true;
        this.createOrUpdateVertexFeature_(vertex);
      } else {
        var pixel1 = map.getPixelFromCoordinate(closestSegment[0]);
        var pixel2 = map.getPixelFromCoordinate(closestSegment[1]);
        var squaredDist1 = _coordinate2.default.squaredDistance(vertexPixel, pixel1);
        var squaredDist2 = _coordinate2.default.squaredDistance(vertexPixel, pixel2);
        dist = Math.sqrt(Math.min(squaredDist1, squaredDist2));
        this.snappedToVertex_ = dist <= this.pixelTolerance_;
        if (this.snappedToVertex_) {
          vertex = squaredDist1 > squaredDist2 ? closestSegment[1] : closestSegment[0];
        }
        this.createOrUpdateVertexFeature_(vertex);
        var segment;
        for (var i = 1, ii = nodes.length; i < ii; ++i) {
          segment = nodes[i].segment;
          if (_coordinate2.default.equals(closestSegment[0], segment[0]) && _coordinate2.default.equals(closestSegment[1], segment[1]) || _coordinate2.default.equals(closestSegment[0], segment[1]) && _coordinate2.default.equals(closestSegment[1], segment[0])) {
            vertexSegments[_index2.default.getUid(segment)] = true;
          } else {
            break;
          }
        }
      }

      vertexSegments[_index2.default.getUid(closestSegment)] = true;
      this.vertexSegments_ = vertexSegments;
      return;
    }
  }
  if (this.vertexFeature_) {
    this.overlay_.getSource().removeFeature(this.vertexFeature_);
    this.vertexFeature_ = null;
  }
};

/**
 * Returns the distance from a point to a line segment.
 *
 * @param {ol.Coordinate} pointCoordinates The coordinates of the point from
 *        which to calculate the distance.
 * @param {ol.ModifySegmentDataType} segmentData The object describing the line
 *        segment we are calculating the distance to.
 * @return {number} The square of the distance between a point and a line segment.
 */
_ol_interaction_Modify_.pointDistanceToSegmentDataSquared_ = function (pointCoordinates, segmentData) {
  var geometry = segmentData.geometry;

  if (geometry.getType() === _geometrytype2.default.CIRCLE) {
    var circleGeometry = /** @type {ol.geom.Circle} */geometry;

    if (segmentData.index === _ol_interaction_Modify_.MODIFY_SEGMENT_CIRCLE_CIRCUMFERENCE_INDEX) {
      var distanceToCenterSquared = _coordinate2.default.squaredDistance(circleGeometry.getCenter(), pointCoordinates);
      var distanceToCircumference = Math.sqrt(distanceToCenterSquared) - circleGeometry.getRadius();
      return distanceToCircumference * distanceToCircumference;
    }
  }
  return _coordinate2.default.squaredDistanceToSegment(pointCoordinates, segmentData.segment);
};

/**
 * Returns the point closest to a given line segment.
 *
 * @param {ol.Coordinate} pointCoordinates The point to which a closest point
 *        should be found.
 * @param {ol.ModifySegmentDataType} segmentData The object describing the line
 *        segment which should contain the closest point.
 * @return {ol.Coordinate} The point closest to the specified line segment.
 */
_ol_interaction_Modify_.closestOnSegmentData_ = function (pointCoordinates, segmentData) {
  var geometry = segmentData.geometry;

  if (geometry.getType() === _geometrytype2.default.CIRCLE && segmentData.index === _ol_interaction_Modify_.MODIFY_SEGMENT_CIRCLE_CIRCUMFERENCE_INDEX) {
    return geometry.getClosestPoint(pointCoordinates);
  }
  return _coordinate2.default.closestOnSegment(pointCoordinates, segmentData.segment);
};

/**
 * @param {ol.ModifySegmentDataType} segmentData Segment data.
 * @param {ol.Coordinate} vertex Vertex.
 * @private
 */
_ol_interaction_Modify_.prototype.insertVertex_ = function (segmentData, vertex) {
  var segment = segmentData.segment;
  var feature = segmentData.feature;
  var geometry = segmentData.geometry;
  var depth = segmentData.depth;
  var index = /** @type {number} */segmentData.index;
  var coordinates;

  while (vertex.length < geometry.getStride()) {
    vertex.push(0);
  }

  switch (geometry.getType()) {
    case _geometrytype2.default.MULTI_LINE_STRING:
      coordinates = geometry.getCoordinates();
      coordinates[depth[0]].splice(index + 1, 0, vertex);
      break;
    case _geometrytype2.default.POLYGON:
      coordinates = geometry.getCoordinates();
      coordinates[depth[0]].splice(index + 1, 0, vertex);
      break;
    case _geometrytype2.default.MULTI_POLYGON:
      coordinates = geometry.getCoordinates();
      coordinates[depth[1]][depth[0]].splice(index + 1, 0, vertex);
      break;
    case _geometrytype2.default.LINE_STRING:
      coordinates = geometry.getCoordinates();
      coordinates.splice(index + 1, 0, vertex);
      break;
    default:
      return;
  }

  this.setGeometryCoordinates_(geometry, coordinates);
  var rTree = this.rBush_;
  rTree.remove(segmentData);
  this.updateSegmentIndices_(geometry, index, depth, 1);
  var newSegmentData = /** @type {ol.ModifySegmentDataType} */{
    segment: [segment[0], vertex],
    feature: feature,
    geometry: geometry,
    depth: depth,
    index: index
  };
  rTree.insert(_extent2.default.boundingExtent(newSegmentData.segment), newSegmentData);
  this.dragSegments_.push([newSegmentData, 1]);

  var newSegmentData2 = /** @type {ol.ModifySegmentDataType} */{
    segment: [vertex, segment[1]],
    feature: feature,
    geometry: geometry,
    depth: depth,
    index: index + 1
  };
  rTree.insert(_extent2.default.boundingExtent(newSegmentData2.segment), newSegmentData2);
  this.dragSegments_.push([newSegmentData2, 0]);
  this.ignoreNextSingleClick_ = true;
};

/**
 * Removes the vertex currently being pointed.
 * @return {boolean} True when a vertex was removed.
 * @api
 */
_ol_interaction_Modify_.prototype.removePoint = function () {
  if (this.lastPointerEvent_ && this.lastPointerEvent_.type != _mapbrowsereventtype2.default.POINTERDRAG) {
    var evt = this.lastPointerEvent_;
    this.willModifyFeatures_(evt);
    this.removeVertex_();
    this.dispatchEvent(new _ol_interaction_Modify_.Event(_modifyeventtype2.default.MODIFYEND, this.features_, evt));
    this.modified_ = false;
    return true;
  }
  return false;
};

/**
 * Removes a vertex from all matching features.
 * @return {boolean} True when a vertex was removed.
 * @private
 */
_ol_interaction_Modify_.prototype.removeVertex_ = function () {
  var dragSegments = this.dragSegments_;
  var segmentsByFeature = {};
  var deleted = false;
  var component, coordinates, dragSegment, geometry, i, index, left;
  var newIndex, right, segmentData, uid;
  for (i = dragSegments.length - 1; i >= 0; --i) {
    dragSegment = dragSegments[i];
    segmentData = dragSegment[0];
    uid = _index2.default.getUid(segmentData.feature);
    if (segmentData.depth) {
      // separate feature components
      uid += '-' + segmentData.depth.join('-');
    }
    if (!(uid in segmentsByFeature)) {
      segmentsByFeature[uid] = {};
    }
    if (dragSegment[1] === 0) {
      segmentsByFeature[uid].right = segmentData;
      segmentsByFeature[uid].index = segmentData.index;
    } else if (dragSegment[1] == 1) {
      segmentsByFeature[uid].left = segmentData;
      segmentsByFeature[uid].index = segmentData.index + 1;
    }
  }
  for (uid in segmentsByFeature) {
    right = segmentsByFeature[uid].right;
    left = segmentsByFeature[uid].left;
    index = segmentsByFeature[uid].index;
    newIndex = index - 1;
    if (left !== undefined) {
      segmentData = left;
    } else {
      segmentData = right;
    }
    if (newIndex < 0) {
      newIndex = 0;
    }
    geometry = segmentData.geometry;
    coordinates = geometry.getCoordinates();
    component = coordinates;
    deleted = false;
    switch (geometry.getType()) {
      case _geometrytype2.default.MULTI_LINE_STRING:
        if (coordinates[segmentData.depth[0]].length > 2) {
          coordinates[segmentData.depth[0]].splice(index, 1);
          deleted = true;
        }
        break;
      case _geometrytype2.default.LINE_STRING:
        if (coordinates.length > 2) {
          coordinates.splice(index, 1);
          deleted = true;
        }
        break;
      case _geometrytype2.default.MULTI_POLYGON:
        component = component[segmentData.depth[1]];
      /* falls through */
      case _geometrytype2.default.POLYGON:
        component = component[segmentData.depth[0]];
        if (component.length > 4) {
          if (index == component.length - 1) {
            index = 0;
          }
          component.splice(index, 1);
          deleted = true;
          if (index === 0) {
            // close the ring again
            component.pop();
            component.push(component[0]);
            newIndex = component.length - 1;
          }
        }
        break;
      default:
      // pass
    }

    if (deleted) {
      this.setGeometryCoordinates_(geometry, coordinates);
      var segments = [];
      if (left !== undefined) {
        this.rBush_.remove(left);
        segments.push(left.segment[0]);
      }
      if (right !== undefined) {
        this.rBush_.remove(right);
        segments.push(right.segment[1]);
      }
      if (left !== undefined && right !== undefined) {
        var newSegmentData = /** @type {ol.ModifySegmentDataType} */{
          depth: segmentData.depth,
          feature: segmentData.feature,
          geometry: segmentData.geometry,
          index: newIndex,
          segment: segments
        };
        this.rBush_.insert(_extent2.default.boundingExtent(newSegmentData.segment), newSegmentData);
      }
      this.updateSegmentIndices_(geometry, index, segmentData.depth, -1);
      if (this.vertexFeature_) {
        this.overlay_.getSource().removeFeature(this.vertexFeature_);
        this.vertexFeature_ = null;
      }
      dragSegments.length = 0;
    }
  }
  return deleted;
};

/**
 * @param {ol.geom.SimpleGeometry} geometry Geometry.
 * @param {Array} coordinates Coordinates.
 * @private
 */
_ol_interaction_Modify_.prototype.setGeometryCoordinates_ = function (geometry, coordinates) {
  this.changingFeature_ = true;
  geometry.setCoordinates(coordinates);
  this.changingFeature_ = false;
};

/**
 * @param {ol.geom.SimpleGeometry} geometry Geometry.
 * @param {number} index Index.
 * @param {Array.<number>|undefined} depth Depth.
 * @param {number} delta Delta (1 or -1).
 * @private
 */
_ol_interaction_Modify_.prototype.updateSegmentIndices_ = function (geometry, index, depth, delta) {
  this.rBush_.forEachInExtent(geometry.getExtent(), function (segmentDataMatch) {
    if (segmentDataMatch.geometry === geometry && (depth === undefined || segmentDataMatch.depth === undefined || _array2.default.equals(segmentDataMatch.depth, depth)) && segmentDataMatch.index > index) {
      segmentDataMatch.index += delta;
    }
  });
};

/**
 * @return {ol.StyleFunction} Styles.
 */
_ol_interaction_Modify_.getDefaultStyleFunction = function () {
  var style = _style2.default.createDefaultEditing();
  return function (feature, resolution) {
    return style[_geometrytype2.default.POINT];
  };
};

/**
 * @classdesc
 * Events emitted by {@link ol.interaction.Modify} instances are instances of
 * this type.
 *
 * @constructor
 * @extends {ol.events.Event}
 * @implements {oli.ModifyEvent}
 * @param {ol.interaction.ModifyEventType} type Type.
 * @param {ol.Collection.<ol.Feature>} features The features modified.
 * @param {ol.MapBrowserPointerEvent} mapBrowserPointerEvent Associated
 *     {@link ol.MapBrowserPointerEvent}.
 */
_ol_interaction_Modify_.Event = function (type, features, mapBrowserPointerEvent) {

  _event2.default.call(this, type);

  /**
   * The features being modified.
   * @type {ol.Collection.<ol.Feature>}
   * @api
   */
  this.features = features;

  /**
   * Associated {@link ol.MapBrowserEvent}.
   * @type {ol.MapBrowserEvent}
   * @api
   */
  this.mapBrowserEvent = mapBrowserPointerEvent;
};
_index2.default.inherits(_ol_interaction_Modify_.Event, _event2.default);
exports.default = _ol_interaction_Modify_;
module.exports = exports['default'];