// Aug 28, 2014
// koji ota

function ko2graph(ctx)
{
	this.ctx = ctx;
	this.palette = ["skyblue", "orange", "darkgreen", "brown"];
	this.canvas = {};
	this.canvas.w = 0;
	this.canvas.h = 0;
	this.resize = function(w, h)  {
		this.canvas.w = w;
		this.canvas.h = h;
	};
	this.gx1 =  70;
	this.gy1 = 140;
	this.gx2 = 620;
	this.gy2 = 440;
	this.locate = function(gx1, gy1, gx2, gy2) {
		this.gx1 = gx1;
		this.gy1 = gy1;
		this.gx2 = gx2;
		this.gy2 = gy2;
	};
	this.px1 = 0;
	this.py1 = 0;
	this.px2 = 0;
	this.py2 = 0;
	this.range = function(px1, py1, px2, py2) {
		this.px1 = px1;
		this.py1 = py1;
		this.px2 = px2;
		this.py2 = py2;
	};
	this.xrange = function(px1, px2) {
		this.px1 = px1;
		this.px2 = px2;
	};
	this.yrange = function(py1, py2) {
		this.py1 = py1;
		this.py2 = py2;
	};
	this.type = null;
	this.loadType = function(type) {
		this.type = type;
	};
	this.data = null;
	this.loadData = function(data) {
		this.data = data;
	};
	this.px2gx = function(x) {
		return this.gx1 + ( ( x - this.px1 ) * (this.gx2 - this.gx1) ) / (this.px2 - this.px1 );
	};
	this.py2gy = function(y) {
		return this.gy2 - ( ( y - this.py1 ) * (this.gy2 - this.gy1) ) / (this.py2 - this.py1 );
	};
	this.clear = function() {
		this.ctx.clearRect(0, 0, this.canvas.w, this.canvas.h);
	};
	this.drawLine = function(x1, y1, x2, y2) {
		this.ctx.beginPath();
		this.ctx.moveTo( x1, y1);
		this.ctx.lineTo( x2, y2);
		this.ctx.stroke();
	};
	this.drawRect = function(x1, y1, x2, y2) {
		this.ctx.beginPath();
		this.ctx.moveTo( x1, y1);
		this.ctx.lineTo( x2, y1);
		this.ctx.lineTo( x2, y2);
		this.ctx.lineTo( x1, y2);
		this.ctx.closePath();
		this.ctx.stroke();
	};
	this.drawString = function(str, x, y) {
		if (arguments.length == 4) {
			var style = arguments[3];
			if (style.font != undefined) this.ctx.font = style.font;
			if (style.textAlign != undefined) this.ctx.textAlign = style.textAlign;
			if (style.textBaseline != undefined) this.ctx.textBaseline = style.textBaseline;
			if (style.strokeStyle != undefined) this.ctx.strokeStyle = style.strokeStyle;
			if (style.fillStyle != undefined) this.ctx.fillStyle = style.fillStyle;
		}
		this.ctx.fillText(str, x, y);
		//this.ctx.strokeText(str, x, y);
	};
	this.autoscale = function(min, max) {
		if (min == max) {
			max = min + 1;
		}
		//console.log("min="+min+", max="+max);
		var minBase = Math.pow(10, Math.floor(Math.log10(min)));
		var maxBase = Math.pow(10, Math.floor(Math.log10(max)));
		//console.log("min.base="+minBase);
		//console.log("max.base="+maxBase);
		var aMin = 0;
		var aMax = 0;
		if (minBase == maxBase) {
			aMin = (Math.floor(min/minBase) - 1) * minBase;
			aMax = (Math.floor(max/maxBase) + 1) * maxBase;
		} else {
			aMax = (Math.floor(max/maxBase) + 1) * maxBase;
		}
		//console.log("min.1st="+Math.floor(min/minBase));
		//console.log("max.1st="+Math.floor(max/maxBase));
		
		return {min: aMin, max: aMax};
	};
	this.stack = {};
	this.push = function() {
		this.stack.textAlign    = this.ctx.textAlign;
		this.stack.textBaseline = this.ctx.textBaseline;
		this.stack.strokeStyle  = this.ctx.strokeStyle;
		this.stack.font         = this.ctx.font;
	};
	this.pop = function() {
		this.ctx.textAlign    = this.stack.textAlign;
		this.ctx.textBaseline = this.stack.textBaseline;
		this.ctx.strokeStyle  = this.stack.strokeStyle;
		this.ctx.font         = this.stack.font;
	};
	this.compareNumber = function(a, b) {
		return a-b;
	};
	this.context = function() {
		return this.ctx;
	};
	this.draw = function() {
		if (this.data == null) {
			return;
		}
		
		var data = this.data;
		
		this.drawRect(this.gx1, this.gy1, this.gx2, this.gy2);
	
		// finds min/max values from whole data.
		var minY = 0.0;
		var maxY = 0.0;
		for(var i=0; i<data.length; i++) {
			for(var j=0; j<data[i].data.length; j++) {
				if ((i == 0) && (j == 0)) {
					minY = data[i].data[j];
					maxY = data[i].data[j];
				} else {
					if (data[i].data[j] < minY) minY = data[i].data[j];
					if (data[i].data[j] > maxY) maxY = data[i].data[j];
				}
			}
		}
		var autoscale = this.autoscale(minY, maxY);
		//console.log("auto.min="+autoscale.min+", auto.max="+autoscale.max);
		
		// set up y axis (range)
		this.yrange(autoscale.min, this.py2 = autoscale.max);
		// draw axis
		this.ctx.textAlign = "right";
		this.ctx.textBaseline = "middle";
		var yincr = (autoscale.max - autoscale.min)/4;
		for(var y=autoscale.min; y<=autoscale.max; y+=yincr) {
			var gy = this.py2gy(y);
			this.drawLine(this.gx1, gy, this.gx1+10, gy);
			this.ctx.fillText(""+sprintf("%7.3f",y), this.gx1-5, gy);
		}
		
		switch(this.type) {
		case "box":
			this.xrange(0, data.length+1);
			// http://dreamer.gobiz.jp/archives/489
			var letters = [ "A", "B", "C" ];
			var group = new Array();
			for(var i=0; i<data.length; i++) {
				this.ctx.strokeStyle = "black";
				
				// draw x axis
				this.ctx.textAlign = "center";
				var gx = this.px2gx(i+1);
				this.ctx.fillText(letters[i],  gx, this.gy2+12);
				this.ctx.fillText(data[i].project, gx, this.gy2+24);
					
				var sum = 0.0;
				var sorted = data[i].data.sort(this.compareNumber);
				//for(var j=0; j<sorted.length; j++) {
				//	console.log(sorted[j]);
				//}
				group.push(sorted);
					
				for(var j=0; j<sorted.length; j++) {
					sum += sorted[j];
				}
				var ave = sum/data[i].data.length;
				var median = 0;
				var LH = 0;
				var UH = 0;
				if (sorted.length & 1) {
					var center = Math.floor(sorted.length/2);
					median = sorted[center];
					var center1 = Math.floor(center/2);
					LH = sorted[center-center1];
					UH = sorted[center+center1];
				} else {
					var center0 = Math.floor(sorted.length/2);
					median = (sorted[center0-1]+sorted[center0-1+1])/2;
					var center1 = center0 - Math.floor(center0/2);
					var center2 = center0 + Math.floor(center0/2);
					LH = (sorted[center1-1]+sorted[center1-1+1])/2;
					UH = (sorted[center2-1]+sorted[center2-1+1])/2;
				}
				var lineMin = LH-(UH-LH)*1.5;
				var lineMax = UH+(UH-LH)*1.5;
				var oobLo = 1;
				var oobHi = 1;
				if (sorted[0] >= lineMin) {
					lineMin = sorted[0];
					oobLo = 0;
				}
				if (sorted[sorted.length-1] <= lineMax) {
					lineMax = sorted[sorted.length-1];
					oobHi = 0;
				}
					
				//console.log("median="+median);
				//console.log("ave="+ave);
				//console.log("LH="+LH);
				//console.log("UH="+UH);
				//console.log("lineMin="+lineMin);
				//console.log("lineMax="+lineMax);
					
				// box, line
				var gx1 = gx - 10;
				var gx2 = gx + 10;
				var gy1 = this.py2gy(LH);
				var gy2 = this.py2gy(UH);
				//console.log("gx1="+gx1+", gy1="+gy1+", gx2="+gx2+", gy2="+gy2);
				this.drawRect(gx1, gy1, gx2, gy2);
				var gy = 0;
				gy = this.py2gy(lineMin);
				this.drawLine(gx, gy1, gx, gy);
				gy = this.py2gy(lineMax);
				this.drawLine(gx, gy2, gx, gy);
					
				// median
				gy = this.py2gy(median);
				this.drawLine(gx1, gy, gx2, gy);
					
				// ave
				this.ctx.strokeStyle = "red";
				gy = this.py2gy(ave);
				this.drawRect(gx-1, gy-1, gx+1, gy+1);
				this.ctx.textAlign = "start";
				this.ctx.fillText(sprintf("%7.3f",ave)+"("+sorted.length+")", gx2 + 5, gy);
					
				// oob
				this.ctx.strokeStyle = "blue";
				if (oobLo) {
					gy = this.py2gy(sorted[0]);
					this.drawRect(gx-1, gy-1, gx+1, gy+1);
				}
				if (oobHi) {
					gy = this.py2gy(sorted[sorted.length-1]);
					this.drawRect(gx-1, gy-1, gx+1, gy+1);
				}
			}
			//console.log("group="+group.length);
			if (group.length >= 2) {
				if (group[0].length > 1) {
					this.ctx.textAlign = "end";
					var rf = ftest(group[0], group[1])
					var rt = ttest(group[0], group[1]);
					
					var text = "";
					
					if ((rf.result === "same") && (rt.result === "same")) {
						text = "OK";
					} else {
						text = "FAIL(";
						if (rf.result === "same") {
							text += "same";
						} else {
							text += rf.detail;
						}
						text += "|";
						if (rt.result === "same") {
							text += "same";
						} else {
							text += rt.detail;
						}
						text += ")";
					}
					
					//var ru = utest(group[0], group[1]);
					//text += "A/B="+rt.result;
					//if (rt.result == "different") {
					//	text +="("+rt.detail+")";
					//}
					//text += ","+ru.result;
					
					this.ctx.fillText(text, this.gx2, this.gy1-12);
				}
			}
			break;
			
		case "lineplot":
			var max = 0;
			for(var i=0; i<data.length; i++) {
				if (data[i].data.length > max) {
					max = data[i].data.length;
				}
			}
			this.xrange(0, max);
			
			// draw x axis
			this.ctx.textAlign = "center";
			var xinc = 1;
			var d = data[0].data;
			if (d.length > 10) {
				xincr = 10;
			}
			this.ctx.strokeStyle = "black";
			for(var i=0; i<d.length; i+=xinc) {
				var gx = this.px2gx(i);
				this.drawLine(gx, this.gy2, gx, this.gy2-10);
				this.ctx.fillText(i, gx, this.gy2+12);
			}
			var lx = 0;
			var ly = 0;
			for(var i=0; i<data.length; i++) {
				lx = 0;
				ly = 0;
				d = data[i].data;
				this.ctx.strokeStyle = this.palette[i];
				for(var j=0; j<d.length; j++) {
					var gx = this.px2gx(j);
					var gy = this.py2gy(d[j]);
					this.drawRect(gx-1, gy-1, gx+1, gy+1);
					if (j > 0) {
						this.drawLine(lx, ly, gx, gy);
					}
					lx = gx;
					ly = gy;
				}
			}
			var tx = this.gx2-5;
			var ty = this.gy1+10;
			this.ctx.textAlign = "end";
			for(var i=0; i<data.length; i++) {
				var r = linearRegression(data[i].data);
				this.ctx.strokeStyle = this.palette[i];
				this.ctx.fillText("y=("+r.slope+")x + ("+r.bias+" )", tx, ty);
				ty += 20;
			}
			break;
			
		case "plot":
			var max = 0;
			for(var i=0; i<data.length; i++) {
				if (data[i].data.length > max) {
					max = data[i].data.length;
				}
			}
			this.xrange(0, max);
			
			// draw x axis
			this.ctx.textAlign = "center";
			var xinc = 1;
			var d = data[0].data;
			if (d.length > 5000) {
				xinc = 5000;
			} else if (d.length > 1000) {
				xinc = 1000;
			} else if (d.length > 100) {
				xinc = 100;
			} else if (d.length > 10) {
				xinc = 10;
			}
			this.ctx.strokeStyle = "black";
			for(var i=0; i<d.length; i+=xinc) {
				var gx = this.px2gx(i);
				this.drawLine(gx, this.gy2, gx, this.gy2-10);
				this.ctx.fillText(i, gx, this.gy2+12);
			}
			for(var i=0; i<data.length; i++) {
				this.ctx.strokeStyle = this.palette[i];
				d = data[i].data;
				for(var j=0; j<d.length; j++) {
					var gx = this.px2gx(j);
					var gy = this.py2gy(d[j]);
					this.drawRect(gx-1, gy-1, gx+1, gy+1);
				}
			}
			var tx = this.gx2-5;
			var ty = this.gy1+10;
			this.ctx.textAlign = "end";
			for(var i=0; i<data.length; i++) {
				var r = linearRegression(data[i].data);
				this.ctx.strokeStyle = this.palette[i];
				this.ctx.fillText("y=("+r.slope+")x + ("+r.bias+" )", tx, ty);
				ty += 20;
			}
			break;
		}
	};
}

