/* Flot plugin for showing crosshairs when the mouse hovers over the plot.

 Copyright (c) 2007-2013 IOLA and Ole Laursen.
 Licensed under the MIT license.

 The plugin supports these options:

 crosshair: {
 mode: null or "x" or "y" or "xy"
 color: color
 lineWidth: number
 }

 Set the mode to one of "x", "y" or "xy". The "x" mode enables a vertical
 crosshair that lets you trace the values on the x axis, "y" enables a
 horizontal crosshair and "xy" enables them both. "color" is the color of the
 crosshair (default is "rgba(170, 0, 0, 0.80)"), "lineWidth" is the width of
 the drawn lines (default is 1).

 The plugin also adds four public methods:

 - setCrosshair( pos )

 Set the position of the crosshair. Note that this is cleared if the user
 moves the mouse. "pos" is in coordinates of the plot and should be on the
 form { x: xpos, y: ypos } (you can use x2/x3/... if you're using multiple
 axes), which is coincidentally the same format as what you get from a
 "plothover" event. If "pos" is null, the crosshair is cleared.

 - clearCrosshair()

 Clear the crosshair.

 - lockCrosshair(pos)

 Cause the crosshair to lock to the current location, no longer updating if
 the user moves the mouse. Optionally supply a position (passed on to
 setCrosshair()) to move it to.

 Example usage:

 var myFlot = $.plot( $("#graph"), ..., { crosshair: { mode: "x" } } };
 $("#graph").bind( "plothover", function ( evt, position, item ) {
 if ( item ) {
 // Lock the crosshair to the data point being hovered
 myFlot.lockCrosshair({
 x: item.datapoint[ 0 ],
 y: item.datapoint[ 1 ]
 });
 } else {
 // Return normal crosshair operation
 myFlot.unlockCrosshair();
 }
 });

 - unlockCrosshair()

 Free the crosshair to move again after locking it.
 */

