/*
 * L.Point represents a point with x and y coordinates.
 */

L.Point = function (/*Number*/ x, /*Number*/ y, /*Boolean*/ round) {
	this.x = (round ? Math.round(x) : x);
	this.y = (round ? Math.round(y) : y);
};

L.Point.prototype = {
	add: function (point) {
		return this.clone()._add(L.point(point));
	},

	_add: function (point) {
		this.x += point.x;
		this.y += point.y;
		return this;
	},

	subtract: function (point) {
		return this.clone()._subtract(L.point(point));
	},

	// destructive subtract (faster)
	_subtract: function (point) {
		this.x -= point.x;
		this.y -= point.y;
		return this;
	},

	divideBy: function (num, round) {
		return new L.Point(this.x / num, this.y / num, round);
	},

	multiplyBy: function (num, round) {
		return new L.Point(this.x * num, this.y * num, round);
	},

	distanceTo: function (point) {
		point = L.point(point);

		var x = point.x - this.x,
			y = point.y - this.y;

		return Math.sqrt(x * x + y * y);
	},

	round: function () {
		return this.clone()._round();
	},

	// destructive round
	_round: function () {
		this.x = Math.round(this.x);
		this.y = Math.round(this.y);
		return this;
	},

	floor: function () {
		return this.clone()._floor();
	},

	_floor: function () {
		this.x = Math.floor(this.x);
		this.y = Math.floor(this.y);
		return this;
	},

	clone: function () {
		return new L.Point(this.x, this.y);
	},

	toString: function () {
		return 'Point(' +
				L.Util.formatNum(this.x) + ', ' +
				L.Util.formatNum(this.y) + ')';
	}
};

L.point = function (x, y, round) {
	if (x instanceof L.Point) {
		return x;
	}
	if (x instanceof Array) {
		return new L.Point(x[0], x[1]);
	}
	if (isNaN(x)) {
		return x;
	}
	return new L.Point(x, y, round);
};


keep keep keep keep keep replace keep keep keep keep

needOpacityUpdate = (this.options.opacity < 1);

/*
 * L.Handler.ShiftDragZoom is used internally by L.Map to add shift-drag zoom (zoom to a selected bounding box).
 */

L.Map.mergeOptions({
	boxZoom: true
});

L.Map.BoxZoom = L.Handler.extend({
	initialize: function (map) {
		this._map = map;
		this._container = map._container;
		this._pane = map._panes.overlayPane;
	},

	addHooks: function () {
		L.DomEvent.on(this._container, 'mousedown', this._onMouseDown, this);
	},

	removeHooks: function () {
		L.DomEvent.off(this._container, 'mousedown', this._onMouseDown);
	},

	_onMouseDown: function (e) {
		if (!e.shiftKey || ((e.which !== 1) && (e.button !== 1))) { return false; }

		L.DomUtil.disableTextSelection();

		this._startLayerPoint = this._map.mouseEventToLayerPoint(e);

		this._box = L.DomUtil.create('div', 'leaflet-zoom-box', this._pane);
		L.DomUtil.setPosition(this._box, this._startLayerPoint);

		//TODO refactor: move cursor to styles
		this._container.style.cursor = 'crosshair';

		L.DomEvent
			.on(document, 'mousemove', this._onMouseMove, this)
			.on(document, 'mouseup', this._onMouseUp, this)
			.preventDefault(e);

		this._map.fire("boxzoomstart");
	},

	_onMouseMove: function (e) {
		var startPoint = this._startLayerPoint,
			box = this._box,

			layerPoint = this._map.mouseEventToLayerPoint(e),
			offset = layerPoint.subtract(startPoint),

			newPos = new L.Point(
				Math.min(layerPoint.x, startPoint.x),
				Math.min(layerPoint.y, startPoint.y));

		L.DomUtil.setPosition(box, newPos);

		// TODO refactor: remove hardcoded 4 pixels
		box.style.width  = (Math.abs(offset.x) - 4) + 'px';
		box.style.height = (Math.abs(offset.y) - 4) + 'px';
	},

	_onMouseUp: function (e) {
		this._pane.removeChild(this._box);
		this._container.style.cursor = '';

		L.DomUtil.enableTextSelection();

		L.DomEvent
			.off(document, 'mousemove', this._onMouseMove)
			.off(document, 'mouseup', this._onMouseUp);

		var map = this._map,
			layerPoint = map.mouseEventToLayerPoint(e);

		var bounds = new L.LatLngBounds(
				map.layerPointToLatLng(this._startLayerPoint),
				map.layerPointToLatLng(layerPoint));

		map.fitBounds(bounds);

		map.fire("boxzoomend", {
			boxZoomBounds: bounds
		});
	}
});

L.Map.addInitHook('addHandler', 'boxZoom', L.Map.BoxZoom);