/* A VectorTrace object
–––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––*/
var VectorTrace = Class.create({
	// Constructor
	initialize: function() {
		this.traceArray = new Array();
		this.colorArray = new Array("#B24949", "#23238C", "#009800");
		this.nextAvailableColorIndex = 0;
		this.bounds = new GLatLngBounds(); // The bounding box for the vector trace
	},
	
	// Add a trace to the vector trace
	addTrace: function(trace) {
		this.traceArray.push(trace);
	},

	// Update the Google map with any new trace information
	updateOnMap: function() {
		this.traceArray.each(function(trace, index) {
			trace.updateOnMap();
		});
		map.panTo(vectorTrace.bounds.getCenter());
	},
	
	// Check to see if we have already started a trace from the server
	containsTraceFromServer: function(serverNumber) {
		var containsTraceFromServer = false;
		if(this.traceArray) {
			this.traceArray.each(function(value, index) {
				if(value.serverNumber == serverNumber) {
					containsTraceFromServer = true;
				}
			});
		}
		return containsTraceFromServer;
	},
	
	getTraceArrayIndexByServerNumber: function(serverNumber) {
		var indexToReturn = null;
		if(this.traceArray) {
			this.traceArray.each(function(value, index) {
				if(value.serverNumber == serverNumber) {
					indexToReturn = index;
				}
			});
		}
		return indexToReturn;
	},
	
	getNextAvailableColor: function() {
		var color = this.colorArray[this.nextAvailableColorIndex];
		this.nextAvailableColorIndex++;
		return color;
	}
});

var polylineEncoder = new PolylineEncoder(18, 2, 0.000001);

var Trace = Class.create({
	// Constructor
	initialize: function(serverNumber, traceColor) {
		this.serverNumber = serverNumber;
		this.updatedOnMap = true;
		this.traceColor = traceColor;
		this.traceHopArray = new Array();
		this.markerArray = new Array();
		this.markerHtmlArray = new Array();
		this.lastTraceHopWithAPolylineDrawnToIt = null;
	},
	
	// Add a hop to the trace
	addHop: function(traceHop) {
		this.traceHopArray.push(traceHop);
		this.updatedOnMap = false;
	},
	
	// Update the Google map with the new trace
	updateOnMap: function() {
		// Special starting case to load the map
		if(typeof(map) == "undefined") {
			loadMap();
		}
		for(var i = 0; i < this.traceHopArray.length; i++) {
			// Place the marker for the hop
			if(this.traceHopArray[i]) {
				if(this.traceHopArray[i].placeMarker) {
					if(i >= 1 && (this.traceHopArray[i].gLatLng != this.traceHopArray[i - 1].gLatLng)) {
						this.placeMarker(this.traceHopArray[i], this.traceColor, "Testing");
						this.traceHopArray[i].markerPlaced = true;
					}
					else if(i == 0) {
						this.placeMarker(this.traceHopArray[i], this.traceColor, "Testing");
						this.traceHopArray[i].markerPlaced = true;						
					}
				}
			}
			// Draw a polyline from the last point
			if(this.traceHopArray[i].drawPolylineToHop && this.traceHopArray[i].polylineDrawnToHop == false) { // Do this for all undrawn points, and not the starting point
				// Special starting case to get the first trace hop to draw a polyline from
				if(this.lastTraceHopWithAPolylineDrawnToIt == null) {
					this.lastTraceHopWithAPolylineDrawnToIt = this.getFirstTraceHopToDrawAPolylineFrom();
				}
				var gLatLng1 = this.lastTraceHopWithAPolylineDrawnToIt.gLatLng;
				var gLatLng2 = this.traceHopArray[i].gLatLng; // The current hop that needs a line connecting
				
				this.drawPolyline(gLatLng1, gLatLng2, this.traceHopArray[i].traceThickness, this.traceColor); // Draw the polyline and log it
				this.lastTraceHopWithAPolylineDrawnToIt = this.traceHopArray[i];
				this.traceHopArray[i].polylineDrawnToHop = true;
			}
			
			vectorTrace.bounds.extend(new GLatLng(this.traceHopArray[i].lat, this.traceHopArray[i].lng)); // Update the bounding box, center and zoom on it
			map.setCenter(vectorTrace.bounds.getCenter());
			map.setZoom(map.getBoundsZoomLevel(vectorTrace.bounds));
		}
	},
	
	getFirstTraceHopToDrawAPolylineFrom: function() {
		var firstTraceHopToDrawAPolylineFrom = null;
		this.traceHopArray.each(function(traceHop, index) {
			if(traceHop.drawPolylineFromHop && firstTraceHopToDrawAPolylineFrom == null) {
				firstTraceHopToDrawAPolylineFrom = traceHop;
			}
		});
		return firstTraceHopToDrawAPolylineFrom;
	},
	
	drawPolyline: function(gLatLng1, gLatLng2, traceThickness, traceColor) {
		// Draw a polyline from the last point
		var polyline = polylineEncoder.dpEncodeToGPolyline([gLatLng1, gLatLng2], traceColor, traceThickness, .7, "#FF0000", 4);
		map.addOverlay(polyline);
		// vectorTrace.traceArray[0].drawPolyline(new GLatLng(0, 0), new GLatLng(100, 100), 0, 0);
	},
	
	placeMarker: function(traceHop, markerColor, html) {
		var numberedIcon = new GIcon(baseIcon); // Base icon defined in map initialization
		var markerImage = "marker.png";
		if(markerColor == "#23238C") {
			markerImage = "marker_blue.png";
		}
		else if(markerColor == "#009800") {
			markerImage = "marker_green.png";
		}
		else if(markerColor == "#B24949") {
			markerImage = "marker_red.png";
		}
		
		numberedIcon.image = "/tools/vector-trace/markers/get-marker.php?sourceImage=" + markerImage + "&text=" + traceHop.hop;
		
		// Set up our GMarkerOptions object
		gMarker = new GMarker(traceHop.gLatLng, {icon: numberedIcon});
		map.addOverlay(gMarker);
		traceHop.gMarker = gMarker;
		traceHop.gMarkerHtml = html;
		
		GEvent.addListener(traceHop.gMarker, "click", function() {
			traceHop.gMarker.openInfoWindowHtml(html);
		});		
	}
});

