/* ---------------------------------------------------------------------------
Copyright:
	Davis Instruments, (c) 2010

Code by:
	Author:  Andy Schmidt
	Created: 01/15/2010

Notes:
	Best viewed with TAB-Size = 4

ToDo:
	Todo items are marked with a $$$ comment

	- Add Map Bounds cutoff
	- Improve individual station icon size on zoom in
	- Add MapStation as a type to hold station data
	- Make Icon interactive on click
	- Add Map 'wrapping' when map is scrolled over the dateline

Revisions:

--------------------------------------------------------------------------- */

//-------------------------------------
function MapClusterManager(oStationJson) {
var i,n,s,ilat,ilng,iIndex,dCenterLat,dCenterLng,iClustIndex;
var intLat,intLng,s,oc,sindex,q;
var dGridLat,dGridLng,dGLat2,dGLng2,dGLat4,dGLng4;

	// Quick input check
	if (oStationJson == null) { return null; }

	// Create an array of MapStations using the json input
	oStations = new Array();
	for (s=0; s<oStationJson.length; s++) {
		oc 	= new GLatLng(oStationJson[s].lat,oStationJson[s].lng);
		oStations[s] = new MapStation(oc,
			oStationJson[s].DID,
			oStationJson[s].temp,
			oStationJson[s].uname,
			oStationJson[s].sname,
			makeStationMarker(oc,
				getIconImageName(oStationJson[s].temp),
				oStationJson[s].DID,
				"Temp: " + formatTemp(oStationJson[s].temp,true) + " - " + oStationJson[s].sname
			)
		);
	}

	// Create a global array using a (dGridLat * dGridLng) grid
	// and initialize each MapCluster object
	dGridLat	= 180 / dGridSizeLat;
	dGridLng	= 360 / dGridSizeLng;
	dGLat2		= dGridSizeLat / 2;
	dGLng2		= dGridSizeLng / 2;
	oClusterArray = new Array(dGridLat * dGridLng);
	for (i=0; i<dGridLng; i++) {
		dCenterLng = (i * dGridSizeLng) - 180 + dGLng2;
		for (n=0; n<dGridLat; n++) {
			dCenterLat	= (n * dGridSizeLat) - 90 + dGLat2;
			iClustIndex	= (i*dGridLat) + n;
			oClusterArray[iClustIndex]				= new MapCluster(new GLatLng(dCenterLat,dCenterLng));
			oClusterArray[iClustIndex].dLatHeight	= dGridSizeLat;
			oClusterArray[iClustIndex].dLngWidth	= dGridSizeLng;
			oClusterArray[iClustIndex].oLocSW		= new GLatLng(dCenterLat - dGLat2,dCenterLng - dGLng2);
			oClusterArray[iClustIndex].oLocNE		= new GLatLng(dCenterLat + dGLat2,dCenterLng + dGLng2);
		}
	}

	// Loop through all Stations and add them to their Clusters
	for (i=0; i<oStationJson.length; i++) {

		// Use the Station Lat/Lng to determine the Cluster it belongs to
		intLat	= parseInt(oStationJson[i].lat + 90);
		intLng	= parseInt(oStationJson[i].lng + 180);
		ilat	= parseInt(intLat / dGridSizeLat);
		ilng	= parseInt(intLng / dGridSizeLng);
		iIndex	= (ilng * dGridLat) + ilat;

		// If this is the first Station for this Cluster, initialize all variables
		if (oClusterArray[iIndex].iCount == 0) {
			oClusterArray[iIndex].oStations		= new Array();
			oClusterArray[iIndex].oAvgCenter	= new GLatLng(oStationJson[i].lat,oStationJson[i].lng);
		}

		oClusterArray[iIndex].iCount++;				// Increment the Station count for this Cluster
		oClusterArray[iIndex].oStations.push(i);	// Add the Station index to the array

		// Update the Average Center of the Cluster
		oClusterArray[iIndex].oAvgCenter = new GLatLng(
			(oClusterArray[iIndex].oAvgCenter.lat() + oStationJson[i].lat) / 2,
			(oClusterArray[iIndex].oAvgCenter.lng() + oStationJson[i].lng) / 2
		);

		// Accumulated the Average Temp of the Cluster, the actual
		// calculation is done in the code block below
		if (oStationJson[i].temp != 3276.7) {
			oClusterArray[iIndex].iNumGoodStations++;
			if (oClusterArray[iIndex].dAvgTempF != 3276.7) {
				oClusterArray[iIndex].dAvgTempF += Math.round(oStationJson[i].temp);
			} else {
				oClusterArray[iIndex].dAvgTempF = Math.round(oStationJson[i].temp);
			}
		}
	}

	// Set the Cluster icon according to the Temperature
	// and create the Active Cluster array
	oActiveClusters = new Array();
	for (i=0; i<oClusterArray.length; i++) {
		if (oClusterArray[i].iCount > 0) {

			// Calculate the average temp from the accumulated station temps
			if (oClusterArray[i].iNumGoodStations > 0) {
				oClusterArray[i].dAvgTempF = parseInt(oClusterArray[i].dAvgTempF / oClusterArray[i].iNumGoodStations);
			}

			oClusterArray[i].oMarker = new MapClusterMarker(i,
				oClusterArray[i].oCenter,
				oClusterArray[i].iCount,
				oClusterArray[i].dAvgTempF,
				getIconImageName(oClusterArray[i].dAvgTempF),
				iIconSize,iIconSize,"#000000",1
			);

			oActiveClusters.push(i);	// Add the index to the Active Cluster array
		}
	}

	clearMap();
	redrawMap();
}

