/**
 * JustGage - a handy JavaScript plugin for generating and animating nice & clean dashboard gauges.
 * Copyright (c) 2012 Bojan Djuricic - pindjur(at)gmail(dot)com | http://www.madcog.com
 * Licensed under MIT.
 * Date: 31/07/2012
 * @author Bojan Djuricic  (@Toorshia)
 * @version 1.0
 *
 * http://www.justgage.com
 */

JustGage = function(config)
{

	if(!config.id)
	{
		alert("Missing id parameter for gauge!");
		return false;
	}
	if(!document.getElementById(config.id))
	{
		alert("No element with id: \"" + config.id + "\" found!");
		return false;
	}

	// configurable parameters
	this.config = {
		// id : string
		// this is container element id
		id                  : config.id,

		// value : int
		// value gauge is showing
		value               : (config.value) ? config.value : 0,

		// valueFontColor : string
		// color of label showing current value
		valueFontColor      : (config.valueFontColor) ? config.valueFontColor : "#010101",

		// min : int
		// min value
		min                 : (config.min) ? config.min : 0,

		// max : int
		// max value
		max                 : (config.max) ? config.max : 100,

		// showMinMax : bool
		// hide or display min and max values
		showMinMax          : (config.showMinMax != null) ? config.showMinMax : true,

		// gaugeWidthScale : float
		// width of the gauge element
		gaugeWidthScale     : (config.gaugeWidthScale) ? config.gaugeWidthScale : 1.0,

		// gaugeColor : string
		// background color of gauge element
		gaugeColor          : (config.gaugeColor) ? config.gaugeColor : "#edebeb",

		// label : string
		// text to show below value
		label               : (config.label) ? config.label : "",

		// showInnerShadow : bool
		// give gauge element small amount of inner shadow
		showInnerShadow     : (config.showInnerShadow != null) ? config.showInnerShadow : true,

		// shadowOpacity : int
		// 0 ~ 1
		shadowOpacity       : (config.shadowOpacity) ? config.shadowOpacity : 0.2,

		// shadowSize: int
		// inner shadow size
		shadowSize          : (config.shadowSize) ? config.shadowSize : 5,

		// shadowVerticalOffset : int
		// how much shadow is offset from top
		shadowVerticalOffset: (config.shadowVerticalOffset) ? config.shadowVerticalOffset : 3,

		// levelColors : string[]
		// colors of indicator, from lower to upper, in RGB format
		levelColors         : (config.levelColors) ? config.levelColors : percentColors,

		// levelColorsGradient : bool
		// whether to use gradual color change for value, or sector-based
		levelColorsGradient : (config.levelColorsGradient != null) ? config.levelColorsGradient : true,

		// labelFontColor : string
		// color of label showing label under value
		labelFontColor      : (config.labelFontColor) ? config.labelFontColor : "#b3b3b3",

		// startAnimationTime : int
		// length of initial animation
		startAnimationTime  : (config.startAnimationTime) ? config.startAnimationTime : 700,

		// startAnimationType : string
		// type of initial animation (linear, >, <,  <>, bounce)
		startAnimationType  : (config.startAnimationType) ? config.startAnimationType : ">",

		// refreshAnimationTime : int
		// length of refresh animation
		refreshAnimationTime: (config.refreshAnimationTime) ? config.refreshAnimationTime : 700,

		// refreshAnimationType : string
		// type of refresh animation (linear, >, <,  <>, bounce)
		refreshAnimationType: (config.refreshAnimationType) ? config.refreshAnimationType : ">"
	};

	// overflow values
	if(config.value > this.config.max) this.config.value = this.config.max;
	if(config.value < this.config.min) this.config.value = this.config.min;
	this.originalValue = config.value;

	// canvas
	this.canvas = Raphael(this.config.id, "100%", "100%");

	// canvas dimensions
	//var canvasW = document.getElementById(this.config.id).clientWidth;
	//var canvasH = document.getElementById(this.config.id).clientHeight;
	var canvasW = getStyle(document.getElementById(this.config.id), "width").slice(0, -2) * 1;
	var canvasH = getStyle(document.getElementById(this.config.id), "height").slice(0, -2) * 1;

	// widget dimensions
	var widgetW, widgetH;
	if((canvasW / canvasH) > 1.25)
	{
		widgetW = 1.25 * canvasH;
		widgetH = canvasH;
	}
	else
	{
		widgetW = canvasW;
		widgetH = canvasW / 1.25;
	}

	// delta
	var dx = (canvasW - widgetW) / 2;
	var dy = (canvasH - widgetH) / 2;

	// value
	var valueFontSize = ((widgetH / 6.4) > 16) ? (widgetH / 6.4) : 16;
	var valueX = dx + widgetW / 2;
	var valueY = dy + widgetH / 1.4;

	// label
	var labelFontSize = ((widgetH / 16) > 10) ? (widgetH / 16) : 10;
	var labelX = dx + widgetW / 2;
	//var labelY = dy + widgetH / 1.126760563380282;
	var labelY = valueY + valueFontSize / 2 + 6;

	// min
	var minFontSize = ((widgetH / 16) > 10) ? (widgetH / 16) : 10;
	var minX = dx + (widgetW / 10) + (widgetW / 6.666666666666667 * this.config.gaugeWidthScale) / 2;
	var minY = dy + widgetH / 1.126760563380282;

	// max
	var maxFontSize = ((widgetH / 16) > 10) ? (widgetH / 16) : 10;
	var maxX = dx + widgetW - (widgetW / 10) - (widgetW / 6.666666666666667 * this.config.gaugeWidthScale) / 2;
	var maxY = dy + widgetH / 1.126760563380282;

	// parameters
	this.params = {
		canvasW      : canvasW,
		canvasH      : canvasH,
		widgetW      : widgetW,
		widgetH      : widgetH,
		dx           : dx,
		dy           : dy,
		valueFontSize: valueFontSize,
		valueX       : valueX,
		valueY       : valueY,
		labelFontSize: labelFontSize,
		labelX       : labelX,
		labelY       : labelY,
		minFontSize  : minFontSize,
		minX         : minX,
		minY         : minY,
		maxFontSize  : maxFontSize,
		maxX         : maxX,
		maxY         : maxY
	};

	// pki - custom attribute for generating gauge paths
	this.canvas.customAttributes.pki = function(value, min, max, w, h, dx, dy, gws)
	{

		var alpha = (1 - (value - min) / (max - min)) * Math.PI , Ro = w / 2 - w / 10, Ri = Ro - w / 6.666666666666667 * gws,

			Cx = w / 2 + dx, Cy = h / 1.25 + dy,

			Xo = w / 2 + dx + Ro * Math.cos(alpha), Yo = h - (h - Cy) + dy - Ro * Math.sin(alpha), Xi = w / 2 + dx + Ri * Math.cos(alpha), Yi = h - (h - Cy) + dy - Ri * Math.sin(alpha), path;

		path += "M" + (Cx - Ri) + "," + Cy + " ";
		path += "L" + (Cx - Ro) + "," + Cy + " ";
		path += "A" + Ro + "," + Ro + " 0 0,1 " + Xo + "," + Yo + " ";
		path += "L" + Xi + "," + Yi + " ";
		path += "A" + Ri + "," + Ri + " 0 0,0 " + (Cx - Ri) + "," + Cy + " ";
		path += "z ";
		return { path: path };
	}

	// gauge
	this.gauge = this.canvas.path().attr({
		"stroke": "none",
		"fill"  : this.config.gaugeColor,
		pki     : [this.config.max, this.config.min, this.config.max, this.params.widgetW, this.params.widgetH,
		           this.params.dx, this.params.dy, this.config.gaugeWidthScale]
	});
	this.gauge.id = this.config.id + "-gauge";

	// level
	this.level = this.canvas.path().attr({
		"stroke": "none",
		"fill"  : getColorForPercentage((this.config.value - this.config.min) / (this.config.max - this.config.min), this.config.levelColors, this.config.levelColorsGradient),
		pki     : [this.config.min, this.config.min, this.config.max, this.params.widgetW, this.params.widgetH,
		           this.params.dx, this.params.dy, this.config.gaugeWidthScale]
	});
	this.level.id = this.config.id + "-level";

	// value
	this.txtValue = this.canvas.text(this.params.valueX, this.params.valueY, this.originalValue);
	this.txtValue.attr({
		"font-size"   : this.params.valueFontSize,
		"font-weight" : "bold",
		"font-family" : "Arial",
		"fill"        : this.config.valueFontColor,
		"fill-opacity": "0"
	});
	this.txtValue.id = this.config.id + "-txtvalue";

	// label
	this.txtLabel = this.canvas.text(this.params.labelX, this.params.labelY, this.config.label);
	this.txtLabel.attr({
		"font-size"   : this.params.labelFontSize,
		"font-weight" : "normal",
		"font-family" : "Arial",
		"fill"        : this.config.labelFontColor,
		"fill-opacity": "0"
	});
	this.txtLabel.id = this.config.id + "-txtlabel";

	// min
	this.txtMin = this.canvas.text(this.params.minX, this.params.minY, this.config.min);
	this.txtMin.attr({
		"font-size"   : this.params.minFontSize,
		"font-weight" : "normal",
		"font-family" : "Arial",
		"fill"        : this.config.labelFontColor,
		"fill-opacity": (this.config.showMinMax == true) ? "1" : "0"
	});
	this.txtMin.id = this.config.id + "-txtmin";

	// max
	this.txtMax = this.canvas.text(this.params.maxX, this.params.maxY, this.config.max);
	this.txtMax.attr({
		"font-size"   : this.params.maxFontSize,
		"font-weight" : "normal",
		"font-family" : "Arial",
		"fill"        : this.config.labelFontColor,
		"fill-opacity": (this.config.showMinMax == true) ? "1" : "0"
	});
	this.txtMax.id = this.config.id + "-txtmax";

	var defs = this.canvas.canvas.childNodes[1];
	var svg = "http://www.w3.org/2000/svg";


	if(ie < 9)
	{
		onCreateElementNsReady(function()
		{
			this.generateShadow();
		});
	}
	else
	{
		this.generateShadow(svg, defs);
	}

	// animate
	this.level.animate({pki: [this.config.value, this.config.min, this.config.max, this.params.widgetW,
	                          this.params.widgetH, this.params.dx, this.params.dy, this.config.gaugeWidthScale
	]}, this.config.startAnimationTime, this.config.startAnimationType);

	this.txtValue.animate({"fill-opacity": "1"}, this.config.startAnimationTime, this.config.startAnimationType);
	this.txtLabel.animate({"fill-opacity": "1"}, this.config.startAnimationTime, this.config.startAnimationType);
};