var ko2DateUtil = {
	daysOfYear: function(y) {
		var s = new Date(y,  0, 1);
		var d = new Date(y, 12, 0);
		var f = Math.floor((d.getTime() - s.getTime()) / (1000 * 86400));
		return f;
	},
	weeksOfYear: function(y) {
		var s = new Date(y,  0, 1);
		var d = new Date(y, 12, 0);
		var f = Math.floor((d.getTime() - s.getTime()) / (1000 * 86400));
		weeks = Math.floor((f + 12 ) / 7);
		return weeks;
	},
	date2daysOfYear: function(date) {
		var firstDay = new Date(date.getFullYear(), 0, 1);
		//return Math.ceil((date - firstDay) / (1000 * 86400));
		var days = Math.floor((date.getTime() - firstDay.getTime()) / (1000 * 86400));
		return days;
	},
	date2weeksOfYear: function(date) {
		var firstDay = new Date(date.getFullYear(), 0, 1);
		var offset = firstDay.getDay() - 1;
		var weeks = Math.floor((ko2DateUtil.date2daysOfYear(date) + 12) / 7);
		var result = (firstDay.getDay() == 0) ? weeks + 1 : weeks;
		return result;
	},
	weeksOfYear2date: function(weeks, year) {
		// object of year/1/1
		var Jan01 = new Date(year, 0, 1);
		// get week day (0=sun, 1=mon, 2=tue, 3=wed, 4=thr, 5=fri, 6=sat)
		var offset = Jan01.getDay();
		return Jan01.setDate(Jan01.getDate() + (weeks * 7) - offset);
	},
};