var TraceHop = Class.create({
	// Constructor
	initialize: function(serverNumber, hop, time1, time2, time3, bestTime, ip, hostname, asn, country, lat, lng) {
    	this.serverNumber = serverNumber;
		this.hop = hop;
		this.time1 = time1;
		this.time2 = time2;
		this.time3 = time3;
		this.bestTime = bestTime;
		this.ip = ip;
		this.hostname = hostname;
		this.asn = asn;
		this.country = country;
		this.lat = lat;
		this.lng = lng;
		this.drawPolylineToHop = true; // A boolean that controls whether or not to draw a polyline to the point
		if(this.hop == 1 || (this.lat == 0 && this.lng == 0)) {
			this.drawPolylineToHop = false;
		}
		this.drawPolylineFromHop = true; // A boolean that controls whether or not to draw a polyline from the point		
		this.placeMarker = true;
		this.markerPlaced = false;
		if(this.lat == 0 && this.lng == 0) {
			this.drawPolylineFromHop = false;
			this.placeMarker = false;
		}
		this.polylineDrawnToHop = false;
		this.gLatLng = new GLatLng(this.lat, this.lng);
		this.gMarker = null;
		this.gMarkerHtml = null;
		
		this.lineThickness = 1;
		
		if(this.bestTime < 50) {
			this.lineThickness = 5;
		}
		else if(this.bestTime < 100) {
			this.lineThickness = 20;
		}
		else if(this.bestTime < 200) {
			this.lineThickness = 40;
		}
		else {
			this.lineThickness = 60;
		}		
	}
});

    // A Rectangle is a simple overlay that outlines a lat/lng bounds on the
    // map. It has a border of the given weight and color and can optionally
    // have a semi-transparent background color.
	
	//map.addOverlay(new Rectangle(vectorTrace.bounds))
	
    function Rectangle(bounds, opt_weight, opt_color) {
      this.bounds_ = bounds;
      this.weight_ = opt_weight || 2;
      this.color_ = opt_color || "#888888";
    }
    Rectangle.prototype = new GOverlay();

    // Creates the DIV representing this rectangle.
    Rectangle.prototype.initialize = function(map) {
      // Create the DIV representing our rectangle
      var div = document.createElement("div");
      div.style.border = this.weight_ + "px solid " + this.color_;
      div.style.position = "absolute";

      // Our rectangle is flat against the map, so we add our selves to the
      // MAP_PANE pane, which is at the same z-index as the map itself (i.e.,
      // below the marker shadows)
      map.getPane(G_MAP_MAP_PANE).appendChild(div);

      this.map_ = map;
      this.div_ = div;
    }

    // Remove the main DIV from the map pane
    Rectangle.prototype.remove = function() {
      this.div_.parentNode.removeChild(this.div_);
    }

    // Copy our data to a new Rectangle
    Rectangle.prototype.copy = function() {
      return new Rectangle(this.bounds_, this.weight_, this.color_,
                           this.backgroundColor_, this.opacity_);
    }

    // Redraw the rectangle based on the current projection and zoom level
    Rectangle.prototype.redraw = function(force) {
      // We only need to redraw if the coordinate system has changed
      if (!force) return;

      // Calculate the DIV coordinates of two opposite corners of our bounds to
      // get the size and position of our rectangle
      var c1 = this.map_.fromLatLngToDivPixel(this.bounds_.getSouthWest());
      var c2 = this.map_.fromLatLngToDivPixel(this.bounds_.getNorthEast());

      // Now position our DIV based on the DIV coordinates of our bounds
      this.div_.style.width = Math.abs(c2.x - c1.x) + "px";
      this.div_.style.height = Math.abs(c2.y - c1.y) + "px";
      this.div_.style.left = (Math.min(c2.x, c1.x) - this.weight_) + "px";
      this.div_.style.top = (Math.min(c2.y, c1.y) - this.weight_) + "px";
    }