// refresh gauge level
JustGage.prototype.refresh = function(val)
{
	// overflow values
	originalVal = val;
	if(val > this.config.max)
	{
		val = this.config.max;
	}
	if(val < this.config.min)
	{
		val = this.config.min;
	}

	var color = getColorForPercentage((val - this.config.min) / (this.config.max - this.config.min), this.config.levelColors, this.config.levelColorsGradient);
	this.canvas.getById(this.config.id + "-txtvalue").attr({"text": originalVal});
	this.canvas.getById(this.config.id + "-level").animate({pki                                       : [val,
	                                                                                                     this.config.min,
	                                                                                                     this.config.max,
	                                                                                                     this.params.widgetW,
	                                                                                                     this.params.widgetH,
	                                                                                                     this.params.dx,
	                                                                                                     this.params.dy,
	                                                                                                     this.config.gaugeWidthScale
	], "fill"                                                                                         : color}, this.config.refreshAnimationTime, this.config.refreshAnimationType);
};

var percentColors = [
	"#a9d70b", "#f9c802", "#ff0000"
]

JustGage.prototype.generateShadow = function(svg, defs)
{
	// FILTER
	var gaussFilter = document.createElementNS(svg, "filter");
	gaussFilter.setAttribute("id", this.config.id + "-inner-shadow");
	defs.appendChild(gaussFilter);

	// offset
	var feOffset = document.createElementNS(svg, "feOffset");
	feOffset.setAttribute("dx", 0);
	feOffset.setAttribute("dy", this.config.shadowVerticalOffset);
	gaussFilter.appendChild(feOffset);

	// blur
	var feGaussianBlur = document.createElementNS(svg, "feGaussianBlur");
	feGaussianBlur.setAttribute("result", "offset-blur");
	feGaussianBlur.setAttribute("stdDeviation", this.config.shadowSize);
	gaussFilter.appendChild(feGaussianBlur);

	// composite 1
	var feComposite1 = document.createElementNS(svg, "feComposite");
	feComposite1.setAttribute("operator", "out");
	feComposite1.setAttribute("in", "SourceGraphic");
	feComposite1.setAttribute("in2", "offset-blur");
	feComposite1.setAttribute("result", "inverse");
	gaussFilter.appendChild(feComposite1);

	// flood
	var feFlood = document.createElementNS(svg, "feFlood");
	feFlood.setAttribute("flood-color", "black");
	feFlood.setAttribute("flood-opacity", this.config.shadowOpacity);
	feFlood.setAttribute("result", "color");
	gaussFilter.appendChild(feFlood);

	// composite 2
	var feComposite2 = document.createElementNS(svg, "feComposite");
	feComposite2.setAttribute("operator", "in");
	feComposite2.setAttribute("in", "color");
	feComposite2.setAttribute("in2", "inverse");
	feComposite2.setAttribute("result", "shadow");
	gaussFilter.appendChild(feComposite2);

	// composite 3
	var feComposite3 = document.createElementNS(svg, "feComposite");
	feComposite3.setAttribute("operator", "over");
	feComposite3.setAttribute("in", "shadow");
	feComposite3.setAttribute("in2", "SourceGraphic");
	gaussFilter.appendChild(feComposite3);

	// set shadow
	if(this.config.showInnerShadow == true)
	{
		this.canvas.canvas.childNodes[2].setAttribute("filter", "url(#" + this.config.id + "-inner-shadow)");
		this.canvas.canvas.childNodes[3].setAttribute("filter", "url(#" + this.config.id + "-inner-shadow)");
	}
}

