function Point(x,y,label) {
	this.x = x;
	this.y = y;
	this.label = label;
	this.draw = function(ctx,color,radius) {
		ctx.beginPath();
		ctx.fillStyle = color.toString();
		ctx.arc(this.x,this.y,radius,0,Math.PI*2,true);
		ctx.fill();	
	}
	this.toString = function() {
		var cs = "(" + this.x + "," + this.y + ")"
		if (this.label) {
			cs += ' ["' + this.label + '"]';
		}
		return cs;
	}
}

function Line(beginPoint,endPoint) {
	this.beginPoint = beginPoint;
	this.endPoint = endPoint;
	// fillTo = y-coordinate to fill to (typically the location of the x-axis)
	// fillWash = floating point value from 0 to 1; values approaching 1 will cause area within fill to appear lighter in color
	this.draw = function(ctx,color,fillTo,fillWash) {
		if (fillTo) {
			ctx.beginPath();
			ctx.fillStyle = color.toString();
			ctx.moveTo(this.beginPoint.x,this.beginPoint.y);
			ctx.lineTo(this.endPoint.x,this.endPoint.y);
			ctx.lineTo(this.endPoint.x,fillTo);
			ctx.lineTo(this.beginPoint.x,fillTo);
			ctx.fill();
			ctx.beginPath();
			//alert(color.toString(120));
			var fw = fillWash? fillWash : 0.75;		
			ctx.fillStyle = Color.WHITE.toString(fw);
			ctx.moveTo(this.beginPoint.x,this.beginPoint.y);
			ctx.lineTo(this.endPoint.x,this.endPoint.y);
			ctx.lineTo(this.endPoint.x,fillTo);
			ctx.lineTo(this.beginPoint.x,fillTo);
			ctx.fill();
		}
		//alert("drawing line from " + this.beginPoint.toString() + " to " + this.endPoint.toString() + " in color " + color.toString());
		ctx.beginPath();
		ctx.strokeStyle = color.toString();
		ctx.moveTo(this.beginPoint.x,this.beginPoint.y);
		ctx.lineTo(this.endPoint.x,this.endPoint.y);
		ctx.stroke();			
	}
}

function Group(name,color,points) {
	this.name = name;
	this.color = color? color : Color.BLACK;
	this.points = points? points : new Array();
	this.setColor = function(color) {
		this.color = color;
	}
	this.addPoint = function(x,y,label) {
		var point = (x instanceof Point)? x : new Point(x,y,label);
		this.points[this.points.length] = point;
	}
	this.addPoints = function(pointArray) {
		for (var i=0; i<pointArray.length; i++) {
			var pointValues = pointArray[i];
			this.addPoint(pointValues[0],pointValues[1],pointValues[2]);
		}
	}
	this.translate = function(translation) {
		var pts = new Array();
		for (var i=0; i<this.points.length; i++) {
			pts[pts.length] = translation.getPoint(this.points[i]);
		}
		return new Group(this.name,this.color,pts);
	}
}

function Bounds(minX,minY,maxX,maxY) {
	this.minX = minX;
	this.minY = minY;
	this.maxX = maxX;
	this.maxY = maxY;
	this.getWidth = function() {
		return (this.maxX - this.minX);
	}
	this.getHeight = function() {
		return (this.maxY - this.minY);
	}
}

function Translation(logicalBounds,physicalBounds) {
	this.logicalBounds = logicalBounds;
	this.physicalBounds = physicalBounds;
	this.getPoint = function(point) {	// get the physical coordinate for the given logical coordinate
		var px = this.physicalBounds.minX + (point.x - this.logicalBounds.minX) * this.physicalBounds.getWidth() / this.logicalBounds.getWidth();
		var py = this.physicalBounds.maxY - (point.y - this.logicalBounds.minY) * this.physicalBounds.getHeight() / this.logicalBounds.getHeight();
		return new Point(px,py);
	}
	this.getAxisXLine = function() {
		var beginPoint = this.getPoint(new Point(this.logicalBounds.minX,0));
		var endPoint = this.getPoint(new Point(this.logicalBounds.maxX,0));
		return new Line(beginPoint,endPoint);
	}
	this.getAxisYLine = function() {
		var beginPoint = this.getPoint(new Point(0,this.logicalBounds.minY));
		var endPoint = this.getPoint(new Point(0,this.logicalBounds.maxY));
		return new Line(beginPoint,endPoint);
	}
	this.getAxisY = function() {
		return this.getPoint(new Point(0,0)).y;
	}
}

