// JavaScript code for the IconRatingSelector class, used in the 5 Star Rating Component

function RatingSelector(submissionUrl) {
	this.submissionUrl = submissionUrl;

	this.onSubmissionSuccess = function(rating, updatedXmlData) {
	}
	
	this.onSubmissionError = function(rating, errorMessage) {
		alert(errorMessage);
	}

	/**
	 * Internal method to return the body text of an XML Element.  FIXME: This method
	 * should exist in a utility script somewhere.
	 */
	this.getElementText = function(xmlElement) {
		var text = "";
		if (xmlElement.textContent) {
			text = xmlElement.textContent;
		}
		else if (xmlElement.text) {
			text = xmlElement.text;
		}
		return text;
	}

	/**
	 * Sets the currently displayed rating.
	 * @param ratingCount
	 *          the number of submitted ratings.
	 * @param ratingTotal
	 *          the sum of all submitted ratings.
	 * @param averageRating
	 *          the average rating.
	 */
	this.setRatingStatistics = function(ratingCount, ratingTotal, averageRating) {
	}

	this.submitRating = function(rating) {
		// start ajax request to submit this rating, but return immediately.
		var savingRatingSelector = this;
		function handleRatingSubmission(xmlDoc, text) {
			var success = false;
			var message = "error, recieved: " + text;
			if (xmlDoc.documentElement.getElementsByTagName("Submission").length > 0) {
				var submissionElement = xmlDoc.documentElement.getElementsByTagName("Submission")[0];
				success = (submissionElement.getAttribute("success") == "true");
				message = savingRatingSelector.getElementText(submissionElement);
			}
			else if (xmlDoc.documentElement.getElementsByTagName("Errors").length > 0) {
				var errorsElement = xmlDoc.documentElement.getElementsByTagName("Errors")[0];
				var messageElements = errorsElement.getElementsByTagName("Message");
				message = "Rating could not be submitted.";
				if (messageElements.length > 0) {
					message = message + "  (" + savingRatingSelector.getElementText(messageElements[0]) + ")";
				}
			}
			if (success) {
				savingRatingSelector.onSubmissionSuccess(rating, xmlDoc);
			}
			else {
				alert(message);
			}
		}

		var req = new AjaxRequest(this.submissionUrl, handleRatingSubmission);
		req.setParameter('submittedRating', rating);
		req.submit();
	}

	/**
	 * Updates the display given an XML payload retreived from the RatingController.
	 * 
	 * Expects XML in the format:
	 *   <Result>
	 *     <RatingStatistics targetId="12345">
	 *       <AverageRating>3.75</AverageRating>
	 *       <RatingCount>48</RatingCount>
	 *       <RatingTotal>180</RatingTotal>
	 *       <SubmitterRating submitterId="45678">3</SubmitterRating>
	 *     </RatingStatistics>
	 *   </Result> 
	 * @param xmlDoc
	 *          the XML Document object.
	 */
	this.updateFromXml = function(xmlDoc) {
		var statsElement = xmlDoc.getElementsByTagName("RatingStatistics")[0];
		var averageRatingElement = statsElement.getElementsByTagName("AverageRating")[0];
		var ratingCountElement = statsElement.getElementsByTagName("RatingCount")[0];
		var ratingTotalElement = statsElement.getElementsByTagName("RatingTotal")[0];
		var averageRating = this.getElementText(averageRatingElement);
		var ratingCount = this.getElementText(ratingCountElement);
		var ratingTotal = this.getElementText(ratingTotalElement);
		averageRating = Math.round(averageRating * 10.0) / 10.0;
		this.setRatingStatistics(ratingCount, ratingTotal, averageRating);
	}
}

/** Global array holding all instances. */
var iconRatingSelectors = new Array();

/**
 * Creates an IconRatingSelector.
 * 
 * An IconRatingSelector serves as both the Display of a rating and the Selector for inputting a rating.
 * 
 * @param key 
 *          the unique key for the component being rendered.  This object expects several html elements to exist
 *          in the current document with IDs that include this key (plus suffixes by convention).
 * @param solidIconImageUrl
 *          the URL of the solid image to display (e.g. the star to display for the first 4 positions when 4 out of 5 are rated).  
 * @param hollowIconImageUrl
 *          the URL of the hollow image to display (e.g. the star to display for the 5th position when 4 out of 5 are rated).
 * @param selectedSolidIconImageUrl
 *          the URL of the solid image during selection (e.g. the star to display for the first 3 positions when 3 out of 5 are being selected).
 * @param selectedHollowIconImageUrl
 *          the URL of the hollow image during selection (e.g. the star to display for the last 2 positions when 3 out of 5 are being selected).
 * @param submissionUrl
 *          the AJAX Submission URL for the component.  This is the value of $AJAX_URL when the component is rendered. 
 */
