// 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;
	}
}
// === spica ===
if (!window.Spica) var Spica = {};
// === browser ===
Spica.Browser = new function()
{
	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/));
	}
};
// === event ===
Spica.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' && Spica.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,handler);
		}
		else
		{
			object['on' + type] = handler;
		}
	},
	deregister : function(object, type, handler)
	{
		if (type == 'keypress' && !object.addEventListener) type = 'keydown';
		if (type == 'mousewheel' && Spica.Browser.isGecko) type = 'DOMMouseScroll';
		if (object.removeEventListener)
			object.removeEventListener(type, handler, false);
		else if (object.detachEvent)
			object.detachEvent('on' + type, handler);
		else
			object['on' + type] = null;
	},
	deregisterAll : function()
	{
		if (!Spica.Event.cache) return
		for (var i=0,n=Spica.Event.cache.length;i<n;i++)
		{
			Spica.Event.deregister(Spica.Event.cache[i]);
			Spica.Event.cache[i][0] = null;
		}
		Spica.Event.cache = false;
	},
	run : function(func)
	{
		if (typeof func == 'function')
			this.register(window,'load',func);
	}
};
Spica.Event.register(window, 'unload', Spica.Event.deregisterAll);
// === hash ===
Spica.Hash = function(hash)
{
	this.$ = (typeof hash == 'object') ? hash : {};
	return this;
}
Spica.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 ===
Spica.getElementsByClassName = function(name,target)
{
	var result = [];
	var object  = null;
	var search = new RegExp('(^|\\s)' + name + '(\\s|$)');
	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;
}
Spica.$ = 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 ===
Spica.createXmlHttp = function()
{
	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;
}
Spica.parseXml = function(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;
}
Spica.Ajax = function()
{
	this.req = null;
	this.type = 'xml'; // xml or text
	this.method = 'GET'; // GET or POST
	this.async = true;
	return this;
}
Spica.Ajax.prototype = {
	onerror : function(status)
	{ // default error handler - should be overridden
		alert('error : ' + status);
	},
	callback : function(res)
	{ // default handler - should be overridden
		alert('loaded : ' + res.toString());
	},
	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 = Spica.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 (Spica.Browser.isKHTML)
						{
							var esc = escape(response);
							if (esc.indexOf("%u") < 0 && esc.indexOf("%") > -1)
								response = decodeURIComponent(esc);
						}
					}
					else if (!req.responseXML)
					{ // xml
						response = Spica.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 ===
Spica.Cookie = function(option)
{
	this._setting = (new Spica.Hash({
		domain: window.location.hostname,
		path: '/',
		expire: 10
	})).extend(option);
	this._cookies = {};
	this.update();
	return this;
}
Spica.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 ===
Spica.Star = function(option)
{
	if (!option.target) option.target = 'rating';
	this.stars = new Spica.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.text = (option.text) ? option.text : '';
	this.multi = (option.cookie != 0) ? true : false;
	this.cookie = new Spica.Cookie({expire:100});
	this.key = 'sbStarKey';
	this.tried = 0;
	var objs = Spica.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,
				num  : 0,
				done : (this.cookie.get(this.key + star) == 'done') ? true : false
			};
			this._updateText(objs[i].firstChild);
		}
		Spica.Event.register(objs[i],'mouseover',this._getMouseEvent(star));
		Spica.Event.register(objs[i],'mousemove',this._getMouseEvent(star));
		Spica.Event.register(objs[i],'mouseout',this._getMouseExit(star));
		Spica.Event.register(objs[i],'click',this._getMouseClick(star));
	}
	this.updateData();
}
Spica.Star.prototype = {
	updateData : function()
	{
		var self = this;
		var req = new Spica.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());
	},
	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 || !Spica.$(id)) continue;
			var rate = (star[2] > 0) ? star[1]/star[2] : 0;
			if (!self.stars.$[id])
			{
				self.stars.$[id] = {
					init : 0,
					num  : 0,
					done : (self.cookie.get(self.key + id) == 'done' && !self.multi) ? true : false
				};
			}
			self.stars.$[id].init = rate;
			if (star[2] > 0) self.stars.$[id].num = star[2];
			self.updateStar(id);
		}
	},
	updateStar : function(id)
	{
		if (!Spica.$(id)) return;
		if (this.width > 0)
		{
			var span = this.width / this.max;
			var rating = parseInt(this.stars.$[id].init * span);
			if (rating > this.width) rating = this.width;
			Spica.$(id).style.width = rating + 'px';
		}
		this._updateText(Spica.$(id));
	},
	_updateText : function(obj)
	{
		if (!this.text || !obj) return;
		var text = this.text;
		var rating = parseInt(this.stars.$[obj.id].init * 10) / 10;
		var num = this.stars.$[obj.id].num;
		text = text.replace(/%RATE%/, (rating > 0) ? rating : '-');
		text = text.replace(/%NUM%/, (num > 0) ? num : '-');
		obj.innerHTML = text;
	},
	_getMouseEvent : function(id)
	{
		var self = this;
		var obj = Spica.$(id);
		return function(ev) {
			if (!ev) ev = Spica.getEvent(ev);
			if (self.stars.$[id].done) return;
			obj.className = 'hover';
			if (self.width > 0)
			{
				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';
			}
			obj.style.cursor = 'pointer';
		};
	},
	_getMouseExit : function(id)
	{
		var self = this;
		var obj = Spica.$(id);
		return function(ev) {
			obj.className = '';
			obj.style.cursor = '';
			self.updateStar(id);
		};
	},
	_getMouseClick : function(id)
	{
		var self = this;
		var obj = Spica.$(id);
		return function() {
			if (self.stars.$[id].done) return;
			var rate = 1;
			if (self.width > 0)
			{
				var span = self.width / self.max;
				rate = Math.floor(parseInt(obj.style.width) / span);
				obj.style.width = '0px';
			}
			var req = new Spica.Ajax();
			obj.className = '';
			req.onerror = self._getResult(id,true);
			req.callback = self._getResult(id,false);
			req.get(self.xml + '?id=' + id + '&rate=' + rate);
		}
	},
	_getResult : function(id,isError)
	{
		var self = this;
		var star = Spica.$(id);
		return function(res) {
			if (!isError && res && res.getElementsByTagName('suceeded'))
			{
				if (!self.multi)
				{
					self.stars.$[id].done = true;
					self.cookie.set(self.key + id,'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;
	}
};
Spica.Event.run( function() { new Spica.Star({
// ==== setting ====
max: 5,
width: 100,
data: 'http://ettan.jp/log/sbstar.txt',
xml: 'http://ettan.jp/rating.cgi',
text: "ΚΏ¶Ρ%RATE% ²σΏτ%NUM%",
target: 'sbrating',
cookie: 0
// ==== setting ====
}) } );
