angular.module("trendsApp")
	.directive("nagiosTrends", function() {
		return {
			templateUrl: "trends-graph.html",
			restrict: "AE",
			scope: {
				cgiurl: "@cgiurl",
				reporttype: "@reporttype",
				host: "@host",
				service: "@service",
				timeperiod: "@timeperiod",
				t1: "@t1",
				t2: "@t2",
				assumeinitialstates: "@assumeinitialstates",
				assumestateretention: "@assumestateretention",
				assumestatesduringnotrunning: "@assumestatesduringnotrunning",
				includesoftstates: "@includesoftstates",
				initialassumedhoststate: "@initialassumedhoststate",
				initialassumedservicestate: "@initialassumedservicestate",
				backtrack: "@backtrack",
				nopopups: "@nopopups",
				nomap: "@nomap",
				lastUpdate: "=lastUpdate",
				reload: "@reload",
				build: "&build"
			},
			controller: function($scope, $element, $attrs, $http,
					spinnerOpts) {

				// Global variables
				$scope.fontSize = 11;
				$scope.states = null;
				$scope.stateChangeMargin = {
					top: 53,
					right: 280,
					bottom: 156,
					left: 110
				};
				$scope.svgHeight = $scope.svgHostHeight;
				$scope.svgWidth = 900;
				$scope.dateFormat = d3.time.format("%a %b %d %H:%M:%S %Y");
				$scope.showPopups = $scope.nopopups == "false";

				// State information for services
				$scope.serviceStates = {
					"ok": {
						"label": "Ok",
						"popupText": "OK",
						"y": 13,
						"color": { "r": 0, "g": 210, "b": 0 }
					},
					"warning": {
						"label": "Warning",
						"popupText": "WARNING",
						"y": 36,
						"color": { "r": 176, "g": 178, "b": 20 }
					},
					"unknown": {
						"label": "Unknown",
						"popupText": "UNKNOWN",
						"y": 59,
						"color": { "r": 255, "g": 100, "b": 25 }
					},
					"critical": {
						"label": "Critical",
						"popupText": "CRITICAL",
						"y": 82,
						"color": { "r": 255, "g": 0, "b": 0 }
					},
					"nodata": {
						"label": "Indeterminate",
						"popupText": "?",
						"y": 107,
						"color": { "r": 0, "g": 0, "b": 0, "a": 0 }
					}
				};
	
				// State information for hosts
				$scope.hostStates = {
					"up": {
						"label": "Up",
						"popupText": "UP",
						"y": 13,
						"color": { "r": 0, "g": 210, "b": 0 }
					},
					"down": {
						"label": "Down",
						"popupText": "DOWN",
						"y": 38,
						"color": { "r": 255, "g": 0, "b": 0 }
					},
					"unreachable": {
						"label": "Unreachable",
						"popupText": "UNREACHABLE",
						"y": 63,
						"color": { "r": 128, "g": 0, "b": 0 }
					},
					"nodata": {
						"label": "Indeterminate",
						"popupText": "?",
						"y": 88,
						"color": { "r": 0, "g": 0, "b": 0, "a": 0 }
					}
				};

				// Application state variables
				$scope.fetchingStateChanges = false;
				$scope.fetchingAvailability = false;
				$scope.popupDisplayed = false;
				$scope.popupDisplayedOnZoomStart = false;

				$scope.$watch("reload", function(newValue) {
					// Set the graph dimensions
					switch ($scope.reporttype) {
					case "hosts":
						$scope.svgHeight = 300;
						break;
					case "services":
						$scope.svgHeight = 320;
						break;
					default:
						$scope.svgHeight = 0;
						break;
					}
					// Set the start and end times
					$scope.startTime = $scope.t1 * 1000;
					$scope.endTime = $scope.t2 * 1000;
					// Show the trend
					if($scope.build()) {
						displayStateChanges();
						getAvailability($scope.t1, $scope.t2);
					}
				});

				$scope.getYAxisTemplate = function() {
					switch ($scope.reporttype) {
					case "hosts":
						return "trends-host-yaxis.html";
						break;
					case "services":
						return "trends-service-yaxis.html";
						break;
					default:
						return null;
						break;
					}
				};

				// Create a color from a color object
				var genColor = function(color) {
					if(color.hasOwnProperty("a")) {
						return "rgba(" + color.r + "," + color.g + "," +
								color.b + "," + color.a + ")";
					}
					else {
						return "rgb(" + color.r + "," + color.g + "," +
								color.b + ")";
					}
				};

				var setPopupContents = function(popup, previousState,
						currentState) {

					$scope.popupContents = {
						state: $scope.states[previousState.state].popupText,
						start: previousState.timestamp,
						end: currentState.timestamp,
						duration: currentState.timestamp -
								previousState.timestamp,
						info: previousState.plugin_output
					};
					$scope.$apply($scope.popupContents);
				};

				var getTickValues = function(stateChangeList) {

					// Set up tick values, skipping those that are too
					// close together
					var tickValues = [];
					tickValues.push(stateChangeList[0].timestamp);
					for(var i = 1; i < stateChangeList.length; i++) {
						var ts = stateChangeList[i].timestamp;
						if($scope.xScale(ts) >
								$scope.xScale(tickValues[tickValues.length - 1]) +
								$scope.fontSize) {
							tickValues.push(ts);
						}
					}

					return tickValues;
				};

				var displayGrid = function(zoom, sidePadding) {

					// Local layout variables
					var bottomPadding = 3;
					var gridWidth = $scope.svgWidth -
							($scope.stateChangeMargin.left +
							$scope.stateChangeMargin.right);
					var gridHeight = $scope.svgHeight -
							($scope.stateChangeMargin.top +
							$scope.stateChangeMargin.bottom);

					// Adjust the grid group attributes
					d3.select("g#grid").call(zoom);

					// Generate rectangles for state durations
					var rects = d3.select("g#groupRects")
						.selectAll("rect.state")
						.data($scope.stateChangeJSON.data.statechangelist,
								function(d) {
							return d.timestamp;
						})
						.enter()
						.append("rect")
						.attr({
							x: function(d, i) {
								if(i == 0) { return 0; }
								var ts = d.previous.timestamp;
								return $scope.xScale(ts);
							},
							y: function(d, i) {
								if(i == 0) { return 0; }
								var state = d.previous.state;
								return $scope.states[state].y - 1;
							},
							width: function(d, i) {
								if(i == 0) { return 0; }
								var ts = d.previous.timestamp;
								return $scope.xScale(d.timestamp) -
										$scope.xScale(ts);
							},
							height: function(d, i) {
								if(i == 0) { return 0; }
								var state = d.previous.state;
								return gridHeight - $scope.states[state].y -
										bottomPadding;
							},
							fill: function(d, i) {
								if(i == 0) { return "white"; }
								var state = d.previous.state;
								return genColor($scope.states[state].color);
							},
							class: "state"
						});

					if($scope.showPopups) {
						rects.on("mouseover", function(d, i) {
								var mouse = d3.mouse(this);
								var popup = d3.select("#popup")
									.style({
										left: (mouse[0] +
												$scope.stateChangeMargin.left) +
												"px",
										top: (mouse[1] +
												$scope.stateChangeMargin.top) +
												"px"
									});
								setPopupContents(popup, d.previous, d);
								$scope.popupDisplayed = true;
								$scope.$apply("popupDisplayed");
							})
							.on("mouseout", function(d, i) {
								$scope.popupDisplayed = false;
								$scope.$apply("popupDisplayed");
							});
					}

					// Display the x-axis
					var xTranslate = -gridHeight;
					var yTranslate = -($scope.fontSize / 2) - gridHeight;
					var x = d3.select("g#groupXAxis").call($scope.xAxis);
					x.selectAll("text")
						.attr({
							class: "xaxis",
							transform: "rotate(-90) translate(" +
									xTranslate + "," + yTranslate + ")"
						})
						.style({
							"text-anchor": "end"
						});
					x.selectAll("line")
						.attr({
							class: "vLine",
						});
					d3.select("g#groupXAxis").select("path").remove();
				};

				// Handle a succesful availabilty response
				var onAvailabilitySuccess = function(json) {

					// Local layout variables
					var dataPosition = 100;

					// Attach the json to the current scope
					$scope.availabilityJSON = json;

					var totalTime = (json.data.selectors.endtime -
							json.data.selectors.starttime) / 1000;
					switch ($scope.reporttype) {
					case "services":
						$scope.states = $scope.serviceStates;
						$scope.availabilityStates =
								calculateAvailability(json.data.service,
								$scope.serviceStates, totalTime);
						break;
					case "hosts":
						$scope.states = $scope.hostStates;
						$scope.availabilityStates =
								calculateAvailability(json.data.host,
								$scope.hostStates, totalTime);
						break;
					}
				};

				// Display the availability information
				var getAvailability = function(start, end) {

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

					var parameters = {
						query: "availability",
						formatoptions: "enumerate bitmask",
						availabilityobjecttype: $scope.reporttype,
						hostname: $scope.host,
						statetypes: $scope.includesoftstates ?
								"hard soft" : "hard",
						starttime: start,
						endtime: end,
					};
					if($scope.reporttype == "services") {
						parameters.servicedescription = $scope.service;
					}

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

					// Send the query
					$http.get($scope.cgiurl + "/archivejson.cgi?",
							getConfig)
						.error(function(err) {
							// Stop the spinner
							spinner.stop();
							$scope.fetchingAvailability = false;

							console.warn(err);
						})
						.success(function(json) {
							// Stop the spinner
							spinner.stop();
							$scope.fetchingAvailability = false;

							onAvailabilitySuccess(json);
						});

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

				var onStateChangeSuccess = function(json) {

					// Local layout variables
					var sidePadding = 3;

					// Attach the json to the current scope
					$scope.stateChangeJSON = json;

					// Record the time of the query
					$scope.lastUpdate = new Date(json.result.query_time);

					// Add a pseudo end state so drawing will be correct
					var last = $scope.stateChangeJSON.data.statechangelist[json.data.statechangelist.length - 1];
					if(last.timestamp < $scope.stateChangeJSON.data.selectors.endtime) {
						var final = {
							timestamp: $scope.stateChangeJSON.data.selectors.endtime
						};
						$scope.stateChangeJSON.data.statechangelist.push(final);
					}

					// For all but the first entry create a previous
					// entry "pointer"
					for(var i = 1;
							i < $scope.stateChangeJSON.data.statechangelist.length;
							i++) {
						$scope.stateChangeJSON.data.statechangelist[i].previous =
								$scope.stateChangeJSON.data.statechangelist[i-1];
					}

					// Determine whether the state change list is for a
					// host or service
					switch ($scope.reporttype) {
					case "services":
						$scope.states = $scope.serviceStates;
						break;
					case "hosts":
						$scope.states = $scope.hostStates;
						break;
					}

					var gridWidth = $scope.svgWidth -
							($scope.stateChangeMargin.left +
							$scope.stateChangeMargin.right);
					var gridHeight = $scope.svgHeight -
							($scope.stateChangeMargin.top +
							$scope.stateChangeMargin.bottom);

					// Set up the x-axis scale
					$scope.xScale = d3.time.scale()
						.domain([$scope.stateChangeJSON.data.selectors.starttime,
								$scope.stateChangeJSON.data.selectors.endtime])
						.rangeRound([sidePadding,
								gridWidth - sidePadding * 2])
						.clamp(true);

					// Set up x axis
					$scope.xAxis = d3.svg.axis()
						.scale($scope.xScale)
						.orient("bottom")
						.tickSize(gridHeight)
						.tickValues(function() {
							return getTickValues($scope.stateChangeJSON.data.statechangelist);
						})
						.tickFormat(function(d) {
							var ts = new Date(d);
							return $scope.dateFormat(ts);
						});

					// Create the zoom
					var maxExtent = Math.round(($scope.stateChangeJSON.data.selectors.endtime -
								$scope.stateChangeJSON.data.selectors.starttime) / 2000);
					var zoom = d3.behavior.zoom()
						.x($scope.xScale)
						.scaleExtent([1, maxExtent])
						.on("zoomstart", onZoomStart)
						.on("zoom", onZoom)
						.on("zoomend", onZoomEnd);

					// Display the grid
					displayGrid(zoom, sidePadding);
				};

				var displayStateChanges = function() {

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

					var parameters = {
						query: "statechangelist",
						formatoptions: "enumerate bitmask",
						objecttype: ($scope.reporttype == "hosts" ?
								"host" : "service"),
						hostname: $scope.host,
						starttime: $scope.t1,
						endtime: $scope.t2,
						statetypes: $scope.includesoftstates ?
								"hard soft" : "hard",
						backtrackedarchives: $scope.backtrack
					};
					if($scope.reporttype == "services") {
						parameters.servicedescription = $scope.service;
					}

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

					// Send the query
					$http.get($scope.cgiurl + "/archivejson.cgi?",
							getConfig)
						.error(function(err) {
							// Stop the spinner
							spinner.stop();
							$scope.fetchingStateChanges = false;

							console.warn(err);
						})
						.success(function(json) {
							// Stop the spinner
							spinner.stop();
							$scope.fetchingStateChanges = false;

							onStateChangeSuccess(json);
						});

					// Clear the current grid
					d3.select("g#groupRects")
						.selectAll("rect.state")
						.data([])
						.exit()
						.remove();
		
					// Clear the current x-axis	
					if($scope.xAxis != undefined) {
						$scope.xAxis.tickValues([]);
						var x = d3.select("g#groupXAxis")
								.call($scope.xAxis);
						x.selectAll("text").remove();
						x.selectAll("line").remove();
						x.selectAll("path").remove();
					}

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

				var onZoomStart = function() {
					$scope.popupDisplayedOnZoomStart = $scope.popupDisplayed;
					$scope.popupDisplayed = false;
					$scope.$apply("popupDisplayed");
				};

				var onZoom = function() {

					var gridHeight = $scope.svgHeight -
							($scope.stateChangeMargin.top +
							$scope.stateChangeMargin.bottom);
					var xTranslate = -gridHeight;
					var yTranslate = -($scope.fontSize / 2) - gridHeight;
					var domain = $scope.xScale.domain();

					// Update the x-axis
					var x = d3.select("g#groupXAxis");
					x.call($scope.xAxis);
					x.selectAll("text")
						.attr({
							class: "xaxis",
							transform: "rotate(-90) translate(" +
									xTranslate + "," + yTranslate + ")"
						})
						.style({
							"text-anchor": "end"
						})
						.text(function(d, i) {
							// This is a kludge to get the endpoints
							// to update
							if(i == 0) {
								return $scope.dateFormat(new Date(domain[0]));
							}
							else if(d > domain[1]) {
								return $scope.dateFormat(new Date(domain[1]));
							}
							else {
								return $scope.dateFormat(new Date(d));
							}
						});
					x.selectAll("line")
						.attr({
							class: "vLine",
						});
					x.select("path").remove();

					// Update the times for the header
					$scope.startTime = domain[0];
					$scope.$apply("startTime");
					$scope.endTime = domain[1];
					$scope.$apply("endTime");

					// Update the rectangles
					d3.select("g#groupRects")
						.selectAll("rect")
						.attr({
							x: function(d, i) {
								if(i == 0) { return 0; }
								var ts = d.previous.timestamp;
								return $scope.xScale(ts);
							},
							width: function(d, i) {
								if(i == 0) { return 0; }
								var ts = d.previous.timestamp;
								return $scope.xScale(d.timestamp) -
										$scope.xScale(ts);
							}
						});
				}

				var onZoomEnd = function() {

					// Get the new domain
					var domain = $scope.xScale.domain();

					// Determin the new start and end times
					var start = Math.round(domain[0].getTime() / 1000);
					var end = Math.round(domain[1].getTime() / 1000);
					if(start == end) {
						start--;
					}

					// Update the availability display
					getAvailability(start, end);

					// Re-display the popup if it was shown before zooming
					$scope.popupDisplayed = $scope.popupDisplayedOnZoomStart;
					$scope.$apply("popupDisplayed");
					$scope.popupDisplayedOnZoomStart = false;
				};

				var calculateAvailability = function(availability,
						layoutStates, totalTime) {

					var states = {};

					for(var s in layoutStates) {
						if(s == "up") {
							layoutStates[s].totalTime =
									availability.time_up +
									availability.scheduled_time_up;
						}
						else if(s == "down") {
							layoutStates[s].totalTime =
									availability.time_down +
									availability.scheduled_time_down;
						}
						else if(s == "unreachable") {
							layoutStates[s].totalTime =
									availability.time_unreachable +
									availability.scheduled_time_unreachable;
						}
						else if(s == "ok") {
							layoutStates[s].totalTime =
									availability.time_ok +
									availability.scheduled_time_ok;
						}
						else if(s == "warning") {
							layoutStates[s].totalTime =
									availability.time_warning +
									availability.scheduled_time_warning;
						}
						else if(s == "unknown") {
							layoutStates[s].totalTime =
									availability.time_unknown +
									availability.scheduled_time_unknown;
						}
						else if(s == "critical") {
							layoutStates[s].totalTime =
									availability.time_critical +
									availability.scheduled_time_critical;
						}
						else if(s == "nodata") {
							layoutStates[s].totalTime =
									availability.time_indeterminate_nodata +
									availability.time_indeterminate_notrunning;
						}
						layoutStates[s].percentageTime =
								layoutStates[s].totalTime / totalTime;
						states[s] = layoutStates[s];
					}

					return states;
				};
			}
		};
	});