var getColorForPercentage = function(pct, col, grad)
{

	var no = col.length;
	if(no === 1) return col[0];
	var inc = (grad) ? (1 / (no - 1)) : (1 / no);
	var colors = new Array();
	for(var i = 0; i < col.length; i++)
	{
		var percentage = (grad) ? (inc * i) : (inc * (i + 1));
		var rval = parseInt((cutHex(col[i])).substring(0, 2), 16);
		var gval = parseInt((cutHex(col[i])).substring(2, 4), 16);
		var bval = parseInt((cutHex(col[i])).substring(4, 6), 16);
		colors[i] = { pct: percentage, color: { r: rval, g: gval, b: bval  } };
	}

	if(pct == 0) return 'rgb(' + [colors[0].color.r, colors[0].color.g, colors[0].color.b].join(',') + ')';
	for(var i = 0; i < colors.length; i++)
	{
		if(pct <= colors[i].pct)
		{
			if(grad == true)
			{
				var lower = colors[i - 1];
				var upper = colors[i];
				var range = upper.pct - lower.pct;
				var rangePct = (pct - lower.pct) / range;
				var pctLower = 1 - rangePct;
				var pctUpper = rangePct;
				var color = {
					r: Math.floor(lower.color.r * pctLower + upper.color.r * pctUpper),
					g: Math.floor(lower.color.g * pctLower + upper.color.g * pctUpper),
					b: Math.floor(lower.color.b * pctLower + upper.color.b * pctUpper)
				};
				return 'rgb(' + [color.r, color.g, color.b].join(',') + ')';
			}
			else
			{
				return 'rgb(' + [colors[i].color.r, colors[i].color.g, colors[i].color.b].join(',') + ')';
			}
		}
	}
}

