define([
	"dojo/_base/array",
	"dojo/_base/connect",
	"dojo/_base/lang",
	"dojo/_base/fx",
	"dojo/dom-style",
	"dojo/fx"
], function(array, connect, lang, baseFx, domStyle, fx) {

	// Constants used to identify which edge the pane pans in from.
	var DOWN = 0,
		RIGHT = 1,
		UP = 2,
		LEFT = 3;

	function _pan(/*int*/type, /*Object*/args){
		// summary:
		//		Handles the preparation of the dom node and creates the dojo.Animation object.
		var j = {
				node: args.current.node,
				duration: args.duration,
				easing: args.easing
			},
			k = {
				node: args.next.node,
				duration: args.duration,
				easing: args.easing
			},
			r = args.rotatorBox,
			m = type % 2,
			a = m ? "left" : "top",
			s = (m ? r.w : r.h) * (type < 2 ? -1 : 1),
			p = {},
			q = {};

		domStyle.set(k.node, {
			display: "",
			opacity: 0
		});

		p[a] = {
			start: 0,
			end: -s
		};

		q[a] = {
			start: s,
			end: 0
		};

		return fx.combine([ /*dojo.Animation*/
			baseFx.animateProperty(lang.mixin({ properties: p }, j)),
			baseFx.fadeOut(j),
			baseFx.animateProperty(lang.mixin({ properties: q }, k)),
			baseFx.fadeIn(k)
		]);
	}

	function _setZindex(/*DomNode*/n, /*int*/z){
		// summary:
		//		Helper function for continuously panning.
		domStyle.set(n, "zIndex", z);
	}

	var exports = {
		panFade: function(/*Object*/args){
			// summary:
			//		Returns a dojo.Animation that either pans left or right to the next pane.
			//		The actual direction depends on the order of the panes.
			//
			//		If panning forward from index 1 to 3, it will perform a pan left. If panning
			//		backwards from 5 to 1, then it will perform a pan right.
			//
			//		If the parameter "continuous" is set to true, it will return an animation
			//		chain of several pan animations of each intermediate pane panning. For
			//		example, if you pan forward from 1 to 3, it will return an animation panning
			//		left from 1 to 2 and then 2 to 3.
			//
			//		If an easing is specified, it will be applied to each pan transition.  For
			//		example, if you are panning from pane 1 to pane 5 and you set the easing to
			//		"dojo.fx.easing.elasticInOut", then it will "wobble" 5 times, once for each
			//		pan transition.
			//
			//		If the parameter "wrap" is set to true, it will pan to the next pane using
			//		the shortest distance in the array of panes. For example, if there are 6
			//		panes, then panning from 5 to 1 will pan forward (left) from pane 5 to 6 and
			//		6 to 1.  If the distance is the same either going forward or backwards, then
			//		it will always pan forward (left).
			//
			//		A continuous pan will use the target pane's duration to pan all intermediate
			//		panes.  To use the target's pane duration for each intermediate pane, then
			//		set the "quick" parameter to "false".

			var w = args.wrap,
				p = args.rotator.panes,
				len = p.length,
				z = len,
				j = args.current.idx,
				k = args.next.idx,
				nw = Math.abs(k - j),
				ww = Math.abs((len - Math.max(j, k)) + Math.min(j, k)) % len,
				_forward = j < k,
				_dir = LEFT,
				_pans = [],
				_nodes = [],
				_duration = args.duration;

			// default to pan left, but check if we should pan right.
			// need to take into account wrapping.
			if((!w && !_forward) || (w && (_forward && nw > ww || !_forward && nw < ww))){
				_dir = RIGHT;
			}

			if(args.continuous){
				// if continuous pans are quick, then divide the duration by the number of panes
				if(args.quick){
					_duration = Math.round(_duration / (w ? Math.min(ww, nw) : nw));
				}

				// set the current pane's z-index
				_setZindex(p[j].node, z--);

				var f = (_dir == LEFT);

				// loop and set z-indexes and get all pan animations
				while(1){
					// set the current pane
					var i = j;

					// increment/decrement the next pane's index
					if(f){
						if(++j >= len){
							j = 0;
						}
					}else{
						if(--j < 0){
							j = len - 1;
						}
					}

					var x = p[i],
						y = p[j];

					// set next pane's z-index
					_setZindex(y.node, z--);

					// build the pan animation
					_pans.push(_pan(_dir, lang.mixin({
						easing: function(m){ return m; } // continuous gets a linear easing by default
					}, args, {
						current: x,
						next: y,
						duration: _duration
					})));

					// if we're done, then break out of the loop
					if((f && j == k) || (!f && j == k)){
						break;
					}

					// this must come after the break... we don't want the last pane to get it's
					// styles reset.
					_nodes.push(y.node);
				}

				// build the chained animation of all pan animations
				var _anim = fx.chain(_pans);

				// clean up styles when the chained animation finishes
				var h = connect.connect(_anim, "onEnd", function(){
					connect.disconnect(h);
					array.forEach(_nodes, function(q){
						domStyle.set(q, {
							display: "none",
							left: 0,
							opacity: 1,
							top: 0,
							zIndex: 0
						});
					});
				});

				return _anim;
			}

			// we're not continuous, so just return a normal pan animation
			return _pan(_dir, args); /*dojo.Animation*/
		},

		panFadeDown: function(/*Object*/args){
			// summary:
			//		Returns a dojo.Animation that pans in the next rotator pane from the top.
			return _pan(DOWN, args); /*dojo.Animation*/
		},

		panFadeRight: function(/*Object*/args){
			// summary:
			//		Returns a dojo.Animation that pans in the next rotator pane from the right.
			return _pan(RIGHT, args); /*dojo.Animation*/
		},

		panFadeUp: function(/*Object*/args){
			// summary:
			//		Returns a dojo.Animation that pans in the next rotator pane from the bottom.
			return _pan(UP, args); /*dojo.Animation*/
		},

		panFadeLeft: function(/*Object*/args){
			// summary:
			//		Returns a dojo.Animation that pans in the next rotator pane from the left.
			return _pan(LEFT, args); /*dojo.Animation*/
		}
	};

	// back-compat, remove for 2.0
	lang.mixin(lang.getObject("dojox.widget.rotator"), exports);

	return exports;
});