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

/* Cluster controller: fetch cluster data */
controller('clusterViewController', ['$scope', '$rootScope', '$route', '$log', '$interval', 'quasardbSettings', 'quasardbAPIService', 'dataFunctions',
	function($scope, $rootScope, $route, $log, $interval, quasardbSettings, quasardbAPIService, dataFunctions) {
		var clusterInfo = new HashMap();
		$rootScope.nodelist = [];
		var currentStep = 0;
		var historyLoaded = false;
		var status;

        // Build a NodeInfoValue object
        function buildNodeInfoValue(info) {
            $log.debug('clusterViewController : buildNodeInfoValue(info)', info);

            var default_info = {
                local: {
                    hostname: "unknown",
                    depot: {
                        max_bytes: 0
                    },
                    limiter: {
                        max_bytes: 0,
                        max_resident_entries: quasardbSettings.max_count
                    }
                },
                global: {
                    cluster: {
                        transient: false
                    }
                }
            };

            // Use default values if something is missing in info.
            var combined_info = extend(default_info, info);

            return {
                "node_hostname" : combined_info.hostname,
                "r_max_count" : combined_info.local.limiter.max_resident_entries,
                "r_max_bytes" : info.local.limiter.max_bytes,
                "p_max_bytes" : info.local.depot.max_bytes,
                "transient_mode" : info.global.cluster.transient
            };
        }

		// Empty cluster
		function emptyCluster() {
			status = 'instable';
			if ($rootScope.nodelist.length === 0) {
				$rootScope.nodesStatus = 'no node found !';
			} else {
				// Some nodes in ring => update status
				$rootScope.nodesStatus = 'cluster is offline !';
				angular.forEach($rootScope.nodelist, function(node) {
					node.status = 'offline';
					node.icon = 'close';
				});
			}
		}

		// Get all data from cluster
		function getCluster(id, mock) {
			$log.debug('clusterViewController : getCluster(id, mock) => ', id, mock, historyLoaded);

			// Retrieve cluster topology :
			quasardbAPIService.getClusterTopology(id, mock).
				//  -> request successfull : there are some nodes
				success(function (responseClusterTopology) {
					if ((responseClusterTopology.nodes !== undefined) && (responseClusterTopology.nodes.length > 0)) {
						$log.debug('clusterViewController : getCluster(id, mock) - getNodeTopology(id, mock) ', id, mock, ' => ', responseClusterTopology);

						if ($rootScope.nodelist.length != responseClusterTopology.nodes.length) {
							$rootScope.nodelist = [];

							// Cluster ring is OK to render :
							//  -> Compute node rotation
							step = 360 / responseClusterTopology.nodes.length;
						}

						angular.forEach(responseClusterTopology.nodes, function(node, idxNode) {
							// Store nodes informations
							if (clusterInfo.get(node.endpoint) === undefined) {
								// Ask node configuration in order to get max number of objects
								quasardbAPIService.getNodeConfiguration(node.endpoint, $route.current.params.mock).
									//  -> node configuration retrieved
									success(function (responseNodeConfiguration) {
										$log.debug('clusterViewController -> getCluster(id, mock) - getNodeConfiguration(node, mock) ', node.endpoint, $route.current.params.mock, ' => ', responseNodeConfiguration, $rootScope);
										responseNodeConfiguration.hostname = node.hostname;
										clusterInfo.add(node.endpoint, buildNodeInfoValue(responseNodeConfiguration));

										// Build cluster ring
										if ($rootScope.nodelist.length != responseClusterTopology.nodes.length) {
											$rootScope.nodelist.push({
												"id" : 				node.reference,
												"status" : 			"online",
												"ip" : 				node.endpoint,
												"hostname" : 		node.hostname,
												"degree" : 			(step * currentStep),
												"showTooltip" : 	false,
												"icon" : 			'data',
												"transient_mode" : 	clusterInfo.get(node.endpoint).transient_mode
											});
											currentStep += 1;
										}

										// Add first nodeId to rootscope (for default node view tab)
										if (idxNode == 0) {
											$rootScope.nodeId = node.reference;
											$rootScope.nodeIp = node.endpoint;
										}

										// Setup cluster's status and cluster's nb nodes
										$rootScope.status = 'stable';
										$rootScope.nodesStatus = $rootScope.nodelist.length + ' / ' + responseClusterTopology.nodes.length;
									}).
									//  -> node configuration KO
									error(function (data, status, headers, config) {
										$log.info('clusterViewController : getCluster(id, mock) - getNodeConfiguration(node, mock) ', node.endpoint, $route.current.params.mock, ' => ERROR - ', data, status, headers, config);
										clusterInfo.add(node.endpoint, buildNodeInfoValue(null));
									}
								);
							} else {
								// Cluster becomes stable
								if (status === 'instable') {
									angular.forEach($rootScope.nodelist, function(node) {
										node.status = 'online';
										node.icon = 'data';
									});
									status = 'stable';
								}
							}
						});

						// Ask cluster for historical data -> 1 day ago
						if ((historyLoaded !== true) && ($scope.tab == 1)) {
							$scope.$parent.clusterData = [];
							quasardbAPIService.getClusterHistoricalData(responseClusterTopology.nodes[0].endpoint, $route.current.params.mock, null, null).
								//  -> cluster historic retrieved
								success(function (responseClusterHistoricalData) {
									dataFunctions.buildHistory($scope, clusterInfo, responseClusterHistoricalData);
									if (responseClusterHistoricalData.count != 0) {
										$scope.$root.$broadcast('cluster_data_ready');
									} else {
										$scope.refresh();
									}
									historyLoaded = true;
								}).

								//  -> cluster historic KO
								error(function (data, status, headers, config) {
									$log.info('clusterViewController : getCluster(id, mock) - getClusterHistoricalData(node, mock, start, end) ', responseClusterTopology.nodes[0].endpoint, $route.current.params.mock, 'null, null => ERROR - ', data, status, headers, config);
									// TODO handle error
								}
							);
						} else {
							// Cluster history already loaded => ask for latest cluster sample
							$log.debug('clusterViewController : getCluster(id, mock) => history already loaded : ', $scope.$parent.clusterData);
							(($scope.tab == 1) ? quasardbAPIService.getClusterData(responseClusterTopology.nodes[0].endpoint, $route.current.params.mock) : quasardbAPIService.getNodeData(responseClusterTopology.nodes[$scope.$root.indexCurrentNode].endpoint, $route.current.params.mock)).
								//  -> latest cluster data retrieved
								success(function (responseData) {
									var data = ($scope.tab == 1) ? $scope.$parent.clusterData : $scope.$parent.nodeData;
									if ((data !== undefined) && (responseData.cpu_times !== undefined)) {
										if ($route.current.params.mock) {
											responseData.timestamp = new Date();
											responseData.cpu = Math.round(Math.floor(Math.random() * 101) * 100) / 100;
										} else {
											// CPU
											responseData.cpu = (data[data.length - 1] === undefined) ? 0 : 100 - (Math.max(responseData.cpu_times.idle - data[data.length - 1].value.cpu_idle , 0) / responseData.hardware_concurrency / quasardbSettings.refresh / 10);
											responseData.cpu = (responseData.cpu < 0) ? data[data.length - 1].value.cpu_average : Math.round(responseData.cpu * 100) / 100;
											responseData.cpu_idle = responseData.cpu_times.idle;
										}
										data.push({
											key 	: responseData.timestamp,
											value	: dataFunctions.buildValues(clusterInfo, responseData)
										});
										$rootScope.$broadcast(($scope.tab == 1) ? 'cluster_data_ready' : (($scope.$root.usingCarousel == false) ? 'node_changed' : 'refresh_node' ));
									}
								}).
								//  -> latest cluster data KO
								error(function (data, status, headers, config) {
									$log.info('clusterViewController : getCluster(id, mock) - getClusterData(node, mock) ', ($scope.tab == 1) ? responseClusterTopology.nodes[0].endpoint : responseClusterTopology.nodes[$scope.$root.indexCurrentNode].endpoint, $route.current.params.mock, ' => ERROR - ', data, status, headers, config);
									// TODO handle error
								}
							);
						}
					} else {
						emptyCluster();
					}
				}).

				//  -> request failed : no nodes
				error(function (data, status, headers, config) {
					$log.info('clusterViewController : getCluster(id, mock) - getNodeTopology(id, mock) ', id, mock, ' => ERROR - ', data, status, headers, config);
					emptyCluster();

					// Stop searching animation => see directive.js
					if ($scope.$root != null) {
						$scope.$root.status = 'offline';
					}
				});
		}

		// Hide or display node tooltip
		$scope.hover = function(node) {
			$log.debug('clusterViewController : node changed => ', node);
			return node.showTooltip = ! node.showTooltip;
		};

		// Show node view for a specific node
		$scope.showNode = function(nodeId) {
			$log.debug('clusterViewController : showNode(nodeId) => ', nodeId, $scope, $rootScope);
			$rootScope.nodelist.forEach(function (value, index) {
				$log.debug('clusterViewController -> showNode(nodeId) -> $rootScope.nodelist.forEach', nodeId, value, index);
				if (value.id === nodeId) {
					$rootScope.indexCurrentNode = index;
				}
			});
			$rootScope.nodeId = nodeId;
			$scope.$parent.tab = 2;
			$scope.$parent.myVar2='b-tabs__item_state_current';
			$scope.$parent.myVar1='';
		};

		// Handle refresh request (windows close events and refresh button)
		$scope.refresh = function() {
			$log.debug('clusterViewController : refresh() => ', $scope, $rootScope);
			getCluster(null, $route.current.params.mock);
		};

		// Get cluster data (cluster topology, nodes configuration, cluster history)
		getCluster(null, $route.current.params.mock);

		// Refresh data at regular interval
		$interval($scope.refresh, quasardbSettings.refresh);
	}
]).