function IconRatingSelector(key, solidIconImageUrl, hollowIconImageUrl, selectedSolidIconImageUrl, selectedHollowIconImageUrl, submissionUrl) {
	// call the super constructor
	this.iconRatingSelectorParent = RatingSelector;
	this.iconRatingSelectorParent(submissionUrl);
	delete this.iconRatingSelectorParent;
	
	this.key = key;
	this.divId = key + "_div";
	this.averageRatingValueDivId = key + "_AverageRatingValueTextDiv";
	this.ratingCountValueDivId = key + "_IconRatingCountValueTextDiv";
	this.solidIconImageUrl = solidIconImageUrl;
	this.hollowIconImageUrl = hollowIconImageUrl;
	this.selectedSolidIconImageUrl = selectedSolidIconImageUrl;
	this.selectedHollowIconImageUrl = selectedHollowIconImageUrl;
	this.submissionUrl = submissionUrl;
	this.enabled = true;
	if (!iconRatingSelectors[key]) {iconRatingSelectors[key] = this};

	/**
	 * Sets the Display to show the given rating.
	 * @param rating
	 *          the rating, an integer from 1 to 5.
	 * @return void
	 */	
	this.showRatingSelection = function(rating) {
		var images = this.getIconImageElements();
		for (var i = 0; i < images.length; i++) {
			var imgElement = images[i];
			if (!imgElement.originalSrc) {
				imgElement.originalSrc = imgElement.src;
			}
			if (i <= rating - 1) {
				imgElement.src = this.selectedSolidIconImageUrl;
			}
			else {
				imgElement.src = this.selectedHollowIconImageUrl;
			}
		}
	}

	/**
	 * Internal method to return the 5 <a> elements containing the 5 icon links.
	 * @return an array with 5 HTML elements.
	 */
	this.getIconAnchorElements = function() {
		var containerDiv = document.getElementById(this.divId);
		var anchors = containerDiv.getElementsByTagName('a');
		return anchors;
	}

	/**
	 * Internal method to return the 5 <img> elements for the 5 icons.
	 * @return an array with 5 HTML Elements.
	 */
	this.getIconImageElements = function() {
		var anchors = this.getIconAnchorElements();
		var images = new Array();
		for (var i = 0; i < anchors.length; i++) {
			images[i] = anchors[i].getElementsByTagName('img')[0];
		}
		return images;
	}

	/**
	 * Erases any selection currently being displayed.
	 * @return void
	 */
	this.clearRatingSelection = function() {
		var anchors = this.getIconAnchorElements();
		for (var i = 0; i < anchors.length; i++) {
			var anchorElement = anchors[i];
			var imgElement = anchorElement.getElementsByTagName('img')[0];
			if (imgElement.originalSrc) {
				imgElement.src = imgElement.originalSrc;
			}
			anchorElement.blur();
		}
	}

	/**
	 * Sets the current selection to the given rating.
	 * @param rating
	 *          the rating to select, an integer from 1 to 5.
	 * @return void
	 */
	this.setRatingSelection = function(rating) {
		this.startRatingSelectionSave(rating);
		this.showRatingSelectionIndicator(rating);
	}

	/**
	 * An internal method to issue the AJAX request to submit a rating.
	 * This method returns immediately while the request is issued
	 * asynchronously.
	 * @return void
	 */
	this.startRatingSelectionSave = function(rating) {
		this.submitRating(rating);
	}

	/**
	 * Shows an indicator that a selection has been made (e.g. an animated highlight).
	 */ 
	this.showRatingSelectionIndicator = function(rating) {
		// FIXME: this animation is a bit lame
		var rate = 300;
		setTimeout("iconRatingSelectors['" + this.key + "'].clearRatingSelection()", 0);
		setTimeout("iconRatingSelectors['" + this.key + "'].showRatingSelection(" + rating + ")", rate);
		setTimeout("iconRatingSelectors['" + this.key + "'].clearRatingSelection()", rate * 2);
		setTimeout("iconRatingSelectors['" + this.key + "'].showRatingSelection(" + rating + ")", rate * 3);
		setTimeout("iconRatingSelectors['" + this.key + "'].clearRatingSelection()", rate * 4);
	}

	/**
	 * Enables or disables input for this component.
	 * The way ratings are displayed is not affected by the enabled state.
	 */
	this.setEnabled = function(inputEnabled) {
		this.enabled = inputEnabled;
		var anchors = this.getIconAnchorElements();
		for (var i = 0; i < anchors.length; i++) {
			if (inputEnabled) {
				this.removeDisabledStyle(anchors[i]);
			}
			else {
				this.addDisabledStyle(anchors[i]);
			}
		}
	}

	/** 
	 * Internal method that appends the IconRatingInputDisabled CSS class to the current 
	 * style of the given HTML Element.
	 * @param htmlElement
	 *          the element to modify.
	 * @return void
	 */  
	this.addDisabledStyle = function(htmlElement) {
		var newClassName = this.computeClassNamesWithoutDisabledClassName(htmlElement.className);
		if (newClassName.length > 0) {
			newClassName = newClassName + " ";
		}
		newClassName = newClassName + "IconRatingInputDisabled";
		htmlElement.className = newClassName;
	}

	/** 
	 * Internal method that removes the IconRatingInputDisabled CSS class from the given HTML Element.
	 * @param htmlElement
	 *          the element to modify.
	 * @return void
	 */  
	this.removeDisabledStyle = function(htmlElement) {
		htmlElement.className = this.computeClassNamesWithoutDisabledClassName(htmlElement.className);
	}

	/**
	 * Internal method used by addDisabledStyle() and removeDisabledStyle().
	 * @param classNames
	 *          a String of space-separated CSS class names.
	 * @return a String of space-separated CSS class names, with the "IconRatingInputDisabled" token 
	 *         removed, if it was present.
	 */
	this.computeClassNamesWithoutDisabledClassName = function(classNames) {
		var oldClassNames = (classNames ? classNames : "").split(" ");
		var newClassName = "";
		for (var i = 0; i < oldClassNames.length; i++) {
			var c = oldClassNames[i];
			if (c != "IconRatingInputDisabled") {
				if (newClassName.length > 0) {
					newClassName = newClassName + " ";
				}
				newClassName = newClassName + c;
			}
		}
		return newClassName;
	}

	/**
	 * Called when the mouse is moved over one of the input icons.
	 * @param iconIndex
	 *          the index of the icon the event occurred on, from 1 to 5.
	 */
	this.onMouseOver = function(iconIndex) {
		if (this.enabled) {
			this.showRatingSelection(iconIndex);
		}
	}

	/**
	 * Called when the mouse leaves one of the input icons.
	 * @param iconIndex
	 *          the index of the icon the event occurred on, from 1 to 5.
	 */
	this.onMouseOut = function(iconIndex) {
		this.clearRatingSelection();
	}

	/**
	 * Called when the mouse is clicked on one of the input icons.
	 * @param iconIndex
	 *          the index of the icon the event occurred on, from 1 to 5.
	 */
	this.onClick = function(iconIndex) {
		if (this.enabled) {
			this.setRatingSelection(iconIndex);
		}
	}

	this.onSubmissionSuccess = function(rating, updatedXmlData) {
		this.setEnabled(false);
		this.updateFromXml(updatedXmlData);
	}

	/**
	 * Sets the currently displayed rating.
	 * @param ratingCount
	 *          the number of submitted ratings.
	 * @param ratingTotal
	 *          the sum of all submitted ratings.
	 * @param averageRating
	 *          the average rating.
	 */
	this.setRatingStatistics = function(ratingCount, ratingTotal, averageRating) {
		var averageRatingDiv = document.getElementById(this.averageRatingValueDivId);
		if (averageRatingDiv) {
			averageRatingDiv.innerHTML = "" + averageRating;
		}
		var ratingCountDiv = document.getElementById(this.ratingCountValueDivId);
		if (ratingCountDiv) {
			ratingCountDiv.innerHTML = "" + ratingCount;
		}
		var images = this.getIconImageElements();
		for (var i = 0; i < images.length; i++) {
			var imgElement = images[i];
			var newSrc;
			if (i <= averageRating - 0.88) {
				newSrc = this.solidIconImageUrl;
			}
			else {
				newSrc = this.hollowIconImageUrl;
			}
			imgElement.src = newSrc;
			imgElement.originalSrc = newSrc;
		}
		// TODO: render a partial star if appropriate.
	}

}


