/*
 * Copyright (c) 2007 JS-Kit <support@js-kit.com>. All rights reserved.
 * You may copy and modify this script as long as the above copyright notice,
 * this condition and the following disclaimer is left intact.
 * This software is provided by the author "AS IS" and no warranties are
 * implied, including fitness for a particular purpose. In no event shall
 * the author be liable for any damages arising in any way out of the use
 * of this software, even if advised of the possibility of such damage.
 * $Date: 2007-12-20 04:05:59 -0800 (Thu, 20 Dec 2007) $
 * $Id: ratings.js 2838 2007-12-20 12:05:59Z jskit $
 */

if ( ! window.$JRA) {
  /* Global JS Ratings Array */
  var $JRA = [];
  var $JRH = {};

  var $JRLT = {
    yourRatingTitleCase: 'Your Rating',
    yourRating: 'Your rating',
    vote: 'vote',
    votes: 'votes',
    unrated: 'Unrated',
    rateThis: 'Rate this',
    avgRating: 'avg rating',
    poweredBy: 'Powered by',
    youHaveNotRatedYet: 'You have not rated yet',
    addACommentToYourRating: 'Add a comment to your rating',
    noVotesReceivedYet: 'No votes received yet',
    beTheFirstToRate: 'Be the first to rate!',
    ratingsDisabled: 'Voting Closed',
    thankYou: 'Thank you!'
  };

  var $JRL = function(t) {
    return $JRLT[t] || t;
  };
}

if(!window.JSKitLib) JSKitLib = {};

JSKitLib.vars = {}; // Global JSKitLib variables

/* Element Class Handlers */

JSKitLib.getElementsByClass = function(node, searchClass, tag) {
    var classElements = [];
    node = node || document;
    tag = tag || '*';
    var tagElements = node.getElementsByTagName(tag);
    var regex = new RegExp("(^|\\s)" + searchClass + "(\\s|$)");
    for (var i=0, j=0; i < tagElements.length; i++) {
      if (regex.test(tagElements[i].className)) {
        classElements[j] = tagElements[i];
        j++;
      }
    }
    return classElements;
};

JSKitLib.isIE = function() {
	if (document.body.filters && navigator.appVersion.match(/MSIE/))
		return true;
}

JSKitLib.isPreIE7 = function() {
	if (document.body.filters && parseInt(navigator.appVersion.split("MSIE")[1]) < 7)
		return true;
}

JSKitLib.addPNG = function(node, imageURL) {
	if (JSKitLib.isPreIE7()) {
		node.runtimeStyle.filter
			= "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='"
			+ imageURL + "', sizingMethod='crop')"
	} else {
		node.style.backgroundImage = 'url(' + imageURL + ')';
		node.style.backgroundRepeat = 'no-repeat';        
	}
	return node;
}

JSKitLib.preloadImg = function(imgURL) { 

  if (!JSKitLib.preloadImgList) JSKitLib.preloadImgList = {};

  if (!JSKitLib.preloadImgList[imgURL]) {
    (new Image()).src = imgURL; 
    JSKitLib.preloadImgList[imgURL] = true;
  }
};

JSKitLib.filter = function(f, arr) {
	var newArr = [];
	if(arr)
		for(var i = 0; i < arr.length; i++)
			if(f(arr[i], i, arr))
				newArr.push(arr[i]);
	return newArr;
}

/* Element Handlers */

JSKitLib.visible = function(element) {
  return element.style.display != 'none';
}

JSKitLib.show = function(element) {
  element.style.display = '';
}

JSKitLib.hide = function(element) {
  element.style.display = 'none';
}

JSKitLib.toggle = function(element) {
  (element.style.display == 'none') ? JSKitLib.show(element) :  JSKitLib.hide(element);
}

JSKitLib.getStyle = function(element) {
	if (typeof element.style.cssText != "undefined") {
		return element.style.cssText;
	} else {
		return element.getAttribute("style");
	}
}

JSKitLib.setStyle = function(element, style) {
	if (typeof element.style.cssText != "undefined") {
		element.style.cssText = style;
	} else {
		element.setAttribute("style", style);
	}
}

JSKitLib.addStyle = function(element, style) {
	var oldStyle = JSKitLib.getStyle(element);
	JSKitLib.setStyle(element, oldStyle + ' ' + style);
}

JSKitLib.findPos = function(obj) {
    var origObj = obj;
    var curleft = curtop = curright = curbottom = 0;
    if (obj.offsetParent) {
        curleft = obj.offsetLeft;
        curtop = obj.offsetTop;
        while (obj = obj.offsetParent) {
            curleft += obj.offsetLeft;
            curtop += obj.offsetTop;
        }
    }
    curright = curleft + origObj.offsetWidth;
    curbottom = curtop + origObj.offsetHeight;
    return [curleft,curtop,curright,curbottom];
}

/* Math */

// rounds number to x decimal places
JSKitLib.round = function(number, x) {
    x = (!x ? 2 : x);
    return Math.round(number*Math.pow(10,x))/Math.pow(10,x);
}

JSKitLib.zeroPad = function(number, x) {
  number = JSKitLib.round(number, x);
  var text = new String(number);
  var matches = text.match(/(\d*)(\.(\d*))?/) || [];
  var decimal = matches[3] || '';
  if (!decimal) {
    text += '.';
  }
  var count = x - decimal.length;
  for (var i=0; i<count; i++) {
    text += '0';
  }
  return text;
}

