/*
The MIT License (MIT)

Copyright (c) 2014 Valerio Neri

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
*/
			

var donutChart = function(chartElementID){
	var centerx = 0;
	var centery = 0;
	var sizex = 0;
	var sizey = 0;
	var scaling = 0;
	var radius = 0;
	var startx = 0;
	var starty = 0;
	var endx = 0;
	var endy = 0;
	var animationSpeed = 1;
	var firedAnimation = false;
	var innerCircleColor = "#666666";
	var outerCircleColor = "#aade87";
	var textColor = "#ffffff";
	var unitText = "%";
	var innerCircleDOM = undefined;
	var outerCircleDOM = undefined;
	var tNumberDOM = undefined;
	var textDOM = undefined;
	var tUnitDOM = undefined;
	var titleDOM = undefined;
	var svg = undefined;
	var chartID = chartElementID;
	var chart = undefined;
	var lastValue = -1;
	var startValue = 0;
	var endValue = 0;
	var loaded = false;
	var that=this;
	var maxValue = 100;
	var titleText ="";
	var titlePosition = "top";
	var titleColor = "#ffffff";
	var textShift = 20; // this is required for moving the chart
	var textScaling = 1;

	// update: unfortunately in some cases the event is fired, and nobody tells us - that's why we just wait for the element to be available
	// wait for the DOM, otherwise you won't find the reference to elements
	//document.addEventListener("DOMContentLoaded", onLoad);
	onLoad();
	
	// this is used for refreshing the animation
	(function() {
		var requestAnimationFrame = window.requestAnimationFrame || window.mozRequestAnimationFrame ||
		window.webkitRequestAnimationFrame || window.msRequestAnimationFrame;
		window.requestAnimationFrame = requestAnimationFrame;
	})();

	this.getValue = function(){
		if (lastValue>-1)
			return Math.round(lastValue/360*maxValue*10)/10;
		else
			return 0;
	}
	
	this.setValue = function(value){
		that.draw({end:value, animationSpeed:0});
	}
	
	function setAnimate(fromD, toD, duration){
		dAct = fromD;
		// determine direction (up or down)
		if (dAct<toD)
			doIt(dAct, toD, duration, "up");
		else
			doIt(dAct, toD, duration, "down");
	}	

	// this function is used for the animation - it uses an exponential acceleration
	function calculateAccValue(xx, m){
		var mxValue = m;
		var x=Math.round(xx);
		if (m==0){
			mxValue=1;
		}
		if (x==0){
			x=1;
		}
		if (Math.abs(x)>=mxValue){
			return mxValue;
		}else{
			//return ( Math.pow( (-2),( (-1*x) + (Math.log(maxValue)/Math.log(2)) ) ) + maxValue);
			var num1 = -2;
			var num2 = Math.round( ((-1*x)+(Math.log(mxValue)/Math.log(2))) );
			return Math.pow(num1 , num2 )+mxValue;
		}
	}

	function textSetter(DOM, text){
		// text scaling
		if ((text>=1000)&&(textScaling=1)){
			textScaling=1-((Math.floor( (Math.log(text)/Math.log(10))-2))/10)-0.1;
			tNumberDOM.setAttribute('font-size', 50*scaling*textScaling);
			tUnitDOM.setAttribute('font-size', 30*scaling*textScaling);
		} else{
			textScaling = 1;
			tNumberDOM.setAttribute('font-size', 50*scaling*textScaling);
			tUnitDOM.setAttribute('font-size', 30*scaling*textScaling);
		}	
		DOM.textContent = text;
	}
	
	function doIt(dAct, toD, duration, direction){
		var toString = "";
		// -0.0001 is used for preventing IE reach 360 and close the circle
		toString = getD(dAct-0.0001);
		outerCircleDOM.setAttribute('d',toString);
		
		//force the value to be an integer
		var anim_cur_val = Math.round(dAct/360*maxValue*10)/10;
		//textSetter(tNumberDOM, Math.round(dAct/360*maxValue*10)/10);
		textSetter(tNumberDOM, anim_cur_val.toFixed(0));
		
		//console.log('dAct '+dAct+ '   toD'+toD);
		// determine direction (up or down)
		if (direction=="up")
			dAct = dAct + 5.5;
		else
			dAct = dAct - 5.5;
			
		duration = duration - calculateAccValue(duration, 500);
		if (direction=="up"){
			if (dAct < toD){
				setTimeout(function(){
					// animate it according to refresh rate
					requestAnimationFrame(function(){
						doIt(dAct, toD, duration, direction);
					})
				},duration);
			} else {
				
				outerCircleDOM.setAttribute('d',getD((toD-0.0001)));
				textSetter(tNumberDOM, Math.round(toD/360*maxValue*10)/10);
			}
		} else {
			if (dAct >= toD){
				setTimeout(function(){
					// animate it according to refresh rate
					requestAnimationFrame(function(){
						doIt(dAct, toD, duration, direction);
					})
				},duration);
			} else {
				outerCircleDOM.setAttribute('d',getD(toD-0.0001));
				textSetter(tNumberDOM ,Math.round(toD/360*maxValue*10)/10);
			}
		}
		
	};

	// this function gives us the circle coordinates
	function getCoordinates(radius,offset,degrees){
		var radians =  degreesToRadians(degrees);
		var x = offset.x + radius * Math.cos(radians)
		var y = offset.y + radius * Math.sin(radians)
		return {x:x,y:y};
	};
	
	// small converstion beween degrees and radians
	function degreesToRadians(degrees){
		var radians = (degrees * Math.PI) / 180;
		return radians
	};

	// this function returns the "d" string for the path, according to the degrees
	function getD(degree){
		radius = 100*scaling;
		startx = centerx+radius;
		starty = centery;
		var coor = getCoordinates(radius, {x:startx,y:starty},degree);
		endx = coor.x;
		endy = coor.y;
		var largearc = 0;
		if (degree>180){
			largearc=1;
		} else {
			largearc=0;
		}
		// don't ask me how I did it folks
		d="M "+startx+" "+starty+" a "+radius+" "+radius+" 0 "+largearc+" 1 "+(endx-startx-radius)+" "+(endy-starty)+" L "+(centerx)+" "+(centery)+" Z";
		//console.log('GETd '+degree+ "    "+d);
		return d;
	}

	// this function checks, if an element is fully visible (according to the scrolling)
	function checkVisible(){
		if (firedAnimation){
			// animation has already fired, detach the event listeners
			document.removeEventListener("scroll", checkVisible, false);
			window.removeEventListener("resize", checkVisible, false);
			return;
		}
		var rect = chart.getBoundingClientRect();
		var top = window.pageYOffset || document.documentElement.scrollTop;
		var left = window.pageXOffset || document.documentElement.scrollLeft;
		if ((rect.top>=0)&&(rect.top+rect.height<window.innerHeight) && 
			(rect.left>=0)&&(rect.left+rect.width<window.innerWidth)){
			// this means that we fully see the chart
			// fire the animation (only once)
			firedAnimation = true;
			setAnimate(startValue, endValue, (500/animationSpeed));					
		}
	}
	
	var waitingLoad =0;
	
	function onLoad(){
		// get the container for the chart
		chart = document.getElementById(chartID);
		
		// if the container is not ready in the DOM, then retry
		// try for around 20secs, then throw an exception
		if (chart==null){
			
			// after 10 seconds, pause a little bit
			if ((waitingLoad > 10000)&&(waitingLoad<20000)){
				setTimeout(onLoad, 1000);
				waitingLoad = waitingLoad +1000;
				return;
			}
			if (waitingLoad > 20000){
				throw('donutChart: onLoad() - The chart element "'+chartID+'" could not be loaded in the last 20 seconds');
			}
			setTimeout(onLoad, 500);
			waitingLoad = waitingLoad +500;
			return;
		}
		// create an svg object and all other things - thanks to Thoka
		svg = document.createElementNS('http://www.w3.org/2000/svg','svg');
		innerCircleDOM = document.createElementNS('http://www.w3.org/2000/svg','circle');
		outerCircleDOM = document.createElementNS('http://www.w3.org/2000/svg','path');
		textDOM = document.createElementNS('http://www.w3.org/2000/svg','text');
		tNumberDOM = document.createElementNS('http://www.w3.org/2000/svg','tspan');
		tUnitDOM = document.createElementNS('http://www.w3.org/2000/svg','tspan');
		titleDOM = document.createElementNS('http://www.w3.org/2000/svg','text');
		
		// append to the chart
		chart.appendChild(svg);
		svg.appendChild(outerCircleDOM);
		svg.appendChild(innerCircleDOM);
		svg.appendChild(textDOM);
		svg.appendChild(titleDOM);
		
		textDOM.appendChild(tNumberDOM);
		textDOM.appendChild(tUnitDOM);
		loaded = true;
	}
	
	this.reload = function(){
		if (!loaded)
			onLoad();
	}
	
	this.clear = function(){
		// reinitialize some parameters
		firedAnimation = false;
		animationSpeed = 1;
		maxValue = 100;
		unitText = "%";
		lastValue = -1;
		this.setValue(0);
	}
	
	this.delete = function(){
		// remove everything
		this.clear();
		textDOM.removeChild(tNumberDOM);
		textDOM.removeChild(tUnitDOM);
		svg.removeChild(textDOM);
		svg.removeChild(titleDOM);
		svg.removeChild(outerCircleDOM);
		svg.removeChild(innerCircleDOM);
		chart.removeChild(svg);
		loaded = false;
		// yes but we leave chart, because it was provided
	}
	
	this.draw = function(options){
		// check if main function has already loaded, if not, reiterate
		if (!loaded){
			setTimeout(function(){
				that.draw(options);
			}, 500);
			return;
		}

		// OPTION CHECK - contains the options
		// check if all options are set
		var oTester = [];
		oTester.options = typeof(options)!="undefined";
		
		if (! (oTester.options)){
			throw('donutChart: draw() - Not enough parameters or no parameters set in object');
			return;
		}
		
		oTester.end = typeof(options.end)!="undefined";
		oTester.start = typeof(options.start)!="undefined";
		oTester.scaling = typeof(options.scaling)!="undefined";
		oTester.size = typeof(options.size)!="undefined";
		oTester.animationSpeed = typeof(options.animationSpeed)!="undefined";
		oTester.textColor = typeof(options.textColor)!="undefined";
		oTester.innerCircleColor = typeof(options.innerCircleColor)!="undefined";
		oTester.outerCircleColor = typeof(options.outerCircleColor)!="undefined";
		oTester.unitText = typeof(options.unitText)!="undefined";
		oTester.maxValue = typeof(options.maxValue)!="undefined";
		oTester.titleText = typeof(options.titleText)!="undefined";
		oTester.titleColor = typeof(options.titleColor)!="undefined";
		oTester.titlePosition = typeof(options.titlePosition)!="undefined";
		
		if (! (oTester.end)){
			throw('donutChart: draw() - No "end" value specified');
			return;
		}
		
		// reinitialize some parameters
		firedAnimation = false;
		animationSpeed = 1;
		maxValue = 100;
		unitText = "%";
		textShift = 20;
		textScaling = 1;
		
		// if the values are not set, set the standard
		if (!oTester.size){
			options.size = 200;
		}
		
		if (!oTester.scaling){
			options.scaling = 1;
		}
		
		// if size ist not set, then take the options.scaling and the standard size of 200x200
		if (!oTester.size){
			svg.setAttribute('width', 200*options.scaling);
			svg.setAttribute('height', 200*options.scaling);
			options.size = 200*options.scaling;
		}
		// if scaling not set, then take the size anc calculate the options.scaling
		if (!oTester.scaling){
			svg.setAttribute('width', options.size);
			svg.setAttribute('height', options.size);
			if (svg.getAttribute('width')<svg.getAttribute('height')){
				options.scaling = svg.getAttribute('width') / 200;
			} else{
				options.scaling = svg.getAttribute('height') / 200;
			}
		}
		
		// set the colors, if set
		if (oTester.outerCircleColor){
			outerCircleColor = options.outerCircleColor;
		}
		if (oTester.innerCircleColor){
			innerCircleColor = options.innerCircleColor;
		}
		if (oTester.textColor){
			textColor = options.textColor;
		}
		
		// set the unitText, if set
		if (oTester.unitText){
			unitText = options.unitText;
		}
		
		// set the title, if set
		if (oTester.titleText){
			titleText = options.titleText;
		}
		if (oTester.titleColor){
			titleColor = options.titleColor;
		}
		if (oTester.titlePosition){
			titlePosition = options.titlePosition;
		}		
		
		
		// set the maxValue, if set
		if (oTester.maxValue){
			maxValue = options.maxValue;
		}

		// set the starting position, if set
		if (oTester.start){
			options.start = Math.round(parseFloat(options.start)*10)/10;
			if (options.start>maxValue)
				options.start = maxValue;
			if (options.start<0)
				options.start = 0;
			if (isNaN(options.start)){
				// not a number and not parseable, ignore
				options.start = 0;
				oTester.start = false;
			}
			startValue = options.start/maxValue*360;
		}
		
		options.end = Math.round(parseFloat(options.end)*10)/10;

		if (options.end>maxValue)
			options.end = maxValue;
		if (options.end<0)
			options.end = 0;
		
		if (isNaN(options.end)){
			// not a number and not parseable, ignore
			return;
		}
		
		options.end = options.end/maxValue*360;
		endValue = options.end;
		
		textShift = textShift*options.scaling;
		
		// set the size and the center
		sizex = options.size;
		sizey = options.size+2*textShift;
		centerx = sizex/2;
		centery = (sizey/2);
		scaling = options.scaling;
		
		// set animation speed (0 = no animation), standard is one
		if(oTester.animationSpeed){
			animationSpeed = options.animationSpeed;
		}
		
		//e4j
		var innerCircleStroke = typeof(options.innerCircleStroke)!="undefined" ? options.innerCircleStroke : 'none';
		var outerCircleStroke = typeof(options.outerCircleStroke)!="undefined" ? options.outerCircleStroke : 'none';
		//
		
		// initialise with start position
		outerCircleDOM.setAttribute('d', getD(startValue));
		
		chart.style.width = sizex;
		chart.style.height = sizey;
		
		svg.setAttribute('height', sizey);
		svg.setAttribute('class', 'donutChart');
		svg.setAttribute('xmlns', 'http://www.w3.org/2000/svg');
		svg.setAttribute('version', '1.1');

		innerCircleDOM.setAttribute('cx', centerx);
		innerCircleDOM.setAttribute('cy', centery);
		innerCircleDOM.setAttribute('r', 80*options.scaling);
		innerCircleDOM.setAttribute('stroke', innerCircleStroke);
		innerCircleDOM.setAttribute('stroke-width', '2');
		innerCircleDOM.setAttribute('fill', innerCircleColor);

		outerCircleDOM.setAttribute('fill', outerCircleColor);
		outerCircleDOM.setAttribute('stroke', outerCircleStroke);
		outerCircleDOM.setAttribute('stroke-width', '36');
		
		tNumberDOM.setAttribute('x', centerx);
		tNumberDOM.setAttribute('y', centery+Math.round(18.75*options.scaling));
		tNumberDOM.setAttribute('font-size', 50*options.scaling);
		tNumberDOM.setAttribute('fill', textColor);
		tNumberDOM.setAttribute('font-family', 'arial');
		tNumberDOM.setAttribute('font-weight', 'normal');
		tNumberDOM.setAttribute('text-anchor', 'middle');
		
		tUnitDOM.setAttribute('font-size', 30*options.scaling);
		tUnitDOM.textContent = unitText;
		tUnitDOM.setAttribute('fill', textColor);
		tUnitDOM.setAttribute('text-anchor', 'middle');
		tUnitDOM.setAttribute('font-family', 'arial');
		tUnitDOM.setAttribute('font-weight', 'normal');
		
		titleDOM.setAttribute('x', centerx);
		// set the position of the title
		switch(titlePosition){
			case "inner-bottom":
				if (!oTester.titleColor)
					titleColor = "#ffffff";
				titleDOM.setAttribute('y', centery+sizey/5+textShift/2);
				break;

				case "outer-bottom":
				if (!oTester.titleColor)
					titleColor = "#666666";
				
				titleDOM.setAttribute('y', sizey-textShift/2);
				break;

			case "inner-top":
				if (!oTester.titleColor)
					titleColor = "#ffffff";
				titleDOM.setAttribute('y', centery-sizey/5-textShift/2);
				break;

				default:
			case "outer-top":
				if (!oTester.titleColor)
					titleColor = "#666666";
				
				titleDOM.setAttribute('y', textShift/2);
				break;
		}
		
		titleDOM.setAttribute('font-size', 12*options.scaling);
		titleDOM.setAttribute('fill', titleColor);
		titleDOM.setAttribute('font-family', 'arial');
		titleDOM.setAttribute('font-weight', 'normal');
		titleDOM.setAttribute('text-anchor', 'middle');
		titleDOM.textContent = titleText;
		
		
		// if the animation has been chosen
		if (animationSpeed>0){
			// ignore start value if lastvalue is already set
			if (lastValue>-1){
				startValue = lastValue;
			}
			window.addEventListener("resize", checkVisible, false);
			document.addEventListener("scroll", checkVisible, false);
			tNumberDOM.textContent = Math.round(startValue/360*maxValue*10)/10;
			checkVisible();
		} else {
			// set the circle to the end position, without animation
			// the 0.0001 is for avoiding the circle to disappear, due to some browser
			outerCircleDOM.setAttribute('d', getD(options.end-0.0001));
			tNumberDOM.textContent = Math.round(options.end/360*maxValue*10)/10;
		}
		lastValue = endValue;
	}
}
