text: /*
   * Copyright 2012 LinkedIn Corp.
   * 
   * Licensed under the Apache License, Version 2.0 (the "License"); you may not
   * use this file except in compliance with the License. You may obtain a copy of
   * the License at
   * 
   * http://www.apache.org/licenses/LICENSE-2.0
   * 
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
   * License for the specific language governing permissions and limitations under
   * the License.
   */
  
  var svgGraphView;
  azkaban.SvgGraphView = Backbone.View.extend({
  	events: {
  		"click g" : "clickGraph"
  	},
  	initialize: function(settings) {
  		this.model.bind('change:selected', this.changeSelected, this);
  		this.model.bind('change:graph', this.render, this);
  		this.model.bind('resetPanZoom', this.resetPanZoom, this);
  		this.model.bind('change:update', this.handleStatusUpdate, this);
  		this.model.bind('change:disabled', this.handleDisabledChange, this);
  		this.model.bind('change:updateAll', this.handleUpdateAllStatus, this);
  		
  		this.svgns = "http://www.w3.org/2000/svg";
  		this.xlinksn = "http://www.w3.org/1999/xlink";
  		
  		var graphDiv = this.el[0];
  		var svg = $(this.el).find('svg')[0];
  		this.svgGraph = svg;
  		
  		this.contextMenu = settings.rightClick;
  		
  		var gNode = document.createElementNS(this.svgns, 'g');
  		if (settings.topGId) {
  			gNode.setAttribute("id", settings.topGId);
  		}
  		if (settings.clickCallback) {
  			this.clickCallback = settings.clickCallback;
  			$(this.el).click(this.clickCallback);
  		}
  
  		svg.appendChild(gNode);
  		this.mainG = gNode;
  
  		$(svg).svgNavigate();
  	},
  	initializeDefs: function(self) {
  		var def = document.createElementNS(svgns, 'defs');
  		def.setAttributeNS(null, "id", "buttonDefs");
  
  		// ArrowHead
  		var arrowHeadMarker = document.createElementNS(svgns, 'marker');
  		arrowHeadMarker.setAttribute("id", "triangle");
  		arrowHeadMarker.setAttribute("viewBox", "0 0 10 10");
  		arrowHeadMarker.setAttribute("refX", "5");
  		arrowHeadMarker.setAttribute("refY", "5");
  		arrowHeadMarker.setAttribute("markerUnits", "strokeWidth");
  		arrowHeadMarker.setAttribute("markerWidth", "4");
  		arrowHeadMarker.setAttribute("markerHeight", "3");
  		arrowHeadMarker.setAttribute("orient", "auto");
  		var path = document.createElementNS(svgns, 'polyline');
  		arrowHeadMarker.appendChild(path);
  		path.setAttribute("points", "0,0 10,5 0,10 1,5");
  
  		def.appendChild(arrowHeadMarker);
  		
  		this.svgGraph.appendChild(def);
  	},
  	render: function(self) {
  		console.log("graph render");
  
  		var data = this.model.get("data");
  		var nodes = data.nodes;
  		var edges = data.edges;
  		if (nodes.length == 0) {
  			console.log("No results");
  			return;
  		};
  	
  		// layout
  		layoutGraph(nodes, edges);
  		
  		var bounds = {};
  		this.nodes = {};
  		for (var i = 0; i < nodes.length; ++i) {
  			this.nodes[nodes[i].id] = nodes[i];
  		}
  		
  		for (var i = 0; i < edges.length; ++i) {
  			this.drawEdge(this, edges[i]);
  		}
  		
  		this.gNodes = {};
  		for (var i = 0; i < nodes.length; ++i) {
  			this.drawNode(this, nodes[i], bounds);
  		}
  		
  		bounds.minX = bounds.minX ? bounds.minX - 200 : -200;
  		bounds.minY = bounds.minY ? bounds.minY - 200 : -200;
  		bounds.maxX = bounds.maxX ? bounds.maxX + 200 : 200;
  		bounds.maxY = bounds.maxY ? bounds.maxY + 200 : 200;
  		
  		this.assignInitialStatus(self);
  		this.handleDisabledChange(self);
  		this.graphBounds = bounds;
  		this.resetPanZoom(0);
  	},
  	handleDisabledChange: function(evt) {
  		var disabledMap = this.model.get("disabled");
  
  		for(var id in this.nodes) {
  			 var g = this.gNodes[id];
  			if (disabledMap[id]) {
  				this.nodes[id].disabled = true;
  				addClass(g, "disabled");
  			}
  			else {
  				this.nodes[id].disabled = false;
  				removeClass(g, "disabled");
  			}
  		}
  	},
  	assignInitialStatus: function(evt) {
  		var data = this.model.get("data");
  		for (var i = 0; i < data.nodes.length; ++i) {
  			var updateNode = data.nodes[i];
  			var g = this.gNodes[updateNode.id];
  			addClass(g, updateNode.status);
  		}
  	},
  	changeSelected: function(self) {
  		console.log("change selected");
  		var selected = this.model.get("selected");
  		var previous = this.model.previous("selected");
  		
  		if (previous) {
  			// Unset previous
  			var g = this.gNodes[previous];
  			removeClass(g, "selected");
  		}
  		
  		if (selected) {
  			var g = this.gNodes[selected];
  			var node = this.nodes[selected];
  			
  			addClass(g, "selected");
  			
  			var offset = 200;
  			var widthHeight = offset*2;
  			var x = node.x - offset;
  			var y = node.y - offset;
  			
  			$(this.svgGraph).svgNavigate("transformToBox", {x: x, y: y, width: widthHeight, height: widthHeight});
  		}
  	},
  	handleStatusUpdate: function(evt) {
  		var updateData = this.model.get("update");
  		if (updateData.nodes) {
  			for (var i = 0; i < updateData.nodes.length; ++i) {
  				var updateNode = updateData.nodes[i];
  				
  				var g = this.gNodes[updateNode.id];
  				this.handleRemoveAllStatus(g);
  				
  				addClass(g, updateNode.status);
  			}
  		}
  	},
  	handleRemoveAllStatus: function(gNode) {
  		for (var j = 0; j < statusList.length; ++j) {
  			var status = statusList[j];
  			removeClass(gNode, status);
  		}
  	},
  	clickGraph: function(self) {
  		console.log("click");
  		if (self.currentTarget.jobid) {
  			this.model.set({"selected": self.currentTarget.jobid});
  		}
  		if (this.clickCallback) {
  			this.clickCallback(self);
  		}
  	},
  	drawEdge: function(self, edge) {
  		var svg = self.svgGraph;
  		var svgns = self.svgns;
  		
  		var startNode = this.nodes[edge.from];
  		var endNode = this.nodes[edge.target];
  		
  		if (edge.guides) {
  			var pointString = "" + startNode.x + "," + startNode.y + " ";
  
  			for (var i = 0; i < edge.guides.length; ++i ) {
  				edgeGuidePoint = edge.guides[i];
  				pointString += edgeGuidePoint.x + "," + edgeGuidePoint.y + " ";
  			}
  			
  			pointString += endNode.x + "," + endNode.y;
  			var polyLine = document.createElementNS(svgns, "polyline");
  			polyLine.setAttributeNS(null, "class", "edge");
  			polyLine.setAttributeNS(null, "points", pointString);
  			polyLine.setAttributeNS(null, "style", "fill:none;");
  			self.mainG.appendChild(polyLine);
  		}
  		else { 
  			var line = document.createElementNS(svgns, 'line');
  			line.setAttributeNS(null, "class", "edge");
  			line.setAttributeNS(null, "x1", startNode.x);
  			line.setAttributeNS(null, "y1", startNode.y);
  			line.setAttributeNS(null, "x2", endNode.x);
  			line.setAttributeNS(null, "y2", endNode.y);
  			
  			self.mainG.appendChild(line);
  		}
  	},
  	drawNode: function(self, node, bounds) {
  		var svg = self.svgGraph;
  		var svgns = self.svgns;
  
  		var xOffset = 10;
  		var yOffset = 10;
  		
  		var nodeG = document.createElementNS(svgns, "g");
  		nodeG.setAttributeNS(null, "class", "jobnode");
  		nodeG.setAttributeNS(null, "font-family", "helvetica");
  		nodeG.setAttributeNS(null, "transform", "translate(" + node.x + "," + node.y + ")");
  		this.gNodes[node.id] = nodeG;
  		
  		var innerG = document.createElementNS(svgns, "g");
  		innerG.setAttributeNS(null, "transform", "translate(-10,-10)");
  		
  		var circle = document.createElementNS(svgns, 'circle');
  		circle.setAttributeNS(null, "cy", 10);
  		circle.setAttributeNS(null, "cx", 10);
  		circle.setAttributeNS(null, "r", 12);
  		circle.setAttributeNS(null, "style", "width:inherit;stroke-opacity:1");
  		
  		var text = document.createElementNS(svgns, 'text');
  		var textLabel = document.createTextNode(node.label);
  		text.appendChild(textLabel);
  		text.setAttributeNS(null, "x", 4);
  		text.setAttributeNS(null, "y", 15);
  		text.setAttributeNS(null, "height", 10); 
  				
  		this.addBounds(bounds, {
  			minX: node.x - xOffset, 
  			minY: node.y - yOffset, 
  			maxX: node.x + xOffset, 
  			maxY: node.y + yOffset
  		});
  		
  		var backRect = document.createElementNS(svgns, 'rect');
  		backRect.setAttributeNS(null, "x", 0);
  		backRect.setAttributeNS(null, "y", 2);
  		backRect.setAttributeNS(null, "class", "backboard");
  		backRect.setAttributeNS(null, "width", 10);
  		backRect.setAttributeNS(null, "height", 15);
  		
  		innerG.appendChild(circle);
  		innerG.appendChild(backRect);
  		innerG.appendChild(text);
  		innerG.jobid = node.id;
  
  		nodeG.appendChild(innerG);
  		self.mainG.appendChild(nodeG);
  
  		// Need to get text width after attaching to SVG.
  		var computeText = text.getComputedTextLength();
  		var halfWidth = computeText/2;
  		text.setAttributeNS(null, "x", -halfWidth + 10);
  		backRect.setAttributeNS(null, "x", -halfWidth);
  		backRect.setAttributeNS(null, "width", computeText + 20);
  
  		nodeG.setAttributeNS(null, "class", "node");
  		nodeG.jobid=node.id;
  		$(nodeG).contextMenu({
  				menu: this.contextMenu.id
  			},
  			this.contextMenu.callback
  		);
  	},
  	addBounds: function(toBounds, addBounds) {
  		toBounds.minX = toBounds.minX ? Math.min(toBounds.minX, addBounds.minX) : addBounds.minX;
  		toBounds.minY = toBounds.minY ? Math.min(toBounds.minY, addBounds.minY) : addBounds.minY;
  		toBounds.maxX = toBounds.maxX ? Math.max(toBounds.maxX, addBounds.maxX) : addBounds.maxX;
  		toBounds.maxY = toBounds.maxY ? Math.max(toBounds.maxY, addBounds.maxY) : addBounds.maxY;
  	},
  	resetPanZoom : function(duration) {
  		var bounds = this.graphBounds;
  		var param = {x: bounds.minX, y: bounds.minY, width: (bounds.maxX - bounds.minX), height: (bounds.maxY - bounds.minY), duration: duration };
  
  		$(this.svgGraph).svgNavigate("transformToBox", param);
  	}
  });
  