/**
 * @author Kirk Ouimet
 * @website http://www.yougetsignal.com/tools/visual-tracert/
 * @copyright 2008 Kirk Ouimet Design. All rights reserved.
 */

/* AJAX request to initialize the Google Map to the default remote address
–––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––*/
var map;
var baseIcon;
function loadMap() {
	if(GBrowserIsCompatible()) {
		map = new GMap2(document.getElementById("map"));
		map.enableContinuousZoom();
		map.enableDoubleClickZoom();
		map.enableScrollWheelZoom();
		//map.addControl(new GLargeMapControl());
		map.addControl(new GSmallMapControl());
		map.addControl(new GMapTypeControl());
		gKeyboardHandler = new GKeyboardHandler(map);
		map.setCenter(new GLatLng(0, 0), 1);
		// Fix the page scroll on zoom issue
		GEvent.addDomListener(map.getContainer(), "DOMMouseScroll", wheelevent);
		map.getContainer().onmousewheel = wheelevent; 		
        // Create a base icon for all markers
        baseIcon = new GIcon();
        baseIcon.shadow = "/img/map/shadow.png";
        baseIcon.iconSize = new GSize(20, 20);
        baseIcon.shadowSize = new GSize(30, 20);
        baseIcon.iconAnchor = new GPoint(10, 20);
        baseIcon.infoWindowAnchor = new GPoint(10, 20);
        baseIcon.infoShadowAnchor = new GPoint(10, 20);
	}
}

function loadTrace(trace) {
	if(trace != "") {
		$('remoteAddress').value = trace;
		tracert(trace, 'Proxy');
	}
}

/* AJAX request to acquire the tracert information
–––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––*/
var tracertJsonData;
var ajaxRequest = new Ajax.Request("",{});
var traceType = "";
function tracert(remoteAddress, funcTraceType) {
	remoteAddress = remoteAddress.replace("http://", "");
	remoteAddress = remoteAddress.replace("https://", "");
	remoteAddress = remoteAddress.replace("ftp://", "");
	
	traceType = funcTraceType;
	// Reset everything
	timerReset(); // Reset the tracert timer
	timerStartStop(); // Start the tracert timer
	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('hostTraceButton').disabled = true;
	document.getElementById('proxyTraceButton').disabled = true;

	// Notify the user a request is in process
	document.getElementById('traceHeader').innerHTML = "<p>" + traceType + " tracing path to<br /><b>" + remoteAddress + "</b></p><br />";
	if(traceType == "Host") {
		document.getElementById('traceInfo').innerHTML = "<br /><a onclick=\"cancelTrace();\">Cancel Trace</a><br /><br /><p>You are currently performing a host trace. This type of trace begins at the host, YouGetSignal.com (served by DreamHost), and identifies each hop to the specified remote address.<br /><br />A host trace will usually take under 30 seconds to complete.</p>";
	}
	else { // Proxy trace type
		document.getElementById('traceInfo').innerHTML = "<br /><a onclick=\"cancelTrace();\">Cancel Trace</a><br /><br /><p>You are currently performing a proxy trace. This type of trace begins at your network, traces to YouGetSignal.com (hosted by DreamHost), and then proceeds to the specified remote address.<br /><br />A proxy trace will usually take under 60 seconds to complete.</p>";
	}

	// Perform the AJAX request
	ajaxRequest.transport.abort(); // Cancel the previous request		
	url = "/tools/visual-tracert/php/get-tracert-json.php";
	ajaxRequest = new Ajax.Request(url, {
		method: 'post',
		parameters: {'remoteAddress': remoteAddress, 'traceType': traceType},
		onFailure: function(transport) {
			cancelTrace();
			document.getElementById('traceHeader').innerHTML = "<p><span style=\"color: #DF454B;\">Service currently unavailable.</span></p>";
		},
		onException: function(transport) {
			cancelTrace();
			document.getElementById('traceHeader').innerHTML = "<p><span style=\"color: #DF454B;\">Service currently unavailable.</span></p>";
		},			
		onSuccess: function(transport) {
			tracertJsonData = transport.responseText.evalJSON(); // Place the results into JSON
			timeout = 0; // Stop the timer
			timerStartStop();
			if(tracertJsonData.status != "Fail") {
				// Notify the user the request has finished
				document.getElementById('traceHeader').innerHTML = "<p><b>" + traceType + " trace to</b><br /><b>" + remoteAddress + "</b><br /><b>" + tracertJsonData.tracertArray.length + " hops / " + (ms / 1000).toFixed(1) + " seconds</b></p>";
				document.getElementById('traceInfo').innerHTML = "";
				traceHops();
			}
			else {
				// Reset everything
				document.getElementById('traceHeader').innerHTML = "<p><b style=\"color: #FF0000;\">" + remoteAddress + "</b> is an invalid remote address.</p>";
				document.getElementById('traceInfo').innerHTML = "";
				document.getElementById('traceTimer').innerHTML = "";
				timerReset(); // Reset the tracert timer
				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
				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 
				ajaxRequest.transport.abort(); // Cancel the previous request	
				document.getElementById('hostTraceButton').disabled = false;
				document.getElementById('proxyTraceButton').disabled = false;				
			}
		}
	});
}

/* AJAX request to locate a new location on the Google Map
–––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––*/
function cancelTrace() {
	// Reset everything
	timerReset(); // Reset the tracert timer
	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
	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 
	ajaxRequest.transport.abort(); // Cancel the previous request	
	document.getElementById('hostTraceButton').disabled = false;
	document.getElementById('proxyTraceButton').disabled = false;
	document.getElementById('traceHeader').innerHTML = "<p>Trace cancelled.<br /><br />Awaiting trace initialization.</p>"; 
	document.getElementById('traceInfo').innerHTML = "";
	document.getElementById('traceTimer').innerHTML = "";
}

/* 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];
		traceResponseTime = tracertJsonData.tracertArray[traceCount][14];

		// 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";
			//}
			
			if(traceResponseTime != '') {
				traceResponseTime = ' (' + traceResponseTime + ' ms)';
			}
		
			// Create a marker
			markerHTML = traceAddress + traceResponseTime + "<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;
}