// compat
Array.prototype.forEach = function(callback,thisObject){
	for(var i=0,len=this.length;i<len;i++)
		callback.call(thisObject,this[i],i,this)
};
Array.prototype.map = function(callback,thisObject){
	for(var i=0,res=[],len=this.length;i<len;i++)
		res[i] = callback.call(thisObject,this[i],i,this);
	return res
};
Array.prototype.filter = function(callback,thisObject){
	for(var i=0,res=[],len=this.length;i<len;i++)
		callback.call(thisObject,this[i],i,this) && res.push(this[i]);
	return res
};
Array.prototype.indexOf = function(searchElement,fromIndex){
	var i = (fromIndex < 0) ? this.length+fromIndex : fromIndex || 0;
	for(;i<this.length;i++)
		if(searchElement === this[i]) return i;
	return -1
};
Array.prototype.lastIndexOf = function(searchElement,fromIndex){
	var max = this.length-1;
	var i = (fromIndex < 0)   ? Math.max(max+1 + fromIndex,0) :
			(fromIndex > max) ? max :
			max-(fromIndex||0) || max;
	for(;i>=0;i--)
		if(searchElement === this[i]) return i;
	return -1
};
Array.prototype.every = function(callback,thisObject){
	for(var i=0,len=this.length;i<len;i++)
		if(!callback.call(thisObject,this[i],i,this)) return false;
	return true
};
Array.prototype.some = function(callback,thisObject){
	for(var i=0,len=this.length;i<len;i++)
		if(callback.call(thisObject,this[i],i,this)) return true;
	return false
};

(function(){
// IE
if(typeof ActiveXObject == "function"){
	var empty = function(){};
	_XMLHttpRequest = function(){
		var self = this;
		var props = "readyState,responseText,responseXML,status,statusText".split(",");
		this.readyState  = 0;
		this.__request__ = new ActiveXObject("Microsoft.XMLHTTP");
		this.__request__.onreadystatechange = function(){
			for(var i=0;i<props.length;i++){
				try{
					self[props[i]] = self.__request__[props[i]]
				}catch(e){
				}
			}
			self.onreadystatechange();
			if(self.readyState == 4){
				self.onload();
				self.__request__.onreadystatechange = empty;
				self.__request__ = null;
				self.onreadystatechange = empty;
				self.onload = empty;
			}
		}
		this.onreadystatechange = empty;
		this.onload = empty;
	}
	var methods = "open,abort,send,setRequestHeader,getResponseHeader,getAllResponseHeaders".split(",");
	var make_method = function(name){
		_XMLHttpRequest.prototype[name] = function(){
			var params = new Array(arguments.length);
			for(var i=0;i<params.length;i++) params[i] = "_"+i;
			return Function(
				params.join(","),
				["return this.__request__.",name,"(",params.join(","),")"].join("")
			).apply(this,arguments);
		}
	};
	for(var i=0;i<methods.length;i++) make_method(methods[i]);
	try{ XMLHttpRequest = _XMLHttpRequest } catch(e){}
} else if(typeof XMLHttpRequest != "undefined"){
	_XMLHttpRequest = XMLHttpRequest;
}
})();


// replace callback support for safari 1.3
(function(){
  var m = navigator.userAgent.match(/(?:AppleWebKit\/)(\d+)/i);
  if(!m) return;
  if(m[1] > 417) return;
  var default_replace = String.prototype.replace;
  String.prototype.replace = function(search,replace){
	// replace is not function
	if(typeof replace != "function"){
		return default_replace.apply(this,arguments)
	}
	var str = "" + this;
	var callback = replace;
	// search string is not RegExp
	if(!(search instanceof RegExp)){
		var idx = str.indexOf(search);
		return (
			idx == -1 ? str :
			default_replace.apply(str,[search,callback(search, idx, str)])
		)
	}
	var reg = search;
	var result = [];
	var lastidx = reg.lastIndex;
	var re;
	while((re = reg.exec(str)) != null){
		var idx  = re.index;
		var args = re.concat(idx, str);
		result.push(
			str.slice(lastidx,idx),
			callback.apply(null,args).toString()
		);
		if(!reg.global){
			lastidx += RegExp.lastMatch.length;
			break
		}else{
			lastidx = reg.lastIndex;
		}
	}
	result.push(str.slice(lastidx));
	return result.join("")
  }
})();

/*
 EventDispatcher
*/
var Event = {};
Event.list = [];
//Event.sweep_queue = [];
Event.sweep = function(){
	var q = Event.list;
	for(var i=0;i<q.length;i++){
		try{
			removeEvent.apply(this,q[i]);
			q[i] = null;
		}catch(e){alert(e)}
	}
};
Event.observe = addEvent;
Event.stop = function(e){
	Event.stopAction(e);
	Event.stopEvent(e);
};
Event.stopAction = function(e){
	e.preventDefault ? e.preventDefault() : (e.returnValue = false)
};
Event.stopEvent = function(e){
	e.stopPropagation ? e.stopPropagation() : (e.cancelBubble = true)
};
window.onunload = Event.sweep;
function addEvent(obj, evType, fn, useCapture){
	Event.list.push(arguments);
	if(obj.addEventListener){
		obj.addEventListener(evType, fn, useCapture);
	}else if (obj.attachEvent){
		obj.attachEvent("on"+evType, fn);
	}
	var args = arguments;
	return function(){
		removeEvent.apply(this,args);
	}
}
function removeEvent(obj, evType, fn, useCapture){
	if (obj.addEventListener){
		obj.removeEventListener(evType, fn, useCapture);
		return true;
	}else if (obj.detachEvent){
		obj.detachEvent("on"+evType, fn);
	}
}
/*
 良く使う関数
*/

var GLOBAL = this;

// bench
function $(el){
	//return typeof el == 'string' ? document.getElementById(el) : el;
	if(typeof el == 'string'){
		return ($.cacheable[el])
			? $.cache[el] || ($.cache[el] = document.getElementById(el))
			: document.getElementById(el)
	} else {
		return el
	}
}
$.cache = {};
$.cacheable = {};

var _ = {};
$N = function (name, attr, childs) {
	var ret = document.createElement(name);
	for (k in attr) {
		if (!attr.hasOwnProperty(k)) continue;
		v = attr[k];
		(k == "class") ? (ret.className = v) :
		(k == "style") ? setStyle(ret,v) : ret.setAttribute(k, v);
	}
	var t = typeof clilds;
	(isString(childs))? ret.appendChild(document.createTextNode(childs)) :
	(isArray(childs)) ? foreach(childs,function(child){
		isString(child)
			? ret.appendChild(document.createTextNode(child))
			: ret.appendChild(child);
		})
	: null;
	return ret;
};
function $DF(){
	var df = document.createDocumentFragment();
	foreach(arguments,function(f){ df.appendChild(f) });
	return df;
}
function reserveName(name){
	var root = this;
	var ns = name.split(".");
	for(var i=0;i<ns.length;i++){
		var current = ns[i];
		if(!root[current]) root[current] = {};
		root = root[current]
	}
}
function True(){return true}
function False(){return false}
Array.from = function(array) {
	// arguments
	if(array.callee){
		return Array.prototype.slice.call(array,0)
	}
	var length = array.length;
	var result = new Array(length);
	for (var i = 0; i < length; i++)
		result[i] = array[i];
	return result;
};
Function.prototype.cut = function() {
	var func = this;
	var template = Array.from(arguments);
	return function() {
		var args = Array.from(arguments);
		return func.apply(null, template.map(function(arg) {
			return arg == _ ? args.shift() : arg;
		}));
	}
};
Function.prototype.o = function(a) {
	var f = this;
	return function() {
		return f(a.apply(null, arguments));
	}
};

/*
 Class
*/
Class = function(){return function(){return this}};
Class.Traits = {};
Class.create = function(traits){
	var f = function(){
		this.initialize.apply(this, arguments);
	};
	f.prototype.initialize = function(){};
	f.isClass = true;
	f.extend = function(other){
		extend(f.prototype,other);
		return f;
	};
	if(traits && Class.Traits[traits])
		f.extend(Class.Traits[traits]);
	return f;
};
// 他のクラスまたはオブジェクトをベースに新しいクラスを作成する
Class.base = function(base_class){
	if(base_class.isClass){
		var child = Class.create();
		child.prototype = new base_class;
		return child;
	} else {
		var base = Class();
		base.prototype = base_class;
		var child = Class.create();
		child.prototype = new base;
		return child;
	}
};
// クラスを合成
Class.merge = function(a,b){
	var c = Class.create();
	var ap = a.prototype;
	var bp = b.prototype;
	var cp = c.prototype;
	var methods = Array.concat(keys(ap),keys(bp)).uniq();
	foreach(methods,function(key){
		if(isFunction(ap[key]) && isFunction(bp[key])){
			cp[key] = function(){
				ap[key].apply(this,arguments);
				return bp[key].apply(this,arguments);
			}
		} else {
			cp[key] = 
				isFunction(ap[key]) ? ap[key] :
				isFunction(bp[key]) ? bp[key] : null
		}
	});
	return c;
}
/*
*/


/* generic */
if(!Array.concat){
	(function(){
		var methods = "concat slice shift push unshift pop sort reverse".split(" ");
		foreach(methods,function(mn){
			Array[mn] = function(){
				var args = Array.from(arguments);
				var self = args.shift();
				return Array.prototype[mn].apply(self,args);
			}
		})
	})();
}


function extend(self,other){
	for(var i in other){
		self[i] = other[i]
	}
	return self;
}
function base(obj){
	function f(){return this};
	f.prototype = obj;
	return new f;
}
function clone(obj){
	var o = {};
	for(var i in obj){
		o[i] = obj[i]
	};
	return o
}
function keys(hash){
	var tmp = [];
	for(var key in hash){
		tmp.push(key)
	}
	return tmp;
}
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 each(obj,callback){
	for(var i in obj){
		callback(obj[i],i)
	}
}
function foreach(array,callback){
	var len = array.length;
	for(var i=0;i<len;i++){
		callback(array[i],i,array)
	}
}
/* Perlのjoin、中の配列も含めて同じルールでjoinする  */
function join(){
	var args = Array.from(arguments);
	var sep = args.shift();
	var to_s = Array.prototype.toString;
	Array.prototype.toString = function(){return this.join(sep)}
	var res = args.join(sep);
	Array.prototype.toString = to_s;
	return res;
}

/*  */

function Arg(n){
	return function(){ return arguments[n] }
}
function This(){
	return function(){ return this }
}
// 関数の結果に対してsend
Function.prototype.send = function(method,args){
	var self = this;
	return function(){
		return send(self.apply(this,arguments),method,args)
	}
};
function send(self,method,args){
	if(isFunction(self[method]))
		return self[method].apply(self,args);
	else if(isFunction(self.method_missing))
		return self.method_missing(method,args)
	else
		return null
}
// methodに別名を付ける
function alias(method){
	return function(){
		return this[method].apply(this,arguments)
	}
}

