// USGS_NATMAP_utilities.js

nmUtilitiesPresent = true;

/**
 * checks if the passed in bounding box matches 
 * the aspect ratio of the image width and height
 * returns an array with the corrected bounding box values,
 * the corrected bounding box will contain the old bounding box,
 * have the same center point, and match the aspect ratio of the image.
 * for the returned array index 0 = newMinX, 1 = newMinY, 2 = newMaxX, 3 =  newMaxY
 * this format makes for easy use as a bbox value in an ogc request using toString() to convert
 */
function checkEnvelopeRatio (bbMinX, bbMinY, bbMaxX, bbMaxY, imgWidth, imgHeight) {
	if (debugOn >= 3) {
		clientLogMemo("checkEnvelopeRatio: bbMinX='" + bbMinX + "' bbMinY='" + bbMinY + "' bbMaxX='" + bbMaxX + 
									" bbMaxY='" + bbMaxY + "'");
	}		
	// covert to numeric context if came from strings
	bbMinX = bbMinX - 0;
	bbMaxX = bbMaxX - 0;
	bbMinY = bbMinY - 0;
	bbMaxY = bbMaxY - 0;
	imgWidth = imgWidth - 0;
	imgHeight = imgHeight - 0;
	var bbWidth = (bbMaxX - bbMinX);
	var bbHeight = (bbMaxY - bbMinY);
	var newWidth = 0;
	var newHeight = 0;
	var newEnvelope = new Array();
  var bbCenterX;
	var bbCenterY;
	// first try adjusting the height
	newHeight = ((imgHeight * bbWidth) / imgWidth);
	if (newHeight >= bbHeight) {
		// adjust the Height and leave the width the same
		bbCenterY = (bbMinY + (bbHeight / 2));
		newEnvelope[0] = bbMinX;
		newEnvelope[1] = (bbCenterY - (newHeight / 2));
		newEnvelope[2] = bbMaxX;
		newEnvelope[3] = (bbCenterY + (newHeight / 2));
	} else {
		//else adjust the width and leave the height the same
		newWidth = ((bbHeight * imgWidth) / imgHeight);
		bbCenterX = (bbMinX + (bbWidth / 2));
		newEnvelope[0] = (bbCenterX - (newWidth / 2));
		newEnvelope[1] = bbMinY;
		newEnvelope[2] = (bbCenterX + (newWidth / 2));
		newEnvelope[3] = bbMaxY;
	}
	return newEnvelope;
}

/**
 * Checks the passed in bounding box to see if its within the passed in limits
 * returns an array with the corrected bounding box values,
 * the corrected bounding box will be of the same ratio as the old bounding box,
 * the center point will be as close as possible to the orginal center point.
 * for the returned array index 0 = newMinX, 1 = newMinY, 2 = newMaxX, 3 =  newMaxY
 * this format makes for easy use as a bbox value in an ogc request using toString() to convert
 */
