define(["dojo/_base/declare", "dojo/_base/window", "dojo/_base/array", "dojo/_base/event",
	"dojo/_base/connect", "dojo/mouse", "./ChartAction", "dojo/sniff", "dojo/dom-prop", "dojo/keys",
	"dojo/has!dojo-bidi?../bidi/action2d/ZoomAndPan"],
	function(declare, win, arr, eventUtil, connect, mouse, ChartAction, has, domProp, keys, BidiMouseZoomAndPan){

	/*=====
	var __MouseZoomAndPanCtorArgs = {
		// summary:
		//		Additional arguments for mouse zoom and pan actions.
		// axis: String?
		//		Target axis name for this action.  Default is "x".
		// scaleFactor: Number?
		//		The scale factor applied on mouse wheel zoom.  Default is 1.2.
		// maxScale: Number?
		//		The max scale factor accepted by this chart action.  Default is 100.
		// enableScroll: Boolean?
		//		Whether mouse drag gesture should scroll the chart.  Default is true.
		// enableDoubleClickZoom: Boolean?
		//		Whether a double click gesture should toggle between fit and zoom on the chart.  Default is true.
		// enableKeyZoom: Boolean?
		//		Whether a keyZoomModifier + + or keyZoomModifier + - key press should zoom in our out on the chart.  Default is true.
		// keyZoomModifier: String?
		//		Which keyboard modifier should used for keyboard zoom in and out. This should be one of "alt", "ctrl", "shift" or "none" for no modifier. Default is "ctrl".
	};
	=====*/

	var sUnit = has("mozilla") ? 3 : 120;
	var keyTests = {
		none: function(event){
			return !event.ctrlKey && !event.altKey && !event.shiftKey;
		},
		ctrl: function(event){
			return event.ctrlKey && !event.altKey && !event.shiftKey;
		},
		alt: function(event){
			return !event.ctrlKey && event.altKey && !event.shiftKey;
		},
		shift: function(event){
			return !event.ctrlKey && !event.altKey && event.shiftKey;
		}
	};

	var MouseZoomAndPan = declare(has("dojo-bidi")? "dojox.charting.action2d.NonBidiMouseZoomAndPan" : "dojox.charting.action2d.MouseZoomAndPan", ChartAction, {
		// summary:
		//		Create an mouse zoom and pan action.
		//		You can zoom in or out the data window with mouse wheel. You can scroll using mouse drag gesture. 
		//		You can toggle between zoom and fit view using double click on the chart.

		// the data description block for the widget parser
		defaultParams: {
			axis: "x",
			scaleFactor: 1.2,	
			maxScale: 100,
			enableScroll: true,
			enableDoubleClickZoom: true,
			enableKeyZoom: true,
			keyZoomModifier: "ctrl"
		},
		optionalParams: {}, // no optional parameters
		
		constructor: function(chart, plot, kwArgs){
			// summary:
			//		Create an mouse zoom and pan action and connect it.
			// chart: dojox/charting/Chart
			//		The chart this action applies to.
			// kwArgs: __MouseZoomAndPanCtorArgs?
			//		Optional arguments for the chart action.
			this._listeners = [{eventName: mouse.wheel, methodName: "onMouseWheel"}];
			if(!kwArgs){ kwArgs = {}; }
			this.axis = kwArgs.axis ? kwArgs.axis : "x";
			this.scaleFactor = kwArgs.scaleFactor ? kwArgs.scaleFactor : 1.2;
			this.maxScale = kwArgs.maxScale ? kwArgs.maxScale : 100;
			this.enableScroll = kwArgs.enableScroll != undefined ? kwArgs.enableScroll : true;
			this.enableDoubleClickZoom = kwArgs.enableDoubleClickZoom != undefined ? kwArgs.enableDoubleClickZoom : true;
			this.enableKeyZoom = kwArgs.enableKeyZoom != undefined ? kwArgs.enableKeyZoom : true;
			this.keyZoomModifier = kwArgs.keyZoomModifier ? kwArgs.keyZoomModifier : "ctrl";
			if(this.enableScroll){
				this._listeners.push({eventName: "onmousedown", methodName: "onMouseDown"});
			}
			if(this.enableDoubleClickZoom){
				this._listeners.push({eventName: "ondblclick", methodName: "onDoubleClick"});
			}
			if(this.enableKeyZoom){
				this._listeners.push({eventName: "keypress", methodName: "onKeyPress"});				
			}
			this._handles = [];
			this.connect();
		},
		
		_disconnectHandles: function(){
			if(has("ie")){
				this.chart.node.releaseCapture();
			}
			arr.forEach(this._handles, connect.disconnect);
			this._handles = [];
		},
		
		connect: function(){
			// summary:
			//		Connect this action to the chart.
			this.inherited(arguments);
			if(this.enableKeyZoom){
				// we want to be able to get focus to receive key events 
				domProp.set(this.chart.node, "tabindex", "0");
				// if one doesn't want a focus border he can do something like
				// dojo.style(this.chart.node, "outline", "none");
			}
		},
		
		disconnect: function(){
			// summary:
			//		Disconnect this action from the chart.
			this.inherited(arguments);
			if(this.enableKeyZoom){
				// we don't need anymore to be able to get focus to receive key events 
				domProp.set(this.chart.node, "tabindex", "-1");
			}
			// in case we disconnect before the end of the action
			this._disconnectHandles();
		},
	
		onMouseDown: function(event){
			// summary:
			//		Called when mouse is down on the chart.
			var chart = this.chart, axis = chart.getAxis(this.axis);
			if(!axis.vertical){
				this._startCoord = event.pageX;
			}else{
				this._startCoord = event.pageY;
			}
			this._startOffset = axis.getWindowOffset();
			this._isPanning = true;
			// we now want to capture mouse move events everywhere to avoid
			// stop scrolling when going out of the chart window
			if(has("ie")){
				this._handles.push(connect.connect(this.chart.node, "onmousemove", this, "onMouseMove"));
				this._handles.push(connect.connect(this.chart.node, "onmouseup", this, "onMouseUp"));
				this.chart.node.setCapture();
			}else{
				this._handles.push(connect.connect(win.doc, "onmousemove", this, "onMouseMove"));
				this._handles.push(connect.connect(win.doc, "onmouseup", this, "onMouseUp"));
			}
			chart.node.focus();
			// prevent the browser from trying the drag on the "image"
			eventUtil.stop(event);
		},
	
		onMouseMove: function(event){
			// summary:
			//		Called when mouse is moved on the chart.
			if(this._isPanning){
				var chart = this.chart, axis = chart.getAxis(this.axis);
				var delta = this._getDelta(event);
				
				var bounds = axis.getScaler().bounds,
					s = bounds.span / (bounds.upper - bounds.lower);
		
				var scale = axis.getWindowScale();
				chart.setAxisWindow(this.axis, scale, this._startOffset - delta / s / scale);
				chart.render();
			}
		},
	
		onMouseUp: function(event){
			// summary:
			//		Called when mouse is up on the chart.
			this._isPanning = false;
			this._disconnectHandles();
		},
		
		onMouseWheel: function(event){
			// summary:
			//		Called when mouse wheel is used on the chart.
			var scroll = event.wheelDelta / sUnit;
			// on Mozilla the sUnit might actually not always be 3
			// make sure we never have -1 < scroll < 1
			if(scroll > -1 && scroll < 0){
				scroll = -1;
			}else if(scroll > 0 && scroll < 1){
				scroll = 1;
			}
 			this._onZoom(scroll, event);
		},
		
		onKeyPress: function(event){
			// summary:
			//		Called when a key is pressed on the chart.
			if(keyTests[this.keyZoomModifier](event)){
				if(event.keyChar == "+" || event.keyCode == keys.NUMPAD_PLUS){
					this._onZoom(1, event);
				}else if(event.keyChar == "-" || event.keyCode == keys.NUMPAD_MINUS){
					this._onZoom(-1, event);					
				}
			} 
		},
		
		onDoubleClick: function(event){
			// summary:
			//		Called when the mouse is double is double clicked on the chart. Toggle between zoom and fit chart.
			var chart = this.chart, axis = chart.getAxis(this.axis);
			var scale = 1 / this.scaleFactor;
			// are we fit?
			if(axis.getWindowScale()==1){
				// fit => zoom
				var scaler = axis.getScaler(), start = scaler.bounds.from, end = scaler.bounds.to, 
				oldMiddle = (start + end) / 2, newMiddle = this.plot.toData({x: event.pageX, y: event.pageY})[this.axis], 
				newStart = scale * (start - oldMiddle) + newMiddle, newEnd = scale * (end - oldMiddle) + newMiddle;
				chart.zoomIn(this.axis, [newStart, newEnd]);
			}else{
				// non fit => fit
				chart.setAxisWindow(this.axis, 1, 0);
				chart.render();
			}
			eventUtil.stop(event);
		},
		
		_onZoom: function(scroll, event){
			var scale = (scroll < 0 ? Math.abs(scroll)*this.scaleFactor : 
				1 / (Math.abs(scroll)*this.scaleFactor));
			var chart = this.chart, axis = chart.getAxis(this.axis);
			// after wheel reset event position exactly if we could start a new scroll action
			var cscale = axis.getWindowScale();
			if(cscale / scale > this.maxScale){
				return;
			}
			var scaler = axis.getScaler(), start = scaler.bounds.from, end = scaler.bounds.to;
			// keep mouse pointer as transformation center if available otherwise center
			var middle = (event.type == "keypress") ? (start + end) / 2 :
				this.plot.toData({x: event.pageX, y: event.pageY})[this.axis];
			var newStart = scale * (start - middle) + middle, newEnd = scale * (end - middle) + middle;
			chart.zoomIn(this.axis, [newStart, newEnd]);
			// do not scroll browser
			eventUtil.stop(event);
		},
		
		_getDelta: function(event){
			return this.chart.getAxis(this.axis).vertical?(this._startCoord- event.pageY):(event.pageX - this._startCoord);
		}
	});
	return has("dojo-bidi")? declare("dojox.charting.action2d.MouseZoomAndPan", [MouseZoomAndPan, BidiMouseZoomAndPan]) : MouseZoomAndPan;
});
