/**
 * community.js
 *

/*
 * GENERIC TOOLS
 *
 */
function GenericTools () {

	/**
	 * 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;
	}

	/**
	 * Safe method for determining if a variable is available.
	 */
	this.isDefined = function(object, variable) {
		return (typeof(eval(object)[variable]) != "undefined");
	}
	
	/**
	 * Gets the value of the specified cookie.
	 *
	 * name  Name of the desired cookie.
	 *
	 * Returns a string containing value of specified cookie, or null if cookie does not exist.
	 */
	
	this.getCookie = function(name) {
		var dc = document.cookie;// this returns/reads all cookies for the domain separated by a semicolon
		var prefix = name + "=";// hd=
		var begin = dc.indexOf("; " + prefix);// returns a -1 or whole number for position of "; hd"
		if (begin == -1){ // "; hd" string does not exist - meaning that hd is the first cookie in the collection of cookies for the domain
			begin = dc.indexOf(prefix); //begin is 0 since prefix ("hd") is at the 0 position
			if (begin != 0) {return null;} //begin should be at index 0 - fail if not
		} else {
			begin += 2; // add 2 to current begin -- not sure why. perhaps this is useful if hd is not the first cookie for the domain
		}
		var end = document.cookie.indexOf(";", begin); // search the cookie collection for ";", start the search at begin
		if (end == -1) { // end does not exist so there is only one cookie for the domain
			end = dc.length; // only one cookie for the domain so the end is the length
		}
		return unescape(dc.substring(begin + prefix.length, end)); // return the values between the end of "hd=" and the end of the cookie
	}

	/**
	 * 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(name) {
		// for now, piggyback on SiteLife's function for this.
		return gSiteLife.GetParameter(name);
	}

	/**
	 *
	 */
	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.hideDiv = 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 = mouseX(mouseEvent) - 170;    
		var posy = 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.debug = function(s) {
		if (SITELIFE_DEBUG && this.isDefined(window, "console")) {
			console.log(s);
		}
	}

}

// Instantiate a tools object for use elsewhere.
var tools = new GenericTools();

function recommendArticle(key, count)
{
	uniqueCommentKey = key;//update the global variable for the DAAPI response handler
	recommendedCount = count;//update the global variable for the DAAPI response handler
	var wp_RequestBatch = new RequestBatch();
	var recommendRequest = new RecommendAction(new ArticleKey(key));
	wp_RequestBatch.AddToRequest(recommendRequest);
	// daapiServerUrl is from shared.js
	wp_RequestBatch.BeginRequest(daapiServerUrl, recommendArticleActionHandler);
}

function recommendArticleActionHandler(responseBatch)
{
	for (var i=0; i<responseBatch.Messages.length; i++)
	{
		var serverMessage = responseBatch.Messages[i];
		//alert(serverMessage.MessageTime);
		//alert(serverMessage.Message);
	}
	for(var i = 0; i<responseBatch.Responses.length; i++)
	{
		var response = responseBatch.Responses[i];
		//alert(response);
	}
	//alert("responses = " + responseBatch.Responses + " -- " + "responses length = " + responseBatch.Responses.length);
	var idStringRecommend = "recommendText"+uniqueCommentKey;
	var recommendContainer = document.getElementById(idStringRecommend);
	recommendedCount = parseInt(recommendedCount) + 1;
	recommendContainer.innerHTML = "<span style='white-space:nowrap;color:#666' title='You have already recommended this article.'>Recommended (" + recommendedCount + ")</span>";
}

function charLimit(oSrc)
{
	//need to verify getAttribute method on older browsers
	var mlength = oSrc.getAttribute ? parseInt(oSrc.getAttribute("maxlength")) : "";
	if (oSrc.getAttribute && oSrc.value.length > mlength)
	{
		oSrc.value = oSrc.value.substring(0, mlength);
	}
}

function charCount(oSrc, charCountLabel)
{ 
	var charCount = document.getElementById(charCountLabel);
	var mlength = oSrc.getAttribute ? parseInt(oSrc.getAttribute("maxlength")) : "";
	if (oSrc.getAttribute && oSrc.value.length > mlength)
	{
		oSrc.value = oSrc.value.substring(0, mlength);
	}
	if(charCount)
	{
		charCount.innerHTML = mlength - oSrc.value.length;
	}
}

function clearReportAbuseForms()
{
	var reportAbuseReasonForm = document.getElementById("ReportAbuse_Form");
	reportAbuseReasonForm.reset();//reset is a clean, scalable way to handle the form clearing but will not reset the character counter
	var charCount = document.getElementById("charCountDisplayRemoval");
	charCount.innerHTML = "500"; //not ideal but need this element update because a form clear will not update the char count text
}


/***
 ***
 *** SITELIFE INTEGRATION
 ***
 ***/

