//>>built
define("dojox/geo/charting/TouchInteractionSupport", ["dojo/_base/lang","dojo/_base/declare","dojo/_base/event", "dojo/_base/connect","dojo/_base/window"],
  function(lang,declare,event,connect,win) {

return declare("dojox.geo.charting.TouchInteractionSupport",null, {
	//	summary: 
	//		class to handle touch interactions on a dojox.geo.charting.Map widget
	//	tags:
	//		private
	
	_map : null,
	_centerTouchLocation : null,
	_touchMoveListener: null,
	_touchEndListener: null,
	_touchEndTapListener: null,
	_touchStartListener: null,
	_initialFingerSpacing: null,
	_initialScale: null,
	_tapCount: null,
	_tapThreshold: null,
	_lastTap: null,
	_doubleTapPerformed:false,
	_oneFingerTouch:false,
	_tapCancel:false,
	
	constructor : function(/* dojox.geo.charting.Map */map,options) {
		//	summary: 
		//		Constructs a new _TouchInteractionSupport instance
		//	map: dojox.geo.charting.Map
		//		the Map widget this class provides touch navigation for.
		this._map = map;
		this._centerTouchLocation = {x: 0,y: 0};		
				
		this._tapCount = 0;
		this._lastTap = {x: 0,y: 0};
		this._tapThreshold = 100; // square distance in pixels
	},

	connect: function() {
		//	summary: 
		//		install touch listeners
		_touchStartListener = this._map.surface.connect("touchstart", this, this._touchStartHandler);
	},

	disconnect: function() {
		//	summary: 
		//		disconnects any installed listeners. Must be called only when disposing of this instance 
		if (this._touchStartListener) {
			connect.disconnect(this._touchStartListener);
			this._touchStartListener = null;
		}
	},

	_getTouchBarycenter: function(touchEvent) {
		//	summary: 
		//		returns the midpoint of the two first fingers (or the first finger location if only one)
		//	touchEvent: a touch event
		//	returns: dojox.gfx.Point
		//		the midpoint
		//	tags:
		//		private
		var touches = touchEvent.touches;
		var firstTouch = touches[0];
		var secondTouch = null;
		if (touches.length > 1) {
			secondTouch = touches[1];
		} else {
			secondTouch = touches[0];
		}
		var containerBounds = this._map._getContainerBounds();
		var middleX = (firstTouch.pageX + secondTouch.pageX) / 2.0 - containerBounds.x;
		var middleY = (firstTouch.pageY + secondTouch.pageY) / 2.0 - containerBounds.y;
		return {x: middleX,y: middleY};
	},

	_getFingerSpacing: function(touchEvent) {
		//	summary: 
		//		computes the distance between the first two fingers
		//	touchEvent: a touch event
		//	returns: float
		//		a distance. -1 if less that 2 fingers
		//	tags:
		//		private
		var touches = touchEvent.touches;
		var spacing = -1;
		if (touches.length >= 2) {
			var dx = (touches[1].pageX - touches[0].pageX);
			var dy = (touches[1].pageY - touches[0].pageY);
			spacing = Math.sqrt(dx*dx + dy*dy);
		}
		return spacing;
	},

	_isDoubleTap: function(touchEvent) {
		//	summary: 
		//		checks whether the specified touchStart event is a double tap 
		//		(i.e. follows closely a previous touchStart at approximately the same location)
		//	touchEvent: a touch event
		//	returns: boolean
		//		true if this event is considered a double tap
		//	tags:
		//		private
		var isDoubleTap = false;
		var touches = touchEvent.touches;
		if ((this._tapCount > 0) && touches.length == 1) {
			// test distance from last tap
			var dx = (touches[0].pageX - this._lastTap.x);
			var dy = (touches[0].pageY - this._lastTap.y);
			var distance = dx*dx + dy*dy;
			if (distance < this._tapThreshold) {
				isDoubleTap = true;
			} else {
				this._tapCount = 0;
			}
		}
		this._tapCount++;
		this._lastTap.x = touches[0].pageX;
		this._lastTap.y = touches[0].pageY;
		setTimeout(lang.hitch(this,function() {
		this._tapCount = 0;}),300);
		return isDoubleTap;
	},

	_doubleTapHandler: function(touchEvent) {
		//	summary: 
		//		action performed on the map when a double tap was triggered 
		//	touchEvent: a touch event
		//	tags:
		//		private
		var feature = this._getFeatureFromTouchEvent(touchEvent);
		if (feature) {
			this._map.fitToMapArea(feature._bbox, 15, true);
		} else {
			// perform a basic 2x zoom on touch
			var touches = touchEvent.touches;
			var containerBounds = this._map._getContainerBounds();
			var offX = touches[0].pageX - containerBounds.x;
			var offY = touches[0].pageY - containerBounds.y;
			// clicked map point before zooming
			var mapPoint = this._map.screenCoordsToMapCoords(offX,offY);
			// zoom increment power
			this._map.setMapCenterAndScale(mapPoint.x, mapPoint.y,this._map.getMapScale()*2,true);
		}
	},

	_getFeatureFromTouchEvent: function(touchEvent) {
		//	summary: 
		//		utility function to return the feature located at this touch event location
		//	touchEvent: a touch event
		//	returns: dojox.geo.charting.Feature
		//		the feature found if any, null otherwise.
		//	tags:
		//		private
		var feature = null;
		if (touchEvent.gfxTarget && touchEvent.gfxTarget.getParent) {
			feature = this._map.mapObj.features[touchEvent.gfxTarget.getParent().id];
		}
		return feature;
	},

	_touchStartHandler: function(touchEvent){
		//	summary: 
		//		action performed on the map when a touch start was triggered 
		//	touchEvent: a touch event
		//	tags:
		//		private
		event.stop(touchEvent);
		this._oneFingerTouch = (touchEvent.touches.length == 1);
		this._tapCancel = !this._oneFingerTouch;
		// test double tap
		this._doubleTapPerformed = false;
		if (this._isDoubleTap(touchEvent)) {
			//console.log("double tap recognized");
			this._doubleTapHandler(touchEvent);
			this._doubleTapPerformed = true;
			return;
		}
		// compute map midpoint between fingers		
		var middlePoint = this._getTouchBarycenter(touchEvent);
		var mapPoint = this._map.screenCoordsToMapCoords(middlePoint.x,middlePoint.y);
		this._centerTouchLocation.x = mapPoint.x;
		this._centerTouchLocation.y = mapPoint.y;
		// store initial finger spacing to compute zoom later
		this._initialFingerSpacing = this._getFingerSpacing(touchEvent);
		// store initial map scale
		this._initialScale = this._map.getMapScale();
		// install touch move and up listeners (if not done by other fingers before)
		if (!this._touchMoveListener) 
			this._touchMoveListener = connect.connect(win.global,"touchmove",this,this._touchMoveHandler);
		if (!this._touchEndTapListener) 
			this._touchEndTapListener = this._map.surface.connect("touchend", this, this._touchEndTapHandler);
		if (!this._touchEndListener) 
			this._touchEndListener = connect.connect(win.global,"touchend",this, this._touchEndHandler);
	},

	_touchEndTapHandler: function(touchEvent) {
		//	summary: 
		//		action performed on the map when a tap was triggered 
		//	touchEvent: a touch event
		//	tags:
		//		private
		var touches = touchEvent.touches;
		if (touches.length == 0) {
			
			// test potential tap ?
			if (this._oneFingerTouch && !this._tapCancel) {
				this._oneFingerTouch = false;
				setTimeout(lang.hitch(this,function() {
					// wait to check if double tap
					// perform test for single tap
					//console.log("double tap was performed ? " + this._doubleTapPerformed);
					if (!this._doubleTapPerformed) {
						// test distance from last tap
						var dx = (touchEvent.changedTouches[0].pageX - this._lastTap.x);
						var dy = (touchEvent.changedTouches[0].pageY - this._lastTap.y);
						var distance = dx*dx + dy*dy;
						if (distance < this._tapThreshold) {
							// single tap ok
							this._singleTapHandler(touchEvent);
						}
					}					
				}),350);
			}
			this._tapCancel = false;
			
		}
	},

	_touchEndHandler: function(touchEvent) {
		//	summary: 
		//		action performed on the map when a touch end was triggered 
		//	touchEvent: a touch event
		//	tags:
		//		private
		event.stop(touchEvent);
		var touches = touchEvent.touches;
		if (touches.length == 0) {
			// disconnect listeners only when all fingers are up
			if (this._touchMoveListener) {
				connect.disconnect(this._touchMoveListener);
				this._touchMoveListener = null;
			}
			if (this._touchEndListener) {
				connect.disconnect(this._touchEndListener);
				this._touchEndListener = null;
			}
		} else {
			// recompute touch center
			var middlePoint = this._getTouchBarycenter(touchEvent);
			var mapPoint = this._map.screenCoordsToMapCoords(middlePoint.x,middlePoint.y);
			this._centerTouchLocation.x = mapPoint.x;
			this._centerTouchLocation.y = mapPoint.y;
		}
	},
	
	_singleTapHandler: function(touchEvent) {
		//	summary: 
		//		action performed on the map when a single tap was triggered 
		//	touchEvent: a touch event
		//	tags:
		//		private
		var feature = this._getFeatureFromTouchEvent(touchEvent);
		if (feature) {
			// call feature handler
			feature._onclickHandler(touchEvent);
		} else {
			// unselect all
			for (var name in this._map.mapObj.features){
				this._map.mapObj.features[name].select(false);
			}
			this._map.onFeatureClick(null);
		}
	},

	_touchMoveHandler: function(touchEvent){
		//	summary: 
		//		action performed on the map when a touch move was triggered 
		//	touchEvent: a touch event
		//	tags:
		//		private

		// prevent browser interaction
		event.stop(touchEvent);

		// cancel tap if moved too far from first touch location
		if (!this._tapCancel) {
			var dx = (touchEvent.touches[0].pageX - this._lastTap.x),
				dy = (touchEvent.touches[0].pageY - this._lastTap.y);
			var distance = dx*dx + dy*dy;
			if (distance > this._tapThreshold) {
				this._tapCancel = true;
			}
		}
		var middlePoint = this._getTouchBarycenter(touchEvent);
		// compute map offset
		var mapPoint = this._map.screenCoordsToMapCoords(middlePoint.x,middlePoint.y),
			mapOffsetX = mapPoint.x - this._centerTouchLocation.x,
			mapOffsetY = mapPoint.y - this._centerTouchLocation.y;
		// compute scale factor
		var scaleFactor = 1;
		var touches = touchEvent.touches;
		if (touches.length >= 2) {
			var fingerSpacing = this._getFingerSpacing(touchEvent);
			scaleFactor = fingerSpacing / this._initialFingerSpacing;
			// scale map
			this._map.setMapScale(this._initialScale*scaleFactor);
		}
		// adjust map center on barycentre
		var currentMapCenter = this._map.getMapCenter();
		this._map.setMapCenter(currentMapCenter.x - mapOffsetX, currentMapCenter.y - mapOffsetY);
	}
});
});
