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

// quasardb querying service
factory('quasardbAPIService', ['$http', function($http) {
	var quasardbAPI = {};
	var regexpDateHistory = /^[0-9]{4}[0-1]{1}[0-9]{1}[0-3]{1}[0-9]{1}[0-2]{1}[0-9]{1}[0-5]{1}[0-9]{1}$/;

	// ** Cluster services : **
	// 	-> Ask quasardb for cluster topology
	quasardbAPI.getClusterTopology = function(id, mock) {
		if (mock) {
			return $http({
				method: 'GET', 
				url: 'mocks/cluster_topology.json'
			});
		} else {
			return $http({
				method : (id ? 'JSONP' : 'GET'),
				headers: { 'Accept-Encoding': 'gzip, deflate' },
				url : '../cluster_topology' + (id ? '?daemon=' + id + '&callback=JSON_CALLBACK': '')
			});
		}
	};
	
	// 	-> Ask quasardb for latest cluster data
	quasardbAPI.getClusterData = function(id, mock) {
		if (mock) {
			return $http({
				method: 'GET', 
				url: 'mocks/cluster_status.json'
			});
		} else {
			return $http({
				method : (id ? 'JSONP' : 'GET'),
				headers: { 'Accept-Encoding': 'gzip, deflate' },
				url : '../cluster_status' + (id ? '?daemon=' + id + '&callback=JSON_CALLBACK': '')
			});
		}
	};
	
	// 	-> Ask quasardb for cluster history
	quasardbAPI.getClusterHistoricalData = function(id, mock, start, end) {
		if (mock) {
			return $http({
				method: 'GET', 
				url: 'mocks/cluster_history.json'
			});
		} else {
            // Provide yesterday date
            var today = new Date();
            var yesterday = new Date();
            yesterday.setTime(today.getTime() - 24*60*60*1000);
            quasardbAPI.yesterday = yesterday;

            // Launch request
			return $http({
				method : (id ? 'JSONP' : 'GET'),
				headers: { 'Accept-Encoding': 'gzip, deflate' },
				url : '../cluster_history' + (id ? '?daemon=' + id
					+ '&start=' + (regexpDateHistory.test(start)?start:yesterday.format("yyyymmddHHMM")) 
					+ '&end=' + (regexpDateHistory.test(end)?end:today.format("yyyymmddHHMM"))
					+ '&callback=JSON_CALLBACK': '') 
			});
		}
	};

	// ** Node services : **
	// 	-> Ask quasardb for data of one node
	quasardbAPI.getNodeData = function(id, mock) {
		if (mock) {
			return $http({
				method: 'GET', 
				url: 'mocks/' + (id ? id : 'node1') + '_data.json'
			});
		} else {
			return eval($http({
				method : (id ? 'JSONP' : 'JSON'),
				url : '../global_status' + (id ? '?daemon=' + id + '&callback=JSON_CALLBACK': '')
			}));
		}
	};

	// 	-> Ask quasardb for one node's configuration
	quasardbAPI.getNodeConfiguration = function(id, mock) {
		if (mock) {
			return $http({
				method: 'GET', 
				url: 'mocks/' + (id ? id : 'node1') + '_conf.json'
			});
		} else {
			return $http({
				method : (id ? 'JSONP' : 'JSON'),
				url : '../config' + (id ? '?daemon=' + id + '&callback=JSON_CALLBACK': '')
			});
		}
	};
	
	// 	-> Ask quasardb for data of one node
	quasardbAPI.getNodeHistoricalData = function(id, mock, start, end) {
		if (mock) {
			return $http({
				method: 'GET', 
				url: 'mocks/' + (id ? id : 'node1') + '_history.json'
			});
		} else {
            // Provide yesterday date
            var today = new Date();
            var yesterday = new Date();
            yesterday.setTime(today.getTime() - 24*60*60*1000);
            quasardbAPI.yesterday = yesterday;

            // Launch request
			return $http({
				method : (id ? 'JSONP' : 'JSON'),
				url : '../history' + (id ? '?daemon=' + id
					+ '&start=' + (regexpDateHistory.test(start)?start:yesterday.format("yyyymmddHHMM")) 
					+ '&end=' + (regexpDateHistory.test(end)?end:today.format("yyyymmddHHMM"))
					+ '&callback=JSON_CALLBACK': '') 
			});
		}
	};
	
	return quasardbAPI;
}]).