function SiteLifeIntegration () {

	/**
	 ** Load-time DAAPI tools
	 **/
	this.doPageLoadDAAPI = function() {
		if (this.pageLoadDAAPI_RequestBatch != null) {
			this.pageLoadDAAPI_RequestBatch.BeginRequest(daapiServerUrl, new Function("respBatch", "sli.loadTimeCallBack(respBatch)"));
			tools.debug("Begun Page Load DAAPI request to server with " + this.batchRequests.length + " requests");
		}
	}

	/**
	 * Not really sure iof this is needed.  But since there are places where we need to
	 * make a second call to the server, I thought I would make the second call distinct
	 * so that we can come back to it if we need to.
	 */
	this.doPageLoadSubsequentDAAPI = function() {
		if (this.pageLoadDAAPI_RequestBatch != null) {
			this.pageLoadDAAPI_RequestBatch.BeginRequest(daapiServerUrl, new Function("respBatch", "sli.loadTimeCallBack(respBatch)"));
			tools.debug("Begun Subsequent Page Load DAAPI request to server with " + this.batchRequests.length + " requests");
		}
	}

	this.pageLoadDAAPI_RequestBatch = null;

	// Keep a handle on all of the things that we have asked for.
	this.batchRequests = new Array();

	// convenience function so th that batchRequest stays up-to-date.
	this.addToPageLoadRequestBatch = function(request) {
		this.pageLoadDAAPI_RequestBatch.AddToRequest(request);
		this.batchRequests.push(request);
		tools.debug("L132 batchRequests length now " + this.batchRequests.length);
	}

	this.clearPageLoadRequestBatch = function() {
		this.pageLoadDAAPI_RequestBatch = new RequestBatch();
		this.batchRequests.length = 0;
		tools.debug("L137 batchRequests length now " + this.batchRequests.length);
	}

	this.setupPageLoadDAAPI = function() {
		this.pageLoadDAAPI_RequestBatch = new RequestBatch();
	}

	this.loadTimeCallBack = function (responseBatch) { // old jClientCallBack()
		if (SITELIFE_DEBUG) {
			tools.debug("ResponseBatch Messages length is " + responseBatch.Messages.length);
			for (var i = 0; i < responseBatch.Messages.length; i++) {
				var serverMessage = responseBatch.Messages[i];
				tools.debug("Server Message " + i + ":  " + serverMessage.MessageTime + " -- " + serverMessage.Message);
			}

			for (var i = 0; i < responseBatch.Responses.length; i++) {
				var response = responseBatch.Responses[i];
				tools.debug("Response " + i + ":  " + response);
			}
		}
		
		//tools.showProps(responseBatch);
		var tn = document.createTextNode("" + responseBatch.toSource());
		var pre = document.createElement('pre','prid');
		pre.appendChild(tn);
		document.body.appendChild(pre);
		
		for (var i = 0; i < responseBatch.Responses.length; i++) {
			var response = responseBatch.Responses[i];

			if (response.DiscoverArticlesAction != null) {
				if (this.discoverArticlesHandler != null) {
					this.discoverArticlesHandler(response.DiscoverArticlesAction);
				} else {
					tools.debug("No discoverArticlesHandler defined and response " + i + " is a DiscoverArticlesAction");
				}
			} else if (response.Article != null) {
				if (this.articleHandler != null) {
					this.articleHandler(response.Article);
				} else {
					tools.debug("No articleHandler defined and response " + i + " is an Article");
				}
			} else if (response.CommentPage != null) {
				if (this.commentsPageHandler != null) {
//window.alert(response.CommentPage.Comments[0].Author.UserTier);
					this.commentsPageHandler(response.CommentPage);
				} else {
					tools.debug("No commentsPageHandler defined and response " + i + " is a CommentPage");
				}
			}
		}
	}

	/**
	 * DAAPI Action Handlers
	 */
	//this.reportAbuseActionHandler = null;
	this.commentsPageHandler = null;
	this.discoverArticlesHandler = null;
	this.articleHandler = null;

	this.commentActionHandler = function(responseBatch) {
		if (responseBatch.Messages.length != 1) {
			// ASSERT that we only are doing one thing here.
			alert("SiteLife sent me back too many messages.");
			for (var i = 0; i < responseBatch.Messages.length; i++) {
				var serverMessage = responseBatch.Messages[i];
				alert(serverMessage.MessageTime + ": " + serverMessage.Message);
			}
			return false;
		} 
		var serverMessage = responseBatch.Messages[0];
		var response = responseBatch.Responses[0];
		
		tools.debug("Handling response from CommentAction"); // BOOKMARK!
		//we need to do this stuff async
		// check for errors.
		for (var i = 0; i < responseBatch.Responses.length; i++) {
			response = responseBatch.Responses[i];

			if (response.CommentAction != null) {
				alert("Ok, so I get a CommentAction back.  response object " + i);
			}
		}
		// empty out the box so that no one gets confused.
		// ASSERT
		document.getElementById("wp_comments_comment").value = "";
		
		// go look at comments.
		document.location.href = "/wp-dyn/comments/index.html?contentID=" + wp_article.id;
	}
  
	/**
	 ** Form Handlers
	 **/

	/**
	 * Handle comments left on articles.  This method should be invoked by the form
	 * when it is ready to submit the comments to SiteLife.
	 */
	this.doArticleCommentAction = function() {
		tools.debug("I am commenting on an article!");
		var commentEl = document.getElementById("wp_comments_comment");
		var commentActionButtonEl = document.getElementById("wp_comments_submit");

		// ASSERT elements are present.
		
		// stop user from doing this multiple times.
		commentEl.disabled = true;
		commentActionButtonEl.disabled = true;
		
		var commentsRequestBatch = new RequestBatch();
		var commentText = commentEl.value;
	
		if (commentText != null) {
			var finalCommentText = tools.newlinesToHTML(commentText);
			//alert("yea:  " + finalCommentText);
			var theComment = new CommentAction(new ArticleKey(wp_article.id),
											   communityHostUrl + wp_article.path,
											   wp_article.headline,
											   finalCommentText);

			tools.debug("Sending comment for article " + wp_article.id + " to SiteLife.");
			commentsRequestBatch.AddToRequest(theComment);

			// daapiServerUrl is from shared.js
			commentsRequestBatch.BeginRequest(daapiServerUrl, new Function("respBatch", "sli.commentActionHandler(respBatch)"));

		} else {
			tools.debug("Something happened when trying to submit comments.  Empty comment?  REVISIT");
			commentEl.disabled = false;
			commentActionButtonEl.disabled = false;
		}
	}



	this.debug = function(message) {
		if (SITELIFE_DEBUG) {
			var debugEl = document.getElementById("SiteLifeDebug");
			if (debugEl != null) {
				debugEl.innerHTML = debugEl.innerHTML + message + "\n";
			} else {
				window.alert(message);
			}  
		}
	}
}

// Instantiate a SiteLifeIntegration, if we need to.
var sli = null;
if (SITELIFE_ENABLED) {
	sli = new SiteLifeIntegration();
}

