/*
 TagSuggest
*/
var TagSuggest;
var TagParser = {};
TagParser.split_tags = function(){
	var str = arguments[0];
	if(str instanceof Array){
		str = str.join(" ");
	}
	str = str.replace(/[\r\n]/g,"");
	str = str.replace(/　/g," ");
	var tags = str.match(/(\[(?:[^\[\]])*?\])|(\S+)/g);
	if(!tags) return null;
	for(var i=0;i<tags.length;i++){
		var t = tags[i];
		if(/^\[(.*)\]$/.test(t)){
			tags[i] = t.slice(1,-1);
		};
	}
	return tags;
};

(function(){
	// utils
	var KEYCODE = {
		 9 : 'tab',
		13 : 'enter',
		33 : 'pageup',
		34 : 'pagedown',
		37 : 'left',
		38 : 'up',
		39 : 'right',
		40 : 'down'
	};
	function $(el){
		return typeof el == 'string' ? document.getElementById(el) : el;
	}
	function map(a,c,t){
		for(var i=0,res=[],len=a.length;i<len;i++) res[i]=c.call(t,a[i],i,a);
		return res;
	}
	function filter(a,c,t){
		for(var i=0,res=[],len=a.length;i<len;i++) c.call(t,a[i],i,a) && res.push(a[i]);
		return res;
	}
	function bind(f,t){
		return function(){
			return f.apply(t,arguments)
		}
	}
	function toHash(array,prefix){
		var hash = {};
		var p = prefix || "";
		map(array,function(v){hash[p+v] = true});
		return hash
	}
	function addEvent(obj, evType, fn, useCapture){
		if(obj.addEventListener){
			obj.addEventListener(evType, fn, useCapture);
		}else if (obj.attachEvent){
			obj.attachEvent("on"+evType, fn);
		}
	}
	function stopEvent(e){
		 e.preventDefault ? e.preventDefault() : (e.returnValue = false);
		 e.stopPropagation ? e.stopPropagation() : (e.cancelBubble = true);
	}
	function getPosition(el){
		var top = 0, left = 0;
		do {
			top  += el.offsetTop  || 0;
			left += el.offsetLeft || 0;
			el    = el.offsetParent;
		} while (el);
		return [left,top];
	}
	function setStyle(el,obj){
		try{
			for(var key in obj){
				el.style[key] = obj[key]
			}
		}catch(e){}
	}
	// ignore case
	function startWith(str,ignore_case){
		if(ignore_case){
			str = str.toLowerCase();
			return function(tag){
				var tmp = tag.slice(0, str.length).toLowerCase();
				return str == tmp;
			}
		} else {
			return function(tag){return tag.indexOf(str) == 0}
		}
	}
	var REG_NOT_ASCII = /\s|[^\w.]/;
	function not_ascii(tag){
		return REG_NOT_ASCII.test(tag);
	}
	
	// suggest box
	function SuggestWindow(base){
		this.id = base.id;
		this.base = base;
		// init
		var div = document.createElement("div");
		div.id = "suggest_for_" + this.id;
		div.className = "tagsuggest-window";
		document.body.appendChild(div);

		setStyle(div,{
			position  : "absolute",
			fontFamily : getStyle(base.element,"font-family"),
			fontSize   : getStyle(base.element,"font-size")
		});
		function getStyle(o,s){
			// alert([o,s]);
			var res;
			try{
				if (document.defaultView && document.defaultView.getComputedStyle){
					res = document.defaultView.getComputedStyle(o, null).getPropertyValue(s);
				} else {
					if (o.currentStyle){
						var camelized = s.replace(/-([^-])/g, function(a,b){return b.toUpperCase()});
						res = o.currentStyle[camelized];
					}
				}
				return res;
			} catch(e){}
			return "";
		}
		function hilight(str){
			var b = base.hilight_string;
			if(b){
				return str.slice(0,b.length).bold() + str.slice(b.length);
				// return b.bold() + str.slice(b.length)
			} else {
				return str;
			}
		}
		function completed(str){
			return "<span class='completed'>"+str+"</span>"
		}
		this.selectedIndex = 0;
		var result_formatter = function(tag,index){
			return [
				"<li",
				" onclick='TagSuggest.click.call(this,event)'",
				" onmouseover='TagSuggest.hover.call(this,event)'",
				">",
				completed(base.left_str),
				hilight(tag),
				"</li>"
			].join("")
		};
		this.getSelectedValue = function(){
			this.list = this.base.suggest;
			return this.list[this.selectedIndex];
		};
		this.getSelectedIndex = function(){
			return this.selectedIndex
		};
		this.select = function(num){
			num = Math.max(num,0);
			var list = div.getElementsByTagName("li");
			num = Math.min(num,list.length-1);
			this.selectedIndex = num;
			map(list,function(el,i){
				el.className = (i == num) ? "selected" : ""
			});
			this.update_scroll();
		};
		this.show = function(){div.style.display = "block";div.scrollTop = 0;}
		this.hide = function(){div.style.display = "none"}
		this.scroll = function(num){div.scrollTop = num}
		this.update_scroll = function(){
			var list = div.getElementsByTagName("li");
			var sel = this.getSelectedIndex();
			if(!list[sel]) return;
			var top_offset = list[0].offsetTop;
			var list_height = list[sel].offsetHeight;
			var scroll_to = list[sel].offsetTop - top_offset;
			if(scroll_to > list_height){
				this.scroll(scroll_to - list_height + 1)
			} else {
				this.scroll(0)
			}
		};
		this.update_position = function(){
			var pos = getPosition(base.element);
			var left = 0;
			setStyle(div,{
				position  : "absolute",
				left: pos[0] + left + "px",
				top : pos[1] + base.element.offsetHeight + "px",
				width: base.element.offsetWidth - 2 + "px"
			});
		};
		this.update = function(){
			div.innerHTML = "<ul>"+map(
				base.suggest,
				result_formatter
			).join("")+"</ul>"
			this.select(0);
		};
		this.update_position();
		return this;
	};
	
	TagSuggest = function(id, list){
		TagSuggest.instanse[id] = this;
		this.id = id;
		this.element = $(id);
		this.element.setAttribute("autocomplete","off");
		this._list = list;
		this.state = false;
		this.suggest_window = new SuggestWindow(this);
		this.getInputOffset = function(){
			var value = this.element.value;
			var text = value.replace(/[\s　]/g," ");
			var o = text.lastIndexOf(" ");
			return (o == -1) ? 0 : o + 1;
		}
		this.setValue = function(str){
			var self = this;
			setTimeout(function(){self.element.value = str}, 0);
		};
		this.get_used_tags = function(){
			var raw_tag = this.element.value;
			var tags = TagParser.split_tags(raw_tag);
			return tags ? tags : [];
		};
		this.hide = function(){
			this.state = false;
			this.suggest_window.hide();
		};
		this.focus = function(from){
			var el = this.element;
			if (typeof from == 'undefined') from = el.value.length;
			var to = el.value.length;
			if(el.createTextRange){ //ie + opera
				var range = el.createTextRange();
				range.moveEnd("character", to);
				range.moveStart("character", from);
				setTimeout(function(){
					range.select()
				}, 10);
			} else if (el.setSelectionRange){ //ff
				el.select();
				el.setSelectionRange(from,to)
			} else { //safari
				el.blur();
				el.focus();
			}
		};
		this.str_to_tag = function(str){
			return /[\s　]/.test(str) ? ("["+str+"]") : str;
		};
		this.enter = function(){
			if(!this.state) return;
			var str = this.suggest_window.getSelectedValue();
			var tag = this.str_to_tag(str);
			this.append_tag(tag);
			this.suggest_window.hide();
			if(typeof this.oncomplete == "function"){
				this.oncomplete();
			}
		};
		var self = this;
		this.init_suggest = function(){
			var value = this.element.value;
			var len  = value.length;
			var from = this.getInputOffset();
			var str = value.slice(from);
			if(!str){
				this.suggest_window.hide();
				this.state = false;
				return;
			}
			this.left_str = value.slice(0,from);
			this.hilight_string = null;

			var list = this._list.concat();
			var used_tags = TagParser.split_tags(this.left_str);
			if(used_tags){
				var used_hash = toHash(used_tags,"_");
				list = filter(list,function(tag){
					return !used_hash.hasOwnProperty("_"+tag)
				});
			}

			var suggest;
			if(str.indexOf("[") == 0){
				if(str.length == 1){
					suggest = filter(list,not_ascii);
				} else {
					this.hilight_string = str.slice(1);
					suggest = filter(list,not_ascii);
					suggest = filter(suggest,startWith(str.slice(1),1));
				}
			} else {
				this.hilight_string = str;
				suggest = filter(list,startWith(str,1));
			}
			if(suggest.length){
				this.state = true;
				this.suggest = suggest;
				this.suggest_window.update_position();
				this.suggest_window.update();
				this.suggest_window.show();
			} else {
				this.suggest_window.hide();
				this.state = false;
			}
		};
		this.start = function(){
			self.old_value = this.element.value;
			self.old_tags = this.get_used_tags(this.old_value);
			self.timer = setInterval(function(){
				var value = self.element.value;
				var tags = self.get_used_tags(value);
				if(value != self.old_value){
					self.init_suggest();
					self.old_value = value;
				}
				if(tags.length != self.old_tags.length){
					if(typeof self.onchange == "function"){
						self.onchange();
						self.old_tags = tags;
					}
				}
			},100);
		};
		this.stop = function(){
			clearInterval(self.timer)
		};
		this.start();
		addEvent(this.element, "keypress", bind(keypress_listener,this));
		addEvent(this.element, "keydown",  bind(keydown_listener,this));
		addEvent(this.element, "keyup",    bind(keyup_listener,this));
	};
	TagSuggest.prototype.append_tag = function(tag){
		var value = this.element.value;
		var from = this.getInputOffset();
		var left_str = value.slice(0,from);
		this.setValue(left_str + tag + " ");
	};
	TagSuggest.prototype.remove_tag = function(tag){
		var used_tags = this.get_used_tags();
		var buf = [];
		for(var i=0;i<used_tags.length;i++){
			var str = used_tags[i];
			if(tag != str){
				buf.push(this.str_to_tag(str))
			}
		}
		this.setValue(buf.join(" ")+" ");
	}
	TagSuggest.prototype.swapTag = function(tag){
		var used_tags = this.get_used_tags();
		var used_tags_hash = {};
		for(var i=0;i<used_tags.length;i++){
			used_tags_hash[used_tags[i]] = true;
		}
		if(used_tags_hash.hasOwnProperty(tag)){
			this.remove_tag(tag);
			this.suggest_window.hide();
		} else {
			this.append_tag(this.str_to_tag(tag));
			this.suggest_window.hide();
		}
	}

	TagSuggest.instanse = {};
	TagSuggest.getInstanse = function(id){
		return TagSuggest.instanse[id];
	};
	TagSuggest.click = function(e){
		var el = this;
		var suggest_window = el.parentNode.parentNode;
		var id = suggest_window.id.replace("suggest_for_","");
		var self = TagSuggest.getInstanse(id);
		self.enter();
		self.focus();
	};
	TagSuggest.hover = function(e){
		var el = this;
		var ul = el.parentNode;
		var suggest_window = ul.parentNode;
		var id = suggest_window.id.replace("suggest_for_","");
		var self = TagSuggest.getInstanse(id);

		var list = ul.getElementsByTagName("li");
		map(list,function(li,i){
			if(el == li){
				li.className = "selected";
				self.suggest_window.selectedIndex = i;
			} else {
				li.className = "";
			}
		});
	};

	
	function key_event(e){
		if(!this.state){
			//alert("");
			this.KEY_LISTENED = false;
			return;
		}
		var s = this.suggest_window;
		var cancel = false;
		var sel = s.getSelectedIndex();
		var key = this.input_key;
		function c(s){return key==s};
		switch(true){
			case (e.shiftKey && c("tab")):
			case c("up"):
				s.select(sel - 1);break;
			case c("pageup"):
				s.select(sel - 3);break;
			case c("pagedown"):
				s.select(sel + 3);break;
			case c("down"):
				s.select(sel + 1);break;
			case c("tab") :
				if(this.suggest.length == 1){
					this.enter();
					this.focus();
				} else {
					s.select(sel + 1);
				}
				break;
			case c("enter"):
				this.enter();
				this.focus();
				break;
			case c("left") || c("right"):
		}
		// cancel && stopEvent(e);
		stopEvent(e);
	}
	function keyup_listener(e){
		this.KEY_STATE = 0;
		this.KEY_LISTENED = 0;
	}
	function keydown_listener(e){
		this.KEY_STATE = 1;
		//window.status = ["keydown",[e.keyCode,e.button, e.which]];
		if(window.opera){stopEvent(e);return false}
		var kc = e.keyCode;
		var el = e.target || e.srcElement;
		switch(KEYCODE[kc]){
			case 'tab':
			case 'enter':
			//case 'left':
			//case 'right':
			case 'pageup':
			case 'pagedown':
			case 'up':
			case 'down':
				this.input_key = KEYCODE[kc];
				this.KEY_LISTENED = true;
				key_event.apply(this,arguments);
				break;
			default: // window.status = kc;
		}
	}
	function keypress_listener(e){
		 //window.status = ["keypress",[e.keyCode,e.button, e.which]];
		this.KEY_STATE = 2;
		if(this.KEY_LISTENED){
			stopEvent(e);
			return;
		}
		// safari shift+tab
		if(e.keyCode == 25 && e.shiftKey){
			this.input_key = "tab";
			this.KEY_LISTENED = true;
			key_event.apply(this,arguments);
		}
		var kc = e.keyCode;
		var el = e.target || e.srcElement;
		switch(KEYCODE[kc]){
			case 'tab':
			case 'enter':
			//case 'left':
			//case 'right':
			case 'up':
			case 'down':
				this.input_key = KEYCODE[kc];
				this.KEY_LISTENED = true;
				key_event.apply(this,arguments);
				break;
			default: // window.status = kc;
		}
	}
})();