// Used when we don't want events to bubble up
JSKitLib.stopEventPropagation = function(e) {
  if (!e) var e = window.event;
  e.cancelBubble = true;
  if (e.stopPropagation) e.stopPropagation();
}

JSKitLib.addLoadEvent = function(newLoadEvent) {
  var origLoadEvent = window.onload;
  if (typeof origLoadEvent == "function") {
    window.onload = function() { 
      origLoadEvent();
      newLoadEvent();
    }
  } else {
    window.onload = newLoadEvent;
  }
}

// Detect wether window.onload has fired
if ( ! JSKitLib.vars.windowOnLoadFired) JSKitLib.vars.windowOnLoadFired = false;
JSKitLib.addLoadEvent(function() { JSKitLib.vars.windowOnLoadFired = true; });

JSKitLib.deferCallIfIE = function(func) {
  if (JSKitLib.isIE() && ! JSKitLib.vars.windowOnLoadFired) {
    JSKitLib.addLoadEvent(func);
  } else {
    func();
  }
}

var JSKitGlobal = function() {

  this._appAvailable = {};
  this._appObjects = {};  // Specific objects of an application type 
  this._appObjectActions = {}; // app.object.actions

  this._isAppAvailable = function(app) {
    return (this._appAvailable[app]) ? true : false;
  }
    
  this.isRatingsAppAvailable = function() {
    return this._isAppAvailable('ratings');
  }

  this.isCommentsAppAvailable = function() {
    return this._isAppAvailable('comments');
  }

  this._setAppAvailable = function(app) {
    this._appAvailable[app] = true;
    /* index this app */
    this.indexAppObjects(app);
    /* execute any queued actions */
    this.executeAppObjectActions(app);
  }

  this.setRatingsAppAvailable = function() {
    this._setAppAvailable('ratings');
  }
  this.setCommentsAppAvailable = function() {
    this._setAppAvailable('comments');
  }

  this.indexAppObjects = function(app) {

    if (app == 'ratings') {
      var appArray = $JRA;
    } else if (app == 'comments') {
      var appArray = $JCA;
    } else {
      alert('Attempt to index invalid app type');
      return;
    }

    for (var i=0; i < appArray.length; i++) {
      // Check that it's not standalone
      if (appArray[i].isStandalone()) {
        continue;
      }
      var uniq = appArray[i].uniq;
      if ( ! this._appObjects[uniq] ) {
        this._appObjects[uniq] = {};
      }
      if ( ! this._appObjects[uniq][app]) {
        this._appObjects[uniq][app] = [];
      }
      this._appObjects[uniq][app].push(appArray[i]);
    }
  }

  this.executeAppObjectActions = function(app) {
    if (this._appObjectActions[app]) {
      for (var i=0; i < this._appObjectActions[app].length; i++) {
        var uniq = this._appObjectActions[app][i].uniq;
        if (this._getAppObject(app, uniq)) {
          this._appObjectActions[app][i].action();
        }
      }
    }
  }

  this._getAppObject = function(app, uniq) {
    if (this._appObjects[uniq] && this._appObjects[uniq][app]) {
      return this._appObjects[uniq][app][0];  // Return only the first
    }
    return null;
  }

  this.getCommentsAppObject = function(uniq) {
    return this._getAppObject('comments', uniq);
  }

  /* Returns a Ratings Object */
  this.getRatingsAppObject = function(uniq) {
    return this._getAppObject('ratings', uniq);
  }

  this.copyRatingsAppObject = function(uniq, node) {
    if ( ! this.isRatingsAppAvailable()) {
      return;
    }

    var oldObj = this.getRatingsAppObject(uniq);
    var newObj = oldObj.clone(node, { 'view':'user', 'commentprompt':'no', 'menu':'no'  } );
    return newObj;
  }

  this._tryAppObjectAction = function(app, uniq, action) {
    if (this._isAppAvailable(app)) {
      if (this._getAppObject(app, uniq)) {
        action();
      }
    } else {

      if ( ! this._appObjectActions[app]) {
        this._appObjectActions[app] = [];
      }
      this._appObjectActions[app].push( { 'uniq' : uniq, 'action' : action } );
    }
  }

  this.tryRatingsAppObjectAction = function(uniq, action) {
    this._tryAppObjectAction('ratings', uniq, action);
  }

  this.tryCommentsAppObjectAction = function(uniq, action) {
    this._tryAppObjectAction('comments', uniq, action);
  }

}

/* Singleton-like handler */
JSKitGlobal.getInstance = function() {
  if ( ! window.JSKitGlobalInstance) {
    JSKitGlobalInstance = new JSKitGlobal();
  }
  return JSKitGlobalInstance;
}

/* Class Functions */