Color.BLUE = new Color(0,0,255);
Color.RED = new Color(255,0,0);
Color.GREEN = new Color(0,255,0);
Color.BLACK = new Color(0,0,0);
Color.WHITE = new Color(255,255,255);
Color.YELLOW = new Color(255,255,0);
Color.PURPLE = new Color(255,0,255);
Color.AQUA = new Color(0,255,255);
Color.GRAY = Color.GREY = new Color(127,127,127);
Color.ORANGE = new Color(255,85,0);
Color.LIGHT_GRAY = Color.LIGHT_GREY = new Color(190,190,190);
function Color(r,g,b) {
	this.r = r;
	this.g = g;
	this.b = b;
	this.cv = new Array();
	// intesity is a multiplier; 1 = no change
	// opacity is floating point number in range 0 to 1
	this.toString = function(opacity,intensity) {
		this.cv[0] = this.r;
		this.cv[1] = this.g;
		this.cv[2] = this.b;
		if (intensity) {
			for (var i=0; i<3; i++) {
				this.cv[i] = Math.min(255,Math.round(this.cv[i] * intensity));
			}
		}
		if (opacity) {
			return "rgba(" + this.cv[0] + "," + this.cv[1] + "," + this.cv[2] + "," + opacity + ")";
		} else {
			return "rgb(" + this.cv[0] + "," + this.cv[1] + "," + this.cv[2] + ")";
		}
	}
}

PointAlign.LEFT_OF_POINT = 1;
PointAlign.CENTER = 2;
PointAlign.RIGHT_OF_POINT = 3;
PointAlign.ABOVE_POINT = 4;
PointAlign.BELOW_POINT = 5;
function PointAlign() {
}

CanvasTool.textElements = new Array();
CanvasTool.drawText = function(canvasId,text,x,y,hAlign,vAlign,textStyle) {
	var canvas = document.getElementById(canvasId);
	x += canvas.offsetLeft;
	y += canvas.offsetTop;
	var canvasParent = canvas.offsetParent;
	var div = window.document.createElement("div");
	div.appendChild(window.document.createTextNode(text));
	var divStyle = "position:absolute; visibility: hidden; left:0px; top:0px;";
	if (textStyle) {
		divStyle += textStyle;
	}
	var isIE = /*@cc_on!@*/false;	//IE detector
	if (isIE) {
		div.style.setAttribute("cssText",divStyle);
	} else {
		div.setAttribute("style",divStyle);
	}
	// div must be placed before offsetWidth and offsetHeight attributes provide proper numbers
	canvasParent.appendChild(div);
	if (hAlign) {
		if (hAlign == PointAlign.CENTER) {
			x -= Math.round(div.offsetWidth / 2);
		} else if (hAlign == PointAlign.LEFT_OF_POINT) {
			x -= div.offsetWidth;
		}
	}
	if (vAlign) {
		if (vAlign == PointAlign.CENTER) {
			y -= Math.round(div.offsetHeight / 2);
		} else if (vAlign == PointAlign.ABOVE_POINT) {
			y -= div.offsetHeight;
		}
	}
	div.style.left = x + "px";
	div.style.top = y + "px";
	div.style.visibility = "visible";
	if (!CanvasTool.textElements[canvasId]) {
		CanvasTool.textElements[canvasId] = new Array();
	}
	CanvasTool.textElements[canvasId][CanvasTool.textElements[canvasId].length] = div;
}
CanvasTool.clearById = function(canvasId,bgColor) {
	var canvas = document.getElementById(canvasId);
	var ctx = canvas.getContext("2d");
	//fill in with white rectangle first in case bgColor has transparency
	ctx.fillStyle = Color.WHITE.toString();
	ctx.fillRect(0,0,canvas.width,canvas.height);
	ctx.fillStyle = bgColor.toString();
	ctx.fillRect(0,0,canvas.width,canvas.height);
	if (CanvasTool.textElements[canvasId]) {
		var canvasParent = canvas.offsetParent;
		for (var i=0; i<CanvasTool.textElements[canvasId].length; i++) {
			var div = CanvasTool.textElements[canvasId][i];
			canvasParent.removeChild(div);
		}
		CanvasTool.textElements[canvasId] = null;
	}
}
function CanvasTool() {
}

