HD.util.StarRating = function(config) {
	this.observers = [];	
	
	this.config = config || {};
	if(config.captionParent != null) {
		this.captionParent = HD.get(config.captionParent);
	}
	if(config.starRatingParent != null) {
		this.starRatingParent = HD.get(config.starRatingParent);
	}
	if(config.textRatingParent != null) {
		this.textRatingParent = HD.get(config.textRatingParent);
	}
	if(config.numOfVotesParent != null) {
		this.numOfVotesParent = HD.get(config.numOfVotesParent);
	}
	if(config.numOfDecimals == null || config.numOfDecimals < 0) {
		this.config.numOfDecimals = 0;
	}
	this.config.requiresLogin = this.config.requiresLogin || false;
	this.config.showRatingHalves = this.config.showRatingHalves || false;
	this.config.showYourRatingText = this.config.showYourRatingText || false;
	
	this.queue = new HD.util.Queue();
	this.render();
	
	// Handle login monitoring.
	HD.util.StarRating.monitor.add(this);
};

HD.util.StarRating.monitor = {
	init: false,
	ratings: [],
	user: null,
	
	// Handle the login events, re-rendering on login state change.
	update: function(eventName, eventData) {
		// Re-render all live star rating objects.
		if (eventName == 'logged_In' || eventName == 'notLogged_In') {
			this.user = eventData && eventData.loggedInLevel > 1 ? eventData.user && eventData.user.userName : null;
			this.clean();
			for (var i = 0; i < this.ratings.length; i++) {
				this.ratings[i].render();
			}
		}
	},
	
	// Add a star rating object to the collection.
	add: function(starRating) {
		if (!this.init) {
			login.model.addObserver(HD.util.StarRating.monitor);
			this.init = true;
		}
		this.ratings.push(starRating);
	},
	
	// Remove star rating objects that are no longer in the live DOM.
	clean: function() {
		for (var i = this.ratings.length - 1; i >= 0; i--) {
			if (!this.isLiveElement(this.ratings[i].starRatingParent)) {
				this.ratings.splice(i, 1);
			}
		}
	},
	
	isLiveElement: function(el) {
		while (el != null && el.nodeName.toLowerCase() != 'body') {
			el = el.parentNode;
		}
		return !!el;
	}
};

HD.util.StarRating.CSS_CLASSES = {
	CAPTION : "hdCaption",
	STAR_RATING : "hdStarRating",
	TEXT_RATING : "hdTextRating",
	NUM_OF_VOTES : "hdNumOfVotes",
	AVERAGE_RATING : "hdAverageRating",
	MY_RATING : "hdMyRating"
};

HD.util.StarRating.templates = {
	oneStarLabel    : 'Poor',
	twoStarLabel    : 'Fair',
	threeStarLabel  : 'Good',
	fourStarLabel   : 'Great',
	fiveStarLabel   : 'Excellent',
	rateThisLabel   : 'Rate this: ',
	yourRatingLabel : 'Your rating: ',
	ratingLabel     : 'Rating: ',
	rateThisText    : 'Not yet rated',
	yourRatingText  : '${rating} stars',
	
	getOneStarLabel : function() {
		return this.oneStarLabel;
	},
	
	getTwoStarLabel : function() {
		return this.twoStarLabel;
	},
	
	getThreeStarLabel : function() {
		return this.threeStarLabel;
	},
	
	getFourStarLabel : function() {
		return this.fourStarLabel;
	},
	
	getFiveStarLabel : function() {
		return this.fiveStarLabel;
	},
	
	getRateThisLabel : function() {
		return this.rateThisLabel;
	},
	
	getYourRatingLabel : function() {
		return this.yourRatingLabel;
	},
	
	getRatingLabel : function() {
		return this.ratingLabel;
	},
	
	getRateThisText : function() {
		return this.rateThisText;
	},
	
	getYourRatingText : function(rating) {
		return this.yourRatingText.replace(/\$\{rating\}/, rating);
	}
};