// Initialize instances of JSRC objects
JSRC.init = function() {

    /* Iterate and find all rating divs */
	// js-kit-rating and js-kit-ratings qualify
	var els = JSKitLib.filter(function(el) {
		return (el.className.match(/^js-kit-rating/)); }, document.getElementsByTagName('div'));

	if (!els.length) return;

	var multiQ = '';
	var multiI = 0;
	var wl = window.location;

	var reqMulti = function(atext) {
		if(!atext.length) return;
		var sc = document.createElement("script");
		var uri = JSRC.URI;
		if(wl.host.match(/^(www\.)?icanhascheezburger.com/)) {
			uri = "http://b.js-kit.com/rating";
		}
		sc.src = uri + "-data.js?ref="
			+ encodeURIComponent(wl.protocol + "//" + wl.host + wl.pathname)
			+ atext;

		$JRA[0].target.appendChild(sc);
	}

	for (var i=0; i < els.length; i++) {
		var r = new JSRC(els[i]);
		var mr = r.myref('path');

		multiQ += "&p["+multiI+"]=" + encodeURIComponent(mr)
			+ ((mr == r.uniq) ? ''
			: ("&u["+multiI+"]=" + encodeURIComponent(r.uniq)))
			+ (r.config.property ? "&pr["+multiI+"]=" + encodeURIComponent(r.config.property) : '')
			+ (r.config.category ? "&cg["+multiI+"]=" + encodeURIComponent(r.config.category) : '')
			+ "&jx[" + multiI + "]=" + r.jraIndex;

		if(multiQ.length > 700) {
			reqMulti(multiQ);
			multiQ = '';
			multiI = 0;
		} else {
			multiI ++;
		}
	}

	reqMulti(multiQ);
}


/* JS Rating Class */
function JSRC(target) {

	this.jraIndex = $JRA.length;
	$JRA.push(this);
	var self = this;

	var options = arguments[1] || {};

	this.cr = function(tag) { return document.createElement(tag) };

	this.pathOverride = '';
	this.raterInc = 2;  // Increment ratio of rateable v. displayable
	this.scale    = 10; // Points on rating scale

	this.onRate = []; // Callbacks for post rating processing

	this.isStandalone = function() {
		return (this.config.standalone == 'yes') ? true : false;
	}

	this.starWidth       = 16;
	this.starHeight      = 15;
	this.miniStarWidth   = 9;
	this.miniStarHeight  = 9;
	this.totalWidth; //The total width of the visible widget

	var wl = window.location;

	this.target = target;

	/* Configuration */

	// Handle block level config
	var bConfig = {}; // block level config
	var bcels = target.getElementsByTagName('span') || [];
	if (bcels.length) {
		for (var i=0; i < bcels.length; i++) {
			var bcMatch = (bcels[i].className.match(/^js-kit-config-(.*)$/)); 
			if (bcMatch.length) {
				var bcKey = bcMatch[1].toLowerCase();
				var bcVal = bcels[i].innerHTML;
				bConfig[bcKey] = bcVal;
			}
		}
	}

	target.innerHTML = "";
	JSKitLib.show(target);

	this.config = (function() {
		var cf = {};
		for(var i = 0; i < arguments.length; i++) {
			var arg = arguments[i];
			if(typeof(arg) == 'string') arg = [arg];
			var name = arg[0];
			var value = options[name] || target.getAttribute(name) || bConfig[name];
			if(arg.length > 1) {
				if(typeof(arg[1]) == 'number') {
					if(value) {
						var n = parseInt(value);
						if(isNaN(n) || n < 0) {
							if(value == "no")
								value = 0;
							else
								value = arg[1];
						} else {
							value = n;
						}
					} else {
						value = arg[1];
					}
				} else if(typeof(arg[1]) == 'object') {
					for(var j=arg[1].length; j; j--)
						if(arg[1][j-1] == value)
							break;
					if(!j) value = arg[1][j];
				} else {
					if(!value) value = arg[1];
				}
			}
			cf[name] = value;
		}
		return cf;
	})(
		'path',
		'uniq',
		['standalone', 'no'],
		['view', 'combo'],
		['commentprompt', true],
		'imageurl',
		'imagesize',
		'title',
		'notop',
		'permalink',
		['freeze', 'no'],
		['menu', 'yes'],
		['subtext', 'yes'],
		'property',
		'category',
		'starcolor',
		'usercolor'
	);

	if (this.config.starcolor) this.config.starcolor = this.config.starcolor.toLowerCase();
	if (this.config.usercolor) this.config.usercolor = this.config.usercolor.toLowerCase();

	// Special menu handling for particular sites
	if (wl.host.match(/icanhascheezburger.com/)) { this.config.menu = 'no'; }

	if(this.config.imageurl && this.config.imagesize) {
		var dim = this.config.imagesize.match(/(\d+)([^\d]+(\d+))?/);
		if(dim) {
			this.starWidth = dim[1];
			this.starHeight = dim[3] || this.starWidth;
		}
	}

	this.ratingBarWidth  = this.scale / this.raterInc * this.starWidth; 
	this.ratingBarHeight = this.starHeight;

	if (this.config.path) {
		var path = String(this.config.path);
		var ar = path.match(/^https?:\/\/[^\/]+(.*)/);
		if(ar) this.pathOverride = ar[1];
		else this.pathOverride = path.replace(/^([^\/]+)/, wl.pathname + "/$1");
	}

	this.path = this.pathOverride || wl.pathname;
	this.uniq = this.config.uniq || this.path;
	if ( ! $JRH[this.uniq]) {
		$JRH[this.uniq] = [];
	}
	$JRH[this.uniq].push(this);

	this.defineIcons();
	JSKitLib.preloadImg(JSRC.INFO_IMG);

	if (options.newRating) {
		//TODO
		this.newRating({ Sum: options.newRating.objSum, Num: options.newRating.objNum, Votes: options.newRating.objVotes }, { Sum: options.newRating.userRating});
	}

	this.myref = function() {
		if(arguments.length) return self.path;
		return encodeURIComponent(wl.protocol + "//" + wl.host
			+ (self.pathOverride.length ? "/" : wl.pathname));
	}

	this.server = function(ext, data) {
		var sc = self.cr("script");
		sc.setAttribute("charset", "utf-8");
		sc.src = JSRC.URI + ext + self.pathOverride
			+ "?ref=" + self.myref() + "&" + data;
		self.target.appendChild(sc);
		return false;
	}

	if(options.autorequest) {
		var mr = this.myref('path');
		this.server('-data.js', 'p[0]=' + encodeURIComponent(mr)
			+ ((mr == this.uniq) ? '' : ('&u[0]=' + encodeURIComponent(this.uniq)))
			+ (this.config.property ? '&pr[0]=' + encodeURIComponent(this.config.property) : '')
			+ (this.config.category ? '&cg[0]=' + encodeURIComponent(this.config.category) : '')
			+ '&jx[0]=' + this.jraIndex);
	}

}

