angular.module('quasardbGUIApp.directives', []).

// Use cluster data template
directive('clusterData', function() {
	return {
		templateUrl:'templates/cluster-data.html'
	};
}).

// Use cluster chart template
directive('clusterCharts', function() {
	return {
		templateUrl:'templates/cluster-charts.html'
	};
}).

// Use node data template
directive('nodeData', function() {
	return {
		templateUrl:'templates/node-data.html'
	};
}).

// Use node chart template
directive('nodeCharts', function() {
	return {
		templateUrl:'templates/node-charts.html'
	};
}).

// Use node operation template
directive('nodeOperations', function() {
	return {
		templateUrl:'templates/node-operations.html'
	};
}).

// Use node operation template
directive('nodeSessions', function() {
	return {
		templateUrl:'templates/node-sessions.html'
	};
}).

// Use cluster chart template
directive('chart', ['$injector', '$log', 'quasardbSettings', function($injector, $log, quasardbSettings) {
	// Default graph palette
	var palette = new Rickshaw.Color.Palette({ scheme: 'cool' });
	var selectedX = 0;

	// Add riskpoint to provided graph
	function createRiskPoints(graph, maxRiskPoints, maxHeight) {
		$log.debug('rickshawChart.createRiskPoints(graph, maxRiskPoints, maxHeight) -> ', graph, maxRiskPoints, maxHeight);
		if (graph === undefined) {
			return;
		}
		
		var yScale = graph.series[0].scale;
		var riskpoints = [maxRiskPoints];
		graph.vis.insert("svg:rect", ":first-child").
		    attr("x", 0).
		    attr("y", yScale(riskpoints[0])).
		    attr("height", 1).
		    attr("width", '100%').
		    attr("fill", "rgba(100, 0, 0, 1)");
	}
	
	// Directive implementation
	return {
		scope: {
        	data: '='
        },
		templateUrl : 'partials/graph.html',
		restrict : 'E',
		link : function postLink(scope, element, attrs) {
			// Initialize an empty graph
			var graph = null;
			
			// Build current history shortcuts
			scope.ranges = [];
			scope.selectedRange = 1;
			angular.copy(quasardbSettings.ranges, scope.ranges);
			
			// History launch handler
			scope.launch = function(idxRange, action) {
				if (scope.selectedRange == idxRange) {
					return;
				}
				scope.selectedRange = idxRange;
				$log.debug('chart directive => ', idxRange, action, scope);
				
				// Handle CSS for selected period
				angular.forEach(scope.ranges, function(range, idx) {
					if (idx != idxRange) {
						range.selected = '';
					} else {
						range.selected = 'selected';
					}
				});
				
				// Handle action
				scope.launchAction(action);
			};
			
			// Call defined action function
			scope.launchAction = function(action) {
				if (action == null) {
					return;
				}
				
				var functionToCall = '$injector.get("shortcuts").' + action.name + '(';
				angular.forEach(action.args, function(arg, idx) {
					if (idx > 0) {
						functionToCall += ', ' ;
					}
					try {
						eval(arg);
						functionToCall += 'eval(action.args[' + idx + '])';
					} catch (e) {
						functionToCall += 'action.args[' + idx + ']';
					}
				});
				functionToCall += ');';
				try {
					eval(functionToCall);	
				} catch (e) {
					$log.debug('chart : callAction() => ', functionToCall, e);
				}
			};
			
			// Watch incoming data and refresh graph
			scope.$watch('data', function(newVal, oldVal) {
				$log.debug("chart $watch(data) => ", newVal, oldVal, " - scope => ", scope, " - graph => ", graph, " - attrs => ", attrs);

				if (!newVal) {
					return;
				}

				var series = [];
				angular.forEach(newVal, function(value, idx) {
  					var graphData = eval('scope.$parent.' + value);
					if (graphData !== undefined) {
						if (graph === null) {
							// Prepare current graph serie
							var serie = {
								name			: attrs.name ? JSON.parse(attrs.name)[idx] : scope.name,
								color			: attrs.color ? JSON.parse(attrs.color)[idx] : palette.color(),
								stroke			: attrs.color ? JSON.parse(attrs.color)[idx] : palette.color(),
								dotted			: attrs.dotted ? attrs.dotted : '',
								renderer		: attrs.renderer ? JSON.parse(attrs.renderer)[idx] : 'area',
								maxDataPoints	: quasardbSettings.maxPoints,
								data			: graphData.sort(function(a,b) {
														var c = new Date(a.x);
														var d = new Date(b.x);
														return c-d;
												  })
							};
							
							// Setting max to series
							var max = attrs.max ? JSON.parse(attrs.max)[idx] : undefined;
							$log.debug('chart $watch(data) => max for ', value, ' is ', max);
							if (max !== undefined) {
								var logScale = d3.scale.log().domain([0, max]);
								//var linearScale = d3.scale.linear().domain([0, max]).range(logScale.range());
								serie.scale = d3.scale.linear().domain([0, max]).range(logScale.range());
							}
							
							// Update series with computed values
							series.push(serie);
							$log.debug('chart $watch(data) => graph is null - Data for ', value, idx, ' => ', series);
						} else {
							// Store new data to an empty array in order to update graph
							series.push(graphData[graphData.length - 1]);
							$log.debug('chart $watch(data) => graph exists - Data : ', graphData);
						}
					}
				});
				$log.debug('chart : $watch - building series for [' + newVal + '] => ', series, graph, attrs);
				
				if ((graph === null) && (series.length != 0)) {
					$log.debug('chart : creating graphs => ', series);
					
					// Instantiate Rickshaw graph with previous computed series
					graph = new Rickshaw.Graph({
						unstack: true,
						element : element[0].querySelector('#graph'),
						width : attrs.width,
						height : attrs.height,
						interpolation : 'linear',
						padding: {
							top: 0.05, 
							left: 0, 
							right: 0.01, 
							bottom: 0
						},
						stroke: attrs.stroke || true,
						preserve: attrs.preserve || true,
						renderer : (newVal.length > 1) ? 'multi' : JSON.parse(attrs.renderer)[0],
						series : series
					});
					graph.renderer.unstack = true;
					graph.render();
					
					// Set up X Axis
					var ticksTreatment = 'glow';
					var xAxis = new Rickshaw.Graph.Axis.Time({
						graph: graph,
						ticksTreatment: ticksTreatment,
						timeFixture: new Rickshaw.Fixtures.Time.Local()
					});
					xAxis.render();
					
					// Set up Y Axis
					angular.forEach(JSON.parse(attrs.formatnumber), function(value, idx) {
						var formatFunction = "Rickshaw.Fixtures.Number.format" + value;
						var max = attrs.max ? JSON.parse(attrs.max)[idx] : undefined;
						$log.debug('chart - building graph => max for ', value, ' is ', max, ' - scales ', series[idx].scale);
						if (max !== undefined) {
							new Rickshaw.Graph.Axis.Y.Scaled({
								graph: graph,
								orientation: (idx == 0) ? 'right' : 'left',
								tickFormat: eval(formatFunction) || Rickshaw.Fixtures.Number.formatRAM,
								ticksTreatment: ticksTreatment,
								grid: false,
								scale: series[idx].scale,
								element: element[0].querySelector('#y_axis_' + (idx + 1))
							});
						} else {
							new Rickshaw.Graph.Axis.Y({
								graph: graph,
								orientation: (idx == 0) ? 'right' : 'left',
								tickFormat: eval(formatFunction) || Rickshaw.Fixtures.Number.formatRAM,
								ticksTreatment: ticksTreatment,
								element: element[0].querySelector('#y_axis_' + (idx + 1))
							});
						}
					});

					// Set up preview sub-chart
					var preview = new Rickshaw.Graph.RangeSlider.Preview({
						graph: graph,
						element: element[0].querySelector('#preview')
					});
					var previewXAxis = new Rickshaw.Graph.Axis.Time({
						graph: preview.previews[0],
						timeFixture: new Rickshaw.Fixtures.Time.Local(),
						ticksTreatment: ticksTreatment
					});
					previewXAxis.render();
					
					// Set up chart 'on-browse popups'
					new Rickshaw.Graph.HoverDetail({
						graph : graph,
						formatter: function(series, x, y, formattedX, formattedY, d) {
							$log.debug('chart onHover() -> ', series, x, y, formattedX, formattedY, d);
							if ((selectedX === 0) || (selectedX !== x)) {
								selectedX = x;
							}
							var date = '<span class="date">' + new Date(x * 1000) + '</span>';
							var swatch = '<span class="detail_swatch" style="background-color: ' + series.color + '"></span>';
							var yvalue = parseFloat(y);
							angular.forEach(JSON.parse(attrs.formatnumber), function(value) {
								yvalue = eval("Rickshaw.Fixtures.Number.format" + value + "(" + parseFloat(y) + ")");
							});
							return swatch + series.name + ': ' + yvalue + '<br>' + date;
						},
						onHide: function() {
							// TODO handle browsing graph function
							$log.debug('chart onHide() => ', new Date(selectedX * 1000).format('yyyymmddHHMM'));
						},
						onShow: function() {
							// TODO handle browsing graph function
							$log.debug('chart onShow() => ', new Date(selectedX * 1000).format('yyyymmddHHMM'));
						}
					});
	
					// Set up event timeline
					new Rickshaw.Graph.Annotate({
						graph: graph,
						element: element[0].querySelector('#timeline')
					});
					
					// Add limiter if required
					if (attrs.showlimiter !== 'never') {
						$log.debug('chart $watch(data) => show limiter is true, max is ', JSON.parse(attrs.max)[0], ' -> ', graph.y.domain());
						if (attrs.max) {
							var maxLimiter = JSON.parse(attrs.max)[0];
							var maxYDomain = series[0].scale.invert(graph.y.domain()[1]);
							$log.debug('chart $watch(data) => show limiter : max ', maxLimiter, ' - maxDomain', maxYDomain);
							
							if (maxYDomain > (maxLimiter * quasardbSettings.trigger / 100)) {
								scope.$parent.chart.show_limiter = 'true';
								graph.onUpdate(function() {
									createRiskPoints(graph, maxLimiter, attrs.height);
								});
							} else {
								scope.$parent.chart.show_limiter = 'false';
								graph.onUpdate(function() {});
							}
							
							$log.debug('chart $watch(data) => show limiter - alert must be shown !', graph);
						}
					}
					
					// Render chart
					graph.update();
				} else {
					$log.debug('chart $watch(data) => graph exist => ', newVal, series, scope.selectedRange);
					
					// Update graph data for each axes
					angular.forEach(newVal, function(serie, idx) {
						if (graph.series[idx].data.length === quasardbSettings.maxPoints) {
							graph.series[idx].data.shift();
						}
						graph.series[idx].data.push(series[idx]);
					
						// Update preview windows for selected period
						if (scope.selectedRange == 0) {
							graph.window.xMin = (graph.dataDomain()[0] > (graph.dataDomain()[1] - 4*60*60*1000))?graph.dataDomain()[0]:(graph.dataDomain()[1] - 4*60*60*1000);
							graph.window.xMax = graph.dataDomain()[1];
						} else if (scope.selectedRange == 1) {
							graph.window.xMin = (graph.dataDomain()[0] > (graph.dataDomain()[1] - 48*60*60*1000))?graph.dataDomain()[0]:(graph.dataDomain()[1] - 48*60*60*1000);
							graph.window.xMax = graph.dataDomain()[1];
						}
					});
					
					// Add limiter if required
					if (attrs.showlimiter === 'true') {
						$log.debug('chart $watch(data) => graph exists and show limiter is true, max is ', JSON.parse(attrs.max)[0], ' -> ', graph.y.domain(), series);
						if (attrs.max) {
							var max = JSON.parse(attrs.max)[0];
							$log.debug('chart $watch(data) => graph exists and show limiter : max = ', max, ' - maxDomainY = ', graph.series[0].scale.invert(graph.y.domain()[1]));
							
							if (graph.series[0].scale.invert(graph.y.domain()[1]) > (max * quasardbSettings.trigger / 100)) {
								graph.onUpdate(function() {
									createRiskPoints(graph, max, attrs.height);
								});
							} else {
								graph.onUpdate(function() {});
							}
							
							$log.debug('chart $watch(data) => graph exists and show limiter - alert must be shown !', graph);
						}
					}
					
					// Refresh graph
					graph.update();
				}
				
				// Fix case 880
    			if (graph !== null) {
					scope.launchAction(scope.ranges[scope.selectedRange].action);
				}
			}); 
		}
	};
}]).