function checkEnvelopeLimit(inMinX, inMinY, inMaxX, inMaxY, limMinX, limMinY, limMaxX, limMaxY) {
	var adjExt = new Array(inMinX, inMinY, inMaxX, inMaxY);

	// check if the limit extent fully contains the passed in extent
	if (inMinX >= limMinX &&
			inMinY >= limMinY &&
			inMaxX <= limMaxX && 
			inMaxY <= limMaxY) {
		// extent is fully within the limit extent
		// don't have to change any values
		return adjExt;
	} 
	if (debugOn >= 3) {			
		clientLogMemo("checkEnvelopeLimit: box not within limit extent after initial check\r\n adjExt = " + adjExt);
	}
	var limitXDist = limMaxX - limMinX;
	var limitYDist = limMaxY - limMinY;
	var inXDist = inMaxX - inMinX;
	var inYDist = inMaxY - inMinY;
	if (inXDist > limitXDist || inYDist > limitYDist) {
		// the in extent is bigger than the limit extent in some dimension
		// now need to use cropping around the center to make it fit
		var origXoverY = inXDist / inYDist;
		var newXDist = limitXDist;
		var newYDist = limitXDist / origXoverY;
		if (newYDist > limitYDist) {
			newXDist = limitYDist * origXoverY;
			newYDist = limitYDist;
		}
		var centX = inMinX + inXDist / 2;
		var centY = inMinY + inYDist / 2;
		adjExt[0] = centX - newXDist / 2;
		adjExt[1] = centY - newYDist / 2;
		adjExt[2] = centX + newXDist / 2;
		adjExt[3] = centY + newYDist / 2;
	}

	// next try bumping it back into the limit extent
	if (adjExt[2] > limMaxX) {
		//bump x from right
		adjExt[0] = adjExt[0] - (adjExt[2] - limMaxX);
		adjExt[2] = limMaxX;
	}
	if (adjExt[0] < limMinX) {
		//bump x from left
		adjExt[2] = adjExt[2] + (limMinX - adjExt[0]);
		adjExt[0] = limMinX;
	}
	if (adjExt[3] > limMaxY) {
		//bump y from top
		adjExt[1] = adjExt[1] - (adjExt[3] - limMaxY);
		adjExt[3] = limMaxY;
	}
	if (adjExt[1] < limMinY) {
		//bump y from bottom 
		adjExt[3] = adjExt[3] + (limMinY - adjExt[1]);
		adjExt[1] = limMinY;
	}

	// check if limit extent now fully contains the adjExt
	if (adjExt[2] <= limMaxX &&
			adjExt[3] <= limMaxY &&
			adjExt[0] >= limMinX &&
			adjExt[1] >= limMinY) {
		// bbox is fully within the limit extent
		return adjExt;
	} else {
		if (debugOn >= 3) {			
			clientLogMemo("checkEnvelopeLimit error: box not within limit extent after adjustment attempts\r\n adjExt = " + adjExt);
		}
		return adjExt;
	}	
}

// For use on XML strings. Replaces '>\s+<' with '>\n<' - mainly used to format
// XML response for easier debugging and inspection.
function formatXML(str) {
	str = str.replace(/></g, '>\n<');
	return str.replace(/>\s+</g, '>\n<');
}

/**
 * uses ovImgHeight, ovImgWidth, ovBbMinX, ovBbMaxX, ovBbMinY, ovBbMaxY, ovOgcUrlStub from global scope
 * may reset the values of ovBb* match the ratio of the image
 * return string url for overview map with appropriate bounding box and image size
 */
function generateOVUrl () {
	var tmpOVbb = checkEnvelopeRatio(ovBbMinX, ovBbMinY, ovBbMaxX, ovBbMaxY, ovImgWidth, ovImgHeight);
	ovBbMinX = tmpOVbb[0];
	ovBbMinY = tmpOVbb[1];
	ovBbMaxX = tmpOVbb[2];
	ovBbMaxY = tmpOVbb[3];

	var theUrl = ovOgcUrlStub;
	theUrl += '&BBOX=' + tmpOVbb.toString();
	theUrl += '&WIDTH=' + ovImgWidth;
	theUrl += '&HEIGHT=' + ovImgHeight;
	return theUrl;
}

/**
 *	Truncates input to specified decimal precision
 */
function truncate(rawNum, precision) {
	var multiplier = Math.pow(10, precision);
	var truncatedNum = parseInt(rawNum * multiplier + (0.5)) / multiplier;
	return truncatedNum;
}

/**
 *  Converts input decimal degree number to DDMMSS
 *  Obtained from USGS National Atlas.
 */