//-------------------------------------
// Redraws the Station Map
//-------------------------------------
function redrawMap() {
var i,n,cd,sd,sindex;
var sExtra = "";

	showStatusText("Creating Station Map ...");

	getMapBounds();			// Get the current Map boundaries

	iClustersDrawn	= 0;
	iStationsDrawn	= 0;

	// Clear all displayed Clusters
	for (cd=0; cd<oClusterDisplay.length; cd++) {
		if (oClusterDisplay[cd].oMarker != null) {
			oGMap.removeOverlay(oClusterDisplay[cd].oMarker);
		}
	}
	oClusterDisplay = [];

	// Clear all displayed Stations
	for (sd=0; sd<oStationDisplay.length; sd++) {
		sindex = oStationDisplay[sd];
		if (oStations[sindex].oMarker != null) {
			oGMap.removeOverlay(oStations[sindex].oMarker);
		}
	}
	oStationDisplay = [];

	// Find all Cluster and Station markers
	for (n=0; n<oActiveClusters.length; n++) {

		i = oActiveClusters[n];		// Get the Cluster index

		// If we don't check bounds, display all clusters
		if (iMapZoom < iZoomCheckBounds) {
			oClusterDisplay.push(oClusterArray[i]);
		} else {
			drawClusterWithBounds(oClusterArray[i]);
		}
	}

	// Display all found Clusters
	for (cd=0; cd<oClusterDisplay.length; cd++) {
		oGMap.addOverlay(oClusterDisplay[cd].oMarker);
		iClustersDrawn++;
	}

	// Display all found Stations
	for (sd=0; sd<oStationDisplay.length; sd++) {
		sindex = oStationDisplay[sd];
		oGMap.addOverlay(oStations[sindex].oMarker);
		iStationsDrawn++;
	}

	hideStatusText();
}

//-------------------------------------
// Draws a Cluster using a bounding box check on the current viewport
//-------------------------------------
function drawClusterWithBounds(oClust) {
var s,sindex,q;

	// Check if the Cluster is in the current viewport
	if (checkBounds(oClust)) {

		if (iMapZoom < iZoomDrawStations) {

			if (iMapZoom < iZoomShowSegments) {
				oClusterDisplay.push(oClust);
			} else {
				makeClusterSegments(oClust);
			}

		} else {

			if (oClust.iCount > iMinClusterStations) {

				if (iMapZoom < iZoomShowSegments) {
					oClusterDisplay.push(oClust);
				} else {

					if (iMapZoom < iZoomMaxSegLevels) {
						makeClusterSegments(oClust);
					} else {
						for (s=0; s<oClust.iCount; s++) {
							if (checkLocation(oStations[oClust.oStations[s]].oLocation)) {
								oStationDisplay.push(oClust.oStations[s]);
							}
						}
					}
				}

			} else {
				for (s=0; s<oClust.iCount; s++) {
					if (checkLocation(oStations[oClust.oStations[s]].oLocation)) {
						oStationDisplay.push(oClust.oStations[s]);
					}
				}
			}
		}
	}
}