factory('dataFunctions', ['$log', '$filter', '$route', '$rootScope', 'quasardbSettings', function($log, $filter, $route, $rootScope, quasardbSettings) {
	var dataFunctions = {};
	
	// Remove duplicate from an array
	dataFunctions.removeDuplicates = function(a) {
		var seen = {};
		var out = [];
		var len = a.length;
		var j = 0;
        for (var i = 0; i < len; i++) {
        	var item = a[i];
			if (seen[item] !== 1) {
            	seen[item] = 1;
				out[j++] = item;
			}
		}
		return out;
   	};
	
	// Return a compliant JSON from cluster or node data
	dataFunctions.buildValues = function(infos, values) {
		$log.debug('dataFunctions.buildValues(infos, values) -> ', infos, values, $rootScope);
		if (values === null) {
			return {};
		} else {
			r_max_count = 0;
			r_max_bytes = 0;
			p_max_bytes = 0;
			
			// Check data case :
			if (infos[0] !== undefined) {
				// 	=> Node's case : read max values
				r_max_count += infos[0].value.r_max_count;
				r_max_bytes += infos[0].value.r_max_bytes;
				p_max_bytes += infos[0].value.p_max_bytes;
			} else {
				// 	=> Cluster's case : aggregate each max values
				angular.forEach(infos.values, function(info) {
					r_max_count += info.r_max_count;
					r_max_bytes += info.r_max_bytes;
					p_max_bytes += info.p_max_bytes;
				});
			}
			
			// Format output :
			return {
				// 	-> Common part :
				"date" : 				d3.time.format.iso.parse(values.timestamp),
				"status" : 				($rootScope.nodelist[$rootScope.indexCurrentNode] === undefined) ? 'online' : $rootScope.nodelist[$rootScope.indexCurrentNode].status,
				"put" : 				values.operations.put.count,
				"put_failed" : 			values.operations.put.failures,
				"get" : 				values.operations.find.count,
				"get_failed" : 			values.operations.find.failures,
				"update" : 				values.operations.update.count,
				"update_failed" : 		values.operations.update.failures,
				"get_update" : 			values.operations.find_update.count,
				"get_update_failed" : 	values.operations.find_update.failures,
				"remove" : 				values.operations.remove.count,
				"remove_failed" : 		values.operations.remove.failures,
				"remove_if" : 			values.operations.remove_if.count,
				"remove_if_failed" : 	values.operations.remove_if.failures,
				"get_remove" : 			values.operations.find_remove.count,
				"get_remove_failed" : 	values.operations.find_remove.failures,
				"cas" : 				values.operations.compare_and_swap.count,
				"cas_failed" : 			values.operations.compare_and_swap.failures,
				"vm" : 					values.memory.vm.used,
				"mem" : 				values.memory.physmem.used,
				"disk" : 				values.disk_usage.total,
				"free" : 				values.disk_usage.free,
				"evictions" : 			values.overall.evictions,
				"entries" : 			values.overall.count,
				"p_size" : 				values.entries.persisted.size,
				"p_count" : 			values.entries.persisted.count,
				"r_size" : 				values.entries.resident.size,
				"r_count" : 			values.entries.resident.count,
				"cpu_average" : 		values.cpu,
				"cpu_idle" : 			values.cpu_idle,
				"ram" : 				values.memory.physmem.total,
				"r_max_count" : 		r_max_count,
				"r_max_bytes" : 		r_max_bytes,
				"p_max_bytes" : 		p_max_bytes,
				"max_size" : 			values.disk_usage.total,
				
				// 	-> Node part :
				"node_id" : 			values.node_id,
				"node_hostname" : 		($rootScope.nodelist[$rootScope.indexCurrentNode] === undefined) ? 'unknown' : $rootScope.nodelist[$rootScope.indexCurrentNode].hostname,
				"node_ip" : 			(values.network !== undefined) ? (values.network.listening_address + ':' + values.network.listening_port) : '',
				"os" : 					values.operating_system,
				"build" : 				values.engine_build_date,
				"version" : 			values.engine_version,
				"nbCpu" : 				values.hardware_concurrency,
				"partitions" : 			(values.network !== undefined) ? values.network.partitions : '',
				"uptime" :  			Date.DateDiff(new Date(values.startup), new Date(/*values.timestamp*/)),
				"transient_mode" : 		($rootScope.nodelist[$rootScope.indexCurrentNode] === undefined) ? false : $rootScope.nodelist[$rootScope.indexCurrentNode].transient_mode
			};
		}
	};

	// Build history from provided values
	dataFunctions.buildHistory = function(scope, clusterInfo, values) {
		if (values.count > 0) {
			$log.debug('dataFunctions.buildHistory(scope, clusterInfo, values) - ', scope, clusterInfo, values, scope.$root);
			
			// Compute new data :
			var firstCPU = 0;
			var secondCPU = 0;
            var nbCpu = 0;
			var timestamp = null;

            if (scope.tab == 2 && scope.$parent.nodeData === undefined) {
                scope.$parent.nodeData = [];
            }
			
			// Browse history samples :
			angular.forEach(values.results, function(result) {
				//  -> Compute cluster CPU usage
				secondCPU = firstCPU;
				firstCPU = result.cpu_times.idle;
				var cpu = (timestamp == null || nbCpu !== result.hardware_concurrency)? 0 : (100 - (Math.max(firstCPU - secondCPU, 0) / result.hardware_concurrency / (new Date(result.timestamp).getTime() - new Date(timestamp).getTime()) / 10));

                //  -> Send computed data
				timestamp = result.timestamp;
				nbCpu = result.hardware_concurrency;
				result.cpu = Math.round(cpu * 100) / 100;
				result.cpu_idle = firstCPU;
				
				//   -> Fetch cluster data
				if (scope.tab == 1) {
					scope.$parent.clusterData.push({
						key 	: result.timestamp,
						value 	: dataFunctions.buildValues(clusterInfo, result)
					});
				} else {
					// 	-> Fetch node data
					scope.$parent.nodeData.push({
						key 	: result.timestamp,
						value 	: dataFunctions.buildValues(scope.$parent.clusterData, result)
					});
				}
			});
		}
	};

	// Add computed data to graphs
	dataFunctions.setGraphData = function($scope, data) {
		$log.debug("dataFunctions.setGraphData($scope, data) => ", $scope, data);
		
		// No data => return
		if ((data === undefined) || (data.length == 0)) {
			return;
		}
		
		// Update scope
		$scope.charts = quasardbSettings.charts;
		$scope.ranges = quasardbSettings.ranges;
		
		// Compute limiters :
		var p_max_count = (data[0].value.r_max_count ? data[0].value.r_max_count : quasardbSettings.max_count);
		var r_max_count = p_max_count;
		var p_max_size = (data[0].value.p_max_bytes ? data[0].value.p_max_bytes : 0);
		//   -> max_size == 0 means no limit setted up => max_size = max disk space 
		if (p_max_size == 0) {
			p_max_size = data[0].value.max_size;
		}
		var r_max_size = (data[0].value.r_max_bytes ? data[0].value.r_max_bytes : 0);
		$log.debug("dataFunctions.setGraphData() -> maxs are ", p_max_count, r_max_count, p_max_size, r_max_size, ' - initial data is ', data[0]);
		
		// Update scope in order to refresh graphs
		// TODO : fill preview events
		angular.forEach($scope.charts, function(chart, idxChart) {
			var newMax = [];
			angular.forEach(chart.max, function(value) {
				if (eval(value) !== undefined) {
					newMax.push(eval(value));
				} else {
					newMax.push(value);
				}
			});
			$scope.charts[idxChart].max = newMax;
			
			var newData = [];
			angular.forEach(chart.data, function(value) {
				var toEval ='var ' + value + '= [];' + 
					 'angular.forEach(data, function(aData, idx) { ' +
					 '   var timestamp = d3.time.format.iso.parse(aData.key).getTime() / 1000;' +
					 '   angular.forEach(aData.value, function(value, name) {' +
					 '      if (name == "'+ value + '") { ' +
						       value + '.push({' +
					 '		      x: timestamp, ' +
					 '		      y: value' +
					 '         });' +
					 '      }' +
					 '    });' +
					 '}); ' +
					 'if (' + value + '.length > quasardbSettings.maxPoints) ' + value + '.shift();' +
					 '$scope.' + value + ' = ' + value + ';';
				$log.debug('dataFunctions.setGraphData() toEval => ', toEval, ' - Values => ', data);
				eval(toEval);
				newData.push(value);
			});
			$scope.charts[idxChart].data = newData;
			
			$log.debug('dataFunctions.setGraphData() ', chart.data, ' *** ', $scope);
		});
	};
	
	// Update graph data and preview graphs
	dataFunctions.updateData = function(scope, graph, spinner, computedData) {
		$log.debug('dataFunctions.updateData(scope, graph, spinner, computedData) => ', scope, graph, spinner, computedData);
		var newData = [[],[]];

		// Don't refresh already loaded period		
		if (scope.histoLoaded !== undefined) {
			scope.histoLoaded[scope.selectedRange] = true;
		}
		
		// Compute all new data
		angular.forEach(computedData, function(data) {
			$log.debug('dataFunctions.updateData => ', data);
			newData[0].push({
				x : d3.time.format.iso.parse(data.key).getTime() / 1000,
				y : eval('data.value.' + scope.data[0])
			});
			if (scope.data.length > 1) {
				newData[1].push({
					x : d3.time.format.iso.parse(data.key).getTime() / 1000,
					y : eval('data.value.' + scope.data[1])
				});
			}
		});
		$log.debug('dataFunctions.updateData => newData = ', newData);
		
		// Refresh graphs and preview graphs
		if (graph !== null) {
			angular.forEach(graph.series, function(serie, idx) {
				serie.data = newData[idx].sort(function(a,b) {
					var c = new Date(a.x);
					var d = new Date(b.x);
					return c-d;
		  		});
			});
			graph.window.xMin = graph.dataDomain()[0];
			graph.window.xMax = graph.dataDomain()[1];
			
			$log.debug('dataFunctions.updateData(scope, graph, spinner, computedData) => ', scope, graph, computedData, ' - domains => ', graph.dataDomain(), graph.window.xMin, graph.window.xMax);
			
			if (spinner !== null) {
				spinner.stop();
			}
			graph.update();
		}
	};
	
	return dataFunctions;
}]).

