define(["dojo/_base/array", "dojo/_base/declare", "dojo/query",
		"dojo/_base/connect", "dojo/_base/Color", "./Legend", "dijit/form/CheckBox", "../action2d/Highlight",
		"dojox/lang/functional", "dojox/gfx/fx", "dojo/keys", "dojo/dom-construct",
		"dojo/dom-prop"], 
	function(arrayUtil, declare, query, hub, Color, Legend, CheckBox,
			 Highlight, df, fx, keys, dom, domProp){

	var FocusManager = declare(null, {
		// summary:
		//		It will take legend as a tab stop, and using
		//		cursor keys to navigate labels within the legend.
		// tags:
		//		private
		constructor: function(legend){
			this.legend = legend;
			this.index = 0;
			this.horizontalLength = this._getHrizontalLength();
			arrayUtil.forEach(legend.legends, function(item, i){
				if(i > 0){
					query("input", item).attr("tabindex", -1);
				}
			});
			this.firstLabel = query("input", legend.legends[0])[0];
			hub.connect(this.firstLabel, "focus", this, function(){this.legend.active = true;});
			hub.connect(this.legend.domNode, "keydown", this, "_onKeyEvent");
		},
		_getHrizontalLength: function(){
			var horizontal = this.legend.horizontal;
			if(typeof horizontal == "number"){
				return Math.min(horizontal, this.legend.legends.length);
			}else if(!horizontal){
				return 1;
			}else{
				return this.legend.legends.length;
			}
		},
		_onKeyEvent: function(e){
			//	if not focused
			if(!this.legend.active){
				return;
			}
			//	lose focus
			if(e.keyCode == keys.TAB){
				this.legend.active = false;
				return;
			}
			//	handle with arrow keys
			var max = this.legend.legends.length;
			switch(e.keyCode){
				case keys.LEFT_ARROW:
					this.index--;
					if(this.index < 0){
						this.index += max;
					}
					break;
				case keys.RIGHT_ARROW:
					this.index++;
					if(this.index >= max){
						this.index -= max;
					}
					break;
				case keys.UP_ARROW:
					if(this.index - this.horizontalLength >= 0){
						this.index -= this.horizontalLength;
					}
					break;
				case keys.DOWN_ARROW:
					if(this.index + this.horizontalLength < max){
						this.index += this.horizontalLength;
					}
					break;
				default:
					return;
			}
			this._moveToFocus();
			Event.stop(e);
		},
		_moveToFocus: function(){
			query("input", this.legend.legends[this.index])[0].focus();
		}
	});
			
	var SelectableLegend = declare("dojox.charting.widget.SelectableLegend", Legend, {
		// summary:
		//		An enhanced chart legend supporting interactive events on data series
		
		//	theme component
		outline:			false,	//	outline of vanished data series
		transitionFill:		null,	//	fill of deselected data series
		transitionStroke:	null,	//	stroke of deselected data series
		
		postCreate: function(){
			this.legends = [];
			this.legendAnim = {};
			this._cbs = [];
			this.inherited(arguments);
		},
		refresh: function(){
			this.legends = [];
			this._clearLabels();
			this.inherited(arguments);
			this._applyEvents();
			new FocusManager(this);
		},
		_clearLabels: function(){
			var cbs = this._cbs;
			while(cbs.length){
				cbs.pop().destroyRecursive();
			}
		},
		_addLabel: function(dyn, label){
			this.inherited(arguments);
			//	create checkbox
			var legendNodes = query("td", this.legendBody);
			var currentLegendNode = legendNodes[legendNodes.length - 1];
			this.legends.push(currentLegendNode);
			var checkbox = new CheckBox({checked: true});
			this._cbs.push(checkbox);
			dom.place(checkbox.domNode, currentLegendNode, "first");
			// connect checkbox and existed label
			var clabel = query("label", currentLegendNode)[0];
			domProp.set(clabel, "for", checkbox.id);
		},
		_applyEvents: function(){
			// summary:
			//		Apply click-event on checkbox and hover-event on legend icon,
			//		highlight data series or toggle it.
			
			// if the chart has not yet been refreshed it will crash here (targetData.group == null)
			if(this.chart.dirty){
				return;
			}
			arrayUtil.forEach(this.legends, function(legend, i){
				var targetData, shapes = [], plotName, seriesName;
				if(this._isPie()){
					targetData = this.chart.stack[0];
					shapes.push(targetData.group.children[i]);
					plotName = targetData.name;
					seriesName = this.chart.series[0].name;
				}else{
					targetData = this.chart.series[i];
					shapes = targetData.group.children;
					plotName = targetData.plot;
					seriesName = targetData.name;
				}
				var originalDyn = {
					fills : df.map(shapes, "x.getFill()"),
					strokes: df.map(shapes, "x.getStroke()")
				};
				//	toggle action
				var legendCheckBox = query(".dijitCheckBox", legend)[0];
				hub.connect(legendCheckBox, "onclick", this, function(e){
					this._toggle(shapes, i, legend.vanished, originalDyn, seriesName, plotName);
					legend.vanished = !legend.vanished;
					e.stopPropagation();
				});
				
				//	highlight action
				var legendIcon = query(".dojoxLegendIcon", legend)[0],
					iconShape = this._getFilledShape(this._surfaces[i].children);
				arrayUtil.forEach(["onmouseenter", "onmouseleave"], function(event){
					hub.connect(legendIcon, event, this, function(e){
						this._highlight(e, iconShape, shapes, i, legend.vanished, originalDyn, seriesName, plotName);
					});
				}, this);
			},this);
		},
		_toggle: function(shapes, index, isOff, dyn, seriesName, plotName){
			arrayUtil.forEach(shapes, function(shape, i){
				var startFill = dyn.fills[i],
					endFill = this._getTransitionFill(plotName),
					startStroke = dyn.strokes[i],
					endStroke = this.transitionStroke;
				if(startFill){
					if(endFill && (typeof startFill == "string" || startFill instanceof Color)){
						fx.animateFill({
							shape: shape,
							color: {
								start: isOff ? endFill : startFill,
								end: isOff ? startFill : endFill
							}
						}).play();
					}else{
						shape.setFill(isOff ? startFill : endFill);
					}
				}
				if(startStroke && !this.outline){
					shape.setStroke(isOff ? startStroke : endStroke);
				}
			}, this);
		},
		_highlight: function(e, iconShape, shapes, index, isOff, dyn, seriesName, plotName){
			if(!isOff){
				var anim = this._getAnim(plotName),
					isPie = this._isPie(),
					type = formatEventType(e.type);
				//	highlight the label icon,
				var label = {
					shape: iconShape,
					index: isPie ? "legend" + index : "legend",
					run: {name: seriesName},
					type: type
				};
				anim.process(label);
				//	highlight the data items
				arrayUtil.forEach(shapes, function(shape, i){
					shape.setFill(dyn.fills[i]);
					var o = {
						shape: shape,
						index: isPie ? index : i,
						run: {name: seriesName},
						type: type
					};
					anim.duration = 100;
					anim.process(o);
				});
			}
		},
		_getAnim: function(plotName){
			if(!this.legendAnim[plotName]){
				this.legendAnim[plotName] = new Highlight(this.chart, plotName);
				// calling this is marking the plot dirty however here this is a "fake" highlight action
				// we don't want to re-render the chart, _highlight is the in charge of running the animation
				this.chart.getPlot(plotName).dirty = false;
			}
			return this.legendAnim[plotName];
		},
		_getTransitionFill: function(plotName){
			// Since series of stacked charts all start from the base line,
			// fill the "front" series with plotarea color to make it disappear .
			if(this.chart.stack[this.chart.plots[plotName]].declaredClass.indexOf("dojox.charting.plot2d.Stacked") != -1){
				return this.chart.theme.plotarea.fill;
			}
			return null;
		},
		_getFilledShape: function(shapes){
			// summary:
			//		Get filled shape in legend icon which would be highlighted when hovered
			var i = 0;
			while(shapes[i]){
				if(shapes[i].getFill())return shapes[i];
				i++;
			}
			return null;
		},
		_isPie: function(){
			return this.chart.stack[0].declaredClass == "dojox.charting.plot2d.Pie";
		},
		destroy: function(){
			this._clearLabels();
			this.inherited(arguments);
		}
	});
	
	function formatEventType(type){
		if(type == "mouseenter")return "onmouseover";
		if(type == "mouseleave")return "onmouseout";
		return "on" + type;
	}

	return SelectableLegend;
});