//-------------------------------------
// Creates Cluster Segments
//-------------------------------------
function makeClusterSegments(oClust) {
var dLatSW,dLngSW,dTempLat,dTempLng,dLatStep,dLngStep,dLatStep2,dLngStep2;
var oSegs,iQIndex,iQLevel,dQMod,dQMod2,dQCount,oTempClust;
var ifraclat,ifraclng,iflngind,iflatind,ifracind;
var x,y,s,sindex,seg;

	iQLevel		= iMapZoom - iZoomShowSegments + 1;
	dQMod		= Math.pow(2,iQLevel);
	dQMod2		= dQMod * 2;
	dQCount		= dQMod * dQMod;

	dLatStep	= oClust.dLatHeight / dQMod;
	dLngStep	= oClust.dLngWidth  / dQMod;
	dLatStep2	= oClust.dLatHeight / dQMod2;
	dLngStep2	= oClust.dLngWidth  / dQMod2;
	dLatSW		= oClust.oLocSW.lat() + dLatStep2;
	dLngSW		= oClust.oLocSW.lng() + dLngStep2;

	oSegs = new Array();

	// Calculate the center for each Segment and add it to the array
	for (y=0; y<dQMod; y++) {
		dTempLat = dLatSW + (dLatStep * y);
		for (x=0; x<dQMod; x++) {
			dTempLng = dLngSW + (dLngStep * x);

			// Check if the Segment is in the current viewport
			oTempClust = new MapCluster(new GLatLng(dTempLat,dTempLng));
			if (checkBounds(oTempClust)) {
				oTempClust.isOnMap = 1;
			}
			oSegs.push(oTempClust);
		}
	}

	// Loop through all Cluster Stations and add them to their Segment
	for (s=0; s<oClust.iCount; s++) {

		sindex = oClust.oStations[s];

		// Use the Station Lat/Lng fraction to determine the Segment it belongs to
		ifraclat = oStations[sindex].oLocation.lat() - oClust.oLocSW.lat();
		ifraclng = oStations[sindex].oLocation.lng() - oClust.oLocSW.lng();
		iflatind = parseInt(ifraclat / dLatStep);
		iflngind = parseInt(ifraclng / dLngStep);
		ifracind = (iflatind * dQMod) + iflngind;

		// Is this Segment on the Map?
		if (oSegs[ifracind].isOnMap == 1) {

			// If this is the first Station for this Segment, initialize all variables
			if (oSegs[ifracind].iCount == 0) {
				oSegs[ifracind].oStations	= new Array();
				oSegs[ifracind].oAvgCenter	= oSegs[ifracind].oCenter;
			}

			// Add the Station and increment the Station count for this Segment
			oSegs[ifracind].oStations.push(sindex);
			oSegs[ifracind].iCount++;

			// Update the Average Center of the Segment
			oSegs[ifracind].oAvgCenter = new GLatLng(
				(oSegs[ifracind].oAvgCenter.lat() + oStations[sindex].oLocation.lat()) / 2,
				(oSegs[ifracind].oAvgCenter.lng() + oStations[sindex].oLocation.lng()) / 2
			);

			// Accumulated the Average Temp of the Segment, the actual
			// calculation is done in the code block below
			if (oStations[sindex].dTempF != 3276.7) {
				oSegs[ifracind].iNumGoodStations++;
				if (oSegs[ifracind].dAvgTempF != 3276.7) {
					oSegs[ifracind].dAvgTempF += Math.round(oStations[sindex].dTempF);
				} else {
					oSegs[ifracind].dAvgTempF = Math.round(oStations[sindex].dTempF);
				}
			}
		}
	}

	// Loop through all Segments and add them to the Clusters
	for (seg=0; seg<oSegs.length; seg++) {

		// Is this Segment on the Map?
		if (oSegs[seg].isOnMap == 1) {

			if (oSegs[seg].iCount > 0) {

				// Calculate the average temp from the accumulated station temps
				if (oSegs[seg].iNumGoodStations > 0) {
					oSegs[seg].dAvgTempF = parseInt(oSegs[seg].dAvgTempF / oSegs[seg].iNumGoodStations);
				}

				if (oSegs[seg].iCount > iMinClusterStations) {

					oSegs[seg].oMarker = new MapClusterMarker(-1,
						oSegs[seg].oCenter,
						oSegs[seg].iCount,
						oSegs[seg].dAvgTempF,
						getIconImageName(oSegs[seg].dAvgTempF),
						iIconSize,iIconSize,"#000000",1
					);

					oClusterDisplay.push(oSegs[seg]);

				} else {

					for (s=0; s<oSegs[seg].iCount; s++) {
						if (checkLocation(oStations[oSegs[seg].oStations[s]].oLocation)) {
							oStationDisplay.push(oSegs[seg].oStations[s]);
						}
					}
				}
			}
		}
	}
}

