
function ColorCycler(){
	return {
		rgb:[0,122,122]
		,next:function(){
			this.rgb[2]+=20000;
			if(this.rgb[2]>255){
				var d=Math.floor(this.rgb[2]/255);
	    		this.rgb[1]+=d;
	    		this.rgb[2]=this.rgb[2]%255;
				if (this.rgb[1] > 255) {
					var d=Math.floor(this.rgb[1]/255);
					this.rgb[0] += d;
					this.rgb[1] = this.rgb[1]%255;
				}		
			}
			
		}
		,getRGBString:function(){
			return 'rgb('+this.rgb[0]+','+this.rgb[1]+','+this.rgb[2]+')';
		}
	}
}	

function Graph(canvas,parent){
	return new GraphClass(canvas,parent);
}
function GraphClass(canvas,parent){
	this.canvas=canvas;
	this.parent=parent;
	this.colors_cycler=ColorCycler();
	this.plots=[];
	this.title="";
	this.ylabel="";
	this.xlabel="";
	DomNodeInterface(this);
	
	//Legend box info.
	this.legend = {
		//The font used.
		font: {
			size: '12px',
			name: 'verdana'
		}
		//The width of the box.
		,width:0
		//The height of the box.
		,height:0
	  	,paddingLeft:3
		,paddingLeftRight:3
		,paddingTop:3
		,paddingBottom:3	
	}

	this.graphbox={};
	this.selectedX=null;
	this.graphImage=null;
	//dictates whether the coordinate grid is shown.
	this.showGrid=false;
	
	//dictates the number of y and x indicators to make
	this.makeYindicators=5;
	this.makeXindicators=5;

	this.view={x:{min:0,max:0},y:{min:0,max:0}};
	
	//holds the location and dimension of the legendBox.
	this._legendBox=null
}

GraphClass.prototype.Plot = function(){
	this.colors_cycler.next();	
	return {values:[],title:'',color:this.colors_cycler.getRGBString(),legend:null};		
}

GraphClass.prototype.addPlot = function(plot){
	this.canvas.oFont(this.legend.font);		
	var box=this.canvas.textBox(plot.title);
	this.legend.width=Math.max(box.width,this.legend.width);
	this.legend.height+=box.height+2;
	plot.legend=box;
	plot.show=true;
	this.plots.push(plot);	
}
GraphClass.prototype.drawYindicators = function(x, y,h){
	var value_indicators=[];
	var indicator_max_width=0;
	for (var i = this.view.y.min; i <= this.view.y.max; i += (this.view.y.max-this.view.y.min) /this.makeYindicators) {
		var box = this.canvas.textBox(i.toPrecision(4));		
		value_indicators.push({
			box: box,
			value: i.toPrecision(4)
		});
		indicator_max_width=Math.max(indicator_max_width,box.width);
	}

	//Draw the value indicators.
	var topIndicatorY=y+value_indicators[value_indicators.length-1].box.height/2;
	var bottomIndicatorY=y+h-value_indicators[0].box.height/2;	
	var indicatorDist=(bottomIndicatorY-topIndicatorY)/(value_indicators.length-1);
	for (var i  in value_indicators) {
		var y=bottomIndicatorY-i*indicatorDist;
		var indicator=value_indicators[i];
		var state=this.canvas.save();
		this.canvas.context.textAlign='left';
		this.canvas.oText({
			style:'fill',
			rgb: 'rgb(0,0,0)',
			text: indicator.value,
			x:x+indicator_max_width-indicator.box.width,
			y:y-indicator.box.height/2
		});	
		this.canvas.restore(state);
		if (this.showGrid&&i!=0) {
			var state=this.canvas.save();
			this.canvas.strokeStyle("rgba(200,200,200,0.3)");
			this.canvas.beginPath();
			this.canvas.moveTo(x + indicator_max_width + 6, y);
			this.canvas.lineTo(this.width-this.legendBox.width-6, y);
			this.canvas.stroke();
			this.canvas.restore(state);			
		}		
		this.canvas.beginPath();	
		this.canvas.moveTo(x + indicator_max_width + 2, y);
		this.canvas.lineTo(x + indicator_max_width + 6, y);

		this.canvas.stroke();			
	}	
	return {
		width: indicator_max_width+6,
		innerYTop:  topIndicatorY,
		innerYBottom: bottomIndicatorY,
		innerHeight:bottomIndicatorY-topIndicatorY
	};
}