// Use cluster view template
directive('clusterView', ['$log', '$interval', 'quasardbSettings', function($log, $interval, quasardbSettings) {
	return {
		restrict: 'E',
		link: function (scope, element, attrs) {
			$log.debug('clusterView directive : link function was called => ', scope, element, attrs);

			// Add a loading data animation
			var spinner = new Spinner(quasardbSettings.loaderOptions).spin(element[0].querySelector('#nodeRing'));
			
			// Stop refresh on destroy
			element.on('$destroy', function () {
				$interval.cancel(quasardbSettings.refresh);
				spinner.stop();
			});
			
			// Stop loading data animation
			scope.$root.$watch('status', function(newVal) {
				if (!newVal) {
					return;
				}
				spinner.stop();
			});
			
		},
		templateUrl:'templates/cluster-view.html'
	};
}]).

// Directive tool : get top and left position of nodes in cluster view
directive('getsize', ['$log', function($log) {
	// Crossbrowser offset function
	function offset(elm) {
		try {
			return elm.offset();
		} catch(e) {
		}
		var rawDom = elm[0];
		var body = document.documentElement || document.body;
		var scrollX = window.pageXOffset || body.scrollLeft;
		var scrollY = window.pageYOffset || body.scrollTop;
        var _x = rawDom.getBoundingClientRect().left + scrollX;
        var _y = rawDom.getBoundingClientRect().top + scrollY;
		$log.debug('getsize directive : offset function was called => left : ' + _x + ' - top : ' + _y);
		return {
			left : _x,
			top : _y
		};
	}

	function XY(o) {
		var z=o, x=0,y=0, c; 
		while(z && !isNaN(z.offsetLeft) && !isNaN(z.offsetTop)) {        
			c = isNaN(window.globalStorage)?0:window.getComputedStyle(z,null); 
			x += z.offsetLeft-z.scrollLeft+(c?parseInt(c.getPropertyValue('border-left-width'),10):0);
			y += z.offsetTop-z.scrollTop+(c?parseInt(c.getPropertyValue('border-top-width'),10):0);
			z = z.offsetParent;
		} 
		return {
			x:o.X=x,
			y:o.Y=y
		};
	}

	return function (scope, element) {
		scope.getElementDimensions = function () {
			return { 
				'h': element[0].getBoundingClientRect().height, 
				'w': element[0].getBoundingClientRect().width 
			};
		};
		
		// Listen the following event : windows is resized
		scope.$watch(scope.getElementDimensions, function () {
			$log.debug('getsize directive : resize was called => ', element, offset(element), XY(element[0]));
           	scope.top = offset(element).top - XY(element[0]).y - 32;
           	scope.left = offset(element).left - XY(element[0]).x + 130;
		}, true);
	
		element.bind('resize', function () {
			scope.$apply();
		});
	};
}]).

// Use cluster tab template
directive('clusterTab', function() {
	return {
		templateUrl:'templates/cluster-tab.html'
	};
}).

// Use node tab template
directive('nodeTab', function() {
	return {
		templateUrl:'templates/node-tab.html'
	};
}).

// Use node carousel template
directive('nodeCarousel', function() {
	return {
		templateUrl:'templates/node-carousel.html'
	};
}).

// Node carousel animation
directive('animateCarousel', ['$log', function($log) {
	$log.debug('animateCarousel directive => init');
	
	return function(scope, element, attrs) {
		scope.$watch(attrs.animateCarousel, function() {
			$log.debug('animateCarousel directive => scope.$watch(attrs.animateMe)', scope, element, attrs);
			
			if (scope.animateToggle !== undefined) {
				element.animate({ left: '-=400'	}, 250, 'easeOutQuint').
						animate({ left: '+=800'	}, 0).
						animate({ left: '-=400'	}, 250, 'easeOutQuint');
			}
		});
	};
}]);