/* Constants */
JSRC.DOMAIN = (window.location.protocol.substr(0, 4) != 'http' ? 'http:' : '')
              + '//js-kit.com';
JSRC.URI = JSRC.DOMAIN + '/rating';
JSRC.BASE_STAR_URI = JSRC.DOMAIN + '/images/stars/';
JSRC.INFO_IMG = JSRC.DOMAIN + '/images/i-wg.png';
JSRC.INFO_IMG_WIDTH = 15;
JSRC.INFO_IMG_OFFSET = 7;


JSRC.prototype.defineIcons = function() {

  var self = this;
  this.fullStar  = [];
  this.halfStar  = [];
  this.emptyStar = [];
  this.miniFullStar  = [];
  this.miniEmptyStar = [];

  var genstar = function(confColor, defColor, type) {
	var acceptedColors = { blue:1, yellow:1, gold:1, golden:1,
			green:1, violet:1, emerald:1, indigo:1, red:1, ruby:1 };
	var color = (confColor && acceptedColors[confColor])
			? confColor : defColor;
	var starURI = JSRC.BASE_STAR_URI;
	if(self.config.imageurl) {
		starURI = self.config.imageurl + '/';
		color = type;
	}
	var size = '';

	self.fullStar[type]  = starURI + color + size + '.png';
	self.halfStar[type]  = starURI + color + size + '-half.png';
	self.emptyStar[type] = starURI + size + 'gray.png';

	if ( ! self.config.imageurl) {
		self.miniFullStar[type]  = starURI + color + '-tiny.png';
		self.miniEmptyStar[type] = starURI + 'gray-tiny.png';
		self.miniStarWidth = 9;
		self.miniStarHeight = 9;
	} else {
		self.miniFullStar[type]  = self.fullStar[type];
		self.miniEmptyStar[type] = self.emptyStar[type];
		self.miniStarWidth = self.starWidth;
		self.miniStarHeight = self.starHeight;
	}

	JSKitLib.preloadImg(self.fullStar[type]);
	JSKitLib.preloadImg(self.halfStar[type]);
	JSKitLib.preloadImg(self.emptyStar[type]);
	JSKitLib.preloadImg(self.miniFullStar[type]);
	JSKitLib.preloadImg(self.miniEmptyStar[type]);

  }

  genstar(this.config.starcolor, 'ruby', 'star');
  genstar(this.config.usercolor, 'gold', 'user');
}

/* Create our global JSKit object */
$JSKitGlobal = JSKitGlobal.getInstance();

/* Init a single call to init */
if ( ! $JRA.length) {
  JSRC.init();
  $JSKitGlobal.setRatingsAppAvailable();
}


/* CSS Stylings */
document.write('<style type="text/css">'
  + '.js-rating-labelText { padding-top: 2px; font-size: 11px; text-align: center; cursor: default; -moz-user-select: none; }'
  + '.js-rating-splitObjectRating { margin-right: 10px; }'
  + '.js-rating-afterRating { width: 100px; font-size: 12px; text-align: center; padding: .3em;}'
  + '.js-rating-windowWrapper { border: 1px solid #ccc; }'
  + '.js-rating-window {  background: #ffc; border: none; filter: alpha(opacity=90); opacity: 0.9; padding: .3em; }'
  + '.js-rating-menuArrow { width:15px; height:15px; margin-left:' + JSRC.INFO_IMG_OFFSET + 'px; cursor:pointer; float: left; }'
  + '.js-rating-infoBox { color: black; padding: .3em; text-align:left; -moz-user-select: none; }'
  + '.js-rating-infoBoxStats { padding-bottom: 1em; line-height: 12pt;  }'
  + '.js-rating-infoBoxText { font-size: 9pt; }'
  + '.js-rating-infoBoxTextEm { font-size: 9pt; color: #a00; }'
  + '</style>'
)

JSRC.prototype.html = function(text) {
	var div = this.cr("div");
	div.innerHTML = text;
	var ch = div.firstChild;
	div = null;
	return ch;
}

/* Will add a callback for post rating processing */
JSRC.prototype.addOnRate = function(action) {
  this.onRate.push(action);
}

JSRC.prototype.processOnRate = function() {
  for (var i=0; i < this.onRate.length; i++) {
    this.onRate[i]();
  }
}

JSRC.prototype.table = function(content) {
  var self = this;
  var a = function(n, w) {var o=self.cr(n);o.appendChild(w);return o;}
  var t = a('table', a('tbody', a('tr', a('td', content))));
  var z = function(a) {t.setAttribute(a, '0')}
  z('cellSpacing');
  z('cellPadding');
  z('border');
  return t;
}