GraphClass.prototype.drawXindicators = function(x, y,w){
	var value_indicators=[];
	var indicator_max_width=0;
	for (var i = this.view.x.min; i <= this.view.x.max; i += (this.view.x.max-this.view.x.min) /this.makeXindicators) {
		var box = this.canvas.textBox(i.toPrecision(4));		
		value_indicators.push({
			box: box,
			value: i.toPrecision(4)
		});
	}

	//Draw the value indicators.
	var leftIndicatorX=x;
	var rightIndicatorX=x+w;	
	var indicatorDist=(rightIndicatorX-leftIndicatorX)/(value_indicators.length-1);
	for (var i  in value_indicators) {
		var x=leftIndicatorX+i*indicatorDist;
		var indicator=value_indicators[i];

		this.canvas.oText({
			style:'fill',
			baseline:'top',
			rgb: 'rgb(0,0,0)',
			text: indicator.value,
			x:x-indicator.box.width/2,
			y:y+3
		});		
		if (this.showGrid&&i!=0) {
			var state=this.canvas.save();
			this.canvas.strokeStyle("rgba(200,200,200,0.3)");
			this.canvas.beginPath();
			this.canvas.moveTo(x, this.graphbox.y);
			this.canvas.lineTo(x, y);	
			this.canvas.stroke();
			this.canvas.restore(state);			
		}		
		this.canvas.beginPath();
		this.canvas.moveTo(x, y);
		this.canvas.lineTo(x, y + 3);
		this.canvas.stroke();			
	}	
}

GraphClass.prototype.showHidePlot = function(e, index){
	this.plots[index].show=!this.plots[index].show
	this.clear();
	this.draw();
}

GraphClass.prototype.addCheckBoxListener = function(x,y,w,h,i){
	var me=this;
	this.canvas.listen_click(
		x,
		y,
		w,
		h,
		function(e){me.showHidePlot(e,i)}
	);
}
/*!
 * Draws the legend box.
 */
GraphClass.prototype.drawLegendBox = function(x, y){
	var me=this;
	//Draw Legend Box
	this.legendBox={
		x:x,
	  	y:y,
	  	width:this.legend.width+this.legend.paddingLeft+this.legend.paddingLeftRight+16,
	  	height:this.legend.height+this.legend.paddingTop+this.legend.paddingBottom		
	};
	this.canvas.strokeStyle('rgb(0,0,0)');
	this.canvas.strokeRect(
		this.legendBox.x,
		this.legendBox.y,
		this.legendBox.width,
		this.legendBox.height						
	);	  

	this.canvas.oFont(this.legend.font);		
	var legendTextX=x+this.legend.paddingLeft;
	var legendTextY=y+this.legend.paddingTop;	
	for(var index in this.plots){
		//Draw Legend Texts.
		this.canvas.strokeRect(legendTextX,legendTextY,14,14);
		if(this.plots[index].show){
			this.canvas.oFillCircle({rgb:"#00ff00",x:legendTextX+7,y:legendTextY+7,radius:5});
		}
		if(this.graphImage==null){
			this.addCheckBoxListener (
				legendTextX,
				legendTextY,
				14,
				14,
				index);
 		}
		
		this.canvas.fillStyle(this.plots[index].color);
		var state=this.canvas.save();
		this.canvas.textBaseline('top');
		this.canvas.fillText(this.plots[index].title,legendTextX+16,legendTextY);
		this.canvas.restore(state);
		legendTextY+=this.plots[index].legend.height+2;
	}				
}