/*
 transform functions
*/
function sender(method){
	var args = Array.slice(arguments,1);
	return function(self){
		var ex_args = Array.from(arguments);
		return send(self,method,args.concat(ex_args))
	}
}
// thisに対してmethodを呼び出す関数を生成する
// methodは文字列もしくは関数
/*
 invoker(arg(1),)
*/
function invoker(method){
	var args = Array.slice(arguments,1);
	if(isFunction(method)){
		return function(){
			var ex_args = Array.from(arguments);
			return method.apply(this,args.concat(ex_args))
		}
	} else {
		return function(){
			var ex_args = Array.from(arguments);
			return send(this,method,args.concat(ex_args))
		}
	}
}
/*
 push : function(item){ return this.list.push(item) }
 -> push : delegator("list","push");
*/
function delegator(key,method){
	return function(){
		var self = this[key];
		return self[method].apply(self,arguments);
	}
}

function getter(attr){
	return function(self){return self[attr]}
}

/*
 extend buildin object
*/
foreach("String,Number,Array,Function".split(","),function(c){
	var klass = GLOBAL[c];
	klass.isClass = true;
	klass.extend = function(other){
		return extend(klass.prototype,other);
	}
	klass.prototype["is"+c] = true
})
Object.extend = extend;
/*
  and more extra methods
*/

Array.prototype.min = function(cmp){return (cmp?this.sort(cmp):this.sort()).first()}
Array.prototype.max = function(cmp){return (cmp?this.sort(cmp):this.sort()).last()}
Array.prototype.compact = function(){
	return this.remove("");
}

Array.extend({
	isArray : true,
	clone : function(){ return this.concat() },
	clear : function(){ this.length = 0; return this },
	delete_at : function(pos){
		return this.splice(pos,1);
	},
	first : function(size){
		return (size == undefined) ? this[0] : this.slice(0,size)
	},
	last : function(size){
		return (size == undefined) ? this[this.length-1] : this.slice(this.length-size)
	},
	select : alias("filter"),
	remove : function(to_remove){
		return this.select(function(val){return val != to_remove ? true : false});
	},
	compact : invoke("remove",""),
	// 重複をなくす、破壊的
	uniq : function(){
		var tmp = {};
		var len = this.length;
		for(var i=0;i<this.length;i++){
			if(tmp.hasOwnProperty(this[i])){
				this.splice(i,1);
				if(this.length == i){break}
				i--;
			}else{
				tmp[this[i]] = true;
			}
		}
		return this;
	},
	// 各要素にメソッドを送る
	invoke : function(){
		var args = Array.from(arguments);
		var method = args.shift();
		return this.map(sender(method,args));
	},
	// ハッシュの配列から指定キーのvalueのみを集めた配列を返す
	pluck : function(name){
		return this.map(getter(name))
	},
	partition : function(callback,thisObj) {
		var trues = [], falses = [];
		this.forEach(function(v,i,self){
			(callback.call(thisObj,v,i,self) ? trues : falses).push(v);
		});
		return [trues, falses];
	}
});

/*
 detect
*/
function isString(obj){
	return (typeof obj == "string" || obj instanceof String) ? true : false
}
function isNumber(obj){
	return (typeof obj == "number" || obj instanceof Number) ? true : false;
}
function isElement(obj){
	return obj.nodeType ? true : false;
}
function isFunction(obj){
	return typeof obj == "function";
}
function isArray(obj){
	return obj instanceof Array;
}
function isRegExp(obj){
	return obj instanceof RegExp
}

/* Array */
Array.extend({
	collect : alias("map"),
	reduce : function(callback){
		return this.map(callback).join("")
	}
});
/* Number */
Number.extend({
	toHex : invoker("toString",16),
	zerofill : function(len){
		var n = "" + this;
		for(;n.length < len;)
			n = "0" + n;
		return n;
	},
	// 両端を含む
	between : function(a,b){
		var min = Math.min(a,b);
		var max = Math.max(a,b);
		return (this >= min && this <= max);
	}
});
/* Function */
Function.extend({
	applied : function(thisObj,args){
		var self = this;
		return function(){
			return self.apply(thisObj,args)
		}
	},
	bindThis : function(thisObj){
		var self = this;
		return function(){
			return self.apply(thisObj,arguments)
		}
	},
	bind : alias("bindThis"),
	// 引数をsliceする
	slicer : function(to,end){
		var self = this;
		return function(){
			var sliced = Array.slice(arguments,to,end);
			return self.apply(this,sliced);
		}
	}
});


function loadRaw(url){
	var req = new XMLHttpRequest;
	var res;
	req.open("GET",url,false);
	req.onload = function(){
		res = req.responseText;
	};
	req.send(null);
	return res;
}
function loadJson(url,callback){
	if(dev){
		if(!dummy[url]){return}
		var json = dummy[url].isString ? eval("("+dummy[url]+")") : dummy[url];
		callback(json);
		return;
	}
	var req = new XMLHttpRequest;
	req.open("GET",url,true);
	req.onload = function(){
		eval("var json ="+req.responseText);
		callback(json)
	};
	req.send(null)
}

/*
 className
*/
function hasClass(element,classname){
	element = $(element);
	var cl = element.className;
	var cls = cl.split(/\s+/);
	return cls.indexOf(classname) != -1;
}
function addClass(element,classname){
	element = $(element);
	var cl = element.className;
	if(!contain(cl,classname)){
		element.className += " " + classname;
	}
}
function removeClass(element,classname){
	element = $(element);
	var cl = element.className;
	var cls = cl.split(/\s+/);
	element.className = cls.remove(classname).join(" ");
}
function switchClass(element, classname){
	element = $(element);
	var cl = element.className;
	var tmp = classname.split("-");
	var ns = tmp[0];
	var cls = cl.split(/\s+/);
	var buf = [];
	cls.forEach(function(v){
		if(v.indexOf(ns+"-") != 0) buf.push(v)}
	);
	buf.push(classname);
	element.className = buf.join(" ");
}
function toggleClass(element, classname){
	element = $(element);
	hasClass(element, classname) ?
		removeClass(element, classname):
		addClass(element, classname);
}
/* 文字列が含まれているかの判別 */
function contain(self,other){
	if(isString(self) && isString(other)){
		return self.indexOf(other) != -1
	}
	if(isRegExp(other)){
		return other.test(self)
	}
}


/* Form */

var Form = {};
Form.toJson = function(form){
	var json = {};
	var len = form.elements.length;
	foreach(form.elements, function(el){
		if(!el.name) return;
		var value = Form.getValue(el);
		value && (json[el.name] = value);
	});
	return json;
};
Form.getValue = function(el){
	return (
		/text|hidden|submit/.test(el.type) ? el.value :
		el.type == "checkbox" && el.checked ? el.value :
		el.type == "radio"    && el.checked ? el.value :
		el.tagName == "SELECT" ? el.options[el.selectedIndex].value :
		null
	)
};
// formを埋める
Form.fill = function(form,json){
	form = $(form);
	foreach(form.elements, function(el){
		var name = el.name; 
		var value = json[name];
		if(!name || value == null) return;
		(/text|hidden|submit/.test(el.type)) ? 
			(el.value = value) :
		(el.type == "checkbox") ? (el.value = value, el.checked = true) :
		(el.type == "radio") ?
			(value == el.value) ? (el.checked = true) : (el.checked = false) :
		null
	})
}
Form.setValue = function(el, value){
	el.value = value;
	
}
Form.setValue.selecte = function(){
	
}


Object.extend(Form,{
	disable: function(el){
		$(el).disabled = "disabled";
	},
	enable: function(el){
		$(el).disabled = "";
	},
	disable_all: function(el){
		el = $(el);
		Form.disable(el);
		var child = el.getElementsByTagName("*");
		foreach(child, Form.disable);
	},
	enable_all: function(el){
		el = $(el);
		Form.enable(el);
		var child = el.getElementsByTagName("*");
		foreach(child, Form.enable);
	}
});


/* DateTime */
function DateTime(time){
	this._date = time ? new Date(time) : new Date;
	this._update();
	this.toString = function(){ return [this.ymd(),this.hms()].join(" ")}
	this.valueOf  = function(){ return this._date - 0 };
}
DateTime.now = function(){
	return new DateTime;
};
DateTime.prototype = {
	_update : function(){
		var dt = this._date;
		this.year  = dt.getFullYear();
		this.month = this.mon  = dt.getMonth() + 1;
		this.day   = this.mday = this.day_of_month = dt.getDate();
		this.hour  = dt.getHours();
		this.minute = this.min = dt.getMinutes();
		this.second = this.sec = dt.getSeconds();
	},
	ymd : function(sep){
		sep = arguments.length ? sep : "/";
		return [this.year,this.month,this.day].invoke("zerofill",2).join(sep)
	},
	hms : function(sep){
		sep = arguments.length ? sep : ":";
		return [this.hour,this.minute,this.second].invoke("zerofill",2).join(sep)
	}
};

/* Cache */

var Cache = Class.create();
Cache.extend({
	initialize : function(option){
		this._index = {};
		this._exprs = {};
		this._cache = [];
		if(option){
			this.max = option.max || 0;
		}
	},
	_get: function(key){
		return this._index["_" + key];
	},
	get: function(key){
		return this._get(key)[1]
	},
	set: function(key,value){
		// delete
		if(this.max && this._cache.length > this.max){
			var to_delete = this._cache.shift();
			delete this._index["_" + to_delete[0]];
		}
		// update
		if(this.has(key)){
			this._get(key)[1] = value;
		} else {
			// create
			var pair = [key,value];
			this._cache.push(pair);
			this._index["_"+key] = pair;
		}
		return value;
	},
	set_expr: function(key,expr){
		this._exprs["_" + key] = expr;
	},
	get_expr: function(key){
		return this._exprs["_" + key] || null;
	},
	check_expr: function(key){
		var expr = this.get_expr(key);
		if(expr){
			var r = new Date() - expr;
			var f = (r < 0) ? true : false;
			// if(!f) message("再読み込みします")
			return f;
		} else {
			return true;
		}
	},
	has : function(key){
		return (this._index.hasOwnProperty("_" + key) && this.check_expr(key));
	},
	clear : function(){
		this._index = {};
		this._cache  = [];
	},
	find_or_create : function(key,callback){
		return this.has(key) ? this.get(key) : this.set(key,callback())
	}
});

Number.extend({
	times: function(callback){
		var c = 0;
		for(;c<this;c++) callback(c)
	}
});

