'use strict';

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

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

var _index2 = _interopRequireDefault(_index);

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 _coordinate = require('../coordinate.js');

var _coordinate2 = _interopRequireDefault(_coordinate);

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

var _event2 = _interopRequireDefault(_event);

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 _polygon = require('../geom/polygon.js');

var _polygon2 = _interopRequireDefault(_polygon);

var _extenteventtype = require('../interaction/extenteventtype.js');

var _extenteventtype2 = _interopRequireDefault(_extenteventtype);

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 _style = require('../style/style.js');

var _style2 = _interopRequireDefault(_style);

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

/**
 * @classdesc
 * Allows the user to draw a vector box by clicking and dragging on the map.
 * Once drawn, the vector box can be modified by dragging its vertices or edges.
 * This interaction is only supported for mouse devices.
 *
 * @constructor
 * @extends {ol.interaction.Pointer}
 * @fires ol.interaction.Extent.Event
 * @param {olx.interaction.ExtentOptions=} opt_options Options.
 * @api
 */
var _ol_interaction_Extent_ = function (opt_options) {

  var options = opt_options || {};

  /**
   * Extent of the drawn box
   * @type {ol.Extent}
   * @private
   */
  this.extent_ = null;

  /**
   * Handler for pointer move events
   * @type {function (ol.Coordinate): ol.Extent|null}
   * @private
   */
  this.pointerHandler_ = null;

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

  /**
   * Is the pointer snapped to an extent vertex
   * @type {boolean}
   * @private
   */
  this.snappedToVertex_ = false;

  /**
   * Feature for displaying the visible extent
   * @type {ol.Feature}
   * @private
   */
  this.extentFeature_ = null;

  /**
   * Feature for displaying the visible pointer
   * @type {ol.Feature}
   * @private
   */
  this.vertexFeature_ = null;

  if (!opt_options) {
    opt_options = {};
  }

  /* Inherit ol.interaction.Pointer */
  _pointer2.default.call(this, {
    handleDownEvent: _ol_interaction_Extent_.handleDownEvent_,
    handleDragEvent: _ol_interaction_Extent_.handleDragEvent_,
    handleEvent: _ol_interaction_Extent_.handleEvent_,
    handleUpEvent: _ol_interaction_Extent_.handleUpEvent_
  });

  /**
   * Layer for the extentFeature
   * @type {ol.layer.Vector}
   * @private
   */
  this.extentOverlay_ = new _vector2.default({
    source: new _vector4.default({
      useSpatialIndex: false,
      wrapX: !!opt_options.wrapX
    }),
    style: opt_options.boxStyle ? opt_options.boxStyle : _ol_interaction_Extent_.getDefaultExtentStyleFunction_(),
    updateWhileAnimating: true,
    updateWhileInteracting: true
  });

  /**
   * Layer for the vertexFeature
   * @type {ol.layer.Vector}
   * @private
   */
  this.vertexOverlay_ = new _vector2.default({
    source: new _vector4.default({
      useSpatialIndex: false,
      wrapX: !!opt_options.wrapX
    }),
    style: opt_options.pointerStyle ? opt_options.pointerStyle : _ol_interaction_Extent_.getDefaultPointerStyleFunction_(),
    updateWhileAnimating: true,
    updateWhileInteracting: true
  });

  if (opt_options.extent) {
    this.setExtent(opt_options.extent);
  }
};

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

/**
 * @param {ol.MapBrowserEvent} mapBrowserEvent Event.
 * @return {boolean} Propagate event?
 * @this {ol.interaction.Extent}
 * @private
 */
_ol_interaction_Extent_.handleEvent_ = function (mapBrowserEvent) {
  if (!(mapBrowserEvent instanceof _mapbrowserpointerevent2.default)) {
    return true;
  }
  //display pointer (if not dragging)
  if (mapBrowserEvent.type == _mapbrowsereventtype2.default.POINTERMOVE && !this.handlingDownUpSequence) {
    this.handlePointerMove_(mapBrowserEvent);
  }
  //call pointer to determine up/down/drag
  _pointer2.default.handleEvent.call(this, mapBrowserEvent);
  //return false to stop propagation
  return false;
};

/**
 * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event.
 * @return {boolean} Event handled?
 * @this {ol.interaction.Extent}
 * @private
 */