GraphClass.prototype.drawSelectArea=function(e){
	if (this.graphImage == false) {
		this.clear();
		this.draw();
	} else {
		this.canvas.putImageData(this.graphImage, this.graphbox.x, this.graphbox.y);
	}
	if(this.selectedX==null){
		this.canvas.strokeStyle("#00ff00");
		this.canvas.beginPath();
		this.canvas.moveTo(e.x,this.graphbox.y+1);
		this.canvas.lineTo(e.x,this.graphbox.y+this.graphbox.h-2);
		this.canvas.stroke();	
	}else{
		this.canvas.fillStyle("rgba(0,255,0,0.2)");
		this.canvas.fillRect(Math.min(this.selectedX,e.x),this.graphbox.y+1,Math.abs(this.selectedX-e.x),this.graphbox.h-2)
	}
	
	var v=this.getXCoordinateToValue((e.x-this.graphbox.x)/this.graphbox.w);
	var box=this.canvas.textBox('x:'+v.toPrecision(4))
	var values=[['x:'+v.toPrecision(4),"#000000",box]];
	var lx=box.width;
	for(var i=0;i<this.plots.length;i++){
		if(this.plots[i].show==false){
			continue;
		}					
		for(var p=0;p<this.plots[i].values.length-1;p++){
			if(this.plots[i].values[p][0]==v){
				var box=this.canvas.textBox(this.plots[i].values[p][1].toPrecision(4));
				values.push([this.plots[i].values[p][1].toPrecision(4),this.plots[i].color,box]);
				lx=Math.max(box.width,lx);
				break;
			}else if(this.plots[i].values[p][0]<v&&this.plots[i].values[p+1][0]>v){
				var dy=(this.plots[i].values[p+1][1]-this.plots[i].values[p][1])/(this.plots[i].values[p+1][0]-this.plots[i].values[p][0]);
				var y=dy*(v-this.plots[i].values[p][0])+this.plots[i].values[p][1];
				var box=this.canvas.textBox(y.toPrecision(4));
				values.push([y.toPrecision(4),this.plots[i].color,box]);
				lx=Math.max(box.width,lx);					
				break;					
			}else if(this.plots[i].values[p][0]>v){
				break;					
			}
		}
	}
	var y=this.graphbox.y+2;
	if(lx+e.x+2>this.graphbox.x+this.graphbox.w){
		var dx=e.x-2-lx;
	}else{
		var dx=e.x+2;
	}
	for(var index in values){
		this.canvas.fillStyle(values[index][1]);
		this.canvas.fillText(values[index][0],dx,y);
		y+=2+values[index][2].height;
	}		
}


GraphClass.prototype.selectArea=function(e){
	if(this.selectedX==null){
		this.selectedX=e.x;		
	}else{
		var lx,hx;
		if(this.selectedX<e.x){
			lx=this.selectedX;
			hx=e.x;
		}else{
			hx=this.selectedX;
			lx=e.x;
		}
		lx=(lx-this.graphbox.x)/this.graphbox.w;
		hx=(hx-this.graphbox.x)/this.graphbox.w;

		var dx=this.view.x.max-this.view.x.min;
		this.view.x.max=dx*hx+this.view.x.min;
		this.view.x.min=dx*lx+this.view.x.min;
		this.selectedX=null;
		this.clear();
		this.draw();
	}
}


GraphClass.prototype.init=function(){
	this.resetView();	
}

GraphClass.prototype.clear=function(){
	this.canvas.clearRect(this.offsetAbsoluteLeft(),this.offsetAbsoluteTop(),this.width,this.height);
}

GraphClass.prototype.resetView=function(){
	var xmin=this.plots[0].values[0][0];
	var xmax=this.plots[0].values[this.plots[0].values.length-1][0];
	var ymin=0;
	var ymax=this.plots[0].values[0][1];
	for (var p = 1; p < this.plots[0].values.length; p++) {
		ymin=Math.min(this.plots[0].values[p][1],ymin);
		ymax=Math.max(this.plots[0].values[p][1],ymax);				
	}
	for(var i=1;i<this.plots.length;i++){
		xmin=Math.min(this.plots[i].values[0][0],xmin);
		xmax=Math.max(this.plots[i].values[this.plots[i].values.length-1][0],xmax);
		
		for (var p = 0; p < this.plots[i].values.length; p++) {
			ymin=Math.min(this.plots[i].values[p][1],ymin);
			ymax=Math.max(this.plots[i].values[p][1],ymax);				
		}						
	}
	ymax+=(ymax-ymin)/10;	
	this.view={x:{min:xmin,max:xmax},y:{min:ymin,max:ymax}};	
	this.clear();
	this.draw();
}

