define([
	"dojo/_base/kernel",
	"dojo/_base/declare",
	"dojo/_base/lang",
	"dojo/_base/window",
	"dojo/dom-geometry",
	"dojo/dom-style",
	"dojo/dom-attr",
	"dojo/window",
	"dojo/touch",
	"dijit/form/_AutoCompleterMixin",
	"dijit/popup",
	"./_ComboBoxMenu",
	"./TextBox",
	"./sniff"
], function(kernel, declare, lang, win, domGeometry, domStyle, domAttr, windowUtils, touch, AutoCompleterMixin, popup, ComboBoxMenu, TextBox, has){
	kernel.experimental("dojox.mobile.ComboBox"); // should be using a more native search-type UI

	return declare("dojox.mobile.ComboBox", [TextBox, AutoCompleterMixin], {
		// summary:
		//		A non-templated auto-completing text box widget.

		// dropDownClass: [protected extension] String
		//		Name of the drop-down widget class used to select a date/time.
		//		Should be specified by subclasses.
		dropDownClass: "dojox.mobile._ComboBoxMenu",

		// initially disable selection since iphone displays selection handles
		// that makes it hard to pick from the list
		
		// selectOnClick: Boolean
		//		Flag which enables the selection on click.
		selectOnClick: false,
		
		// autoComplete: Boolean
		//		Flag which enables the auto-completion.
		autoComplete: false,

		// dropDown: [protected] Widget
		//		The widget to display as a popup. This widget *must* be
		//		defined before the startup function is called.
		dropDown: null,

		// maxHeight: [protected] int
		//		The maximum height for the drop-down.
		//		Any drop-down taller than this value will have scrollbars.
		//		Set to -1 to limit the height to the available space in the viewport.
		maxHeight: -1,

		// dropDownPosition: [const] String[]
		//		This variable controls the position of the drop-down.
		//		It is an array of strings with the following values:
		//
		//		- before: places drop down to the left of the target node/widget, or to the right in
		//		  the case of RTL scripts like Hebrew and Arabic
		//		- after: places drop down to the right of the target node/widget, or to the left in
		//		  the case of RTL scripts like Hebrew and Arabic
		//		- above: drop down goes above target node
		//		- below: drop down goes below target node
		//
		//		The list is positions is tried, in order, until a position is found where the drop down fits
		//		within the viewport.
		dropDownPosition: ["below","above"],

		_throttleOpenClose: function(){
			// summary:
			//		Prevents the open/close in rapid succession.
			// tags:
			//		private
			if(this._throttleHandler){
				this._throttleHandler.remove();
			}
			this._throttleHandler = this.defer(function(){ this._throttleHandler = null; }, 500);
		},

		_onFocus: function(){
			// summary:
			//		Shows drop-down if the user is selecting Next/Previous from the virtual keyboard.
			// tags:
			//		private
			this.inherited(arguments);
			if(!this._opened && !this._throttleHandler){
				this._startSearchAll(); 
			}

			if(has("windows-theme")) {
				this.domNode.blur();
			}
		},

		onInput: function(e){
			if(!e || e.charCode !== 0){ // #18047
				this._onKey(e);
				this.inherited(arguments);
			}
		},

		_setListAttr: function(v){
			// tags:
			//		private
			this._set('list', v); // needed for Firefox 4+ to prevent HTML5 mode
		},

		closeDropDown: function(){
			// summary:
			//		Closes the drop down on this widget
			// tags:
			//		protected

			this._throttleOpenClose();
			if(this.endHandler){
				this.disconnect(this.startHandler);
				this.disconnect(this.endHandler);
				this.disconnect(this.moveHandler);
				clearInterval(this.repositionTimer);
				this.repositionTimer = this.endHandler = null;
			}
			this.inherited(arguments);
			domAttr.remove(this.domNode, "aria-owns");
			domAttr.set(this.domNode, "aria-expanded", "false");
			popup.close(this.dropDown);
			this._opened = false;

			// Remove disable attribute to make input element clickable after context menu closed
			if(has("windows-theme") && this.domNode.disabled){
				this.defer(function(){
					this.domNode.removeAttribute("disabled");
				}, 300);
			}

		},

		openDropDown: function(){
			// summary:
			//		Opens the dropdown for this widget. To be called only when this.dropDown
			//		has been created and is ready to display (that is, its data is loaded).
			// returns:
			//		Returns the value of popup.open().
			// tags:
			//		protected

			var wasClosed = !this._opened;
			var dropDown = this.dropDown,
				ddNode = dropDown.domNode,
				aroundNode = this.domNode,
				self = this;
				
			domAttr.set(dropDown.domNode, "role", "listbox");
			domAttr.set(this.domNode, "aria-expanded", "true");
			if(dropDown.id){
				domAttr.set(this.domNode, "aria-owns", dropDown.id);
			}

			if(has("touch")){
				win.global.scrollBy(0, domGeometry.position(aroundNode, false).y); // don't call scrollIntoView since it messes up ScrollableView
			}

			// TODO: isn't maxHeight dependent on the return value from popup.open(),
			// i.e., dependent on how much space is available (BK)

			if(!this._preparedNode){
				this._preparedNode = true;
				// Check if we have explicitly set width and height on the dropdown widget dom node
				if(ddNode.style.width){
					this._explicitDDWidth = true;
				}
				if(ddNode.style.height){
					this._explicitDDHeight = true;
				}
			}

			// Code for resizing dropdown (height limitation, or increasing width to match my width)
			var myStyle = {
				display: "",
				overflow: "hidden",
				visibility: "hidden"
			};
			if(!this._explicitDDWidth){
				myStyle.width = "";
			}
			if(!this._explicitDDHeight){
				myStyle.height = "";
			}
			domStyle.set(ddNode, myStyle);

			// Figure out maximum height allowed (if there is a height restriction)
			var maxHeight = this.maxHeight;
			if(maxHeight == -1){
				// limit height to space available in viewport either above or below my domNode
				// (whichever side has more room)
				var viewport = windowUtils.getBox(),
					position = domGeometry.position(aroundNode, false);
				maxHeight = Math.floor(Math.max(position.y, viewport.h - (position.y + position.h)));
			}

			// Attach dropDown to DOM and make make visibility:hidden rather than display:none
			// so we call startup() and also get the size
			popup.moveOffScreen(dropDown);

			if(dropDown.startup && !dropDown._started){
				dropDown.startup(); // this has to be done after being added to the DOM
			}
			// Get size of drop down, and determine if vertical scroll bar needed
			var mb = domGeometry.position(this.dropDown.containerNode, false);
			var overHeight = (maxHeight && mb.h > maxHeight);
			if(overHeight){
				mb.h = maxHeight;
			}

			// Adjust dropdown width to match or be larger than my width
			mb.w = Math.max(mb.w, aroundNode.offsetWidth);
			domGeometry.setMarginBox(ddNode, mb);

			var retVal = popup.open({
				parent: this,
				popup: dropDown,
				around: aroundNode,
				orient: has("windows-theme") ? ["above"] : this.dropDownPosition,
				onExecute: function(){
					self.closeDropDown();
				},
				onCancel: function(){
					self.closeDropDown();
				},
				onClose: function(){
					self._opened = false;
				}
			});
			this._opened=true;

			if(wasClosed){
				var	isGesture = false,
					skipReposition = false,
					active = false,
					wrapper = dropDown.domNode.parentNode,
					aroundNodePos = domGeometry.position(aroundNode, false),
					popupPos = domGeometry.position(wrapper, false),
					deltaX = popupPos.x - aroundNodePos.x,
					deltaY = popupPos.y - aroundNodePos.y,
					startX = -1, startY = -1;

				// touchstart isn't really needed since touchmove implies touchstart, but
				// mousedown is needed since mousemove doesn't know if the left button is down or not
				this.startHandler = this.connect(win.doc.documentElement, touch.press,
					function(e){
						skipReposition = true;
						active = true;
						isGesture = false;
						startX = e.clientX;
						startY = e.clientY;
					}
				);
				this.moveHandler = this.connect(win.doc.documentElement, touch.move,
					function(e){
						skipReposition = true;
						if(e.touches){
							active = isGesture = true; // touchmove implies touchstart
						}else if(active && (e.clientX != startX || e.clientY != startY)){
							isGesture = true;
						}
					}
				);
				this.clickHandler = this.connect(dropDown.domNode, "onclick",
					function(){
						skipReposition = true;
						active = isGesture = false; // click implies no gesture movement
					}
				);
				this.endHandler = this.connect(win.doc.documentElement, touch.release,
					function(){
						this.defer(function(){ // allow onclick to go first
							skipReposition = true;
							if(!isGesture && active){ // if click without move, then close dropdown
								this.closeDropDown();
							}
							active = false;
						});
					}
				);
				this.repositionTimer = setInterval(lang.hitch(this, function(){
					if(skipReposition){ // don't reposition if busy
						skipReposition = false;
						return;
					}
					var	currentAroundNodePos = domGeometry.position(aroundNode, false),
						currentPopupPos = domGeometry.position(wrapper, false),
						currentDeltaX = currentPopupPos.x - currentAroundNodePos.x,
						currentDeltaY = currentPopupPos.y - currentAroundNodePos.y;
					// if the popup is no longer placed correctly, relocate it
					if(Math.abs(currentDeltaX - deltaX) >= 1 || Math.abs(currentDeltaY - deltaY) >= 1){ // Firefox plays with partial pixels
						domStyle.set(wrapper, { left: parseInt(domStyle.get(wrapper, "left")) + deltaX - currentDeltaX + 'px', top: parseInt(domStyle.get(wrapper, "top")) + deltaY - currentDeltaY + 'px' });
					}
				}), 50); // yield a short time to allow for consolidation for better CPU throughput
			}

			// We need to disable input control in order to prevent opening the soft keyboard in IE
			if(has("windows-theme")){
				this.domNode.setAttribute("disabled", true);
			}

			return retVal;
		},

		postCreate: function(){
			this.inherited(arguments);
			this.connect(this.domNode, "onclick", "_onClick");
			domAttr.set(this.domNode, "role", "combobox");
			domAttr.set(this.domNode, "aria-expanded", "false");
		},

		destroy: function(){
			if(this.repositionTimer){
				clearInterval(this.repositionTimer);
			}
			this.inherited(arguments);
		},

		_onClick: function(/*Event*/ e){
			// tags:
			//		private
			
			// throttle clicks to prevent double click from doing double actions
			if(!this._throttleHandler){
				if(this.opened){
					this.closeDropDown();
				}else{
					this._startSearchAll();
				}
			}
		}
	});
});