String.escapeRules = [
	[/&/g , "&amp;"],
	[/</g , "&lt;"],
	[/>/g , "&gt;"]
];
String.unescapeRules = [
	[/&lt;/g,  "<"],
	[/&gt;/g,  ">"],
	[/&amp;/g, "&"]
];
String.extend({
	strip : invoker("replace",/^\s+(.*?)\s+$/,"$1"),
	repeat : function(num){
		var buf = [];
		for(var i=0;i<num;buf[i++]=this);
		return buf.join("");
	},
	mreplace : function(rule){
		var tmp = ""+this;
		foreach(rule,function(v){
			tmp = tmp.replace(v[0],v[1])
		});
		return tmp;
	},
	escapeHTML : invoker("mreplace",String.escapeRules),
	unescapeHTML : invoker("mreplace",String.unescapeRules),
	ry : function(max,str){
		if(this.length <= max) return this;
		var tmp = this.split("");
		return Array.concat(this.slice(0,max/2),str,this.slice(-max/2)).join("")
	},
	camelize : invoker("replace",/-([a-z])/g, Arg(1).send("toUpperCase")),
	parseQuery : function () {
    		var str = this.indexOf("?", 0) > -1 ? this.split("?")[1] : this,
        	    res = {};
    		if ( !/=/.test( str ) ) return res;
    		str.match(/^\??(.*)$/)[1].split('&').forEach(function (pair) {
		    pair = pair.split("="), res[ pair[0] ] = decodeURIComponent( pair[1].replace(/\+/g, ' ') );
    		});
    		return res;
	},
	explate: function (args) {
		return this.replace(/%\{(\w+?)\}/g, function (w,g) {
		    return typeof args[g] != 'undefined'
			? args[g]
			: w;
		});         
	}
});

/* Math */
Math.rand = function(num){return Math.random() * num};



function inspect(obj){
	return keys(obj).map(function(key){
		return key +" = "+obj[key] + "\n";
	})
}

