define("dojox/calendar/Touch", ["dojo/_base/array", "dojo/_base/lang", "dojo/_base/declare", "dojo/dom", "dojo/dom-geometry", "dojo/_base/window", "dojo/on", "dojo/_base/event", "dojo/keys"],

	function(arr, lang, declare, dom, domGeometry, win, on, event, keys){

	return declare("dojox.calendar.Touch", null, {

		// summary:
		//		This plugin is managing the touch interactions on item renderers displayed by a calendar view.

		// touchStartEditingTimer: Integer
		//		The delay of one touch over the renderer before setting the item in editing mode.
		touchStartEditingTimer: 500,

		// touchEndEditingTimer: Integer
		//		The delay after which the item is leaving the editing mode after the previous editing gesture, in touch context.
		touchEndEditingTimer: 10000,

		postMixInProperties: function(){

			this.on("rendererCreated", lang.hitch(this, function(irEvent){

				var renderer = irEvent.renderer.renderer;

				this.own(on(renderer.domNode, "touchstart", lang.hitch(this, function(e){
					this._onRendererTouchStart(e, renderer);
				})));

			}));
		},

		_onRendererTouchStart: function(e, renderer){
			// tags:
			//		private
			var p = this._edProps;

			if(p && p.endEditingTimer){
				clearTimeout(p.endEditingTimer);
				p.endEditingTimer = null;
			}

			var theItem = renderer.item.item;

			if(p && p.endEditingTimer){
				clearTimeout(p.endEditingTimer);
				p.endEditingTimer = null;
			}

			if(p != null && p.item != theItem){
				// another item is edited.
				// stop previous item
				if(p.startEditingTimer){
					clearTimeout(p.startEditingTimer);
				}

				this._endItemEditing("touch", false);
				p = null;

			}

			// initialize editing properties
			if(!p){

				// register event listeners to manage gestures.
				var handles = [];

				handles.push(on(win.doc, "touchend", lang.hitch(this, this._docEditingTouchEndHandler)));
				handles.push(on(this.itemContainer, "touchmove", lang.hitch(this, this._docEditingTouchMoveHandler)));

				this._setEditingProperties({
					touchMoved: false,
					item: theItem,
					renderer: renderer,
					rendererKind: renderer.rendererKind,
					event: e,
					handles: handles,
					liveLayout: this.liveLayout
				});

				p = this._edProps;
			}

			if(this._isEditing){

				// get info on touches
				lang.mixin(p, this._getTouchesOnRenderers(e, p.editedItem));

				// start an editing gesture.
				this._startTouchItemEditingGesture(e);

			} else {

				// initial touch that will trigger or not the editing

				if(e.touches.length > 1){
					event.stop(e);
					return;
				}

				// set the selection state without dispatching (on touch end) after a short amount of time.
				// to allow a bit of time to scroll without selecting (graphically at least)
				this._touchSelectionTimer = setTimeout(lang.hitch(this, function(){

					this._saveSelectedItems = this.get("selectedItems");

					var changed = this.selectFromEvent(e, theItem._item, renderer, false);

					if(changed){
						this._pendingSelectedItem = theItem;
					}else{
						delete this._saveSelectedItems;
					}
					this._touchSelectionTimer = null;
				}), 200);

				p.start = {x: e.touches[0].screenX, y: e.touches[0].screenY};

				if(this.isItemEditable(p.item, p.rendererKind)){

					// editing gesture timer
					this._edProps.startEditingTimer = setTimeout(lang.hitch(this, function(){

						// we are editing, so the item *must* be selected.
						if(this._touchSelectionTimer){
							clearTimeout(this._touchSelectionTimer);
							delete this._touchSelectionTime;
						}
						if(this._pendingSelectedItem){
							this.dispatchChange(this._saveSelectedItems == null ? null : this._saveSelectedItems[0], this._pendingSelectedItem, null, e);
							delete this._saveSelectedItems;
							delete this._pendingSelectedItem;
						}else{
							this.selectFromEvent(e, theItem._item, renderer);
						}

						this._startItemEditing(p.item, "touch", e);

						p.moveTouchIndex = 0;

						// A move gesture is initiated even if we don't move
						this._startItemEditingGesture([this.getTime(e)], "move", "touch", e);

					}), this.touchStartEditingTimer);

				}
			}
		},

		_docEditingTouchMoveHandler: function(e){
			// tags:
			//		private
			var p = this._edProps;

			// When the screen is touched, it can dispatch move events if the
			// user press the finger a little more...
			var touch = {x: e.touches[0].screenX, y: e.touches[0].screenY};
			if(p.startEditingTimer &&
					(Math.abs(touch.x - p.start.x) > 25 ||
					 Math.abs(touch.y - p.start.y) > 25)) {

				// scroll use case, do not edit
				clearTimeout(p.startEditingTimer);
				p.startEditingTimer = null;

				clearTimeout(this._touchSelectionTimer);
				this._touchSelectionTimer = null;

				if(this._pendingSelectedItem){
					delete this._pendingSelectedItem;
					this.selectFromEvent(e, null, null, false);
				}
			}
			
			// 10px tolerance to select the item (#105)
			if(!p.touchMoved &&
				(Math.abs(touch.x - p.start.x) > 10 ||
				 Math.abs(touch.y - p.start.y) > 10)) {
				p.touchMoved = true;
			}

			if(this._editingGesture){

				event.stop(e);

				if(p.itemBeginDispatched){

					var times = [];
					var d = p.editKind == "resizeEnd" ? p.editedItem.endTime : p.editedItem.startTime;
					var subColumn = p.editedItem.subColumn;

					switch(p.editKind){
						case "move":
						  var touchIndex = p.moveTouchIndex == null || p.moveTouchIndex < 0 ? 0 : p.moveTouchIndex;
							times[0] = this.getTime(e, -1, -1, touchIndex);
							subColumn = this.getSubColumn(e, -1, -1, touchIndex);
							break;
						case "resizeStart":
							times[0] = this.getTime(e, -1, -1, p.resizeStartTouchIndex);
							break;
						case "resizeEnd":
							times[0] = this.getTime(e, -1, -1, p.resizeEndTouchIndex);
							break;
						case "resizeBoth":
							times[0] = this.getTime(e, -1, -1, p.resizeStartTouchIndex);
							times[1] = this.getTime(e, -1, -1, p.resizeEndTouchIndex);
							break;
					}

					this._moveOrResizeItemGesture(times, "touch", e, subColumn);

					if(p.editKind == "move"){
						if(this.renderData.dateModule.compare(p.editedItem.startTime, d) == -1){
							this.ensureVisibility(p.editedItem.startTime, p.editedItem.endTime, "start", this.autoScrollTouchMargin);
						}else{
							this.ensureVisibility(p.editedItem.startTime, p.editedItem.endTime, "end", this.autoScrollTouchMargin);
						}
					}else if(e.editKind == "resizeStart" || e.editKind == "resizeBoth"){
						this.ensureVisibility(p.editedItem.startTime, p.editedItem.endTime, "start", this.autoScrollTouchMargin);
					}else{
						this.ensureVisibility(p.editedItem.startTime, p.editedItem.endTime, "end", this.autoScrollTouchMargin);
					}

				}
			} // else scroll, if any, is delegated to sub class

		},

		// autoScrollTouchMargin: Integer
		//		The minimum number of minutes of margin around the edited event.
		autoScrollTouchMargin: 10,

		_docEditingTouchEndHandler: function(e){
			// tags:
			//		private
			event.stop(e);

			var p = this._edProps;

			if(p.startEditingTimer){
				clearTimeout(p.startEditingTimer);
				p.startEditingTimer = null;
			}

			if(this._isEditing){

				lang.mixin(p, this._getTouchesOnRenderers(e, p.editedItem));

				if(this._editingGesture){

					if(p.touchesLen == 0){

						// all touches were removed => end of editing gesture
						this._endItemEditingGesture("touch", e);

						if(this.touchEndEditingTimer > 0){

							// Timer that trigger the end of the item editing mode.
							p.endEditingTimer = setTimeout(lang.hitch(this, function(){

								this._endItemEditing("touch", false);

							}), this.touchEndEditingTimer);
						} // else validation must be explicit

					}else{

						if(this._editingGesture){
							this._endItemEditingGesture("touch", e);
						}
						// there touches of interest on item, process them.
						this._startTouchItemEditingGesture(e);
					}
				}

			}else if(!p.touchMoved){

				event.stop(e);

				arr.forEach(p.handles, function(handle){
					handle.remove();
				});

				if(this._touchSelectionTimer){
					// selection timer was not reached to a proper selection.
					clearTimeout(this._touchSelectionTimer);
					this.selectFromEvent(e, p.item._item, p.renderer, true);

				}else if(this._pendingSelectedItem){
					// selection timer was reached, dispatch change event
					this.dispatchChange(this._saveSelectedItems.length == 0 ? null : this._saveSelectedItems[0],
						this._pendingSelectedItem, null, e); // todo renderer ?
					delete this._saveSelectedItems;
					delete this._pendingSelectedItem;
				}

				if(this._pendingDoubleTap && this._pendingDoubleTap.item == p.item){
					this._onItemDoubleClick({
						triggerEvent: e,
						renderer: p.renderer,
						item: p.item._item
					});

					clearTimeout(this._pendingDoubleTap.timer);

					delete this._pendingDoubleTap;

				}else{

					this._pendingDoubleTap = {
						item: p.item,
						timer: setTimeout(lang.hitch(this, function(){
								delete this._pendingDoubleTap;
							}), this.doubleTapDelay)
					};

					this._onItemClick({
						triggerEvent: e,
						renderer: p.renderer,
						item: p.item._item
					});
				}

				this._edProps = null;

			}else{
				// scroll view has finished.

				if(this._saveSelectedItems){

					// selection without dipatching was done, but the view scrolled,
					// so revert last selection
				  this.set("selectedItems", this._saveSelectedItems);
					delete this._saveSelectedItems;
					delete this._pendingSelectedItem;
				}

				arr.forEach(p.handles, function(handle){
					handle.remove();
				});

				this._edProps = null;
			}
		},

		_startTouchItemEditingGesture: function(e){
			// summary:
			//		Determines if a editing gesture is starting according to touches.
			// tags:
			//		private

			var p = this._edProps;

			var fromResizeStart = p.resizeStartTouchIndex != -1;
			var fromResizeEnd = p.resizeEndTouchIndex != -1;

			if(fromResizeStart && fromResizeEnd || // initial gesture using two touches
					this._editingGesture && p.touchesLen == 2 &&
					(fromResizeEnd && p.editKind == "resizeStart" ||
					 fromResizeStart && p.editKind =="resizeEnd")){ // gesture one after the other touch

				if(this._editingGesture && p.editKind != "resizeBoth"){ // stop ongoing gesture
					this._endItemEditingGesture("touch", e);
				}

				p.editKind = "resizeBoth";

				this._startItemEditingGesture([this.getTime(e, -1, -1, p.resizeStartTouchIndex),
					this.getTime(e, -1, -1, p.resizeEndTouchIndex)],
					p.editKind, "touch", e);

			}else if(fromResizeStart && p.touchesLen == 1 && !this._editingGesture){

				this._startItemEditingGesture([this.getTime(e, -1, -1, p.resizeStartTouchIndex)],
					"resizeStart", "touch", e);

			}else if(fromResizeEnd && p.touchesLen == 1 && !this._editingGesture){

				this._startItemEditingGesture([this.getTime(e, -1, -1, p.resizeEndTouchIndex)],
					"resizeEnd", "touch", e);

			} else {
				// A move gesture is initiated even if we don't move
				this._startItemEditingGesture([this.getTime(e)], "move", "touch", e);
			}
		},

		_getTouchesOnRenderers: function(e, item){
			// summary:
			//		Returns the touch indices that are on a editing handles or body of the renderers
			// tags:
			//		private
			// item: Object
			//		The render item.
			// e: Event
			//		The touch event.
			// tags:
			//		private

			var irs = this._getStartEndRenderers(item);

			var resizeStartTouchIndex = -1;
			var resizeEndTouchIndex = -1;
			var moveTouchIndex = -1;
			var hasResizeStart = irs[0] != null && irs[0].resizeStartHandle != null;
			var hasResizeEnd = irs[1] != null && irs[1].resizeEndHandle != null;
			var len = 0;
			var touched = false;
			var list = this.rendererManager.itemToRenderer[item.id];

			for(var i=0; i<e.touches.length; i++){

				if(resizeStartTouchIndex == -1 && hasResizeStart){
					touched = dom.isDescendant(e.touches[i].target, irs[0].resizeStartHandle);
					if(touched){
						resizeStartTouchIndex = i;
						len++;
					}
				}

				if(resizeEndTouchIndex == -1 && hasResizeEnd){
					touched = dom.isDescendant(e.touches[i].target, irs[1].resizeEndHandle);
					if(touched){
						resizeEndTouchIndex = i;
						len++;
					}
				}

				if(resizeStartTouchIndex == -1 && resizeEndTouchIndex == -1){

					for (var j=0; j<list.length; j++){
					  touched = dom.isDescendant(e.touches[i].target, list[j].container);
						if(touched){
							moveTouchIndex = i;
							len++;
							break;
						}
					}
				}

				if(resizeStartTouchIndex != -1 && resizeEndTouchIndex != -1 && moveTouchIndex != -1){
					// all touches of interest were found, ignore other ones.
				  break;
				}
			}

			return {
				touchesLen: len,
				resizeStartTouchIndex: resizeStartTouchIndex,
				resizeEndTouchIndex: resizeEndTouchIndex,
				moveTouchIndex: moveTouchIndex
			};
		}

	});

});