HD.util.StarRating.prototype = {

	render : function() {
		var templates = HD.util.StarRating.templates;
		// make an array of star image objects
		this.stars = [	new HD.util.StarRatingImage("star1", templates.getOneStarLabel()), 
			new HD.util.StarRatingImage("star2", templates.getTwoStarLabel()), 
			new HD.util.StarRatingImage("star3", templates.getThreeStarLabel()), 
			new HD.util.StarRatingImage("star4", templates.getFourStarLabel()), 
			new HD.util.StarRatingImage("star5", templates.getFiveStarLabel())
		];
	
		// check to see if item has already been rated
		this.checkIfRated(this.config.itemId);
	
		var isStatic = this.config.isStatic;
		var isRated = this.config.isRated;
	
		if(isStatic) {
			HD.addClass(this.starRatingParent, HD.CSS_CLASSES.STATIC);
		} else {
			HD.removeClass(this.starRatingParent, HD.CSS_CLASSES.STATIC);
			
			if(isRated) {
				HD.addClass(this.starRatingParent, HD.CSS_CLASSES.RATED);
				HD.removeClass(this.starRatingParent, HD.CSS_CLASSES.UNRATED);
			} else {
				HD.removeClass(this.starRatingParent, HD.CSS_CLASSES.RATED);
				HD.addClass(this.starRatingParent, HD.CSS_CLASSES.UNRATED);
			}
		}
		
		this.starRatingParent.innerHTML = "";
		
		// loop over the stars array, attach events, and place it in the dom
		for(var a = 0; a < this.stars.length; a++) {
			var star = this.stars[a];
			if(!isStatic && !isRated) {
				this.attachEvents(star,a);
			}
			star.out();
			this.starRatingParent.appendChild(star.image);
			// this.starRatingParent.appendChild(document.createTextNode("&nbsp;"));
		}
		
		// if in static mode, always show average rating
		if(isStatic) {
			this.selectedIndex = this.config.averageRating - 1;
		} else if(isRated) {// if not in static mode and user has already rated, show their rating
			this.selectedIndex = this.config.myRating - 1;
		}
		else{
			this.selectedIndex = -1;	
		}
		
		// if selected index is not negative, highlight stars
		if(this.selectedIndex >= 0) {
			this.over(this.selectedIndex);
		}
		
		var caption = templates.getRateThisLabel();
		var text = this.config.averageRating;
		if(isRated && !isStatic) {
			caption = templates.getYourRatingLabel();
		}		
		if(isStatic) {
			caption = templates.getRatingLabel();
	    }
		if (this.config.showYourRatingText && !isStatic) {
			text = templates.getRateThisText();	
			if (isRated) {
				text = templates.getYourRatingText(this.config.myRating);
			}
		}
		
		this.setNumOfVotes(this.config.numOfVotes);
		this.setTextRating(text, this.config.showYourRatingText && !isStatic);
		this.setCaption(caption);
	},
	
	attachEvents : function(star,i) {
		var callback = this;
		star.image.onmouseover = function(){
			callback.overUnrated(i);}
		
		star.image.onmouseout = function(){
			callback.out(i);}
		
		star.image.onclick = function(){callback.click(i);}
	},
	overUnrated : function(index) {
		// If showing half stars
		if (this.config.showRatingHalves) {
			index = Math.min(Math.round(index*100), (this.stars.length - 1) * 100);
			for(var i = 0; i <= (this.stars.length - 1) * 100; i += 100) {
				var compareValue = index - i + 100;
				
				if (compareValue >= 26 && compareValue <= 75) {
					this.stars[Math.floor(i/100)].half();
				}
				else if (compareValue > 75) {
					this.stars[Math.floor(i/100)].overUnrated();
				}
				else {
					this.stars[Math.floor(i/100)].out();
				}
				
			}
		}
		// Otherwise
		else {
			index = Math.min(Math.round(index), this.stars.length - 1);
			for(var i = 0; i <= index; i++) {
				this.stars[i].overUnrated();
			}
	
			for(var j = index + 1; j < this.stars.length; j++) {
				this.stars[j].out();
			}
		}
	},
	
	click : function(index) {
		var i = index;
		var cb = this;
		
		this.queue.add(function() {
			if (!cb.checkIfRated(cb.config.itemId)) {
				cb.executeClick.call(cb, i);
			}
		});
		
		if(this.config.requiresLogin) {		
			login.model.isUserLoggedIn(function(userResponse) {
				if(userResponse && userResponse.loggedInLevel > 1) {
					HD.util.StarRating.monitor.user = (userResponse.user && userResponse.user.userName) || null;
					cb.queue.execute();
				} else {
					HD.util.StarRating.monitor.user = null;
					var observer = {
						update : function(eventName, eventData) {
							if (eventName == "logged_In" && eventData && eventData.loggedInLevel > 1) {
								HD.util.StarRating.monitor.user = (eventData && eventData.loggedInLevel > 1 && eventData.user && eventData.user.userName) || null;
								cb.queue.execute();
								login.model.removeObserver(this);
								HD.util.Common.getObservable().removeObserver(this);
							} else if (eventName == "overlayClose_Click") {
								login.model.removeObserver(this);
								HD.util.Common.getObservable().removeObserver(this);
							}
						}
					};
					
					HD.util.Common.getObservable().addObserver(observer);
					login.model.addObserver(observer);
					
					if(userResponse.loggedInLevel > 0){
						login.view.promptCreateScreenName();
					}else{
						login.view.promptAuthentication();
					}
				}
			});
		}
		else{
			this.queue.execute();
		}
	},
	
	executeClick : function(index) {
		this.selectedIndex = index;
		//this.out(index);
		this.markRated(index + 1);
		this.config.isRated = true;
		this.notifyObservers("starRating_Selected", index + 1);
		this.render();
	},
	
	out : function(index) {
		if(this.selectedIndex >= 0) {
			// highlight the selected star
			this.over(this.selectedIndex);
		} else {
			// set the default caption
			//this.setCaption(HD.util.StarRating.templates.getRatingLabel());
			// clear the stars hover/selected state
			for(var a = 0; a <= index; a++) {
				this.stars[a].out();
			}
		}
	},
	
	over : function(index) {
		// If showing half stars
		if (this.config.showRatingHalves) {
			index = Math.min(Math.round(index*100), (this.stars.length - 1) * 100);
			// set the caption
			//this.setCaption(this.stars[Math.floor(index/100)].caption);
			
			// set the state of stars (0-25 out, 26-75 half, 76-99 over)
			for(var i = 0; i <= (this.stars.length - 1) * 100; i += 100) {
				var compareValue = index - i + 100;
				
				if (compareValue >= 26 && compareValue <= 75) {
					this.stars[Math.floor(i/100)].half();
				}
				else if (compareValue > 75) {
					this.stars[Math.floor(i/100)].over();
				
				}
				else {
					this.stars[Math.floor(i/100)].out();
				}
				
			}
		}
		// Otherwise
		else {
			index = Math.min(Math.round(index), this.stars.length - 1);
			// set the caption
			//this.setCaption(this.stars[index].caption);
	
			
			// set the over state of stars less than or equal to the index
			for(var i = 0; i <= index; i++) {
				this.stars[i].over();
			}
	
			// set the out state for indexes greater than the selected index
			for(var j = index + 1; j < this.stars.length; j++) {
				this.stars[j].out();
			}
		}
	},
	
	setCaption : function(text) {
		if(this.captionParent != null) {
			this.captionParent.innerHTML = text;
		}
	},
	
	setTextRating : function(rating, override) {
		if(this.textRatingParent != null) {
			this.textRatingParent.innerHTML = !!override ? rating : HD.util.Common.round(rating, this.config.numOfDecimals);
		}
	},
	
	setNumOfVotes : function(numOfVotes) {
		if(this.numOfVotesParent != null) {
			this.numOfVotesParent.innerHTML = numOfVotes;
		}
	},
	
	setRating : function(rating) {
		this.selectedIndex = rating - 1;
	},
	
	getRating : function() {
		return this.selectedIndex + 1;
	},
	
	getUser : function() {
		return (this.config.requiresLogin && !this.config.isStatic && HD.util.StarRating.monitor.user) || '-';
	},
	
	markRated : function(rating) {
		var cookie = this.getRatingCookie('ratedItems');
		this.getRatingFromCookie(cookie, this.config.itemId, this.getUser()).rating = rating;
		this.setRatingCookie('ratedItems', cookie);
	},
	
	checkIfRated : function(itemId) {
		// Set defaults.
		this.config.isRated = false;
		
		// Check the cookie.
		var cookie = this.getRatingCookie('ratedItems');
		var myRating = this.getRatingFromCookie(cookie, itemId, this.getUser());
		if (myRating.rating > 0) {
			this.config.isRated = true;
			this.config.myRating = myRating.rating;
		}
		
		return this.config.isRated;
	}
};