/* JSON */
var JSON = {};
JSON.parse = function(str){
	try{
		var res = eval("(" + str + ")");
	} catch(e){
		// alert(inspect(e))
		return null;
	}
	return res;
}
JSON.parse = function(str){
    try {
        return !(/[^,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]/.test(
                str.replace(/"(\\.|[^"\\])*"/g, ''))) &&
            eval('(' + str + ')');
    } catch (e) {
        return false;
    }
};

Array.toJSON = function(array){
	var i=0,len=array.length,json=[];
	for(;i<len;i++)
		json[i] = (array[i] != null) ? Object.toJSON(array[i]) : "null";
	return "[" + json.join(",") + "]";
}
Array.prototype.toJSON = function(){
	return Array.toJSON(this);
}
String.toJSON = function(string){
	return '"' +
		string.replace(/(\\|\")/g,"\\$1")
		.replace(/\n|\r|\t/g,function(){
			var a = arguments[0];
			return  (a == '\n') ? '\\n':
					(a == '\r') ? '\\r':
					(a == '\t') ? '\\t': ""
		}) + '"'
}
String.prototype.toJSON = function(){
	return String.toJSON(this)
}
Number.toJSON = function(number){
	return isFinite(number) ? String(number) : 'null'
}
Number.prototype.toJSON = function(){
	return Number.toJSON(this)
}
Boolean.prototype.toJSON = function(){return this}
Function.prototype.toJSON = function(){return this}
RegExp.prototype.toJSON = function(){return this}
// strict but slow
String.prototype.toJSON = function(){
	var tmp = this.split("");
	for(var i=0;i<tmp.length;i++){
		var c = tmp[i];
		(c >= ' ') ?
			(c == '\\') ? (tmp[i] = '\\\\'):
			(c == '"')  ? (tmp[i] = '\\"' ): 0 :
		(tmp[i] = 
			(c == '\n') ? '\\n' :
			(c == '\r') ? '\\r' :
			(c == '\t') ? '\\t' :
			(c == '\b') ? '\\b' :
			(c == '\f') ? '\\f' :
			(c = c.charCodeAt(),('\\u00' + ((c>15)?1:0)+(c%16)))
		)
	}
	return '"' + tmp.join("") + '"';
}
Object.toJSON = function(obj){
	var json = [];
	if(typeof obj == 'undefined') return "null";
	if(obj == null) return "null";
	if(typeof obj.toJSON == 'function'){
		return obj.toJSON();
	}
	for(var i in obj){
		if(!obj.hasOwnProperty(i)) continue;
		if(typeof obj[i] == "function") continue;
		json.push(
			i.toJSON()+":"+((obj[i] != null)? Object.toJSON(obj[i]) : "null")
		)
	}
	return "{" + json.join(",") + "}";
};
JSON.Parse = JSON.parse;
JSON.Dump = Object.toJSON;
JSON.Load = JSON.parse;



Object.toQuery = function(self){
	var buf = [];
	for(var key in self){
		var value = self[key];
		if(isFunction(value)) continue;
		buf.push(
			encodeURIComponent(key)+"="+
			encodeURIComponent(value)
		)
	}
	return buf.join("&");
}


/* from prototype.js */

var Element = {
  getStyle: function(element, style) {
    element = $(element);
    style = style == 'float' ? 'cssFloat' : style.camelize();
    var value = element.style[style];
    if (!value || value == 'auto') {
      var css = element.currentStyle ? element.currentStyle : document.defaultView.getComputedStyle(element, null);
      value = css ? css[style] : null;
    }
    if (style == 'opacity') return value ? parseFloat(value) : 1.0;
    return value == 'auto' ? null : value;
  }
}

var Position = {
  // set to true if needed, warning: firefox performance problems
  // NOT neeeded for page scrolling, only if draggable contained in
  // scrollable elements
  includeScrollOffsets: false,

  // must be called before calling withinIncludingScrolloffset, every time the
  // page is scrolled
  prepare: function() {
    this.deltaX =  window.pageXOffset
                || document.documentElement.scrollLeft
                || document.body.scrollLeft
                || 0;
    this.deltaY =  window.pageYOffset
                || document.documentElement.scrollTop
                || document.body.scrollTop
                || 0;
  },

  realOffset: function(element) {
    var valueT = 0, valueL = 0;
    do {
      valueT += element.scrollTop  || 0;
      valueL += element.scrollLeft || 0;
      element = element.parentNode;
    } while (element);
    return [valueL, valueT];
  },

  cumulativeOffset: function(element) {
    var valueT = 0, valueL = 0;
    do {
      valueT += element.offsetTop  || 0;
      valueL += element.offsetLeft || 0;
      element = element.offsetParent;
    } while (element);
    return [valueL, valueT];
  },

  positionedOffset: function(element) {
    var valueT = 0, valueL = 0;
    do {
      valueT += element.offsetTop  || 0;
      valueL += element.offsetLeft || 0;
      element = element.offsetParent;
      if (element) {
        p = Element.getStyle(element, 'position');
        if (p == 'relative' || p == 'absolute') break;
      }
    } while (element);
    return [valueL, valueT];
  },

  offsetParent: function(element) {
    if (element.offsetParent) return element.offsetParent;
    if (element == document.body) return element;

    while ((element = element.parentNode) && element != document.body)
      if (Element.getStyle(element, 'position') != 'static')
        return element;

    return document.body;
  },

  // caches x/y coordinate pair to use with overlap
  within: function(element, x, y) {
    if (this.includeScrollOffsets)
      return this.withinIncludingScrolloffsets(element, x, y);
    this.xcomp = x;
    this.ycomp = y;
    this.offset = this.cumulativeOffset(element);

    return (y >= this.offset[1] &&
            y <  this.offset[1] + element.offsetHeight &&
            x >= this.offset[0] &&
            x <  this.offset[0] + element.offsetWidth);
  },

  withinIncludingScrolloffsets: function(element, x, y) {
    var offsetcache = this.realOffset(element);

    this.xcomp = x + offsetcache[0] - this.deltaX;
    this.ycomp = y + offsetcache[1] - this.deltaY;
    this.offset = this.cumulativeOffset(element);

    return (this.ycomp >= this.offset[1] &&
            this.ycomp <  this.offset[1] + element.offsetHeight &&
            this.xcomp >= this.offset[0] &&
            this.xcomp <  this.offset[0] + element.offsetWidth);
  },

  // within must be called directly before
  overlap: function(mode, element) {
    if (!mode) return 0;
    if (mode == 'vertical')
      return ((this.offset[1] + element.offsetHeight) - this.ycomp) /
        element.offsetHeight;
    if (mode == 'horizontal')
      return ((this.offset[0] + element.offsetWidth) - this.xcomp) /
        element.offsetWidth;
  },

  clone: function(source, target) {
    source = $(source);
    target = $(target);
    target.style.position = 'absolute';
    var offsets = this.cumulativeOffset(source);
    target.style.top    = offsets[1] + 'px';
    target.style.left   = offsets[0] + 'px';
    target.style.width  = source.offsetWidth + 'px';
    target.style.height = source.offsetHeight + 'px';
  },

  page: function(forElement) {
    var valueT = 0, valueL = 0;

    var element = forElement;
    do {
      valueT += element.offsetTop  || 0;
      valueL += element.offsetLeft || 0;

      // Safari fix
      if (element.offsetParent==document.body)
        if (Element.getStyle(element,'position')=='absolute') break;

    } while (element = element.offsetParent);

    element = forElement;
    do {
      valueT -= element.scrollTop  || 0;
      valueL -= element.scrollLeft || 0;
    } while (element = element.parentNode);

    return [valueL, valueT];
  },

  clone: function(source, target) {
    var options = Object.extend({
      setLeft:    true,
      setTop:     true,
      setWidth:   true,
      setHeight:  true,
      offsetTop:  0,
      offsetLeft: 0
    }, arguments[2] || {})

    // find page position of source
    source = $(source);
    var p = Position.page(source);

    // find coordinate system to use
    target = $(target);
    var delta = [0, 0];
    var parent = null;
    // delta [0,0] will do fine with position: fixed elements,
    // position:absolute needs offsetParent deltas
    if (Element.getStyle(target,'position') == 'absolute') {
      parent = Position.offsetParent(target);
      delta = Position.page(parent);
    }

    // correct by body offsets (fixes Safari)
    if (parent == document.body) {
      delta[0] -= document.body.offsetLeft;
      delta[1] -= document.body.offsetTop;
    }

    // set position
    if(options.setLeft)   target.style.left  = (p[0] - delta[0] + options.offsetLeft) + 'px';
    if(options.setTop)    target.style.top   = (p[1] - delta[1] + options.offsetTop) + 'px';
    if(options.setWidth)  target.style.width = source.offsetWidth + 'px';
    if(options.setHeight) target.style.height = source.offsetHeight + 'px';
  },

  absolutize: function(element) {
    element = $(element);
    if (element.style.position == 'absolute') return;
    Position.prepare();

    var offsets = Position.positionedOffset(element);
    var top     = offsets[1];
    var left    = offsets[0];
    var width   = element.clientWidth;
    var height  = element.clientHeight;

    element._originalLeft   = left - parseFloat(element.style.left  || 0);
    element._originalTop    = top  - parseFloat(element.style.top || 0);
    element._originalWidth  = element.style.width;
    element._originalHeight = element.style.height;

    element.style.position = 'absolute';
    element.style.top    = top + 'px';;
    element.style.left   = left + 'px';;
    element.style.width  = width + 'px';;
    element.style.height = height + 'px';;
  },

  relativize: function(element) {
    element = $(element);
    if (element.style.position == 'relative') return;
    Position.prepare();

    element.style.position = 'relative';
    var top  = parseFloat(element.style.top  || 0) - (element._originalTop || 0);
    var left = parseFloat(element.style.left || 0) - (element._originalLeft || 0);

    element.style.top    = top + 'px';
    element.style.left   = left + 'px';
    element.style.height = element._originalHeight;
    element.style.width  = element._originalWidth;
  }
}

// Safari returns margins on body which is incorrect if the child is absolutely
// positioned.  For performance reasons, redefine Position.cumulativeOffset for
// KHTML/WebKit only.
if (/Konqueror|Safari|KHTML/.test(navigator.userAgent)) {
  Position.cumulativeOffset = function(element) {
    var valueT = 0, valueL = 0;
    do {
      valueT += element.offsetTop  || 0;
      valueL += element.offsetLeft || 0;
      if (element.offsetParent == document.body)
        if (Element.getStyle(element, 'position') == 'absolute') break;

      element = element.offsetParent;
    } while (element);

    return [valueL, valueT];
  }
}

/* CSS */
function parseCSS(text){
	var pairs = text.split(";");
	var res = {};
	pairs.forEach(function(pair){
		var tmp = pair.split(":");
		res[tmp[0].strip()] = tmp[1];
	});
	return res;
}
// cssセット、透明度、floatの互換性を取る
function setStyle(element,style){
	element = $(element);
	var es = element.style;
	if(isString(style)){
		es.cssText ? (es.cssText = style) : setStyle(element,parseCSS(style));
	} else {
		// objectの場合
		each(style,function(value,key){
			if(setStyle.hack.hasOwnProperty(key)){
				var tmp = setStyle.hack[key](key,value);
				key = tmp[0],value = tmp[1]
			}
			element.style[key.camelize()] = value
		});
	}
}
setStyle.hack = {
	opacity : function(key,value){
		return (
			(/MSIE/.test(navigator.userAgent))
				? ["filter" , 'alpha(opacity='+value*100+')']
				: [ key , value]
		)
	}
}


Object.extend(Function.prototype,{
	/* TIMERS */
	_timeouts : [],
	_interals : [],
	do_later  : function(ms){
		this._intervals.push(setTimeout(this,ms));
		return this;
	},
	bg : function(ms){
		this._intervals.push(setInterval(this,ms));
		return this;
	},
	kill : function(){
		this._timeouts.forEach(function(pid){
			clearTimeout(pid)
		});
		this._intervals.forEach(function(pid){
			clearInterval(pid)
		})
	},
	/* TASK */
	observers : [],
	complete  : function(result){
		this.result_code = result;
		this.observers.invoke("update", this)
	},
	_changed  : false,
	changed   : function(state){
		return arguments.length ? (this._changed = state) : this._changed;
	},
	add_observer : function(observer){
		this.observers.push(observer)
		return this;
	}
})
function Task(tasks,callback){
	var self = this;
	this.tasks = tasks;
	tasks.map(function(v){
		return isFunction(v) ? v : send(v,"toTask")
	}).invoke("add_observer", this);
	if(callback){
		this.oncomplete = callback;
		this.exec();
	}
	return this;
}
Task.prototype = {
	isTask : true,
	exec : function(){
		this.tasks.invoke("call",null);
	},
	update : function(f){
		send(this,"onprogress");
		
	}
}

Array.prototype.toTask = function(){
	return new Task(this);
}

/*
 super 
*/
/*
 parent
  this.parent.update();
  this.parent.parent.update();
   -> parent(this,"update")
*/
/* 
function parent(obj, method, args){
	var p = obj.parent;
	while(p){
		send(p,method,args);
		p = obj.parent;
	}
}
*/

/*
 invoke 別のクラスに処理を伝播させる
  invoke(this, "method_name", arguments);
*/
function invoke(obj, method, args){
	var o = obj.parent;
	for (;typeof(o) != 'undefined'; o = o.parent) {
		if (typeof(o[method]) == 'function') {
			return o[method].apply(obj, args);
		}
	}
	return false;
}
/*
 next たらいまわしにする。
  this.parent.childs
   next(this, "initialize");
  
*/
function next(obj, method, args){
	obj.parent.childs.invoke(method,args)
}

function childOf(element, ancestor){
	element = $(element), ancestor = $(ancestor);
    while (element = element.parentNode)
      if (element == ancestor) return true;
    return false;
}
function $ref(element){
}
String.prototype.op = function(){
	return new Function("a,b","return a"+this+"b");
}


// regexp marger
// 正規表現を複数行にわたって記述できるようにします。

RegExp.prototype.get_body = function(){
	var str = this.toString();
	return str.slice(1,str.lastIndexOf("/"));
};
RegExp.prototype.get_flags = function(){
		return [
			this.ignoreCase ? "i":"",
			this.global     ? "g":"",
			this.multiline  ? "m":""
		].join("");
};
RegExp.concat = function(){
	var args = Array.from(arguments);
	var buf = [];
	args.forEach(function(reg){
		buf.push(reg.get_body());
	});
	var flag = args[args.length-1].get_flags();
	return new RegExp(buf.join(""), flag);
}

Array.prototype.flatten = function(){
	var res = [];
	this.forEach(function(a){
		if(isArray(a))
			a = a.flatten();
		res = res.concat(a)
	});
	return res;
};
Function.prototype.forEachArgs = function(){
	var f = this;
	return function(){
		var target = Array.from(arguments).flatten();
		if(!target.length) return;
		target.forEach(function(v){
			f(v)
		})
	}
};

/*
 Element Updater
*/
function MakeUpdater(label){
	var hash = {};
	var updater = (label?label+"_":"") + "updater";
	var update  = (label?label+"_":"") + "update";
	function get_func(label){
		return(
			isFunction(hash["_"+label])
			 ? hash["_" + label]
			 : function(){}
		);
	}
	var u = GLOBAL[update] = function(label){
		if(isRegExp(label)){
			keys(hash).filter(function(l){
				l = l.slice(1);
				return label.test(l)
			}).forEach(function(label){
				label = label.slice(1);
				get_func(label).call($(label));
			})
		} else {
			return get_func(label).call($(label));
		}
	}.forEachArgs();
	GLOBAL[updater] = function(label, callback){
		if(callback){
			hash["_"+label] = callback;
		} else {
			return function(){ u(label) }
		}
	};
}
MakeUpdater();


/*
 clip
*/
var browser = new BrowserDetect;
function BrowserDetect(){
	var ua = navigator.userAgent;
	if(ua.indexOf( "KHTML" ) > -1) this.isKHTML = true;
	if(ua.indexOf( "Macintosh" ) > -1) this.isMac   = true;
	if(ua.indexOf( "Gecko" ) > -1 && !this.isKHTML) this.isGecko = true;
	if(ua.indexOf( "Firefox" ) > -1) this.isFirefox = true;
	if(window.opera){
		this.isOpera = true;
	} else if(ua.indexOf( "MSIE" ) > -1){
		this.isIE = true;
	}
}
// idを抜き出す
function get_id(id){
	if(isElement(id)){ id = id.id }
	var l = id.lastIndexOf("_");
	return id.slice(l+1);
}
function get_link(id){
	return $("link_"+id).getAttribute("href");
}
function get_hash(id){
	var hash = $("hash_"+id).getAttribute("name");
	if(hash.indexOf("_") != -1){
		return hash.split("_")[1]
	} else {
		return hash
	}
}

function str2tag(str){
	return /[\s　]/.test(str) ? ("["+str+"]") : str;
}
function create_form(params){
	var f = document.createElement("form");
	for(var key in params){
	
	}
}
function show(id){$(id).style.display="block"}
function hide(id){$(id).style.display="none"}
var State = {};
var clips = [];
var stash = {};
function ClipEdit(id){
	this.id = id;
	this.edit_mode = true;
	this.body  = $("body_"+id);
	this.notes = $("notes_"+id);
	this.title = $("link_"+id);
//	this.notes_html = this.notes.innerHTML;
//	this.notes_value = this.notes_html;
//	this.notes_value = "loading...";
	this.notes_value = "";
	this.title_value = this.title.firstChild.nodeValue;
	this.tags = $("tags_"+id);
	if(this.tags){
		this.tags_value = map(this.tags.getElementsByTagName("a"), 
			function(a){return a.firstChild ? a.firstChild.nodeValue : ""}
		).map(str2tag).join(" ") + " ";
	} else {
		this.tags_value = "";
	}
}
ClipEdit.prototype = {
	open: function(){
		try{
		var id = this.id;
		var is_private = hasClass("clip_block_"+id,"clip-private");
		// $("edit_link").value = get_link(id);
		$("edit_link").value = get_hash(id);
		$("jump_link").value = location.href;
		$("edit_tags").value = this.tags_value;
		$("edit_notes").value = this.notes_value.unescapeHTML();
		$("edit_my_title").value = this.title_value;
		$("public").value = is_private ? "off" : "on";
		this.form = $("edit_form");

		(new API("/internal_api/get_notes")).post({
			hash : get_hash(id)
		}, function (res) {
			if ( res.isSuccess == 1 ) {
				$("edit_notes").value = res.contents.unescapeHTML();
			}
		});

		/*hide(this.notes);*/
		show(this.form);
		addClass(this.body,"clip-editmode");
		this.body.appendChild(this.form);
		$("edit_public").checked = is_private ? true : false;
		}catch(e){}
	},
	get_tags: function(){
	
	},
	update_tags: function(){
	
	},
	save: function(){
		this.form.submit();
		this.form.style.display = "none";
		this.notes.innerHTML = this.notes_form.value.escapeHTML();
		this.tags.innerHTML = "";
	},
	cancel: function(){
		hide(this.form);
		// show(this.notes);
		removeClass(this.body,"clip-editmode");
	}
}

function close_edit(e){
	this.edit_mode = false;
	var id = get_id(this.id);
	var c = clips[id];
	if(!c) return;
	c.cancel();
	var el = $("editlink_"+id);
	el.innerHTML = "編集"
	$("fullscreen_edit").style.display = "none";
	suggest.hide();
}
function open_edit(e){
	this.edit_mode = true;
	var id = get_id(this.id);
	var el = $("editlink_"+id);
	var clip = $("clip_"+id);
	if(clips[id]){
		clips[id].open();
	} else {
		clips[id] = new ClipEdit(id);
		clips[id].open();
	}
	el.innerHTML = "キャンセル";
	var fslink = $("fullscreen_edit");
	fslink.href = el.href;
	clip.appendChild(fslink);
	fslink.style.display = "inline";
	State.active_edit = el;
	suggest.start();
	
}
function close_other(){
	if(State.active_edit){
		close_edit.call(State.active_edit);
	}
}
function inline_edit(e){
	if(this.edit_mode == true){
		close_edit.call(this,e)
	} else {
		close_other();
		open_edit.call(this,e)
	}
}




/* 画像の先読み */
var preload = function(url){ new Image().src = url }.forEachArgs();
preload([
	'/img/rate/0.gif',
	'/img/rate/1.gif',
	'/img/rate/2.gif',
	'/img/rate/3.gif',
	'/img/rate/4.gif',
	'/img/rate/5.gif',
	'/img/rate/pad/0.gif',
	'/img/rate/pad/1.gif',
	'/img/rate/pad/2.gif',
	'/img/rate/pad/3.gif',
	'/img/rate/pad/4.gif',
	'/img/rate/pad/5.gif'
]);


/*
   rateで使う処理
 */
var Rate = {};
Rate.image_path = "/img/rate/";
Rate.image_path_p = "/img/rate/pad/";
Rate._calc_rate = function(e){
	var el = this;
	var img_w = el.offsetWidth;
	var cell = img_w / 6;
	var offsetX = !isNaN(e.offsetX) ? e.offsetX : e.layerX - el.offsetLeft;
	if(offsetX == 0) offsetX++;
	if(offsetX>img_w) offsetX = img_w;
	var rate = Math.ceil(offsetX/cell) - 1;
	// window.status = [img_w,cell,el.offsetLeft,e.layerX,offsetX];
	return rate;
};
Rate.click = function(e){
	var el = this;
	var rate = Rate._calc_rate.call(this,e);
	var id = get_id(el);
	set_rate(id,rate);
	el.src = Rate.image_path_p + rate + ".gif";
	el.setAttribute("orig_src",el.src);
};
Rate.click_noset = function(e){
	var el = this;
	var rate = Rate._calc_rate.call(this,e);
	var id = get_id(el);
	el.src = Rate.image_path_p + rate + ".gif";
	el.setAttribute("orig_src",el.src);
    document.getElementById('rate').value = rate;
};
Rate.out = function(e){
	var src;
	var el = this;
	if(src = el.getAttribute("orig_src")){
		el.src = src
	}
};
Rate.hover = function(e){
	var el = this;
	var rate = Rate._calc_rate.call(this,e);
	// window.status = rate;
	if(!el.getAttribute("orig_src")){
		el.setAttribute("orig_src",el.src);
	}
	el.src = Rate.image_path_p + rate + ".gif";
};

function API(ap){
	this.api  = ap;
	var self = this;
	this.post = function(param, onload){
		if(typeof onload == "function"){
			self.onload = onload;
		}
		var req = new _XMLHttpRequest;
		req.open("POST", self.api, true);
		req.setRequestHeader(
			"Content-Type", "application/x-www-form-urlencoded"
		);
		req.onload = function(){
			if(typeof self.onload == "function"){
				var obj = JSON.parse(req.responseText);
				if(obj){
					self.onload(obj);
				} else {
					self.onload(req);
				}
			}
		};
		req.onerror = function(){
			if(typeof self.onerror == "function"){
				self.onerror(req);
			}
		};
		var query = Object.toQuery(param);
		req.send(query);
	}
}

function modify(url,param,callback){
	var api = new API("/clip/modify");
	param.link = url;
	if(callback){
		api.onload = callback;
	}
	api.post(param);
}
function set_rate(id, rate){
	var hash = get_hash(id);
	var api = new API('/clip/set_rate');
	api.post({
		hash : hash,
		rate : rate,
		postkey : $('postkey').value
	});
	// modify(url,{rate : rate});
}
function change_private(){
	var c = confirm("このクリップを公開しますか？");
	if(!c) return;
	var id = get_id(this);
	var el = this;
	var hash = get_hash(id);
	var api = new API('/clip/set_public');
	api.onload = function(){
		removeClass("clip_block_"+id,"clip-private");
		setStyle(el,{display:"none"});
	};
	api.post({
		hash : hash,
		"public":"on",
		postkey : $('postkey').value
	});

}
function delete_clip(e){
	Event.stop(e);
	var c = confirm("削除してもよろしいですか？");
	if(!c) return ;
	var id = get_id(this);
	var hash = get_hash(id);
	var param = {
		hash : hash,
		postkey : $("postkey").value,
		rand : new Date -0
	};
	var api = new API("/clip/delete");
	api.onload = function(){};
	api.post(param)
	setStyle("clip_block_"+id,{display:"none"});
}
function toggle_star(e){
	var self = this;
	var href = this.href;
	var mode = /mode=add/.test(href) ? 'add' : 'delete';
	var target_id = href.match(/livedoor_id=(\w*)/)[1];
	var param = {
		livedoor_id : target_id,
		postkey : $("postkey").value,
		format : 'json',
		mode : mode
	};
	var api = new API("/manage/watchlist");
	var on_add = function(){
		var title = 'このユーザーをウォッチリストから削除';
		self.innerHTML = [
			'<img src="/img/icon/star_on.gif" width="16" height="16"',
			' alt="',title,'" title="',title,'" class="star-icon">'].join("");
		self.href = self.href.replace(/mode=add/,"mode=delete");
	};
	var on_delete = function(){
		var title = 'このユーザーをウォッチリストに追加';
		self.innerHTML = [
			'<img src="/img/icon/star_off.gif" width="16" height="16"',
			' alt="',title,'" title="',title,'" class="star-icon">'].join("");
		self.href = self.href.replace(/mode=delete/,"mode=add");
	};
	api.onload = (mode == 'add') ? on_add : on_delete;
	api.post(param);
}

function do_tagsearch(){
	var input_tag= $("tagsearch").value;
	var target_id = $("target_id").value;
	var host = location.host;
	var tags = TagParser.split_tags(input_tag);
	if(!tags) return;

	var redirect = "http://"+host+"/clips/"+target_id+"/tag/";
	redirect += tags.map(function(v){return encodeURIComponent(v)}).join("/");
	location.href = redirect;
}

function fit_screen(){
	// for firefox
	try{
		var isGecko = (navigator.userAgent.indexOf("Gecko") != -1);
		var w = $("wrapper");
		var root = document.documentElement;
		if(isGecko && w && (root.scrollWidth != root.offsetWidth)){
			w.style.width = document.body.offsetWidth - 215 + "px";
		}
	}catch(e){}
}
function getInputForms(){
	var inp = document.getElementsByTagName("input");
	var tex = document.getElementsByTagName("textarea");
	var input = Array.from(inp).filter(function(el){return el.type=="text"});
	var textarea =  Array.from(tex);
	return input.concat(textarea);
}
// focus
function use_focus_effect(){
	var input_box = getInputForms();
	input_box.forEach(function(el){
		var id = el.id;
		if(!id) return;
		var label = $("label_"+id);
		if(label){
			Event.observe(el,"focus",function(){
				this.style.color = "red";
			}.bind(label));
			Event.observe(el,"blur",function(){
				this.style.color = "";
			}.bind(label));
		}
		// Event.observe(el,"focus",function(){this.style.backgroundColor="#f5f5f5"}.bind(el));
		// Event.observe(el,"blur",function(){this.style.backgroundColor="#ffffff"}.bind(el));
	});
}
function isDate(obj){
	return obj instanceof Date
}
/* Accessor */
var Accessor = Class.create().extend({
	initialize: function(){
		var value;
		var p_getter = this.getter;
		var p_setter = this.setter;
		var accessor = function(new_value){
			if(arguments.length){
				var setter = accessor.setter || p_setter;
				return (value = setter(new_value, value));
			} else {
				var getter = accessor.getter || p_getter;
				return getter(value)
			}
			return (arguments.length) ? (value = new_value) : value
		};
		accessor.isAccessor = true;
		return accessor;
	},
	getter: function(value){
		return value
	},
	setter: function(value){
		return value
	}
});

var Cookie = Class.create();
Cookie.extend({
	initialize: function(opt){
		this._options = "name,value,expires,path,domain,secure".split(",");
		this._mk_accessors(this._options);
		this.expires.setter = function(value){
			if(isDate(value)){
				value = this.expires.toString();
			} else if(isNumber(value)){
				value = new Date(new Date() - 0 + value).toString();
			}
			return value
		}
		if(opt) this._set_options(opt);
	},
	_set_options : function(opt){
		var self = this;
		this._options.forEach(function(key){
			opt.hasOwnProperty(key) && self[key](opt[key])
		})
	},
	_mk_accessors: function(args){
		for(var i=0;i<args.length;i++){
			var name = args[i];
			this[name] = new Accessor()
		}
	},
	parse: function(str){
		var hash = {};
		var ck = str || document.cookie;
		var pairs = ck.split(/\s*;\s*/);
		pairs.forEach(function(v){
			var tmp = v.split("=",2);
			hash[tmp[0]] = tmp[1];
		})
		return hash;
	},
	bake: function(){
		document.cookie = this.as_string();
	},
	as_string: function(){
		var e,p,d,s;
		e = this.expires();
		p = this.path();
		d = this.domain();
		s = this.secure();
		var options = [
			(e ? ";expires=" + e.toGMTString() : ""),
			(p ? ";path=" + p : ""),
			(d ? ";domain=" + d : ""),
			(s ? ";secure" : "")
		].join("");
		var cookie = [key,"=",value,options].join("");
		return cookie;
	}
});
Cookie.default_expire = 60*60*24*365*1000;
function setCookie(key,value,expire,path){
	var expire_str = "";
	if(isDate(expire)){
		expire_str = "expires="+expire.toString();
	} else if(isNumber(expire)){
		expire_str = "expires="+new Date(new Date() - 0 + expire).toString();
	} else {
		expire_str = "expires="+new Date(new Date() - 0 + Cookie.default_expire).toString();
	}
	if(!path) path = "; path=/;";
	var cookie = key + "=" + value + "; " + expire_str + path;
	//alert(cookie);
	document.cookie = cookie;
}
function getCookie(key){
	var hash = new Cookie().parse();
	return hash[key];
}
/* Config */
var Config = {};
Config.save = function(){setCookie("clip_config", JSON.Dump(Config))}
Config.load = function(){
	var json = getCookie("clip_config");
	var obj =  JSON.Parse(json);
	if(obj){
		Object.extend(this, obj);
	}
}



function switch_display_mode(e){
	var sel = $("sel_display_mode");
	var current = sel.selectedIndex;
	if(sel.options.length -1 == current){
		sel.selectedIndex = 0;
	} else {
		sel.selectedIndex++;
	}
	Event.stop(e);
	change_display_mode.call(sel,e);
}
function get_display_mode(){
	return $("sel_display_mode").value;
}
function change_display_mode(e){
	var el = this;
	var mode = el.value;
	if(watchlist){
		var old_mode = watchlist.current_mode;
		if(mode == "image"){
			watchlist.load_template("image");
		} else {
			watchlist.load_template("list")
		}
		if(old_mode != watchlist.current_mode){
			watchlist.rewrite();
		}
	}
	setCookie("clip_dm", mode, 60*60*24*365*1000);
	switchClass(document.body, "display-" + mode);
	update("show_thumbnail");
}
function change_thumbnail(e){
	var el = this;
	var t = el.checked;
	var mode = t ? "on" : "off";
	setCookie("clip_ss", mode, 60*60*24*365*1000);
	if(mode == "off"){
		addClass(document.body, "disable_ss");
	} else {
		removeClass(document.body, "disable_ss");
	}
	update("show_thumbnail");
}
updater("show_thumbnail",function(){
	var sel = $("sel_display_mode");
	if(!sel) return;
	if(sel.value == "detail"){
		Form.enable(this);
	} else {
		Form.disable(this);
	}
});

// window.onresize = fit_screen;
window.onload = function(){
	update("show_thumbnail");
	addClass(document.body, "disable_thumbnail");
}
var now;
var Watchlist = Class.create();
Watchlist.extend({
	initialize: function(){
		this.loaded = false;
		this._cache = {};
		this._last_clipped_on = {};
		this.offset = 0;
		this.per_page = Watchlist.per_page;
		this.api_host = "http://" + "api." + location.host;
		this.session_atime = (Config.watchlist_atime) ?
			Config.watchlist_atime : (Date.Now/1000) - Watchlist.atime_default;
		this.session_mtime =  (Config.watchlist_mtime) ?
			Math.min(Config.watchlist_mtime, Config.watchlist_mtime_saved) : (Date.Now/1000) - Watchlist.atime_default;
	},
	update_atime: function(){
		var last_atime = Config.watchlist_atime;
		var now = Math.floor(Date.Now/1000);
		Config.watchlist_atime = now;
		if(!this.loaded) return;
		if(!this._list.length) return;
		// watchlistの最終更新時刻: Config.watchlist_mtime
		// 前回の最終更新時刻: Config.watchlist_mtime_saved
		var times = this._list.pluck("last_clipped_on");
		var last = Math.max.apply(null,times);
		// 初回
		if(!Config.watchlist_mtime_saved){
			Config.watchlist_mtime_saved = this.session_mtime;
			Config.watchlist_mtime = last;
		// 連続アクセスは無視する
		} else if (now - last_atime < Watchlist.mtime_session){
		// 更新されている場合
		} else if(last != Config.watchlist_mtime){
			Config.watchlist_mtime_saved = Config.watchlist_mtime;
			Config.watchlist_mtime = last;
			this.session_mtime = Config.watchlist_mtime_saved;
		}
		Config.save();
	},
	load_list: function(id, callback){
		var self = this;
		var api = new ScriptLoader(this.api_host + "/json/watchlist");
		var nocache = Math.floor(Date.Now / Watchlist.expire);
		api.get({
			livedoor_id: id,
			mode: "detail",
			nocache: nocache
		}, function(w){
			var list = w.watchlist || w.watched;
			self.loaded = true;
			self._list = list;
			self.size = self._list.length;
			self.is_owner && self.update_atime();
			callback()
		});
	},
	load_user: function(id, callback){
		var self = this;
		// use cache
		if(this._cache.hasOwnProperty("_"+id)){
			setTimeout(function(){
				callback(self._cache["_"+id]);
			},10);
			return;
		}
		var api = new ScriptLoader(this.api_host + "/json/clips");
		api.get({
			livedoor_id: id,
			limit: Watchlist.load_num,
			nocache: self._last_clipped_on["_"+id],
			escape: 1
		}, function(clip){
			self._cache["_"+id] = clip;
			callback(clip)
		});
	},
	load_template: function(mode){
		var formatter = Template.get("tmpl_"+mode).compile();
		var self = this;
		this.list_formatter = function(item){
			// 新着のみフォーマッター
			if(Config.updated_only && self.is_owner){
				var visited = self.get_last_visited();
				if(item.created_on < visited){
					return "";
				} else {
					return formatter.apply(this, arguments);
				}
			} else {
				return formatter.apply(this, arguments);
			}
		};
		this.current_mode = mode;
	},
	formatter: function(json){
		var tmpl = Template.get("tmpl_clips").compile();
		return tmpl(json)
	},
	error_formatter: function(json){
		var tmpl = Template.get("tmpl_error").compile();
		return tmpl(json)
	},
	get_last_visited: function(){
		return this.session_mtime;
	},
	get_list: function(){
		if(Config.updated_only && this.is_owner) {
			var lasttime = this.get_last_visited();
			return this._list.filter(function(user){
				return user.last_clipped_on > lasttime;
			});
		} else {
			return this._list;
		}
	},
	rewrite: function(){
		if(!this.loaded) return;
		var list = this.get_list();
		this.size = list.length;
		TagCount.clear();
		removeClass("watchlist-body", "tagsearch");
		if(!this.size && Config.updated_only && this.is_owner){
			$("watchlist-body").innerHTML = [
				"<center style='margin-top:10px'>新着クリップがありません。しばらく時間を置いてからアクセスしてください。<br><br>",
				"<button onclick='Control.toggle_updated_only.call($(\"updated_only_toggle\"))'>全て表示</button></center>"
			].join("");
			$("watchlist-tags").innerHTML = "";
			update(/watchlist-pager/);
			return;
		}
		$("watchlist-body").innerHTML = "<table><tr><td id='col1'></td><td id='col2'></td></tr></table>";
		update(/watchlist-pager/);
		var list = list.sort(function(a,b){
			return b.last_clipped_on - a.last_clipped_on
		});
		var b1 = $("col1");
		var b2 = $("col2");
		var loading = Template.get("tmpl_loading").compile();
		list = list.slice(watchlist.offset, watchlist.offset + watchlist.per_page);
		list.forEach(function(user, i){
			watchlist._last_clipped_on["_" + user.livedoor_id] = user.last_clipped_on;
			var div = document.createElement("div");
			div.className = "block";
			div.innerHTML = loading({livedoor_id: user.livedoor_id});
			var cell = (i % 2) ? b2 : b1;
			cell.appendChild(div);
			watchlist.load_user(user.livedoor_id, function(json){
				if(json.clips){
					div.innerHTML = watchlist.formatter(json);
				} else {
					div.innerHTML = watchlist.error_formatter(json);
				}
				update("watchlist-tags");
			});
		});
	},
	has_next: function(){
		var end = this.offset + this.per_page;
		end = Math.min(this.size, end);
		return (this.size > end)
	},
	next_page: function(){
		this.offset += this.per_page;
		this.rewrite();
	},
	has_prev: function(){
		return (this.offset > 0);
	},
	prev_page: function(){
		this.offset -= this.per_page;
		this.rewrite();
	},
	as_singleton: function(){
		var self = this;
		keys(this).forEach(function(method){
			if(isFunction(self[method])){
				self[method] = self[method].bind(self)
			}
		});
		return this;
	}
});

// for search engine user.
function searchEngineAfter (callback) {
    var referrer = document.referrer;
    if ( !referrer || referrer.match(/^(undefined|unknown|bookmark)$/i) ) return false;
    if ( !callback || typeof callback != 'function' ) return false;
    if ( !this.term ) this.term = null;
    if ( this.term ) return callback( this.term );
    
    // format is '{engine-regex} {term-key}'
    var searchEngineList = [
        '^http://www\.google\.(co\.jp|com) q',
        '^http://search\.yahoo\.(co\.jp|com) p',
        '^http://search\.(msn\.co\.jp|live\.com)/results\.aspx q',
        '^http://www\.baidu\.jp/s wd',
        '^http://search\.goo\.ne\.jp/web\.jsp MT',
        '^http://search\.nifty\.com/websearch/search q',
        '^http://search\.www\.infoseek\.co\.jp/Web qt'
    ], engine;

    while ( engine = searchEngineList.shift() ) {
        engine = engine.split(" "), regexp = engine[0], query = engine[1];
        if ( referrer.match( regexp ) ) {
            callback( this.term = referrer.parseQuery()[query] || "" );
            break;
        }
    }
}

// goods search.
var GoodsSearch = Class.create().extend({
    initialize : function (endpoint, callback) {
        this.api = new API( endpoint );
	this.api._callback = typeof callback == 'function' ? callback : function(){};
	this.asins = $("exist_asins").value ? $("exist_asins").value.split(",") : [];

	["goods_search_pagging_list_top", "goods_search_pagging_list_bottom"].forEach(function (t) {
	     $( t ) && addEvent( $( t ), "click", function (o) {
	     	var target = o.srcElement ? o.srcElement : o.target ? o.target : $N("span");
		    if ( target.tagName && /^(?:a)$/i.test( target.tagName ) ) {
		       switch ( target.className ) {
			 case "previous-disactive" : return;
			 case "next-disactive" : return;
			 case "previous-active" :
		             var index = parseInt( $("goods_search_index").value ) || 2;
			     $("goods_search_index").value = index - 1;
			     return this.search();
		     	 break;
		     	 case "next-active" :
		             var index = parseInt( $("goods_search_index").value ) || 1;
			     $("goods_search_index").value = index + 1;
			     return this.search();
		     	 break;
		     	 default:
	         	     var index = parseInt( target.innerHTML ) || 1;
		 	     if ( $("goods_search_index").value != index ) {
		     	        $("goods_search_index").value = index;
		     	        return this.search();
		 	     }   
		    }
	         }
	      }.bind(this), false );
	}.bind(this));


	$("search-result-item-box") && addEvent( $("search-result-item-box"), "click", function (o) {
	     var target = o.srcElement ? o.srcElement : o.target ? o.target : $N("span");
	     if ( target.tagName && /^(?:img)$/i.test( target.tagName ) && /item_asin_(\w+)/.test( target.id ) )
	     {
	         var asin = target.id.replace(/^item_asin_/, ""),
		     pid  = $("clip_page_id") ? $("clip_page_id").value || 1 : 1;

		 (new API("/internal_api/item_add")).post({
		     item: asin,
		     page_id: pid
		 }, function (res) {
		     if ( res.isSuccess == 1 ) {
		         target.parentNode.innerHTML = '追加済み';
		         this.add_asin( asin );
			 this.refresh();
		     } else {
			 alert( res.errorMessage || "Error!!" );
		     }
		 }.bind(this));
	     }
	}.bind(this), false);

	$("recommend-item-box") && addEvent( $("recommend-item-box"), "click", function (o) {
	     var target = o.srcElement ? o.srcElement : o.target ? o.target : $N("span");
	     if ( target.tagName && /^(?:img)$/i.test( target.tagName ) && /item_(?:\d+)_(?:\w+)/.test( target.id ) && confirm("本当に削除しますか？") )
	     {
	         var m    = target.id.match( /item_(\d+)_(\w+)/ ),
		     id   = m[1],
		     asin = m[2];
		 (new API("/internal_api/item_delete")).post({
		     id : id
		 }, function (res) {
		     if ( res.isSuccess == 1 ) {
		         this.remove_asin( asin );
			 this.refresh();
		     } else {
		         alert( res.errorMessage || "Error!!" );
		     }
		 }.bind(this));
	     }
	}.bind(this), false);

	$("goods_search") && addEvent( $("goods_search"), "submit", function (o) {
	     this.search(true);
	     Event.stop(o);
	}.bind(this), false);

	$("edit-btn-area-button") && addEvent( $("edit-btn-area-button"), "click", function (o) {
             hide('edit-btn-area');
	     show('search-item-box');
	     $('goods_search_term').focus();
	     if ( !this.first_click ) {
	     	var tags = Array.from( $("search-from-tag-box").getElementsByTagName("a") ).filter(function (a) {
		    	return !!( a.innerHTML );
		    }),
		    pickup = tags[ parseInt( Math.random() * tags.length ) ];
		
                pickup && this.tag_search( pickup );
	     	this.first_click = true;
	     }
	}.bind(this), false);

	
	$("is_refresh").value != 0 && addEvent( window, "load", function () {
	     this.refresh();
	}.bind(this), false);
    },
    search: function (is_direct) {
        if ( this.not_loading() ) {
	   typeof is_direct != 'undefined'
	   	  ? this.reset_index()
		  : this.term_check();
	   this.loading();
           this.api.post(Form.toJson($("goods_search")), this.api._callback);
	}
    },
    tag_search: function (self) {
        var term = self.innerHTML;
	$("goods_search_term").value = term;
	this.search( true );
    },
    term_check: function () {
	if ( ( this.term_cache != $("goods_search_term").value ) || ( this.searchindex_cache != $("searchindex").value ) ) {
	    this.term_cache = $("goods_search_term").value;
	    this.searchindex_cache = $("searchindex").value;
	    this.reset_index();
	}
    },
    reset_index: function () {
        this.term_cache = $("goods_search_term").value;
	this.searchindex_cache = $("searchindex").value;
	$("goods_search_index").value = 1;
    },
    loading: function () {
        this.loading_start();
        var stat = $("loading_stat");
	stat.value = 'loading';
	var tid = setInterval(function () {
	    if ( stat.value != 'loading' ) {
		clearInterval( tid );
	        tid = null;
		this.loading_finish();
	    }
	}.bind(this), 20);
    },
    not_loading: function () {
        return $("loading_stat").value != 'loading';
    },
    loading_start: function () {
	var offset = Position.cumulativeOffset($("search-item-box")),
	    stop   = ( document.body.scrollTop || document.documentElement.scrollTop ),
	    sleft  = 500,
	    stop   = ( offset[1] > stop ) ? offset[1] + 3 : stop;
	with ( $("search-visible-box").style ) {
	    left = sleft + "px";
	    top  = stop  + "px";
	    display = "block";
	}
    },
    loading_finish: function () {
        $("search-visible-box").style.display = "none";
    },
    include: function (asin) {
        return this.asins.indexOf( asin ) != -1;
    },
    add_asin: function (asin) {
        this.asins.push( asin );
	var o = $("exist_asins");
	o.value = this.asins.join(",");
    },
    remove_asin: function (asin) {
        var idx = this.asins.indexOf( asin );
	this.asins.splice( idx, 1 );
	var o = $("exist_asins");
	o.value = this.asins.join(",");
    },
    refresh: function () {
        (new API("/internal_api/item_view")).post({
	    page_id : $("clip_page_id").value
	}, function (res) {
	    if ( res.isSuccess == 1 ) {
	        $("is_refresh").value = 1;
	        $("recommend-item-box").innerHTML = res.contents;
		setTimeout(function () {
		    exeScript( $("recommend-item-box") );
		}, 1);
	    } else {
	        alert( res.errorMessage || "Error!!" );
	    }           
	});
    },
    term_cache: "",
    searchindex_cache: "",
    items: [],
    asins: []
});

function pagging (page,tp) {
   var result = [],
       page   = parseInt( page ) || 1,
       tp     = parseInt( tp ) || 1,
       inc    = ( page < 10 || tp == 10 ) ? 1 : page - 5;
   if ( page + 4 > tp && page > 10 )
      inc -= (4 - (tp - page));
   var end    = ( tp < 10 )
       ? tp
       : ( page < 10 )
         ? 10
         : ( page + 4 > tp )
           ? tp             
           : page + 4;
   for (; inc <= end; inc++) {
       result.push( $N("li", {}, [$N("a", {
       	    "class": ( inc == page ) ? 'active' : 'disactive',
	    "href":"javascript:void(0)"
	  }, inc.toString())])
       );
   }
   // add previous-link
   result.unshift( $N("li", {}, [$N("a", {
       "class" : page != 1 ? "previous-active" : "previous-disactive",
       "href" : "javascript:void(0)"
   }, "前のページ")]) );
   
   result.push( $N("li", {}, [$N("a", {
       "class" : ( tp != 1 && page != end ) ? "next-active" : "next-disactive",
       "href" : "javascript:void(0)"
   }, "次のページ")]) );
   // return hash in result elements.
   return {
        elements: result,
        as_string: function () {
             var dummy = $N("span", {}, result);
             return dummy.innerHTML ? dummy.innerHTML : "";
        }
   }
}

// config
Watchlist.expire = 1000 * 60;
Watchlist.recent_time = 60 * 30;
Watchlist.load_num = 10;
Watchlist.per_page = 10;
// 前回アクセス時からこれ以上経過していた場合は既読フラグをセット(sec)
Watchlist.mtime_session = 60 * 10;
// 新着のみ、atimeが保存されていない場合(sec)
Watchlist.atime_default = 60 * 60 * 12;

var watchlist;

var Control = {
	toggle_updated_only: function(e){
		if(hasClass(this,"toggle-on")){
			Config.updated_only = false;
		} else {
			Config.updated_only = true;
		}
		Config.save();
		if(e){Event.stop(e)};
		update("updated_only_toggle");
		watchlist.rewrite();
	}
};

function ajax(url, onload){
	x= new _XMLHttpRequest;
	x.onload = function(){
		var res = x.responseText
		onload(res)
	}
	x.open("GET",url,true);
	x.send("");
}
function load_template(callback){
	ajax("/js/tmpl.js?"+(new Date()-0), function(text){
		$("watchlist-tmp").innerHTML = text;
		load_template.loaded = true;
		callback();
	});
}
load_template.loaded = true;
updater("updated_only", function(){
	var title = '前回のアクセスから追加されたクリップのみを表示します';
	this.innerHTML = [
		"<span id='updated_only_toggle' ",
		"title='", title, "'",
		"onselectstart='return false' ",
		"onmousedown='Control.toggle_updated_only.call(this,event)'>",
		"新着のみ表示</span>"
	].join("");
});
updater("updated_only_toggle", function(){
	if(Config.updated_only){
		switchClass(this,"toggle-on")
	} else {
		switchClass(this,"toggle-off")
	}
});
function watchlist_init(){
	if(!$("watchlist-tags")) return;
	var target_id = $("watchlist_target_id").value;
	var member_id = $("watchlist_member_id").value;
	Config.load();
	var setup_toolbar = function(){
		var toolbar = $("watchlist-toolbar");
		var t = $N("span");
		t.id = "updated_only";
		toolbar.appendChild(t);
		update("updated_only");
		update("updated_only_toggle");
	}
	watchlist = new Watchlist().as_singleton();
	if(target_id == member_id){
		setup_toolbar();
		watchlist.is_owner = true;
	}
	if(!load_template.loaded){
		var self = arguments.callee;
		load_template(self);
		return;
	}
	var update_pager = function(){
		if(!watchlist.size && Config.updated_only){
			this.innerHTML = "";
			return;
		}
		var start = (watchlist.offset+1);
		var end = watchlist.offset + watchlist.per_page;
		end = Math.min(watchlist.size, end);
		var buf = [];
		buf.push(watchlist.size + "件中 " + start +" - "+ end + "件目");
		if(!watchlist.has_next() && !watchlist.has_prev()){
			this.innerHTML = buf.join("");
			return;
		} else {
			buf.push(" | ");
		}
		if(watchlist.offset > 0){
			buf.push('<span class="button" onclick="watchlist.prev_page()">前のページ</span>')
		} else {
			buf.push('<span class="disabled">前のページ</span>')
		}
		buf.push(" | ");
		if(watchlist.size > end){
			buf.push('<span class="button" onclick="watchlist.next_page()">次のページ</span>')
		} else {
			buf.push('<span class="disabled">次のページ</span>')
		}
		this.innerHTML = buf.join(""); ;
	}
	$("watchlist-tags").innerHTML = "loading ... ";
	updater("watchlist-pager-top",update_pager);
	updater("watchlist-pager-bottom",update_pager);
	var mode = get_display_mode();
	if(mode == "image"){
		watchlist.load_template(mode);
	} else {
		watchlist.load_template("list");
	}
	switchClass(document.body, "display-" + mode);
	now = Math.floor((new Date - 0) / 1000);
	watchlist.load_list(target_id, watchlist.rewrite);
}

updater("watchlist-tags", function(){
	var self = this;
	var tag_count = TagCount.tags;
	var tags = keys(TagCount.tags).sort(function(a,b){return tag_count[b].length - tag_count[a].length });
	var appended = TagCount.appended;
	this.innerHTML = "<b>Tags: </b>";
	if(tags.length > 5){
		var show_all = $N("span");
		show_all.className = "show_all_button";
		show_all.innerHTML = hasClass(self, "show_all") ?  "≪ タグを隠す" : "全てのタグを表示 ≫";
		addEvent(show_all, "click", function(){
			toggleClass(self,"show_all");
			show_all.innerHTML = hasClass(self, "show_all") ?  "≪ タグを隠す" : "全てのタグを表示 ≫";
		});
		this.appendChild(show_all);
	}
	tags.forEach(function(tag,i){
		if(appended[tag]){
			// appended[tag].style.fontSize = 12 * Math.sqrt(tag_count[tag].length) + "px";
		} else {
			var span = document.createElement("span");
			span.innerHTML = tag + "("+ tag_count[tag].length +")";
			span.className = "watchlist-tag";
			if(i > 5){
				span.className += " hidden";
			}
			addEvent(span, "click", TagCount.click.bind(span));
			addEvent(span, "mouseover", TagCount.mouseover.bind(span));
			addEvent(span, "mouseout", TagCount.mouseout.bind(span));
			self.appendChild(span);
		}
	});
});
function get_hotlevel(num){
	return (num < 3) ? 0 : (num >= 3 && num < 10) ? 1 : 2;
}
function alert_once(v){
	if(alert_once.alerted) return;
	alert(v);
	alert_once.alerted = true;
}
TagCount.click = function(e){
	var text = this.innerHTML.replace(/\(\d+\)$/,"");
	var tags = TagCount.tags[text];
	if(!tags) return;
	var toggle_off = function(){
		tags.forEach(function(id){
			switchClass($("item_" + id), "watchlist-list");
		});
	};
	var toggle_on = function(){
		tags.forEach(function(id){
			switchClass($("item_" + id), "watchlist-hilight");
		});
	}
	if(TagCount.clicked == this){
		TagCount.clicked = null;
		removeClass(this, "selected");
		removeClass("watchlist-body", "tagsearch");
		toggle_off();
		return;
	} else if(TagCount.clicked){
		var el = TagCount.clicked;
		TagCount.click.call(el, e);
		TagCount.clicked = null;
	}
	addClass(this,"selected");
	TagCount.clicked = this;
	addClass("watchlist-body", "tagsearch");
	toggle_on();
};
TagCount.mouseover = function(e){
	return;
	var text = this.innerHTML.replace(/\(\d+\)$/,"");
	var tags = TagCount.tags[text];
	if(TagCount.clicked) return;
	if(!tags) return;
	addClass("watchlist-body", "tagsearch");
	tags.forEach(function(id){
		switchClass($("item_" + id), "watchlist-hilight");
	});
};
TagCount.mouseout = function(e){
	return;
	var text = this.innerHTML.replace(/\(\d+\)$/,"");
	var tags = TagCount.tags[text];
	if(!tags) return;
	if(TagCount.clicked) return;
	this.style.backgroundColor = "transparent";
	removeClass("watchlist-body", "tagsearch");
	tags.forEach(function(id){
		switchClass($("item_" + id), "watchlist-list");
	});
};

TagCount.appended = {};
TagCount.count = 0;
TagCount.tags = {};
TagCount.clear = function(){
	this.count = 0;
	this.tags  = {};
};
function TagCount(param){
	TagCount.count++;
	var tags = param.tags;
	tags.forEach(function(tag){
		if(!TagCount.tags[tag]) TagCount.tags[tag] = [];
		TagCount.tags[tag].push(TagCount.count);
	});
	return TagCount.count;
}



function Target(){}
function Member(){}


var suggest;
// startup global
function dom_init(){
	var tags_array = [];
	if($("htmltagcloud")){
		tags_array = map(
			$("htmltagcloud").getElementsByTagName("a"),
			function(a){return a.firstChild ? a.firstChild.nodeValue : ""}
		);
	}
	// inline edit
	if($("edit_tags")){
		suggest = new TagSuggest("edit_tags",tags_array);
		suggest.stop();
	}
	// search
	if($("tagsearch")){
		var search_suggest = new TagSuggest("tagsearch", tags_array);
	}
	if($("fans")){
	
	}
}
function toggleTagList(e) {
    var taglist = $("tag_list");
	var visibility = (taglist.style.display == "block") ? false : true;
    if (visibility) {
        taglist.style.display = "block";
        this.innerHTML = '過去に入力したタグを隠す';
    } else {
        taglist.style.display = "none";
        this.innerHTML = '過去に入力したタグを表示する';
        setCookie('show_taglist_at_addform', '', -60);
    }
}
function swapTag(e){
	var sel_tag = this.innerHTML;
	swapTag.suggest.swapTag(sel_tag);
	swapTag.taglist.update();
	setCookie('show_taglist_at_addform', 'true', 60*60*24*14);
}
// startup add/edit
function clip_init(){
	var tags_array = map(
		$("tag_list").getElementsByTagName("span"),
		function(a){return a.firstChild.nodeValue}
	);
	var suggest = new TagSuggest("tags",tags_array);
	TagSuggest.prototype.onchange = function(){
		taglist.update();
	};
	TagSuggest.prototype.oncomplete = function(){
		taglist.update();
	}
	function TagList(id){
		this.el = $(id);
		this.init();
	}
	TagList.prototype = {
		init : function(){
			var list = [];
			var hash = {};
			var span_list = this.el.getElementsByTagName('span');
			var len = span_list.length;
			for(var i=0;i<len;i++){
				var span = span_list[i];
				list.push(span);
				hash[span.innerHTML] = span;
			}
			this.list = list;
			this.hash = hash;
		},
		update : function(){
			var used_tags = suggest.get_used_tags();
			var used_tags_hash = {};
			for(var i=0;i<used_tags.length;i++){
				used_tags_hash[used_tags[i]] = true;
			}
			var len = this.list.length;
			for(var tag in this.hash){
				if(used_tags_hash.hasOwnProperty(tag)){
					this.hash[tag].className = "tag-active";
				} else {
					this.hash[tag].className = "tag-inactive";
				}
			}
		}
	}
	taglist = new TagList("tag_list");
	taglist.update();
    var tagListIsVisible = ($('tag_list').style.display != "none") ? true : false;
    var showTagList = getCookie('show_taglist_at_addform') ? true : false;
    if ( !tagListIsVisible && showTagList ) { toggleTagList.call($('toggle_tag_list')); }
	setTimeout(function(){$("tags").focus()},10);
	swapTag.taglist = taglist;
	swapTag.suggest = suggest;
}



function ScriptLoader(ap){
	this.ap = ap;
	this.count = ScriptLoader.count;
	ScriptLoader.count++;
};
ScriptLoader.prototype = {
	get: function(param, callback){
		var self = this;
		self.param = param;
		ScriptLoader.callback["_"+this.count] = function(){
			callback.apply(self, arguments);
			self._remove();
		};
		ScriptLoader.connection_count++;
		param = param || {};
		param["callback"] = "ScriptLoader.callback._" + this.count;
		var query = this._query(param);
		var url = this.ap + "?" + query;
		this._append(url);
	},
	_query: function(self){
		var buf = [];
		for(var key in self){
			var value = self[key];
			if(isFunction(value)) continue;
			buf.push(
				encodeURIComponent(key)+"="+
				encodeURIComponent(value)
			)
		}
		return buf.join("&");
	},
	_append: function(url){
		var s = document.createElement("script");
		s.type = "text/javascript";
		s.charset = "utf-8";
		s.src = url;
		this._script = s;
		var head = document.getElementsByTagName("head")[0];
		head.appendChild(s)
	},
	_remove: function(){
		ScriptLoader.connection_count--;
		if(ScriptLoader.DEBUG) return;
		delete ScriptLoader.callback["_" + this.count];
		try{
			var head = document.getElementsByTagName("head")[0];
			head.removeChild(this._script)
		}catch(e){}
	}
};
ScriptLoader.count = 0;
ScriptLoader.callback = {};
ScriptLoader.connection_count = 0;
ScriptLoader.DEBUG = true;

Number.prototype.toRelativeDate = function(){
	var k = this > 0 ? this : -this;
	var u = "sec";
	var jp = {
		sec : "秒",
		min : "分",
		hour: "時間",
		day : "日",
		Mon : "ヶ月"
	};
	var vec = this >= 0 ? "前" : "後";
	var st = 0;
	(k>=60) ? (k/=60,u="min",st=1) : 0;
	(st && k>=60) ? (k/=60,u="hour",st=1) : st=0;
	(st && k>=24) ? (k/=24,u="day" ,st=1) : st=0;
	(st && k>=30) ? (k/=30,u="Mon" ,st=1) : st=0;
	k = Math.floor(k);
	v = jp[u];
	return (isNaN(k)) ? "nan" : k+v+vec;
}

Date.prototype.to_color = function(){
	return color.make_color(this.pastTime_g());
}

Date.prototype.pastTime_g = function(o){
	var gra = o ? o.gra : 1;
	var keika = Math.ceil(
		((Date.Now - this.getTime())/1000)/(3600 * gra)
	);
	return (keika > 512)?512:keika;
};
Date.Now = {};
Date.Now.toString = function(){
	return this.value;
}
Date.Now.update = function(){
	this.value = new Date().getTime();
}
Date.Now.update();

function Color(){
	this.cache = {};
	this.rule  = "gra";
	var self = this;
	this.rules = {
		gra: function(n){
			var sikii = 160;
			var abs = (sikii<120) ? 120 : sikii;
			if(sikii < 120){sikii=120}
			var r = Math.ceil(255-(n*2));
			var g = Math.ceil(255-Math.abs(n-128)*2);
			var b = Math.ceil((n-128)*2);
			if(r>128){r=255}else{r*=2}if(r<0){r=0}
			if(g>128){g=255}else{g*=2}if(g<0){g=0}
			if(b>128){b=255}else{b*=2}if(b<0){b=0}
			r = Math.ceil(r * (255-abs)/255 + sikii);
			g = Math.ceil(g * (255-abs)/255 + sikii);
			b = Math.ceil(b * (255-abs)/255 + sikii);
			var result = "rgb("+r+","+g+","+b+")";
			return result;
		}
	};
	this.make_color = function(o){
		if(!this.cache[this.rule]){
			this.cache[this.rule] = {};
		}
		var c = this.cache[this.rule];
		if(c.hasOwnProperty("_"+o)){
			return c["_"+o]
		} else {
			return (c["_"+o] = this.rules[this.rule](o));
		}
	};
	return this;
}
var color = new Color();

/*
(function()
{ // http://d.hatena.ne.jp/shogo4405/20070405/1175776629
    if(window.HTMLElement)
	{
	    if('insertAdjacentElement' in HTMLElement.prototype){
		return;
	    };
	}
    else
	{
	    if(navigator.vendor == 'Apple Computer, Inc.')
		{
		    document.createElement('html');
		    window.HTMLElement = { prototype : window["[[DOMElement.prototype]]"] || {}};
		}
	    else
		{
		    return;
		};
	};
    HTMLElement.prototype.insertAdjacentElement = function(w, n)
    {
	switch(w.toLowerCase())
	    {
	    case 'beforebegin':
	    this.parentNode.insertBefore(n, this);
	    break;
	    case 'afterbegin':
	    this.insertBefore(n, this.childNodes[0]);
	    break;
	    case 'beforeend':
	    this.appendChild(n);
	    break;
	    case 'afterend':
	    this.parentNode.insertBefore(n, this.nextSibling);
	    break;
	    };
	return n;
    };
    HTMLElement.prototype.insertAdjacentText = function(w, t){
	this.insertAdjacentElement(w, document.createTextNode(t || ''));
    };
    HTMLElement.prototype.insertAdjacentHTML = function(w, h)
    {
	var r = document.createRange(); r.selectNode(this);
	this.insertAdjacentElement(w, r.createContextualFragment(h));
    };
})();
*/

function exeScript(element)
{ // http://d.hatena.ne.jp/shogo4405/20080127/1201442279
    function writer(script){
	return function(){
	    var span = document.createElement("span");
	    span.innerHTML = Array.prototype.join.call(arguments, '');
	    script.parentNode.appendChild( span );
	    // script.insertAdjacentHTML('beforeBegin', Array.prototype.join.call(arguments, ''));
	};
    };
    var temp = document.write;
    var i, f, script, data, scripts = element.getElementsByTagName('script');
    for(i=0,f=scripts.length;i<f;i++)
	{
	    script = scripts[i],
	    data   = script.text || script.textContent || script.innerHTML || "";
	    if(data)
		{
		    document.write = writer(script);
		    // eval.call(window, data);
		    (new Function(data))();
		};
	};
    script = null;
    scripts = null;
    document.write = temp;
};
