/*
	Plotr.Canvas
	============	
	Plotr.Canvas is part of the Plotr Charting Framework.
	
	For license/info/documentation: http://www.solutoire.com/plotr/
	
	Credits
	-------
	Plotr is partially based on PlotKit (BSD license) by
	Alastair Tse <http://www.liquidx.net/plotkit>.
	
	Copyright
	---------
	Copyright 2007 (c) Bas Wenneker <sabmann[a]gmail[d]com>
	For use under the BSD license. <http://www.solutoire.com/plotr>
*/


/**
 * Plotr.Canvas
 * 
 * @alias Plotr.Canvas
 * @namespace Plotr 
 */
Plotr.Canvas = {
	
	/**
	 * Sets options of this chart. Current options are:
	 * - sweetRender: using more advanced renderering techniques the chart will look much sweeter. {Boolean}
	 * - drawBackground: whether a background should be drawn. {Boolean}
	 * - backgroundLineColor: color of backgroundlines. {String}
	 * - backgroundColor: background color. {String}
	 * - padding: padding. {Object}
	 * - colorScheme: Array of colors used for chart. {Array}
	 * - strokeColor: color of a stroke. {String}
	 * - strokeWidth: width of a stroke. {Number}
	 * - shouldFill: whether bars/lines/pies should be filled. {Boolean}
	 * - shouldStroke: whether strokes should be drawn. {Boolean}
	 * - drawXAxis: whether the X axis should be drawn. {Boolean}
	 * - drawYAxis: whether the Y axis should be drawn. {Boolean}
	 * - axisTickSize: size of a tick in pixels. {Number}
	 * - axisLineColor: color of axis lines. {String}
	 * - axisLineWidth: line width of axis. {Number}
	 * - axisLabelColor: axis label color. {String}
	 * - axisLabelFont: font familily used for labels. {String}
	 * - axisLabelFontSize: font size used for labels. {String}
	 * - axisLabelWidth: axis label width. {Number} 
	 * - barWidthFillFraction: sets the bar width (>1 will cause bars to overlap eachother). {Integer} 
	 * - barOrientation: whether bars are horizontal. {'horizontal','vertical'} 
	 * - xOriginIsZero: whether or not the origin of the X axis starts at zero. {Boolean}
	 * - yOriginIsZero: whether or not the origin of the Y axis starts at zero. {Boolean}
	 * - xAxis: values of xAxis {[xmin,xmax]}
	 * - yAxis: values of yAxis {[ymin,ymax]}
	 * - xTicks: labels for the X axis. {[{label: "somelabel", v: value}, ..]} (label = optional)
	 * - yTicks: labels for the Y axis. {[{label: "somelabel", v: value}, ..]} (label = optional)
	 * - xNumberOfTicks: number of ticks on X axis when xTicks is null. {Integer}
	 * - yNumberOfTicks: number of ticks on Y axis when yTicks is null. {Integer}
	 * - xTickPrecision: decimals for the labels on the X axis. {Integer}
	 * - yTickPrecision: decimals for the labels on the Y axis. {Integer}
	 * 
	 * @alias Chart.setOptions
	 * @param {Object} options - Object with options.
	 */
	setOptions: function(options) {
		this.options = Object.extend({
			sweetRender:		true,
			drawBackground: 	true,
			backgroundLineColor:'#ffffff',
			backgroundColor: 	'#f5f5f5',
			padding: 			{left: 30, right: 30, top: 5, bottom: 10},
			colorScheme: 		[ "#468bcc", "#89c83c", "#cf3838" ],
			strokeColor: 		'#ffffff',
			strokeWidth: 		0.5,
			shouldFill: 		true,
			shouldStroke: 		true,
			drawXAxis: 			true,
			drawYAxis: 			true,			
			axisTickSize: 		3,
			axisLineColor: 		'#000000',
			axisLineWidth: 		0.5,
			axisLabelColor: 	'#333333',
			axisLabelShadow: 	'#FFFFFF',
			axisLabelFont: 		'Arial',
			axisLabelFontSize: 	9,
			axisLabelWidth: 	50,
			barWidthFillFraction: 0.75,
			barOrientation: 'vertical',
			xOriginIsZero: true,
			yOriginIsZero: true,
			xAxis: null,
			yAxis: null,
			xTicks: null,
			yTicks: null,
			xNumberOfTicks: 10,
			yNumberOfTicks: 10,
			xTickPrecision: 1,
			yTickPrecision: 1,
			pieRadius: 0.4
		}, options || {});
	},
	
	/**
	 * Resets options and datasets of this chart.
	 * 
	 * @alias reset
	 */
	reset: function() {
		this.setOptions();
		this.dataStores = new Array();
		if(!Plotr.Base.isNil(this.renderDelay)){
			this.renderDelay.stop();
			this.renderDelay = null;
		}
	},
	
	/**
	 * The constructor of Plotr.Canvas. 
	 * 
	 * @alias initialize
	 * @see setOptions
	 * @param {String} element  - Canvas element ID.
	 * @param {Plotr.Chart} chart - Chart object to render.
	 * @param {Object} options - Options.
	 */
	_initCanvas: function(element) {
		this.canvasNode = $(element);
		this.containerNode = this.canvasNode.getParent().setStyles({position: 'relative',width: this.canvasNode.width + 'px'});
		this.isIE = Plotr.Base.excanvasSupported();
		
		if (this.isIE && !Plotr.Base.isNil(G_vmlCanvasManager)) {
			this.IEDelay = 0.5;
			this.maxTries = 10;
			this.renderDelay = null;
			this.renderStack = new Array();
			this.canvasNode = G_vmlCanvasManager.initElement(this.canvasNode);			
		}
		
		if(Plotr.Base.isNil(this.canvasNode))
			throw 'Plotr.Canvas(): Could\'nt find canvas.';	
		if(Plotr.Base.isNil(this.containerNode) || this.containerNode.nodeName.toLowerCase() != 'div')
			throw 'Plotr.Canvas(): Canvas element is not enclosed by a <div> element.';	
		if (!this.isIE && !(Plotr.Base.isSupported(element)))
			throw "Plotr.Canvas(): Canvas is not supported.";
		
		this.xlabels = new Array();
		this.ylabels = new Array();
		
		this.area = {
			x: this.options.padding.left,
			y: this.options.padding.top,
			w: this.canvasNode.width - this.options.padding.left - this.options.padding.right,
			h: this.canvasNode.height - this.options.padding.top - this.options.padding.bottom
		};
	},
	
	/**
	 * This function renders the background in the canvas element.
	 * 
	 * @alias render
	 * @param {String} [element] - (optional) ID of the canvas element to render in.
	 */
	_render: function(element) {
		if(!Plotr.Base.isNil(element)) this._initCanvas(element);
	
		if (this.options.drawBackground) {
			this._renderBackground();
		}
	},
	
	_ieWaitForVML: function(element, options){
		var isNil = Plotr.Base.isNil;
		
		if(!isNil(element)) {
			this.renderStack[element] = options;
		}
		
		try{
			if(!isNil(this.canvasNode)) {	
				this.canvasNode.getContext("2d");					
			} else {
				$(element).getContext("2d");
			}
		} catch(e) {
			if(isNil(this.renderDelay)){
				this.renderDelay = new PeriodicalExecuter(function(pe){
					if(!isNil(this.canvasNode)) {	
						this.render(this.canvasNode,options);					
					} else {
						this.render(element,options);
					}					
				}.bind(this), this.IEDelay);
			}else if(this.maxTries-- <= 0) {
				this.renderDelay.stop();
			}
			return true;
		}
		if(!isNil(this.renderDelay)){
			this.renderDelay.stop();
			delete this.renderStack[element || this.canvasNode];
		}
		
		return false;
	},
	
	/**
	 * Sets the colorScheme used for the chart.
	 * 
	 * @alias setColorScheme 
	 */
	setColorscheme: function() {
		var scheme = this.options.colorScheme;
		
		if(scheme instanceof Array) {
			return;
		} else if(typeof(scheme) == 'string') {
			if(this.type == 'pie'){
				this.options.colorScheme = Plotr.Base.getColorscheme(scheme, Math.max(this.stores[0].length,3));
			}else{
				this.options.colorScheme = Plotr.Base.getColorscheme(scheme, Math.max(this.sets,3));
			}
		} else { 
			throw 'Plotr.Canvas.setColorscheme(): colorScheme is invalid!';
		}
	},
	
	/**
	 * Renders the background of the chart.
	 * 
	 * @alias _renderBackground
	 */
	_renderBackground: function() {
		var cx = this.canvasNode.getContext('2d');
		cx.save();
		cx.fillStyle = this.options.backgroundColor;
			
		if(this.options.sweetRender) {
			cx.fillRect(this.area.x, this.area.y, this.area.w, this.area.h);
			cx.strokeStyle = this.options.backgroundLineColor;
			cx.lineWidth = 1.0;
			
			var ticks = this.yticks;
			var horiz = false;
			if (this.type == 'bar' && this.options.barOrientation == 'horizontal') {
				ticks = this.xticks;
				horiz = true;
			}
			
			var drawBackgroundLines = function(tick) {
				var x1 = x2 = y1 = y2 = 0;
				
				if(horiz) {
					x1 = x2 = tick[0] * this.area.w + this.area.x;
					y1 = this.area.y;
					y2 = y1 + this.area.h;
				}else{
					x1 = this.area.x;
					y1 = tick[0] * this.area.h + this.area.y;
					x2 = x1 + this.area.w;
					y2 = y1;
				}
				
				cx.beginPath();
				cx.moveTo(x1, y1);
				cx.lineTo(x2, y2);
				cx.closePath();
				cx.stroke();
			}.bind(this);			
			ticks.each(drawBackgroundLines);
		} else {
			cx.fillRect(0, 0, this.canvasNode.width, this.canvasNode.height);
		}
		cx.restore();
	},
	
	/**
	 * Renders the axis for line charts.
	 * 
	 * @alias _renderLineAxis
	 */
	_renderLineAxis: function() {
		this._renderAxis();
	},
	
	/**
	 * Renders axis.
	 * 
	 * @alias _renderAxis 
	 */
	_renderAxis: function() {
		if (!this.options.drawXAxis && !this.options.drawYAxis)
			return;
	
		var cx = this.canvasNode.getContext('2d');
	
		var labelStyle = {
			position: 'absolute',
			fontSize: this.options.axisLabelFontSize + 'px',			
			fontFamily: this.options.axisLabelFont,
			zIndex: 10,
			color: this.options.axisLabelColor,
			width: this.options.axisLabelWidth + 'px',
			overflow: 'hidden'
		};
		
		cx.save();
		cx.strokeStyle = this.options.axisLineColor;
		cx.lineWidth = this.options.axisLineWidth;
		
		if (this.options.drawYAxis) {
			if (this.yticks) {
				var collectYLabels = function(tick) {
					if(typeof(tick) == 'function') return;
					
					var x = this.area.x;
					var y = this.area.y + tick[0] * this.area.h;
					
					cx.beginPath();
					cx.moveTo(x, y);
					cx.lineTo(x - this.options.axisTickSize, y);
					cx.closePath();
					cx.stroke();
					
					return new Element( 'div' )
						.appendText( tick[1] )
						.setStyles( 
							$extend( labelStyle, {
								top: (y - this.options.axisLabelFontSize) + 'px',
								left: (x - this.options.padding.left - this.options.axisTickSize) + 'px',
								width: (this.options.padding.left - this.options.axisTickSize * 2) + 'px',
								textAlign: 'right'
							} )
						)
						.inject( this.containerNode );
						
				}.bind(this);
				this.ylabels = this.yticks.collect(collectYLabels);
			}
			
			cx.beginPath();
			cx.moveTo(this.area.x, this.area.y);
			cx.lineTo(this.area.x, this.area.y + this.area.h);
			cx.closePath();
			cx.stroke();
		}
		
		if(this.options.drawXAxis) {
			if(this.xticks) {
				var collectXLabels = function(tick) {
					if (typeof(tick) == 'function') return;
					
					var x = this.area.x + tick[0] * this.area.w;
					var y = this.area.y + this.area.h;
					
					cx.beginPath();
					cx.moveTo(x, y);
					cx.lineTo(x, y + this.options.axisTickSize);
					cx.closePath();
					cx.stroke();
					
					return new Element( 'div' )
						.appendText( tick[1] )
						.setStyles( 
							$extend( labelStyle, {
								top: (y + this.options.axisTickSize) + 'px',
								left: (x - this.options.axisLabelWidth/2) + 'px',
								width: (this.options.axisLabelWidth+5) + 'px',
								textAlign: 'center'
							} )
						)
						.inject( this.containerNode );
						
				}.bind(this);
				this.xlabels = this.xticks.collect(collectXLabels);
			}
			
			cx.beginPath();
			cx.moveTo(this.area.x, this.area.y + this.area.h);
			cx.lineTo(this.area.x + this.area.w, this.area.y + this.area.h);
			cx.closePath();
			cx.stroke();
		}		
		cx.restore();
	}
};