/* Cluster graphs controller */
controller('clusterChartsController', ['$scope', '$log', 'quasardbSettings', 'dataFunctions', 'shortcuts', function($scope, $log, quasardbSettings, dataFunctions, shortcuts) {
	// Refresh cluster data
	function refreshClusterData(scope) {
		if ((scope.clusterData !== undefined) && (scope.tab == 1)) {
			dataFunctions.setGraphData(scope, scope.clusterData);
		}
	}

	// Refresh GUI on new data
	$scope.$on('cluster_data_ready', function() {
		$log.debug('clusterChartsController : $scope.$on(cluster_data_ready) => ', $scope.clusterData);
		refreshClusterData($scope);
	});

	// Detect active tab
	$scope.$watch('tab', function() {
		$log.debug('clusterChartsController : $watch(tab) => ', $scope.tab, $scope);
		refreshClusterData($scope);
		if ($scope.tab == 1) {
			$scope.$root.usingCarousel = false;
			$scope.$parent.nodeData = undefined;
			shortcuts.resetNodesLoaded();
		}
	});

	// Initialization part
	$log.debug('clusterChartsController : init => ', $scope);
}]).

/* Nodes controller (handle node view) */
controller('nodesController', ['$log', '$route', '$scope', 'quasardbAPIService', 'dataFunctions', function($log, $route, $scope, quasardbAPIService, dataFunctions) {
	$log.debug('nodesController.init($scope)', $scope);
	var loaded = false;

	// Refresh node view with incoming data
	refreshNodeView = function() {
		$log.debug('nodesController - refreshNodeView() => ', $scope);

		// Refresh scope
		$scope.node = $scope.$root.nodelist[$scope.$root.indexCurrentNode];

		// Get selected node data
		(($scope.$parent.nodeData === undefined) ? quasardbAPIService.getNodeHistoricalData($scope.node.ip, $route.current.params.mock, null, null) : quasardbAPIService.getNodeData($scope.node.ip, $route.current.params.mock, null, null)).
			//  -> latest cluster data retrieved
			success(function (response) {
				if ($scope.$parent.nodeData === undefined) {
					if (response.count > 0) {
						// Build history
						dataFunctions.buildHistory($scope, null, response);

						// Refresh graphs
						dataFunctions.setGraphData($scope, $scope.$parent.nodeData);

						// There are historic data
						quasardbAPIService.getNodeData($scope.node.ip, $route.current.params.mock).
							success(function(responseData) {
								responseData.cpu = 0.5;

								responseData.cpu_idle = responseData.cpu_times.idle;
								$scope.$parent.nodeData.push({
									key 	: responseData.timestamp,
									value	: dataFunctions.buildValues($scope.$parent.clusterData[0], responseData)
								});
								dataFunctions.setGraphData($scope, $scope.$parent.nodeData);
							}).
							error(function() {
								// TODO
								$scope.$parent.nodeData = [];
								refreshNodeView();
							});
					} else {
						// No history yet or transient mode activated => get current data
						$scope.$parent.nodeData = [];
						refreshNodeView();
					}
				} else {
					// Fresh data are fetched => refresh some metrics
					response.cpu = 0.5;
					response.cpu_idle = response.cpu_times.idle;
					$scope.$parent.nodeData.push({
						key 	: response.timestamp,
						value	: dataFunctions.buildValues($scope.$parent.clusterData[0], response)
					});
					dataFunctions.setGraphData($scope, $scope.$parent.nodeData);
				}

				// Refresh nodelist
				$scope.$root.nodelist[$scope.$root.indexCurrentNode].build = ($scope.$parent.nodeData !== undefined) && ($scope.$parent.nodeData[$scope.$parent.nodeData.length - 1] !== undefined) ? $scope.$parent.nodeData[$scope.$parent.nodeData.length - 1].value.build : response.engine_build_date;
				$scope.$root.nodelist[$scope.$root.indexCurrentNode].version = ($scope.$parent.nodeData !== undefined) && ($scope.$parent.nodeData[$scope.$parent.nodeData.length - 1] !== undefined) ? $scope.$parent.nodeData[$scope.$parent.nodeData.length - 1].value.version : response.engine_version;
				loaded = true;
				$scope.$root.$broadcast('node_changed');
			}).
			//  -> latest cluster data KO
			error(function (data, status, headers, config) {
				$log.info('clusterViewController : refreshNodeView() - getNodeHistoricalData(node, mock, start, end) ', $scope.node.ip, $route.current.params.mock, 'null, null => ERROR - ', data, status, headers, config);
				// TODO handle error
			}
		);
	};

	// Listen the following event : new view is selected
	$scope.$watch('tab', function() {
		if ($scope.tab == 2) {
			$log.debug('nodesController.$watch(tab) => ', $scope);
			$scope.$root.usingCarousel = true;
			refreshNodeView();
		}
	});

	// Listen the following event : node view has changed
	$scope.$on('node_changed', function() {
		if ($scope.tab == 2) {
			$log.debug('nodeSessionsController => node_changed event => ', $scope);
			if (!loaded) {
				refreshNodeView();
			} else {
				loaded = false;
			}
		}
	});

	// TODO
	$scope.$on('refresh_node', function() {
		if ($scope.tab == 2) {
			dataFunctions.setGraphData($scope, $scope.$parent.nodeData);
		}
	});
}]).