function LineGraph(title) {
	this.title = title;
	this.showPointLabels = false;
	this.showHorizontalGridLines = false;
	this.showVerticalGridLines = false;
	this.showPoints = true;
	this.showLines = true;
	this.fillAreaUnderLine = false;
	this.groups = new Array();
	this.addGroup = function(group) {
		this.groups[this.groups.length] = group;
	}
	this.setShowPointLabels = function(showPointLabels) {
		this.showPointLabels = showPointLabels;
	}
	this.setShowGridLines = function(showHorizontalGridLines,showVerticalGridLines) {
		this.showHorizontalGridLines = showHorizontalGridLines;
		this.showVerticalGridLines = showVerticalGridLines;
	}
	this.setShowPoints = function(showPoints) {
		this.showPoints = showPoints;
	}
	this.setShowLines = function(showLines) {
		this.showLines = showLines;
	}
	this.setFillAreaUnderLine = function(fillAreaUnderLine) {
		this.fillAreaUnderLine = fillAreaUnderLine;
	}	
}

function LineGraphRenderer(graph,canvasId) {
	this.padding = 4;
	this.axisLabelPadding = 20;
	this.graph = graph;
	this.canvasId = canvasId;	
	// we dont actually get the canvas and ctx until later; allows renderer to be created earlier
	this.canvas = null;
	this.ctx = null;
	this.bgColor = Color.WHITE;
	this.axisColor = Color.BLACK;
	this.gridColor = Color.LIGHT_GREY;
	this.translation = null;
	this.translatedGroups = null;
	this.initialize = function() {
		this.canvas = document.getElementById(this.canvasId);
		this.ctx = this.canvas.getContext("2d");
		CanvasTool.clearById(this.canvasId,this.bgColor);
		var minX = 0; var minY = 0;
		var maxX = 0; var maxY = 0;
		for (var i=0; i<this.graph.groups.length; i++) {
			var group = this.graph.groups[i];
			if (i == 0) {
				maxX = minX = group.points[0].x;
				maxY = minY = group.points[0].y;
			}
			for (var j=0; j<group.points.length; j++) {
				var p = group.points[j];
				maxX = Math.max(maxX, p.x);
				minX = Math.min(minX, p.x);
				maxY = Math.max(maxY, p.y);
				minY = Math.min(minY, p.y);
			}
		}
		var logicalBounds = new Bounds(minX,minY,maxX,maxY);
		var physicalBounds = new Bounds(this.axisLabelPadding+this.padding,
			this.padding,
			this.canvas.width-this.padding,
			this.canvas.height-this.axisLabelPadding-this.padding);
		this.translation = new Translation(logicalBounds,physicalBounds);
		this.translatedGroups = new Array();
		for (var i=0; i<this.graph.groups.length; i++) {
			this.translatedGroups[this.translatedGroups.length] = this.graph.groups[i].translate(this.translation);
		}
	}
	this.draw = function() {
		this.drawBase();
		this.plotAll();
	}
	this.drawAxis = function() {
		this.translation.getAxisXLine().draw(this.ctx,this.axisColor);
		this.translation.getAxisYLine().draw(this.ctx,this.axisColor);
	}
	this.drawBase = function() {
		this.initialize();	// this clears drawing area and sets up physical data points
		this.drawAxis();	
	}
	this.plotAll = function() {
		for (var i=0; i<this.graph.groups.length; i++) {
			this.plot(i);
		}
	}
	this.plot = function(groupIndex) {
		if (this.canvas == null) {
			this.initialize();
		}
		var group = this.translatedGroups[groupIndex];
		//alert("plot group " + groupIndex + " " + group);
		if (this.graph.showPoints) {
			for (var i=0; i<group.points.length; i++) {
				group.points[i].draw(this.ctx,group.color,3);
			}
		}
		if (this.graph.showLines) {
			for (var i=1; i<group.points.length; i++) {
				var line = new Line(group.points[i],group.points[i-1]);
				if (this.graph.fillAreaUnderLine) {
					line.draw(this.ctx,group.color,this.translation.getAxisY());
				} else {
					line.draw(this.ctx,group.color);
				}
			}
		}
	}
}

function PieGraph(title) {
	this.title = title;
	this.showLabels = false;
	this.showPercentages = true;
	this.groups = new Array();
	this.addPiece = function(label,value,color) {
		var points = new Array();
		points[0] = new Point(value,0,label);
		this.groups[this.groups.length] = new Group(label,color,points);
	}
	this.setShowLabels = function(showLabels) {
		this.showLabels = showLabels;
	}
	this.setShowPercentages = function(showPercentages) {
		this.showPercentages = showPercentages;
	}
}

