// Serene Bach Entry Rating Script
// == written by Takuya Otani <takuya.otani@gmail.com> ===
// == Copyright (C) 2006 SimpleBoxes/SerendipityNZ Ltd. ==

// === array ===
if (!Array.prototype.pop)
{
	Array.prototype.pop = function()
	{
		if (!this.length) return null;
		var last = this[this.length - 1];
		--this.length;
		return last;
	}
}
if (!Array.prototype.push)
{
	Array.prototype.push = function()
	{
		for (var i=0,n=arguments.length;i<n;i++)
			this[this.length] = arguments[i];
		return this.length;
	}
}
if (!Array.prototype.indexOf)
{
	Array.prototype.indexOf = function(value,idx)
	{
		idx = (typeof idx != 'number') ? 0
		    : (idx < 0)                ? this.length + idx
		    :                            idx;
		for (var i=idx,n=this.length;i<n;i++)
			if (this[i] === value) return i;
		return -1;
	}
}
// === browser ===
function Browser()
{
	this.name = navigator.userAgent;
	this.isWinIE = this.isMacIE = false;
	this.isGecko  = this.name.match(/Gecko\//);
	this.isSafari = this.name.match(/AppleWebKit/);
	this.isKHTML  = this.isSafari || navigator.appVersion.match(/Konqueror|KHTML/);
	this.isOpera  = window.opera;
	if (document.all && !this.isGecko && !this.isSafari && !this.isOpera)
	{
		this.isWinIE = this.name.match(/Win/);
		this.isMacIE = this.name.match(/Mac/);
		this.isNewIE = (this.name.match(/MSIE 5\.5/) || this.name.match(/MSIE 6\.0/));
	}
}
var Browser = new Browser();
// === event ===
if (!window.Event) var Event = new Object;
Event = {
	cache : false,
	getEvent : function(evnt)
	{
		return 
			  (evnt)         ? evnt
			: (window.event) ? window.event
			:                  null;
	},
	getKey : function(evnt)
	{
		evnt = this.getEvent(evnt);
		return (evnt.which) ? evnt.which : evnt.keyCode;
	},
	stop : function(evnt)
	{
		try
		{
			evnt.stopPropagation();
		}
		catch(err) {};
		evnt.cancelBubble = true;
		try
		{
			evnt.preventDefault();
		}
		catch(err) {};
		return (evnt.returnValue = false);
	},
	register : function(object, type, handler)
	{
		if (type == 'keypress' && !object.addEventListener) type = 'keydown';
		if (type == 'mousewheel' && Browser.isGecko) type = 'DOMMouseScroll';
		if (!this.cache) this.cache = [];
		if (object.addEventListener)
		{
			this.cache.push([object,type,handler]);
			object.addEventListener(type, handler, false);
		}
		else if (object.attachEvent)
		{
			this.cache.push([object,type,handler]);
			object.attachEvent(['on',type].join(''),handler);
		}
		else
		{
			object[['on',type].join('')] = handler;
		}
	},
	deregister : function(object, type, handler)
	{
		if (type == 'keypress' && !object.addEventListener) type = 'keydown';
		if (type == 'mousewheel' && Browser.isGecko) type = 'DOMMouseScroll';
		if (object.removeEventListener)
			object.removeEventListener(type, handler, false);
		else if (object.detachEvent)
			object.detachEvent(['on',type].join(''), handler);
		else
			object[['on',type].join('')] = null;
	},
	deregisterAll : function()
	{
		if (!Event.cache) return
		for (var i=0,n=Event.cache.length;i<n;i++)
		{
			Event.deregister(Event.cache[i]);
			Event.cache[i][0] = null;
		}
		Event.cache = false;
	},
	run : function(func)
	{
		if (typeof func == 'function')
			this.register(window,'load',func);
	}
};
Event.register(window, 'unload', Event.deregisterAll);
// === hash ===
function Hash(hash)
{
	this.$ = (typeof hash == 'object') ? hash : {};
	return this;
}
Hash.prototype = {
	extend : function(source)
	{
		for (var elem in source)
			this.$[elem] = source[elem];
		return this;
	},
	keys : function()
	{
		var key = [];
		for (var elem in this.$)
			key.push(elem);
		return key;
	},
	values : function()
	{
		var val = [];
		var key = this.keys();
		for (i=0,n=key.length;i<n;i++)
			val.push(this.$[key[i]]);
		return val;
	}
};
// === element ===
document.getElementsByClassName = function(name,target)
{
	var result = [];
	var object  = null;
	var search = new RegExp(['(^|\\s)',name,'(\\s|$)'].join(''));
	if (target && target.getElementsByTagName)
		object = this.getElementsByTagName('*');
	if (!object)
		object = document.getElementsByTagName ? document.getElementsByTagName('*') : document.all;
	for (var i=0,n=object.length;i<n;i++)
	{
		var check = object[i].getAttribute('class') || object[i].className;
		if (check.match(search)) result.push(object[i]);
	}
	return result;
}
function $(element)
{
	if (arguments.length > 1)
	{
		var elements = new Array();
		for (var i=0,n=arguments.length;i<n;i++)
			elements.push($(arguments[i]));
		return elements;
	}
	if (typeof element == 'string')
		return document.getElementById(element);
	return null;
}
// === ajax ===
function createXmlHttp()
{
	var object = null;
	try
	{
		if (window.XMLHttpRequest) object = new XMLHttpRequest();
	}
	catch(e) {}
	if (object) return object;
	try
	{
		object = new ActiveXObject("Msxml2.XMLHTTP");
	}
	catch(e) {
		try
		{
			object = new ActiveXObject("Microsoft.XMLHTTP");
		}
		catch(e) {}
	}
	return object;
}
function parseXml(xmlText)
{
	if (window.ActiveXObject)
	{
		var xmlDom = new ActiveXObject('Microsoft.XMLDOM');
		xmlDom.async = false;
		xmlDom.loadXML(xmlText);
		return xmlDom;
	}
	else if (window.DOMParser)
	{
		var parser = new DOMParser();
		return parser.parseFromString(xmlText, "application/xml");
	}
	return null;
}
function Ajax()
{
	this.req = null;
	this.type = 'xml'; // xml or text
	this.method = 'GET'; // GET or POST
	this.async = true;
	return this;
}
Ajax.prototype = {
	onerror : function(status)
	{ // default error handler - should be overridden
		alert(['error : ',status].join(''));
	},
	callback : function(res)
	{ // default handler - should be overridden
		alert(['loaded : ',res.toString()].join(''));
	},
	post : function(url,data)
	{
		this.method = 'POST';
		this.request(url,data);
	},
	get : function(url,data)
	{
		this.method = 'GET';
		this.request(url,data);
	},
	request : function(url,data)
	{
		var req = this.req = createXmlHttp();
		var self = this;
		this.req.onreadystatechange = function()
		{
			if (req.readyState == 4)
			{
				var stat = req.status;
				if (stat == undefined) stat = 0;
				switch (stat)
				{
				case 0: // for local debugging
				case 200:
					var response = null;
					if (self.type == 'text')
					{ // text
						response = req.responseText;
						if (Browser.isKHTML)
						{
							var esc = escape(response);
							if (esc.indexOf("%u") < 0 && esc.indexOf("%") > -1)
								response = decodeURIComponent(esc);
						}
					}
					else if (!req.responseXML)
					{ // xml
						response = parseXml(req.responseText);
					}
					else
					{
						response = req.responseXML;
					}
					self.callback(response);
					break;
				case 304:
					self.callback();
					req.onreadystatechange = null;
					break;
				default:
					self.onerror(req.status);
				}
			} // end of if (req.readyState == 4)
		} // end of this.req.onreadystatechange
		this.req.open(this.method,url,this.async);
		if (this.method == "POST")
		{
			try
			{
				this.req.setRequestHeader("Content-Type","application/x-www-form-urlencoded;");
			}
			catch(e) {}
		}
		if (this.type == 'xml')
		{
			try
			{
				this.req.overrideMimeType("text/xml");
			}
			catch(e) {}
		}
		this.req.send(data);
	}
};
// === cookie ===
function Cookie(option)
{
	this._setting = (new Hash({
		domain: window.location.hostname,
		path: '/',
		expire: 10
	})).extend(option);
	this._cookies = {};
	this.update();
	return this;
}
Cookie.prototype = {
	update : function()
	{
		var cookies = document.cookie.split(';');
		for (var i=0,n=cookies.length;i<n;i++)
		{
			cookies[i] = cookies[i].replace(/^\s*/,'');
			var value = cookies[i].split('=',2);
			this._cookies[value[0]] = value[1];
		}
	},
	get : function(name)
	{
		return this._cookies[name];
	},
	set : function(name,value)
	{
		var cookies = [];
		cookies.push([name,value].join('='));
		if (this._setting.$['expire'])
		{ // expires
			var date = new Date();
			date.setTime(date.getTime() + (this._setting.$['expire'] * 24 * 3600 * 1000));
			cookies.push(['expires',date.toGMTString()].join('='));
		}
		for (var value in this._setting.$)
		{
			if (value == 'expire') continue; // already handled
			if (!this._setting.$[value]) continue; // invalid value
			cookies.push([value,this._setting.$[value]].join('='));
		}
		document.cookie = cookies.join('; ');
	}
};
// === star ===
function Star(option)
{
	if (!option.target) option.target = 'rating';
	this.stars = new Hash();
	this.max = (option.max) ? option.max : 5;
	this.width = (option.width) ? option.width : 100;
	this.data = (option.data) ? option.data : './sbstar.txt';
	this.xml = (option.xml) ? option.xml : './rating.cgi';
	this.cookie = new Cookie({expire:100});
	this.key = 'sbStarKey';
	this.tried = 0;
	var objs = document.getElementsByClassName(option.target);
	for (var i=0,n=objs.length;i<n;i++)
	{
		var star = objs[i].firstChild.id;
		if (!this.stars.$[star])
		{
			this.stars.$[star] = {
				init : 0,
				done : (this.cookie.get([this.key,star].join('')) == 'done') ? true : false
			};
		}
		Event.register(objs[i],'mouseover',this._getMouseEvent(star));
		Event.register(objs[i],'mousemove',this._getMouseEvent(star));
		Event.register(objs[i],'mouseout',this._getMouseExit(star));
		Event.register(objs[i],'click',this._getMouseClick(star));
	}
	this.updateData();
}
Star.prototype = {
	updateData : function()
	{
		var self = this;
		var req = new Ajax();
		req.type = 'text';
		req.onerror = function() {
			self.tried++;
			if (self.tried < 10)
				window.setTimeout(function(){ self.updateData() },1000);
		};
		req.callback = function(res) {
			self.parseData(res);
		};
		req.get([self.data,'?',(new Date()).getTime()].join(''));
	},
	parseData : function(raw_data)
	{
		var self = this;
		var data = raw_data.replace(/\n|\r/g,'').split(';');
		for (var i=0,n=data.length;i<n;i++)
		{
			if (!data[i]) continue;
			var star = data[i].split(',',3);
			var id = star[0];
			if (!id || !$(id)) continue;
			var rate = (star[2] > 0) ? star[1]/star[2] : 0;
			if (!self.stars.$[id])
			{
				self.stars.$[id] = {
					init : 0,
					done : (self.cookie.get([self.key,id].join('')) == 'done') ? true : false
				};
			}
			self.stars.$[id].init = rate;
			self.updateStar(id);
		}
	},
	updateStar : function(id)
	{
		var span = this.width / this.max;
		var rating = parseInt(this.stars.$[id].init * span);
		if (rating > this.width) rating = this.width;
		if (!$(id)) return;
		$(id).style.width = [rating,'px'].join('');
	},
	_getMouseEvent : function(id)
	{
		var self = this;
		var obj = $(id);
		return function(ev) {
			if (!ev) ev = getEvent(ev);
			if (self.stars.$[id].done) return;
			obj.className = 'hover';
			var x = (ev.x || ev.clientX) - self.getOffsetX(obj.parentNode || obj.parentElement);
			var span = self.width / self.max;
			var width = Math.floor(x / span + 1) * span;
			if (width > self.width) width = self.width;
			obj.style.width = [width,'px'].join('');
			obj.style.cursor = 'pointer';
		};
	},
	_getMouseExit : function(id)
	{
		var self = this;
		var obj = $(id);
		return function(ev) {
			obj.className = '';
			obj.style.cursor = '';
			self.updateStar(id);
		};
	},
	_getMouseClick : function(id)
	{
		var self = this;
		var obj = $(id);
		return function() {
			if (self.stars.$[id].done) return;
			var span = self.width / self.max;
			var rate = Math.floor(parseInt(obj.style.width) / span);
			var req = new Ajax();
			obj.className = '';
			obj.style.width = '0px';
			req.onerror = self._getResult(id,true);
			req.callback = self._getResult(id,false);
			req.get([self.xml,'?id=',id,'&rate=',rate].join(''));
		}
	},
	_getResult : function(id,isError)
	{
		var self = this;
		var star = $(id);
		return function(res) {
			if (!isError && res && res.getElementsByTagName('suceeded'))
			{
				self.stars.$[id].done = true;
				self.cookie.set([self.key,id].join(''),'done');
				self.updateData();
			}
			star.className = '';
			self.updateStar(id);
		};
	},
	getOffsetX : function(obj)
	{
		if (!obj) return 0;
		var x = obj.offsetLeft;
		var parent = obj.offsetParent;
		while (parent)
		{
			x += parent.offsetLeft;
			parent = parent.offsetParent;
		}
		return x;
	}
};
Event.run( function() { new Star({
// ==== setting ====
max: 1,
width: 100,
data: 'http://www.semigem.org/log/sbstar.txt',
xml: 'http://www.semigem.org/rating.cgi',
target: 'sbrating'
// ==== setting ====
}) } );
