dojo.provide("dojo.widget.Chart");

dojo.require("dojo.widget.*");
dojo.require("dojo.gfx.color");
dojo.require("dojo.gfx.color.hsl");

dojo.declare(
	"dojo.widget.Chart",
	null,
	function(){
		// summary: Base class for svg and vml implementations of Chart
		this.series = [];
	},
{
	isContainer: false,

	assignColors: function(){
		//	summary
		//	Assigns/generates a color for a data series.
		var hue=30;
		var sat=120;
		var lum=120;
		var steps = Math.round(330/this.series.length);

		for(var i=0; i<this.series.length; i++){
			var c=dojo.gfx.color.hsl2rgb(hue,sat,lum);
			if(!this.series[i].color){
				this.series[i].color = dojo.gfx.color.rgb2hex(c[0],c[1],c[2]);
			}
			hue += steps;
		}
	},
	parseData: function(table){
		var thead=table.getElementsByTagName("thead")[0];
		var tbody=table.getElementsByTagName("tbody")[0];
		if(!(thead&&tbody)) dojo.raise("dojo.widget.Chart: supplied table must define a head and a body.");

		//	set up the series.
		var columns=thead.getElementsByTagName("tr")[0].getElementsByTagName("th");	//	should be <tr><..>
		
		//	assume column 0 == X
		for (var i=1; i<columns.length; i++){
			var key="column"+i;
			var label=columns[i].innerHTML;
			var plotType=columns[i].getAttribute("plotType")||"line";
			var color=columns[i].getAttribute("color");
			var ds=new dojo.widget.Chart.DataSeries(key,label,plotType,color);
			this.series.push(ds);
		}

		//	ok, get the values.
		var rows=tbody.rows;
		var xMin=Number.MAX_VALUE,xMax=Number.MIN_VALUE;
		var yMin=Number.MAX_VALUE,yMax=Number.MIN_VALUE;
		var ignore = [
			"accesskey","align","bgcolor","class",
			"colspan","height","id","nowrap",
			"rowspan","style","tabindex","title",
			"valign","width"
		];

		for(var i=0; i<rows.length; i++){
			var row=rows[i];
			var cells=row.cells;
			var x=Number.MIN_VALUE;
			for (var j=0; j<cells.length; j++){
				if (j==0){
					x=parseFloat(cells[j].innerHTML);
					xMin=Math.min(xMin, x);
					xMax=Math.max(xMax, x);
				} else {
					var ds=this.series[j-1];
					var y=parseFloat(cells[j].innerHTML);
					yMin=Math.min(yMin,y);
					yMax=Math.max(yMax,y);
					var o={x:x, value:y};
					var attrs=cells[j].attributes;
					for(var k=0; k<attrs.length; k++){
						var attr=attrs.item(k);
						var bIgnore=false;
						var lower = attr.nodeName.toLowerCase();
						for (var l=0; l<ignore.length; l++){
							if (lower==ignore[l]){
								bIgnore=true;
								break;
							}
						}
						if(!bIgnore) o[attr.nodeName]=attr.nodeValue;
					}
					ds.add(o);
				}
			}
		}
		return { x:{ min:xMin, max:xMax}, y:{ min:yMin, max:yMax} };
	}
});

dojo.declare(
	"dojo.widget.Chart.DataSeries",
	null,
	function(key, label, plotType, color){
		//	summary:
		//		Every chart has a set of data series; this is the series.  Note that each
		//		member of value is an object and in the minimum has 2 properties: .x and
		//		.value.
		//
		this.id = "DataSeries"+dojo.widget.Chart.DataSeries.count++;
		this.key = key;
		this.label = label||this.id;
		this.plotType = plotType||"line";	//	let line be the default.
		this.color = color;
		this.values = [];
	},
{
	add: function(v){
		if(v.x==null||v.value==null){
			dojo.raise("dojo.widget.Chart.DataSeries.add: v must have both an 'x' and 'value' property.");
		}
		this.values.push(v);
	},

	clear: function(){
		this.values=[];
	},

	createRange: function(len){
		var idx = this.values.length-1;
		var length = (len||this.values.length);
		return { "index": idx, "length": length, "start":Math.max(idx-length,0) };
	},

	//	trend values
	getMean: function(len){
		var range = this.createRange(len);
		if(range.index<0){ return 0; }
		var t = 0;
		var c = 0;
		for(var i=range.index; i>=range.start; i--){
			var n = parseFloat(this.values[i].value);
			if(!isNaN(n)){ t += n; c++; }
		}
		t /= Math.max(c,1);
		return t;
	},

	getMovingAverage: function(len){
		var range = this.createRange(len);
		if(range.index<0){ return 0; }
		var t = 0;
		var c = 0;
		for(var i=range.index; i>=range.start; i--){
			var n = parseFloat(this.values[i].value);
			if(!isNaN(n)){ t += n; c++; }
		}
		t /= Math.max(c,1);
		return t;
	},

	getVariance: function(len){
		var range = this.createRange(len);
		if(range.index < 0){ return 0; }
		var t = 0; // FIXME: for tom: wtf are t, c, and s?
		var s = 0;
		var c = 0;
		for(var i=range.index; i>=range.start; i--){
			var n = parseFloat(this.values[i].value);
			if(!isNaN(n)){
				t += n;
				s += Math.pow(n,2);
				c++;
			}
		}
		return (s/c)-Math.pow(t/c,2);
	},

	getStandardDeviation: function(len){
		return Math.sqrt(this.getVariance(len));
	},

	getMax: function(len){
		var range = this.createRange(len);
		if(range.index < 0){ return 0; }
		var t = 0;
		for (var i=range.index; i>=range.start; i--){
			var n=parseFloat(this.values[i].value);
			if (!isNaN(n)){
				t=Math.max(n,t);
			}
		}
		return t;
	},

	getMin: function(len){
		var range=this.createRange(len);
		if(range.index < 0){ return 0; }
		var t = 0;
		for(var i=range.index; i>=range.start; i--){
			var n = parseFloat(this.values[i].value);
			if(!isNaN(n)){
				t=Math.min(n,t);
			}
		}
		return t;
	},

	getMedian: function(len){
		var range = this.createRange(len);

		if(range.index<0){ return 0; }

		var a = [];
		for (var i=range.index; i>=range.start; i--){
			var n=parseFloat(this.values[i].value);
			if (!isNaN(n)){
				var b=false;
				for(var j=0; j<a.length&&!b; j++){
					if (n==a[j]) b=true; 
				}
				if(!b){ a.push(n); }
			}
		}
		a.sort();
		if(a.length>0){ return a[Math.ceil(a.length/2)]; }
		return 0;
	},

	getMode: function(len){
		var range=this.createRange(len);
		if(range.index<0){ return 0; }
		var o = {};
		var ret = 0
		var m = 0;
		for(var i=range.index; i>=range.start; i--){
			var n=parseFloat(this.values[i].value);
			if(!isNaN(n)){
				if (!o[this.values[i].value]) o[this.values[i].value] = 1;
				else o[this.values[i].value]++;
			}
		}
		for(var p in o){
			if(m<o[p]){ m=o[p]; ret=p; }
		}
		return parseFloat(ret);
	}
});

dojo.requireIf(dojo.render.svg.capable, "dojo.widget.svg.Chart");
dojo.requireIf(!dojo.render.svg.capable && dojo.render.vml.capable, "dojo.widget.vml.Chart");
