/******************************************************************************
******************************************************************************
// UTILITY SECTION                                                          //
// Copyright 2007-2008 Collaborative Bike Map Project
******************************************************************************
******************************************************************************/

/***************************************************************
****
* Set page layout -- looks at globals
********************************************************************/
function setLayout() {

    // If editor site
	if (Site == SITE_EDITOR) {		
		// show/hide various divs
	    setStyle ('leftcol', 'visibility', 'visible');	      	
	    setStyle ('headermain', 'visibility', 'visible');
	    setStyle ('viewerheader', 'visibility', 'hidden');
	    setStyle ('infobox', 'visibility', 'hidden');	      	
		// move map to middle
	    setStyle ('mapcont', 'left', 300 + 'px');
	}
	// Else viewer site
	else {
		// show/hide various divs
		setStyle ('leftcol', 'visibility', 'hidden');
	    setStyle ('headermain', 'visibility', 'hidden');
	    setStyle ('viewerheader', 'visibility', 'visible');
	    setStyle ('infobox', 'visibility', 'visible');

	    if (Layout == LAYOUT_LONG || Layout == LAYOUT_SHORT) {      // floating infobox
	        setStyle ('infobox', 'top', 204 + 'px');
			setStyle ('infobox', 'width', 220 + 'px');  
	        setStyle ('infobox', 'border', 'solid black 2px');
	        setStyle ('infobox', 'padding', '10px');
            setStyle ('mapcont', 'left', 0);	  // left align map
            if (Layout == LAYOUT_LONG)
				setStyle ('infobox', 'height', 500 + 'px');
			else
				setStyle ('infobox', 'height', 200 + 'px');
		}	
	    else if (Layout == LAYOUT_DOCKED) {           // docked infobox
			// move map to middle
			setStyle ('mapcont', 'left', 300 + 'px');
	        setStyle ('infobox', 'left', 0);          // dock infobox to left
	        setStyle ('infobox', 'top', 170 + 'px');
			setStyle ('infobox', 'width', 280 + 'px');  
	        setStyle ('infobox', 'border', 'solid black 1px');
	        setStyle ('infobox', 'padding', '3px 10px');
	        // infobox height will be determined dynamically
		}
	}
	
	// Set dimensions based on screen size
	handleResize();
}


/****************************************************************/
// Set div dimensions based on screen size
/****************************************************************/
function handleResize() {

	var height = windowHeight();
	var width = windowWidth();
	// dumpText ("height = " + height + ", width = " + width);

    setStyle('header', 'width', (width) + 'px');
    setStyle('leftcol', 'height', (height - 173) + 'px');	       
    setStyle('leftcol', 'top', (170) + 'px');	       
    setStyle('mapcont', 'height', (height - 175) + 'px');	      
    setStyle('mapcont', 'top', (170) + 'px');	       
    setStyle('mapdiv', 'height', (height - 175) + 'px');	       
    
    // Set map width based on position...
    // If map not left-aligned...
    if (Site == SITE_EDITOR || Layout == LAYOUT_DOCKED) {
        setStyle('mapcont', 'width', (width - 300) + 'px');	       
		setStyle('mapdiv', 'width', (width - 305) + 'px');	  
	}
	// else map is left-aligned
	else { 
        setStyle('mapcont', 'width', (width) + 'px');	       
		setStyle('mapdiv', 'width', (width - 5) + 'px');
	}
	
	// Site-specific divs
	if (Site == SITE_VIEWER) {    	// Viewer-specific
	    setStyle('viewerheader', 'width', (width - 312) + 'px');	  
		if (Layout == LAYOUT_DOCKED) 
			setStyle('infobox', 'height', (height - 180) + 'px');		
		else 
			setStyle('infobox', 'left', (width - 256) + 'px');       		
	}
	else {                      	// Editor-specific
		setStyle('headermain', 'width', (width - 312) + 'px');
	}
	
}  

/************************************
// Get window height
************************************/
function windowHeight() {
	// Standard browsers
	if (self.innerHeight)
		return self.innerHeight;
	// IE 6+
	if (document.documentElement && document.documentElement.clientHeight)
		return y = document.documentElement.clientHeight;
	// just in case
	return 0;
}

/************************************
// Get window width
************************************/
function windowWidth() {
  	// Standard browsers
	if (self.innerWidth)
		return self.innerWidth;
	// IE 6+
	if (document.documentElement && document.documentElement.clientWidth)
		return y = document.documentElement.clientWidth;
	// just in case  
	return 0;
}