/* Node Sessions controller */
controller('nodeSessionsController', ['$scope', '$log', function($scope, $log) {
	$scope.maxSessions = 2000;
	$scope.nodePartitions = [];

	// Refresh node sessions
	refreshNodeSessions = function() {
		var partitions = [];
		$log.debug('nodeSessionsController => buildSessions() ', $scope.$parent.$parent.nodeData);
		if (($scope.$parent.$parent.nodeData !== undefined) && ($scope.$parent.nodeData[($scope.$parent.$parent.nodeData.length - 1)] !== undefined)) {
			$scope.maxSessions = $scope.$parent.nodeData[$scope.$parent.$parent.nodeData.length - 1].value.partitions.max_sessions;
			for (var i = 0; i < $scope.$parent.nodeData[$scope.$parent.$parent.nodeData.length - 1].value.partitions.count; i++) {
				partitions.push({
					id: i,
					value: $scope.$parent.$parent.nodeData[$scope.$parent.$parent.nodeData.length - 1].value.partitions.available_sessions[i]
				});
			}
		}
		$scope.nodePartitions = partitions;

		// If no data available
		if ($scope.nodePartitions.length === 0) {
			$scope.nodePartitions = [{
				id: "data available yet",
				value: "0"
			}];
		}
	};

	// Listen the following event : new tab is selected
	$scope.$watch('tab', function() {
		if ($scope.tab == 2) {
			$log.debug('nodeSessionsController => $watch(tab) => ', $scope);
			refreshNodeSessions();
		}
	});

	// Listen the following event : node view has changed
	$scope.$on('node_changed', function() {
		if ($scope.tab == 2) {
			$log.debug('nodeSessionsController => node_changed event => ', $scope);
			refreshNodeSessions();
		}
	});
}]).

