define([
	'dojo/on',
	'dojo/query'
], function (on, query) {
	// This module exposes useful functions for working with touch devices.

	var util = {
		// Overridable defaults related to extension events defined below.
		tapRadius: 10,
		dbltapTime: 250,

		selector: function (selector, eventType, children) {
			// summary:
			//		Reimplementation of on.selector, taking an iOS quirk into account
			return function (target, listener) {
				var bubble = eventType.bubble;
				if (bubble) {
					// the event type doesn't naturally bubble, but has a bubbling form, use that
					eventType = bubble;
				}
				else if (children !== false) {
					// for normal bubbling events we default to allowing children of the selector
					children = true;
				}
				return on(target, eventType, function (event) {
					var eventTarget = event.target;

					// iOS tends to report the text node an event was fired on, rather than
					// the top-level element; this may end up causing errors in selector engines
					if (eventTarget.nodeType === 3) {
						eventTarget = eventTarget.parentNode;
					}

					// there is a selector, so make sure it matches
					while (!query.matches(eventTarget, selector, target)) {
						if (eventTarget === target || !children || !(eventTarget = eventTarget.parentNode)) {
							return;
						}
					}
					return listener.call(eventTarget, event);
				});
			};
		},

		countCurrentTouches: function (evt, node) {
			// summary:
			//		Given a touch event and a DOM node, counts how many current touches
			//		presently lie within that node.  Useful in cases where an accurate
			//		count is needed but tracking changedTouches won't suffice because
			//		other handlers stop events from bubbling high enough.

			if (!('touches' in evt)) {
				// Not a touch event (perhaps called from a mouse event on a
				// platform supporting touch events)
				return -1;
			}

			var i, numTouches, touch;
			for (i = 0, numTouches = 0; (touch = evt.touches[i]); ++i) {
				if (node.contains(touch.target)) {
					++numTouches;
				}
			}
			return numTouches;
		}
	};

	function handleTapStart(target, listener, evt, prevent) {
		// Common function for handling tap detection.
		// The passed listener will only be fired when and if a touchend is fired
		// which confirms the overall gesture resembled a tap.

		if (evt.targetTouches.length > 1) {
			return; // ignore multitouch
		}

		var start = evt.changedTouches[0],
			startX = start.screenX,
			startY = start.screenY;

		prevent && evt.preventDefault();

		var endListener = on(target, 'touchend', function (evt) {
			var end = evt.changedTouches[0];
			if (!evt.targetTouches.length) {
				// only call listener if this really seems like a tap
				if (Math.abs(end.screenX - startX) < util.tapRadius &&
						Math.abs(end.screenY - startY) < util.tapRadius) {
					prevent && evt.preventDefault();
					listener.call(this, evt);
				}
				endListener.remove();
			}
		});
	}

	function tap(target, listener) {
		// Function usable by dojo/on as a synthetic tap event.
		return on(target, 'touchstart', function (evt) {
			handleTapStart(target, listener, evt);
		});
	}

	function dbltap(target, listener) {
		// Function usable by dojo/on as a synthetic double-tap event.
		var first, timeout;

		return on(target, 'touchstart', function (evt) {
			if (!first) {
				// first potential tap: detect as usual, but with specific logic
				handleTapStart(target, function (evt) {
					first = evt.changedTouches[0];
					timeout = setTimeout(function () {
						first = timeout = null;
					}, util.dbltapTime);
				}, evt);
			}
			else {
				handleTapStart(target, function (evt) {
					// bail out if first was cleared between 2nd touchstart and touchend
					if (!first) {
						return;
					}
					var second = evt.changedTouches[0];
					// only call listener if both taps occurred near the same place
					if (Math.abs(second.screenX - first.screenX) < util.tapRadius &&
							Math.abs(second.screenY - first.screenY) < util.tapRadius) {
						timeout && clearTimeout(timeout);
						first = timeout = null;
						listener.call(this, evt);
					}
				}, evt, true);
			}
		});
	}

	util.tap = tap;
	util.dbltap = dbltap;

	return util;
});