/************************************
// Open popup prompt window
************************************/
function openPrompt (innerHTML) {
	var contentdiv = document.getElementById ("popupcontent");
	contentdiv.innerHTML = innerHTML;
	setStyle ("popup", "display", "inline");
	setStyle ("overdiv", "display", "inline");
}

/************************************
// Close popup prompt window
************************************/
function closePrompt() {
	setStyle ("popup", "display", "none");
	setStyle ("overdiv", "display", "none");
}


/*******************************************************************
// Determine whether specified div is shown or hidden.
// Return true if shown, false if hidden.
********************************************************************/
function divShown (div) {
	// debugger;
	/* var obj = document.getElementById ("oppseg");
	var ar = document.getElementsByTagName ("div");
	alert (ar.length);
	var nm1, nm2, i;
	var msg = "";
	for (i=0; i<ar.length; i++) {
		msg = msg + i + ": " + ar[i].id + " / ";
	} 
	alert (msg); */
	return (getStyle (div, "display") != "none");
}


/*******************************************************************
// Generate an array of arrays (array of GLatLng) from pointlist
// to support polyline display.  firstpoint and lastpoint indicate
// where to start and end the generation (assumed to be in linked list).
// Offset indicates how far to offset the line (in pixels) from the 
// true line between each pair of points.
// TO DO: Make this routine do both sides at once, so some computations
//        only need to be done once.  Use radians instead of degrees.
********************************************************************/
function getLatLngLists (firstpoint, lastpoint, offset) {
	var outlists = [];
	var pointlist = [];
	var m = [];
	var t = [];
	var c = [];
	var new_p1 = [];
	var new_p2 = [];
	var angle = [];
	var pntct;
	var stoppnt, pnt;
	var line, currlist;
	var glatlng;
	var scale, abs_delta, angle_delta;
	var diffx, diffy, offsetx, offsety;

	// Determine bounds
	stoppnt = lastpoint.next;
	
	// Put unaltered points into array of GLatLng 
	pnt = firstpoint;
	currlist = 0;
	pntct = 0;
	while (pnt != stoppnt) {
		pointlist[pntct++] = new GLatLng (pnt.lat, pnt.lng);
		pnt = pnt.next;
	}
	
	// If zero offset - just return the array we already created
	if (offset == 0) {
		outlists[0] = pointlist;      // copy array into output list of lists
		return (outlists);
	}
	
	// Else non-zero offset
	else {			
		//Set current list = empty array
		outlists[currlist] = [];	
		//debugger;
		
		// Loop through line segments (pairs of adjacent points)
		for (line=0; line<pntct-1; line++) {		
			
			// Calculate slope/angle, x & y deltas, x & y offsets
			p1 = Map.fromLatLngToDivPixel(pointlist[line]);
			p2 = Map.fromLatLngToDivPixel(pointlist[line+1]);
			diffx = p2.x - p1.x;
			diffy = p2.y - p1.y;
			scale = offset/Math.sqrt(diffy*diffy + diffx*diffx);     // thus accounts for sign of offset
			offsetx = diffy * scale;
			offsety = -(diffx * scale);			
			angle[line] = Math.atan2 (diffy, diffx) * 180/Math.PI;
			//offsetx = offset * Math.cos (angle);       // try offset = 4       
			//offsety = offset * Math.sin (angle);			
			// Calculate points using new offset			
			new_p1[line] = new GPoint (p1.x + offsetx, p1.y + offsety);     // pixel coords
			new_p2[line] = new GPoint (p2.x + offsetx, p2.y + offsety);     // pixel coords
			//p1[line] = new GPoint (p1.x - offsetx, p1.y - offsety);     // pixel coords
			//p2[line] = new GPoint (p2.x - offsetx, p2.y - offsety);     // pixel coords
			// Compute line equation y = mx + c OR x = ty + c (use t if m too big)
			m[line] = t[line] = null;
			if (diffx < 0.01 || (diffy/diffx > 100)) {    // if m would be too big
				if (diffy > 0.01) {                      // if points aren't super-close together
					t[line] = diffx/diffy;       // x = yt + c
					c[line] = new_p1[line].x - (t[line] * new_p1[line].y);     // compute constant c
				}
				else      // else points very close together - just use zero slope
					m[line] = 0;             
			}
			else {       // else m is reasonable - use it
				m[line] = diffy/diffx;
				c[line] = new_p1[line].y - (m[line] * new_p1[line].x);	      // compute constant c
			}
				
			// if first line segment in loop
			if (line == 0) {
				// push first point with offsets onto current array
				glatlng = Map.fromDivPixelToLatLng (new_p1[line]);
				outlists[line].push (glatlng);
			}
			
			// else line segment isn't the first in loop
			else {
			
				// get delta of angles (with previous line segment) in degrees (round to nearest hundredth degree)
				angle_delta = (Math.round((angle[line] - angle[line-1]) * 100)) / 100;
				if (angle_delta > 360)
					angle_delta = angle_delta - 360;
				if (angle_delta < -360)
					angle_delta = angle_delta + 360;
			
				// if angle delta and whether inside or outside requires intersection
				abs_delta = Math.abs (angle_delta);
				
				if (false) 				
				/* if ((abs_delta >= 60 && abs_delta <= 90) || 
					(abs_delta > 3 && abs_delta < 60 && offset > 0) ||
					(abs_delta < 3 && abs_delta >-60 && offset < 0))  */
				{
					// compute intersection of this segment with previous one (p ={x,y})
					p = computeLineInt (m[line-1], t[line-1], c[line-1], m[line], t[line], c[line]);	 // returns {x,y}
					glatlng = Map.fromDivPixelToLatLng (new GPoint(p.x,p.y));
					outlists[line].push (glatlng);				// push intersection onto current array
				}

				// else no intersection warranted
				else {														
					// push last segment's end point onto current array
					glatlng = Map.fromDivPixelToLatLng (new_p2[line-1]);
					outlists[currlist].push (glatlng);
					
					// create new array, make it current
					outlists[++currlist] = [];
					
					// push this segment's start point onto new current array
					glatlng = Map.fromDivPixelToLatLng (new_p1[line]);
					outlists[currlist].push (glatlng);
				}
			}
			
			// IF line segment is the last one in loop, do final processing
			// NOTE: This is not an "else" clause
			if (line == pntct - 2) {
				// push this segment's end point onto new current array
				glatlng = Map.fromDivPixelToLatLng (new_p2[line]);
				outlists[currlist].push (glatlng);
			}
		}       // next line segment
	}		// end else offset != 0	

	return (outlists);
} 