// Abstract the cookie rating mechanism.
HD.util.StarRatingCookie = function() {};
HD.util.StarRatingCookie.prototype = {
	// Cookie format ("-" is an anonymous user):
	//   MEDIA_ID=USERNAME:RATING,USERNAME2:RATING,-:RATING;MEDIA_ID2=...;MEDIA_ID3=...
	
	getRatingCookie : function(cookieName) {
		var util = HD.util.Common;
		cookieName = cookieName || 'ratedItems';
		var cookie = (decodeURIComponent(util.getCookie(cookieName)) || '').split('|');
		
		for (var i = 0; i < cookie.length; i++) {
			var item = cookie[i].split('=');
			item = {
				id: item[0] || '',
				ratings: item[1] || ''
			};
			item.ratings = item.ratings.split(',');
			for (var j = 0; j < item.ratings.length; j++) {
				var rating = item.ratings[j].split(':');
				rating = {
					username: rating[0] || '',
					rating: rating[1] || 0
				}
				item.ratings[j] = rating;
			}	
			cookie[i] = item;
		}
		
		return cookie;
	},
	
	setRatingCookie : function(cookieName, cookie) {
		HD.util.Common.setCookie(cookieName || 'ratedItems', this.serializeRatingCookie(cookie));	
	},
	
	serializeRatingCookie : function(cookie) {
		for (var i = 0; i < cookie.length; i++) {
			for (var j = 0; j < cookie[i].ratings.length; j++) {
				cookie[i].ratings[j] = cookie[i].ratings[j].username + ':' + cookie[i].ratings[j].rating;
			}
			cookie[i] = cookie[i].id + '=' + cookie[i].ratings.join(',');
		}
		return encodeURIComponent(cookie.join('|'));
	},
	
	// Find a specific rating entry in the cookie (or create it if it doesn't exist).
	getRatingFromCookie : function(cookie, itemId, username) {
		username = username || '-';
		for (var i = 0; i < cookie.length; i++) {
			if (cookie[i].id == itemId) {
				for (var j = 0; j < cookie[i].ratings.length; j++) {
					if (cookie[i].ratings[j].username == username) {
						return cookie[i].ratings[j];
					}
				}
				cookie[i].ratings.push({ username: username, rating: 0 });
				return cookie[i].ratings[cookie[i].ratings.length-1];
			}
		}
		cookie.push({ id: itemId, ratings: [{ username: username, rating: 0 }] });
		return cookie[cookie.length-1].ratings[0];
	}
};

HD.extend(HD.util.StarRating, [HD.util.Observable, HD.util.StarRatingCookie]);

HD.util.StarRatingImage = function(id, caption, state) {
	this.id = id;
	this.caption = caption;
	this.image = document.createElement("span");
	this.image.innerHTML = "&nbsp;";
	this.image.className = HD.CSS_CLASSES.STAR_OFF;
};

HD.util.StarRatingImage.prototype = {
	over : function() {
		this.image.className = HD.CSS_CLASSES.STAR_ON;
	},
	
	out : function() {
		this.image.className = HD.CSS_CLASSES.STAR_OFF;
	},
	
	half : function() {
		this.image.className = HD.CSS_CLASSES.STAR_HALF;
	},
	overUnrated : function(){
		this.image.className = "hdStarOver";

	}
};

HD.register('hd_star_rating', 'HD.util.StarRating', {version: "1.0", build: "1"});