angular.module("histogramApp")
	.directive("nagiosHistogram", function() {
		return {
			templateUrl: "histogram-graph.html",
			restrict: "AE",
			scope: {
				cgiurl: "@cgiurl",
				reporttype: "@reporttype",
				host: "@host",
				service: "@service",
				timeperiod: "@timeperiod",
				t1: "@t1",
				t2: "@t2",
				breakdown: "@breakdown",
				graphevents: "@graphevents",
				graphstatetypes: "@graphstatetypes",
				assumestateretention: "@assumestateretention",
				initialstateslogged: "@initialstateslogged",
				ignorerepeatedstates: "@ignorerepeatedstates",
				lastUpdate: "=lastUpdate",
				reload: "@reload",
				build: "&build"
			},
			controller: function($scope, $element, $attrs, $http,
					histogramEventsService, statisticsBreakdown) {

				// Layout variables
				$scope.fontSize = 11;
				$scope.graphMargin = {
					top: 40,
					right: 290,
					bottom: 84,
					left: 60
				};
				$scope.svgHeight = 320;
				$scope.svgWidth = 900;
				$scope.dateFormat = d3.time.format("%a %b %d %H:%M:%S %Y");

				// Application state variables
				$scope.fetchingAlerts = false;

				$scope.$watch("reload", function(newValue) {
					// Remove any previous graphs
					d3.select("g#grid").selectAll("path").remove();
					// Set the start and end times
					$scope.startTime = $scope.t1 * 1000;
					$scope.endTime = $scope.t2 * 1000;
					// Show the histogram
					showHistogram();
				});

				// Get the statistics breakdown label
				$scope.statisticsBreakdownLabel = function(value) {
					for (var i = 0; i < statisticsBreakdown.length; i++) {
						var bd = statisticsBreakdown[i];
						if (bd.value == value) {
							return bd.label;
						}
					}
				};

				// Get the x-axis scale domain
				var getXScaleDomain = function() {

					// Set up the x-axis scale domain
					var domain = [];

					switch($scope.breakdown) {
					case "monthly":
						domain = d3.range(0,13);
						break;
					case "dayofweek":
						domain = d3.range(0,8);
						break;
					case "hourly":
						domain = d3.range(0,25);
						break;
					case "dayofmonth":
					default:
						domain = d3.range(0,32);
						break;
					}

					return domain;
				};

				// Get the x-axis tick values
				var getXAxisTickValue = function(d) {

					// Set up the x-axis tick values
					var values = [];

					switch($scope.breakdown) {
					case "monthly":
						var months = ["January", "February", "March",
								"April", "May", "June", "July", "August",
								"September", "October", "November",
								"December", "January"];
						return months[d % 12];
						break;
					case "dayofweek":
						var days = ["Sunday", "Monday", "Tuesday",
								"Wednesday", "Thursday", "Friday",
								"Saturday", "Sunday"];
						return days[d % 7];
						break;
					case "hourly":
						var hourFormat = d3.format("02d");
						return hourFormat(d % 24) + ":00";
						break;
					case "dayofmonth":
					default:
						var value = ((d + 1) % 32);
						return (value == 0 ? 1 : value);
						break;
					}
				};

				// Get the number of periods for the breakdown
				$scope.getBreakdownPeriods = function() {
					switch($scope.breakdown) {
					case "monthly":
						return 12;
						break;
					case "dayofweek":
						return 7;
						break;
					case "hourly":
						return 24;
						break;
					case "dayofmonth":
					default:
						return 31;
						break;
					}
				};

				// Determine the y-axis maximum
				getYMax = function() {

					var states = ["up", "down", "unreachable", "ok",
							"warning", "unknown", "critical" ];
					var yMax = 0;

					states.forEach(function(e, i, a) {
						yMax = Math.max($scope.summary[$scope.breakdown].maxima[e], yMax);
					});

					return yMax;
				};

				// Display the graph
				var displayGraph = function() {

					// Local variables
					var graphHeight = $scope.svgHeight -
							($scope.graphMargin.bottom +
							$scope.graphMargin.top);
					var graphWidth = $scope.svgWidth -
							($scope.graphMargin.right +
							$scope.graphMargin.left);
					var gridCenter = $scope.graphMargin.left +
							graphWidth / 2;

					// Get the header group
					var gridGroup = d3.select("svg#histogram")
						.select("g#grid")
						.attr({
							transform: function() {
								return "translate(" +
										$scope.graphMargin.left + "," +
										$scope.graphMargin.top + ")";
							}
						});

					// Build the x-axis scale
					var xScale = d3.scale.ordinal()
						.domain(getXScaleDomain())
						.rangePoints([0, graphWidth]);

					// Build the x-axis
					var xAxis = d3.svg.axis()
						.scale(xScale)
						.orient("bottom")
						.tickSize(graphHeight)
						.tickFormat(function(d) {
							return getXAxisTickValue(d);
						});

					// Display the x-axis
					d3.select("g#xaxis").call(xAxis);

					// Rotate the value labels for the x-axis
					var translate = -(graphHeight + ($scope.fontSize / 2));
					d3.select("g#xaxis")
						.selectAll("text")
						.attr({
							transform: function() {
								return "rotate(-90) translate(" +
										translate + "," + translate + ")";
							}
						})
						.style({
							"text-anchor": "end"
						});

					// Build the y-axis scale
					var yScale = d3.scale.linear()
						.domain([getYMax(), 0])
						.rangeRound([0, graphHeight]);

					// Build the y-axis
					var yAxis = d3.svg.axis()
						.scale(yScale)
						.orient("left")
						.tickSize(graphWidth);

					// Display the y-axis
					d3.select("g#yaxis").call(yAxis);

					// Generate the lines
					var states = [];
					if($scope.reporttype == "hosts") {
						states = ["up", "down", "unreachable"];
					}
					else {
						states = ["ok", "warning", "unknown", "critical"];
					}
					states.forEach(function(e, i, a) {
						var line = d3.svg.line()
							.x(function(d, i) { return xScale(i); })
							.y(function(d) { return yScale(d[e]); })
							.interpolate("linear");

						gridGroup.append("path")
							.attr({
								"d": line($scope.summary[$scope.breakdown].details),
								"class": e
							});
					});
				};

				// Initialize the data for display
				var initializeData = function(obj, size) {

					var states = ["up", "down", "unreachable", "ok",
							"warning", "unknown", "critical" ];

					obj.maxima = new Object;
					obj.minima = new Object;
					obj.totals = new Object;
					states.forEach(function(e, i, a) {
						obj.maxima[e] = 0;
						obj.minima[e] = -1;
						obj.totals[e] = 0;
					});

					obj.details = new Array;
					for(var i = 0; i < size; i++) {
						obj.details.push({
							"up": 0,
							"down": 0,
							"unreachable": 0,
							"ok": 0,
							"warning": 0,
							"unknown": 0,
							"critical": 0
						});
					}
				};

				// Update the summary data
				var updateData = function(obj, state, index) {

					obj.details[index][state]++;
					obj.maxima[state] = Math.max(obj.maxima[state],
							obj.details[index][state]);
					obj.totals[state]++;
					if(index == 0) {
						// If this is the first entry in the breakdown,
						// also update the last because the graph
						// "wraps around"
						obj.details[obj.details.length - 1][state]++;
					}
				};

				// Calculate the minima
				var calculateMinima = function(obj) {
					for(var key in obj.minima) {
						obj.minima[key] = obj.details[0][key];
						for(var j = 1; j < obj.details.length; j++) {
							obj.minima[key] = Math.min(obj.minima[key],
									obj.details[j][key]);
						}
					}
				};

				// Summarize the data for display
				var summarizeData = function() {

					// Initialize the data
					$scope.summary = {
						monthly: new Object,
						dayofmonth: new Object,
						dayofweek: new Object,
						hourly: new Object,
					};
					initializeData($scope.summary.monthly, 13);
					initializeData($scope.summary.dayofmonth, 32);
					initializeData($scope.summary.dayofweek, 8);
					initializeData($scope.summary.hourly, 25);

					$scope.json.data.alertlist.forEach(function(e, i, a) {
						// Create a Javascript date object
						var ts = new Date(e.timestamp);

						// Update the monthly data
						updateData($scope.summary.monthly, e.state,
								ts.getMonth());

						// Update the day of month data
						updateData($scope.summary.dayofmonth, e.state,
								ts.getDate() - 1);
		
						// Update the day of week data
						updateData($scope.summary.dayofweek, e.state,
								ts.getDay());
		
						// Update the hourly data
						updateData($scope.summary.hourly, e.state,
								ts.getHours());

					});

					// Calculate the minima
					calculateMinima($scope.summary.monthly);
					calculateMinima($scope.summary.dayofmonth);
					calculateMinima($scope.summary.dayofweek);
					calculateMinima($scope.summary.hourly);
				};

				// Show the graph
				var showHistogram = function() {

					if(!$scope.build()) {
						return;
					}

					// Build the list of parameters for the JSON query
					var parameters = {
						query: "alertlist",
						formatoptions: "enumerate bitmask",
						objecttypes: ($scope.reporttype == "hosts" ?
								"host" : "service"),
						hostname: $scope.host,
						starttime: $scope.t1,
						endtime: $scope.t2,
						statetypes: function() {
							switch($scope.graphstatetypes) {
							case 1:
								return "soft";
								break;
							case 2:
								return "hard";
								break;
							case 3:
							default:
								return "hard soft";
								break;
							}
						}(),
						backtrackedarchives: $scope.backtracks
					};

					switch($scope.reporttype) {
					case "hosts":
						var events = histogramEventsService.hostEvents.filter(function(e) {
							return e.value == $scope.graphevents;
						});
						if(events.length > 0) {
							parameters.hoststates = events[0].states;
						}
						else {
							parameters.hoststates = "up down unreachable";
						}
						break;
					case "services":
						var events = histogramEventsService.serviceEvents.filter(function(e) {
							return e.value == $scope.graphevents;
						});
						parameters.servicedescription = $scope.service;
						if(events.length > 0) {
							parameters.servicestates = events[0].states;
						}
						else {
							parameters.servicestates =
									"ok warning unknown critical";
						}
						break;
					}

					var getConfig = {
						params: parameters,
						withCredentials: true
					};

					// Where to place the spinner
					var spinnerdiv = d3.select("div#spinner");
					var spinner = null;

					// Send the JSON query
					$http.get($scope.cgiurl + "archivejson.cgi", getConfig)
						.error(function(err) {
							console.warn(err);
							// Stop the spinner
							$scope.fetchingAlerts = false;
							spinner.stop();
						})
						.success(function(json) {
							// Stop the spinner
							$scope.fetchingAlerts = false;
							spinner.stop();
		
							// Save the json results
							$scope.json = json;
	
							// Record the query time
							$scope.lastUpdate = new Date(json.result.query_time);
	
							// Summarize the results
							summarizeData();

							// Display the graph
							displayGraph();

							// Display the summary
						});

					// Start the spinner
					$scope.fetchingAlerts = true;
					spinner = new Spinner($scope.spinnerOpts).spin(spinnerdiv[0][0]);
				};

			}
		};
	});