JSRC.prototype.display = function() {

  var self = this;

  // wrapper for our floated elements
  var wrapper = this.cr('div');
  wrapper.style.margin = '3px';
  wrapper.style.position = 'relative';
  wrapper.onselectstart = function() { return false; }

  var actionable = (this.config.freeze == "yes") ? false : true;

  if (this.config.view.match(/(combo|user)/) && this.config.freeze == "yes") {
      this.userRatingBar = this.initRating(this.objEffRating, 'star', false);
  } else {
      this.userRatingBar = this.initRating(this.userRating, 'user', actionable);
  }

  this.userRatingDiv = this.cr('div');
  this.userRatingDiv.appendChild(this.userRatingBar);

if (this.config.subtext != 'no') {
  this.textTotal = this.cr('div');
  this.textTotal.className = 'js-rating-labelText';
  this.refreshTextTotal();
}

  if (this.config.view.match(/split/)) {
    // split view : community and user ratings
    this.defaultView = 'user';

    var starRatingBar = this.initRating(this.objEffRating, 'star', false);
    var starRatingDiv = this.cr('div');
        starRatingDiv.className = 'js-rating-splitObjectRating';
        starRatingDiv.style.cssFloat = 'left';
        starRatingDiv.style.styleFloat = 'left';
        starRatingDiv.style.width = this.ratingBarWidth + 'px';
        starRatingDiv.appendChild(starRatingBar);
        starRatingDiv.appendChild(this.textTotal);

    wrapper.appendChild(starRatingDiv);

    this.userRatingDiv.style.cssFloat = 'left';
    this.userRatingDiv.style.styleFloat = 'left';
    this.userRatingDiv.style.width = this.ratingBarWidth + 'px';

if (this.config.subtext != 'no') {
    this.textRating = this.cr('div');
    this.textRating.className = 'js-rating-labelText';
    this.refreshTextRating();
    this.userRatingDiv.appendChild(this.textRating);

    this.activeText = this.textRating;
}
    this.totalWidth = this.ratingBarWidth + this.ratingBarWidth + 10;

    wrapper.appendChild(this.userRatingDiv);

  } else if (this.config.view.match(/user/)) {
    // single star set, only shows current user's rating
    this.defaultView = 'user';

    this.userRatingDiv.style.cssFloat = 'left';
    this.userRatingDiv.style.styleFloat = 'left';

if (this.config.subtext != 'no') {
    this.textRating = this.cr('div');
    this.textRating.className = 'js-rating-labelText';
    this.refreshTextRating();
    this.userRatingDiv.appendChild(this.textRating);

    this.activeText = this.textRating;
}

    this.totalWidth = this.ratingBarWidth;
    wrapper.appendChild(this.userRatingDiv);

  } else {
    // single star set, defaults to community rating
    this.defaultView = 'star';

    this.userRatingDiv.style.cssFloat = 'left';
    this.userRatingDiv.style.styleFloat = 'left'; 

if (this.config.subtext != 'no') {
    this.userRatingDiv.appendChild(this.textTotal);
    this.activeText = this.textTotal;
}

    this.totalWidth = this.ratingBarWidth;

    wrapper.appendChild(this.userRatingDiv);
  }

  // Set our total width
  wrapper.style.width = this.totalWidth + 'px';

  /* Rating Menu */
  if (this.config.menu != 'no') {
    wrapper.style.width = (this.totalWidth + 10 + JSRC.INFO_IMG_WIDTH) + 'px';
    var menuArrow = this.createMenuArrow();
    this.prepMenu(); // 'i' and infobox
    wrapper.appendChild(menuArrow);

  }

  // Set the target width
  var targetMinWidth = parseInt(wrapper.style.width) + 6; // 3px margin
  var targetWidth = this.target.style.width || targetMinWidth;
  if (parseInt(targetWidth) <= targetMinWidth) {
    this.target.style.width = targetMinWidth + 'px';
  }

  if (( ! this.isStandalone()) && this.config.commentprompt != 'no') {

    var addCommentPrompt = function() {

      var afterRatingA = document.createElement('a');
      afterRatingA.appendChild(document.createTextNode($JRL('addACommentToYourRating')));
      afterRatingA.onclick = function() { 
        self.getCommentsAppObject().ShowCommentDialog(null);
        return false;
      };
      afterRatingA.href = 'javascript:void(0);';
  
      var afterRatingDiv = document.createElement('div');
      afterRatingDiv.appendChild(afterRatingA);
      afterRatingDiv.className = 'js-rating-afterRating';

      var afterRating = self.createWindow(afterRatingDiv);
      afterRating.style.position ='absolute';
      afterRating.style.left = (self.totalWidth + 5) + 'px';
      afterRating.style.top = '-4px';
      afterRating.style.zIndex = '110'; // above menuArrow
      JSKitLib.hide(afterRating);

      self.addOnRate(function() { 
        JSKitLib.show(afterRating);
        setTimeout(function() { JSKitLib.hide(afterRating); }, 5000);
      });

  
      wrapper.appendChild(afterRating);
    }
    $JSKitGlobal.tryCommentsAppObjectAction(this.uniq, addCommentPrompt); 
  }

  this.target.appendChild(this.table(wrapper)); // stars

  if ( ! this.config.view.match(/split/)) {
    this.refreshRating();  
  }

}

// generic jskit body tag fror absolutely position elements
JSRC.prototype.createBodyElement = function() {
  if ( ! document.getElementById('js-kit-body-element')) {
    var be = this.cr('div');
    be.id = "js-kit-body-element";
    document.body.appendChild(be);
  }
}