/*
 * Article
*/
function _Article() {
	this.articleKey; // ArticleKey object
	this.serverUrl = daapiServerUrl;
	this.Article = null;
	this.User = null;
	this.CommentCountHandler;
	this.updateArticleActionObj = null;
	this.CallBack = function f() { };
	this.Sections = null;
	this.Categories = null;
	this.UserTier = null;
	this.Age = 2;  // maximum days back to discover articles
	this.ItemsToGet = 10; // number of items to return

	/**
	 * Sets the handler for the response from the SiteLife servers
	 * DAAPI Processor.  For reasons of scope, this has to be set once
	 * the object is instatiated and cannot be set within the object.
	 *
	 * callBack - a function that takes a DAAPI ResponseBatch object.
	 *            Should always be set to the instantiated object of
	 *            this class's ProcessResponse method.
	 */
	this.setCallBack = function (callBack) {
		this.CallBack = callBack;
	}

	/**
	 * articleDetails - an UpdateArticleAction object.
	 */
	this.SetArticleDetails = function(articleDetails) {
//        console.dir(articleDetails);
		this.articleKey = new ArticleKey(articleDetails.UpdateArticleAction.UpdateArticle.ArticleKey.Key);
		this.updateArticleActionObj = articleDetails;
	}
	this.SetDiscoverySections = function(sec) {
		this.Sections = sec;
	}
	this.SetDiscoveryCategories = function(cat) {
		this.Categories = cat;
	}
	this.SetDiscoveryUserTiers = function(contrib) {
		this.UserTier= contrib;
	}
	this.SetDiscoveryActivities = function(act) {
		this.Activity= act;
	}
	this.ProcessResponse = function(responseBatch) {
		//tools.debug("ProcessResponse");
		var bNeedToUpdate = false;
		this.Article=null;  //MSM:9/26/07
		for (var i = 0; i < responseBatch.Responses.length; i++) {
			var response = responseBatch.Responses[i];
			// our request was good
			if (response.Article != null) {
				this.ParseArticle(response.Article);
				// see if we need to run update article action
				if (this.updateArticleActionObj != null) {
					// since WPNI is using only Sections at this time we left out the Categories
					if ((JSON.stringify(response.Article.Section) != JSON.stringify(this.updateArticleActionObj.UpdateArticleAction.Section.Section)) || 
						//(JSON.stringify(response.Article.Categories.Name) != JSON.stringify(this.updateArticleActionObj.UpdateArticleAction.Categories.Categories)) || 
						(response.Article.PageTitle != this.updateArticleActionObj.UpdateArticleAction.OnPageTitle) || 
						(response.Article.PageUrl != this.updateArticleActionObj.UpdateArticleAction.OnPageUrl)) 
					{
						bNeedToUpdate = true;
					}
				}
			} else if (response.User != null) {
				this.User = response.User;
			} else if (response.DiscoverArticlesAction != null) {
				// reuses the 'this.Article.'
				this.ParseArticle(response.DiscoverArticlesAction);
			} else {					// added these lines
				bNeedToUpdate = true;	// added these lines
}										// added these lines
		}
		// if (this.Article)  //MSM:9/26/07
			this.CommentCountHandler(this.Article);  // this is calling your Draw..Function with the Article set from ‘this.ParseArticle(response.Article)’ above.
									// then when that is done, we send off the request to UpdateArticleAction if and only if we need to.
		if (bNeedToUpdate == true) {
			// RunUpdateArticleAction
			var requestBatch = new RequestBatch();
			// Always get teh current user!
			if (this.updateArticleActionObj != null) {
				requestBatch.AddToRequest(this.updateArticleActionObj);
				requestBatch.BeginRequest(daapiServerUrl, this.CallBack);
			}
		}
	}
	this.ParseArticle = function(articleObj) {
		this.Article = articleObj;
	}

	this.SetCommentCountHandler = function(handler) {
		this.CommentCountHandler = handler;
	}
	this.SetDiscoveryHandler = function(handler) {
		this.CommentCountHandler = handler;
	}
	
	this.CallServer = function() {
		if (null == this.Article) {
			// ASSERT: I better have an article id!
			var requestBatch = new RequestBatch();
// console.dir(this.articleKey);
			// Always get teh current user!
			requestBatch.AddToRequest(this.articleKey);
			requestBatch.AddToRequest(new UserKey());
			if (this.updateArticleActionObj != null) {
				// requestBatch.AddToRequest(this.updateArticleActionObj);
			}
// console.dir(requestBatch);
			requestBatch.BeginRequest(daapiServerUrl, this.CallBack);
		}
	}
	this.Discover = function() {
		// lets find some articles via discovery
		var requestBatch = new RequestBatch();      
		var discoveryAction = new DiscoverArticlesAction(  
				this.Sections,  
				this.Categories,  
				this.UserTier,  
				this.Activity,  
				this.Age,  
				this.ItemsToGet);  
		
		requestBatch.AddToRequest(discoveryAction);   
		requestBatch.BeginRequest(daapiServerUrl, this.CallBack);   
	}
}