factory('shortcuts', ['$log', '$route', '$rootScope', 'quasardbSettings', 'quasardbAPIService', 'dataFunctions', function($log, $route, $rootScope, quasardbSettings, quasardbAPIService, dataFunctions) {
	var nodesLoaded = new HashMap();
	return {
		// Reset nodes list
		resetNodesLoaded : function() {
			nodesLoaded.clear();
		},
		
		// function dynamically called when browsing specific historical periods
    	// 	=> See app.js for configuration
    	setRange : function(scope, graph, period) {
    		var activeTab = scope.$parent.tab;
    		$log.debug('shortcuts.setRange(scope, graph, period) => ', scope, graph, period, ' - Tab => ', activeTab);
    		if (graph !== null) {
    			var startDate = new Date();
				if (period === "h") {
					startDate.setHours(startDate.getHours() - 4);	
				} else {
					startDate.setDate(startDate.getDate() - 2);
				}
				var data = (activeTab == 1) ? scope.$parent.clusterData : scope.$parent.nodeData;
				data = data.filter(function(element) { 
					return (new Date(element.key)).getTime() > startDate.getTime(); 
				});
				$log.debug('shortcuts.setRange(scope, graph, period) => ', (activeTab == 1) ? scope.$parent.clusterData : [], ' - filtered data : ', data);
				dataFunctions.updateData(scope, graph, null, data);
			}
    	},
    	
		// function dynamically called when browsing specific historical periods
    	// 	=> See app.js for configuration
        getHistoric : function(scope, graph, start, end) {
        	$log.debug('shortcuts Service : getHistoric(scope, start, end) => ', scope, start, end);
        	var activeTab = scope.$parent.tab;
        	var spinner = new Spinner(quasardbSettings.loaderOptions).spin(graph.element);

    		// Check if data aren't already loaded
   			if (scope.$root.clusterHistoricLoaded === undefined) {
    			scope.$root.clusterHistoricLoaded = [];
    			angular.forEach(scope.ranges, function() {
    				scope.$root.clusterHistoricLoaded.push(false);
    			});
    		}
    		$log.debug('shortcuts Service : getHistoric() : histoLoaded => ', scope.$root.clusterHistoricLoaded, nodesLoaded.get(scope.$root.nodelist[scope.$root.indexCurrentNode].ip));
    		
    		// Don't get already loaded period
    		if (((scope.$root.clusterHistoricLoaded[scope.selectedRange] === true) && (activeTab == 1)) || (nodesLoaded.get(scope.$root.nodelist[scope.$root.indexCurrentNode].ip) !== undefined)) {
    			var data = (activeTab == 1) ? scope.$parent.clusterData : scope.$parent.nodeData;
    			data = data.filter(function(element) {
    				return (new Date(element.key)).getTime() > (new Date(("" + start).substring(0,4), ("" + start).substring(4,6) - 1, ("" + start).substring(6,8), ("" + start).substring(8,10), ("" + start).substring(10,12), 59)).getTime(); 
    			});
    			$log.debug('shortcuts.getHistoric(scope, start, end) ', scope, start, end, ' => ', data);
    			dataFunctions.updateData(scope, graph, spinner, data);
    		} else {
    			var computedEnd = quasardbAPIService.yesterday.format("yyyymmddHHMM");
    			((activeTab == 1) ? quasardbAPIService.getClusterHistoricalData(scope.$root.nodelist[0].ip, $route.current.params.mock, start, computedEnd) : quasardbAPIService.getNodeHistoricalData(scope.$root.nodelist[scope.$root.indexCurrentNode].ip, $route.current.params.mock, start, computedEnd)).
					//  -> cluster historic retrieved
					success(function (response) {
						dataFunctions.buildHistory(scope.$parent, (activeTab == 1) ? scope.$parent.clusterData : scope.$parent.nodeData, response);
						var data = (activeTab == 1) ? scope.$parent.clusterData : scope.$parent.nodeData;
						dataFunctions.updateData(scope, graph, spinner, data);
						(activeTab == 1) ? scope.$root.clusterHistoricLoaded[scope.selectedRange] = true : nodesLoaded.add(scope.$root.nodelist[scope.$root.indexCurrentNode].ip, 'OK'); 
					}).
					
					//  -> cluster historic KO
					error(function (data, status, headers, config) {
						$log.info('clusterViewController : getCluster(id, mock) - getClusterHistoricalData(node, mock, start, end) ', (activeTab == 1) ? scope.$root.nodelist[0].ip : scope.$root.nodelist[scope.$root.indexCurrentNode].ip, $route.current.params.mock, 'null, null => ERROR - ', data, status, headers, config);
						if (spinner !== null) {
							spinner.stop();
						}
					}
				);
    		}
        }
   	};
}]);