GraphClass.prototype.draw=function(){
	var me=this;
	var x=this.offsetAbsoluteLeft();
	var y=this.offsetAbsoluteTop();
	this.canvas.strokeStyle('rgb(0,0,0)');
	this.canvas.strokeRect(x,y,this.width,this.height);
	this.canvas.oFont(this.legend.font);
	var title_box_height=0;
	if (this.title != "") {
		var box = this.canvas.textBox(this.title);
		title_box_height=box.height;
		this.canvas.context.textBaseline="top"
		this.canvas.fillStyle('rgb(0,0,0)');
		this.canvas.fillText(this.title,x+(this.width-box.width)/2,y);
	}

	var xtitle_box_height=0;
	if (this.xlabel != "") {
		var box = this.canvas.textBox(this.xlabel);
		xtitle_box_height=box.height;
		this.canvas.context.textBaseline="top"
		this.canvas.fillStyle('rgb(0,0,0)');
		this.canvas.fillText(this.xlabel,x+(this.width-box.width)/2,y+this.height-box.height);
	}

	this.drawLegendBox(x+this.width-(this.legend.width+9+16),y+3+title_box_height);
	
	var box = this.canvas.textBox("Reset view");
	this.canvas.context.textBaseline="top"
	this.canvas.fillStyle('rgb(0,0,0)');
	this.canvas.fillText("Reset view",this.legendBox.x+(this.legendBox.width-box.width)/2,this.legendBox.y+this.legendBox.height+2);
	if (this.graphImage == null) {
		this.canvas.listen_click(this.legendBox.x+(this.legendBox.width-box.width)/2,this.legendBox.y+this.legendBox.height+2, box.width,box.height,function(e){
			me.resetView()
		});
	}

	ytitle_box_width=0;
	if (this.ylabel != "") {
		var box = this.canvas.textBox(this.ylabel);
		ytitle_box_width=box.height;
		var state=this.canvas.save();
		this.canvas.rotate(270 * Math.PI / 180);
		this.canvas.context.textBaseline="top"
		this.canvas.fillStyle('rgb(0,0,0)');
		this.canvas.fillText(this.ylabel,-title_box_height-y-this.height+(this.height-box.width)/2,x);
		this.canvas.restore(state);
	}
	x+=ytitle_box_width
    var x_indicatorsBox=this.canvas.textBox("1");
	
	var y_indicator_box=this.drawYindicators(x,y+title_box_height,this.height-title_box_height-xtitle_box_height-x_indicatorsBox.height);
	x+=y_indicator_box.width;
	
	var graphwidth=this.width-(this.legendBox.width+3+3+y_indicator_box.width+ytitle_box_width);
	var graphheight=y_indicator_box.innerHeight;

	this.graphbox={x:x,y:y_indicator_box.innerYTop,w:graphwidth,h:graphheight};
	if(this.graphImage==null){
		this.canvas.listen_mousemove(
			this.graphbox.x,
			this.graphbox.y,
			this.graphbox.w,
			this.graphbox.h,
			function(e){me.drawSelectArea(e)}
		);

		this.canvas.listen_click(
			this.graphbox.x,
			this.graphbox.y,
			this.graphbox.w,
			this.graphbox.h,
			function(e){me.selectArea(e)}
		);
		
	}

	this.drawXindicators(x,y_indicator_box.innerYBottom,graphwidth);
	
	this.canvas.beginPath();
	this.canvas.moveTo(x,y_indicator_box.innerYTop);
	this.canvas.lineTo(x,y_indicator_box.innerYBottom);
	this.canvas.lineTo(x+graphwidth,y_indicator_box.innerYBottom);
	this.canvas.stroke();
        
	var dx=graphwidth/(this.view.x.max-this.view.x.min);
	var dy=graphheight/(this.view.y.max-this.view.y.min);



	for(var i=0;i<this.plots.length;i++){
		var started=false;
		if(this.plots[i].show==false){
			continue;
		}
		this.canvas.strokeStyle(this.plots[i].color);	
		if(this.plots[i].values[0][0]>=this.view.x.min){
			this.canvas.beginPath();
			this.canvas.moveTo((this.plots[i].values[0][0]-this.view.x.min)*dx+x,y_indicator_box.innerYBottom-(this.plots[i].values[0][1]-this.view.y.min)*dy);
			started=true;
		}
		for(var p=1;p<this.plots[i].values.length;p++){

			var coord={
				x:(this.plots[i].values[p][0]-this.view.x.min)*dx+x,
				y:y_indicator_box.innerYBottom-(this.plots[i].values[p][1]-this.view.y.min)*dy,
				radius:2
			};
			if(started){
				if(this.plots[i].values[p][0]>this.view.x.max){
					var tempy=this.getIntersectionY(this.plots[i].values[p-1],this.plots[i].values[p],this.view.x.max);
					this.canvas.lineTo((this.view.x.max-this.view.x.min)*dx+x,y_indicator_box.innerYBottom-(tempy-this.view.y.min)*dy);
					break;
				}				
				this.canvas.lineTo(coord.x,coord.y);
			}else{
				if(this.plots[i].values[p][0]>=this.view.x.min){
					this.canvas.beginPath();
					var tempy=this.getIntersectionY(this.plots[i].values[p-1],this.plots[i].values[p],this.view.x.min);
					this.canvas.moveTo(x,y_indicator_box.innerYBottom-(tempy-this.view.y.min)*dy);
					started=true;
					if(this.plots[i].values[p][0]>this.view.x.max){
						var tempy=this.getIntersectionY(this.plots[i].values[p-1],this.plots[i].values[p],this.view.x.max);
						this.canvas.lineTo((this.view.x.max-this.view.x.min)*dx+x,y_indicator_box.innerYBottom-(tempy-this.view.y.min)*dy);
						break;
					}				
					this.canvas.lineTo(coord.x,coord.y);					
				}
			}
		}
		this.canvas.stroke();


		for(var p=0;p<this.plots[i].values.length;p++){
			if(this.plots[i].values[p][0]<=this.view.x.max&&this.plots[i].values[p][0]>=this.view.x.min){			
				this.canvas.fillCircle(
					(this.plots[i].values[p][0]-this.view.x.min)*dx+x
				    ,y_indicator_box.innerYBottom-(this.plots[i].values[p][1]-this.view.y.min)*dy
					,2
				);
			}

		}
	} 

	this.graphImage=this.canvas.getImageData(		
		this.graphbox.x,
		this.graphbox.y,
		this.graphbox.w,
		this.graphbox.h);
}