_ol_interaction_Extent_.handleDownEvent_ = function (mapBrowserEvent) {
  var pixel = mapBrowserEvent.pixel;
  var map = mapBrowserEvent.map;

  var extent = this.getExtent();
  var vertex = this.snapToVertex_(pixel, map);

  //find the extent corner opposite the passed corner
  var getOpposingPoint = function (point) {
    var x_ = null;
    var y_ = null;
    if (point[0] == extent[0]) {
      x_ = extent[2];
    } else if (point[0] == extent[2]) {
      x_ = extent[0];
    }
    if (point[1] == extent[1]) {
      y_ = extent[3];
    } else if (point[1] == extent[3]) {
      y_ = extent[1];
    }
    if (x_ !== null && y_ !== null) {
      return [x_, y_];
    }
    return null;
  };
  if (vertex && extent) {
    var x = vertex[0] == extent[0] || vertex[0] == extent[2] ? vertex[0] : null;
    var y = vertex[1] == extent[1] || vertex[1] == extent[3] ? vertex[1] : null;

    //snap to point
    if (x !== null && y !== null) {
      this.pointerHandler_ = _ol_interaction_Extent_.getPointHandler_(getOpposingPoint(vertex));
      //snap to edge
    } else if (x !== null) {
      this.pointerHandler_ = _ol_interaction_Extent_.getEdgeHandler_(getOpposingPoint([x, extent[1]]), getOpposingPoint([x, extent[3]]));
    } else if (y !== null) {
      this.pointerHandler_ = _ol_interaction_Extent_.getEdgeHandler_(getOpposingPoint([extent[0], y]), getOpposingPoint([extent[2], y]));
    }
    //no snap - new bbox
  } else {
    vertex = map.getCoordinateFromPixel(pixel);
    this.setExtent([vertex[0], vertex[1], vertex[0], vertex[1]]);
    this.pointerHandler_ = _ol_interaction_Extent_.getPointHandler_(vertex);
  }
  return true; //event handled; start downup sequence
};

/**
 * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event.
 * @return {boolean} Event handled?
 * @this {ol.interaction.Extent}
 * @private
 */
_ol_interaction_Extent_.handleDragEvent_ = function (mapBrowserEvent) {
  if (this.pointerHandler_) {
    var pixelCoordinate = mapBrowserEvent.coordinate;
    this.setExtent(this.pointerHandler_(pixelCoordinate));
    this.createOrUpdatePointerFeature_(pixelCoordinate);
  }
  return true;
};

/**
 * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event.
 * @return {boolean} Stop drag sequence?
 * @this {ol.interaction.Extent}
 * @private
 */
_ol_interaction_Extent_.handleUpEvent_ = function (mapBrowserEvent) {
  this.pointerHandler_ = null;
  //If bbox is zero area, set to null;
  var extent = this.getExtent();
  if (!extent || _extent2.default.getArea(extent) === 0) {
    this.setExtent(null);
  }
  return false; //Stop handling downup sequence
};

/**
 * Returns the default style for the drawn bbox
 *
 * @return {ol.StyleFunction} Default Extent style
 * @private
 */
_ol_interaction_Extent_.getDefaultExtentStyleFunction_ = function () {
  var style = _style2.default.createDefaultEditing();
  return function (feature, resolution) {
    return style[_geometrytype2.default.POLYGON];
  };
};

/**
 * Returns the default style for the pointer
 *
 * @return {ol.StyleFunction} Default pointer style
 * @private
 */
_ol_interaction_Extent_.getDefaultPointerStyleFunction_ = function () {
  var style = _style2.default.createDefaultEditing();
  return function (feature, resolution) {
    return style[_geometrytype2.default.POINT];
  };
};

/**
 * @param {ol.Coordinate} fixedPoint corner that will be unchanged in the new extent
 * @returns {function (ol.Coordinate): ol.Extent} event handler
 * @private
 */
_ol_interaction_Extent_.getPointHandler_ = function (fixedPoint) {
  return function (point) {
    return _extent2.default.boundingExtent([fixedPoint, point]);
  };
};

/**
 * @param {ol.Coordinate} fixedP1 first corner that will be unchanged in the new extent
 * @param {ol.Coordinate} fixedP2 second corner that will be unchanged in the new extent
 * @returns {function (ol.Coordinate): ol.Extent|null} event handler
 * @private
 */
_ol_interaction_Extent_.getEdgeHandler_ = function (fixedP1, fixedP2) {
  if (fixedP1[0] == fixedP2[0]) {
    return function (point) {
      return _extent2.default.boundingExtent([fixedP1, [point[0], fixedP2[1]]]);
    };
  } else if (fixedP1[1] == fixedP2[1]) {
    return function (point) {
      return _extent2.default.boundingExtent([fixedP1, [fixedP2[0], point[1]]]);
    };
  } else {
    return null;
  }
};

/**
 * @param {ol.Extent} extent extent
 * @returns {Array<Array<ol.Coordinate>>} extent line segments
 * @private
 */
_ol_interaction_Extent_.getSegments_ = function (extent) {
  return [[[extent[0], extent[1]], [extent[0], extent[3]]], [[extent[0], extent[3]], [extent[2], extent[3]]], [[extent[2], extent[3]], [extent[2], extent[1]]], [[extent[2], extent[1]], [extent[0], extent[1]]]];
};

