/*
 *	JavaScript for chat/plain (http://confetto.s31.xrea.com/)
 *	Copyright (c) 2003-2007 confetto. <confetto@s31.xrea.com>
 *	This script use a japanese character encoding: Shift_JIS
 *
 *	$Name: release-2-0-2 $
 *	$Id: chat.js,v 2.3 2007/08/24 14:20:05 confetto Exp $
 */

/******************************************************************************/

/*
 * 初期設定
 */

/* 選択させるリロード間隔(秒単位) */
IntervalSelector.intervals = [10, 20, 30, 45, 60];

/* HTTPリクエストを行う */
Chat.prototype.useHttp = true;

/* HTTPリクエストを行う場合に保持する発言の数 */
ChatResponse.prototype.messagesMax = 100;

/******************************************************************************/

function Chat() {};

Chat.prototype.intialize = function() {
	if (this._form = document.getElementsByTagName('form')[0]) {
		HTMLFormElement.extend(this._form);
		IEEventTarget.extend(this._form);
		if (this._form.elements.mssg) {				// 発言画面
			if (this._form.elements.rtime.value > 0)
				new ChatCount(this._form);
			if (this.useHttp && window.XMLHttpRequest &&
				!navigator.userAgent.match('^Opera/8'))
				new MessageLoader(this._form);
			this._form.elements.mssg.focus();
		} else if (this._form.elements.name) {		// 入室画面
			IntervalSelector.insertBefore(this._form.elements[1]);
			this._form.addEventListener('submit',
				this.validateForm.bind(this), false);
			this._form.elements.name.focus();
		}
	}
};

/**
 * 入室フォームを検証する
 */
Chat.prototype.validateForm = function(e) {
	if (this._form.elements.name.value == "") {
		alert("名前を入力してください");
		this._form.elements.name.focus();
		e.preventDefault();
	}
};

/******************************************************************************/

/**
 * カウントダウン
 */
function Count(count) {
	this._count = this._current = count;
}

/** カウント値の初期化 */
Count.prototype.reset = function() {
	this._current = this._count;
};

/** カウントの開始 */
Count.prototype.start = function() {
	this._timerID = setInterval(this._down.bind(this), 1000);
	this._down();
};

/** カウントの一時停止 */
Count.prototype.pause = function() {
	clearInterval(this._timerID);
};

/** カウント満了イベント */
Count.prototype.onexpire = function() {};

/**
 * カウント減少イベント
 * falseが返るとそのカウントダウンをキャンセルする。
 */
Count.prototype.ondecrease = function() { return true };

/** カウントダウンとイベント発動 */
Count.prototype._down = function() {
	if (this.ondecrease())
		this._current--;
	if (this._current < 0) {
		this.reset();
		this.onexpire();
	}
};

/******************************************************************************/

/**
 * 自動更新のためのカウントダウン
 */
function ChatCount(form) {
	this._form  = form;
	Count.call(this, this._form.elements.rtime.value);
	this._form.addEventListener('submit', this.reset.bind(this), false);
	this.start();
}

ChatCount.prototype = new Count();

ChatCount.prototype.onexpire = function() {
	this._form.simulateSubmit();
//	defaultStatus = '新しい発言はありません';
};

ChatCount.prototype.ondecrease = function() {
	defaultStatus = this._current;
	return this._form.elements.mssg.value == '';
};

/******************************************************************************/

/**
 * 更新時間を選択させるフォームを生成・追加する
 */
function IntervalSelector() {}

IntervalSelector.create = function() {
	var jml = ['label',
		'自動更新',
		['select',
			{ 'name' : 'rtime' },
			['option', { 'value' : '0' }, 'なし']
		]
	];
	for (var i = 0; i < this.intervals.length; i++)
		jml[2].push(['option',
			{ 'value' : this.intervals[i] },
			this.intervals[i] + '秒'
		]);
	return jml.parseJsonML();
};

IntervalSelector.insertBefore = function(refChild) {
	refChild.parentNode.insertBefore(this.create(), refChild);
};

/******************************************************************************/

function ChatRequest() {}

ChatRequest.prototype.timeout = 10000;	// 10秒

ChatRequest.prototype.onload = function(response) {};

