if( self == top ) {

	/**
	 * Generic tools for integrating SiteLife.
	 */
	function SLTools() {
		/**
		 * Convert newlines in a string to something that can be displayed in a browser.
		 */
		this.newlinesToHTML = function(text) {
			var result = text.replace(/\r\n/g,"\n");
			result = result.replace(/\n/g,"<br/>\n");
			return result;
		}

		/**
		 * Convert newlines in a string to something that can be displayed in a browser.
		 */
		this.htmlToNewlines = function(text) {
			var result = text.replace(/<[bB][rR][ ]*\/+>/g,""); // we are betting that a newline was put after the BR anyway
			return result;
		}

		this.escapeHTML = function(cmt) {
			var clean = "";
			if (cmt.length > 0) {
				var clean = cmt.replace(/</g,"&lt;");
				//clean=clean.replace(/>/g,"&gt;");
				clean = clean.replace(/\u2019/g,"&#8217;");
				clean = clean.replace(/\u201C/g,"&#8220;");
				clean = clean.replace(/\u201D/g,"&#8221;");
			}
			return clean;
		}

		/**
		 * Safe method for determining if a variable is available.
		 */
		this.isDefined = function(object, variable) {
			return (typeof(eval(object)[variable]) != "undefined");
		}

		/**
		 * Gets the value of a specified query parameter.
		 *
		 * name  Name of the value from the query string.
		 *
		 * Returns a string containing value of specified parameter, or null if it does not exist.
		 */
		this.getQueryParameter = function(parameterName) {
			var result = null;
			var key = parameterName + "=";
			var parameters = document.location.search.substring(1).split("&");
			for (var i = 0; i < parameters.length; i++) {
				if (parameters[i].indexOf(key) == 0) {
					result = parameters[i].substring(key.length);
					i = parameters.length;
				}
			}
			return result;
		}

		/**
		 * Get back the pathname with querystring, with the paramter we want updated to the value.
		 * Returns the whole query string with the value updated.
		 */
		this.updateQueryStringWithParam = function(url, param, value) {
			var regxp = /([^\?]+)\?{0,1}(.*)/;
			var parts = regxp.exec(url);
			var result = parts[1] + "?";
			var tqs = parts[2];
			var pvs = tqs.split("&");
			var added = false;
			var cnt = 0;
			for (var tt = 0; tt < pvs.length; tt++) {
				if (cnt > 0) {
					result += "&";
				}
				var eqL = pvs[tt].indexOf("=");
				var key = "";
				var val = null;
				if (eqL > 0) {
					key = pvs[tt].substring(0, eqL);
					val = pvs[tt].substring(eqL + 1, pvs[tt].length);
				} else {
					key = pvs[tt];
				}

				result += key;
				if (key == param) {
					result += "=";
					result += value;
					added = true;
				} else if (val != null) {
					result += "=" + val;
				}
				cnt++;
			}
			if (! added) {
				if (cnt > 0) {
					result += "&";
				}
				result += param + "=" + value;
				cnt++;
			}
			return result;
		}

		/**
		 *
		 */
		this.mouseX = function(evt) {
			if (evt.pageX) return evt.pageX;
			else if (evt.clientX)
			   return evt.clientX + (document.documentElement.scrollLeft ?
									 document.documentElement.scrollLeft :
									 document.body.scrollLeft);
			else return null;
		}
	
		/**
		 *
		 */
		this.mouseY = function(evt) {
			if (evt.pageY) return evt.pageY;
			else if (evt.clientY)
			  return evt.clientY + (document.documentElement.scrollTop ?
									 document.documentElement.scrollTop :
									 document.body.scrollTop);
			else return null;
		}

		/**
		 *
		 */
		this.hideElement = function(elementId) {
			var el = document.getElementById(elementId);
			if (el) {
				el.style.display = "none";
			}
		}

		/**
		 *
		 */
		this.showDivAtMouse = function(mouseEvent, elementId) {
			var el = document.getElementById(elementId);
			var posx = this.mouseX(mouseEvent) - 170; // REVISIT - I need to get this width from the el, not this made-up number
			var posy = this.mouseY(mouseEvent);
			//normalize to make sure we at least appear on the screen
			if (posx < 0) posx = 10;
			if (posy < 0) posy = 10;
			el.style.left = posx + "px";
			el.style.top = posy + "px";
			el.style.display = "block";
		}


		this.months = new Array("","January","February","March","April","May","June","July","August","September","October","November","December");
		//                      month      day        year      hour   min    sec      period
		this.prettyDateRegEx = /(\d{1,2})\/(\d{1,2})\/(\d{4})\s+(\d{1,2}):(\d\d):(\d\d)\s+([AaPp][Mm])/;
		/**
		 * Accepts input as "8/29/2007 10:06:36 AM"
		 */
		this.formatDateNice = function(dateString) {
			var result = dateString;
			var doa = this.prettyDateRegEx.exec(dateString);
			if (doa != null && doa.length == 8) {
				result = "" + this.months[parseInt(doa[1])] + " " + parseInt(doa[2]) + ", " + doa[3] + " " + parseInt(doa[4]) + ":" + doa[5] + " " + doa[7]
			}
			return result;
		}

		this.debug = function(s) {
			if (SITELIFE_DEBUG) {
				if (this.isDefined(window, "console")) {
					console.log(s);
				} else {
					alert(s);
				}
			}
		}

		this.debugObj = function(obj) {
			if (SITELIFE_DEBUG) {
				if (this.isDefined(window, "console") && navigator.userAgent.indexOf("Safari") < 0) {
					console.dir(obj);
				} else {
					alert(JSON.stringify(obj));
				}
			}
		}
	}
	var slTools = new SLTools();

	/**
	 * A base class for SiteLife Integration
	 */

	function SLBaseIntegration() {

		// This helps us reference ourself when we are indeed ourself.
		// What is my own name in the namespace?
		this.varName = "__base";
		this.childObj = null;

		// The SiteLife User object of the current user.  Set if we
		// ask for the user from SiteLife.
		this.user = null;

		/**
		 * Insert a recommendation widget.
		 */
		this.addRecommendWidget = function(articleId) {
			var replace = escape(articleId);
			document.write("<span id=\"toolRecommend__" + replace + "\"><span id=\"recommendationContainer__" + replace + "\"><span id=\"recommendText__" + replace + "\">Recommend</span> <span>(<span id=\"recommendCount__" + replace + "\">-</span>)</span></span></span>");
		}

		/**
		 * Respond to a UI click off recommending and article.
		 */
		this.recommendArticle = function(uniqueArtKey) {

      var recommend_RequestBatch = new RequestBatch();
			var recommendRequest = new RecommendAction(new ArticleKey(uniqueArtKey));
			recommend_RequestBatch.AddToRequest(recommendRequest);
			// REVISIT - we need to add an UpdatedArticleAction here.
			recommend_RequestBatch.AddToRequest(new ArticleKey(uniqueArtKey));
			slTools.debug("Sending of a request that looks like this");
			slTools.debugObj(recommend_RequestBatch);
			recommend_RequestBatch.BeginRequest(daapiServerUrl, new Function(["respBatch", "objName"], "sl__recommendArticleActionHandler(respBatch, '" + this.varName + "')"));
      
		}

		this.articleHandler = function(article) {
			// needs to be overriden
		}

		this.updateRecommendWidget = function(article) {
			slTools.debug("L141 - SLBase.updateRecommendWidget thinks article is...");
			slTools.debugObj(article);
			var replace = escape(article.ArticleKey.Key);
			var recommendContainerEl = document.getElementById("recommendationContainer__" + replace);
			var recommendTextEl = document.getElementById("recommendText__" + replace);
			var recommendCountEl = document.getElementById("recommendCount__" + replace);
			slTools.debug("L214");

			if (recommendContainerEl == null) {
				slTools.debug("\"recommendationContainer\" element is missing from page.");
				return false;
			}
			if (recommendTextEl == null) {
				slTools.debug("\"recommendText\" element is missing from page.");
				return false;
			}
			if (recommendCountEl == null) {
				slTools.debug("\"recommendCount\" element is missing from page.");
				return false;
			}
			slTools.debug("L227 - " + article.Recommendations.CurrentUserHasRecommended);
			if (article.Recommendations.CurrentUserHasRecommended == "True") {
				slTools.debug("L230");
				recommendTextEl.innerHTML = "Recommended";
			} else {
				slTools.debug("L233");
				recommendTextEl.innerHTML = "<a href=\"javascript://\" onclick=\"" + this.varName + ".recommendArticle('" + article.ArticleKey.Key + "');\">Recommend</a>";
			}
			recommendCountEl.innerHTML = "" + article.Recommendations.NumberOfRecommendations;
		}

		this.userHandler = function(user) {
			slTools.debug("User Handler in action at the base");
			slTools.debugObj(user);
			this.user = user;
		}

		/**
		 * Parse through a comma-separated list in a string, and turn
		 * it into an Array of SiteLife Category objects.
		 */
		this.parseCategoryList = function(categoryList) {
			var result = new Array();
			if (categoryList.length > 0) {
				var categoryListA = categoryList.split(',');
				for (var i=0; i<categoryListA.length; i++) {
					if (categoryListA[i].length > 0) {
						result[result.length] = new Category(categoryListA[i]);
					}
				}
			}
			return result;
		}
	}


	/**
	 * Namespace object to keep the SiteLife integration code logically
	 * grouped together.
	 *
	 * This object would need to be instantiated on any section heads
	 * page that you want to use it.
	 */
	function SLSectionPage() {
 
		this.base = new SLBaseIntegration();
		this.base.childObj = this; // our parent needs to know us.
		this.base.varName = "slInt";
		this.varName = this.base.varName;
		
		/**
		 * A list of all of the UpdateArticleAction objects that we know
		 * about on this page and what we are going to do with them.
		 */
		this.updateArticleActionsArray = new Array();

		/**
		 * A list of all of the ArticleKey objects that we know about on
		 * this page and what we are going to do with them.
		 */
		this.articleKeysArray = new Array();

		/**
		 * Insert a recommendation widget.
		 */
		this.addRecommendWidget = function(articleId) {
			return this.base.addRecommendWidget(articleId);
		}

		/**
		 * Queue up articles to be processed at page laod time.
		 *
		 * articleKey - a string for the article's unique id
		 * articleUrl - a string for the permalink URL of the article
		 * articleTitle - a string for the title of the article
		 * articleSection - a string for the section that the article
		 *                  belongs to
		 * categoryList - a string of a comma-separated list of categories
		 *                that the article belongs to.  E.g.,
		 *                "sports,soccer,premier,2007season".
		 */
		this.addArticleToPageLoadQueue = function(articleKey, articleUrl, articleTitle, articleSection, categoryList) {
			var artKey = new ArticleKey(articleKey);
			this.articleKeysArray.push(artKey);

			// parse out the categories from the comma-separated list of
			// categoryList
			var categoryArray = this.base.parseCategoryList(categoryList);

			this.updateArticleActionsArray.push(new UpdateArticleAction(artKey,
																		articleUrl,
																		articleTitle,
																		new Section(articleSection),
																		categoryArray));
		}

        /**
         * This method makes us look more like the SLArticlePage, so
         * that some of the integration code used for that object can
         * be easily repurposed to work with this function, if
         * comments are not required.
         */
		this.setArticle = function(articleKey, articleUrl, articleTitle, articleSection, categoryList) {
            this.addArticleToPageLoadQueue(articleKey, articleUrl, articleTitle, articleSection, categoryList);
        }

		
		this.doPageLoadDAAPI = function() {
			try { 
				if(hasRecommendTab==1) {
					pageIsLoaded = true;
					if(onmousednLimit>0) {
						onmousednLimit=0;
						onmousednMostRecommend();
					}
				}
			} catch (e) {
					//
			}					
			
			var pageLoadDAAPI_RequestBatch = new RequestBatch();

			// Add all of the articles that need updating to the queue of
			// things we need to do.
			for (var y = 0; y < this.updateArticleActionsArray.length; y++) {
				pageLoadDAAPI_RequestBatch.AddToRequest(this.updateArticleActionsArray[y]);
			}

			// Add all of the articles that need to be retrieved to the queue
			// of things we need to do.
			for (var y = 0; y < this.articleKeysArray.length; y++) {
				pageLoadDAAPI_RequestBatch.AddToRequest(this.articleKeysArray[y]);
			}

			slTools.debug("Sending off request at page load...");
			slTools.debugObj(pageLoadDAAPI_RequestBatch);
			pageLoadDAAPI_RequestBatch.BeginRequest(daapiServerUrl, new Function(["respBatch", "objName"], "sl__SectionPage_pageLoadCallBack(respBatch, '" + this.varName + "')"));
		}


		// called by DAAPI
		this.articleHandler = function(article) {
			slTools.debug("Article Handler in action");
			slTools.debugObj(article);
			this.updateRecommendWidget(article);
		}

		// called by UI
		this.recommendArticle = function(uniqueArtKey) {
			return this.base.recommendArticle(uniqueArtKey);
		}

		this.updateRecommendWidget = function(article) {
			return this.base.updateRecommendWidget(article);
		}
	}

	/**
	 * respBatch - the SiteLife ResponseBatch object that has what we
	 *             need.
	 * objName - the name of the integration object that holds the keys
	 *           to what we need to integrate on the page.
	 */
	function sl__SectionPage_pageLoadCallBack(responseBatch, objName) {
		slTools.debug("sl__SectionPage_pageLoadCallBack Receiving page load response... objName = \"" + objName + "\"");
		slTools.debugObj(responseBatch);
		{
			for (var i = 0; i < responseBatch.Messages.length; i++) {
			   var serverMessage = responseBatch.Messages[i];
				slTools.debug("Server Message " + i + ":  " + serverMessage.MessageTime + " -- " + serverMessage.Message);
			}
			for (var i = 0; i < responseBatch.Responses.length; i++) {
				var response = responseBatch.Responses[i];
				slTools.debug("Response " + i + ":  " + response);
			}
		}
		for (var i = 0; i < responseBatch.Responses.length; i++) {
			var response = responseBatch.Responses[i];
			if (response.Article != null) {
				slTools.debug("L252 Handing off to articleHandler");
				eval(objName + ".articleHandler(response.Article);");
			} else {
				slTools.debug("no handler for " + response);  // if testing in FireFox, try response.toSource()
			}
		}
	}

	/**
	 * respBatch - the SiteLife ResponseBatch object that has what we
	 *             need.
	 * objName - the name of the integration object that holds the keys
	 *           to what we need to integrate on the page.
	 */
	function sl__recommendArticleActionHandler(responseBatch, objName) {
		slTools.debug("Receiving a batch in sl__recommendArticleActionHandler with objName \"" + objName + "\"");
		slTools.debugObj(responseBatch);
		if (responseBatch.Messages.length != 1) {
			slTools.debug("Response from server is empty or the wrong size.");
		}
		if (responseBatch.Messages[0].Message != "ok") {
			slTools.debug("Recommendation failed.  " + responseBatch.Messages[0].Message);
			// At this time, fail silently.
			//alert("Recommendation failed.  " + responseBatch.Messages[0].Message + "  [-103]");
		}
		if (responseBatch.Responses[0].Article != null) {
			eval(objName + ".articleHandler(responseBatch.Responses[0].Article);");
		}
	}
}