/* Node carousel controller */
controller('nodeCarouselController', ['$scope', '$log', function($scope, $log) {
	$log.debug('nodeCarouselController.init($rootScope, $scope) => ', $scope.$root, $scope.$root.nodeId);
	$scope.$root.indexCurrentNode = 0;
	$scope.$root.usingCarousel = false;

	// Change quasardb build and quasardb version
	var initController = function() {
		$log.debug('nodeCarouselController.initController() => ', $scope.$root, $scope.$root.nodelist[$scope.$root.indexCurrentNode]);
		if ($scope.$root.nodelist[$scope.$root.indexCurrentNode] !== undefined) {
			$scope.nodeBuild = $scope.$root.nodelist[$scope.$root.indexCurrentNode].build;
			$scope.nodeVersion = $scope.$root.nodelist[$scope.$root.indexCurrentNode].version;
		}
	};

	// Listen the following event : new tab is selected
	$scope.$watch('tab', function() {
		if ($scope.$parent.$parent.tab == 2) {
			initController();
		}
	});

	// Listen the following event : node view has changed
	$scope.$on('node_changed', function() {
		if ($scope.$parent.$parent.tab == 2) {
			initController();
		}
	});

	// Browse previous node available node on the cluster ring
	$scope.showPreviousNode = function() {
		$log.debug('nodeCarouselController : showPreviousNode() => ', $scope);
		if ($scope.$root.nodelist[$scope.$root.indexCurrentNode] === undefined) {
			return;
		}

		// Erase node data
		$scope.$parent.$parent.nodeData = undefined;

		// Update scope
		$scope.$root.indexCurrentNode = ($scope.$root.indexCurrentNode == 0)?($scope.$root.nodelist.length - 1):(Math.abs($scope.$root.indexCurrentNode - 1) % $scope.$root.nodelist.length);
		$scope.nodeBuild = $scope.$root.nodelist[$scope.$root.indexCurrentNode].build;
		$scope.nodeVersion = $scope.$root.nodelist[$scope.$root.indexCurrentNode].version;
		$scope.$root.node = $scope.$root.nodelist[$scope.$root.indexCurrentNode];
		$scope.$root.nodeId = $scope.$root.nodelist[$scope.$root.indexCurrentNode].id;

		// Notify that new data are ready to be displayed
		$scope.$root.usingCarousel = true;
		$scope.$emit('node_changed');
	};

	// Browse next node available node on the cluster ring
	$scope.showNextNode = function() {
		$log.debug('nodeCarouselController : showNextNode() => ', $scope);
		if ($scope.$root.nodelist[$scope.$root.indexCurrentNode] === undefined) {
			return;
		}

		// Erase node data
		$scope.$parent.$parent.nodeData = undefined;

		// Update scope
		$scope.$root.indexCurrentNode = Math.abs(($scope.$root.indexCurrentNode + 1) % $scope.$root.nodelist.length);
		$scope.nodeBuild = $scope.$root.nodelist[$scope.$root.indexCurrentNode].build;
		$scope.nodeVersion = $scope.$root.nodelist[$scope.$root.indexCurrentNode].version;
		$scope.$root.node = $scope.$root.nodelist[$scope.$root.indexCurrentNode];
		$scope.$root.nodeId = $scope.$root.nodelist[$scope.$root.indexCurrentNode].id;

		// Notify that new data are ready to be displayed
		$scope.$root.usingCarousel = true;
		$scope.$emit('node_changed');
	};
}]).