/* AJAX request to acquire the tracert information
–––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––*/

/* AJAX request to locate a new location on the Google Map
–––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––*/
var oldLatLng = null;
var oldLat = null;
var oldLng = null;
var gLatLng = null;
var distanceInMiles = "0";
var totalDistance = 0;
var lineColor = "";
var bounds = new GLatLngBounds();
var allBounds = new GLatLngBounds();
var traceCount = 0;
var tracePlotCount = 0;
var delay = 2500;
var polylineEncoder = new PolylineEncoder(18, 2, 0.000001);
function traceHops() {
	// Recursion, elegant ^___^
	if(traceCount < tracertJsonData.tracertArray.length) {
		// Pull data out of the array
		traceAddress = tracertJsonData.tracertArray[traceCount][0];
		traceLatency = tracertJsonData.tracertArray[traceCount][1];
		traceStatus = tracertJsonData.tracertArray[traceCount][2];
		traceHostName = tracertJsonData.tracertArray[traceCount][3];
		traceBaseDomain = tracertJsonData.tracertArray[traceCount][4];
		traceCountryCode = tracertJsonData.tracertArray[traceCount][5];
		traceCountryFlag = tracertJsonData.tracertArray[traceCount][6];
		traceCountryName = tracertJsonData.tracertArray[traceCount][7];
		traceRegion = tracertJsonData.tracertArray[traceCount][8];
		traceCity = tracertJsonData.tracertArray[traceCount][9];
		traceLatitude = tracertJsonData.tracertArray[traceCount][10];
		traceLongitude = tracertJsonData.tracertArray[traceCount][11];						
		traceAreaCode = tracertJsonData.tracertArray[traceCount][12];
		tracePostalCode = tracertJsonData.tracertArray[traceCount][13];

		// Only plot data if it is not a guess in the US or if the lat/long is known
		if(traceStatus == "Guess" && traceCountryName == "United States" || traceStatus == "Guess" && traceLatitude == "Unknown") {
			delay = 10;
		}
		else {
			// Special starting case to keep track of last point
			if(oldLatLng == null) {
				oldLatLng = new GLatLng(traceLatitude, traceLongitude);
				oldLat = traceLatitude;
				oldLng = traceLongitude;
			}
		
			// Create a new Google point from trace latitude and longtide
			gLatLng = new GLatLng(traceLatitude, traceLongitude);
			
			// Find the distance from the last point in miles
			distanceInMiles = gLatLng.distanceFrom(oldLatLng) * 0.000621371192237334;
			totalDistance += distanceInMiles;
			distanceInMiles = distanceInMiles.toFixed(1); // Round to first decimal
		
			// Create bounding box around new point and last point
			bounds.extend(gLatLng);
			// Create bounding box around all points
			allBounds.extend(gLatLng);
			
			// Draw a polyline from the last point
			newPolyline = polylineEncoder.dpEncodeToGPolyline([gLatLng, oldLatLng], "#000000", 4, .7, "#FF0000", 4);
			//map.addOverlay(new GPolyline([gLatLng, oldLatLng], "#000000", 4, .7, "#FF0000", 4));
			map.addOverlay(newPolyline);

			// Zoom to appropriate level
			if(tracePlotCount == 0) { // Special start case
				map.setZoom(7);
				map.setCenter(gLatLng);				
			}
			else if((distanceInMiles != 0) && (traceCount != (tracertJsonData.tracertArray.length - 1))) {
				map.setZoom(map.getBoundsZoomLevel(bounds) - 1);
				map.panTo(gLatLng);
			}
		
			// Change dreamhost to yougetsignal.com on host traces
			//if(traceBaseDomain == "dreamhost.com" && traceCount == 0) {
			//	traceBaseDomain = "yougetsignal.com";
			//}
		
			// Create a marker
			markerHTML = traceAddress + "<br />" + traceBaseDomain + "<br />" + traceCity + ", " + traceRegion + "<br />"  + traceCountryName + "&nbsp;" + traceCountryFlag + "<br />";
			placeMarker(gLatLng, (traceCount + 1), traceLatency, markerHTML);
			
			// Set the new point to the old point
			if(oldLat == traceLatitude && oldLng == traceLongitude && traceCount != 0) {
				delay = 10;
			}
			else {
				delay = 1750;
			}
			oldLatLng = gLatLng;
			oldLat = traceLatitude;
			oldLng = traceLongitude;
			tracePlotCount++;

			// Make sure the bounds are just between the new point and the last point
			bounds = new GLatLngBounds();
			bounds.extend(gLatLng);
			bounds.extend(oldLatLng);
		}
		traceCount++;
		
		// Notify the user of the current point
		if(traceLatitude == "Unknown" || (traceStatus == "Guess" && traceCountryName == "United States")) {
			document.getElementById('traceInfo').innerHTML += "<div id=\"traceHop" + traceCount + "\" style=\"display: block;\">" + traceCountryFlag + " <b>" + traceCount + "</b>. " + traceBaseDomain + "</div>";
		}
		else {
			document.getElementById('traceInfo').innerHTML += "<div id=\"traceHop" + traceCount + "\" style=\"display: block; cursor: pointer;\" onclick=\"globalMarkers[" + traceCount + "].openInfoWindow(globalMarkersHTML[" + traceCount + "]);\">" + traceCountryFlag + " <b>" + traceCount + "</b>. " + traceBaseDomain + "</div>";
		}
		//var myEffect = new Effect.Appear('traceHop' + traceCount, {queue: {position: 'end', scope: 'traceScope'}});

		// Special ending case
		if(traceCount == (tracertJsonData.tracertArray.length)) {
			map.setZoom(map.getBoundsZoomLevel(allBounds));
			map.setCenter(allBounds.getCenter());
			document.getElementById('traceInfo').innerHTML += "<br />~" + addCommas(totalDistance.toFixed(0)) + " miles traveled<br /><br /><a onclick=\"redrawTrace();\">Redraw Trace</a>";
			document.getElementById('hostTraceButton').disabled = false;
			document.getElementById('proxyTraceButton').disabled = false;
		}

		// Recursive call
		setTimeout("traceHops()", delay);
	} // End recursive if stop
}