// Adds the 'i' button and infobox
JSRC.prototype.prepMenu = function() {
  var self = this;
  var prepMenu = function() {
    self.createBodyElement();

    var infoBox = self.cr('div');
    self.infoBox = infoBox;

    document.getElementById('js-kit-body-element').appendChild(infoBox);

    var infobox1Show = infobox2Show = false;

    var infoBoxMouseover = function() {
      clearTimeout(self.ratingMenuTimer);
    }

    self.target.onmouseover = function() { 
      infobox1Show = true; 
      infoBoxMouseover(); 
      JSKitLib.show(self.menuArrow);
    }
    self.infoBox.onmouseover = function() { infobox2Show = true; infoBoxMouseover(); }

    var infoBoxMouseout = function() {

      if (infobox1Show || infobox2Show) 
        return;

      self.ratingMenuTimer = setTimeout(function() {
        self.ratingMenuTimer = null;
          self.hideInfoBox();
          JSKitLib.hide(self.menuArrow);
      }, 1500);
    }

    self.target.onmouseout = function() { infobox1Show = false;  infoBoxMouseout(); }
    self.infoBox.onmouseout = function() { infobox2Show = false; infoBoxMouseout(); } 

    self.infoBox.onclick = function(e) { 
      self.toggleInfoBox(); 
      JSKitLib.stopEventPropagation(e);
    }
  };

  // document.body.append functionality can only happen after window.onload in IE
  JSKitLib.deferCallIfIE(prepMenu);

}

JSRC.prototype.createMenuArrow = function() {
  this.menuArrow = document.createElement('div');
  this.menuArrow.className = 'js-rating-menuArrow';
  JSKitLib.hide(this.menuArrow);
  JSKitLib.addPNG(this.menuArrow, JSRC.INFO_IMG);

  this.infoBoxImg = this.menuArrow;
  var self = this;
  this.menuArrow.onclick = function() { 
    self.toggleInfoBox();
  }

  return this.menuArrow;;
}

JSRC.prototype.showInfoBox = function() {
  var infoBox = this.createInfoBox();
  this.infoBox.appendChild(infoBox);
}

JSRC.prototype.hideInfoBox = function() {
  if (this.infoBox) {
    while (this.infoBox.hasChildNodes()) {
      this.infoBox.removeChild(this.infoBox.firstChild);
    }
  }
}

JSRC.prototype.toggleInfoBox = function() {
  if (this.infoBox && this.infoBox.hasChildNodes()) {
    this.hideInfoBox();
  } else {
    this.showInfoBox();
  }
}

JSRC.prototype.refreshInfoBox = function() {
  if (this.infoBox && this.infoBox.hasChildNodes()) {
    this.hideInfoBox();
    this.showInfoBox();
  }
}

JSRC.prototype.createWindow = function(content, opts) {

  if (typeof opts != 'object') opts = {};

  var wrapper = document.createElement('div');
  wrapper.className = 'js-rating-windowWrapper';

  var box = document.createElement('div');
  box.className = 'js-rating-window';
  
  if (typeof content == 'string') {
    box.appendChild(this.html(content));

  } else {
    box.appendChild(content);
  }

  wrapper.appendChild(box);


  return wrapper;

}

JSRC.prototype.createInfoBox = function() {

  var INFOBOX_WINDOW_WIDTH = 170;

  var pos = JSKitLib.findPos(this.target);
  var html = '<div class="js-rating-infoBox" onselectstart="return false">';

  html += '<div class="js-rating-infoBoxStats js-rating-infoBoxText">';

  if (this.config.freeze == 'yes') {
    html += '<span class="js-rating-infoBoxTextEm">' + $JRL('ratingsDisabled') + '</span><br>';
  }

  if (this.objNum) {
    html += this.getTextForTotalVotes(this.objNum);
    html += ' <span style="white-space: nowrap">(' + JSKitLib.zeroPad(this.objAvgStarRating, 2) + '&nbsp;' + $JRL('avgRating') + ')</span><br>';
  } else {
    if (this.config.freeze != 'yes') {
      html += $JRL('noVotesReceivedYet') + '<br>';
    }
  }

  if (this.userRating) {
    html += $JRL('yourRating') + ': ';
    html += (this.userRating / this.raterInc);
  } else {
    if (this.config.freeze != 'yes') {
      if (this.objNum) {
        html += $JRL('youHaveNotRatedYet');
      } else {
        html += $JRL('beTheFirstToRate');
      }
    }
  }
  html += '</div>';

  html += '<span class="js-rating-infoBoxText">' + $JRL('poweredBy') + ' <a href="http://js-kit.com/ratings/?wow" class="js-rating-infoBoxText" target="_blank">JS-Kit</a></span>';
  html += '</div>';
  var self = this;
  var closeInfoBox = function() {
    self.hideInfoBox();
  }
  var node = this.createWindow(html);
  node.style.position = 'absolute';

  node.style.top = pos[3] + 'px';

  // If rating widget is too close to left side, show on the right side
  if (pos[0] > INFOBOX_WINDOW_WIDTH || this.totalWidth >= INFOBOX_WINDOW_WIDTH) {
    node.style.left = (pos[2] - INFOBOX_WINDOW_WIDTH - 6) + 'px'; // 3px margin
  } else {
    node.style.left = pos[0] + 'px'; // 3px margin
  }
  
  node.style.width = INFOBOX_WINDOW_WIDTH + 'px';

  node.style.zIndex = '1000';
  return node;
}