/**
 * @param {ol.Pixel} pixel cursor location
 * @param {ol.PluggableMap} map map
 * @returns {ol.Coordinate|null} snapped vertex on extent
 * @private
 */
_ol_interaction_Extent_.prototype.snapToVertex_ = function (pixel, map) {
  var pixelCoordinate = map.getCoordinateFromPixel(pixel);
  var sortByDistance = function (a, b) {
    return _coordinate2.default.squaredDistanceToSegment(pixelCoordinate, a) - _coordinate2.default.squaredDistanceToSegment(pixelCoordinate, b);
  };
  var extent = this.getExtent();
  if (extent) {
    //convert extents to line segments and find the segment closest to pixelCoordinate
    var segments = _ol_interaction_Extent_.getSegments_(extent);
    segments.sort(sortByDistance);
    var closestSegment = segments[0];

    var vertex = _coordinate2.default.closestOnSegment(pixelCoordinate, closestSegment);
    var vertexPixel = map.getPixelFromCoordinate(vertex);

    //if the distance is within tolerance, snap to the segment
    if (_coordinate2.default.distance(pixel, vertexPixel) <= this.pixelTolerance_) {
      //test if we should further snap to a vertex
      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);
      var dist = Math.sqrt(Math.min(squaredDist1, squaredDist2));
      this.snappedToVertex_ = dist <= this.pixelTolerance_;
      if (this.snappedToVertex_) {
        vertex = squaredDist1 > squaredDist2 ? closestSegment[1] : closestSegment[0];
      }
      return vertex;
    }
  }
  return null;
};

/**
 * @param {ol.MapBrowserEvent} mapBrowserEvent pointer move event
 * @private
 */
_ol_interaction_Extent_.prototype.handlePointerMove_ = function (mapBrowserEvent) {
  var pixel = mapBrowserEvent.pixel;
  var map = mapBrowserEvent.map;

  var vertex = this.snapToVertex_(pixel, map);
  if (!vertex) {
    vertex = map.getCoordinateFromPixel(pixel);
  }
  this.createOrUpdatePointerFeature_(vertex);
};

/**
 * @param {ol.Extent} extent extent
 * @returns {ol.Feature} extent as featrue
 * @private
 */
_ol_interaction_Extent_.prototype.createOrUpdateExtentFeature_ = function (extent) {
  var extentFeature = this.extentFeature_;

  if (!extentFeature) {
    if (!extent) {
      extentFeature = new _feature2.default({});
    } else {
      extentFeature = new _feature2.default(_polygon2.default.fromExtent(extent));
    }
    this.extentFeature_ = extentFeature;
    this.extentOverlay_.getSource().addFeature(extentFeature);
  } else {
    if (!extent) {
      extentFeature.setGeometry(undefined);
    } else {
      extentFeature.setGeometry(_polygon2.default.fromExtent(extent));
    }
  }
  return extentFeature;
};

/**
 * @param {ol.Coordinate} vertex location of feature
 * @returns {ol.Feature} vertex as feature
 * @private
 */
_ol_interaction_Extent_.prototype.createOrUpdatePointerFeature_ = function (vertex) {
  var vertexFeature = this.vertexFeature_;
  if (!vertexFeature) {
    vertexFeature = new _feature2.default(new _point2.default(vertex));
    this.vertexFeature_ = vertexFeature;
    this.vertexOverlay_.getSource().addFeature(vertexFeature);
  } else {
    var geometry = /** @type {ol.geom.Point} */vertexFeature.getGeometry();
    geometry.setCoordinates(vertex);
  }
  return vertexFeature;
};

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

/**
 * Returns the current drawn extent in the view projection
 *
 * @return {ol.Extent} Drawn extent in the view projection.
 * @api
 */
_ol_interaction_Extent_.prototype.getExtent = function () {
  return this.extent_;
};

/**
 * Manually sets the drawn extent, using the view projection.
 *
 * @param {ol.Extent} extent Extent
 * @api
 */
_ol_interaction_Extent_.prototype.setExtent = function (extent) {
  //Null extent means no bbox
  this.extent_ = extent ? extent : null;
  this.createOrUpdateExtentFeature_(extent);
  this.dispatchEvent(new _ol_interaction_Extent_.Event(this.extent_));
};

/**
 * @classdesc
 * Events emitted by {@link ol.interaction.Extent} instances are instances of
 * this type.
 *
 * @constructor
 * @implements {oli.ExtentEvent}
 * @param {ol.Extent} extent the new extent
 * @extends {ol.events.Event}
 */
_ol_interaction_Extent_.Event = function (extent) {
  _event2.default.call(this, _extenteventtype2.default.EXTENTCHANGED);

  /**
   * The current extent.
   * @type {ol.Extent}
   * @api
   */
  this.extent = extent;
};
_index2.default.inherits(_ol_interaction_Extent_.Event, _event2.default);
exports.default = _ol_interaction_Extent_;
module.exports = exports['default'];