function ConvertToDDMMSS(decDeg) {

	var conS;
	var secs;
	var secstr;
	var degs;
	var mins;
	var minsStr;
	var sgna;
	var degDiff;

  sgna = " ";

  // alert('value to convert is: ' + decDeg);
  if (decDeg < 0.0) {
  	sgna = "-";
	}

  conS = Math.abs(decDeg).toString();
  var posX;
  posX = conS.indexOf(".");
  if (posX == -1){
	  conS = conS +".00";
	  posX = conS.length -3;
  }	  
  degs = conS.substring(0, posX);
	
  //alert('degs contains: ' + degs);
  conS = conS.substring(posX, conS.length);
  //alert('whats left is: ' + conS);
    
  mins = (conS * 60).toString();
  posX = mins.indexOf(".");
  minsStr = mins.substring(0, posX);
    
  secstr = mins.substring(posX, mins.length);
  secs = Math.round(secstr * 60);

  if ((secs == "") || (secs == "0")) {
  	secs = "00";
	}
    
  if (secs == 60) {
  	minsStr = (minsStr * 1) + 1;
  	secs = "00";
  }

  if ((minsStr == "") || (minsStr == "0")) {
  	minsStr = "00";
  }

  if (minsStr == 60) {
  	degs = (degs * 1) + 1;
    minsStr = "00";
  }

  if (sgna == "-") {
  	conS = (-1) * conS;
	}
   
  if (((minsStr * 1) < 10) && ((minsStr * 1) > 0)) {
  	minsStr = "0" + minsStr;
	}
    
  if (((secs * 1) < 10) && ((secs * 1) > 0)) {
  	secs = "0" + secs;
	}
 
  var dchar;
  var mchar;
 
  dchar = "\260"; //unescape('%B0');
  mchar = "'"; //String(1, 39);
  
  var degStr;

  if ((degs * 1) > 180) {
    degDiff = degs - 180;
    degs = 180 - degDiff;
  }	
 
  degStr = degs + dchar + " " + minsStr + mchar + " " + secs + "''";
  return(sgna + degStr);
}


/**
 * Returns a copy of the string with first character at left-most word boundaries
 * uppercased.
 * 
 * Uses the Unicode integer value offset between uppercase and lowercase chars
 * to construct mixed case.
 *   a-z on Unicode range 97-122
 *   A-Z on Unicode range 65-90
 */
function toMixedCase(str) {
	var hyphenCharCode = 45;
	var slashCharCode  = 47;
	var spaceCharCode  = 32;
	
	var offset         = 32; // diff between upper and lowercase character codes
	
	var lowerCaseUnicodeLowerBound = 97;
	var lowerCaseUnicodeUpperBound = 122;
	
	var str = str.toLowerCase();
	var codedStrArray = new Array();
	var outStr = "";
	for (var i=0; i < str.length; i++) {
		codedStrArray.push(str.charCodeAt(i));
	}
	// Uppercase first char
	if (codedStrArray[0] >= lowerCaseUnicodeLowerBound && 
			codedStrArray[0] <= lowerCaseUnicodeUpperBound) {
		codedStrArray[0] = codedStrArray[0] - offset;
	}
	
	for (i=1; i < codedStrArray.length; i++) {
		if (codedStrArray[i] == spaceCharCode || codedStrArray[i] == hyphenCharCode
				|| codedStrArray[i] == slashCharCode) {
			if (i < codedStrArray.length - 1 && codedStrArray[i+1] >= lowerCaseUnicodeLowerBound
					&& codedStrArray[i+1] <= lowerCaseUnicodeUpperBound) {
				codedStrArray[i+1] = codedStrArray[i+1] - offset;	
			}
		}
	}

	// create a string out of cased array elems
	for (i=0; i < codedStrArray.length; i++) {
		outStr = outStr + String.fromCharCode(codedStrArray[i]);
	}
	return outStr;
}

function notYetImplemented() {
	var theUrl = appDir + "not-yet-implemented.html";
	window.open(theUrl, "not_yet", "width=200,height=100");
}

// Loops through array looking for an element matching lookupStr.
// Returns index where the name was found, or -1 if not found
function findInArray(arrayIn, lookupStr) {
	if (arrayIn == null   || arrayIn == undefined   || arrayIn.length == 0 ||
	    lookupStr == null || lookupStr == undefined || lookupStr.length == 0) {
		return -1;
	}
	for (var i = 0; i < arrayIn.length; i++) {
		if (arrayIn[i] == lookupStr) {
			return i;
		}
	}
	return -1;
}