/* Process all rating objects with the same ID */
JSRC.prototype.processSiblings = function(handler) {
  for (var i=0; i < $JRH[this.uniq].length; i++) {
    // property must match as well
    if (this.config.property || $JRH[this.uniq][i].config.property) {
      if ($JRH[this.uniq][i].config.property == this.config.property) {
        handler($JRH[this.uniq][i]);
      }
	} else if (this.config.category || $JRH[this.uniq][i].config.category) {
      if ($JRH[this.uniq][i].config.category == this.config.category) {
        handler($JRH[this.uniq][i]);
      }
    } else {
        handler($JRH[this.uniq][i]);
    }
  }
}

JSRC.prototype.rate = function(givenRating) {
  var oldRating = this.userRating;
  this.setUserRating(givenRating);
  var objSum = this.objSum;
  var objNum = this.objNum;
  var objVotes = this.objVotes;
  if(oldRating) {
    objSum -= oldRating;
    objNum --;
  }
  this.setTmpText($JRL('thankYou'));

  // Update all ratings for this ID
  this.processSiblings(function(sibling) {
    //TODO: determine if current user increments objVotes count
    sibling.newRating({ Sum: objSum + givenRating, Num: objNum + 1, Votes: objVotes }, { Sum: givenRating });
  });

  // Refresh the InfoBox 
  if (this.config.menu != 'no') {
    this.refreshInfoBox();
  }

  // Handle any callbacks
  this.processOnRate();

  // TODO: parametric rating
  if (window.$J$PRA && typeof $J$PRA == 'object') {
    for (var i=0; i < $J$PRA.length; i++) {
      if ($J$PRA[i].path == this.path) {
        $J$PRA[i].onRate();
      }
    }
  }

  var title = this.config.title || "";
  this.server(".put", "rating=" + givenRating
    + (this.config.property ? "&property=" + this.config.property : "")
    + (this.config.category ? "&category=" + this.config.category : "")
	+ (title ? ("&title=" + encodeURIComponent(title)) : "")
	+ (this.config.notop ? "&notop=true" : "")
	+ (this.config.permalink ? "&permalink=" + encodeURIComponent(this.config.permalink) : "")
	);
}

JSRC.prototype.setUserRating = function(rating) {
  this.userRating = rating;
}

// Returns: an array of actionable rating icons 
JSRC.prototype.getRatingIcons = function() {

  if (this._ratingIcons && this._ratingIcons.length > 0) {
    return this._ratingIcons;
  }

  this._ratingIcons = this._getIcons('js-kit-rater');
  return this._ratingIcons;
}

JSRC.prototype.getObjIcons = function() {

  if (this._objIcons && this._objIcons.length > 0) {
    return this._objIcons;
  }

  this._objIcons = this._getIcons('js-kit-objIcon');
  return this._objIcons;
}

JSRC.prototype._getIcons = function(iconClass) {

  var divs = this.target.getElementsByTagName('div');
  var icons = [];
  for (var i=0; i < divs.length; i++) {
    if (divs[i].className && divs[i].className.indexOf(iconClass) >= 0) {
      icons.push(divs[i]);
    }
  }
  return icons;
}

JSRC.prototype.getTextForTotalVotes = function(votes) {
  var text;
  switch(votes) {
    case  1: text = votes + ' ' + $JRL('vote');  break;
    default: text = votes + ' ' + $JRL('votes'); break;
  }
  return text;
}

JSRC.prototype.getTextForUserRating = function(rating) {
  var text = $JRL('yourRatingTitleCase') + ': ' + rating;
  return text;
}

JSRC.prototype.refreshTextTotal = function() {
  var text = (this.objNum) ? this.getTextForTotalVotes(this.objNum) : $JRL('unrated');
  this.setTextTotal(text);
}

JSRC.prototype.refreshTextRating = function(text) {
  if (this.userRating) {
    var text = this.getTextForUserRating(this.userRating / this.raterInc);
  } else { 
    var text = $JRL('yourRatingTitleCase');
  }
  this.setTextRating(text);
}

JSRC.prototype.setTextRating = function(text) {
  this._setText(this.textRating, text);
}

JSRC.prototype.setTextTotal = function(text) {
  this.lastSetText = text;
  if(this.tmpTextTimer)
	return;
  this._setText(this.textTotal, text);
}

JSRC.prototype.setActiveText = function(text) {
  this._setText(this.activeText, text);
}

JSRC.prototype.setTmpText = function(text) {
  var self = this;
  if(this.tmpTextTimer)
    clearTimeout(this.tmpTextTimer);
  this.tmpTextTimer = setTimeout(function() {
	self.tmpTextTimer = null;
	self.setTextTotal(self.lastSetText);
    }, 1000);
  this._setText(this.textTotal, text);
}

JSRC.prototype._setText = function(node, text) {
  if ( ! node) {
    return;
  }
  while (node.hasChildNodes()) {
    node.removeChild(node.firstChild);
  }
  node.appendChild(document.createTextNode(text));
}

JSRC.prototype.setImage = function(star, imageURL) {
	if(star.imageURL == imageURL)
		return;	// Already set and we know it
	star.imageURL = imageURL;

	JSKitLib.addPNG(star, imageURL);    
}

// Handles the hover state for the actionable stars
JSRC.prototype.hover = function(index) {

  if(this.tmpTextTimer) return;

  // The text which is under the hover state
  this.setActiveText($JRL('rateThis') + ': ' + (index / this.raterInc));

  var icons = this.getRatingIcons();
  for (var i=0; i < icons.length; i++) {
    if (index > (i * this.raterInc)) {
	this.setImage(icons[i], this.fullStar['user']);
    } else {
	this.setImage(icons[i], this.emptyStar['user']);
    }
  }

}