function getRandomInt(min, max)
{
	return Math.floor(Math.random() * (max - min + 1)) + min;
}

function cutHex(str)
{
	return (str.charAt(0) == "#") ? str.substring(1, 7) : str
}

function getStyle(oElm, strCssRule)
{
	var strValue = "";
	if(document.defaultView && document.defaultView.getComputedStyle)
	{
		strValue = document.defaultView.getComputedStyle(oElm, "").getPropertyValue(strCssRule);
	}
	else if(oElm.currentStyle)
	{
		strCssRule = strCssRule.replace(/\-(\w)/g, function(strMatch, p1)
		{
			return p1.toUpperCase();
		});
		strValue = oElm.currentStyle[strCssRule];
	}
	return strValue;
}

function onCreateElementNsReady(func)
{
	if(document.createElementNS != undefined)
	{
		func();
	}
	else
	{
		setTimeout(function()
		{
			onCreateElementNsReady(func);
		}, 100);
	}
}

// ----------------------------------------------------------
// A short snippet for detecting versions of IE in JavaScript
// without resorting to user-agent sniffing
// ----------------------------------------------------------
// If you're not in IE (or IE version is less than 5) then:
// ie === undefined
// If you're in IE (>=5) then you can determine which version:
// ie === 7; // IE7
// Thus, to detect IE:
// if (ie) {}
// And to detect the version:
// ie === 6 // IE6
// ie > 7 // IE8, IE9 ...
// ie < 9 // Anything less than IE9
// ----------------------------------------------------------

// UPDATE: Now using Live NodeList idea from @jdalton

var ie = (function()
{

	var undef, v = 3, div = document.createElement('div'), all = div.getElementsByTagName('i');

	while(div.innerHTML = '<!--[if gt IE ' + (++v) + ']><i></i><![endif]-->', all[0]);

	return v > 4 ? v : undef;

}());