var feedbackRatingSelectors = new Array();

function FeedbackRatingSelector(key, loginUrl, submissionUrl) {
	this.feedbackRatingSelectorParent = RatingSelector;
	this.feedbackRatingSelectorParent(submissionUrl);
	delete this.feedbackRatingSelectorParent;

  	this.key = key;
	this.preRatingTextPattern = "Was this helpful to you?";
	this.positiveText = "yes";
	this.separatorText = "/";
	this.negativeText = "no";
	this.postRatingTextPattern = "$PERCENTAGE_POSITIVE of readers found this helpful";
	this.loginText = "login";
	this.ratingCount = 0;
	this.ratingTotal = 0;
	this.userRating = null;
	this.preRatingModeEnabled = true;
	this.displayLoginLink = true;
	feedbackRatingSelectors[this.key] = this;

	this.getLoginLink = function () {
		return document.getElementById(this.key + "_loginLink");
	}

	this.getMainTextDiv = function() {
		return document.getElementById(this.key + "_mainTextDiv");
	}

	this.getNegativeLink = function () {
		return document.getElementById(this.key + "_negativeLink");	
	}

	this.getPercentagePositiveString = function() {
		var percentagePositive = this.ratingTotal / this.ratingCount * 100.0;
		return Math.round(percentagePositive) + "%";
	}

	this.getPositiveLink = function () {
		return document.getElementById(this.key + "_positiveLink");
	}

	this.getPreRatingText = function() {
		return this.resolvePattern(this.preRatingTextPattern);
	}

	this.getPostRatingText = function() {
		return this.resolvePattern(this.postRatingTextPattern);
	}

	this.getSeparatorDiv = function () {
		return document.getElementById(this.key + "_separatorDiv");
	}

	this.getThankYouDiv = function () {
		return document.getElementById(this.key + "_thankYouDiv");
	}

	this.initialize = function() {
		this.updateDisplay();
	}

	this.onClick = function(rating) {
		// briefly show the thankyou message
		this.isSubmitting = true;
		this.updateDisplay();
		// submit the rating
		this.submitRating(rating);
	}
 
	this.onSubmissionSuccess = function(rating, updatedXmlData) {
		this.isSubmitting = false;
		this.preRatingModeEnabled = false;
		this.updateFromXml(updatedXmlData); // calls our setRatingStatistics().
		this.userRating = rating;
		this.updateDisplay();
	}

	this.resolvePattern = function(pattern) {
		return pattern.replace(/\$RATING_COUNT/, "" + this.ratingCount).replace(/\$RATING_TOTAL/, "" + this.ratingTotal).replace(/\$PERCENTAGE_POSITIVE/, this.getPercentagePositiveString());
	}
 
	this.setLoginText = function(text) {
		this.loginText = text;
	}

	this.setNegativeText = function(text) {
		this.negativeText = text;
	}

	this.setPositiveText = function(text) {
		this.positiveText = text;
	}

	this.setPostRatingTextPattern = function(pattern) {
		this.postRatingTextPattern = pattern;
	}

	this.setPreRatingTextPattern = function(pattern) {
		this.preRatingTextPattern = pattern;
	}

	/**
	 * Overidden method that sets the currently displayed rating.
	 * @param ratingCount
	 *          the number of submitted ratings.
	 * @param ratingTotal
	 *          the sum of all submitted ratings.
	 * @param averageRating
	 *          the average rating.
	 */
	this.setRatingStatistics = function(ratingCount, ratingTotal, averageRating) {
		this.ratingCount = ratingCount;
		this.ratingTotal = ratingTotal;
	}

	this.setSeparatorText = function(text) {
		this.separatorText = text;
	}
	
	this.setUserRating = function(rating) {
		this.userRating = rating;
		this.preRatingModelEnabled = !(this.userRating); 
	}

	this.updateDisplay = function() {
		this.getThankYouDiv().style.display = "none";
		if (this.preRatingModeEnabled) {
			this.getMainTextDiv().innerHTML = this.getPreRatingText();
			if (this.displayLoginLink) {
				this.getPositiveLink().style.display = "none";
				this.getSeparatorDiv().style.display = "none";
				this.getNegativeLink().style.display = "none";
				this.getLoginLink().style.display = "";
			}
			else {
				if (this.isSubmitting) {
					this.getPositiveLink().style.display = "none";
					this.getSeparatorDiv().style.display = "none";
					this.getNegativeLink().style.display = "none";
					this.getThankYouDiv().style.display = "";
				}
				else {
					this.getPositiveLink().style.display = "";
					this.getSeparatorDiv().style.display = "";
					this.getNegativeLink().style.display = "";
				}
				this.getLoginLink().style.display = "none";
			}
		}
		else {
			this.getMainTextDiv().innerHTML = this.getPostRatingText();
			this.getPositiveLink().style.display = "none";
			this.getSeparatorDiv().style.display = "none";
			this.getNegativeLink().style.display = "none";
			this.getLoginLink().style.display = "none";
		}
	}
}
