/**
 * class StatsChart
 *
 * This is a renderer for Statistics data. This version is modified for use with
 * stock data, so some of the methods make assumptions about the data which
 * should not be considered valid for all data sets. In the future I'll move
 * those methods out and reduce dependencies, then open the license back up.
 *
 * requires helpers.js, class_Stats.php
 * @author Justen Robertson <justen@justenrobertson.com>
 * @link http://www.justenrobertson.com
 * @version 0.5a 6/8/2009
 * @license Proprietary (all rights reserved)
 * @copyright 2009 Justen Robertson
 * @requires Raphaeljs, class_Stats.js, jquery-1.3.2, jquery-ui, jquery-qtip
 */

var StatsChartQueue = [];
function StatsChartProc() {
		var cmd = StatsChartQueue.shift();
		if(cmd) cmd.execute();
}

setInterval(function() {StatsChartProc()}, 500); // interval should be higher than StatsChart default speed

function StatsChart(paper) {
	this.paper = paper;
	this.xMargin = 40;
	this.yMargin = 0;
	this.scaleRange = 10;
	this.speed = 250;
	this.lines = [];
	this.points = [];
	this.gridLines = [];
	this.markers = [];
	this.markerBlocks = [];
	this.proccing = 0;
	this.xfac = new Statistic(); // statistic to plot Y on
	this.yfac = new Statistic(); // statistic to plot X on
	this.sfac = new Statistic(); // statistic to scale data points to
	this.zfac = new Statistic(); // unused
	this.color = rndColor();
	



	this.setData = function(opts) {
		if (opts.speed) {
			this.buildDataPoints();
			this.speed = opts.speed;
		}
		if (opts.xfac) {
			this.xfac = opts.xfac;
			this.buildDataPoints();
			this.queue({chart:this, execute:function(){this.chart.animateX();}});
		}
		if (opts.yfac) {
			this.yfac = opts.yfac;
			this.buildDataPoints();
			this.queue({
					chart:this,
					execute:function(){this.chart.setMarkers();}
				}).queue({
					chart:this,
					execute:function(){this.chart.animateY();}
				}).queue({
					chart:this,
					execute:function(){this.chart.setMoveIndicators();}
				})
		}
		if (opts.sfac) {
			this.sfac = opts.sfac;
			this.buildDataPoints();
			this.queue({chart:this, execute:function(){this.chart.animateScale();}});
		}
		if (opts.zfac) {
			this.zfac = opts.zfac;
			this.buildDataPoints();
		}
		if (opts.color) {
			this.setColor(opts.color);
		}
		if(this.yfac.count() && this.sfac.count()) {
			this.setTooltips();
		}
		return this;
	}




	this.queue = function(cmd) {
		StatsChartQueue.push(cmd);
		return this;
	}




	this.toggleLines = function(line, state) {
		if(!line) {
			for (i=0;i<this.lines.count();i++) {
				if(state !== undefined) this.lines[i].attr({opacity:state});
				if(this.lines[i].attr) this.lines[i].attr('opacity')===0?this.lines[i].attr({opacity:1}):this.lines[i].attr({opacity:0});
			}
		}
		else {
			if(this.lines[line]) var l = this.lines[line];
			else return this;
			if(state !== undefined) l.attr({opacity:state});
			else l.attr('opacity')===0?l.attr({opacity:1}):l.attr({opacity:0});
		}
		return this;
	};



	this.toFront = function() {
		this.markerBlocks.each(function(p) {
			p.toFront();
		});

		this.markers.each(function(p) {
			p.toFront();
		});
		
		this.lines.each(function(p) {
			p.toFront();
		});

		this.points.each(function(p) {
			p.toFront();
		});
		return this;
	};




	this.setColor = function(color) {
		this.color = color;
		var me = this;
		var i;
		for (i in this.lines) {
			if(this.lines[i].attr) this.lines[i].attr({'stroke':me.color});
		}

		for(i in this.points) {
			if(this.points[i].attr) this.points[i].attr({'stroke':me.color});
		}

		for(i in this.markers) {
			if(this.markers[i].attr) this.markers[i].attr({'fill':me.color});
		}
		return this;
	};




	/**
	 * setMarkers sets the grid lines and markers for the graph
	 */
	this.setMarkers = function() {
		if(!this.xfac.count()) return this;
		var xO = this.xMargin; // how much space the markers have
		var item;
		
		// clean up old markers, if there are any
		while ((item = this.markerBlocks.pop())) {
			if(item.remove) item.remove();
		}

		while ((item = this.markers.pop())) {
			if(item.remove) item.remove();
		}

		this.markerBlocks.push(this.paper.rect(this.paper.width-xO, (this.paper.height)-20,			xO, 16, 3).attr({fill:'#000'}));
		this.markerBlocks.push(this.paper.rect(this.paper.width-xO, (this.paper.height*.25)-6,	xO, 16, 3).attr({fill:'#000'}));
		this.markerBlocks.push(this.paper.rect(this.paper.width-xO, (this.paper.height*.5)-6,   xO, 16, 3).attr({fill:'#000'}));
		this.markerBlocks.push(this.paper.rect(this.paper.width-xO, (this.paper.height*.75)-6 , xO, 16, 3).attr({fill:'#000'}));
		this.markerBlocks.push(this.paper.rect(this.paper.width-xO, 0,                          xO, 16, 3).attr({fill:'#000'}));


		var samples = this.yfac.samples(5);

		this.markers.push(this.paper.text(this.paper.width-(xO/2), this.paper.height-8,   toCurrency(samples[0])).attr({fill:this.color}));
		this.markers.push(this.paper.text(this.paper.width-(xO/2), this.paper.height*.75, toCurrency(samples[1])).attr({fill:this.color}));
		this.markers.push(this.paper.text(this.paper.width-(xO/2), this.paper.height*.5,  toCurrency(samples[2])).attr({fill:this.color}));
		this.markers.push(this.paper.text(this.paper.width-(xO/2), this.paper.height*.25, toCurrency(samples[3])).attr({fill:this.color}));
		this.markers.push(this.paper.text(this.paper.width-(xO/2), 8,                     toCurrency(samples[4])).attr({fill:this.color}));
		return this;
	}




	/**
	 * buildDataPoints sets up (count) data points and creates shapes for them
	 */
	this.buildDataPoints = function() {
		var count = Math.max(this.xfac.count(), this.yfac.count(), this.zfac.count(), this.sfac.count());
		if(this.points.count()) {
			while(this.points.count() < count) {
				this.points.push(
							this.paper.circle(this.paper.width/2, 0, 1).attr({
					'stroke-width':1, //,
					stroke:this.color,
					fill:this.color
				})
			);
			}
			return this;
		}
		if(this.points.length) { // clean up old points
			this.points.each(function(p) {
				p.remove();
			});
		}
		this.points = [];
		var i = 0;
		while(i < count) {
			this.points[i] =
				this.paper.circle(this.paper.width/2, 5, 1).attr({
					'stroke-width':1, //,
					stroke:this.color,
					fill:this.color
				});
			i++;
		}
		return this;
	};




	/**
	 * plotHorizontal connects data points with horizontal line
	 */
	this.plotHorizontal = function() {
		var line = (this.paper.path({stroke:this.color, opacity:0}));
		line.moveTo(this.getPointXPos(0), this.getPointYPos(0));
		var count = this.points.count();
		var i = 1;
		while (i < count) {
			line.lineTo(this.getPointXPos(i), this.getPointYPos(i));
			i++;
		}
		this.lines['horizontal'] = line;
		return this;
	}




	/**
	 * vWAP draws volume-weighted average price
	 */
	this.plotVWAP = function(vol, price) {
		var line = (this.paper.path({stroke:this.color, opacity:0}));
		line.moveTo(this.getPointXPos(0), this.getPointYPos(0));
		var scale = price.range()/(this.paper.height-this.yMargin);
		var vwap = this.yfac.linearWeightedAverage(this.sfac);
		var y;
		for (var i=0;i<this.points.count();i++) {
			y = (((this.yfac.largest()-vwap.stats[i])/scale));
			line.lineTo(this.getPointXPos(i), y);
		}
		this.lines['vWAP'] = line;
		return this;
	}




	/**
	 * getPointXPos calculates the x position of data point i
	 */
	this.getPointXPos = function(i) {
		var xfacScale = this.xfac.range()/(this.paper.width-this.xMargin);
		var x = (((this.xfac.stats[i]-this.xfac.smallest())/xfacScale));
		return x;
	}




	/**
	 * getPointYPos calculates the y position of data point i
	 */
	this.getPointYPos = function(i) {
		var yfacScale = this.yfac.range()/(this.paper.height-this.yMargin);
		var y = (((this.yfac.largest()-this.yfac.stats[i])/yfacScale));
		return y;
	}




	/**
	 * getPointScale calculates the scaling value for data point i
	 */
	this.getPointScale = function(i) {
		var scale = 0;
		if(this.sfac === undefined || this.sfac.stats[i] === undefined) scale = 1;
		else scale = (this.scaleRange/this.sfac.range())*this.sfac.stats[i];
		scale = Math.max(1, scale);
		if (typeof(scale) !== "number") throw("getPointScale got non-number");
		return scale;
	}



	/**
	 * animateScale sets the point scaling on data points in speed milliseconds
	 */
	this.animateScale = function() {
		var count = this.points.count();
		var i = 0;
		while (i < count) {
			this.points[i].animate({r:this.getPointScale(i), 'stroke-width':this.getPointScale(i)}, this.speed);
			i++;
		}
		return this;
	}




	/**
	 * animateX animates points to their calculated x position in speed milliseconds
	 */
	this.animateX = function() {
		var count = this.points.count();
		var i = 0;
		while (i < count) {
			this.points[i].animate({cx:this.getPointXPos(i)}, this.speed);
			i++;
		}
		return this;
	}




	/**
	 * animateY animates points to their calculated y position in speed milliseconds
	 */
	this.animateY = function() {
		var count = this.points.count();
		var i = 0;
		while (i < count) {
			this.points[i].animate({cy:this.getPointYPos(i)}, this.speed);
			i++;
		}
		return this;
	}




	/**
	 * setMoveIndicators sets the fill color of each point to red or green
	 * based on difference from last indicator
	 */
	this.setMoveIndicators = function(state) {
		if(state === undefined) state = 1;
		var count = this.points.count();
		var i = 1;
		while (i < count) {
			if (state == 0) fill = this.color;
			else var fill = ((this.yfac.stats[i] > this.yfac.stats[i-1])?"#ffffff":"#000000"); // quick hack for motion indication
			this.points[i].attr({fill:fill});
			i++;
		}
		return this;
	}




	/**
	 * animateUpdate updates all points on all factors
	 */
	this.animateUpdate = function() {
		var count = this.points.count();
		var i = 0;
		var frac = this.speed/count;
		var fracInc = frac;
		var me = this;
		while (i < count) {
			me.points[i].animate({
				cy:me.getPointYPos(i),
				cx:me.getPointXPos(i),
				r:me.getPointScale(i)*3+2,
				'stroke-width':me.getPointScale(i)*5+1
			}, fracInc);
			fracInc += frac;
			i++;
		}
		return this;
	}




	/**
	 * setTooltips sets up tooltips for each data point
	 */
	this.setTooltips = function() {
		var count = this.points.count();
		var i = 0;
		var v = 'unavailable';
		while (i < count) {
			if(this.sfac.stats[i]) v = fNum(this.sfac.stats[i]);
			else v = false;
			this.points[i].attr({title:toCurrency(this.yfac.stats[i])+((v)?" V: "+v:'')});
			i++;
		}
		$('circle[title]').qtip({
			content:this.title,
			show:'mouseover',
			hide:'mouseout',
			style: { name: 'dark', tip: true },
			position: { target: 'mouse', adjust: { screen: true } },
			effect: {type:'fade'}
		});
		return this;
	}
}