/*******************************************************************
// Compute intersection between two lines.  Output object having two 
// elements x and y.  Return null if slopes of same type are too close together.
********************************************************************/
function computeLineInt (m1, t1, c1, m2, c2, t2) {
	var p;
	
	// Case 1: m defined for both lines (y-slope)
	if (m1 != null && m2 != null) {
		if (Math.abs(m1 - m2) < 0.001) {
			p = null;
		}
		p.x = (c1 - c2) / (m1 - m2);
		p.y = (m * p.x) + c1;
	}
	
	// Case 2: m defined for line 1, t for line 2
	else if (m1 != null && t2 != null) {
		p.x = (t2 + c2) / (1 - t2 * m1);
		p.y - (m1 * p.x) + c1;
	}
	
	// Case 3: m defined for line 2, t for line 1
	else if (m2 != null && t1 != null) {
		p.x = (t1 + c1) / (1 - t1 * m2);
		p.y - (m2 * p.x) + c2;
	}
	
	// Case 4: t defined for both lines (x-slope)
	else if (t1 != null && t2 != null) {
		if (Math.abs(t1 - t2) < 0.001) {
			p = null;
		}
		p.y = (c2 - c1) / (t1 - t2);
		p.y = (m * p.x) + c1;
	}	
	
	if (p == null)
		alert ("computeLineInt finding no intersection");
		
	return p;
}

/*******************************************************************
// Generate latlnglist (array of GLatLng) from route pointlist
// to support polyline display.  If firstpoint is specified,
// generate latlnglist for just the sublist starting at firstpoint.
// If both firstpoint and lastpoint are specified, use sublist between 
// and including those two points. If firstpoint is specified, the 
// rte parameter is ignored.  Returns empty list if no points.
********************************************************************/
/* 
function getLatLngList (rte, firstpoint, lastpoint) {
	var outlist = [];
	var i = 0;
	var startpoint, stoppnt;

	// Determine bounds
	if (firstpoint == undefined) {            // Do entire rte.pointlist 
		if (rte.pointlist != null) 
			startpnt = rte.pointlist.firstnode;
		else
			startpnt = null;
		stoppnt = null;
	}
	else {                                      // Do list between firstpoint and lastpoint
		startpnt = firstpoint;
		if (lastpoint == null) 
			stoppnt = null;
		else
			stoppnt = lastpoint.next;           // Set just past where we want to stop
	}

	// Loop through points
	var pnt = startpnt;
	while (pnt != stoppnt) {
		outlist[i++] = new GLatLng (pnt.lat, pnt.lng);
		pnt = pnt.next;
	}

	return (outlist);
} 
*/