ChatRequest.prototype.start = function(form) {
	if (!this._request) {
		defaultStatus = '通信を開始します。';
		this._request = new XMLHttpRequest();
		with (this._request) {
			onreadystatechange = this._handler.bind(this);
			open('POST', location.pathname, true);
			setRequestHeader('Content-Type',
				'application/x-www-form-urlencoded');
			send(form.createQuery() + '&js=1');
		}
		this._timerID = setTimeout(this._abort.bind(this), this.timeout);
	} else {
		defaultStatus = 'まだ通信できません。';
	}
};

ChatRequest.prototype._abort = function() {
	this._request.abort();
	this._request = null;
	alert('通信がタイムアウトしました。');
};

ChatRequest.prototype._handler = function() {
	if (this._request.readyState == 4) {
		defaultStatus = '通信が完了しました。';
		clearTimeout(this._timerID);
		switch (this._request.status) {
			case 200:
				var type = this._request.getResponseHeader('Content-Type');
				if (type.match('text/javascript'))
					eval(this._request.responseText);
				else
					this.onload(new ChatResponse(this._request.responseText));
				break;
			case 204:
			case 1223:	// MSIE bug
			case 0:		// Opera bug
				defaultStatus = '新しい発言はありません。';
				break;
			default:
				alert(this._request.status + ' ' + this._request.statusText);
				break;
		}
		this._request = null;
	}
};

/******************************************************************************/

function ChatResponse(responseText) {
	var parts = responseText.split('\n\n');
	this.mssgs = parts[0].split('\n').map(function(v) {
		var table = v.split('\t');
		return { name : table[0], mssg : table[1] };
	});
	this.names = parts[1].split('\n');
	this.mtime = parts[2];
};

ChatResponse.prototype.showMessages = function() {
	var dListElement = document.getElementById('mssg-list');
	var firstMessage = dListElement.firstChild;
	
	for (var i = 0; i < this.mssgs.length; i++) {
		var dtElement = ['dt', this.mssgs[i].name];
		var ddElement = ['dd', this.mssgs[i].mssg];
		if (this.mssgs[i].name == '')
			dtElement = ['dt', { 'class' : 'info' }, 'Info'];
		dListElement.insertBefore(dtElement.parseJsonML(), firstMessage);
		dListElement.insertBefore(ddElement.parseJsonML(), firstMessage);
	}
	
	var dtElements = dListElement.getElementsByTagName('dt');
	var ddElements = dListElement.getElementsByTagName('dd');
	while (this.messagesMax < dtElements.length) {
		dListElement.removeChild(dtElements[dtElements.length - 1]);
		dListElement.removeChild(ddElements[ddElements.length - 1]);
	}
};

ChatResponse.prototype.showNames = function() {
	var uListElement = document.getElementById('name-list');
	var countElement = document.getElementById('name-count');
	
	while (uListElement.childNodes.length)
		uListElement.removeChild(uListElement.firstChild);
	
	for (var i = 0; i < this.names.length; i++)
		uListElement.appendChild(['li', this.names[i]].parseJsonML());
	
	countElement.replaceChild(
		document.createTextNode(this.names.length), countElement.firstChild);
};

/******************************************************************************/

function MessageLoader(form) {
	this.load = this.load.bind(this);
	this._form = form;
	IEEventTarget.extend(this._form.elements.quit);
	this._form.addEventListener('submit', this.load, false);
	this._form.elements.quit.addEventListener('click', this.quit.bind(this), false);
}

MessageLoader.prototype = new ChatRequest();

MessageLoader.prototype.load = function(event) {
	event.preventDefault();
	this.start(this._form);
	this._form.elements.mssg.value = '';
}

MessageLoader.prototype.onload = function(response) {
	response.showMessages();
	response.showNames();
	this._form.elements.mtime.value = response.mtime;
}

MessageLoader.prototype.quit = function(event) {
	this._form.removeEventListener('submit', this.load, false);
};

/******************************************************************************/

/**
 * MSIE用のEventTarget
 */
function IEEventTarget() {};

IEEventTarget.extend = function(target) {
	var altMethods = {
		'addEventListener'    : 'attachEvent',	// MSIE5+
		'removeEventListener' : 'detachEvent',	// MSIE5+
		'dispatchEvent'       : 'fireEvent'		// MSIE5.5+
	};
	for (var method in altMethods)
		if (!target[method] && target[altMethods[method]])
			target[method] = this[method];
};

