//  Starbox 1.2 - 17-5-2009
//  Copyright (c) 2008-2009 Nick Stakenburg (http://www.nickstakenburg.com)
//
//  Licensed under a Creative Commons Attribution-Noncommercial-No Derivative Works 3.0 Unported License
//  http://creativecommons.org/licenses/by-nc-nd/3.0/

//  More information on this project:
//  http://www.nickstakenburg.com/projects/starbox/

var Starboxes = {
  options: {
    buttons: 5,                                  // amount of clickable areas
    className : 'default',                       // default class
    color: false,                                // would overwrite the css style to set color on the stars
    duration: 0.6,                               // the duration of the revert effect, when effects are used
    effect: {
      mouseover: false,                          // use effects on mouseover, default false
      mouseout: (window.Effect && Effect.Morph)  // use effects on mouseout, default when available
    },
    hoverColor: false,                           // overwrites the css hover color
    hoverClass: 'hover',                         // the css hover class color
    ghostColor: false,                           // the color of the ghost stars, if used
    ghosting: false,                             // ghosts the previous vote
    identity: false,                             // a unique value you can give each starbox
    indicator: false,                            // use an indicator, default false
    inverse: false,                              // inverse the stars, right to left
    locked: false,                               // lock the starbox to prevent voting
    max: 5,                                      // the maximum rating of the starbox
    onRate: Prototype.emptyFunction,             // default onRate, function(element, memo) {}
    rated: false,                                // or a rating to indicate a vote has been cast
    ratedClass: 'rated',                         // class when rated
    rerate: false,                               // allow rerating
    overlay: 'default.png',                      // default star overlay image
    overlayImages: 'http://www.culturasierranorte.org/images/starbox/',            // directory of images relative to this file
    stars: 5,                                    // the amount of stars
    total: 0                                     // amount of votes cast
  }
};