function PieGraphRenderer(graph,canvasId) {
	this.graph = graph;
	this.canvasId = canvasId;
	this.padding = 5;
	this.bgColor = Color.WHITE;
	this.canvas = null;
	this.ctx = null;
	this.centerPoint = null;
	this.radius = 0;
	this.initialize = function() {
		this.canvas = document.getElementById(this.canvasId);
		this.ctx = this.canvas.getContext("2d");
		this.centerPoint = new Point(this.canvas.width / 2, this.canvas.height / 2);
		this.padding = 5;
		if (this.graph.showPercentages) {
			this.padding += 15;
		}
		if (this.graph.showLabels) {
			this.padding += 20;
		}
		this.radius = Math.min(this.canvas.width, this.canvas.height) / 2 - this.padding;
		var total = 0;
		for (var i=0; i<this.graph.groups.length; i++) {
			total += this.graph.groups[i].points[0].x;
		}
		for (var i=0; i<this.graph.groups.length; i++) {
			this.graph.groups[i].points[0].y = this.graph.groups[i].points[0].x / total;
		}
	}
	this.draw = function() {
		this.initialize();
		CanvasTool.clearById(this.canvasId,this.bgColor);
		var s = 0;
		var t = 0;
		var twoPi = Math.PI * 2;
		var halfPi = Math.PI / 2;
		var thPi = (3 * Math.PI) / 2;
		for (var i=0; i<this.graph.groups.length; i++) {
			var point = this.graph.groups[i].points[0];
			t += twoPi * point.y;
			this.ctx.beginPath();
			this.ctx.fillStyle = this.graph.groups[i].color.toString(0.5);
			//alert("arc: " + this.centerPoint.x + "," + this.centerPoint.y + "; r=" + this.radius + "; from " + s + " to " + t + " (" + Math.round(100*point.y) + "%)");
			this.ctx.arc(this.centerPoint.x,this.centerPoint.y,this.radius,s,t,false);
			this.ctx.lineTo(this.centerPoint.x,this.centerPoint.y);
			this.ctx.fill();
			this.ctx.beginPath();
			this.ctx.strokeStyle = this.graph.groups[i].color.toString();
			//alert("arc: " + this.centerPoint.x + "," + this.centerPoint.y + "; r=" + this.radius + "; from " + s + " to " + t + " (" + Math.round(100*point.y) + "%)");
			this.ctx.arc(this.centerPoint.x,this.centerPoint.y,this.radius,s,t,false);
			this.ctx.lineTo(this.centerPoint.x,this.centerPoint.y);
			this.ctx.closePath();
			this.ctx.stroke();
			if (this.graph.showLabels || this.graph.showPercentages) {
				var u = (s+t)/2;
				var ha = ((u >= 0 && u <= halfPi) || (u > thPi))? PointAlign.RIGHT_OF_POINT : PointAlign.LEFT_OF_POINT;
				var va = (u >= 0 && u <= Math.PI)? PointAlign.BELOW_POINT : PointAlign.ABOVE_POINT;
				var tx = this.centerPoint.x + this.radius * Math.cos(u);
				var ty = this.centerPoint.y + this.radius * Math.sin(u);
				var text = "";
				if (this.graph.showLabels) {
					text += this.graph.groups[i].name;
				}
				if (this.graph.showPercentages) {
					if (this.graph.showLabels) {
						text += " (";
					}
					text += (Math.round(this.graph.groups[i].points[0].y * 1000)/10) + "%";
					if (this.graph.showLabels) {
						text += ")";
					}
				}
				CanvasTool.drawText(this.canvasId,text,tx,ty,ha,va,"font-size: 8pt;");
			}
			s = t;
		}
	}
}

function getSampleLineGraph() {
// use JSON ?
	var graph = new LineGraph("Sample Line Graph");
	var group1 = new Group("Data1",Color.BLUE);
	group1.addPoints([[0,-5],[1,-1.5],[2,0.5],[3,4],[4,5],[5,7.5]]);
	var group2 = new Group("Data2",Color.GREEN);
	group2.addPoints([[0,-1],[1,3],[2,3.5],[3,3.5],[4,4],[5,5.5]]);
	graph.addGroup(group1);
	graph.addGroup(group2);
	return graph;	
}

function getSamplePieGraph() {
	var g = new PieGraph("Sample Pie");
	g.addPiece("Data 1",124,Color.RED);
	g.addPiece("Data 2", 55,Color.GREEN);
	g.addPiece("Data 3",215,Color.BLUE);
	return g;	
}