/*
 * Comments:
 * GetAllComments
 * Given an ArticleID build an array of all the comments
*/
function _Comments() {
	this.PERMALINK_QUERYSTRING_PARAM = "sl_cpl";
	this.articlekey;
	this.article = null; // the article that we are working with
	this.batch = 1;
	this.count = 0;
	this.sortOrder = "TimeStampDescending";
	this.serverUrl = "http://community.washingtonpost.com/ver1.0/Direct/Process";
	this.theComments = new Array();     // array of comments 
	this.DesiredComments  = 10;         // number that want to be returned.
	this.AbuseThreshhold = 3;           // number of abuse counts to ignore the comments.
	this.NumberOfComments = 0;          // total number of comments for this article
	this.User = null;                   // currently logged in user
	this.CallBack = null;               // callback function to call after comments are returned
	this.CBCommentDisplay = null;       // user callback function to draw the comments with the array of comments
	this.CommentsPerPage = 20;          // number of comment per paginated page (this has to be a multiple of 10)
	this.OnPage =0;                     // on page what of pagination - pluck count in batches of 10
	this.NumberPerPage=0;               // number of comments per page - pluck count normally 10
	this.PageNumber = 1;                // 1 based page number of current pagination
	this.FailureCallBack = null;        // this is called when a comment fails (typically dirty work filter)
	this.SuccessCallBack = null;        // this is called whne a comment is successful (typically turning the page)
	this.CmtCallBack = null;            // comment callback, necessary to split since we want comments and entry on the same page
	this.needCommentCount = false;
	this.pageToGoTo = 0; //hack
//	console.log("starting  _Comments");
}
_Comments.prototype.SetSortOrder = function(sort) {
	this.sortOrder = sort;
}
_Comments.prototype.SetCommentDisplay = function(func) {
	this.CBCommentDisplay = func;
}
_Comments.prototype.SetReturnCount = function(count) {
	this.DesiredComments = count;
}
_Comments.prototype.SetCallBack = function(ourCB) {
	this.CallBack = ourCB;
}
_Comments.prototype.SetCmtCallBack = function(ourCB) {
	this.CmtCallBack = ourCB;
}
// return the page number that we are currently requesting/viewing
// this is for pagination in the UI
_Comments.prototype.GetCurrentPageNumber = function() {
	return (this.PageNumber);
}
_Comments.prototype.GetCurrentPermaPageNumber = function() {
//	return (this.PageNumber);
	return (this.GetPageCount()- this.PageNumber);
}
_Comments.prototype.SetSuccessCallBack = function(func) {
	this.SuccessCallBack = func;
}
_Comments.prototype.SetFailureCallBack = function(func) {
	this.FailureCallBack = func;
}

_Comments.prototype.GetComments = function(key) {
	this.articlekey = key;
	var  qs = new  _QueryString();
	var page = qs.CommentPage();
		tools.debug("starting page"+page);
		tools.debug("starting page");
	if (page) {
		this.needCommentCount = true;
		this.pageToGoTo = page;
		this.GetAllComments(key);
		this.ScrollToLink = "articleComment_"+qs.CommentKey();
		
	} else {
		this.GetAllComments(key);
	}
	
	return (true);
}
_Comments.prototype.GetCommentsPage = function(pageNumber) {
	this.count = 0;

	this.batch = ((parseInt(pageNumber)*parseInt(this.CommentsPerPage))/10);  // divide by plucks batch number to get the right page
	this.batch = parseInt(this.batch) -1;  // subtract one because the pages are 1 based.
	this.PageNumber = parseInt(pageNumber);
	this.GetAllComments(this.articlekey);
	this.theComments.length = 0;
}
_Comments.prototype.GetPageCount = function() {
	return parseInt(1+parseInt(this.NumberOfComments)/this.CommentsPerPage);
}
_Comments.prototype.GetAllComments = function(key) {
	// article is the unique identifier
	var articleKey = new ArticleKey(key);
	// allocate a new request batch to send to the pluck server
	var requestBatch = new RequestBatch();
	// We are interested in comments, lets get the comment page
	var commentPage = new CommentPage(articleKey,10,this.batch,this.sortOrder);
	requestBatch.AddToRequest(new UserKey());
	requestBatch.AddToRequest(articleKey);
	requestBatch.AddToRequest(commentPage);
	requestBatch.BeginRequest(this.serverUrl, this.CallBack);
	// save the articlekey for subsequent calls.
	this.articlekey = key;
}
_Comments.prototype.GetMoreComments = function() {
	this.GetAllComments(this.articlekey,this.PluckReturn);
}
_Comments.prototype.ParseCommentPage = function(cmtPage) {
	// parse the comment page and build the comment list
	tools.debug("ParseCommentPage");
	this.NumberOfComments = cmtPage.NumberOfComments;
	this.NumberPerPage = cmtPage.NumberPerPage;
	this.OnPage = cmtPage.OnPage;
	tools.debug("cmtPage.Comments.length : " + cmtPage.Comments.length);
	for (var c=0;c < cmtPage.Comments.length; c++) {
		// this check does the normal display stuff. 
		// showing the comments if they are valid (notBlocked,notAbusive,and equal to the number we want to see)
		if ((cmtPage.Comments[c].Author.IsBlocked == "False") &&
			(parseInt(cmtPage.Comments[c].AbuseReportCount) < this.AbuseThreshhold) &&
			(this.count < this.DesiredComments)){
			this.theComments[this.count]= cmtPage.Comments[c];
			this.count ++ ;
		}
		else if ((cmtPage.Comments[c].Author.IsBlocked == "True") &&
			// if this is a bozo'd user, show his comments
			(parseInt(cmtPage.Comments[c].AbuseReportCount) < this.AbuseThreshhold) &&
			(cmtPage.Comments[c].Author.UserKey.Key == this.User.UserKey.Key) &&
			(this.count < this.DesiredComments)){
			this.theComments[this.count]= cmtPage.Comments[c];
			this.count ++ ;
		}
	}
}
_Comments.prototype.PluckReturn = function(responseBatch) {
//	console.log("PluckReturn");
//	console.dir(responseBatch);
	for (var i = 0; i < responseBatch.Responses.length; i++) {
		var response = responseBatch.Responses[i];
		// Need to scrub this, because we are not always going to get an "ok" back now that we are also asking for a User.
		//if(responseBatch.Messages[0].Message == 'ok') {
			// our request was good
			if (response.CommentPage != null) {
				// we have a comment page/header
				if (this.ParseCommentPage != null){
					this.ParseCommentPage(response.CommentPage);
				}
			} else if (response.User != null) {
				this.User = response.User;
			} else if (response.Article != null) {
				// we have an article page with summary information
				this.parseArticle(response.Article);
			} 
		//}
	}
	if (this.needCommentCount == false) {
		if (this.count < this.DesiredComments  && 
			this.count < this.NumberOfComments && 
			(parseInt(this.OnPage)*parseInt(this.NumberPerPage))<this.NumberOfComments) {
			this.batch ++;
			this.GetMoreComments();
		}
		else {
			tools.debug("--Done--");
	//		this.DumpComments();
			this.CBCommentDisplay(this.theComments);
			if (this.ScrollToLink.length > 1 ) {
				document.getElementById(this.ScrollToLink).scrollIntoView(true);
				this.ScrollToLink = "";
			}
			
		}
	}
	else {
		// now we have the total comment count.. lets to get the right page
		this.GetCommentsPage(this.GetPageCount()-this.pageToGoTo);
		this.needCommentCount = false;
	}
}