/* Node download controller */
controller('nodeDownloadController', ['$rootScope', '$scope', '$log', '$route', '$http', function($rootScope, $scope, $log, $route, $http) {
	$scope.filename = '';

	// Download a JSON file containing live data for the current node.
	$scope.getRawNodeData = function() {
		$log.debug('nodeDownloadController.getRawNodeData() => ', $rootScope, $scope, $route.current.params.mock);

		// Set filename
		$scope.filename = 'data_' + $rootScope.nodeIp + '_' + (new Date()).format('yyyymmddHHMM') + '.json';

		// Compute the right url
		var url = '../global_status' + ($rootScope.nodeIp ? '?daemon=' + $rootScope.nodeIp + '&callback=handleJSONP': '');
		if ($route.current.params.mock) {
			url = 'mocks/' + ($rootScope.nodeIp ? $rootScope.nodeIp : 'node1') + '_data.json';
		}

		// Get data with url
		$http.get(url).then(function(response) {
			$scope.$emit('downloaded', response.data);
		});
	};

	// Download a JSON file containing configuration data for the current node.
	$scope.getRawNodeConf = function() {
		$log.debug('nodeDownloadController.getRawNodeConf() => ', $rootScope, $scope, $route.current.params.mock);

		// Set filename
		$scope.filename = 'config_' + $rootScope.nodeIp + '.json';

		// Compute the right url
		var url = '../config' + ($rootScope.nodeIp ? '?daemon=' + $rootScope.nodeIp + '&callback=handleJSONP': '');
		if ($route.current.params.mock) {
			url = 'mocks/' + ($rootScope.nodeIp ? $rootScope.nodeIp : 'node1') + '_conf.json';
		}

		// Get data with url
		$http.get(url).then(function(response) {
			$scope.$emit('downloaded', response.data);
		});
	};

	// When the download finishes, attach the data to a link balise.
	$scope.$on('downloaded', function(event, data) {
		var output = new Blob([JSON.stringify(data, null, '\t')], {type:"text/plain;charset=utf-8"});
		if (!$route.current.params.mock) {
			output = eval(data);
		}
		saveAs(output, $scope.filename);
	});

	// Callback function to call
	handleJSONP = function(data) {
		return new Blob([JSON.stringify(data, null, '\t')], {type:"text/plain;charset=utf-8"});
	};
}]);