function ko2TaskStackedChart(canvas)
{
	this._canvas = canvas;
	this._ctx = canvas.getContext("2d");
	this._axis = [];
	this._date = {};
	this.setDate = function(fromDate, toDate) {
		this._date.date1 = fromDate;
		this._date.date2 = toDate;
	};
	this._weeks = 0;
	this.weeks = function() {
		console.log("->TaskStackedChart.weeks");
		if (this._date == {}) {
			return 0;
		}
		if (this._weeks != 0) {
			return this._weeks;
		}
		var weeks = 0;
		var year1 = this._date.date1.getFullYear();
		var year2 = this._date.date2.getFullYear();
		if (year1 == year2) {
			console.log("--> 1");
			week1 = ko2DateUtil.date2weeksOfYear(this._date.date1);
			week2 = ko2DateUtil.date2weeksOfYear(this._date.date2);
			weeks = week2 - week1;
			for(var i=week1; i<=week2; i++) {
				var d = ko2DateUtil.weeksOfYear2date(i, year1);
				var o = new Date(d);
				this._axis.push(d);
			}
			
		} else if (year1 < year2) {
			console.log("--> 2");
			// 開始            終了
			// --^---|-------|----^--
			// A      B       C
			// A
			weeks = ko2DateUtil.weeksOfYear(year1) - ko2DateUtil.date2weeksOfYear(this._date.date1) - 1;
		console.log("--> "+ko2DateUtil.weeksOfYear(year1)+" - "+ko2DateUtil.date2weeksOfYear(this._date.date1)+" = "+weeks);
			// B
			for(var y=year1+1; y<year2; i++) {
				weeks += ko2DateUtil.weeksOfYear(y);
			}
			// C
			weeks += ko2DateUtil.date2weeksOfYear(this._date.date2);
			
		}
		console.log("--> weeks = "+weeks);
		this._weeks = weeks;
		return weeks;
	};
	this.date2weeks = function(date) {
		var year1 = this._date.date1.getFullYear();
		var year2 = date.getFullYear();
		
		var week = 0;
		if (year1 == year2) {
			weeks = ko2DateUtil.date2weeksOfYear(date) - ko2DateUtil.date2weeksOfYear(this._date.date1);
		} else if (year1 < year2) {
			// 開始            date
			// --^---|-------|----^--
			// A      B       C
			// A
			weeks = ko2DateUtil.weeksOfYear(year1) - ko2DateUtil.date2weeksOfYear(this._date.date1) - 1;
			// B
			for(var y=year1+1; y<year2; i++) {
				weeks += ko2DateUtil.weeksOfYear(y);
			}
			// C
			weeks += ko2DateUtil.date2weeksOfYear(date);
			
		}
		return weeks;
	};
	this.weeks2date = function(weeks) {
		return this._axis[weeks];
	};
	this._items = [];
	this.clear = function() {
		this._items = [];
	};
	this.add = function(c) {
		this._items.push(c);
		this._arranged = 0;
	};
	this._arranged = 0;
	this._planned = [];
	this._actual = [];
	this._max_planned = 0;
	this._max_actual = 0;
	this.arrange = function() {
		if (this._arranged) {
			return;
		}
		this._base = [];
		this._planned = [];
		this._actual = [];
		var n = this.weeks();
		for(var i=0; i<n; i++) {
			this._base.push();
			this._planned.push(new Array());
			this._actual.push(new Array());
		}
		var j = 0;
		var s = 0;
		var e = 0;
		for(var i=0; i<this._items.length; i++) {
			if ((this._items[i].date_plan_start != "") && (this._items[i].date_plan_end != "")) {
				s = this.date2weeks(new Date(this._items[i].date_plan_start));
				e = this.date2weeks(new Date(this._items[i].date_plan_end));
				for(j=s; j<=e; j++) {
					this._planned[j].push(this._items[i]);
				}
			}
			if ((this._items[i].date_actual_start != "") && (this._items[i].date_actual_end != "")) {
				s = this.date2weeks(new Date(this._items[i].date_actual_start));
				e = this.date2weeks(new Date(this._items[i].date_actual_end));
				for(j=s; j<=e; j++) {
					this._actual[j].push(this._items[i]);
				}
			}
		}
		this._max_planned = 0;
		this._max_actual = 0;
		var m = 0;
		for(var i=0; i<n; i++) {
			m = this._planned[i].length;
			if (this._max_planned < m) {
				this._max_planned = m;
			}
			m = this._actual[i].length;
			if (this._max_actual < m) {
				this._max_actual = m;
			}
		}
		this._arranged = 1;
		console.log("arranged. plan="+this._max_planned+", "+this._max_actual);
	};
	this.draw = function() {
		var sw0 = this._canvas.width;
		var sh0 = this._canvas.height;
		sw0 = 800;
		sh0 = 480;
		this._canvas.style.width  = sw0 + "px";
		this._canvas.style.height = sh0 + "px";
		var sw = sw0 * window.devicePixelRatio;
		var sh = sh0 * window.devicePixelRatio;
		this._canvas.width  = sw;
		this._canvas.height = sh; 
		console.log("sw="+sw+", sh="+sh);
		
		// clear
		this._ctx.clearRect(0, 0, sw, sh);
		
		var x1 = 50;
		var x2 = sw-10;
		var cy = sh/2;
		var ya = cy-30; // actual base
		var yp = cy+30; // planned base
		
		// time scale = week
		var n = this.weeks();
		var uw = (x2-x1)/n;
		var stacked = this._max_planned + this._max_actual;
		console.log("stacked = "+this._max_planned+","+this._max_actual);
		if (stacked == 0) {
			stacked = 20;
		}
		var uh = (sh - 20 - 20)/stacked;
		
		this.arrange();
		
		console.log("n="+n);
		for(var i=0; i<n; i++) {
			var x = x1 + i * uw;
			var y = cy;
			// axis
			this._ctx.fillStyle = "rgb(1, 1, 1)"
			this._ctx.fillRect(x, y, uw-1, uh);
			this._ctx.font = (uh-10) + "px 'Calibri'";
			this._ctx.fillStyle = "orange";
			var d = new Date(this.weeks2date(i));
			this._ctx.fillText(d.getFullYear()+"/"+(d.getMonth()+1)+"/"+d.getDate(), x+2, y+(uh-2));
			console.log("x="+x+", y="+y+", uw="+uw+", uh="+uh);
			// actual
			y = ya - uh;
			for(var j=0; j<this._actual[i].length; j++) {
				switch(this._actual[i][j].type) {
				case "タスク": this._ctx.fillStyle = "rgb(155, 187,  89)"; break;
				case "リスク": this._ctx.fillStyle = "rgb(192,  80,  77)"; break;
				}
				this._ctx.fillRect(x, y, uw-1, uh);
				
				if (this._actual[i].conclusion != "") {
					this._ctx.fillStyle = "rgb(128, 128, 128)"
				} else {
					this._ctx.fillStyle = "rgb(255, 255, 255)"
				}
				this._ctx.fillRect(x+2, y, uw-2, uh);
				this._ctx.fillStyle = "black";
				this._ctx.fillText("@"+this._actual[i][j]._sys.id, x+2, y+(uh-2));
				y -= uh;
			}
			// planned
			y = yp;
			for(var j=0; j<this._planned[i].length; j++) {
				switch(this._planned[i][j].type) {
				case "タスク": this._ctx.fillStyle = "rgb(155, 187,  89)"; break;
				case "リスク": this._ctx.fillStyle = "rgb(192,  80,  77)"; break;
				}
				this._ctx.fillRect(x, y, uw-1, uh);
				
				if (this._actual[i].conclusion != "") {
					this._ctx.fillStyle = "rgb(128, 128, 128)"
				} else {
					this._ctx.fillStyle = "rgb(255, 255, 255)"
				}
				this._ctx.fillStyle = "black";
				this._ctx.fillText("@"+this._planned[i][j]._sys.id, x+2, y+(uh-2));
				y += uh;
			}
		}
		console.log("ko2TaskStackedChart.drawn");
	};
}


