//--------------------------------------------------------------------------------
//なるべく高速に検索するためのインデックスクラス
//今のところ、先頭文字（keyword.charAt(0)） による枝打ちを行う
//--------------------------------------------------------------------------------
var SearchIndex = Class.create();
Object.extend(SearchIndex.prototype, {
initialize:function(){
	this.index = $H();
	this.id_hash = $H();
}
,insert:function(keyword, id){
	var cluster = keyword.charAt(0).toLowerCase();
	if( !this.index[cluster] ){ this.index[cluster] = []; }
	this.index[cluster].push(id);
	this.id_hash[id] = keyword;
}
,_candidates:function(keyword){
	var cluster = keyword.charAt(0).toLowerCase();
	return ( keyword && this.index[cluster] ) ? this.index[cluster] : [];
}
,match_id:function(keyword){
	var me = this;
	keyword = keyword.replace(/,/g, "，").replace(/\//g, "／");
	return me._candidates(keyword).findAll( function(value, index){ return me.get(value) == keyword; } );
}
,head_id:function(keyword){
	var me = this;
	keyword = keyword.toLowerCase();
	keyword = keyword.replace(/,/g, "，").replace(/\//g, "／");
	return me._candidates(keyword).findAll( function(value, index){ return me.get(value).toLowerCase().indexOf(keyword) == 0; } );
}
,head:function(keyword){
	var me = this;
	var hash = $H();
	me.head_id(keyword).map( function(value, index){ hash[me.get(value)] = 1; } );
	return hash.keys();
}
,get:function(id){
	return this.id_hash[id];
}
,all:function(){
	return this.id_hash.keys();
}
});

//--------------------------------------------------------------------------------
//選択されている（ユーザが入力済みの）キーワードを管理するクラス
//--------------------------------------------------------------------------------
var SelectedKeywords = Class.create();
Object.extend(SelectedKeywords.prototype, {
initialize:function(search_index, options){
	this.hash = $H();
	this.search_index = search_index;
	this.options = Object.extend({
		class_selected : "suggest_keyword_selected",
		class_unselected : "suggest_keyword"
	}, options || {} );
}
,set_source:function(input_list){
	this.input_list = input_list;
}
//指定したキーワードを選択状態にする。
,doselect:function(keyword){
	var me = this;
	if(!keyword) return;
	if(!me.hash[keyword]) me.hash[keyword] = $H();

	var match = me.search_index.match_id(keyword);
	match.each( function(value, index) { me.hash[keyword][value] = 1; } );
	match.each(me.iterator_select.bind(me));
}
//指定したキーワードを選択解除する。
,unselect:function(keyword){
	if(!keyword) return;

	delete this.hash[keyword];
	var match = this.search_index.match_id(keyword);
	match.each(this.iterator_unselect.bind(this));
}
,update:function(){
	var me = this;

	//新しいのを増やす
	me.input_list.each( function(value, index){
		me.doselect(get_input(value));
	});

	//古いのを消す
	me.hash.keys().each( function(keyword, index){
		if( !me.selected(keyword) ){
			me.unselect(keyword);
		}
	});
}
,iterator_select:function(value, index){
	var me = this;
	var target = $(value);
	if(!target){ return; }

	if(target.className==me.options.class_selected) return;
	target.className = me.options.class_selected;
}
,iterator_unselect:function(value, index){
	var me = this;
	target = $(value);
	if(!target){ return; }

	if(target.className==me.options.class_unselected) return;
	target.className = me.options.class_unselected;
}
,add:function(keyword){
	var me = this;
	var already = false;
	var added;
	var addCallBack = function(val, key){
		if(already) return;
		var input_value = get_input(val);

		if(input_value==keyword){
			already = true;
		}else if(input_value==""){
			already = true;
			set_input(val, keyword);
			added = val;
		}
	};

	this.input_list.each(addCallBack);
	this.update();
	return added;
}
,remove:function(keyword){
	var me = this;
	this.input_list.each( function(value, index){
		if(get_input(value)==keyword) set_input(value, "");
	});
	this.update();
}
//keywordが入力されているかどうか調べる
,selected:function(keyword){
	var me = this;
	var value_list = this.input_list.map( function(value, index){ return get_input(value); } );
	return value_list.include(keyword);
}
,build:function(){
	var me = this;
	var all_ids = this.search_index.all();
	all_ids.nb_each( function(value, index){
		var keyword = me.search_index.get(value);
		( me.selected(keyword) ) ? me.doselect(keyword) : me.unselect(keyword);
	} );
}
});

//--------------------------------------------------------------------------------
//キーワードサジェストクラス
//--------------------------------------------------------------------------------

var KeywordSuggest = Class.create();
Object.extend(KeywordSuggest.prototype, {
initialize: function(options){
	this.search_index = new SearchIndex();
	this.selected_keywords = new SelectedKeywords(this.search_index, options);
	this.options = Object.extend({
		suggest_area : "div_suggest_list"
		,class_selected : "suggest_keyword_selected"
		,class_unselected : "suggest_keyword"
	}, options || {});
}
,set_input_list: function(input_list){
	this.input_list = input_list;
}
,create_tag_list: function(source, target, title, handler){
	var me = this;
	if (!source || source.length==0) return;

	target = $(target);
	var id_prefix = target.id;
	me.selected_keywords.set_source(me.input_list);

	if( title ) target.appendChild(cr("h5", {html:title}));
	var loader = cr("div", {html:'<img src="/static/image/ajax-loader.gif" style="margin: 10px; vertical-align:middle;">'});
	target.appendChild(loader);

	var callback = function(){
		target.removeChild(loader); 
		target.appendChild(p); 
		me.selected_keywords.update();
	}

	var p = cr("p", {className:"kw_list", html:"&nbsp;"});
	var cl = me.selected_keywords.options.class_unselected;
	source.nb_each(function(value, index){
		var keyword_id = id_prefix+index;
		me.search_index.insert(value, keyword_id);
		var span = cr("span", {className:cl, id:keyword_id, html:value.escapeHTML()});
		var h = (handler) ? handler(value) : me.mousedown_normal(value).bind(span);
		Event.observe(span, 'mousedown', h);
		p.appendChild(span);
		if(source.length-1 != index){ p.appendChild( document.createTextNode(" /\n") ); }
	}, { callback: callback } );

}
,mousedown_normal: function(keyword){
	var me = this;
	return function(){
		var sk = me.selected_keywords;
		if( this.className == me.options.class_selected){
			this.className = me.options.class_unselected;
			sk.remove(keyword);
		}else if( sk.add(keyword) ){
			this.className = me.options.class_selected;
		}
	};
}
,mousedown_suggest: function(target, keyword){
	var me = this;
	return function(){
		set_input(target, keyword);
		$(me.options.suggest_area).innerHTML = "";
		me.selected_keywords.update();
	};
}
,auto_complete_handler: function(target){
	var me = this;
	return function(){
		var sa = $(me.options.suggest_area);
		sa.innerHTML = "";
		if(target.value=="") return;

		var current = get_input(target);
		var suggest_list = me.search_index.head(current);

		var handler = me.mousedown_suggest.curry(target).bind(me);
		me.create_tag_list(suggest_list, sa, "", handler);

		var class_name = me.options.class_unselected;
		var id_prefix = me.options.suggest_area;
		suggest_list.each( function(val, key){ $(id_prefix+key).className = class_name; } );
	};
}
,init_suggest: function(){
	var me = this;
	function set_handler(val, key){
		var complete = me.auto_complete_handler(val);
		Event.observe(val, 'keyup', complete);
		Event.observe(val, 'focus', function(){
			me.selected_keywords.update();
			complete();
		});
	}
	me.input_list.each(set_handler);
	me.selected_keywords.build();
}
});

function get_input(obj){
	return $F(obj).replace(/,/g, "，").replace(/\//g, "／");
}
function set_input(obj, value){
	$(obj).value = value;
}

function cr(tag, options){
	var ret = document.createElement(tag);
	if(options){
		if(options.html){ ret.innerHTML = options.html; }
		if(options.id){ ret.id = options.id; }
		if(options.className){ ret.className = options.className; }
	}
	return ret;
}

//var kw_others = [ "人生を変えたい", "子供に読ませたい", "子供と一緒に読みたい", "何回でも読みたい", "新入社員向け", "リーダー向け", "起業家向け", "経営者向け", "恋人への贈り物", "友人への贈り物", "失恋した時に読む本", "辛くてめげそうになった時に読む本", "将来を考える本", "元気になる", "笑える", "感動", "泣ける", "哀しい", "悲しい", "とほほ", "癒し系", "胸がキューン", "すがすがしい", "ほろにがい", "ワクワク", "爽快", "カッコいい", "かわいい", "憧れる", "(^ー^)", "(^-^;", "(￣ー￣)ニヤリ", "(-_☆)キラリ", "(+д+)マズー", "(´▽｀*)アハハ", "(*^ー°)／~~", "必読", "名作", "超オススメ", "イマイチ", "満足", "お役立ち", "モテたい", "キャリアアップ", "お金持ちになる", "センスアップ", "愛されたい", "痩せたい", "運気アップ", "お金が貯まる" ];


Function.prototype.curry = function () {
	var args = arguments;
	var self = this;
	return function () {
		Array.prototype.unshift.apply(arguments, args);
		return self.apply(this, arguments);
	};
};

Array.prototype.nb_each = function(iterator, options){
	var clone = Array.apply(null, this);
	var o = Object.extend( { fps:30, wait:20, callback:function(){} }, options || {} );
	var index=0;
	var execute = function(){
		var start = new Date().getTime();
		var end = start + 1000/o.fps;
		for( var now=start; now<end && clone.length>0; now = new Date().getTime() ){
			iterator(clone.shift(), index++);
		}
		if(clone.length>0){
			setTimeout(arguments.callee, o.wait);
		}else if(typeof o.callback == "function"){
			o.callback();
		}
	};
	execute();
};