// process url parameters passed in with the initial request
//
// Available parameters are:
//
// BBox=minX,minY,maxX,maxY
//   the bounding box for the initial view, in decimal degrees
//   will be reaspected to match window
//   and possibly adjusted to enforce the limitExtent
//
// CenterPoint=lat,lon
//    The initial map will be centered on this point.
//    This value is ignored if BBox is passed in above.
//    Ignored if outside limitExtent
//    If no scale value is given, DEFAULT_RELEATIVE_SCALE is used.
//
// Scale=String 
//    Where string in 'LOCAL,CITY,REGION,STATE,MULTISTATE'
//    Ignored unless CenterPoint passed in above.
//    LOCAL is the largest scale, NATION is the smallest scale, rest in between
//
// Class=String
// LayersOn=String
// LayersOff=String
//    Where string sets the default classes (LayersOn, LayersOff) to be displayed in the  
//    initial view
//
// LogDebug=Yes
//		Show all requests, responses; true also works						
//
// ranking=[none|scaleclass]
// toggleLayers=boolean
//
// skin=[DEFAULT|TEST]
//    skin is an optional parameter
function processUrlParams(queryString) {
	var upQuery = queryString.toUpperCase();

	var debug = getInsideString(upQuery, "LOGDEBUG=", "&", 0, 0, true);
	if (debug == "TRUE" || debug == "YES") {
		allowClientLog = true;
		debugOn = 3;
	}

	var skinStr = getInsideString(upQuery, "SKIN=", "&", 0, 0, true);

	if (allowSkins && skinStr != "") {

		skinStr = skinStr.toUpperCase();

		// check for skin in list of supported skins
		skin = skins[skinStr];
		if (skin != null && skin != undefined) {
			useCustomSkin = true;
		}
		else {
			alert('Unsupported viewer skin: ' + skinStr + '\nUsing default skin.');
			skin = defaultSkin;
		}
		
		if (useCustomSkin) {
			
			rankingMode = skin.rankingMode;
			
			startExt.setWithArray(skin.startXYs);
			ovSymbolSwitchThreshold = skin.ovSymbolSwitchThreshold;
	
			startOVLeft   = skin.ovStartXYs[0];
			startOVBottom = skin.ovStartXYs[1];
			startOVRight  = skin.ovStartXYs[2];
			startOVTop    = skin.ovStartXYs[3];

			ovExt.setFrom(maxExt); 
		}
	}

	var inBboxStr = getInsideString(upQuery, "BBOX=", "&", 0, 0, true);
	if (inBboxStr != "") {
		var bbox = (unescape(inBboxStr)).split(",");
		var isValid = true;
		var i = 0;
		// parse to float and check that each is a number
		for (i = 0; i < bbox.length; i++) {
			bbox[i] = parseFloat(bbox[i]);
			if (isNaN(bbox[i])) {
				isValid = false;
			}
		}
		// check that exactly 4 values passed in
		if (i != 4) {
			isValid = false;
		}

		if (isValid) {
			var tmp = 0;
			//swap if mins > maxs
			if (bbox[0] > bbox[2]) {
				tmp=bbox[0];
				bbox[0] = bbox[2];
				bbox[2] = tmp;
			}
			if (bbox[1] > bbox[3]) {
				tmp=bbox[1];
				bbox[1] = bbox[3];
				bbox[3] = tmp;
			}
			startExt.set(bbox[0],bbox[1],bbox[2],bbox[3]);
		}
	} else {
		// only check for CenterPoint and Scale if bbox was not passed in 
		cpString = getInsideString(upQuery, "CENTERPOINT=", "&", 0, 0, true);
		cpIsValid = false;
		var lat = 0;
		var lon = 0;
		if (cpString != "") {
			cpIsValid = true;
			var cp = cpString.split(",");
			lat = parseFloat(cp[0]);
			lon = parseFloat(cp[1]);
			if (isNaN(lat) || lat < limitMinY || lat > limitMaxY) {
				cpIsValid = false;
			}
			if (isNaN(lon) || lon < limitMinX || lon > limitMaxX) {
				cpIsVlid = false;
			}
		}
		if (cpIsValid) {
			var theScale = DEFAULT_ZOOM_REL_SCALE;
			//only check for Scale if CenterPoint is valid
			switch (getInsideString(upQuery, "SCALE=", "&", 0, 0, true)) {
			case 'LOCAL':
				theScale = REL_SCALE_LOCAL;
				break;
			case 'NEIGHBORHOOD':
				theScale = REL_SCALE_NEIGHBORHOOD;
				break;
			case 'CITY':
				theScale = REL_SCALE_CITY;
				break;
			case 'REGION':
				theScale = REL_SCALE_REGION;
				break;
			case 'STATE':
				theScale = REL_SCALE_STATE;
				break;
			case 'MULTISTATE':
				theScale = REL_SCALE_MULTI_STATE;
				break;
			default:
				theScale = DEFAULT_ZOOM_REL_SCALE;
			}
			var degreesPerPixel = getDegreesPerPixel(theScale);
			var halfYDist = (iHeight * degreesPerPixel) / 2;
			var halfXDist = (iWidth * degreesPerPixel) / 2;
			startExt.set(lon - halfXDist, lat - halfYDist, lon + halfXDist, lat + halfYDist);
		}
	}
	//Check  class information
  	var classString = getInsideString(upQuery, "CLASSES=", "&", 0, 0, true);
		if (classString != "") {
			  classString = unescape(classString); 
			  defaultOnClasses = classString;
		}
    var classIdString = getInsideString(upQuery, "CLASSIDSON=", "&", 0, 0, true);
    if (classIdString != "") {
        classIdString = unescape(classIdString); 
        defaultOnClassIds = classIdString.split("|");
    }
		//Check  layer information
  	var layersOnString = getInsideString(upQuery, "LAYERSON=", "&", 0, 0, true);
		if (layersOnString != "") {
			  layersOnString = unescape(layersOnString); 
			  defaultOnLayers = layersOnString;
		}
		var layersOffString = getInsideString(upQuery, "LAYERSOFF=", "&", 0, 0, true);
		if (layersOffString != "") {
			  layersOffString = unescape(layersOffString); 
			  defaultOffLayers = layersOffString;
		}
		var rankingString = getInsideString(upQuery, "RANKING=", "&", 0, 0, true);
		if (rankingString != "") {
			  rankingString = unescape(rankingString); 
			  rankingMode = rankingModes[rankingString.toLowerCase()];
		}
		var toggleLayersString = getInsideString(upQuery, "TOGGLELAYERS=", "&", 0, 0, true);
		if (toggleLayersString != "") {
			  toggleLayersString = unescape(toggleLayersString); 
				canToggleLayers = (toggleLayersString == "TRUE");				
		}
		var inElementSet = getInsideString(upQuery, "ELEMENTSET=", "&", 0, 0, true);
		if (inElementSet != "") {
			if (allowElementSetOverRide) {
				elementSetId = inElementSet;
			} else {
				alert(msgList[26] + "\r\nUsing element set " + elementSetId + ".");
			}
		}

}