/* Function to retrace the last data set
–––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––*/
function redrawTrace() {
	if(tracertJsonData) {
		// Reset everything
		bounds = new GLatLngBounds(); // Reset the bounds of the map
		allBounds = new GLatLngBounds(); // Reset all bounds of the map
		oldLatLng = null; // Remove the previous position history
		traceCount = 0; // Restart counter
		tracePlotCount = 0; // Counts actual plotted points, not the size of the trace array
		totalDistance = 0;
		map.clearOverlays(); // Clear all previous tracert information off of the map
		map.setCenter(new GLatLng(0, 0), 1); // Jump back to the center of the map
		document.getElementById('traceInfo').innerHTML = "";
		document.getElementById('hostTraceButton').disabled = true;
		document.getElementById('proxyTraceButton').disabled = true;
		traceHops();
	}
	else {
		alert("Please perform a trace first.");
	}
}

/* Function to place a new marker on the map
–––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––*/
var speed = "";
var globalMarkers = new Array();
var globalMarkersHTML = new Array();
function placeMarker(point, traceCount, traceLatency, html) {
	var numberedIcon = new GIcon(baseIcon);

	if(traceLatency <= 150) {
		speed = "fast";
	}
	else if(traceLatency <= 300) {
		speed = "fast";
	}
	else {
		speed = "fast";
	}
		
	numberedIcon.image = "/php/markers/numbered_marker.php?image=marker_" + speed + ".png&text=" + traceCount;
	
	// Set up our GMarkerOptions object
	markerOptions = { icon:numberedIcon };
	var gMarker = new GMarker(point, markerOptions);
	map.addOverlay(gMarker);	
	globalMarkers[traceCount] = gMarker;
	globalMarkersHTML[traceCount] = html;
	
	GEvent.addListener(gMarker, "click", function() {
		gMarker.openInfoWindowHtml(html);
	});
}

