/*
	ゲームソフト検索スクリプト
		fileName : video_game_search.js
		author : Junkieta(webmaster@junkieta.net)
		published : 2008-10-22
		updated : 2008-11-10
*/
(function(){
var req = null, $d = document, $addElement, videogameSearch, searchView;

function initSearchView(e) {
	var e, e2;
	e = $d.createDocumentFragment();
	e.appendChild($d.createElement("h2")).appendChild($d.createTextNode("検索の状況"));
	e2 = $d.createElement("p");
	e2.appendChild($d.createTextNode("ゲームソフトデータを取得中"));
	e2.appendChild($d.createTextNode(""));
	e.appendChild(e2);
	$d.body.insertBefore(e, $d.getElementById("ABOUT-SEARCH").nextSibling);
	e = e2;
	(function(){
		// データロードが未完了
		if(videogameSearch.dataString == -1) {
			if(e.lastChild.length < 5)
				e.lastChild.data += ".";
			else
				e.lastChild.data = "";
			setTimeout(arguments.callee, 500);
			return;
		}
		// データが受信できなかった
		if(videogameSearch.dataString == null) {
			e.lastChild.data = "ゲームソフトデータが正常に受信できませんでした。";
			e = null;
		} else {
			// データが正常に受信されている
			e2 = searchView.createForm(0);
			var unloaded = function() {
				e2.removeEventListener("submit",executeSearchCommand,false);
				e2.TARGET.removeEventListener("change",replaceExpField,false);
				window.removeEventListener("unload",unloaded,false);
				e2 = unloaded = null;
			};
			e2.addEventListener("submit",executeSearchCommand,false);
			e2.TARGET.addEventListener("change",replaceExpField,false);
			window.addEventListener("unload",unloaded,false);
			$d.body.replaceChild(e2, e);
		}
		e = null;
	})();
	window.removeEventListener("load",initSearchView,false);
}
/* ブラウザ振り分けはじめるよー */
// まずはwindowにaddEventListenerが通るか
if(window.addEventListener)	{
	window.addEventListener("load",initSearchView,false);
	// OKなら、addElementを通常通り実装
	$addElement = function(parent, tagName, attrs) {
		var e = parent.appendChild($d.createElement(tagName));
		for(var name in attrs) e.setAttribute(name, attrs[name]);
		return e;
	}
} else if(window.attachEvent) {
	// IEにはね、疲れるね、ほんとに...
	initSearchView = initSearchView.toString();
	initSearchView = initSearchView.replace(/addEventListener\("(.*?)",(.+?),false/gm, "attachEvent(\"on$1\",$2");
	initSearchView = initSearchView.replace(/removeEventListener\("(.*?)",(.+?),false/gm, "detachEvent(\"on$1\",$2");
	eval("initSearchView="+initSearchView);
	window.attachEvent("onload", initSearchView);
	$addElement = function(parent, tag, attrs) {
		var n;
		if(!/INPUT/i.test(tag)) {
			n = parent.appendChild($d.createElement(tag));
			for(var name in attrs)
				n.setAttribute(name != "class" ? name : "className", attrs[name]);
		} else {
			// INPUT生成バグのため、innerHTMLを利用
			var str = ["<input"];
			for(var name in attrs)
				str.push(name+"=\""+attrs[name]+"\"");
			n = $d.createElement("div");
			n.innerHTML = str.join(" ") + ">"; // " />"
			n = n.removeChild(n.lastChild);
			parent.appendChild(n);
		}
		return n;
	};
} else {
	// イベント処理メソッドがないならここで終了
	return;
}

// 検索用変数とメソッドを突っ込むだけのオブジェクト
videogameSearch = {
	// 検索結果の格納
	input : null,
	// データロード後にArrayとしてセットされる
	dataString : -1,
	// データの所在
	dataUrl : $d.getElementById("DATAURL").getAttribute("href"),
	// データの行区切り
	rowSeparator : new String("\n"),
	// データの列区切り
	colSeparator : new String("\t"),
	// データ終端改行を削除してセット
	setData : function(data) {
		this.dataString = data ? data.replace(/\s+$/, "").split(this.rowSeparator) : null;
	},
	// 検索実行
	exec : function(expression) {
		var result, line, i, cells, f, target;
		if(!this.dataString || !expression) return null;
		result = [];
		line = this.dataString;
		for(i = 0; i < line.length; i++) {
			f = true;
			cells = line[i].split(this.colSeparator);
			// expressionに格納された条件式を呼び出す
			for(target in expression) if(target in cells == false || !expression[target].test(cells[target])) {
				// マッチ失敗時はフラグを切ってループを抜ける
				f = false;
				break;
			}
			if(f)
				result.push(i);
		}
		this.input = result;
		return result.length;
	}
};

// 検索の状態に応じてDocumentにアクセスするオブジェクト
searchView = {
	// 各列の名称配列
	colNames : ["title", "maker", "hardware", "price", "release"],
	// 列名(日本語)の配列
	colNamesJP : ["タイトル", "メーカー", "機種", "価格", "発売日"],
	// 2008年現在のデータにおいて、最安値と最高値
	inexpensive : 500,
	expensive : 39800,
	// 項目別判定用オブジェクトの生成
	createExpression : function(form) {
		var expression = new Object, target = form.TARGET.selectedIndex, exp = null;
		switch(target) {
			case 0: exp = new TextExp(form.titleKeyword.value, form.titleAnd[1].checked); break;
			case 1: exp = new TextExp(form.makerKeyword.value, form.makerAnd[1].checked); break;
			case 2: exp = new TextExp(form.hardwareKeyword.value, form.hardwareAnd[1].checked); break;
			case 3: exp = new NumberExp(form.minPrice.value, form.maxPrice.value); break;
			case 4: exp = new NumberExp(form.startY.value, form.startM.value, form.startD.value, form.endY.value, form.endM.value, form.endD.value); break;
			default: return null;
		}
		if(exp == false) return null;
		expression[target] = exp;
		return expression;
	},
	// 検索フォームの生成
	createForm : function(target) {
		var f = $d.createElement("form"),
			cols = this.colNames,
			colsJP = this.colNamesJP,
			n;
		f.setAttribute("id", "VIDEOGAME-SEARCH");
		n = f.appendChild($d.createElement("p"));
		n.appendChild($d.createTextNode("条件を指定して"));
		$addElement(n, "input", { type : "submit", value : "検索を開始" });
		n.appendChild($d.createTextNode("してください。"));
		n = $addElement(f, "fieldset", { "class" : cols[target] });
		n.appendChild($d.createElement("legend")).appendChild($d.createTextNode("検索条件"));
		n = n.appendChild($d.createElement("label"));
		n = $addElement(n, "select", { name : "TARGET", id : "TARGET" });
		for(i = 0; i < cols.length; i++)
			$addElement(n, "option", { value : cols[i] }).appendChild($d.createTextNode(colsJP[i]));
		n.childNodes[target].selected = true;
		n = n.parentNode;
		n.appendChild($d.createTextNode("が"));
		n = n.parentNode;
		n.appendChild(this.createExpInput(cols[target]));
		n = n.appendChild($d.createElement("label"));
		n.appendChild($d.createTextNode("ソフトを"));
		$addElement(n, "input", { type : "submit", value : "検索" });
		return f;
	},
	
	// 条件指定フィールドの内部パーツ生成
	createExpInput : function(type) {
		var df = $d.createDocumentFragment(), n;
		if(/title|maker|hardware/.test(type)) {
			n = $addElement(df, "label", { title : "スペースをはさむと複数のキーワード扱いになります" });
			n.appendChild($d.createTextNode("文字列"));
			$addElement(n, "input", { type : "text", size : 5, name : type + "Keyword", value : "" });
			n.appendChild($d.createTextNode("の"));
			df.appendChild($d.createTextNode("("));
			n = df.appendChild($d.createElement("label"));
			n.appendChild($d.createTextNode("どれか"));
			$addElement(n, "input", { type : "radio", name : type + "And", value : 0, checked : "checked" });
			df.appendChild($d.createTextNode("|"));
			n = df.appendChild($d.createElement("label"));
			n.appendChild($d.createTextNode("すべて"));
			n = $addElement(n, "input", { type : "radio", name : type + "And", value : 1 });
			if(type != "title") n.disabled = true;
			df.appendChild($d.createTextNode(")を含む"));
		} else if(type == "price") {
			n = df.appendChild($d.createElement("label"));
			n.appendChild($d.createTextNode("最低"));
			$addElement(n, "input", { type : "text", size : 5, maxLength : 5, name : "minPrice", value : this.inexpensive });
			n.appendChild($d.createTextNode("円"));
			df.appendChild($d.createTextNode("から"));
			n = df.appendChild($d.createElement("label"));
			n.appendChild($d.createTextNode("最高"));
			$addElement(n, "input", { type : "text", size : 5, maxLength : 5, name : "maxPrice", value : this.expensive });
			n.appendChild($d.createTextNode("円"));
			df.appendChild($d.createTextNode("までの"));
		} else if(type == "release") {
			var s, i, df2;
			n = df.appendChild($d.createElement("label"));
			s = $addElement(n, "select", { name : "startY" });
			n.appendChild($d.createTextNode("年"));
			for(i = 1983; i < 2005; i++)
				$addElement(s, "option", { value : i }).appendChild($d.createTextNode(i));
			s.firstChild.selected = true;
			n = df.appendChild($d.createElement("label"));
			s = $addElement(n, "select", { name : "startM" });
			n.appendChild($d.createTextNode("月"));
			for(i = 1; i < 13; i++)
				$addElement(s, "option", { value : i < 10 ? "0"+i : i }).appendChild($d.createTextNode(i));
			s.firstChild.selected = true;
			n = df.appendChild($d.createElement("label"));
			s = $addElement(n, "select", { name : "startD" });
			for(i = 1; i < 32; i++)
				$addElement(s, "option", { value : i < 10 ? "0"+i : i }).appendChild($d.createTextNode(i));
			s.firstChild.selected = true;
			df2 = df.cloneNode(true);
			for(n = df2.firstChild; n; n = n.nextSibling) if(n.nodeName == "LABEL") {
				s = n.firstChild;
				s.setAttribute("name", s.getAttribute("name").replace("start", "end"));
				s.firstChild.selected = false;
				s.lastChild.selected = true;
			}
			df.appendChild($d.createTextNode("から"));
			df.appendChild(df2);
			df.appendChild($d.createTextNode("までの"));
		}
		return df;
	},
	// 検索結果表の外枠生成
	createResultTable : function() {
		var cols, colsJP, i, table, row, cell;
		cols = this.colNames;
		colsJP = this.colNamesJP;
		table = $d.createElement("table");
		table.createCaption().appendChild($d.createTextNode("検索結果"));
		for(i = 0; i < cols.length; i++)
			table.appendChild($d.createElement("colgroup")).className = cols[i];
		row = table.createTHead().insertRow(0);
		for(i = 0; i < cols.length; i++) {
			cell = row.appendChild($d.createElement("th"));
			cell.setAttribute("scope", "col");
			cell.appendChild($d.createTextNode(colsJP[i]));
		}
		table.appendChild($d.createElement("tbody"));
		return $d.getElementById("VIDEOGAME-SEARCH").appendChild(table);
	}
};

function TextExp(keywordStr, useAnd) {
	var i, tmp;
	if(!/\S/.test(keywordStr)) return new Boolean;
	keywordStr = keywordStr.split(/\s+/);
	for(i =0; i < keywordStr.length; i++) if(!/\S/.test(keywordStr[i])) {
		tmp = i++;
		while(i < keywordStr.length) keywordStr[i-1] = keywordStr[i++];
		keywordStr.pop();
		i = tmp;
	}
	if(!keywordStr.length) return new Boolean;
	try {
		if(keywordStr.length == 1 || !useAnd)
			return new RegExp("(?:"+keywordStr.join("|")+")");
		for(var i = 0; i < keywordStr.length; i++)
			this[i] = new RegExp(keywordStr[i]);
	} catch(err) {
		return new Boolean;
	}
	return this;
}
TextExp.prototype = {
	test : function(str) { for(var i=0; i in this; i++) if(!this[i].test(str)) return false; return true; }
};

function NumberExp(a,b,c,d,e,f) {
	var min, max;
	if(arguments.length == 2) {
		min = parseInt(a);
		max = parseInt(b);
		this.test = this.testPRC;
	} else {
		min = parseInt([a, b, c].join(""));
		max = parseInt([d, e, f].join(""));
		this.test = this.testYMD;
	}
	if(isNaN(min) || isNaN(max) || max < min) return new Boolean;
	this.min = min;
	this.max = max;
	return this;
}
NumberExp.prototype = {
	testPRC : function(n) { n = parseInt(n); return !isNaN(n) && this.min <= n && n <= this.max; },
	testYMD : function(n) { n = parseInt(n.replace(/-/g,"")); return !isNaN(n) && this.min <= n && n <= this.max; }
};

/* 以下、イベントハンドラ */
// 条件指定フィールドの更新
function replaceExpField() {
	var ancestor, target, pNode, node;
	ancestor = $d.getElementById("VIDEOGAME-SEARCH");
	// 状態表示初期化
	pNode = ancestor.firstChild; // == search-messege
	while(pNode.hasChildNodes()) 
		pNode.removeChild(pNode.lastChild);
	pNode.appendChild($d.createTextNode("条件を指定して"));
	$addElement(pNode, "input", { type : "submit", value : "検索を開始" });
	pNode.appendChild($d.createTextNode("してください。"));
	// 検索結果の消去
	while(ancestor.lastChild.nodeName != "FIELDSET")
		ancestor.removeChild(ancestor.lastChild);
	pNode = target = ancestor.TARGET;
	ancestor = ancestor.lastChild; // == FIELDSET
	ancestor.className = target.value;
	while(pNode.parentNode != ancestor)
		pNode = pNode.parentNode;
	// 条件指定の内容を更新
	for(node = pNode.nextSibling; node != ancestor.lastChild; node = pNode.nextSibling)
		ancestor.removeChild(node);
	ancestor.insertBefore(searchView.createExpInput(target.value), node);
}


// SubmitEventからメソッドに接続
function executeSearchCommand(e) {
	var form, node, r;
	try { e.preventDefault(); } catch(err) { event.returnValue = false; }
	form = $d.getElementById("VIDEOGAME-SEARCH");
	// 検索中は、停止ボタンだけ受付
	if(/stop|exec/.test(form.className)) {
		if(form.className == "exec" && form.stop)
			form.className = "stop";
		return;
	}
	r = searchView.createExpression(form);
	if(r == null) return;
	r = videogameSearch.exec(r);
	if(r == null) return;
	// 既存の検索結果は消去
	for(node = form.lastChild; node.nodeName != "FIELDSET"; node = form.lastChild)
		form.removeChild(node);
	// 状態表示の更新
	node = form.firstChild;
	while(node.hasChildNodes())
		node.removeChild(node.lastChild);
	// 結果が0 or ALLの場合は終了
	if(!r || videogameSearch.dataString.length == r) {
		node.appendChild($d.createTextNode(r
			? "下記の条件は全てのソフトに該当しています。"
			: "下記の条件に該当するソフトは見つかりませんでした。"
		));
		return;
	}
	node.appendChild($d.createTextNode("下記の条件に"));
	node.appendChild($d.createElement("strong")).appendChild($d.createTextNode(r));
	node.appendChild($d.createTextNode("件が該当しました。"));
	node.appendChild($d.createTextNode("結果を描画中"));
	// 検索条件フィールドを無効化
	node = form.elements;
	for(count = 0; count < node.length; count++)
		if(/INPUT|TEXTREA|SELECT|BUTTON/.test(node[count].nodeName))
			node[count].disabled = true;
	// 検索条件フィールドに停止ボタンを追加
	node = form.lastChild;
	node.appendChild($d.createTextNode(" / "));
	$addElement(node, "input", { type : "submit", id : "stop", name : "stop", value : "停止" });
	node = form.appendChild(searchView.createResultTable());
	// 100件以上だったら事前にResult用のAnchorを生成しておく
	if(100 < r) {
		node.setAttribute("id", "RESULT1");
		node.caption.lastChild.data += "(1～100件)";
		node = form.insertBefore($d.createElement("ol"), node);
		node.className = "short-item";
		var df = $d.createDocumentFragment(), i, n;
		for(i = 1, n; i < r; i += 100) {
			n = df.appendChild($d.createElement("li"));
			n.className = "wait";
			n = n.appendChild($d.createElement("a"));
			n.appendChild($d.createTextNode(i + "～" + Math.min(i+99, r) + "件"));
		}
		n = df.firstChild;
		n.className = "draw";
		n.id = "draw";
		n.firstChild.setAttribute("href", "#RESULT1");
		node.appendChild(df);
	}
	form.className = "exec";
	form.stop.focus();
	videogameSearch.drawCount = 0;
	updateSearchResult();
}


function updateSearchResult() {
	var count, data, input, form, df, table, node, i, ii;
	count = videogameSearch.drawCount;
	data = videogameSearch.dataString;
	input = videogameSearch.input;
	form = $d.getElementById("VIDEOGAME-SEARCH");
	df = $d.createDocumentFragment();
	// tableに20行、結果を書き足す
	i = count;
	for(count = videogameSearch.drawCount = Math.min(count+20, input.length); i < count; i++) {
		node = df.appendChild($d.createElement("tr"));
		line = data[input[i]].split(videogameSearch.colSeparator);
		for(ii = 0; ii < line.length; ii++)
			node.appendChild($d.createElement("td")).appendChild($d.createTextNode(line[ii]));
	}
	table = form.lastChild.lastChild; // == form > table > tBodies[tBodies.length-1]
	table.appendChild(df);
	// 全ての結果が描画されたか、ストップボタンが押された
	if(count == input.length || form.className == "stop") {
		// 100以上 == Anchorがある
		if(100 < count) {
			node = form.firstChild;
			while(node.nodeName != "OL")
				node = node.nextSibling;
			node.childNodes[Math.floor(count / 100)].className = null; // OL->LI
		}
		node = form.stop.parentNode;
		node.removeChild(node.lastChild); // == 停止ボタン
		node.removeChild(node.lastChild); // == " / "
		// disabledの解除
		node = form.elements;
		for(i = 0; i < node.length; i++)
			if(/INPUT|TEXTREA|SELECT|BUTTON/i.test(node[i].nodeName))
				node[i].disabled = false;
		// 状態表示更新
		node = form.firstChild;
		if(count == input.length)
			node.removeChild(node.lastChild);
		else
			node.lastChild.data = "(うち".concat(count, "件描画済み)");
		form.className = null;
		form.TARGET.onchange = replaceExpField;
		return;
	}
	
	// 100の倍数回に達していたら、tableの分割をする(描画処理速度の向上)
	if(!(count % 100)) {
		i = count / 100;
		table = form.appendChild(table.parentNode.cloneNode(true));
		table.setAttribute("id", "RESULT" + (i+1));
		table.caption.lastChild.data =
			"検索結果(".concat(count+1, "～", Math.min(input.length, count+100),"件)");
		table.replaceChild($d.createElement("tbody"), table.lastChild);
		// 描画中のAnchorを取得
		node = form.firstChild;
		while(node.nodeName != "OL")
			node = node.nextSibling;
		node = node.childNodes[i-1]; // OL->LI
		node.className = null;
		node = node.nextSibling;
		node.className = "draw";
		node.firstChild.setAttribute("href", "#RESULT" + (i+1));
		form.firstChild.lastChild.data = "結果を描画中";
	}
	// 20の倍数回では、進捗状況をピリオド数で表現
	else form.firstChild.lastChild.data += ".";
	// 再度タイマーをかける
	setTimeout(updateSearchResult, 100);
}

// XMLHttpRequest生成
try {
	req = new XMLHttpRequest;
	// IE7, OPERAも生成はできるがaddEventListenerが通らない
	req.addEventListener("load", function() {
		videogameSearch.setData(req.responseText);
		req.removeEventListener("load", arguments.callee, false);
		req = null;
	}, false);
} catch(err) {
	if(!req) {
		// XMLHttpRequest自体生成できていない
		try {
			// IEのActiveXObjectで二段階チェック
			req = new ActiveXObject("Msxml2.XMLHTTP");
		} catch(err) {
			try {
				req = new ActiveXObject("Microsoft.XMLHTTP");
			} catch(err) {
				return;
			}
		}
	}
	req.onreadystatechange = function() {
		if(req.readyState != 4) return;
		videogameSearch.setData(req.responseText);
		req.onreadystatechange = null;
		req = null;
	};
}
req.open("GET", videogameSearch.dataUrl, true);
req.send("");

})();