_Comments.prototype.parseArticle = function(articleObj) {
	this.article = articleObj;
}

/**
 * Returns the current page number from a view standpoint.  If there
 * are 20 comments per page, and we are looking at comments 41 - 60,
 * then this will return 3.
 */
_Comments.prototype.xxxgetCurrentPageNumber = function () {
	// This may bne the right answer... TBD with Matt.
	return this.OnPage;
}
_Comments.prototype.GetCommentArray = function() {
	return this.theComments;
}
// find the comment given the comment key
_Comments.prototype.RecommendedCount = function(key){
	for (var c=0;c < this.theComments.length;c++) {
		if (this.theComments[c].CommentKey.Key == key) {
			return parseInt(this.theComments[c].NumberOfRecommendations);
		}
	}
	return 0;
}

// find the comment given the comment key
_Comments.prototype.getFromLoadedCommentsByKey = function(key){
    var theComment = null;
	for (var c = 0; (theComment == null) && (c < this.theComments.length); c++) {
		if (this.theComments[c].CommentKey.Key == key) {
			theComment = this.theComments[c];
		}
	}
	return theComment;
}

_Comments.prototype.Recommend = function(key,cb) {
	//recommend this comment
	var	recKey=new CommentKey(key);

	var requestBatch=new RequestBatch();
	requestBatch.AddToRequest(new RecommendAction(recKey));
	this.SiteLifeRequest(requestBatch,this.RecommendCallback);
	if (cb) {
		cb(key,this.RecommendedCount(key));
	}
}
_Comments.prototype.RecommendCallback = function(response) {
	//report this comment as recomended
	if(response.Messages.length>0&&response.Messages[0].Message=="ok"){
	}
	else {
		tools.debug("RecommendCallback Failed");	
	}
}
_Comments.prototype.ReportAbuse = function(key,reason,text,cb) {
	//report this comment as abusive.
	var	recKey=new CommentKey(key);

	var requestBatch=new RequestBatch();
	requestBatch.AddToRequest(new ReportAbuseAction(recKey,reason,text));
	this.SiteLifeRequest(requestBatch,this.AbuseCallback);
	if (cb) {
		cb(key);
	}
}
_Comments.prototype.AbuseCallback = function(response) {
	//report this comment as abusive.
	if(response.Messages.length>0&&response.Messages[0].Message=="ok"){
	}
	else {
		tools.debug("ReportAbuseCallback Failed");	
	}
}
_Comments.prototype.NumberOfComments = function(responseBatch) {
	return (this.NumberOfComments);
}
_Comments.prototype.SiteLifeRequest = function(request,callback) {
	request.BeginRequest(this.serverUrl,callback);
}
_Comments.prototype.Clean = 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;");
		clean=clean.replace(/\r\n/g,"\n");
		clean=clean.replace(/\n/g,"<br />\n");
	}
	return (clean);
}
_Comments.prototype.AddComment = function(key,cmt) {
	var articleKey = new ArticleKey(key);
	var pageUrl = document.location.href; 
	var pageTitle = document.title;
	var cleancmt = this.Clean(cmt);
	// create and send request
	var requestBatch = new RequestBatch();
	var commentAction = new CommentAction(articleKey, pageUrl, pageTitle, cleancmt);
	requestBatch.AddToRequest(commentAction);
	this.SiteLifeRequest(requestBatch, this.CmtCallBack);
}
_Comments.prototype.commentSubmitted = function(responseBatch) {
	if (responseBatch.Messages[0].Message == 'ok') {
		if(this.SuccessCallBack) {
			this.SuccessCallBack();
		}
		else {
			alert(responseBatch.Messages[0].Message);
		}
	}
	else { 
		// need to check return codes for 'dirty word filter,etc'
		if(this.FailureCallBack) {
			this.FailureCallBack(responseBatch.Messages[0].Message);
		}
		else {
			alert(responseBatch.Messages[0].Message);
		}
	}
}
_Comments.prototype.countChars = function(field,countfield, maxlimit){
	if (document.getElementById(field).value.length > maxlimit) { // trim if too long
		document.getElementById(field).value = document.getElementById(field).value.substring(0, maxlimit);
	}
	else {
		document.getElementById(countfield).innerHTML = 0 + document.getElementById(field).value.length +"/"+maxlimit ;
	}
}


/**
 * Draws the link for a Staff Recomends button to the comment on the
 * page at the designated element.  The contents of elementId will be
 * overwritten.  At this time, the link will only show for Staff and
 * Editor tier users.
 *
 * Unlike other functions that require a Display method to draw the
 * button, this function writes straight to the DOM.
 *
 * currentComment - the DAAPI Comment object for the comment that we
 *                  are currently working with.
 * parentEl - the DOM element to put the function.
 */
_Comments.prototype.CommentStaffRecommendsButton = function (currentComment, parentEl) {
	// get the menu that we need on the page.
	this.writeStaffRecommendDialog(document.body);

	if (this.User.UserTier == "Editor" || this.User.UserTier == "Staff") {
		var linkEl = document.createElement("a");
		linkEl.innerHTML = "Add as Staff Recommendation";
		linkEl.onclick = function(event,Key) { cmt.openStaffRecommendsDialog(event || window.event, currentComment.CommentKey.Key ); };
		// linkEl.onclick = function() { cmt.openStaffRecommendsDialog(event, currentComment.CommentKey.Key ); };
//		linkEl.setAttribute("onclick", "cmt.openStaffRecommendsDialog(event, '" + currentComment.CommentKey.Key + "');");
		linkEl.setAttribute("href", "javascript:void(0)");
		parentEl.appendChild(linkEl);
	}
}