Object.extend(Starboxes, {
	REQUIRED_Prototype: "1.6.0.3",
	REQUIRED_Scriptaculous: "1.8.2",
	load: function () {
		this.require("Prototype");
		this.identify.counter = 1;
		if (/^(https?:\/\/|\/)/.test(this.options.overlayImages)) {
			this.imageSource = this.options.overlayImages
		} else {
			var a = /starbox(?:-[\w\d.]+)?\.js(.*)/;
			this.imageSource = (($$("script[src]").find(function (b) {
				return b.src.match(a)
			}) || {}).src || "").replace(a, "") + this.options.overlayImages
		}
	},
	require: function (a) {
		if ((typeof window[a] == "undefined") || (this.convertVersionString(window[a].Version) < this.convertVersionString(this["REQUIRED_" + a]))) {
			throw ("Starbox requires " + a + " >= " + this["REQUIRED_" + a]);
		}
	},
	convertVersionString: function (a) {
		var b = a.replace(/_.*|\./g, "");
		b = parseInt(b + "0".times(4 - b.length));
		return a.indexOf("_") > -1 ? b - 1 : b
	},
	fixIE: (function (b) {
		var a = new RegExp("MSIE ([\\d.]+)").exec(b);
		return a ? (parseFloat(a[1]) < 7) : false
	})(navigator.userAgent),
	identify: function (b) {
		b = $(b);
		var c = b.readAttribute("id"),
		a = arguments.callee;
		if (c) {
			return c
		}
		do {
			c = "starbox_" + a.counter++
		} while ($(c));
		b.writeAttribute("id", c);
		return c
	},
	imagecache: [],
	cacheImage: function (a) {
		if (!this.getCachedImage(a.src)) {
			this.imagecache.push(a)
		}
		return a
	},
	getCachedImage: function (a) {
		return this.imagecache.find(function (b) {
			return b.src == a
		})
	},
	buildQueue: [],
	queueBuild: function (a) {
		this.buildQueue.push(a)
	},
	processBuildQueue: function () {
		if (!this.buildQueue[0]) {
			this.batchLoading = true;
			return
		}
		this.cacheBuildBatch(this.buildQueue[0])
	},
	cacheBuildBatch: function (c) {
		var e = [],
		b = c.options.overlay,
		a = this.getCachedImage(b);
		this.buildQueue.each(function (f) {
			if (f.options.overlay == b) {
				e.push(f);
				this.buildQueue = this.buildQueue.without(f)
			}
		}.bind(this));
		if (!a) {
			var d = new Image();
			d.onload = function () {
				this.buildBatch(e, {
					src: b,
					height: d.height,
					width: d.width,
					fullsrc: d.src
				})
			}.bind(this);
			d.src = Starboxes.imageSource + b
		} else {
			this.buildBatch(e, a)
		}
	},
	buildBatch: function (b, a) {
		b.each(function (c) {
			c.imageInfo = a;
			c.build()
		});
		this.processBuildQueue()
	},
	useEvent: (function (a) {
		return {
			click: "click",
			mouseover: "mouseover",
			mouseout: (a ? "mouseleave": "mouseout")
		}
	})(Prototype.Browser.IE),
	capture: function (a) {
		if (!Prototype.Browser.IE) {
			a = a.wrap(function (e, d) {
				var c = Object.isElement(this) ? this: this.element,
				b = d.relatedTarget;
				if (b != c && !$A(c.select("*")).member(b)) {
					e(d)
				}
			})
		}
		return a
	}
});
Starboxes.load();
document.observe("dom:loaded", Starboxes.processBuildQueue.bind(Starboxes));
var Starbox = Class.create({
	initialize: function (a, b) {
		this.element = $(a);
		this.average = b;
		this.options = Object.extend(Object.clone(Starboxes.options), arguments[2] || {});
		$w("identity rated max total").each(function (c) {
			this[c] = this.options[c]
		}.bind(this));
		this.locked = this.options.locked || (this.rated && !this.options.rerate);
		if (!this.identity) {
			this.identity = Starboxes.identify(this.element)
		}
		if (this.options.effect && (this.options.effect.mouseover || this.options.effect.mouseout)) {
			Starboxes.require("Scriptaculous")
		}
		Starboxes.queueBuild(this);
		if (Starboxes.batchLoading) {
			Starboxes.processBuildQueue()
		}
	},
	enable: function () {
		$w("mouseout mouseover click").each(function (c) {
			var b = c.capitalize(),
			a = this["on" + b].bindAsEventListener(this);
			this["on" + b + "_cached"] = (c == "mouseout" && !Prototype.Browser.IE) ? Starboxes.capture(a) : a;
			this.starbar.observe(Starboxes.useEvent[c], this["on" + b + "_cached"])
		}.bind(this));
		this.buttons.invoke("setStyle", {
			cursor: "pointer"
		})
	},
	disable: function () {
		$w("mouseover mouseout click").each(function (a) {
			this.starbar.stopObserving(Starboxes.useEvent[a], this["on" + a.capitalize() + "_cached"])
		}.bind(this));
		this.buttons.invoke("setStyle", {
			cursor: "auto"
		})
	},
	build: function () {
		this.starWidth = this.imageInfo.width;
		this.starHeight = this.imageInfo.height;
		this.starSrc = this.imageInfo.fullsrc;
		this.boxWidth = this.starWidth * this.options.stars;
		this.buttonWidth = this.boxWidth / this.options.buttons;
		this.buttonRating = this.options.max / this.options.buttons;
		if (this.options.effect) {
			this.zeroPosition = this.getBarPosition(0);
			this.maxPosition = this.getBarPosition(this.options.max)
		}
		var a = {
			absolute: {
				position: "absolute",
				top: 0,
				left: 0,
				width: this.boxWidth + "px",
				height: this.starHeight + "px"
			},
			base: {
				position: "relative",
				width: this.boxWidth + "px",
				height: this.starHeight + "px"
			},
			star: {
				position: "absolute",
				top: 0,
				left: 0,
				width: this.starWidth + "px",
				height: this.starHeight + "px"
			}
		};
		this.element.addClassName("starbox");
		this.container = new Element("div", {
			className: this.options.className || ""
		}).setStyle({
			position: "relative"
		}).insert(this.status = new Element("div").insert(this.hover = new Element("div").insert(this.wrapper = new Element("div", {
			className: "stars"
		}).setStyle(Object.extend({
			overflow: "hidden"
		},
		a.base)))));
		if (this.rated) {
			this.status.addClassName("rated")
		}
		if (this.locked) {
			this.status.addClassName("locked")
		}
		if (this.options.ghosting) {
			this.wrapper.insert(this.ghost = new Element("div", {
				className: "ghost"
			}).setStyle(a.absolute));
			if (this.options.ghostColor) {
				this.ghost.setStyle({
					background: this.options.ghostColor
				})
			}
			if (this.options.effect) {
				this.ghost.scope = this.ghost.identify()
			}
			this.setBarPosition(this.ghost, this.average, (window.Effect && Effect.Morph))
		}
		this.wrapper.insert(this.colorbar = new Element("div", {
			className: "colorbar"
		}).setStyle(a.absolute)).insert(new Element("div").setStyle(a.absolute).insert(this.starbar = new Element("div").setStyle(a.base)));
		if (this.options.color) {
			this.colorbar.setStyle({
				background: this.options.color
			})
		}
		if (this.options.effect) {
			this.colorbar.scope = this.colorbar.identify()
		}
		this.options.stars.times(function (b) {
			var c;
			this.starbar.insert(c = new Element("div").setStyle(Object.extend({
				background: "url(" + this.starSrc + ") top left no-repeat",
				left: this.starWidth * b + "px"
			},
			a.star)));
			c.setStyle({
				left: this.starWidth * b + "px"
			});
			if (Starboxes.fixIE) {
				c.setStyle({
					background: "none",
					filter: "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='" + this.starSrc + "'', sizingMethod='scale')"
				})
			}
		}.bind(this));
		this.buttons = [];
		this.options.buttons.times(function (d) {
			var c, b = this.options.inverse ? this.boxWidth - this.buttonWidth * (d + 1) : this.buttonWidth * d;
			this.starbar.insert(c = new Element("div").setStyle({
				position: "absolute",
				top: 0,
				left: b + "px",
				width: this.buttonWidth + (Prototype.Browser.IE ? 1 : 0) + "px",
				height: this.starHeight + "px"
			}));
			c.rating = this.buttonRating * d + this.buttonRating;
			this.buttons.push(c)
		}.bind(this));
		this.setBarPosition(this.colorbar, this.average);
		this.element.update(this.container);
		this.inputs = {};
		$w("average max rated rerated total").each(function (b) {
			this.element.insert(this.inputs[b] = new Element("input", {
				type: "hidden",
				name: this.identity + "_" + b,
				value: "" + (b == "rerated" ? !!this[b] : this[b])
			}))
		}.bind(this));
		if (this.options.indicator) {
			this.hover.insert(this.indicator = new Element("div", {
				className: "indicator"
			}));
			this.updateIndicator()
		}
		if (!this.locked) {
			this.enable()
		}
	},
	updateAverage: function (a) {
		if (this.rated && this.options.rerate) {
			this.average = (this.total * this.average - this.rated) / (this.total - 1 || 1)
		}
		var b = this.rated ? this.total: this.total++;
		this.average = (this.average == 0) ? a: (this.average * (this.rated ? b - 1 : b) + a) / (this.rated ? b: b + 1)
	},
	updateIndicator: function () {
		this.indicator.update(new Template(this.options.indicator).evaluate({
			max: this.options.max,
			total: this.total,
			average: (this.average * 10).round() / 10
		}))
	},
	getBarPosition: function (b) {
		var a = (this.boxWidth - (b / this.buttonRating) * this.buttonWidth);
		return parseInt(this.options.inverse ? a.ceil() : -1 * a.floor())
	},
	setBarPosition: function (a, b) {
		if (this.options.effect && this["activeEffect_" + a.scope]) {
			Effect.Queues.get(a.scope).remove(this["activeEffect_" + a.scope])
		}
		var d = this.getBarPosition(b);
		if (arguments[2]) {
			var c = parseInt(a.getStyle("left")),
			f = this.getBarPosition(b);
			if (c == f) {
				return
			}
			var e = ((this.maxPosition - (c - f).abs()).abs() / this.zeroPosition.abs()).toFixed(2);
			this["activeEffect_" + a.scope] = new Effect.Morph(a, {
				style: {
					left: d + "px"
				},
				queue: {
					position: "end",
					limit: 1,
					scope: a.scope
				},
				duration: (this.options.duration * e)
			})
		} else {
			a.setStyle({
				left: d + "px"
			})
		}
	},
	onClick: function (c) {
		var b = c.element();
		if (!b.rating) {
			return
		}
		this.updateAverage(b.rating);
		if (this.options.indicator) {
			this.updateIndicator()
		}
		if (this.options.ghosting) {
			this.setBarPosition(this.ghost, this.average, (window.Effect && Effect.Morph))
		}
		if (!this.rated) {
			this.status.addClassName("rated")
		}
		this.rerated = !!this.rated;
		this.rated = b.rating;
		if (!this.options.rerate) {
			this.disable();
			this.status.addClassName("locked");
			this.onMouseout(c)
		}
		var a = {};
		$w("average identity max rated rerated total").each(function (d) {
			if (d != "identity") {
				this.inputs[d].value = this[d]
			}
			a[d] = this[d]
		}.bind(this));
		this.options.onRate(this.element, a);
		this.element.fire("starbox:rated", a)
	},
	onMouseout: function (a) {
		this.setBarPosition(this.colorbar, this.average, (this.options.effect && this.options.effect.mouseout));
		this.hovered = false;
		if (this.options.hoverClass) {
			this.hover.removeClassName(this.options.hoverClass)
		}
		if (this.options.hoverColor) {
			this.colorbar.setStyle({
				background: this.options.color
			})
		}
		this.element.fire("starbox:left")
	},
	onMouseover: function (b) {
		var a = b.element();
		if (!a.rating) {
			return
		}
		this.setBarPosition(this.colorbar, a.rating, (this.options.effect && this.options.effect.mouseover));
		if (!this.hovered && this.options.hoverClass) {
			this.hover.addClassName(this.options.hoverClass)
		}
		this.hovered = true;
		if (this.options.hoverColor) {
			this.colorbar.setStyle({
				background: this.options.hoverColor
			})
		}
		this.element.fire("starbox:changed", {
			identify: this.options.identity,
			max: this.options.max,
			rating: a.rating,
			total: this.total
		})
	}
});