/*******************************************************************/
// Open the online help page
/********************************************************************/
function openHelp () {
	openWin ('helpfile.htm', 700, 500);
}



/******************************************************************************
// openWin - Open a new window
******************************************************************************/
function openWin (url, h, w) {
	var features;
	if (h == undefined)
		h = 400;
	if (w == undefined)
		w = 400;
	features = "width=" + w + ",height=" + h + ",scrollbars=yes,status=yes,resizable=yes,toolbar=yes";
	/* alert (">>" + features + "<<"); */
	// features = "width=500,height=500");
	
	// JSC -- removed for demo
	// alert (XmlWindow);
    
	if (XmlWindow == null || XmlWindow.closed) {
		XmlWindow = window.open (url, "textoutput", features);
	}
	XmlWindow.focus();
	var doc = XmlWindow.document;
	return XmlWindow;
}

/******************************************************************************
// Get HTML elements by class name.
// This is from http://textsnippets.com/posts/show/686 
// Other options: 
//     http://www.robertnyman.com/2005/11/07/the-ultimate-getelementsbyclassname/
//     http://www.dustindiaz.com/top-ten-javascript/
******************************************************************************/
function getElementsByClassName(oElm, strTagName, strClassName) {
	var arrElements = (strTagName == "*" && document.all)? document.all : oElm.getElementsByTagName(strTagName);
	var arrReturnElements = new Array();
	strClassName = strClassName.replace(/\-/g, "\\-");
	var oRegExp = new RegExp("(^|\\s)" + strClassName + "(\\s|$)");
	var oElement;
	for(var i=0; i<arrElements.length; i++) {
		oElement = arrElements[i];
		if(oRegExp.test(oElement.className)) {
			arrReturnElements.push(oElement);
		}   
	}
    return (arrReturnElements);
}

/******************************************************************************/
// Draw parallel line
/*******************************************************************************/
function computeParallel (glatlng1, glatlng2, offset, newlatlng1, newlatlng2) {
	// Ditch these two lines...
	//var glatlng1 = new GLatLng (glatlng1.lat, glatlng1.lng);
	//var glatlng2 = new GLatLng (glatlng2.lat, glatlng2.lng);
	// debugger;	
	var p1 = Map.fromLatLngToDivPixel(glatlng1);
	var p2 = Map.fromLatLngToDivPixel(glatlng2);
	var diffx = p2.x - p1.x;
	var diffy = p2.y - p1.y;
	var angle = Math.atan2 (diffx, diffy);
	var offsetx = offset * Math.cos (angle);       // try offset = 4       
	var offsety = offset * Math.sin (angle);
	var new_p1 = new GPoint (p1.x + offsetx, p1.y + offsety);
	var new_p2 = new GPoint (p2.x + offsetx, p2.y + offsety);
	newlatlng1 = Map.fromDivPixelToLatLng (new_p1);
	newlatlng2 = Map.fromDivPixelToLatLng (new_p2);
	// Keep these commented out but save...
	// Plot parallel as polyline
	//var overlay = new GPolyline (new Array (newlatlng1, newlatlng2), "#00AEED", 4, POLYLINE_OPACITY_DEFAULT);
	//Map.addOverlay(overlay);
}


/******************************************************************************/
// Trim leading and trailing spaces from string.  If input is null, return empty string.
/*******************************************************************************/
function trim (str) {
	if (str != null) {
		str.replace(/^[\s\n\t]*/, '').replace(/[\s\n\t]*$/, '');     // trim white space
		return str;
	}
	else
		return "";
}

/******************************************************************************/
// Check if string (or other variable) is empty or null or all white space.  
// Useful for checking field contents.
/*******************************************************************************/
function isEmpty (str) {
	if (str != null) {
		patt = /[^\s\n\t]+/;     // find at least one non-white space character
		return !(patt.test (str));     
	}
	else
		return true;
}

/******************************************************************************/
// Check if string (or other variable) is a number.  
// Matches n or n. or n.n or .n, with optional leading sign (+ or -).
// Empty string not considered a number.
/*******************************************************************************/
function isNumber (str) {
	var patt = /^[\+-]?((\d+(\.\d*)?$)|(\.\d+$))/;     // match reg. exp.
	return patt.test (str);
}