/**
 * Draws the link for a PermaLink to the comment on the page at the
 * designated element.  The contents of elementId will be overwritten.
 * At this time, the link will only show for Staff and Editor tier
 * users.
 *
 * Unlike other functions that require a Display method to draw the
 * button, this function writes straight to the DOM.
 *
 * currentComment - the DAAPI Comment object for the comment that we
 *                  are currently working with.
 * parentEl - the DOM element to put the function.
 */
_Comments.prototype.CommentPermaLinkButton = function (currentComment, parentEl) {
	if (this.User.UserTier == "Editor" || this.User.UserTier == "Staff") {
		var linkEl = document.createElement("a");
		linkEl.setAttribute("href", this.createPermaLink(currentComment.CommentKey.Key));
		linkEl.innerHTML = "PermaLink";
		parentEl.appendChild(linkEl);
	}
}


/**
 * Creates a URL to a comment.  Assumes that the commentId passed is
 * for a comment on hte current page.  The format of the querystring
 * value is:
 *
 *    <pagenumber> ":" <commentId>
 *
 * commenId - the DAAPI Comment CommentKey.Key value.  A text string.
 */
_Comments.prototype.createPermaLink = function (commentId) {
	var pageURL = document.location.protocol
				  + "//" + document.location.host
				  + document.location.pathname + "?"
				  + this.PERMALINK_QUERYSTRING_PARAM + "="
				  + this.GetCurrentPermaPageNumber() + ":"
				  + commentId;
	var keyValuePairs = new Array();
	if (document.location.search) {
		keyValuePairs = document.location.search.substr(1).split("&")
	}
	for (var i = 0 ; i < keyValuePairs.length; i++) {
		var eqLoc = keyValuePairs[i].indexOf("=");
		if (eqLoc > 0) {
			var key = keyValuePairs[i].substr(0, eqLoc);
			if (key != this.PERMALINK_QUERYSTRING_PARAM) {
				pageURL = pageURL + "&" + keyValuePairs[i];
			}
		}
	}

	return pageURL;
}

_Comments.prototype.openStaffRecommendsDialog = function(mouseEvent, commentKey) {
	//alert("I want to tell you about " + mouseEvent);
	//ShowDivAtMouse
	//document.getElementById("ReportAbuse_Key").value = commentKey;
    var formCommentKeyEl = document.getElementById("SLSR_Comment_Key");
    var formCommentOrigTextEl = document.getElementById("SLSR_Comment_Orig_Text");
    var formCommentOrigDateEl = document.getElementById("SLSR_Comment_Orig_Date");
    var formCommentUserDisplayNameEl = document.getElementById("SLSR_Comment_Orig_User_DisplayName");
    var formCommentUserKeyEl = document.getElementById("SLSR_Comment_Orig_User_Key");
    var formCommentOrigPermaLinkEl = document.getElementById("SLSR_Comment_Orig_PermaLink");
    var formArticleKeyEl = document.getElementById("SLSR_Article_Key");
    var formArticleURLEl = document.getElementById("SLSR_Article_URL");
    var formArticleTitleEl = document.getElementById("SLSR_Article_Title");
    var formCommentTextEl = document.getElementById("SL_StaffRecommends_CommentText");
    var formRecommendQueueEl = document.getElementById("SL_StaffRecommends_Queue");
    var origComment = this.getFromLoadedCommentsByKey(commentKey);
    formCommentKeyEl.value = commentKey;
    formCommentOrigTextEl.value = origComment.CommentBody;
    formCommentOrigDateEl.value = origComment.PostedAtTime;

    formCommentUserDisplayNameEl.value = origComment.Author.DisplayName;
    formCommentUserKeyEl.value = origComment.Author.UserKey.Key;

    formCommentOrigPermaLinkEl.value = this.createPermaLink(commentKey);
    //console.dir(this.article);
    formArticleKeyEl.value = this.article.ArticleKey.Key;
    formArticleURLEl.value = this.article.PageUrl;
    formArticleTitleEl.value = this.article.PageTitle;
    
    formCommentTextEl.innerHTML = tools.htmlToNewlines(origComment.CommentBody);

	tools.showDivAtMouse(mouseEvent, "SL_StaffRecommends_Container");
}

/**
 * Writes the hidden menu for Staff Recommends out to the page.  This
 * is safe to call multiple times.
 *
 * perentEl - the element of the DOM to hang the menu off of.
 *            document.body should be fine.
 */