IEEventTarget.addEventListener = function(type, listener) {
	this[type + listener] = function() { listener(new IEEvent()) };
	this.attachEvent('on' + type, this[type + listener]);
};

IEEventTarget.removeEventListener = function(type, listener) {
	this.detachEvent('on' + type, this[type + listener]);
//	delete this[type + listener];
};

IEEventTarget.dispatchEvent = function(event) {
	if (this.fireEvent('on' + event.type))
		if (this[event.type]) this[event.type]();
}

/******************************************************************************/

/**
 * MSIE用のEvent
 * イベント発生時に生成すると、自動的にinitEventをコールする。
 */
function IEEvent() {
	if (window.event)
		this.initEvent(window.event.type, true, true);
}

IEEvent.prototype = {
	stopPropagation : function() {
		window.event.cancelBubble = true;
	},
	preventDefault : function() {
		window.event.returnValue = false;
	},
	initEvent : function(type, bubbles, cancelable) {
		this.type = type;
		this.bubbles = bubbles;
		this.cancelable = cancelable;
	}
};

/******************************************************************************/

/**
 * MSIE用のDocumentEvent
 */
function IEDocumentEvent() {};

IEDocumentEvent.extend = function(object) {
	if (!object.createEvent && window.event !== undefined)
		object.createEvent = this.createEvent;
};

IEDocumentEvent.createEvent = function() {
	return new IEEvent();
};

/******************************************************************************/

/**
 * MSIE用のXMLHttpRequest
 */
if (!window.XMLHttpRequest && window.ActiveXObject) {
	var XMLHttpRequest = function() {
//		return new ActiveXObject('Msxml2.XMLHTTP.3.0');	// IE7でエラー?
		return new ActiveXObject('Microsoft.XMLHTTP');
	};
}

/******************************************************************************/

/**
 * JsonMLとしてパースする(簡易版)
 * 参考: http://jsonml.org/
 */
Array.prototype.parseJsonML = function() {
	var element = document.createElement(this[0]);
	for (var i = 1; i < this.length; i++) {
		if (this[i] instanceof Array)
			element.appendChild(this[i].parseJsonML());
		else if (typeof(this[i]) === 'string' || typeof(this[i]) === 'number')
			element.appendChild(document.createTextNode(this[i]));
		else if (typeof(this[i]) === 'object')
			for (var attr in this[i])
				element.setAttribute(attr, this[i][attr]);
	}
	return element;
};

Array.prototype.map = function(func, thisp) {
	var result = new Array(this.length);
	for (var i = 0; i < this.length; i++)
		result[i] = func.call(thisp, this[i], i, this);
	return result;
};

/******************************************************************************/

/**
 * 任意のオブジェクトをthisで参照するFunctionオブジェクトを生成する
 * 参考: prototype.js (http://www.prototypejs.org/)
 */
Function.prototype.bind = function(object) {
	var method = this;
	return function() { return method.apply(object, arguments) };
};

/******************************************************************************/

if (!window.HTMLFormElement)
	var HTMLFormElement = function() {};

HTMLFormElement.extend = function(object) {
	if (!object.constructor)
		for (var i in this.prototype)
			object[i] = this.prototype[i];
};

/** クエリ文字列を生成する */
HTMLFormElement.prototype.createQuery = function() {
	var queries = [], name;
	for (var i = 0; i < this.elements.length; i++) {
		if (name = this.elements[i].name) {
			switch (this.elements[i].nodeName.toLowerCase()) {
				case 'input':
					switch (this.elements[i].type) {
						case 'text':
						case 'hidden':
							queries.push([name, this.elements[i].value]);
							break;
					}
					break;
			}
		}
	}
	return queries.map(function(v) {
		return v.map(encodeURIComponent).join('=');
	}).join('&');
};

/** submit型イベントを発生させる */
HTMLFormElement.prototype.simulateSubmit = function() {
	var event = document.createEvent('HTMLEvents');
	event.initEvent('submit', true, true);
	this.dispatchEvent(event);
};

/******************************************************************************/

/*
 * メインルーチン
 */
var chat = new Chat();
IEEventTarget.extend(window);
IEDocumentEvent.extend(document);
window.addEventListener('load', chat.intialize.bind(chat), false);