/* Tracert timer
–––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––*/
var ms = 0;
var state = 0;
function timerStartStop() {
	if(state == 0) {
		state = 1;
		then = new Date();
		then.setTime(then.getTime() - ms);
	}
	else {
		state = 0;
		now = new Date();
		ms = now.getTime() - then.getTime();
		document.getElementById('traceTimer').innerHTML = "<p style=\"line-height: 0;\">&nbsp;</p>";
		//document.getElementById('traceTimer').innerHTML = "<p>" + (ms / 1000).toFixed(1) + " seconds elapsed.</p>";
		//new Effect.Highlight('traceTimer');
   }
}
function timerReset() {
	state = 0;
	ms = 0;
	document.getElementById('traceTimer').innerHTML = "";
}
function timerDisplay() {
	setTimeout("timerDisplay();", 100);
	if(state == 1) {
			now = new Date();
			ms = now.getTime() - then.getTime();
			document.getElementById('traceTimer').innerHTML  = "<p>" + (ms / 1000).toFixed(1) + " seconds elapsed.</p>";
	}
}
timerDisplay();

/* Submit AJAX request using enter key
–––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––*/
function submitUsingEnter(e) {
	var characterCode;
	if(e && e.which){ // If which property of event object is supported (NN4)
		e = e;
		characterCode = e.which // Character code is contained in NN4's which property
	}
	else {
		e = event;
		characterCode = e.keyCode; // Character code is contained in IE's keyCode property
	}
	
	if(characterCode == 13){ // If generated character code is equal to ASCII 13 (the enter key)
		if(document.getElementById('hostTraceButton').disabled == false) {
			tracert(document.getElementById('remoteAddress').value, 'Host');
		}
		return false;
	}
	else {
		return true;
	}
}

/* Function to fix the page scroll issue with map zoom
–––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––*/
function wheelevent(e) {
	if (!e){
		e = window.event
	}
	if (e.preventDefault){
		e.preventDefault()
	}
	e.returnValue = false;
}

/* Function for additional text to appear
–––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––*/
var showMoreInfoSwitch = "off";
function moreInfoSwitch() {
	if(showMoreInfoSwitch == "off") {
		new Effect.BlindDown('moreToolInfo', { afterFinish: redrawElement, queue: 'end' });
		showMoreInfoSwitch = "on";
		document.getElementById('moreInfoSwitch').innerHTML = "<a onclick=\"moreInfoSwitch();\">Less about this tool</a>.";
	}
	else {
		new Effect.BlindUp('moreToolInfo', { queue: 'end' });		
		showMoreInfoSwitch = "off";
		document.getElementById('moreInfoSwitch').innerHTML = "<a onclick=\"moreInfoSwitch();\">More about this tool</a>.";
	}
}
// I wasted an hour of my life on this. Redraw the element so IE renders the page correctly after the effect completes
function redrawElement() {
	myElement = document.getElementById('moreToolInfo');
	myElement.style.display = 'none';
	myElement.style.display = 'block';
}

/* Add and strip slashes
–––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––*/
function addSlashes(str) {
	str=str.replace(/\'/g,'\\\'');
	str=str.replace(/\"/g,'\\"');
	str=str.replace(/\\/g,'\\\\');
	str=str.replace(/\0/g,'\\0');
	return str;
}
function stripSlashes(str) {
	str=str.replace(/\\'/g,'\'');
	str=str.replace(/\\"/g,'"');
	str=str.replace(/\\\\/g,'\\');
	str=str.replace(/\\0/g,'\0');
	return str;
}
function addCommas(nStr) {
	nStr += '';
	x = nStr.split('.');
	x1 = x[0];
	x2 = x.length > 1 ? '.' + x[1] : '';
	var rgx = /(\d+)(\d{3})/;
	while (rgx.test(x1)) {
		x1 = x1.replace(rgx, '$1' + ',' + '$2');
	}
	return x1 + x2;
}