/******************************************************************************/
// Round latitude or longitude to meaningful value.
/*******************************************************************************/
function roundLatLng (n) {
	var LATLNG_PRECISION = 6;
	if (!n.NaN) {
		return (n.toFixed (LATLNG_PRECISION));
	}
	else
		return "";
}


/******************************************************************************/
// Check if string (or other variable) is a (non-negative) integer.  
/*******************************************************************************/
function isInteger (str) {
	var patt = /^\d+$/;     
	return patt.test (str);
}

/******************************************************************************/
// Convert null to empty string.  Otherwise just return the input string.
/*******************************************************************************/
function nz (str) {
	if (str == null)
		return "";
	else 
		return (str);
}

/*******************************************************************
// Convert undefined value to null (otherwise just pass value through)
********************************************************************/
function sf (x) {
	if (x == undefined)
		return null;
	else
		return x;
}


/******************************************************************************
// dumpText - Display text in debug area
******************************************************************************/
function dumpText (text) {
	var dumparea;
	if (Site == SITE_EDITOR) 
		dumparea = "leftcol";
	else
		dumparea = "viewerdumparea";
	var elem = document.getElementById (dumparea);
	elem.innerHTML = elem.innerHTML + "<br/>" + text;	
}


/******************************************************************************
// dumpRoute - Display route info in debug area
******************************************************************************/
function dumpRoute (rte) {
	var s, p, segbefid, segaftid;
	
	// Test code
	dumpText ("Segments:");
	for (s=rte.seglist.firstnode; s!=null; s=s.next) {
		dumpText ("  ID=" + s.id + ", span=" + s.span + ", firstpoint=" + s.firstpoint.id + ", lastpoint=" + s.lastpoint.id);
	}
	dumpText ("Points:");
	for (p=rte.pointlist.firstnode; p!=null; p=p.next) {
		segbefid = (p.segbefore != null) ? p.segbefore.id : null;
		segaftid = (p.segafter != null) ? p.segafter.id : null;
		dumpText ("  ID=" + p.id + ", segbefore=" + segbefid + ", segafter=" + segaftid + ", bnd=" + p.boundtype);
	}

}



/******************************************************************************
// getField - Needed?
******************************************************************************/
function getField (obj, field) {
	if (obj == null)
		return ("(null parent)");
	else
		return (obj[field]);
}


/******************************************************************************
// Test procedure 
// Tests whether closure makes copies of objects or copies of object references...
// As it turns out, a closure makes a copy of the object reference (not of the object).
******************************************************************************/
function testClosure () {
	pnt = OpenRoute.pointlist.firstnode;
	if (pnt.opp_pntnote == null)
		pnt.opp_pntnote = "Call x ";
	else
		pnt.opp_pntnote = pnt.opp_pntnote + "x ";
	dumpText ("firstpoint id = " + pnt.id + ", firstpoint text = " + pnt.opp_pntnote);
}


/******************************************************************************
// Test procedure - Tests double line display
******************************************************************************/
function testDoubleLine () {
	var g1 = new GLatLng (39.0437, -77.1842);
	var g2 = new GLatLng (39.0251, -77.1818);
	var g3 = new GLatLng (39.0169, -77.1547);
	mapClick (null, g1);
	mapClick (null, g2);
	mapClick (null, g3);

	computeParallel (g1, g2);
	computeParallel (g2, g3);
}

/******************************************************************************
// Test procedure.
******************************************************************************/
function testit () {
	var s1, s2;
	// debugger;
	s1 = getStyle ("segnew", "opacity");
	s2 = getStyle ("segnew", "filter");
	dumpText ("Opacity = " + s1 + ", Filter = " + s2);
	// Opacity works in both IE and Firefox (with different # of decimal places).  Filter set only in IE.
	s1 = getStyle ("leftcol", "font-size");      // works in Firefox (case doesn't matter)
	s2 = getStyle ("leftcol", "fontsSze");        // works in IE (case-sensitive)
	dumpText ("font-size = " + s1 + ", fontSize = " + s2);
}

/******************************************************************************
// Test procedures 
******************************************************************************/
function testButtons1 () {
	ButtonStates.enable ("segnavfirst", false);
	ButtonStates.enable ("segnew", false);
	ButtonStates.enable ("deleteroute", false);
	testit();
}
function testButtons2 () {
	ButtonStates.enable ("segnavfirst", true);
	ButtonStates.enable ("segnew", true);
	ButtonStates.enable ("deleteroute", true);
	// testit();
}