JSRC.prototype.refreshObjRating = function() {
  var icons = this.getObjIcons();
  this._refreshRating('star', this.objEffRating, icons);
}

// Resets the user rating view to their actual rating
JSRC.prototype.refreshRating = function() {

  if (this.defaultView == 'star') {
    var type = 'star';
    var comparison = this.objEffRating;
  } else {
    var type = 'user';
    var comparison = this.userRating;
  }

  var icons = this.getRatingIcons();

  this._refreshRating(type, comparison, icons);

  if (this.defaultView == 'star') {
    this.refreshTextTotal();
  } else {
    this.refreshTextRating();
  }
}

JSRC.prototype._refreshRating = function(type, comparison, icons) {

  for (var i=0; i < icons.length; i++) {
    if (comparison > (i * this.raterInc)) {
      if (i * this.raterInc + (this.raterInc / 2) == comparison) {
        this.setImage(icons[i], this.halfStar[type]);
      } else {
        this.setImage(icons[i], this.fullStar[type]);
      }
    } else {
      this.setImage(icons[i], this.emptyStar[type]);
    }
  }
}  

JSRC.prototype.initRating = function(rating, type, actionable) {
  var self = this;
  var node = this.cr('div');
  node.style.width = this.ratingBarWidth + 'px';
  node.style.height = this.ratingBarHeight + 'px';

  var inf = function() {
	if(self.refreshScheduled)
		clearTimeout(self.refreshScheduled);
  }
  var outf = function() {
	if(self.refreshScheduled)
		clearTimeout(self.refreshScheduled);
	self.refreshScheduled = setTimeout(
		function(){self.refreshScheduled=null;
		self.refreshRating()}, 300);
  }

  node.onmouseover = function() {
			if(self.refreshScheduled)
				clearTimeout(self.refreshScheduled);
		}
  node.onmouseout = outf;

  /* Increment by Full Star Ratings */
  for (var i=this.raterInc; i <= this.scale; i += this.raterInc) {

    var star = this.cr('div');

    star.style.cssFloat   = 'left';
    star.style.styleFloat = 'left';
    star.style.width    = this.starWidth + 'px';
    star.style.height   = this.starHeight + 'px';

    if (rating + this.raterInc > i) {
      if (rating + this.raterInc - i >=  this.raterInc) {
	this.setImage(star, this.fullStar[type]);
      } else {
	this.setImage(star, this.halfStar[type]);
      }
    } else {
      this.setImage(star, this.emptyStar[type]);
    }

    if (actionable) {
     (function(i) {
      star.className += ' js-kit-rater';
      star.onmouseover = function() { inf(); self.hover(i); }
      star.onmouseout  = outf;
      star.onclick     = function() { self.rate(i); }
     })(i);
    } else {
      star.className += ' js-kit-objIcon';
    }
    node.appendChild(star);
  }

  if (actionable) {
    node.style.cursor = 'pointer';
  }

  return node;
}


JSRC.prototype.getCommentsAppObject = function() {
  if (this.isStandalone()) {
    return null; 
  } else {
    return $JSKitGlobal.getCommentsAppObject(this.uniq);
  }
}

JSRC.prototype.hasCommentsAppObject = function() {
  return this.getCommentsAppObject() ? true : false;
}

JSRC.prototype.clone = function(node, options) {
  if ( ! options) {
    options = {};
  }

  var clone = new JSRC(node, {
    'newRating' : {
      'objSum' : this.objSum,
      'objNum' : this.objNum,
      'userRating' : this.userRating
    },
    'path' : options.path || this.config.path,
    'uniq' : options.uniq || this.config.uniq,
    'view' : options.view || this.config.view,
    'notop' : options.notop || this.config.notop,
    'commentprompt' : options.commentprompt || this.config.commentprompt,
    'starcolor' : options.starcolor || this.config.starcolor,
    'usercolor' : options.usercolor || this.config.usercolor,
    'imageurl' : options.imageurl || this.config.imageurl,
    'imagesize' : options.imagesize || this.config.imagesize,
    'menu' : options.menu || this.config.menu

  });

  return clone;
}

JSRC.prototype.newRating = function() {
  var args = arguments;
  if(typeof args[0] != 'object')
    args = [ args[3], args[4] || {} ];
  var community = args[0];
  var user = args[1] || { Sum: 0 };

  if(user.frozen) this.config.freeze = "yes";

  this.objSum = community.Sum;
  this.objNum = community.Num;
  this.objVotes = community.Votes || community.Num;
  this.userRating = user.Sum;
  this.objAvgStarRating = JSKitLib.round((this.objSum / this.objNum) / this.raterInc, 1);
  this.objEffRating = Math.round(this.objSum / this.objNum) || 0;  // Used for star display purposes

  if(this.refreshScheduled) {
	clearTimeout(this.refreshScheduled);
	this.refreshScheduled = null;
  }

  if (this.constructed) {
    this.refreshTextTotal();
    this.refreshObjRating();
    this.refreshRating();
  } else {
    this.constructed = true;
    this.display();
  }

  // TODO: use JSKitGlobal
  if (window.$J$PRA && typeof $J$PRA == 'object') {
    for (var i=0; i < $J$PRA.length; i++) {
        $J$PRA[i].updateComposite();
    }
  }


}

