// ------------------------------------------------
// GeoAdRotator.js
// Author:	jpotter@troupscreeksoftware.com
// Date:	Oct 27, 2006
// Desc:	Manages advertisement display based on Geo location
// Copyright 2006 Troups Creek Software LLC
// ------------------------------------------------
// Manages the display of image elements on the page based on proximity to the map center, the 'weight'
// of the content, and the setting for favoring host ads.
// ------------------------------------------------
// TODO List:
//	support for multiple categories & subcategories - show ads for selected only
//  support all types of ads, regardless if they are deployed (check for existence)
// -----------------------------------------
// GeoAdRotator::GeoAdRotator
function GeoAdRotator()
{
	// data members --------------------------------------------------------------------------------
	this.adDelay = 10000;		// default is ten seconds between updates
	this.lastCenterLat = 0;
	this.lastCenterLng = 0;
	// banner ad info ------------------------------------------------------------------------------
	this.banner = new Object();
	this.banner.exists = false;
	this.banner.graphic = "";
	this.banner.score = 9999;	// default is way high
	this.banner.url = "";
	this.banner.title = "";
	this.banner.defaultGraphic = "default-f.gif";
	this.banner.defaultUrl = "";
	this.banner.defaultTitle = "";
	// business card ad info --------------------------------------------------------------------------------
	this.cards = new Object();
	this.cards.firstExists = false;
	this.cards.firstGraphic = "";
	this.cards.firstScore = 9999;	// default is way high
	this.cards.firstUrl = "";
	this.cards.firstTitle = "";
	this.cards.secondExists = false;
	this.cards.secondGraphic = "";
	this.cards.secondScore = 9999;	// default is way high
	this.cards.secondUrl = "";
	this.cards.secondTitle = "";
	this.cards.exists = false;
	this.cards.defaultGraphic = "default-c.gif";
	this.cards.defaultUrl = "";
	this.cards.defaultTitle = "";
	// skyscraper ad info ------------------------------------------------------------------------------
	this.sky = new Object();
	this.sky.exists = false;
	this.sky.graphic = "";
	this.sky.score = 9999;	// default is way high
	this.sky.url = "";
	this.sky.title = "";
	this.sky.defaultGraphic = "default-s.gif";
	this.sky.defaultUrl = "";
	this.sky.defaultTitle = "";
	// tile ad info --------------------------------------------------------------------------------
	this.tiles = new Object();
	this.tiles.firstExists = false;
	this.tiles.firstGraphic = "";
	this.tiles.firstScore = 9999;	// default is way high
	this.tiles.firstUrl = "";
	this.tiles.firstTitle = "";
	this.tiles.secondExists = false;
	this.tiles.secondGraphic = "";
	this.tiles.secondScore = 9999;	// default is way high
	this.tiles.secondUrl = "";
	this.tiles.secondTitle = "";
	this.tiles.thirdExists = false;
	this.tiles.thirdGraphic = "";
	this.tiles.thirdScore = 9999;	// default is way high
	this.tiles.thirdUrl = "";
	this.tiles.thirdTitle = "";
	this.tiles.exists = false;
	this.tiles.defaultGraphic = "default-r.gif";
	this.tiles.defaultUrl = "";
	this.tiles.defaultTitle = "";
	// -------------------------------------------------------------------------------------------
	//            B A S I C   O P E R A T I O N S
	// -------------------------------------------------------------------------------------------
	// Fundamental class methods.

	// -----------------------------------------
	// GeoAdRotator::start
	this.start = function()
	{	// initialize the ad rotator
		// on startup, first we configure our runtime values
		this.adDelay = gConfig.timer;
		if(document.getElementById("bannerimg"))
		{	// banner exists - only 1
			this.banner.exists = true;
			this.banner.score = gConfig.hostScore;
		}
		if(document.getElementById("cardimg1"))
		{	// cards exist - up to 2
			this.cards.exists = true;
			this.cards.firstExists = true;
			this.cards.firstScore = gConfig.hostScore;
			if(document.getElementById("cardimg2"))
			{
				this.cards.secondExists = true;
				this.cards.secondScore = gConfig.hostScore;
			}
		}
		if(document.getElementById("skyimg"))
		{	// skyscraper exists - only 1
			this.sky.exists = true;
			this.sky.score = gConfig.hostScore;
		}
		if(document.getElementById("tileimg1"))
		{	// tiles exist - up to 3
			this.tiles.exists = true;
			this.tiles.firstExists = true;
			this.tiles.firstScore = gConfig.hostScore;
			if(document.getElementById("tileimg2"))
			{
				this.tiles.secondExists = true;
				this.tiles.secondScore = gConfig.hostScore;
				if(document.getElementById("tileimg3"))
				{
					this.tiles.thirdExists = true;
					this.tiles.thirdScore = gConfig.hostScore;
				}
			}
		}
		// now kick off the ad rotation, but our delay is set to 1000 for near-immediate, firsttime update
		var tid = setTimeout("gAds.update()", 1000);			
	}

	// -----------------------------------------
	// GeoAdRotator::navigateTo
	this.navigateTo = function(ad)
	{	// navigate to URL on click 
		var url = "";
		var title = "";
		switch(ad)
		{
			case 0:								// banner ad
				url = this.banner.url;
				title = this.banner.title;
				break;
			case 1:								// card ad 1
				url = this.cards.firstUrl;
				title = this.cards.firstTitle;
				break;
			case 2:								// card ad 2
				url = this.cards.secondUrl;
				title = this.cards.secondTitle;
				break;
			case 3:								// sky ad
				url = this.sky.url;
				title = this.sky.title;
				break;
			case 4:								// tile ad 1
				url = this.tiles.firstUrl;
				title = this.tiles.firstTitle;
				break;
			case 5:								// tile ad 2
				url = this.tiles.secondUrl;
				title = this.tiles.secondTitle;
				break;
			case 6:								// tile ad 3
				url = this.tiles.thirdUrl;
				title = this.tiles.thirdTitle;
				break;
		}
		// if the url is empty, there's no link so ignore the click
		if(url != "")
		{	
			if(title != "")
			{	// log the click-thru first
				gData.logContact("ad"+ad, title);
			}
			// open a new window with the ad URL as the target
			window.open(url,"","toolbar=yes,location=yes,status=yes,menubar=yes,scrollbars=yes,resizable=yes");
		}
	}

	// -----------------------------------------
	// GeoAdRotator::update
	this.update = 	function()
	{	// update the ads
		// look at the data for all items and make sure we have some before doing any more work
		var allItems = gData.sourceData;
		if(allItems != null)
		{	// get current map center
			var settings = gMap.getCurrentMapSettings();
			if((this.lastCenterLat != settings.lat) && (this.lastCenterLng != settings.lng))
			{	// different - we need to update the ads based on the new center
				this.lastCenterLat = settings.lat;
				this.lastCenterLng = settings.lng;
				// reset the scoring for this round of updates
				if(this.banner.exists)
					{this.banner.score = gConfig.hostScore;}
				if(this.cards.exists)
				{
					if(this.cards.firstExists)
						{this.cards.firstScore = gConfig.hostScore;}
					if(this.cards.secondExists)
						{this.cards.secondScore = gConfig.hostScore;}
				}
				if(this.sky.exists)
					{this.sky.score = gConfig.hostScore;}
				if(this.tiles.exists)
				{
					if(this.tiles.firstExists)
						{this.tiles.firstScore = gConfig.hostScore;}
					if(this.tiles.secondExists)
						{this.tiles.secondScore = gConfig.hostScore;}
					if(this.tiles.thirdExists)
						{this.tiles.thirdScore = gConfig.hostScore;}
				}
				// init 
				var thisItem, thisItemId, thisItemStatus, thisItemLat, thisItemLng, thisItemTitle;
				var thisItemNavigateNode, thisItemNavigateUrl;
				var thisItemBannerNode, thisItemBanner;
				var thisItemCardNode, thisItemCard;
				var thisItemSkyNode, thisItemSky;
				var thisItemTileNode, thisItemTile;
				var thisItemWeight, thisItemDistance;
				// pick out the individual items
				var itemList = allItems.getElementsByTagName("item");		
				var itemCount = itemList.length;
				// for each item in our list
				for(var i=0; i<itemCount; i++)
				{
					thisItem = itemList[i];
					// extract the item id, status, latitude, and longitude along with the ad weight
					thisItemId = thisItem.getElementsByTagName("id")[0].firstChild.nodeValue;
					thisItemStatus = thisItem.getElementsByTagName("status")[0].firstChild.nodeValue;
					if(thisItemStatus == "active")
					{	// only process active items
						thisItemTitle = thisItem.getElementsByTagName("title")[0].firstChild.nodeValue;
						thisItemLat = parseFloat(thisItem.getElementsByTagName("latitude")[0].firstChild.nodeValue);
						thisItemLng = parseFloat(thisItem.getElementsByTagName("longitude")[0].firstChild.nodeValue);
						thisItemWeight = parseInt(thisItem.getElementsByTagName("weight")[0].firstChild.nodeValue);
						// an item weight of 0 means don't worry 'bout ads
						if(thisItemWeight > 0)
						{	// see if we have an ad navigation url for this item
							// NOTE: the first part of the test is for IE, the second for FF
							thisItemNavigateNode = thisItem.getElementsByTagName("navigate")[0];
							if((thisItemNavigateNode.firstChild != null) && (thisItemNavigateNode.firstChild.nodeValue > " "))
								{thisItemNavigateUrl = thisItemNavigateNode.firstChild.nodeValue;}
							else
								{thisItemNavigateUrl = "";}
							// extract the name of the graphic for the ad and if
							// there is one, calculate the distance from this item to 
							// the map center and see if it applies to the various ad types.
							if(this.banner.exists)
							{	// NOTE: the first part of the test is for IE, the second for FF
								thisItemBannerNode = thisItem.getElementsByTagName("banner")[0];
								if(thisItemBannerNode && (thisItemBannerNode.firstChild != null) && (thisItemBannerNode.firstChild.nodeValue > " "))
								{
									thisItemBanner = thisItemBannerNode.firstChild.nodeValue;
									thisItemDistance = this.calcDistanceFromCenter(thisItemLat, thisItemLng);
									this.updateBanner(thisItemBanner, thisItemWeight, thisItemDistance, thisItemNavigateUrl, thisItemTitle);
								}
							}
							if(this.cards.exists)
							{	// NOTE: the first part of the test is for IE, the second for FF
								thisItemCardNode = thisItem.getElementsByTagName("card")[0];
								if(thisItemCardNode && (thisItemCardNode.firstChild != null) && (thisItemCardNode.firstChild.nodeValue > " "))
								{
									thisItemCard = thisItemCardNode.firstChild.nodeValue;
									thisItemDistance = this.calcDistanceFromCenter(thisItemLat, thisItemLng);
									this.updateCards(thisItemCard, thisItemWeight, thisItemDistance, thisItemNavigateUrl, thisItemTitle);
								}
							}
							if(this.sky.exists)
							{	// NOTE: the first part of the test is for IE, the second for FF
								thisItemSkyNode = thisItem.getElementsByTagName("skyscraper")[0];
								if(thisItemSkyNode && (thisItemSkyNode.firstChild != null) && (thisItemSkyNode.firstChild.nodeValue > " "))
								{
									thisItemSky = thisItemSkyNode.firstChild.nodeValue;
									thisItemDistance = this.calcDistanceFromCenter(thisItemLat, thisItemLng);
									this.updateSky(thisItemSky, thisItemWeight, thisItemDistance, thisItemNavigateUrl, thisItemTitle);
								}
							}
							if(this.tiles.exists)
							{	// NOTE: the first part of the test is for IE, the second for FF
								thisItemTileNode = thisItem.getElementsByTagName("tile")[0];
								if(thisItemTileNode && (thisItemTileNode.firstChild != null) && (thisItemTileNode.firstChild.nodeValue > " "))
								{
									thisItemTile = thisItemTileNode.firstChild.nodeValue;
									thisItemDistance = this.calcDistanceFromCenter(thisItemLat, thisItemLng);
									this.updateTiles(thisItemTile, thisItemWeight, thisItemDistance, thisItemNavigateUrl, thisItemTitle);
								}
							}
						}
					}
				}
				// once we've looked at all items, update the display
				if(this.banner.exists) this.displayBanner();
				if(this.cards.exists) this.displayCards();
				if(this.sky.exists) this.displaySky();
				if(this.tiles.exists) this.displayTiles();
			}
		}	
		// restart the timer so we can do it all again
		var tid = setTimeout("gAds.update()", this.adDelay);			
	}

	// -----------------------------------------
	// GeoAdRotator::updateBanner
	this.updateBanner = function(graphic, weight, distance, url, title)
	{	// update the banner ad settings
		// calculate a score for this guy
		var score;
		if(weight > 0)
		{
			score = distance / weight;
			if(score < this.banner.score)
			{	// replace it
				this.banner.score = score;
				this.banner.graphic = graphic;
				this.banner.url = url;
				this.banner.title = title;
			}
		}
	}

	// -----------------------------------------
	// GeoAdRotator::updateCards
	this.updateCards = function(graphic, weight, distance, url, title)
	{	// update the multiple cards ads settings
		var score;
		if(weight > 0)
		{
			score = distance / weight;
			if(this.cards.firstExists && score < this.cards.firstScore)
			{	// replace it
				if(this.cards.secondExists)
				{	// shuffle the top down
					this.cards.secondScore = this.cards.firstScore;
					this.cards.secondGraphic = this.cards.firstGraphic;
					this.cards.secondUrl = this.cards.firstUrl;
					this.cards.secondTitle = this.cards.firstTitle;
				}
				this.cards.firstScore = score;
				this.cards.firstGraphic = graphic;
				this.cards.firstUrl = url;
				this.cards.firstTitle = title;
			}
			else if(this.cards.secondExists && score < this.cards.secondScore)
			{	// replace the second
				this.cards.secondScore = score;
				this.cards.secondGraphic = graphic;
				this.cards.secondUrl = url;
				this.cards.secondTitle = title;
			}
		}
	}

	// -----------------------------------------
	// GeoAdRotator::updateSky
	this.updateSky = function(graphic, weight, distance, url, title)
	{	// update the skyscraper ad settings
		// calculate a score for this guy
		var score;
		if(weight > 0)
		{
			score = distance / weight;
			if(score < this.sky.score)
			{
				this.sky.score = score;
				this.sky.graphic = graphic;
				this.sky.url = url;
				this.sky.title = title;
			}
		}
	}
	
	// -----------------------------------------
	// GeoAdRotator::updateTiles
	this.updateTiles = function(graphic, weight, distance, url, title)
	{	// update the multiple tile ads settings
		var score;
		if(weight > 0)
		{
			score = distance / weight;
			if(this.tiles.firstExists && score < this.tiles.firstScore)
			{	// replace the top, shuffle the rest down
				if(this.tiles.secondExists)
				{
					if(this.tiles.thirdExists)
					{
						this.tiles.thirdScore = this.tiles.secondScore;
						this.tiles.thirdGraphic = this.tiles.secondGraphic;
						this.tiles.thirdUrl = this.tiles.secondUrl;
						this.tiles.thridTitle = this.tiles.secondTitle;
					}
					this.tiles.secondScore = this.tiles.firstScore;
					this.tiles.secondGraphic = this.tiles.firstGraphic;
					this.tiles.secondUrl = this.tiles.firstUrl;
					this.tiles.secondTitle = this.tiles.firstTitle;
				}
				this.tiles.firstScore = score;
				this.tiles.firstGraphic = graphic;
				this.tiles.firstUrl = url;
				this.tiles.firstTitle = title;
			}
			else if(this.tiles.secondExists && score < this.tiles.secondScore)
			{	// replace the second, shuffle the rest down
				if(this.tiles.thirdExists)
				{
					this.tiles.thirdScore = this.tiles.secondScore;
					this.tiles.thirdGraphic = this.tiles.secondGraphic;
					this.tiles.thirdUrl = this.tiles.secondUrl;
					this.tiles.thridTitle = this.tiles.secondTitle;
				}
				this.tiles.secondScore = score;
				this.tiles.secondGraphic = graphic;
				this.tiles.secondUrl = url;
				this.tiles.secondTitle = title;
			}
			else if(this.tiles.thirdExists && score < this.tiles.thirdScore)
			{	// replace the third
				this.tiles.thirdScore = score;
				this.tiles.thirdGraphic = graphic;
				this.tiles.thirdUrl = url;
				this.tiles.thirdTitle = title;
			}
		}
	}

	// -------------------------------------------------------------------------------------------
	//            P R E S E N T A T I O N
	// -------------------------------------------------------------------------------------------
	// Set of methods that alter the web page presentation
	
	// -----------------------------------------
	// GeoAdRotator::displayBanner
	this.displayBanner = function()
	{	// actually display the "best fit" banner ad
		if(this.banner.score == gConfig.hostScore)
		{	// no banner to display, show default
			document.getElementById("bannerimg").src = "Images/" + gConfig.domainid + "/" + this.banner.defaultGraphic;
			this.banner.url = this.banner.defaultUrl;
			this.banner.title = this.banner.defaultTitle;
		}
		else
		{	// show the one we found to be the best fit
			document.getElementById("bannerimg").src = "Images/" + this.banner.graphic;
		}
	}
	
	// -----------------------------------------
	// GeoAdRotator::displayCards
	this.displayCards = function()
	{	// actually display the "best fit" business card ads
		if(this.cards.firstScore == gConfig.hostScore)
		{	// no ad to display, show all defaults
			document.getElementById("cardimg1").src = "Images/" + gConfig.domainid + "/" + this.cards.defaultGraphic;
			this.cards.firstUrl = this.cards.defaultUrl;
			this.cards.firstTitle = this.cards.defaultTitle;
			if(this.cards.secondExists)
			{
				document.getElementById("cardimg2").src = "Images/" + gConfig.domainid + "/" + this.cards.defaultGraphic;
				this.cards.secondUrl = this.cards.defaultUrl;
				this.cards.secondTitle = this.cards.defaultTitle;
			}
		}
		else
		{	// show our top rated card ad and look at the second
			document.getElementById("cardimg1").src = "Images/" + this.cards.firstGraphic;
			if(this.cards.secondExists)
			{
				if(this.cards.secondScore == gConfig.hostScore)
				{	// no ad to display, show default
					document.getElementById("cardimg2").src = "Images/" + gConfig.domainid + "/" + this.cards.defaultGraphic;
					this.cards.secondUrl = this.cards.defaultUrl;
					this.cards.secondTitle = this.cards.defaultTitle;
				}
				else
				{	// show our next rated ad
					document.getElementById("cardimg2").src = "Images/" + this.cards.secondGraphic;
				}
			}
		}
	}

	// -----------------------------------------
	// GeoAdRotator::displaySky
	this.displaySky = function()
	{	// actually display the "best fit" skyscraper ad
		if(this.sky.score == gConfig.hostScore)
		{	// no ad to display, show default
			document.getElementById("skyimg").src = "Images/" + gConfig.domainid + "/" + this.sky.defaultGraphic;
			this.sky.url = this.sky.defaultUrl;
			this.sky.title = this.sky.defaultTitle;
		}
		else
		{	// show the one we found to be the best fit
			document.getElementById("skyimg").src = "Images/" + this.sky.graphic;
		}
	}

	// -----------------------------------------
	// GeoAdRotator::displayTiles
	this.displayTiles = function()
	{	// actually display the "best fit" tile ads
		if(this.tiles.firstScore == gConfig.hostScore)
		{	// no tile to display, show default
			document.getElementById("tileimg1").src = "Images/" + gConfig.domainid + "/" + this.tiles.defaultGraphic;
			this.tiles.firstUrl = this.tiles.defaultUrl;
			this.tiles.firstTitle = this.tiles.defaultTitle;
			if(this.tiles.secondExists)
			{
				document.getElementById("tileimg2").src = "Images/" + gConfig.domainid + "/" + this.tiles.defaultGraphic;
				this.tiles.secondUrl = this.tiles.defaultUrl;
				this.tiles.secondTitle = this.tiles.defaultTitle;
				if(this.tiles.thirdExists)
				{
					document.getElementById("tileimg3").src = "Images/" + gConfig.domainid + "/" + this.tiles.defaultGraphic;
					this.tiles.thirdUrl = this.tiles.defaultUrl;
					this.tiles.thirdTitle = this.tiles.defaultTitle;
				}
			}
		}
		else
		{	// show our top rated tile ad and look at the second
			document.getElementById("tileimg1").src = "Images/" + this.tiles.firstGraphic;
			if(this.tiles.secondExists)
			{
				if(this.tiles.secondScore == gConfig.hostScore)
				{	// no tile to display, show default
					document.getElementById("tileimg2").src = "Images/" + gConfig.domainid + "/" + this.tiles.defaultGraphic;
					this.tiles.secondUrl = this.tiles.defaultUrl;
					this.tiles.secondTitle = this.tiles.defaultTitle;
					if(this.tiles.thirdExists)
					{
						document.getElementById("tileimg3").src = "Images/" + gConfig.domainid + "/" + this.tiles.defaultGraphic;
						this.tiles.thirdUrl = this.tiles.defaultUrl;
						this.tiles.thirdTitle = this.tiles.defaultTitle;
					}
				}
				else
				{	// show our next rated tile ad and look at the third
					document.getElementById("tileimg2").src = "Images/" + this.tiles.secondGraphic;
					if(this.tiles.thirdExists)
					{
						if(this.tiles.thirdScore == gConfig.hostScore)
						{	// no tile to display, show default
							document.getElementById("tileimg3").src = "Images/" + gConfig.domainid + "/" + this.tiles.defaultGraphic;
							this.tiles.thirdUrl = this.tiles.defaultUrl;
							this.tiles.thirdTitle = this.tiles.defaultTitle;
						}
						else
						{	// show the third rated ad
							document.getElementById("tileimg3").src = "Images/" + this.tiles.thirdGraphic;
						}
					}
				}
			}
		}
	}

	// -----------------------------------------
	// GeoAdRotator::showLink
	this.showLink = function(ad)
	{	// visual to indicate link
		var url = "";
		var id = "";
		var altTxt = "Open a new window";
		switch(ad)
		{
			case 0: 
				url = this.banner.url;
				id = "bannerimg";
				break;
			case 1:
				url = this.cards.firstUrl;
				id = "cardimg1";
				break;
			case 2:
				url = this.cards.secondUrl;
				id = "cardimg2";
				break;
			case 3:
				url = this.sky.url;
				id = "skyimg";
				break;
			case 4:
				url = this.tiles.firstUrl;
				id = "tileimg1";
				break;
			case 5:
				url = this.tiles.secondUrl;
				id = "tileimg2";
				break;
			case 6:
				url = this.tiles.thirdUrl;
				id = "tileimg3";
				break;
		}
		if(url != "")
		{
			document.getElementById(id).alt = altTxt + " to: " + url;
			gMsg.displayMsg(0,altTxt + " to: " + url);
			document.body.style.cursor = "hand";
		}
	}

	// -----------------------------------------
	// GeoAdRotator::hideLink
	this.hideLink = function(ad)
	{	// visual to hide link
		switch(ad)
		{
			case 0: 
				id = "bannerimg";
				break;
			case 1:
				id = "cardimg1";
				break;
			case 2:
				id = "cardimg2";
				break;
			case 3:
				id = "skyimg";
				break;
			case 4:
				id = "tileimg1";
				break;
			case 5:
				id = "tileimg2";
				break;
			case 6:
				id = "tileimg3";
				break;
		}
		document.getElementById(id).alt = "";
		gMsg.displayMsg(0," ");
		document.body.style.cursor = "default";
	}

	// -------------------------------------------------------------------------------------------
	//            U T I L I T I E S
	// -------------------------------------------------------------------------------------------
	// Utility methods.
	
	// -----------------------------------------
	// GeoAdRotator::calcDistanceFromCenter
	this.calcDistanceFromCenter = function(pLat, pLng)
	{	// determine the distance from a point to the map center using Haversine Formula
		var result = 0;
		with (Math)
		{
			var RadiusOfEarth = 3963.1676;		// The radius of the earth is 3963.1676 miles 
			var RadPerDeg = PI/180;
			var latA = RadPerDeg * pLat;
			var latB = RadPerDeg * this.lastCenterLat;
			var lngA = RadPerDeg * pLng;
			var lngB = RadPerDeg * this.lastCenterLng;
			var dLat = latB - latA;
			var dLng = lngB - lngA;
			// a = sin^2(dlat/2) + cos(lat1) * cos(lat2) * sin^2(dlon/2)
			var a = pow(sin(dLat/2),2) + cos(latA) * cos(latB) * pow(sin(dLng/2),2);
			// great circle distance c = 2 * arcsin(min(1,sqrt(a)))
			var c = 2 * asin(min(1,sqrt(a)));
			// distance d = R * c
			result = RadiusOfEarth * c;		// in miles
		}
		return result;
	}
}