function generateBookmarkUrl() {
	var visLayers = new Array();
	var hideLayers = new Array();
	var visClasses = new Array();
	var hideClasses = new Array();
	var outString= "";
	var bbox = "BBOX=" + curExt.minX + "," + curExt.minY + "," 
	  + curExt.maxX + "," + curExt.maxY;
  var tempString = document.location.href;
  tempString = tempString.substring(0,tempString.indexOf("MapFrame.htm",0));

	if (useCustomSkin) {
		// should use Run.htm not viewer.htm
		outString = tempString + "Run.htm?SKIN=" + skin.tag + "&" + bbox;
	}
	else {
  	outString = tempString + "Run.htm?" + bbox;
	}
  
  var layersInClass = null;
  for (var i = 0; i < ClassID.length; i++) {
    if (ClassVisible[i]) {
      visClasses.push(ClassID[i]);
      if (ClassLayerMap[i] != null && ClassLayerMap[i] != undefined && ClassLayerMap[i] != "") {
        layerInClass = ClassLayerMap[i].split("|");
        for (var k = 0; k < layerInClass.length; k++) {
          if (WmsLayerVisible[layerInClass[k]]) {
            visLayers.push(WmsLayerId[layerInClass[k]]);
          } else {
            hideLayers.push(WmsLayerId[layerInClass[k]]);
          } 
        }
      }
    } else {
      hideClasses.push(ClassID[i]);
    }
	}
  if (visClasses.length > 0) {
    outString += "&CLASSIDSON=" + visClasses.join("|");
  }
  if (visLayers.length > 0) {
    outString += "&LAYERSON=" + visLayers.join("|");
  }
  if (hideLayers.length > 0) {
    outString += "&LAYERSOFF=" + hideLayers.join("|");
  }
  return outString;
}

/* Returns true if elem is found in list. Assumes list is an Array object. */
function foundInArray(array, elem) {
	for (var i = 0; i < array.length; i++) {
		if (array[i] == elem) {
			return true;
		}
	}
	return false;
}
