// doc can be found @ http://jburrows.wordpress.com/2011/02/21/documentation-for-graphael-g-line-js/

/*!
 * g.Raphael 0.5 - Charting library, based on Raphaël
 *
 * Copyright (c) 2009 Dmitry Baranovskiy (http://g.raphaeljs.com)
 * Licensed under the MIT (http://www.opensource.org/licenses/mit-license.php) license.
 */
(function () {

		function shrink(values, dim) {
				var k = values.length / dim,
						j = 0,
						l = k,
						sum = 0,
						res = [];

				while (j < values.length) {
						l--;

						if (l < 0) {
								sum += values[j] * (1 + l);
								res.push(sum / k);
								sum = values[j++] * -l;
								l += k;
						} else {
								sum += values[j++];
						}
				}
				return res;
		}

		function getAnchors(p1x, p1y, p2x, p2y, p3x, p3y) {
				var l1 = (p2x - p1x) / 2,
						l2 = (p3x - p2x) / 2,
						a = Math.atan((p2x - p1x) / Math.abs(p2y - p1y)),
						b = Math.atan((p3x - p2x) / Math.abs(p2y - p3y));

				a = p1y < p2y ? Math.PI - a : a;
				b = p3y < p2y ? Math.PI - b : b;

				var alpha = Math.PI / 2 - ((a + b) % (Math.PI * 2)) / 2,
						dx1 = l1 * Math.sin(alpha + a),
						dy1 = l1 * Math.cos(alpha + a),
						dx2 = l2 * Math.sin(alpha + b),
						dy2 = l2 * Math.cos(alpha + b);

				return {
						x1: p2x - dx1,
						y1: p2y + dy1,
						x2: p2x + dx2,
						y2: p2y + dy2
				};
		}

		function Linechart(paper, x, y, width, height, valuesx, valuesy, opts) {

				var chartinst = this;

				opts = opts || {};

				if (!paper.raphael.is(valuesx[0], "array")) {
						valuesx = [valuesx];
				}

				if (!paper.raphael.is(valuesy[0], "array")) {
						valuesy = [valuesy];
				}

				var gutter = opts.gutter || 10,
						len = Math.max(valuesx[0].length, valuesy[0].length),
						symbol = opts.symbol || "",
						colors = opts.colors || chartinst.colors,
						columns = null,
						dots = null,
						chart = paper.set(),
						path = [];

				for (var i = 0, ii = valuesy.length; i < ii; i++) {
						len = Math.max(len, valuesy[i].length);
				}

				var shades = paper.set();

				for (i = 0, ii = valuesy.length; i < ii; i++) {
						if (opts.shade) {
								shades.push(paper.path().attr({ stroke: "none", fill: colors[i], opacity: opts.nostroke ? 1 : .3 }));
						}

						if (valuesy[i].length > width - 2 * gutter) {
								valuesy[i] = shrink(valuesy[i], width - 2 * gutter);
								len = width - 2 * gutter;
						}

						if (valuesx[i] && valuesx[i].length > width - 2 * gutter) {
								valuesx[i] = shrink(valuesx[i], width - 2 * gutter);
						}
				}

				var allx = Array.prototype.concat.apply([], valuesx),
						ally = Array.prototype.concat.apply([], valuesy),
						xdim = chartinst.snapEnds(Math.min.apply(Math, allx), Math.max.apply(Math, allx), valuesx[0].length - 1),
						minx = xdim.from,
						maxx = xdim.to,
						ydim = chartinst.snapEnds(Math.min.apply(Math, ally), Math.max.apply(Math, ally), valuesy[0].length - 1),
						miny = ydim.from,
						maxy = ydim.to,
						kx = (width - gutter * 2) / ((maxx - minx) || 1),
						ky = (height - gutter * 2) / ((maxy - miny) || 1);

				var axis = paper.set();

				if (opts.axis) {
						var ax = (opts.axis + "").split(/[,\s]+/);
						+ax[0] && axis.push(chartinst.axis(x + gutter, y + gutter, width - 2 * gutter, minx, maxx, opts.axisxstep || Math.floor((width - 2 * gutter) / 20), 2, paper));
						+ax[1] && axis.push(chartinst.axis(x + width - gutter, y + height - gutter, height - 2 * gutter, miny, maxy, opts.axisystep || Math.floor((height - 2 * gutter) / 20), 3, paper));
						+ax[2] && axis.push(chartinst.axis(x + gutter, y + height - gutter, width - 2 * gutter, minx, maxx, opts.axisxstep || Math.floor((width - 2 * gutter) / 20), 0, paper));
						+ax[3] && axis.push(chartinst.axis(x + gutter, y + height - gutter, height - 2 * gutter, miny, maxy, opts.axisystep || Math.floor((height - 2 * gutter) / 20), 1, paper));
				}

				var lines = paper.set(),
						symbols = paper.set(),
						line;

				for (i = 0, ii = valuesy.length; i < ii; i++) {
						if (!opts.nostroke) {
								lines.push(line = paper.path().attr({
										stroke: colors[i],
										"stroke-width": opts.width || 2,
										"stroke-linejoin": "round",
										"stroke-linecap": "round",
										"stroke-dasharray": opts.dash || ""
								}));
						}

						var sym = Raphael.is(symbol, "array") ? symbol[i] : symbol,
								symset = paper.set();

						path = [];

						for (var j = 0, jj = valuesy[i].length; j < jj; j++) {
								var X = x + gutter + ((valuesx[i] || valuesx[0])[j] - minx) * kx,
										Y = y + height - gutter - (valuesy[i][j] - miny) * ky;

								(Raphael.is(sym, "array") ? sym[j] : sym) && symset.push(paper[Raphael.is(sym, "array") ? sym[j] : sym](X, Y, (opts.width || 2) * 3).attr({ fill: colors[i], stroke: "none" }));

								if (opts.smooth) {
										if (j && j != jj - 1) {
												var X0 = x + gutter + ((valuesx[i] || valuesx[0])[j - 1] - minx) * kx,
														Y0 = y + height - gutter - (valuesy[i][j - 1] - miny) * ky,
														X2 = x + gutter + ((valuesx[i] || valuesx[0])[j + 1] - minx) * kx,
														Y2 = y + height - gutter - (valuesy[i][j + 1] - miny) * ky,
														a = getAnchors(X0, Y0, X, Y, X2, Y2);

												path = path.concat([a.x1, a.y1, X, Y, a.x2, a.y2]);
										}

										if (!j) {
												path = ["M", X, Y, "C", X, Y];
										}
								} else {
										path = path.concat([j ? "L" : "M", X, Y]);
								}
						}

						if (opts.smooth) {
								path = path.concat([X, Y, X, Y]);
						}

						symbols.push(symset);

						if (opts.shade) {
								shades[i].attr({ path: path.concat(["L", X, y + height - gutter, "L",  x + gutter + ((valuesx[i] || valuesx[0])[0] - minx) * kx, y + height - gutter, "z"]).join(",") });
						}

						!opts.nostroke && line.attr({ path: path.join(",") });
				}

				function createColumns(f) {
						// unite Xs together
						var Xs = [];

						for (var i = 0, ii = valuesx.length; i < ii; i++) {
								Xs = Xs.concat(valuesx[i]);
						}

						Xs.sort();
						// remove duplicates

						var Xs2 = [],
								xs = [];

						for (i = 0, ii = Xs.length; i < ii; i++) {
								Xs[i] != Xs[i - 1] && Xs2.push(Xs[i]) && xs.push(x + gutter + (Xs[i] - minx) * kx);
						}

						Xs = Xs2;
						ii = Xs.length;

						var cvrs = f || paper.set();

						for (i = 0; i < ii; i++) {
								var X = xs[i] - (xs[i] - (xs[i - 1] || x)) / 2,
										w = ((xs[i + 1] || x + width) - xs[i]) / 2 + (xs[i] - (xs[i - 1] || x)) / 2,
										C;

								f ? (C = {}) : cvrs.push(C = paper.rect(X - 1, y, Math.max(w + 1, 1), height).attr({ stroke: "none", fill: "#000", opacity: 0 }));
								C.values = [];
								C.symbols = paper.set();
								C.y = [];
								C.x = xs[i];
								C.axis = Xs[i];

								for (var j = 0, jj = valuesy.length; j < jj; j++) {
										Xs2 = valuesx[j] || valuesx[0];

										for (var k = 0, kk = Xs2.length; k < kk; k++) {
												if (Xs2[k] == Xs[i]) {
														C.values.push(valuesy[j][k]);
														C.y.push(y + height - gutter - (valuesy[j][k] - miny) * ky);
														C.symbols.push(chart.symbols[j][k]);
												}
										}
								}

								f && f.call(C);
						}

						!f && (columns = cvrs);
				}

				function createDots(f) {
						var cvrs = f || paper.set(),
								C;

						for (var i = 0, ii = valuesy.length; i < ii; i++) {
								for (var j = 0, jj = valuesy[i].length; j < jj; j++) {
										var X = x + gutter + ((valuesx[i] || valuesx[0])[j] - minx) * kx,
												nearX = x + gutter + ((valuesx[i] || valuesx[0])[j ? j - 1 : 1] - minx) * kx,
												Y = y + height - gutter - (valuesy[i][j] - miny) * ky;

										f ? (C = {}) : cvrs.push(C = paper.circle(X, Y, Math.abs(nearX - X) / 2).attr({ stroke: "none", fill: "#000", opacity: 0 }));
										C.x = X;
										C.y = Y;
										C.value = valuesy[i][j];
										C.line = chart.lines[i];
										C.shade = chart.shades[i];
										C.symbol = chart.symbols[i][j];
										C.symbols = chart.symbols[i];
										C.axis = (valuesx[i] || valuesx[0])[j];
										f && f.call(C);
								}
						}

						!f && (dots = cvrs);
				}

				chart.push(lines, shades, symbols, axis, columns, dots);
				chart.lines = lines;
				chart.shades = shades;
				chart.symbols = symbols;
				chart.axis = axis;

				chart.hoverColumn = function (fin, fout) {
						!columns && createColumns();
						columns.mouseover(fin).mouseout(fout);
						return this;
				};

				chart.clickColumn = function (f) {
						!columns && createColumns();
						columns.click(f);
						return this;
				};

				chart.hrefColumn = function (cols) {
						var hrefs = paper.raphael.is(arguments[0], "array") ? arguments[0] : arguments;

						if (!(arguments.length - 1) && typeof cols == "object") {
								for (var x in cols) {
										for (var i = 0, ii = columns.length; i < ii; i++) if (columns[i].axis == x) {
												columns[i].attr("href", cols[x]);
										}
								}
						}

						!columns && createColumns();

						for (i = 0, ii = hrefs.length; i < ii; i++) {
								columns[i] && columns[i].attr("href", hrefs[i]);
						}

						return this;
				};

				chart.hover = function (fin, fout) {
						!dots && createDots();
						dots.mouseover(fin).mouseout(fout);
						return this;
				};

				chart.click = function (f) {
						!dots && createDots();
						dots.click(f);
						return this;
				};

				chart.each = function (f) {
						createDots(f);
						return this;
				};

				chart.eachColumn = function (f) {
						createColumns(f);
						return this;
				};

				return chart;
		};

		//inheritance
		var F = function() {};
		F.prototype = Raphael.g;
		Linechart.prototype = new F;

		//public
		Raphael.fn.linechart = function(x, y, width, height, valuesx, valuesy, opts) {
				return new Linechart(this, x, y, width, height, valuesx, valuesy, opts);
		}

})();