_Comments.prototype.writeStaffRecommendDialog = function(parentEl) {
	var alreadyThere = document.getElementById("SL_StaffRecommends_Container");
	if (null === alreadyThere) {
		tools.debug("Need to write SL_StaffRecommends_Container");
		var cont = document.createElement("div");
		cont.className = "SL_StaffRecommends_Container" ;
		cont.id ="SL_StaffRecommends_Container";
		cont.style.display = "none";
		cont.style.left = "763px";
		cont.style.top = "370px";
//		cont.setAttribute("class", "SL_StaffRecommends_Container");
//		cont.setAttribute("id", "SL_StaffRecommends_Container");
//		cont.setAttribute("style", "display: none; left: 763px; top: 370px");
		cont.innerHTML = "<form id=\"SL_StaffRecommends_Form\" action=\"\">    <input id=\"SLSR_Comment_Key\" type=\"text\" value=\"\" />    <input id=\"SLSR_Comment_Orig_Text\" type=\"text\" value=\"\" />    <input id=\"SLSR_Comment_Orig_Date\" type=\"text\" value=\"\" />    <input id=\"SLSR_Comment_Orig_User_DisplayName\" type=\"text\" value=\"\" />    <input id=\"SLSR_Comment_Orig_User_Key\" type=\"text\" value=\"\" />    <input id=\"SLSR_Comment_Orig_PermaLink\" type=\"text\" value=\"\" />    <input id=\"SLSR_Article_Key\" type=\"text\" value=\"\" />    <input id=\"SLSR_Article_URL\" type=\"text\" value=\"\" />    <input id=\"SLSR_Article_Title\" type=\"text\" value=\"\" />    <div class=\"SL_StaffRecommends_SectionHead\">      <span style=\"float: right\"><a href=\"#none\" onclick=\"tools.hideDiv('SL_StaffRecommends_Container');cmt.clearStaffRecommendForm()\" style=\"display:block;padding:1px 6px 1px 6px\">X</a></span>      <span style=\"float: left\">Staff Recommends</span>    </div>    <div class=\"SL_StaffRecommends_Interior\">      <div class=\"SL_StaffRecommends_Comment\">        <textarea id=\"SL_StaffRecommends_CommentText\" name=\"SL_StaffRecommends_CommentText\"></textarea><select id=\"SL_StaffRecommends_Queue\" name=\"SL_StaffRecommends_Queue\"><option value=\"1\">1</option><option value=\"2\">2</option><option value=\"3\">3</option><option value=\"4\">4</option><option value=\"5\">5</option></select><input onclick=\"cmt.doStaffRecommend(); return false;\" type=\"button\" value=\"Recommend\" />      </div>    </div>  </form>";
		parentEl.appendChild(cont);
	}
}

/**
 * Sets all of the form variables/inputs used by the Staff Recommends
 * dialog back to their normal state.
 */
_Comments.prototype.clearStaffRecommendForm = function() {
	// REVISIT
    var valsToClear = new Array("SLSR_Comment_Key",
                                "SLSR_Comment_Orig_Text",
                                "SLSR_Comment_Orig_Date",
                                "SLSR_Comment_Orig_User_DisplayName",
                                "SLSR_Comment_Orig_User_Key",
                                "SLSR_Comment_Orig_PermaLink",
                                "SLSR_Article_Key",
                                "SLSR_Article_URL",
                                "SLSR_Article_Title",
                                "SL_StaffRecommends_CommentText");
}

/**
 * Actually do the work!
 */
_Comments.prototype.doStaffRecommend = function() {
	// REVISIT
    var form = document.getElementById("SL_StaffRecommends_Form");
    var formCommentKeyEl = document.getElementById("SLSR_Comment_Key");
    var formCommentOrigTextEl = document.getElementById("SLSR_Comment_Orig_Text");
    var formCommentOrigDateEl = document.getElementById("SLSR_Comment_Orig_Date");
    var formCommentUserDisplayNameEl = document.getElementById("SLSR_Comment_Orig_User_DisplayName");
    var formCommentUserKeyEl = document.getElementById("SLSR_Comment_Orig_User_Key");
    var formCommentOrigPermaLinkEl = document.getElementById("SLSR_Comment_Orig_PermaLink");
    var formArticleKeyEl = document.getElementById("SLSR_Article_Key");
    var formArticleURLEl = document.getElementById("SLSR_Article_URL");
    var formArticleTitleEl = document.getElementById("SLSR_Article_Title");
    var formCommentTextEl = document.getElementById("SL_StaffRecommends_CommentText");
	var formQueue = document.getElementById("SL_StaffRecommends_Queue");

    var sr = new _StaffRecommendation();

    sr.recommendationBody = formCommentTextEl.innerHTML;
    sr.commentKey = formCommentKeyEl.value;
    sr.commentDate = formCommentOrigDateEl.value;
    sr.commentUserDisplayName = formCommentUserDisplayNameEl.value;
    sr.commentUserKey = formCommentUserKeyEl.value;
    sr.commentPermaLink = formCommentOrigPermaLinkEl.value;
    sr.articleKey = formArticleKeyEl.value;
    sr.articleURL = formArticleURLEl.value;
    sr.articleTitle = formArticleTitleEl.value;
    sr.queueKey = formQueue.options[formQueue.selectedIndex].value
    
    sr.submit();

	tools.hideDiv("SL_StaffRecommends_Container");

}

/**** degugging ****/
_Comments.prototype.DumpComments = function() {
	tools.debug("Dumpin Comments");
	for (var c=0;c < this.theComments.length;c++) {
		//if(console) {
		//	console.log(c + " : " + this.theComments[c].CommentBody.substring(0,50));
		//}
	}
}
function _QueryString() {
	this.PERMALINK_QUERYSTRING_PARAM = "sl_cpl";
}
_QueryString.prototype.PageQuery = function(q) {
	if(q.length > 1)
		this.q = q.substring(1, q.length);
	else
		this.q = null;
	this.keyValuePairs = new Array();
	if(q) {
		for(var i=0; i < this.q.split("&").length; i++) {
			this.keyValuePairs[i] = this.q.split("&")[i];
		}
	}
	this.getKeyValuePairs = function() { return this.keyValuePairs;
	}
}
_QueryString.prototype.getValue = function(s) {
	for(var j=0; j < this.keyValuePairs.length; j++) {
		if(this.keyValuePairs[j].split("=")[0] == s)
			return this.keyValuePairs[j].split("=")[1];
	}
	return '';
}
_QueryString.prototype.getParameters = function() {
	var a = new Array(this.getLength());
	for(var j=0; j < this.keyValuePairs.length; j++) {
		a[j] = this.keyValuePairs[j].split("=")[0];
	}
	return a;
_QueryString.prototype.getLength = function() {
	return this.keyValuePairs.length; }
}
_QueryString.prototype.parseDomainFromURL = function () {
	var url = window.location.href.split("/");
	var domainparts=url[4].split(".");
	var domain = domainparts[parseInt(domainparts.length)-1] + "." + domainparts[parseInt(domainparts.length)];
	parseDomainFromURL = domain;
	return parseDomainFromURL;
}
_QueryString.prototype.queryString = function (key){
	var page = this.PageQuery(window.location.search);
	return unescape(this.getValue(key));
}
_QueryString.prototype.CommentKey = function(){
	var page = this.PageQuery(window.location.search);
	if (window.location.search.length == 0)
		return page;
	var page = this.PageQuery(window.location.search);
	var commenttKey = this.getValue(this.PERMALINK_QUERYSTRING_PARAM);
	return unescape(commenttKey.substring(commenttKey.indexOf(":")+1));
}
_QueryString.prototype.CommentPage = function(){
	var page = this.PageQuery(window.location.search);
	if (window.location.search.length == 0)
		return page;
	var page = this.PageQuery(window.location.search);
	var commenttPage = this.getValue(this.PERMALINK_QUERYSTRING_PARAM);
	return unescape(commenttPage.substring(0,commenttPage.indexOf(":")));
}

