define([
	"dojo/_base/declare",
	"dojo/_base/array",
	"dojo/_base/connect",
	"dojo/_base/lang",
	"dojo/_base/event",
	"dojo/_base/html",
	"dojo/_base/sniff",
	"dojo/keys",
	"dojo/string",
	"dojo/window",
	"dojo/date/locale",		
	"./FilterBuilder",
	"../Dialog",
	"dijit/form/ComboBox",
	"dijit/form/TextBox",
	"dijit/form/NumberTextBox",
	"dijit/form/DateTextBox",
	"dijit/form/TimeTextBox",
	"dijit/form/Button",
	"dijit/layout/AccordionContainer",
	"dijit/layout/ContentPane",
	"dijit/_Widget", 
	"dijit/_TemplatedMixin", 
	"dijit/_WidgetsInTemplateMixin",
	"dijit/focus",
	"dojox/html/metrics",
	"dijit/a11y",
	"dojo/text!../../templates/FilterDefPane.html",
	"dojo/text!../../templates/CriteriaBox.html",
	"dojo/text!../../templates/FilterBoolValueBox.html",	
	"dijit/Tooltip",
	"dijit/form/Select",
	"dijit/form/RadioButton",
	"dojox/html/ellipsis",
	"../../../cells/dijit"
], function(declare, array, connect, lang, event, html, has, keys, string, win, dateLocale, 
	FilterBuilder, Dialog, ComboBox, TextBox, NumberTextBox, DateTextBox, TimeTextBox, Button, 
	AccordionContainer, ContentPane, _Widget, _TemplatedMixin, _WidgetsInTemplateMixin, dijitFocus,
	metrics, dijitA11y, defPaneTemplate, criteriaTemplate, boolValueTemplate){
		
var _tabIdxes = {
		// summary:
		//		Define tabindexes for elements in the filter definition dialog
		relSelect: 60,
		accordionTitle: 70,
		removeCBoxBtn: -1,
		colSelect: 90,
		condSelect: 95,
		valueBox: 10,
		addCBoxBtn: 20,
		filterBtn: 30,
		clearBtn: 40,
		cancelBtn: 50
	};
var FilterAccordionContainer = declare("dojox.grid.enhanced.plugins.filter.AccordionContainer", AccordionContainer, {
	nls: null,
	addChild: function(/*dijit._Widget*/ child, /*Integer?*/ insertIndex){
		var pane = arguments[0] = child._pane = new ContentPane({
			content: child
		});
		this.inherited(arguments);
		this._modifyChild(pane);
	},
	removeChild: function(child){
		var pane = child, isRemoveByUser = false;
		if(child._pane){
			isRemoveByUser = true;
			pane = arguments[0] = child._pane;
		}
		this.inherited(arguments);
		if(isRemoveByUser){
			this._hackHeight(false, this._titleHeight);
			var children = this.getChildren();
			if(children.length === 1){
				html.style(children[0]._removeCBoxBtn.domNode, "display", "none");
			}
		}
		pane.destroyRecursive();
	},
	selectChild: function(child){
		if(child._pane){
			arguments[0] = child._pane;
		}
		this.inherited(arguments);
	},
	resize: function(){
		this.inherited(arguments);
		array.forEach(this.getChildren(), this._setupTitleDom);
	},
	startup: function(){
		if(this._started){
			return;
		}
		this.inherited(arguments);
		if(parseInt(has('ie'), 10) == 7){
			//IE7 will fire a lot of "onresize" event during initialization.
			array.some(this._connects, function(cnnt){
				if((cnnt[0] || {})[1] == "onresize"){
					this.disconnect(cnnt);
					return true;
				}
			}, this);
		}
		array.forEach(this.getChildren(), function(child){
			this._modifyChild(child, true);
		}, this);
	},
	_onKeyPress: function(/*Event*/ e, /*dijit._Widget*/ fromTitle){
		// summary:
		//		Overrides base class method, make left/right button do other things.
		if(this.disabled || e.altKey || !(fromTitle || e.ctrlKey)){
			return;
		}
		var k = keys, c = e.charOrCode, ltr = html._isBodyLtr(), toNext = null;
		if((fromTitle && c == k.UP_ARROW) || (e.ctrlKey && c == k.PAGE_UP)){
			toNext = false;
		}else if((fromTitle && c == k.DOWN_ARROW) || (e.ctrlKey && (c == k.PAGE_DOWN || c == k.TAB))){
			toNext = true;
		}else if(c == (ltr ? k.LEFT_ARROW : k.RIGHT_ARROW)){
			toNext = this._focusOnRemoveBtn ? null : false;
			this._focusOnRemoveBtn = !this._focusOnRemoveBtn;
		}else if(c == (ltr ? k.RIGHT_ARROW : k.LEFT_ARROW)){
			toNext = this._focusOnRemoveBtn ? true : null;
			this._focusOnRemoveBtn = !this._focusOnRemoveBtn;
		}else{
			return;
		}
		if(toNext !== null){
			this._adjacent(toNext)._buttonWidget._onTitleClick();
		}
		event.stop(e);
		win.scrollIntoView(this.selectedChildWidget._buttonWidget.domNode.parentNode);
		if(has('ie')){
			//IE will not show focus indicator if tabIndex is -1
			this.selectedChildWidget._removeCBoxBtn.focusNode.setAttribute("tabIndex", this._focusOnRemoveBtn ? _tabIdxes.accordionTitle : -1);
		}
		dijitFocus.focus(this.selectedChildWidget[this._focusOnRemoveBtn ? "_removeCBoxBtn" : "_buttonWidget"].focusNode);
	},
	_modifyChild: function(child, isFirst){
		if(!child || !this._started){
			return;
		}
		html.style(child.domNode, "overflow", "hidden");
		child._buttonWidget.connect(child._buttonWidget, "_setSelectedAttr", function(){
			this.focusNode.setAttribute("tabIndex", this.selected ? _tabIdxes.accordionTitle : "-1");
		});
		var _this = this;
		child._buttonWidget.connect(child._buttonWidget.domNode, "onclick", function(){
			_this._focusOnRemoveBtn = false;
		});
		(child._removeCBoxBtn = new Button({
			label: this.nls.removeRuleButton,
			showLabel: false,
			iconClass: "dojoxGridFCBoxRemoveCBoxBtnIcon",
			tabIndex: _tabIdxes.removeCBoxBtn,
			onClick: lang.hitch(child.content, "onRemove"),
			onKeyPress: function(e){
				_this._onKeyPress(e, child._buttonWidget.contentWidget);
			}
		})).placeAt(child._buttonWidget.domNode);
		var i, children = this.getChildren();
		if(children.length === 1){
			child._buttonWidget.set("selected", true);
			html.style(child._removeCBoxBtn.domNode, "display", "none");
		}else{
			for(i = 0; i < children.length; ++i){
				if(children[i]._removeCBoxBtn){
					html.style(children[i]._removeCBoxBtn.domNode, "display", "");
				}
			}
		}
		this._setupTitleDom(child);
		if(!this._titleHeight){
			for(i = 0; i < children.length; ++i){
				if(children[i] != this.selectedChildWidget){
					this._titleHeight = html.marginBox(children[i]._buttonWidget.domNode.parentNode).h;
					break;
				}
			}
		}
		if(!isFirst){
			this._hackHeight(true, this._titleHeight);
		}
	},
	_hackHeight: function(/* bool */toGrow,/* int */heightDif){
		var children = this.getChildren(),
			dn = this.domNode, h = html.style(dn, "height");
		if(!toGrow){
			dn.style.height = (h - heightDif) + 'px';
		}else if(children.length > 1){
			dn.style.height = (h + heightDif) + 'px';
		}else{
			//Only one rule, no need to do anything.
			return;
		}
		this.resize();
	},
	_setupTitleDom: function(child){
		var w = html.contentBox(child._buttonWidget.titleNode).w;
		if(has('ie') < 8){ w -= 8; }
		html.style(child._buttonWidget.titleTextNode, "width", w + "px");
	}
});
var FilterDefPane = declare("dojox.grid.enhanced.plugins.filter.FilterDefPane",[_Widget, _TemplatedMixin, _WidgetsInTemplateMixin],{
	templateString: defPaneTemplate,
	widgetsInTemplate: true,
	dlg: null,
	postMixInProperties: function(){
		this.plugin = this.dlg.plugin;
		var nls = this.plugin.nls;
		this._addRuleBtnLabel = nls.addRuleButton;
		this._cancelBtnLabel = nls.cancelButton;
		this._clearBtnLabel = nls.clearButton;
		this._filterBtnLabel = nls.filterButton;
		this._relAll = nls.relationAll;
		this._relAny = nls.relationAny;
		this._relMsgFront = nls.relationMsgFront;
		this._relMsgTail = nls.relationMsgTail;
	},
	postCreate: function(){
		this.inherited(arguments);
		this.connect(this.domNode, "onkeypress", "_onKey");
		(this.cboxContainer = new FilterAccordionContainer({
			nls: this.plugin.nls
		})).placeAt(this.criteriaPane);
		
		this._relSelect.set("tabIndex", _tabIdxes.relSelect);
		this._addCBoxBtn.set("tabIndex", _tabIdxes.addCBoxBtn);
		this._cancelBtn.set("tabIndex", _tabIdxes.cancelBtn);
		this._clearFilterBtn.set("tabIndex", _tabIdxes.clearBtn);
		this._filterBtn.set("tabIndex", _tabIdxes.filterBtn);
		
		var nls = this.plugin.nls;
		this._relSelect.domNode.setAttribute("aria-label", nls.waiRelAll);
		this._addCBoxBtn.domNode.setAttribute("aria-label", nls.waiAddRuleButton);
		this._cancelBtn.domNode.setAttribute("aria-label", nls.waiCancelButton);
		this._clearFilterBtn.domNode.setAttribute("aria-label", nls.waiClearButton);
		this._filterBtn.domNode.setAttribute("aria-label", nls.waiFilterButton);
		
		this._relSelect.set("value", this.dlg._relOpCls === "logicall" ? "0" : "1");
	},
	uninitialize: function(){
		this.cboxContainer.destroyRecursive();
		this.plugin = null;
		this.dlg = null;
	},
	_onRelSelectChange: function(val){
		this.dlg._relOpCls = val == "0" ? "logicall" : "logicany";
		this._relSelect.domNode.setAttribute("aria-label", this.plugin.nls[val == "0" ? "waiRelAll" : "waiRelAny"]);
	},
	_onAddCBox: function(){
		this.dlg.addCriteriaBoxes(1);
	},
	_onCancel: function(){
		this.dlg.onCancel();
	},
	_onClearFilter: function(){
		this.dlg.onClearFilter();
	},
	_onFilter: function(){
		this.dlg.onFilter();
	},
	_onKey: function(e){
		if(e.keyCode == keys.ENTER){
			this.dlg.onFilter();
		}
	}
});
var CriteriaBox = declare("dojox.grid.enhanced.plugins.filter.CriteriaBox",[_Widget, _TemplatedMixin, _WidgetsInTemplateMixin],{
	templateString: criteriaTemplate,
	widgetsInTemplate: true,
	dlg: null,
	postMixInProperties: function(){
		this.plugin = this.dlg.plugin;
		this._curValueBox = null;
		
		var nls = this.plugin.nls;
		this._colSelectLabel = nls.columnSelectLabel;
		this._condSelectLabel = nls.conditionSelectLabel;
		this._valueBoxLabel = nls.valueBoxLabel;
		this._anyColumnOption = nls.anyColumnOption;
	},
	postCreate: function(){
		var dlg = this.dlg, g = this.plugin.grid;
		//Select Column
		this._colSelect.set("tabIndex", _tabIdxes.colSelect);
		this._colOptions = this._getColumnOptions();
		this._colSelect.addOption([
			{label: this.plugin.nls.anyColumnOption, value: "anycolumn", selected: dlg.curColIdx < 0},
			{value: ""}
		].concat(this._colOptions));
		//Select Condition
		this._condSelect.set("tabIndex", _tabIdxes.condSelect);
		this._condSelect.addOption(this._getUsableConditions(dlg.getColumnType(dlg.curColIdx)));
		this._showSelectOrLabel(this._condSelect, this._condSelectAlt);
		
		this.connect(g.layout, "moveColumn", "onMoveColumn");
		var _this = this;
		setTimeout(function(){
			var type = dlg.getColumnType(dlg.curColIdx);
			_this._setValueBoxByType(type);
		}, 0);
	},
	_getColumnOptions: function(){
		var colIdx = this.dlg.curColIdx >= 0 ? String(this.dlg.curColIdx) : "anycolumn";
		return array.map(array.filter(this.plugin.grid.layout.cells, function(cell){
			return !(cell.filterable === false || cell.hidden);
		}), function(cell){
			return {
				label: cell.name || cell.field,
				value: String(cell.index),
				selected: colIdx == String(cell.index)
			};
		});
	},
	onMoveColumn: function(){
		var tmp = this._onChangeColumn;
		this._onChangeColumn = function(){};
		var option = this._colSelect.get("selectedOptions");
		this._colSelect.removeOption(this._colOptions);
		this._colOptions = this._getColumnOptions();
		this._colSelect.addOption(this._colOptions);
		var i = 0;
		for(; i < this._colOptions.length; ++i){
			if(this._colOptions[i].label == option.label){
				break;
			}
		}
		if(i < this._colOptions.length){
			this._colSelect.set("value", this._colOptions[i].value);
		}
		var _this = this;
		setTimeout(function(){
			_this._onChangeColumn = tmp;
		}, 0);
	},
	onRemove: function(){
		this.dlg.removeCriteriaBoxes(this);
	},
	uninitialize: function(){
		if(this._curValueBox){
			this._curValueBox.destroyRecursive();
			this._curValueBox = null;
		}
		this.plugin = null;
		this.dlg = null;
	},
	_showSelectOrLabel: function(sel, alt){
		var options = sel.getOptions();
		if(options.length == 1){
			alt.innerHTML = options[0].label;
			html.style(sel.domNode, "display", "none");
			html.style(alt, "display", "");
		}else{
			html.style(sel.domNode, "display", "");
			html.style(alt, "display", "none");
		}
	},
	_onChangeColumn: function(val){
		this._checkValidCriteria();
		var type = this.dlg.getColumnType(val);
		this._setConditionsByType(type);
		this._setValueBoxByType(type);
		this._updateValueBox();
	},
	_onChangeCondition: function(val){
		this._checkValidCriteria();
		var f = (val == "range");
		if(f ^ this._isRange){
			this._isRange = f;
			this._setValueBoxByType(this.dlg.getColumnType(this._colSelect.get("value")));
		}
		this._updateValueBox();
	},
	_updateValueBox: function(cond){
		this._curValueBox.set("disabled", this._condSelect.get("value") == "isempty");
	},
	_checkValidCriteria: function(){
		// summary:
		//		Check whether the given criteria box is completed. If it is, mark it.
		setTimeout(lang.hitch(this, function(){
			this.updateRuleTitle();
			this.dlg._updatePane();
		}),0);
	},
	_createValueBox: function(/* widget constructor */cls,/* object */arg){
		// summary:
		//		Create a value input box with given class and arguments
		var func = lang.hitch(arg.cbox, "_checkValidCriteria");
		return new cls(lang.mixin(arg,{
			tabIndex: _tabIdxes.valueBox,
			onKeyPress: func,
			onChange: func,
			"class": "dojoxGridFCBoxValueBox"
		}));
	},
	_createRangeBox: function(/* widget constructor */cls,/* object */arg){
		// summary:
		//		Create a DIV containing 2 input widgets, which represents a range, with the given class and arguments
		var func = lang.hitch(arg.cbox, "_checkValidCriteria");
		lang.mixin(arg,{
			tabIndex: _tabIdxes.valueBox,
			onKeyPress: func,
			onChange: func
		});
		var div = html.create("div", {"class": "dojoxGridFCBoxValueBox"}),
			start = new cls(arg),
			txt = html.create("span", {"class": "dojoxGridFCBoxRangeValueTxt", "innerHTML": this.plugin.nls.rangeTo}),
			end = new cls(arg);
		html.addClass(start.domNode, "dojoxGridFCBoxStartValue");
		html.addClass(end.domNode, "dojoxGridFCBoxEndValue");
		div.appendChild(start.domNode);
		div.appendChild(txt);
		div.appendChild(end.domNode);
		div.domNode = div;
		//Mock functions for set and get (in place of the old attr function)
		div.set = function(dummy, args){
			if(lang.isObject(args)){
				start.set("value", args.start);
				end.set("value", args.end);
			}
		};
		div.get = function(){
			var s = start.get("value"),
				e = end.get("value");
			return s && e ? {start: s, end: e} : "";
		};
		return div;
	},
	changeCurrentColumn: function(/* bool */selectCurCol){
		var colIdx = this.dlg.curColIdx;
		//Re-populate the columns in case some of them are set to hidden.
		this._colSelect.removeOption(this._colOptions);
		this._colOptions = this._getColumnOptions();
		this._colSelect.addOption(this._colOptions);
		this._colSelect.set('value', colIdx >= 0 ? String(colIdx) : "anycolumn");
		this.updateRuleTitle(true);
	},
	curColumn: function(){
		return this._colSelect.getOptions(this._colSelect.get("value")).label;
	},
	curCondition: function(){
		return this._condSelect.getOptions(this._condSelect.get("value")).label;
	},
	curValue: function(){
		var cond = this._condSelect.get("value");
		if(cond == "isempty"){return "";}
		return this._curValueBox ? this._curValueBox.get("value") : "";
	},
	save: function(){
		if(this.isEmpty()){
			return null;
		}
		var colIdx = this._colSelect.get("value"),
			type = this.dlg.getColumnType(colIdx),
			value = this.curValue(),
			cond = this._condSelect.get("value");
		return {
			"column": colIdx,
			"condition": cond,
			"value": value,
			"formattedVal": this.formatValue(type, cond, value),
			"type": type,
			"colTxt": this.curColumn(),
			"condTxt": this.curCondition()
		};
	},
	load: function(obj){
		var tmp = [
			this._onChangeColumn,
			this._onChangeCondition
		];
		this._onChangeColumn = this._onChangeCondition = function(){};
		if(obj.column){
			this._colSelect.set("value", obj.column);
		}
		if(obj.type){
			this._setConditionsByType(obj.type);
			this._setValueBoxByType(obj.type);
		}else{
			obj.type = this.dlg.getColumnType(this._colSelect.get("value"));
		}
		if(obj.condition){
			this._condSelect.set("value", obj.condition);
		}
		var value = obj.value || "";
		if(value || (obj.type != "date" && obj.type != "time")){
			this._curValueBox.set("value", value);
		}
		this._updateValueBox();
		setTimeout(lang.hitch(this, function(){
			this._onChangeColumn = tmp[0];
			this._onChangeCondition = tmp[1];
		}), 0);
	},
	getExpr: function(){
		if(this.isEmpty()){
			return null;
		}
		var colval = this._colSelect.get("value");
		return this.dlg.getExprForCriteria({
			"type": this.dlg.getColumnType(colval),
			"column": colval,
			"condition": this._condSelect.get("value"),
			"value": this.curValue()
		});
	},
	isEmpty: function(){
		var cond = this._condSelect.get("value");
		if(cond == "isempty"){return false;}
		var v = this.curValue();
		return v === "" || v === null || typeof v == "undefined" || (typeof v == "number" && isNaN(v));
	},
	updateRuleTitle: function(isEmpty){
		var node = this._pane._buttonWidget.titleTextNode;
		var title = [
			"<div class='dojoxEllipsis'>"
		];
		if(isEmpty || this.isEmpty()){
			node.title = string.substitute(this.plugin.nls.ruleTitleTemplate, [this._ruleIndex || 1]);
			title.push(node.title);
		}else{
			var type = this.dlg.getColumnType(this._colSelect.get("value"));
			var column = this.curColumn();
			var condition = this.curCondition();
			var value = this.formatValue(type, this._condSelect.get("value"), this.curValue());
			title.push(
				column,
				"&nbsp;<span class='dojoxGridRuleTitleCondition'>",
				condition,
				"</span>&nbsp;",
				value
			);
			node.title = [column, " ", condition, " ", value].join('');
		}
		node.innerHTML = title.join('');
		if(has('mozilla')){
			var tt = html.create("div", {
				"style": "width: 100%; height: 100%; position: absolute; top: 0; left: 0; z-index: 9999;"
			}, node);
			tt.title = node.title;
		}
	},
	updateRuleIndex: function(index){
		if(this._ruleIndex != index){
			this._ruleIndex = index;
			if(this.isEmpty()){
				this.updateRuleTitle();
			}
		}
	},
	setAriaInfo: function(idx){
		var dss = string.substitute, nls = this.plugin.nls;
		this._colSelect.domNode.setAttribute("aria-label", dss(nls.waiColumnSelectTemplate, [idx]));
		this._condSelect.domNode.setAttribute("aria-label", dss(nls.waiConditionSelectTemplate, [idx]));
		this._pane._removeCBoxBtn.domNode.setAttribute("aria-label", dss(nls.waiRemoveRuleButtonTemplate, [idx]));
		this._index = idx;
	},
	_getUsableConditions: function(type){
		var conditions = lang.clone(this.dlg._dataTypeMap[type].conditions);
		var typeDisabledConds = (this.plugin.args.disabledConditions || {})[type];
		var colIdx = parseInt(this._colSelect.get("value"), 10);
		var colDisabledConds = isNaN(colIdx) ?
			(this.plugin.args.disabledConditions || {})["anycolumn"] :
			this.plugin.grid.layout.cells[colIdx].disabledConditions;
		if(!lang.isArray(typeDisabledConds)){
			typeDisabledConds = [];
		}
		if(!lang.isArray(colDisabledConds)){
			colDisabledConds = [];
		}
		var arr = typeDisabledConds.concat(colDisabledConds);
		if(arr.length){
			var disabledConds = {};
			array.forEach(arr, function(c){
				if(lang.isString(c)){
					disabledConds[c.toLowerCase()] = true;
				}
			});
			return array.filter(conditions, function(condOption){
				return !(condOption.value in disabledConds);
			});
		}
		return conditions;
	},
	_setConditionsByType: function(/* string */type){
		var condSelect = this._condSelect;
		condSelect.removeOption(condSelect.options);
		condSelect.addOption(this._getUsableConditions(type));
		this._showSelectOrLabel(this._condSelect, this._condSelectAlt);
	},
	_setValueBoxByType: function(/* string */type){
		if(this._curValueBox){
			this.valueNode.removeChild(this._curValueBox.domNode);
			try{
				this._curValueBox.destroyRecursive();
			}catch(e){}
			delete this._curValueBox;
		}
		//value box class
		var vbcls = this.dlg._dataTypeMap[type].valueBoxCls[this._getValueBoxClsInfo(this._colSelect.get("value"), type)],
			vboxArg = this._getValueBoxArgByType(type);
		this._curValueBox = this[this._isRange ? "_createRangeBox" : "_createValueBox"](vbcls, vboxArg);
		this.valueNode.appendChild(this._curValueBox.domNode);
		
		//Can not move to setAriaInfo, 'cause the value box is created after the defpane is loaded.
		this._curValueBox.focusNode.setAttribute("aria-label", string.substitute(this.plugin.nls.waiValueBoxTemplate,[this._index]));
		//Now our cbox is completely ready
		this.dlg.onRendered(this);
	},
	//--------------------------UI Configuration--------------------------------------
	_getValueBoxArgByType: function(/* string */type){
		// summary:
		//		Get the arguments for the value box construction.
		var g = this.plugin.grid,
			cell = g.layout.cells[parseInt(this._colSelect.get("value"), 10)],
			res = {
				cbox: this
			};
		if(type == "string"){
			if(cell && (cell.suggestion || cell.autoComplete)){
				lang.mixin(res, {
					store: g.store,
					searchAttr: cell.field || cell.name,
					query: g.query || {},
					fetchProperties: {
						sort: [{"attribute": cell.field || cell.name}],
						queryOptions: lang.mixin({
							ignoreCase: true,
							deep: true
						}, g.queryOptions || {})
					}
				});
			}
		}else if(type == "boolean"){
			lang.mixin(res, this.dlg.builder.defaultArgs["boolean"]);
		}
		if(cell && cell.dataTypeArgs){
			lang.mixin(res, cell.dataTypeArgs);
		}
		return res;
	},
	formatValue: function(type, cond, v){
		// summary:
		//		Format the value to be shown in tooltip.
		if(cond == "isempty"){return "";}
		if(type == "date" || type == "time"){
			var opt = {selector: type},
				fmt = dateLocale.format;
			if(cond == "range"){
				return string.substitute(this.plugin.nls.rangeTemplate, [fmt(v.start, opt), fmt(v.end, opt)]);
			}
			return fmt(v, opt);
		}else if(type == "boolean"){
			return v ? this._curValueBox._lblTrue : this._curValueBox._lblFalse;
		}
		return v;
	},
	_getValueBoxClsInfo: function(/* int|string */colIndex, /* string */type){
		// summary:
		//		Decide which value box to use given data type and column index.
		var cell = this.plugin.grid.layout.cells[parseInt(colIndex, 10)];
		//Now we only need to handle string. But maybe we need to handle more types here in the future.
		if(type == "string"){
			return (cell && (cell.suggestion || cell.autoComplete)) ? "ac" : "dft";
		}
		return "dft";
	}
});


var UniqueComboBox = declare("dojox.grid.enhanced.plugins.filter.UniqueComboBox", ComboBox, {
	_openResultList: function(results){
		var cache = {}, s = this.store, colName = this.searchAttr;
		arguments[0] = array.filter(results, function(item){
			var key = s.getValue(item, colName), existed = cache[key];
			cache[key] = true;
			return !existed;
		});
		this.inherited(arguments);
	},
	_onKey: function(evt){
		if(evt.charOrCode === keys.ENTER && this._opened){
			event.stop(evt);
		}
		this.inherited(arguments);
	}
});
var BooleanValueBox = declare("dojox.grid.enhanced.plugins.filter.BooleanValueBox", [_Widget, _TemplatedMixin, _WidgetsInTemplateMixin], {
	templateString: boolValueTemplate,
	widgetsInTemplate: true,
	constructor: function(args){
		var nls = args.cbox.plugin.nls;
		this._baseId = args.cbox.id;
		this._lblTrue = args.trueLabel || nls.trueLabel || "true";
		this._lblFalse = args.falseLabel || nls.falseLabel || "false";
		this.args = args;
	},
	postCreate: function(){
		this.onChange();
	},
	onChange: function(){},
	
	get: function(prop){
		return this.rbTrue.get("checked");
	},
	set: function(prop, v){
		this.inherited(arguments);
		if(prop == "value"){
			this.rbTrue.set("checked", !!v);
			this.rbFalse.set("checked", !v);
		}
	}
});
var FilterDefDialog = declare("dojox.grid.enhanced.plugins.filter.FilterDefDialog", null, {
	// summary:
	//		Create the filter definition UI.
	curColIdx: -1,
	_relOpCls: "logicall",
	_savedCriterias: null,
	plugin: null,
	constructor: function(args){
		var plugin = this.plugin = args.plugin;
		this.builder = new FilterBuilder();
		this._setupData();
		this._cboxes = [];
		this.defaultType = plugin.args.defaultType || "string";
		
		(this.filterDefPane = new FilterDefPane({
			"dlg": this
		})).startup();
		(this._defPane = new Dialog({
			"refNode": this.plugin.grid.domNode,
			"title": plugin.nls.filterDefDialogTitle,
			"class": "dojoxGridFDTitlePane",
			"iconClass": "dojoxGridFDPaneIcon",
			"content": this.filterDefPane
		})).startup();
		
		this._defPane.connect(plugin.grid.layer('filter'), "filterDef", lang.hitch(this, "_onSetFilter"));
		plugin.grid.setFilter = lang.hitch(this, "setFilter");
		plugin.grid.getFilter = lang.hitch(this, "getFilter");
		plugin.grid.getFilterRelation = lang.hitch(this, function(){
			return this._relOpCls;
		});
		
		plugin.connect(plugin.grid.layout, "moveColumn", lang.hitch(this, "onMoveColumn"));
	},
	onMoveColumn: function(sourceViewIndex, destViewIndex, cellIndex, targetIndex, before){
		if(this._savedCriterias && cellIndex != targetIndex){
			if(before){ --targetIndex; }
			var min = cellIndex < targetIndex ? cellIndex : targetIndex;
			var max = cellIndex < targetIndex ? targetIndex : cellIndex;
			var dir = targetIndex > min ? 1 : -1;
			array.forEach(this._savedCriterias, function(sc){
				var idx = parseInt(sc.column, 10);
				if(!isNaN(idx) && idx >= min && idx <= max){
					sc.column = String(idx == cellIndex ? idx + (max - min) * dir : idx - dir);
				}
			});
		}
	},
	destroy: function(){
		this._defPane.destroyRecursive();
		this._defPane = null;
		this.filterDefPane = null;
		this.builder = null;
		this._dataTypeMap = null;
		this._cboxes = null;
		var g = this.plugin.grid;
		g.setFilter = null;
		g.getFilter = null;
		g.getFilterRelation = null;
		this.plugin = null;
	},
	_setupData: function(){
		var nls = this.plugin.nls;
		this._dataTypeMap = {
		// summary:
		//		All supported data types
			"number":{
				valueBoxCls: {
					dft: NumberTextBox
				},
				conditions:[
					{label: nls.conditionEqual, value: "equalto", selected: true},
					{label: nls.conditionNotEqual, value: "notequalto"},
					{label: nls.conditionLess, value: "lessthan"},
					{label: nls.conditionLessEqual, value: "lessthanorequalto"},
					{label: nls.conditionLarger, value: "largerthan"},
					{label: nls.conditionLargerEqual, value: "largerthanorequalto"},
					{label: nls.conditionIsEmpty, value: "isempty"}
				]
			},
			"string":{
				valueBoxCls: {
					dft: TextBox,
					ac: UniqueComboBox		//For autoComplete
				},
				conditions:[
					{label: nls.conditionContains, value: "contains", selected: true},
					{label: nls.conditionIs, value: "equalto"},
					{label: nls.conditionStartsWith, value: "startswith"},
					{label: nls.conditionEndWith, value: "endswith"},
					{label: nls.conditionNotContain, value: "notcontains"},
					{label: nls.conditionIsNot, value: "notequalto"},
					{label: nls.conditionNotStartWith, value: "notstartswith"},
					{label: nls.conditionNotEndWith, value: "notendswith"},
					{label: nls.conditionIsEmpty, value: "isempty"}
				]
			},
			"date":{
				valueBoxCls: {
					dft: DateTextBox
				},
				conditions:[
					{label: nls.conditionIs, value: "equalto", selected: true},
					{label: nls.conditionBefore, value: "lessthan"},
					{label: nls.conditionAfter, value: "largerthan"},
					{label: nls.conditionRange, value: "range"},
					{label: nls.conditionIsEmpty, value: "isempty"}
				]
			},
			"time":{
				valueBoxCls: {
					dft: TimeTextBox
				},
				conditions:[
					{label: nls.conditionIs, value: "equalto", selected: true},
					{label: nls.conditionBefore, value: "lessthan"},
					{label: nls.conditionAfter, value: "largerthan"},
					{label: nls.conditionRange, value: "range"},
					{label: nls.conditionIsEmpty, value: "isempty"}
				]
			},
			"boolean": {
				valueBoxCls: {
					dft: BooleanValueBox
				},
				conditions: [
					{label: nls.conditionIs, value: "equalto", selected: true},
					{label: nls.conditionIsEmpty, value: "isempty"}
				]
			}
		};
	},
	setFilter: function(rules, ruleRelation){
		rules = rules || [];
		if(!lang.isArray(rules)){
			rules = [rules];
		}
		var func = function(){
			if(rules.length){
				this._savedCriterias = array.map(rules, function(rule){
					var type = rule.type || this.defaultType;
					return {
						"type": type,
						"column": String(rule.column),
						"condition": rule.condition,
						"value": rule.value,
						"colTxt": this.getColumnLabelByValue(String(rule.column)),
						"condTxt": this.getConditionLabelByValue(type, rule.condition),
						"formattedVal": rule.formattedVal || rule.value
					};
				}, this);
				this._criteriasChanged = true;
				if(ruleRelation === "logicall" || ruleRelation === "logicany"){
					this._relOpCls = ruleRelation;
				}
				var exprs = array.map(rules, this.getExprForCriteria, this);
				exprs = this.builder.buildExpression(exprs.length == 1 ? exprs[0] : {
					"op": this._relOpCls,
					"data": exprs
				});
				this.plugin.grid.layer("filter").filterDef(exprs);
				this.plugin.filterBar.toggleClearFilterBtn(false);
			}
			this._closeDlgAndUpdateGrid();
		};
		if(this._savedCriterias){
			this._clearWithoutRefresh = true;
			var handle = connect.connect(this, "clearFilter", this, function(){
				connect.disconnect(handle);
				this._clearWithoutRefresh = false;
				func.apply(this);
			});
			this.onClearFilter();
		}else{
			func.apply(this);
		}
	},
	getFilter: function(){
		return lang.clone(this._savedCriterias) || [];
	},
	getColumnLabelByValue: function(v){
		var nls = this.plugin.nls;
		if(v.toLowerCase() == "anycolumn"){
			return nls["anyColumnOption"];
		}else{
			var cell = this.plugin.grid.layout.cells[parseInt(v, 10)];
			return cell ? (cell.name || cell.field) : "";
		}
	},
	getConditionLabelByValue: function(type, c){
		var conditions = this._dataTypeMap[type].conditions;
		for(var i = conditions.length - 1; i >= 0; --i){
			var cond = conditions[i];
			if(cond.value == c.toLowerCase()){
				return cond.label;
			}
		}
		return "";
	},
	addCriteriaBoxes: function(/* int */cnt){
		// summary:
		//		Add *cnt* criteria boxes to the filter definition pane.
		//		Check overflow if necessary.
		if(typeof cnt != "number" || cnt <= 0){
			return;
		}
		var cbs = this._cboxes,
			cc = this.filterDefPane.cboxContainer,
			total = this.plugin.args.ruleCount,
			len = cbs.length, cbox;
		//If overflow, add to max rule count.
		if(total > 0 && len + cnt > total){
			cnt = total - len;
		}
		for(; cnt > 0; --cnt){
			cbox = new CriteriaBox({
				dlg: this
			});
			cbs.push(cbox);
			cc.addChild(cbox);
		}
		//If there's no content box in it , AccordionContainer can not startup
		cc.startup();
		this._updatePane();
		this._updateCBoxTitles();
		cc.selectChild(cbs[cbs.length-1]);
		//Asign an impossibly large scrollTop to scroll the criteria pane to the bottom.
		this.filterDefPane.criteriaPane.scrollTop = 1000000;
		if(cbs.length === 4){
			if(has('ie') <= 6 && !this.__alreadyResizedForIE6){
				var size = html.position(cc.domNode);
				size.w -= metrics.getScrollbar().w;
				cc.resize(size);
				this.__alreadyResizedForIE6 = true;
			}else{
				cc.resize();
			}
		}
	},
	removeCriteriaBoxes: function(/* int|CriteriaBox|int[] */cnt,/* bool? */isIdx){
		// summary:
		//		Remove criteria boxes from the filter definition pane.
		var cbs = this._cboxes, cc = this.filterDefPane.cboxContainer,
			len = cbs.length, start = len - cnt,
			end = len - 1, cbox,
			curIdx = array.indexOf(cbs, cc.selectedChildWidget.content);
		if(lang.isArray(cnt)){
			var i, idxes = cnt;
			idxes.sort();
			cnt = idxes.length;
			//find a rule that's not deleted.
			//must find and focus the last one, or the hack will not work.
			for(i = len - 1; i >= 0 && array.indexOf(idxes, i) >= 0; --i){}
			if(i >= 0){
				//must select before remove
				if(i != curIdx){
					cc.selectChild(cbs[i]);
				}
				//idxes is sorted from small to large,
				//so travel reversely won't need change index after delete from array.
				for(i = cnt-1; i >= 0; --i){
					if(idxes[i] >= 0 && idxes[i] < len){
						cc.removeChild(cbs[idxes[i]]);
						cbs.splice(idxes[i],1);
					}
				}
			}
			start = cbs.length;
		}else{
			if(isIdx === true){
				if(cnt >= 0 && cnt < len){
					start = end = cnt;
					cnt = 1;
				}else{
					return;
				}
			}else{
				if(cnt instanceof CriteriaBox){
					cbox = cnt;
					cnt = 1;
					start = end = array.indexOf(cbs, cbox);
				}else if(typeof cnt != "number" || cnt <= 0){
					return;
				}else if(cnt >= len){
					cnt = end;
					start = 1;
				}
			}
			if(end < start){
				return;
			}
			//must select before remove
			if(curIdx >= start && curIdx <= end){
				cc.selectChild(cbs[start ? start-1 : end+1]);
			}
			for(; end >= start; --end){
				cc.removeChild(cbs[end]);
			}
			cbs.splice(start, cnt);
		}
		this._updatePane();
		this._updateCBoxTitles();
		if(cbs.length === 3){
			//In ie6, resize back to the normal width will cause the title button look strange.
			cc.resize();
		}
	},
	getCriteria: function(/* int */idx){
		// summary:
		//		Get the *idx*-th criteria.
		if(typeof idx != "number"){
			return this._savedCriterias ? this._savedCriterias.length : 0;
		}
		if(this._savedCriterias && this._savedCriterias[idx]){
			return lang.mixin({
				relation: this._relOpCls == "logicall" ? this.plugin.nls.and : this.plugin.nls.or
			},this._savedCriterias[idx]);
		}
		return null;
	},
	getExprForCriteria: function(rule){
		if(rule.column == "anycolumn"){
			var cells = array.filter(this.plugin.grid.layout.cells, function(cell){
				return !(cell.filterable === false || cell.hidden);
			});
			return {
				"op": "logicany",
				"data": array.map(cells, function(cell){
					return this.getExprForColumn(rule.value, cell.index, rule.type, rule.condition);
				}, this)
			};
		}else{
			return this.getExprForColumn(rule.value, rule.column, rule.type, rule.condition);
		}
	},
	getExprForColumn: function(value, colIdx, type, condition){
		colIdx = parseInt(colIdx, 10);
		var cell = this.plugin.grid.layout.cells[colIdx],
			colName = cell.field || cell.name,
			obj = {
				"datatype": type || this.getColumnType(colIdx),
				"args": cell.dataTypeArgs,
				"isColumn": true
			},
			operands = [lang.mixin({"data": this.plugin.args.isServerSide ? colName : cell}, obj)];
		obj.isColumn = false;
		if(condition == "range"){
			operands.push(lang.mixin({"data": value.start}, obj),
				lang.mixin({"data": value.end}, obj));
		}else if(condition != "isempty"){
			operands.push(lang.mixin({"data": value}, obj));
		}
		return {
			"op": condition,
			"data": operands
		};
	},
	getColumnType: function(/* int */colIndex){
		var cell = this.plugin.grid.layout.cells[parseInt(colIndex, 10)];
		if(!cell || !cell.datatype){
			return this.defaultType;
		}
		var type = String(cell.datatype).toLowerCase();
		return this._dataTypeMap[type] ? type : this.defaultType;
	},
	//////////////////////////////////////////////////////////////////////////////////////////////////////////
	clearFilter: function(noRefresh){
		// summary:
		//		Clear filter definition.
		if(!this._savedCriterias){
			return;
		}
		this._savedCriterias = null;
		this.plugin.grid.layer("filter").filterDef(null);
		try{
			this.plugin.filterBar.toggleClearFilterBtn(true);
			this.filterDefPane._clearFilterBtn.set("disabled", true);
			this.removeCriteriaBoxes(this._cboxes.length-1);
			this._cboxes[0].load({});
		}catch(e){
			//Any error means the filter is defined outside this plugin.
		}
		if(noRefresh){
			this.closeDialog();
		}else{
			this._closeDlgAndUpdateGrid();
		}
	},
	showDialog: function(/* int */colIndex){
		// summary:
		//		Show the filter definition dialog.
		this._defPane.show();
		this.plugin.filterStatusTip.closeDialog();
		this._prepareDialog(colIndex);
	},
	closeDialog: function(){
		// summary:
		//		Close the filter definition dialog.
		if(this._defPane.open){
			this._defPane.hide();
		}
	},
	onFilter: function(e){
		// summary:
		//		Triggered when the "Filter" button is clicked.
		if(this.canFilter()){
			this._defineFilter();
			this._closeDlgAndUpdateGrid();
			this.plugin.filterBar.toggleClearFilterBtn(false);
		}
	},
	onClearFilter: function(e){
		// summary:
		//		Triggered when the "Clear" button is clicked.
		if(this._savedCriterias){
			if(this._savedCriterias.length >= this.plugin.ruleCountToConfirmClearFilter){
				this.plugin.clearFilterDialog.show();
			}else{
				this.clearFilter(this._clearWithoutRefresh);
			}
		}
	},
	onCancel: function(e){
		// summary:
		//		Triggered when the "Cancel" buttton is clicked.
		var sc = this._savedCriterias;
		var cbs = this._cboxes;
		if(sc){
			this.addCriteriaBoxes(sc.length - cbs.length);
			this.removeCriteriaBoxes(cbs.length - sc.length);
			array.forEach(sc, function(c, i){
				cbs[i].load(c);
			});
		}else{
			this.removeCriteriaBoxes(cbs.length - 1);
			cbs[0].load({});
		}
		this.closeDialog();
	},
	onRendered: function(cbox){
		// summary:
		//		Triggered when the rendering of the filter definition dialog is completely finished.
		// cbox:
		//		Current visible criteria box
		if(!has('ff')){
			var elems = dijitA11y._getTabNavigable(html.byId(cbox.domNode));
			dijitFocus.focus(elems.lowest || elems.first);
		}else{
			var dp = this._defPane;
			dp._getFocusItems(dp.domNode);
			dijitFocus.focus(dp._firstFocusItem);
		}
	},
	_onSetFilter: function(filterDef){
		// summary:
		//		If someone clear the filter def in the store directly, we must clear it in the UI.
		//		If someone defines a filter, don't know how to handle it!
		if(filterDef === null && this._savedCriterias){
			this.clearFilter();
		}
	},
	_prepareDialog: function(/* int */colIndex){
		var sc = this._savedCriterias,
			cbs = this._cboxes, i, cbox;
		this.curColIdx = colIndex;
		if(!sc){
			if(cbs.length === 0){
				this.addCriteriaBoxes(1);
			}else{
				//Re-populate columns anyway, because we don't know when the column is set to hidden.
				for(i = 0; (cbox = cbs[i]); ++i){
					cbox.changeCurrentColumn();
				}
			}
		}else if(this._criteriasChanged){
			this.filterDefPane._relSelect.set("value", this._relOpCls === "logicall" ? "0" : "1");
			this._criteriasChanged = false;
			var needNewCBox = sc.length > cbs.length ? sc.length - cbs.length : 0;
			this.addCriteriaBoxes(needNewCBox);
			this.removeCriteriaBoxes(cbs.length - sc.length);
			this.filterDefPane._clearFilterBtn.set("disabled", false);
			for(i = 0; i < cbs.length - needNewCBox; ++i){
				cbs[i].load(sc[i]);
			}
			if(needNewCBox > 0){
				var handled = [], handle = connect.connect(this, "onRendered", function(cbox){
					var i = array.indexOf(cbs, cbox);
					if(!handled[i]){
						handled[i] = true;
						if(--needNewCBox === 0){
							connect.disconnect(handle);
						}
						cbox.load(sc[i]);
					}
				});
			}
		}
		//Since we're allowed to remove cboxes when the definition pane is not shown,
		//we have to resize the container to have a correct _verticalSpace.
		this.filterDefPane.cboxContainer.resize();
	},
	_defineFilter: function(){
		var cbs = this._cboxes,
			filterCboxes = function(method){
				return array.filter(array.map(cbs, function(cbox){
					return cbox[method]();
				}), function(result){
					return !!result;
				});
			},
			exprs = filterCboxes("getExpr");
		this._savedCriterias = filterCboxes("save");
		exprs = exprs.length == 1 ? exprs[0] : {
			"op": this._relOpCls,
			"data": exprs
		};
		exprs = this.builder.buildExpression(exprs);
		
		this.plugin.grid.layer("filter").filterDef(exprs);
		this.filterDefPane._clearFilterBtn.set("disabled", false);
	},
	_updateCBoxTitles: function(){
		for(var cbs = this._cboxes, i = cbs.length; i > 0; --i){
			cbs[i - 1].updateRuleIndex(i);
			cbs[i - 1].setAriaInfo(i);
		}
	},
	_updatePane: function(){
		var cbs = this._cboxes,
			defPane = this.filterDefPane;
		defPane._addCBoxBtn.set("disabled", cbs.length == this.plugin.args.ruleCount);
		defPane._filterBtn.set("disabled", !this.canFilter());
	},
	canFilter: function(){
		return array.filter(this._cboxes, function(cbox){
			return !cbox.isEmpty();
		}).length > 0;
	},
	_closeDlgAndUpdateGrid: function(){
		this.closeDialog();
		var g = this.plugin.grid;
		g.showMessage(g.loadingMessage);
		setTimeout(lang.hitch(g, g._refresh), this._defPane.duration + 10);
	}
});

return FilterDefDialog;
});