GraphClass.prototype.getXCoordinateToValue=function(x){
	return (this.view.x.max-this.view.x.min)*x+this.view.x.min;
}

GraphClass.prototype.getIntersectionY=function(p1,p2,x){
	var dy=p2[1]-p1[1];
	var dx=p2[0]-p1[0];
	return (dy/dx)*(x-p1[0])+p1[1];
}

if(typeof(JCanvasDSLClass)=='function'){
	JCanvasDSLClass.prototype.rootNode.graph=function(self,node){	
		var ele=Graph(self.canvas);
		self.mapAttributes(node,ele,[
				['x','x',parseInt],
				['y','y',parseInt],
				['width','width',parseInt],
				['height','height',parseInt],	
				['title','title'],						
				['xlabel','xlabel'],
				['ylabel','ylabel'],
				['showGrid','showGrid',self.parseBoolean],
				['yindicators','makeYindicators',parseInt],
				['xindicators','makeXindicators',parseInt]				
				]
		);	
		var c=self.firstChild(node);
		while(c){
			switch(c.nodeName){
				case 'plot':
					var plot=ele.Plot();
					self.mapAttributes(c,plot,[
							['title','title']
							]);
					var node=c.firstChild;  
					var values="";
					while(node){
						values+=node.nodeValue;
						node=node.nextSibling;
					}
					values=values.split(/\n/);
					for(var index in values){
						var value=values[index];					
						value=value.replace(/\s+/g,' ').replace(/^\s*/,'').replace(/\s*$/,'');
						if(value=='e'){
							break;
						}							
						if(value==''){
							continue;
						}
						value=value.split(' ');
						var tuple=[];						
						if(value.length==1){
							tuple.push(plot.values.length);
						}
						for (var index2 in value) {
							if (value[index2].match(/^\d+$/)) {
								tuple.push(parseInt(value[index2]));
								continue;
							}
							if (value[1].match(/^\d+\.\d+$/)) {
								tuple.push(parseFloat(value[index2]));
								continue;
							}
						}
						plot.values.push(tuple);								
					}
					ele.addPlot(plot);				
				break;
			}
			c=self.nextSibling(c);
		}
		ele.init();
		self.canvas.appendChild(ele);		
	}
};

