define(["dojo/_base/lang", "dojo/_base/declare", "dojo/_base/array", "dojo/sniff",
		"./CartesianBase", "./common", "dojox/lang/utils", "dojox/gfx/fx"],
	function(lang, declare, arr, has, CartesianBase, dc, du, fx){

	var sortTicks = function(a,b){return a.value - b.value};

	/*=====
	declare("dojox.charting.plot2d.__GridCtorArgs", dojox.charting.plot2d.__CartesianCtorArgs, {
		// summary:
		//		A special keyword arguments object that is specific to a grid "plot".

		// majorHLine: dojox.gfx.Stroke?
		//		An optional dojox.gfx.Stroke for a major horizontal line. By default major lines use major tick stroke.
		majorHLine:undefined,

		// minorHLine: dojox.gfx.Stroke?
		//		An optional dojox.gfx.Stroke for a minor horizontal line. By default minor lines use minor tick stroke.
		minorHLine:undefined,

		// majorVLine: dojox.gfx.Stroke?
		//		An optional dojox.gfx.Stroke for a major vertical line. By default major lines use major tick stroke.
		majorVLine:undefined,

		// minorVLine: dojox.gfx.Stroke?
		//		An optional dojox.gfx.Stroke for a minor vertical line. By default major lines use major tick stroke.
		minorVLine:undefined,

		// hFill: dojox.gfx.Fill?
		//		An optional dojox.gfx.Fill used to fill every other horizontal stripe created by grid lines.
		hFill: undefined,

		// hAlternateFill: dojox.gfx.Fill?
		//		An optional dojox.gfx.Fill used to fill alternating horizontal stripe created by grid lines not filled by `hFill`.
		hAlternateFill: undefined,

		// vFill: dojox.gfx.Fill?
		//		An optional dojox.gfx.Fill used to fill every other vertical stripe created by grid lines.
		vFill: undefined,

		// vAlternateFill: dojox.gfx.Fill?
		//		An optional dojox.gfx.Fill used to fill alternating vertical stripe created by grid lines not filled by `vFill`.
		vAlternateFill: undefined,

		// hMajorLines: Boolean?
		//		Whether to show lines at the major ticks along the horizontal axis. Default is true.
		hMajorLines: true,

		// hMinorLines: Boolean?
		//		Whether to show lines at the minor ticks along the horizontal axis. Default is false.
		hMinorLines: false,

		// vMajorLines: Boolean?
		//		Whether to show lines at the major ticks along the vertical axis. Default is true.
		vMajorLines: true,

		// vMinorLines: Boolean?
		//		Whether to show lines at the major ticks along the vertical axis. Default is false.
		vMinorLines: false,

		// hStripes: Boolean?
		//		Whether to show horizontal stripes. Default is false.
		hStripes: false,

		// vStripes: Boolean?
		//		Whether to show vertical stripes. Default is false.
		vStripes: false,

		// enableCache: Boolean?
		//		Whether the grid lines are cached from one rendering to another. This improves the rendering performance of
		//		successive rendering but penalize the first rendering.  Default false.
		enableCache: false,

		// renderOnAxis: Boolean?
		//		Whether or not the grid is rendered when drawn at horizontal or vertical axis position. Default is true.
		renderOnAxis: true
	});
	=====*/

	return declare("dojox.charting.plot2d.Grid", CartesianBase, {
		// summary:
		//		A "faux" plot that can be placed behind other plots to represent
		//		a grid against which other plots can be easily measured.
		defaultParams: {
			hMajorLines: true,	// draw horizontal major lines
			hMinorLines: false,	// draw horizontal minor lines
			vMajorLines: true,	// draw vertical major lines
			vMinorLines: false,	// draw vertical minor lines
			hStripes: false,	// draw vertical stripes
			vStripes: false,	// draw vertical stripes
			animate: null,   // animate bars into place
			enableCache: false,
			renderOnAxis: true
		},

		optionalParams: {
			majorHLine: {},
			minorHLine: {},
			majorVLine: {},
			minorVLine: {},
			hFill: {},
			vFill: {},
			hAlternateFill: {},
			vAlternateFill: {}
		},

		constructor: function(chart, kwArgs){
			// summary:
			//		Create the faux Grid plot.
			// chart: dojox/charting/Chart
			//		The chart this plot belongs to.
			// kwArgs: dojox.charting.plot2d.__GridCtorArgs?
			//		An optional keyword arguments object to help define the parameters of the underlying grid.
			this.opt = lang.clone(this.defaultParams);
			du.updateWithObject(this.opt, kwArgs);
			du.updateWithPattern(this.opt, kwArgs, this.optionalParams);
			this.animate = this.opt.animate;
			if(this.opt.enableCache){
				this._lineFreePool = [];
				this._lineUsePool = [];
				this._rectFreePool = [];
				this._rectUsePool = [];
			}
		},
		addSeries: function(run){
			// summary:
			//		Ignored but included as a dummy method.
			// returns: dojox/charting/plot2d/Grid
			//		The reference to this plot for functional chaining.
			return this;	//	dojox/charting/plot2d/Grid
		},
		getSeriesStats: function(){
			// summary:
			//		Returns default stats (irrelevant for this type of plot).
			// returns: Object
			//		{hmin, hmax, vmin, vmax} min/max in both directions.
			return lang.delegate(dc.defaultStats); // Object
		},
		cleanGroup: function(){
			this.inherited(arguments);
			if(this.opt.enableCache){
				this._lineFreePool = this._lineFreePool.concat(this._lineUsePool);
				this._lineUsePool = [];
				this._rectFreePool = this._rectFreePool.concat(this._rectUsePool);
				this._rectUsePool = [];
			}
		},
		createLine: function(creator, params){
			var line;
			if(this.opt.enableCache && this._lineFreePool.length > 0){
				line = this._lineFreePool.pop();
				line.setShape(params);
				// was cleared, add it back
				creator.add(line);
			}else{
				line = creator.createLine(params);
			}
			if(this.opt.enableCache){
				this._lineUsePool.push(line);
			}
			return line;
		},
		createRect: function(creator, params){
			var rect;
			if(this.opt.enableCache && this._rectFreePool.length > 0){
				rect = this._rectFreePool.pop();
				rect.setShape(params);
				// was cleared, add it back
				creator.add(rect);
			}else{
				rect = creator.createRect(params);
			}
			if(this.opt.enableCache){
				this._rectUsePool.push(rect);
			}
			return rect;
		},
		
		render: function(dim, offsets){
			// summary:
			//		Render the plot on the chart.
			// dim: Object
			//		An object of the form { width, height }.
			// offsets: Object
			//		An object of the form { l, r, t, b }.
			// returns: dojox/charting/plot2d/Grid
			//		A reference to this plot for functional chaining.
			if(this.zoom){
				return this.performZoom(dim, offsets);
			}
			this.dirty = this.isDirty();
			if(!this.dirty){ return this; }
			this.cleanGroup();
			var s = this.getGroup(), ta = this.chart.theme, lineStroke, ticks;
			if((has("ios") && has("ios") < 6) || has("android") || (has("safari") && !has("ios"))){
				// clipping seems buggy in some mobile Webkit browser and Safari desktop
				// it does not clip correctly if only lines are present => create a invisible rectangle...
				var w = Math.max(0, dim.width  - offsets.l - offsets.r),
					h = Math.max(0, dim.height - offsets.t - offsets.b);
				s.createRect({ x: offsets.l, y: offsets.t, width: w, height: h});
			}
			if(this._vAxis){
				// draw horizontal stripes and lines
				ticks = this._vAxis.getTicks();
				var vScaler = this._vAxis.getScaler();
				if(ticks != null && vScaler != null){
					var vt = vScaler.scaler.getTransformerFromModel(vScaler);
					if(this.opt.hStripes){
						this._renderHRect(ticks, ta.grid, dim, offsets, vScaler, vt);
					}
					if(this.opt.hMinorLines){
						lineStroke = this.opt.minorHLine || (ta.grid && ta.grid.minorLine) || ta.axis.minorTick;
						this._renderHLines(ticks.minor, lineStroke, dim, offsets, vScaler, vt);
					}
					if(this.opt.hMajorLines){
						lineStroke = this.opt.majorHLine || (ta.grid && ta.grid.majorLine) || ta.axis.majorTick;
						this._renderHLines(ticks.major, lineStroke, dim, offsets, vScaler, vt);
					}
				}
				
			}
			if(this._hAxis){
				// draw vertical stripes and lines
				ticks = this._hAxis.getTicks();
				var hScaler = this._hAxis.getScaler();
				if(ticks != null && hScaler != null){
					var ht = hScaler.scaler.getTransformerFromModel(hScaler);
					if(this.opt.vStripes){
						this._renderVRect(ticks, ta.grid, dim, offsets, hScaler, ht);
					}
					if(ticks && this.opt.vMinorLines){
						lineStroke = this.opt.minorVLine || (ta.grid && ta.grid.minorLine) || ta.axis.minorTick;
						this._renderVLines(ticks.minor, lineStroke, dim, offsets, hScaler, ht);
					}
					if(ticks && this.opt.vMajorLines){
						lineStroke = this.opt.majorVLine || (ta.grid && ta.grid.majorLine) || ta.axis.majorTick;
						this._renderVLines(ticks.major, lineStroke, dim, offsets, hScaler, ht);
					}

				}
			}
			this.dirty = false;
			return this;	//	dojox/charting/plot2d/Grid
		},
		_renderHLines: function(ticks, lineStroke, dim, offsets, vScaler, vt){
			var s = this.getGroup();
			arr.forEach(ticks, function(tick){
				if(!this.opt.renderOnAxis && tick.value == (this._vAxis.opt.leftBottom?vScaler.bounds.from:vScaler.bounds.to)){
					return;
				}
				var y = dim.height - offsets.b - vt(tick.value);
				var hLine = this.createLine(s, {
					x1: offsets.l,
					y1: y,
					x2: dim.width - offsets.r,
					y2: y
				}).setStroke(lineStroke);
				if(this.animate){
					this._animateGrid(hLine, "h", offsets.l, offsets.r + offsets.l - dim.width);
				}
			}, this);
		},
		_renderVLines: function(ticks, lineStroke, dim, offsets, hScaler, ht){
			var s = this.getGroup();
			arr.forEach(ticks, function(tick){
				if(!this.opt.renderOnAxis && tick.value == (this._hAxis.opt.leftBottom?hScaler.bounds.from:hScaler.bounds.to)){
					return;
				}
				var x = offsets.l + ht(tick.value);
				var vLine = this.createLine(s, {
					x1: x,
					y1: offsets.t,
					x2: x,
					y2: dim.height - offsets.b
				}).setStroke(lineStroke);
				if(this.animate){
					this._animateGrid(vLine, "v", dim.height - offsets.b, dim.height - offsets.b - offsets.t);
				}
			}, this);
		},
		_renderHRect: function(ticks, theme, dim, offsets, vScaler, vt){
			var fill, tick, y, y2, hStripe;
			var allTicks = ticks.major.concat(ticks.minor);
			allTicks.sort(sortTicks);
			if(allTicks[0].value > vScaler.bounds.from){
				allTicks.splice(0, 0, {value: vScaler.bounds.from});
			}
			if(allTicks[allTicks.length - 1].value < vScaler.bounds.to){
				allTicks.push({value: vScaler.bounds.to});
			}
			var s = this.getGroup();
			for(var j = 0; j < allTicks.length - 1; j++){
				tick = allTicks[j];
				y = dim.height - offsets.b - vt(tick.value);
				y2 = dim.height - offsets.b - vt(allTicks[j+1].value);

				fill = (j%2 == 0)?(this.opt.hAlternateFill ||(theme && theme.alternateFill)):
					(this.opt.hFill || (theme && theme.fill));
				if(fill){
					hStripe = this.createRect(s, {
						x: offsets.l,
						y: y,
						width: dim.width - offsets.r,
						height: y - y2
					}).setFill(fill);
					if(this.animate){
						this._animateGrid(hStripe, "h", offsets.l, offsets.r + offsets.l - dim.width);
					}
				}
			}
		},
		_renderVRect: function(ticks, theme, dim, offsets, hScaler, ht){
			var fill, tick, x, x2, vStripe;
			var allTicks = ticks.major.concat(ticks.minor);
			allTicks.sort(sortTicks);
			if(allTicks[0].value > hScaler.bounds.from){
				allTicks.splice(0, 0, {value: hScaler.bounds.from});
			}
			if(allTicks[allTicks.length - 1].value < hScaler.bounds.to){
				allTicks.push({value: hScaler.bounds.to});
			}
			var s = this.getGroup();
			for(var j = 0; j < allTicks.length - 1; j++){
				tick = allTicks[j];
				x = offsets.l + ht(tick.value);
				x2 = offsets.l + ht(allTicks[j+1].value);

				fill = (j%2 == 0)?(this.opt.vAlternateFill ||(theme && theme.alternateFill)):
					(this.opt.vFill || (theme && theme.fill));
				if(fill){
					vStripe = this.createRect(s, {
						x: x,
						y: offsets.t,
						width: x2 - x,
						height: dim.width - offsets.r
					}).setFill(fill);
					if(this.animate){
						this._animateGrid(vStripe, "v", dim.height - offsets.b, dim.height - offsets.b - offsets.t);
					}
				}
			}
		},
		_animateGrid: function(shape, type, offset, size){
			var transStart = type == "h" ? [offset, 0] : [0, offset];
			var scaleStart = type == "h" ? [1/size, 1] : [1, 1/size];
			fx.animateTransform(lang.delegate({
				shape: shape,
				duration: 1200,
				transform: [
					{name: "translate", start: transStart, end: [0, 0]},
					{name: "scale", start: scaleStart, end: [1, 1]},
					{name: "original"}
				]
			}, this.animate)).play();
		}
	});
});