/***
***
***/
function _StaffRecommendation() {
    this.recommendationBody = "";
    this.commentKey = "";
    this.commentDate = "";
    this.commentUserDisplayName = "";
    this.commentUserKey = "";
    this.commentPermaLink = "";
    this.articleKey = "";
    this.articleURL = "";
    this.articleTitle = "";
    this.queueKey = "1";

    /**
     * 
     */
    this.submit = function() {
        // create and send request
        var requestBatch = new RequestBatch();

        var reviewAction = new ReviewAction(new ArticleKey(this.getQueueName()), this.commentPermaLink, this.articleTitle, this.articleTitle, "5", this.recommendationBody, "----EDITOR RECOMMENDATION (ignore data below) ----", this.getEncapsulatedDataBlob());

//        console.log("Here is our ReviewAction object...");
//        console.dir(reviewAction);

        requestBatch.AddToRequest(reviewAction);
        requestBatch.BeginRequest(daapiServerUrl, __StaffRecommendationCallBack);
    }

    /**
     * 
     */
    this.getQueueName = function() {
        return "SL_StaffRecommendation_Queue_" + this.queueKey;
    }

    /**
     * 
     */
    this.getDataBlob = function() {
        // we need to create a serializable version of ourselves.
        var StaffRecommendation = {};
        StaffRecommendation.commentKey = this.commentKey;
        StaffRecommendation.commentDate = this.commentDate;
        StaffRecommendation.commentUserDisplayName = this.commentUserDisplayName;
        StaffRecommendation.commentUserKey = this.commentUserKey;
        StaffRecommendation.commentPermaLink = this.commentPermaLink;
        StaffRecommendation.articleKey = this.articleKey;
        StaffRecommendation.articleURL = this.articleURL;
        StaffRecommendation.articleTitle = this.articleTitle;

//        console.log("Here is our StaffRecommendation object...");
//        console.dir(StaffRecommendation);
        
	    var strJSON = JSON.stringify(StaffRecommendation);
		console.log(strJSON);
		return(strJSON);
    }

    this.getEncapsulatedDataBlob = function() {
        return (this.getDataBlob());
// can't wrap in comments as it all get stripped.
//        return "JSON Data <!-- " + this.getDataBlob() + " -->";
    }
}

/**
 *
 */
function __StaffRecommendationCallBack(responseBatch) {
//    console.dir(responseBatch);
//alert(responseBatch.Messages[0].Message);
    if (responseBatch.Messages[0].Message == 'ok') {
        alert('staff recommendation successfully submitted');
    }
}

/*
 * Comments:
 * GetAllComments
 * Given an ArticleID build an array of all the comments
*/
function _Reviews() {
	this.articlekey;
	this.article = null; // the article that we are working with
	this.batch = 1;
	this.count = 0;
	this.sortOrder = "TimeStampDescending";
	this.serverUrl = "http://community.washingtonpost.com/ver1.0/Direct/Process";
	this.CallBack = null;               // callback function to call after comments are returned
	this.CBReviewDisplay = null;       // user callback function to draw the comments with the array of comments
	this.theReviews = null ;// = new Array();
}
_Reviews.prototype.SetSortOrder = function(sort) {
	this.sortOrder = sort;
}
_Reviews.prototype.SetReviewDisplay = function(func) {
	this.CBReviewDisplay = func;
}
_Reviews.prototype.SetCallBack = function(ourCB) {
	this.CallBack = ourCB;
}
_Reviews.prototype.GetReviews = function(key) {
	this.articlekey = key;
	// article is the unique identifier
	var articleKey = new ArticleKey(key);
	// allocate a new request batch to send to the pluck server
	var requestBatch = new RequestBatch();
	// We are interested in comments, lets get the comment page
	var reviewPage = new ReviewPage(articleKey,10,this.batch,this.sortOrder);
	requestBatch.AddToRequest(reviewPage);
	requestBatch.BeginRequest(this.serverUrl, this.CallBack);
	// save the articlekey for subsequent calls.
	this.articlekey = key;
}
_Reviews.prototype.ParseReviewsPage = function(revPage) {
	// parse the comment page and build the comment list
	tools.debug("ParseCommentPage");
	tools.debug("revPage.Reviews.length : " + revPage.Reviews.length);
//	for (var c=0;c < revPage.Reviews.length; c++) {
		// this check does the normal display stuff. 
		// showing the comments if they are valid (notBlocked,notAbusive,and equal to the number we want to see)
		this.theReviews = revPage;
//	}
}
_Reviews.prototype.PluckReturn = function(responseBatch) {
	console.log("PluckReturn");
	console.dir(responseBatch);
	for (var i = 0; i < responseBatch.Responses.length; i++) {
		var response = responseBatch.Responses[i];
			if (response.ReviewPage != null) {
				// we have a comment page/header
				if (this.ParseReviewsPage != null){
					this.ParseReviewsPage(response.ReviewPage);
				}
			}
		}
	this.CBReviewDisplay(this.theReviews);
}

_Reviews.prototype.parseArticle = function(articleObj) {
	this.article = articleObj;
}