(function ($) {
	var options = {
		crosshair: {
			mode: null, // one of null, "x", "y" or "xy",
			color: "rgba(170, 0, 0, 0.80)",
			lineWidth: 1,
			snapX: false,
			showXValue: false,
			xValueFormat: "%d.%m.%Y %H:%M",
			xValuePos: '',
		}
	};

	function formatDate(d, fmt, monthNames, dayNames) {
		/*if (typeof d.strftime == "function") {
		 return d.strftime(fmt);
		 }*/
		var leftPad = function (n, pad) {
			n = "" + n;
			pad = "" + (pad == null ? "0" : pad);
			return n.length == 1 ? pad + n : n;
		};

		var r = [];
		var escape = false;
		var hours = d.getHours();
		var isAM = hours < 12;

		if (monthNames == null) {
			monthNames = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
		}

		if (dayNames == null) {
			dayNames = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
		}

		var hours12;

		if (hours > 12) {
			hours12 = hours - 12;
		} else if (hours == 0) {
			hours12 = 12;
		} else {
			hours12 = hours;
		}
		for (var i = 0; i < fmt.length; ++i) {

			var c = fmt.charAt(i);
			if (escape) {
				switch (c) {
					case 'a':
						c = "" + dayNames[d.getDay()];
						break;
					case 'b':
						c = "" + monthNames[d.getMonth()];
						break;
					case 'd':
						c = leftPad(d.getDate());
						break;
					case 'e':
						c = leftPad(d.getDate(), " ");
						break;
					case 'h':	// For back-compat with 0.7; remove in 1.0
					case 'H':
						c = leftPad(hours);
						break;
					case 'I':
						c = leftPad(hours12);
						break;
					case 'l':
						c = leftPad(hours12, " ");
						break;
					case 'm':
						c = leftPad(d.getMonth() + 1);
						break;
					case 'M':
						c = leftPad(d.getMinutes());
						break;
					// quarters not in Open Group's strftime specification
					case 'q':
						c = "" + (Math.floor(d.getMonth() / 3) + 1);
						break;
					case 'S':
						c = leftPad(d.getSeconds());
						break;
					case 'y':
						c = leftPad(d.getFullYear() % 100);
						break;
					case 'Y':
						c = "" + d.getFullYear();
						break;
					case 'p':
						c = (isAM) ? ("" + "am") : ("" + "pm");
						break;
					case 'P':
						c = (isAM) ? ("" + "AM") : ("" + "PM");
						break;
					case 'w':
						c = "" + d.getDay();
						break;
				}
				r.push(c);
				escape = false;
			} else {
				if (c == "%") {
					escape = true;
				} else {
					r.push(c);
				}
			}
		}
		return r.join("");
	}

	// To have a consistent view of time-based data independent of which time
	// zone the client happens to be in we need a date-like object independent
	// of time zones.  This is done through a wrapper that only calls the UTC
	// versions of the accessor methods.

	function makeUtcWrapper(d) {

		function addProxyMethod(sourceObj, sourceMethod, targetObj, targetMethod) {
			sourceObj[sourceMethod] = function () {
				return targetObj[targetMethod].apply(targetObj, arguments);
			};
		};

		var utc = {
			date: d
		};

		// support strftime, if found

		if (d.strftime != undefined) {
			addProxyMethod(utc, "strftime", d, "strftime");
		}

		addProxyMethod(utc, "getTime", d, "getTime");
		addProxyMethod(utc, "setTime", d, "setTime");

		var props = ["Date", "Day", "FullYear", "Hours", "Milliseconds", "Minutes", "Month", "Seconds"];

		for (var p = 0; p < props.length; p++) {
			addProxyMethod(utc, "get" + props[p], d, "getUTC" + props[p]);
			addProxyMethod(utc, "set" + props[p], d, "setUTC" + props[p]);
		}

		return utc;
	};

	// select time zone strategy.  This returns a date-like object tied to the
	// desired timezone

	function dateGenerator(ts, opts) {
		if (opts.timezone == "browser") {
			return new Date(ts);
		} else if (!opts.timezone || opts.timezone == "utc") {
			return makeUtcWrapper(new Date(ts));
		} else if (typeof timezoneJS != "undefined" && typeof timezoneJS.Date != "undefined") {
			var d = new timezoneJS.Date();
			// timezone-js is fickle, so be sure to set the time zone before
			// setting the time.
			d.setTimezone(opts.timezone);
			d.setTime(ts);
			return d;
		} else {
			return makeUtcWrapper(new Date(ts));
		}
	}

	function init(plot) {
		// position of crosshair in pixels
		var crosshair = { x: -1, y: -1, locked: false };
		var closest_to_crosshair = null;
		plot.setCrosshair = function setCrosshair(pos) {
			if (!pos)
				crosshair.x = -1;
			else {
				var o = plot.p2c(pos);
				crosshair.x = Math.max(0, Math.min(o.left, plot.width()));
				crosshair.y = Math.max(0, Math.min(o.top, plot.height()));
			}

			plot.triggerRedrawOverlay();
		};

		plot.clearCrosshair = plot.setCrosshair; // passes null for pos

		plot.lockCrosshair = function lockCrosshair(pos) {
			if (pos)
				plot.setCrosshair(pos);
			crosshair.locked = true;
		};

		plot.unlockCrosshair = function unlockCrosshair() {
			crosshair.locked = false;
		};

		function onMouseOut(e) {
			if (crosshair.locked)
				return;

			if (crosshair.x != -1) {
				crosshair.x = -1;
				if (plot.getOptions().crosshair.snapX) {
					plot.getPlaceholder().trigger('snapEvent', [ -1 ]);
				}
				plot.triggerRedrawOverlay();
			}
		}

		function onMouseMove(e) {
			if (crosshair.locked)
				return;

			if (plot.getSelection && plot.getSelection()) {
				crosshair.x = -1; // hide the crosshair while selecting
				return;
			}
			lastX = crosshair.x;
			var offset = plot.offset();
			crosshair.x = Math.max(0, Math.min(e.pageX - offset.left, plot.width()));
			crosshair.y = Math.max(0, Math.min(e.pageY - offset.top, plot.height()));

			// Calculate snap point
			if (plot.getOptions().crosshair.snapX) {

				// convert canvas x to plot x
				var coords = plot.c2p({ left: crosshair.x});
				var x = coords['x'];
				var closest = null;
				var distance = 0;
				var datas = plot.getData();

				// iterate over all series
				for (i = 0; i < datas.length; i++) {
					var data = datas[i].data;
					// Find closest point for this series
					if (data.length > 0) {
						var done = false;
						var point = null;
						var last_point = null;
						// find two closest points to x value
						if (data.length > 0) {
							for (j = 0; j < data.length && !done; j++) {
								last_point = point;
								point = data[j][0];
								if (point > x) {
									done = true;
								}
							}

							// determine closest
							var point_distance = Math.abs(point - x);
							if (last_point != null && done && (point_distance > Math.abs(last_point - x))) {
								point = last_point;
								point_distance = Math.abs(point - x);
							}

							// check if closest for this series is closer than overall closest
							if (closest == null || distance > point_distance) {
								closest = point;
								distance = point_distance;
							}
						}
					}
				}

				var canvas_coords = plot.p2c({x: closest});

				// if closest is outside of canvas, disable crosshair
				if (canvas_coords["left"] < 0 || canvas_coords["left"] > plot.width()) {
					crosshair.x = -1;
				} else {
					crosshair.x = canvas_coords["left"];
				}
				if (crosshair.x != lastX) {
					closest_to_crosshair = closest;
					plot.getPlaceholder().trigger('snapEvent', [ crosshair.x == -1 ? -1 : closest ]);
				}
			}

			plot.triggerRedrawOverlay();
		}


		plot.hooks.bindEvents.push(function (plot, eventHolder) {
			if (!plot.getOptions().crosshair.mode)
				return;

			eventHolder.mouseout(onMouseOut);
			eventHolder.mousemove(onMouseMove);
		});

		plot.hooks.drawOverlay.push(function (plot, ctx) {
			var c = plot.getOptions().crosshair;
			if (!c.mode)
				return;

			var plotOffset = plot.getPlotOffset();

			ctx.save();
			ctx.translate(plotOffset.left, plotOffset.top);

			if (crosshair.x != -1) {
				var adj = plot.getOptions().crosshair.lineWidth % 2 === 0 ? 0 : 0.5;

				ctx.strokeStyle = c.color;
				ctx.lineWidth = c.lineWidth;
				ctx.lineJoin = "round";

				ctx.beginPath();
				if (c.mode.indexOf("x") != -1) {
					var drawX = Math.round(crosshair.x) + adj;
					ctx.moveTo(drawX, 0);
					ctx.lineTo(drawX, plot.height());
					if (plot.getOptions().crosshair.showXValue) {
//						var axis_val = new Date(plot.c2p({left: drawX})["x"]);
						var axis_val = new Date(closest_to_crosshair);

						var time_txt = axis_val.strftime(plot.getOptions().crosshair.xValueFormat);
						var ax_opts = plot.getAxes().xaxis.options;
						var time_txt = formatDate(axis_val, plot.getOptions().crosshair.xValueFormat, ax_opts.monthNames, ax_opts.dayNames)
						var text_width = ctx.measureText(time_txt).width;

						if (drawX + 50 > plot.width()) {
							ctx.textAlign = "right";
							var x_val = drawX - 5
							var rect_x = x_val - text_width - 2
						} else {
							ctx.textAlign = "left";
							var x_val = drawX + 5
							rect_x = x_val - 2
						}
						if (plot.getOptions().crosshair.xValuePos == 'top') {
							var y_val = 11;
							var rect_y = y_val - 10;

						} else {
							var y_val = plot.height() - 5;
							var rect_y = y_val - 10;
						}
						ctx.fillStyle = "white";
						ctx.fillRect(rect_x, rect_y, text_width + 4, 12);
						ctx.fillStyle = "black";
						ctx.fillText(time_txt, x_val, y_val);
					}
				}
				if (c.mode.indexOf("y") != -1) {
					var drawY = Math.round(crosshair.y) + adj;
					ctx.moveTo(0, drawY);
					ctx.lineTo(plot.width(), drawY);
				}
				ctx.stroke();
			}
			ctx.restore();
		});

		plot.hooks.shutdown.push(function (plot, eventHolder) {
			eventHolder.unbind("mouseout", onMouseOut);
			eventHolder.unbind("mousemove", onMouseMove);
		});
	}

	$.plot.plugins.push({
		init: init,
		options: options,
		name: 'crosshair',
		version: '1.0'
	});
})(jQuery);
