// ==UserScript==
// @name          NicoNicoPlaylist mod
// @namespace     oamaxa
// @original      d.hatena.ne.jp/Sore_0
// @description   List up the series of movies and play them all!
// @include       http://www.nicovideo.jp/*
// @include       http://ch.nicovideo.jp/*
// @include       http://com.nicovideo.jp/*
// @include       http://dic.nicovideo.jp/*
// @include       https://secure.nicovideo.jp/secure/login_form*
// @exclude       http://www.nicovideo.jp/api/*
// @exclude       http://www.nicovideo.jp/thumb/*
// @version	      1.9.0
// @update        2010/01/07
// ==/UserScript==

//var DEBUG=true;
var w=(unsafeWindow||window),document=w.document;
var $=function(id){return document.getElementById(id);};
var $n=function(tag){return document.createElement(tag);};
var $x=function(node){
	var x=document.evaluate(node,document,null,7,null),array=new Array();
	for(var i=0; i<x.snapshotLength; i++) array.push(x.snapshotItem(i));
	return array;
};
var Util={
	getElementPosition:function(e){
		var offsetTrail=e,offsetTop=0;
		while(offsetTrail){
			offsetTop+=offsetTrail.offsetTop;
			offsetTrail=offsetTrail.offsetParent;
		}
		return offsetTop;
	},
	xhr:function(e,f){
		var f=(typeof f=="function")? f():function(){},r=new XMLHttpRequest();
		r.open("GET",e,false);
		r.onload=r.onerror=f;
		r.send(null);
	},
	opacity:function(e){
		if(e.className.indexOf(" opacity")>-1) return;
		var opacity=0,css=e.style.cssText;
		e.className+=" opacity";
		var timer=setInterval(function(){
			if(opacity==100){
				clearInterval(timer);
				e.style.cssText=css;
				e.className=e.className.replace(" opacity","");
			}
			else{
				opacity=(opacity>96)? 100:opacity+4;
				e.style.cssText=[
					css,
					"-moz-opacity:"+opacity/100+";",
					"opacity:"+opacity/100+";"
				].join(" ");
			}
	    },40);
	}
};

var URL={
	NICO:/^https?:\/\/.*?\.(?:nico|smile)video\.jp\//, 
	WATCH_REGEXP:/http:\/\/.*?\.nicovideo\.jp\/watch\/([^\/?<>\"\'#]+)/, 
	WATCH:"http://www.nicovideo.jp/watch/",
	CACHE:"http://www.nicovideo.jp/cache/",
	CHANNEL:"http://ch.nicovideo.jp/",
	COM:"http://com.nicovideo.jp/",
	DIC:"http://dic.nicovideo.jp/",
	MYLIST:"http://www.nicovideo.jp/mylist/",
	GETTHUMB:"http://ext.nicovideo.jp/api/getthumbinfo/",
	GETRELATION:"http://ext.nicovideo.jp/api/getrelation?sort=p&order=d&video=",
	WHATPAGE:function(){
		var m;
		if(m=location.href.match(/http:\/\/.*?\.nicovideo\.jp\/([^\/]*)\/?/)) return m[1];
	}
};
const playlistWidth=434;
const watchPage=(location.href.match(URL.WATCH_REGEXP)&&$("flvplayer_container"))? true:false;
const chPage=location.href.match(URL.CHANNEL)? true:false;
const comPage=location.href.match(URL.COM)? true:false;
const dicPage=location.href.match(URL.DIC)? true:false;

var Video=function(){this.initialize.apply(this,arguments);};
	Video.prototype={
		initialize:function(){
			this.id=arguments[0]||"";
			this.title=arguments[1]||"";
			this.opt=arguments[2]||"";
		},
		serialize:function(){return [escape(this.id),escape(this.title),escape(this.opt)].join("&");},
		unserialize:function(data){
			var r=[];
			if(data) r=data.split("&");
			this.id=unescape(r[0]);
			this.title=unescape(r[1]);
			this.opt=r[2]? unescape(r[2]):"";
		},
		getPlayUri:function(){return URL.WATCH+this.id;},
		play:function(e){location.replace(this.getPlayUri()+e);},
		getRelatedVideos:function(iterator,terminator){
			GM_xmlhttpRequest({
				method:"GET",
				url:URL.GETRELATION+this.id,
				headers:{"Content-type":"text/xml"},
				onload:function(r){
					var tags=r.responseText.match(/<video>((?:.|\r|\n)+?)<\/video>/gm);
					if(tags){
						for(var i=0,len=tags.length; i<len; i++){
							var videoId=tags[i].match(URL.WATCH_REGEXP);
							var title=tags[i].match(/<title>(.+?)<\/title>/);
							iterator(videoId[1],title[1]);
						}
					}
					terminator();
				},
				onerror:function(){terminator();}
			});
		}
	};

var PlayList=function(){this.initialize.apply(this,arguments);};
	PlayList.all=function(){
		var list=GM_getValue("playlist.all",false);
		return list? list.split(/,/):[];
	};
	PlayList.add=function(id){
		GM_setValue("current",id);
		if(PlayList.checker(id)) return;
		var list=PlayList.all();
		list.push(id);
		GM_setValue("playlist.all",list.join(","));
	};
	PlayList.del=function(id){
		var list=PlayList.all();
		for(var i=0; i<list.length; i++) if(list[i]==id) list.splice(i,1);
		GM_setValue("playlist.all",list.join(","));
		var val=GM_listValues();
		for(var i=0; i<val.length; i++) if(val[i].replace(/[a-z]+_/,"")==id) GM_deleteValue(val[i]);
	};
	PlayList.checker=function(id){
		var m=false,list=PlayList.all();
		for(var i=0; i<list.length; i++){
			if(list[i]==id){
				m=true;
				break;
			}
		}
		return m;
	};
	PlayList.videos=function(){
		var list=PlayList.all(),meta,n=0;
		for(var i=0; i<list.length; i++){
			meta=GM_getValue("playlist_"+list[i]).split(/;/)[1].split(/:/);
			n+=(meta[0]=="")? 0:meta.length;
		}
		return n;
	};
	PlayList.prototype={
		initialize:function(){
			this.id=arguments[0]||"default";
			this.videos=[];
			this.load();
		},
		serialize:function(){
			var videos=[];
			for(var i=0; i<this.videos.length; i++) videos.push(this.videos[i].serialize());
			return [this.id,videos.join(":")].join(";");
		},
		unserialize:function(data){
			var r=data.split(/;/)[1];
			if(!r) return;
			var vids=r.split(/:/);
			for(var i=0; i<vids.length; i++){
				if(vids[i]){
					var v=new Video();
					v.unserialize(vids[i]);
					this.videos.push(v);
				}
			}
		},
		save:function(){GM_setValue("playlist_"+this.id,this.serialize());},
		copy:function(id){GM_setValue("playlist_"+id,id+this.serialize().slice(this.id.length));},
		load:function(){
			var data=GM_getValue("playlist_"+this.id,false);
			if(data) this.unserialize(data);
		},
		push:function(video){this.videos.push(video);},
		unshift:function(video){this.videos.unshift(video);},
		pop:function(){return this.videos.shift();},
		popRandom:function(){
			if(this.videos.length==0) return null;
			var i=Math.floor(Math.random()*this.videos.length);
			var v=this.videos[i];
			this.videos.splice(i,1);
			return v;
		},
		remove:function(a,b){
			var start=a<b? a:b,end=a<b? b:a;
			this.videos.splice(start,end-start+1);
		},
		move:function(a,b){
			if(a<b){
				this.videos.splice(b+1,0,this.videos[a]);
				this.videos.splice(a,1);
			}
			else{
				this.videos.splice(b,0,this.videos[a]);
				this.videos.splice(a+1,1);
			}
		},
		reverse:function(a,b){
			var start=a<b? a:b,end=a<b? b:a,videos=this.videos.slice(start,end+1);
			videos.reverse();
			for(var i=0; i<videos.length; i++) this.videos.splice(start+i,1,videos[i]);
		},
		fetch:function(n,f){
			var id=this.videos[n].id,self=this;
			GM_xmlhttpRequest({
				method:"GET",
				url:URL.GETTHUMB+id,
				headers:{"Content-type":"text/xml","User-agent":"Mozilla/5.0 Greasemonkey"},
				onload:function(r){
					var m,title=(m=r.responseText.match(/<title>(.+?)<\/title>/))? m[1]:id;
					for(var i=0; i<self.videos.length; i++){
						if(self.videos[i].id==id) self.videos[i].title=title;
					}
					f();
				},
				onerror:function(){return false;}
			});
		},
		clear:function(){this.videos=[];}
	};

var PlayListController=function(){this.initialize.apply(this,arguments);};
	PlayListController.prototype={
		initialize:function(){
			var playlist=arguments[0];
			PlayList.add(playlist.id);
			this.continuity=arguments[1];
			this.scroll=GM_getValue("scroll",0);
			this.delay=GM_getValue("delay",5000);
			this.rc=GM_getValue("rc",2);
			this.lp=[];
			this.mark="del";
			this.merge=[];
			this.pick=false;
			var data=["auto","extend","popmark","pause"];
			for(var i=0; i<data.length; i++) this[data[i]]=GM_getValue(data[i],false);
			this.bind(playlist);
			var self=this;
			document.addEventListener("click",function(e){
				if(!self.pick||e.which==3) return;
				var ee=e.target;
				if(ee.tagName=="SPAN"&&ee.className=="vinfo_title") ee=ee.parentNode;
				else if(ee.tagName!="A") return;
				var video=ee.href.match(/watch\/([a-z]*\d+)$/)[1]||false;
				if(video&&ee.className.indexOf("noadd")==-1){
					e.preventDefault();
					Util.opacity(ee);
					self.pushVideo(video,dicPage? ee.textContent:ee.innerHTML,(e.which==2)? true:false);
				}
			},true);
		},
		bind:function(playlist){
			this.playlist=playlist;
			this.buffer={playlist:[],n:0};
			this.color=GM_getValue("color_"+playlist.id,"#FFF0FC");
			var data=["eco","com","vol","fs","random","loop","cache"];
			for(var i=0; i<data.length; i++) this[data[i]]=GM_getValue(data[i]+"_"+playlist.id,false);
			this.update();
		},
		change:function(id){
			PlayList.add(id);
			this.bind(playlist=new PlayList(id));
		},
		getPageVideoId:function(){
			var m;
			return (m=location.href.match(URL.WATCH_REGEXP))? m[1]:false;
		},
		isWatchPage:function(){return this.getPageVideoId()!=false;},
		pushVideo:function(video,title,opt){
			if(!video.match(/^[a-z]*\d+$/)) return;
			this.clone();
			if(!!opt) this.playlist.unshift(new Video(video,title));
			else this.playlist.push(new Video(video,title));
			this.buffer.playlist[-1]=Array.apply(null,this.playlist.videos);
			this.playlist.save();
			this.update();
		},
		pushVideos:function(videos,opt){
			this.clone();
			for(var i=0,len=videos.length; i<len; i++){
				if(!videos[i].id.match(/^[a-z]*\d+$/)) continue;
				var video=new Video(videos[i].id,videos[i].title,videos[i].opt);
				this.playlist.push(video);
			}
			this.buffer.playlist[-1]=Array.apply(null,this.playlist.videos);
			this.playlist.save();
			this.update();
		},
		pushThisVideo:function(){
			var videoId=this.getPageVideoId();
			if(videoId){
				this.clone();
				this.playlist.unshift(new Video(videoId,unescape(w.Video.title)));
//				this.playlist.push(new Video(videoId,unescape(w.Video.title)));
				this.buffer.playlist[-1]=Array.apply(null,this.playlist.videos);
				this.playlist.save();
				this.update();
			}
		},
		pushAllVideos:function(){
			this.clone();
			var as=$x("//a[contains(concat(' ',normalize-space(@class),' '),' video ')]|//a[contains(concat(' ',normalize-space(@class),' '),' watch ')]");
			if(dicPage) as=as.concat($x("//a[@rel='nofollow']"));
			for(var i=0, len=as.length; i<len; i++){
				var a=as[i],m,title,t;
				if(a.className.indexOf("noadd")>-1) continue;
				if(m=a.href.match(URL.WATCH_REGEXP)){
					if(dicPage) title=a.textContent;
					else title=(t=a.innerHTML.match(/<span[^>]*?>(.*?)<\/span>/))? t[1]:a.innerHTML;
					this.playlist.push(new Video(m[1],title));
				}
			}

			var self=this;
			if(watchPage){
				var thisVideo=new Video(w.Video.id);
				thisVideo.getRelatedVideos(function(videoId,title){
					self.playlist.push(new Video(videoId,title));
				},function(){
					self.buffer.playlist[-1]=Array.apply(null,self.playlist.videos);
					self.playlist.save();
					self.update();
				});
			}
			else{
				self.buffer.playlist[-1]=Array.apply(null,self.playlist.videos);
				self.playlist.save();
				self.update();
			}
		},
		pushMarkedVideos:function(){
			var $marked=$x("//a[contains(concat(' ',normalize-space(@class),' '),' video ')][./ancestor::*[contains(concat(' ',normalize-space(@class),' '), ' marked ')]]|//a[contains(concat(' ',normalize-space(@class),' '),' watch ')][./ancestor::*[contains(concat(' ',normalize-space(@class),' '), ' marked ')]]");
			if($marked.length==0) return;
			this.clone();
			for(var i=0; i<$marked.length; i++){
				if($marked[i].href.match(URL.WATCH)&&$marked[i].href.indexOf($marked[i].textContent)==-1){
					var link=$marked[i].href.match(/watch\/([^\/?<>\"\'#]+)/)[1];
					this.playlist.push(new Video(link,$marked[i].textContent));	
					if(this.popmark) this.popVideoMark();
				}
			}
			this.buffer.playlist[-1]=Array.apply(null,this.playlist.videos);
			this.playlist.save();
			this.update();
		},
		popVideoMark:function(){
			var $marked=$x("//*[contains(concat(' ',normalize-space(@class),' '),' marked ')]");
			if($marked.length==0) return;
			var page=URL.WHATPAGE();
			for(var i=0; i<$marked.length; i++){
				if($marked[i].className.indexOf("marking")>-1) $marked[i].className=$marked[i].className.replace(/\s+marked/,"");
				else if(page.match(/ranking|tag|search|newarrival|recent|myvideo|hotlist|\?g=/g)||page=="") $marked[i].setAttribute("class","thumb_frm");
				else for(var i=0; i<$marked.length; i++) $marked[i].setAttribute("class","");
			}
		},
		playNext:function(){
			var video=this.random? this.playlist.popRandom():this.playlist.pop();
			if(video){
				if(this.loop) this.playlist.push(video);
				this.playlist.save();
				window.name+=" continuity:\""+this.playlist.id+"\"";
				if(this.eco&&this.lp.indexOf("eco=1")==-1) this.lp.unshift("eco=1");
				video.play(this.lp[0]? "?"+this.lp.join("&"):"");
			}
		},
		capture:function(e,video,f){
			this.pin=true;
			var mark=$x("//*[contains(concat(' ',normalize-space(@class),' '),' checked ')]")[0];
			if(typeof mark!="undefined"){
				var n=parseInt(mark.className.split(/\s+/)[2],10);
				if(n!=video){
					this.clone();
					f(n,video);
					this.buffer.playlist[-1]=Array.apply(null,this.playlist.videos);
					this.playlist.save();
				}
				this.update();
			}
			else{
				var n=$x("//a[@class='"+e+"']")[video];
				n.className=e+" checked "+video;
				n.parentNode.style.backgroundColor=n.parentNode.lastChild.style.backgroundColor=n.style.color;
				n.parentNode.lastChild.style.color=n.style.color="white";
			}
		},
		remove:function(video,event){
			if(event=="contextmenu"){
				var self=this;
				this.capture("del",video,function(a,b){self.playlist.remove(a,b);});
			}
			else{
				this.clone();
				this.playlist.videos.splice(video,1);
				this.buffer.playlist[-1]=Array.apply(null,this.playlist.videos);
				this.playlist.save();
				this.update();
			}
		},
		move:function(video){
			var self=this;
			this.capture("move",video,function(a,b){self.playlist.move(a,b);});
		},
		reverse:function(video){
			var self=this;
			this.capture("rev",video,function(a,b){self.playlist.reverse(a,b);});
		},
		clear:function(){
			this.clone();
			this.playlist.clear();
			this.buffer.playlist[-1]=Array.apply(null,this.playlist.videos);
			this.playlist.save();
			this.update();
		},
		fetch:function(video){
			var self=this;
			this.playlist.fetch(video,function(){
				self.playlist.save();
				self.update();
			});
		},
		state:function(e,state){
			this[e]=state;
			GM_setValue(e,state);
		},
		_state:function(e,state){
			this[e]=state;
			GM_setValue(e+"_"+this.playlist.id,state);
		},
		copy:function(id){
			var data=["color","eco","com","vol","vl","fs","random","loop","cache"];
			for(var i=0; i<data.length; i++) if(typeof this[data[i]]!="undefined") GM_setValue(data[i]+"_"+id,this[data[i]]);
			this.playlist.copy(id);
		},
		duplicate:function(id){
			this.copy(id);
			this.change(id);
		},
		rename:function(id){
			this.copy(id);
			PlayList.del(this.playlist.id);
			this.change(id);
		},
		clone:function(){
			this.buffer.playlist.splice(0,this.buffer.n,Array.apply(null,this.playlist.videos));
			this.buffer.n=0;
		},
		track:function(back){
			(back)? this.buffer.n++:this.buffer.n--;
			this.playlist.videos=this.buffer.playlist[this.buffer.n-1];
			this.playlist.save();
			this.update();
		},
		marker:function(e){
			var a={},mark=["del","move","rev","rename"];
			for(var i=0; i<mark.length; i++) a[mark[i]]=$x("//a[contains(concat(' ',normalize-space(@class),' '),' "+mark[i]+" ')]");
			for(var j=0; j<this.playlist.videos.length; j++){
				for(var k in a) a[k][j].style.display="none";
				a[e][j].style.display="inline";
			}
			this.mark=e;
			this.update();
		},
		config:function(){
			this.pin=true;
			var self=this;
			var box=$("playlistController");
				box.innerHTML="";

			var header=$n("p");
				header.className="playlist-header";
				header.style.margin="2px 10px 0px 10px";
				header.style.fontSize="15px";
			box.appendChild(header);
			var title=$n("span");
				title.style.fontWeight="bold";
				title.style.color="#C9A9CC";
				title.innerHTML="Playlist";
				title.style.cursor="pointer";
				title.addEventListener("click",function(){self.update();},false);
			header.appendChild(title);
			var conf=$n("span");
				conf.style.marginLeft="10px";
				conf.style.fontWeight="bold";
				conf.innerHTML="Preference";
			header.appendChild(conf);

			var hr=$n("hr");
				hr.style.margin="0px 10px";
			box.appendChild(hr);

			var div=$n("div");
				div.style.margin="10px";
				div.style.padding="0px";
			box.appendChild(div);
			var ul=$n("ul");
				ul.className="font12";
				ul.style.listStyleType="circle";
				ul.style.margin="0px 10px 0px 20px";
				ul.style.padding="0px";
				ul.style.fontWeight="bold";
			div.appendChild(ul);

			var checkDef=[
				{caption:"disable auto play",title:"自動再生を無効化",click:function(){
					self.state("auto",this.checked);
				},checked:this.auto},
				{caption:"extend assistive functions",title:"プレイリスト外の動画に補助機能を適用",click:function(){
					self.state("extend",this.checked);
				},checked:this.extend},
				{caption:"unmark after adding marked movies",title:"マークした動画を追加後、動画のマークを外す",click:function(){
					self.state("popmark",this.checked);
				},checked:this.popmark},
				{caption:"enable auto login",title:"自動ログインを有効化",click:function(){
					self.state("login",this.checked);
				},checked:login}
			];
			for(var i=0, len=checkDef.length; i<len; i++){
				var def=checkDef[i];
				var list=$n("li");
					list.style.marginTop="10px";
				ul.appendChild(list);
				var chk=$n("input");
					chk.id="config_check_"+i;
					chk.type="checkbox";
					chk.style.marginTop="0px";
					chk.style.verticalAlign="middle";
				if(def.click) chk.addEventListener("click",def.click,false);
				if(def.checked) chk.checked="checked";
				list.appendChild(chk);
				var label=$n("label");
					label.htmlFor=chk.id;
					label.innerHTML=def.caption;
					label.style.verticalAlign="middle";
					label.style.border="none";
				list.appendChild(label);
				chk.title=label.title=def.title;
			}

			var timer;
			var textDef=[
				{caption:"adjust scroll",title:"スクロール調整",click:function(e){
					var value=e.target.previousSibling.value,span=e.target.nextSibling;
					if(value.match(/^-?\d+$/)){
						self.scroll=value;
						GM_setValue("scroll",value);
						if(value=="184") span.innerHTML="scroll disabled";
						else{
							e.target.style.display="none";
							span.innerHTML="updated";
							if(typeof player!="undefined") player.scroll(self.scroll);
						}
					}
					else span.innerHTML="error";
					timer=setTimeout(function(){
						e.target.style.display="none";
						span.innerHTML="";
					},3000);
				},init:"scroll"},
				{caption:"delay for auto play",title:"自動再生までの遅延時間(ミリ秒)",click:function(e){
					var value=e.target.previousSibling.value,span=e.target.nextSibling;
					if(value.match(/^\d+$/)){
						self.delay=value;
						GM_setValue("delay",value);
						e.target.style.display="none";
						span.innerHTML="updated";
					}
					else span.innerHTML="error";
					timer=setTimeout(function(){
						e.target.style.display="none";
						span.innerHTML="";
					},3000);
				},init:"delay"},
				{caption:"auto reload",title:"動画読み込みエラー時の自動リロード回数",click:function(e){
					var value=e.target.previousSibling.value,span=e.target.nextSibling;
					if(value.match(/^\d$/)){
						self.rc=value;
						GM_setValue("rc",value);
						e.target.style.display="none";
						span.innerHTML="updated";
					}
					else span.innerHTML="error";
					timer=setTimeout(function(){
						e.target.style.display="none";
						span.innerHTML="";
					},3000);
				},init:"rc"},
				{caption:"chrome code",title:"背景色を変更",click:function(e){
					var value=e.target.previousSibling.value,span=e.target.nextSibling;
					if(value.match(/^#(?:[a-fA-F0-9]{6}|[a-fA-F0-9]{3})$/)){
						box.style.backgroundColor=self.color=value;
						GM_setValue("color_"+self.playlist.id,value);
						e.target.style.display="none";
						span.innerHTML="updated";
					}
					else span.innerHTML="error";
					timer=setTimeout(function(){
						e.target.style.display="none";
						span.innerHTML="";
					},3000);
				},init:"color"}
			];
			for(var i=0, len=textDef.length; i<len; i++){
				var def=textDef[i];
				var list=$n("li");
					list.style.paddingLeft="5px";
					list.style.paddingTop="8px";
				ul.appendChild(list);
				var desc=$n("span");
					desc.innerHTML=def.caption+" : ";
				list.appendChild(desc);
				var text=$n("input");
					text.className="font12";
					text.type="text";
					text.size="7";
					text.style.border="1px solid black";
					text.style.padding="3px";
					text.value=this[def.init];
					text.addEventListener("keyup",function(e){
						if(timer) clearTimeout(timer);
						e.target.nextSibling.style.display="";
					},false);
				list.appendChild(text);
				var button=$n("input");
					button.className="_submit";
					button.type="button";
					button.value="save";
					button.style.marginLeft="10px";
					button.style.display="none";
					button.addEventListener("click",def.click,false);
				list.appendChild(button);
				var span=$n("span");
					span.style.marginLeft="10px";
					span.style.color="red";
				list.appendChild(span);
				desc.title=text.title=def.title;
			}
		},
		update:function(){
			this.pin=(typeof DEBUG=="undefined")? false:true;
			var self=this,listScrollTop=0,box=$("playlistController");
			if(!box){
				box=$n("div");
				box.id="playlistController";
				box.style.position="fixed";
				box.style.width=playlistWidth+"px";
				box.style.left=this.pin? "0px":4-playlistWidth+"px";
				box.style.top="40px";
				box.style.border="1px solid #CCC";
				box.style.zIndex="100";
				box.style.overflow="auto";
				box.addEventListener("mouseover",function(e){this.style.left="0px";},false);
				box.addEventListener("mouseout",function(e){
					if(self.pin) return;
					var x=e.clientX,y=e.clientY,top=this.offsetTop,height=this.offsetHeight;
					if((x!=-1&&y!=-1)&&(x>=playlistWidth||y<=top||y>=top+height)&&e.relatedTarget) this.style.left=4-playlistWidth+"px";
				},true);
				document.body.appendChild(box);
			}
			else if($("listbox")) listScrollTop=$("listbox").scrollTop;
			box.innerHTML="";
			box.style.backgroundColor=this.color;

			var header=$n("p");
				header.className="playlist-header";
				header.style.margin="2px 10px 0px 10px";
				header.style.fontSize="15px";
				header.style.fontWeight="bold";
				header.innerHTML="Playlist";
			box.appendChild(header);
			var conf=$n("span");
				conf.style.marginLeft="10px";
				conf.style.fontWeight="bold";
				conf.style.color="#C9A9CC";
				conf.innerHTML="Preference";
				conf.style.cursor="pointer";
				conf.addEventListener("click",function(){self.config();},false);
			header.appendChild(conf);

			var hr=$n("hr");
				hr.style.margin="0px 10px 3px 10px";
			box.appendChild(hr);

			var div2=$n("div");
				div2.style.margin="0px 10px";
				div2.style.padding="0px";
				div2.style.cssFloat="left";
			box.appendChild(div2);

			var modeSelect=$n("select");
				modeSelect.style.fontWeight="bold";
				modeSelect.style.border="solid 1px #888";
				modeSelect.title="編集モード";
			var selectDefinition=[
				{caption:"Delete",color:"red",value:"del"},
				{caption:"Move",color:"blue",value:"move"},
				{caption:"Reverse",color:"gray",value:"rev"},
				{caption:"Rename",color:"#804000",value:"rename"}
			];
			for(var i=0; i<selectDefinition.length; i++){
				var def=selectDefinition[i];
				var opt=$n("option");
					opt.value=def.value;
					opt.innerHTML=def.caption;
					opt.style.color=def.color;
					if(def.value==this.mark){
						opt.selected=true;
						modeSelect.style.color=def.color;
					}
					modeSelect.appendChild(opt);				
			}
			modeSelect.addEventListener("change",function(){self.marker(this.value);},false);
			div2.appendChild(modeSelect);
			if(this.playlist.videos.length>0){
				var count=$n("span");
					count.className="font10";
					count.style.verticalAlign="middle";
					count.style.marginLeft="7px";
					count.style.padding="2px 3px 1px 3px";
					count.style.border="solid 1px #888";
					count.style.fontWeight="bold";
					count.style.backgroundColor="white";
					count.innerHTML=this.playlist.videos.length+" items";
				div2.appendChild(count);
			}

			var changerDiv=$n("div");
				changerDiv.style.margin="0px 10px 0px 0px";
				changerDiv.style.padding="0px";
				changerDiv.style.cssFloat="right";
			box.appendChild(changerDiv);

			var listSelect=$n("select");
				listSelect.style.fontWeight="bold";
				listSelect.style.border="solid 1px #888";
			var playlist_all=PlayList.all();
				playlist_all.push("new","delete","duplicate","rename");
			for(var i=0,len=playlist_all.length; i<len; i++){
				var opt=$n("option");
					opt.value=(i<len-4)? playlist_all[i]:"_"+playlist_all[i];
					opt.innerHTML=unescape(playlist_all[i]);
					if(playlist_all[i]==this.playlist.id){
						var selectValue=opt.value;
						opt.selected=true;
					}
					switch(opt.value){
						case "_new":opt.style.color="green"; break;
						case "_delete":opt.style.color="red"; break;
						case "_duplicate":opt.style.color="#886BFD"; break;
						case "_rename":opt.style.color="#804000"; break;
					}
					listSelect.appendChild(opt);				
			}
			listSelect.addEventListener("change",function(){
				var ret,value=this.value,id=unescape(self.playlist.id),indicate="Please enter playlist name to ";
				this.value=selectValue;
				if(value=="_new"){
					this.style.color="green";
					var title=location.href.match(URL.MYLIST)? document.getElementsByTagName("h1")[0].innerHTML:"playlist ("+(PlayList.all().length+1)+")";
					ret=prompt(indicate+"make it.",title);
					(!ret||ret==null||ret=="")? self.update():self.change(escape(ret));
				}
				else if(value=="_delete"){
					this.style.color="red";
					ret=confirm("Do you want to delete \""+id+"\"?");
					if(ret){
						PlayList.del(self.playlist.id);
						self.change(PlayList.all()[0]? PlayList.all()[0]:"default");
					}
					else self.update();
				}
				else if(value=="_duplicate"||value=="_rename"){
					var val=value.slice(1);
					this.style.color=(val=="duplicate")? "#886BFD":"#804000";
					ret=prompt(indicate+val+" it.",id+" copy");
					if(!ret||ret==null||ret==""||(val=="rename"&&ret==id)) self.update();
					else if(PlayList.checker(escape(ret))){
						var c=confirm("\""+ret+"\" already exists.\n\tDo you want to replace it?");
						c? self[val](escape(ret)):self.update();
					}
					else self[val](escape(ret));
				}
				else self.change(value);
			},false);
			changerDiv.appendChild(listSelect);
			if(listSelect.offsetWidth>200) listSelect.style.width="200px";

			var buttons=$n("div");
				buttons.style.margin="0px 10px 4px 10px";
				buttons.style.padding="3px 0px 0px 0px";
				buttons.style.color="#000000";
				buttons.style.clear="both";
			var empty=(this.playlist.videos.length==0);
			var undo=(this.buffer.n==this.buffer.playlist.length||this.buffer.playlist.length==0);
			var redo=(this.buffer.n==0);
			var btnDef=[
				{caption:"add this",title:"この動画を追加",click:function(){
					self.pushThisVideo();
				},unset:!watchPage},
				{caption:"add marked",title:"マークした動画を追加",click:function(){
					self.pushMarkedVideos();
				},unset:watchPage},
				{caption:"add all",title:"ページ内の動画を追加",click:function(){self.pushAllVideos();}},
				{caption:"next",title:"再生",click:function(){
					if(typeof player!="undefined") player.hide();
					self.playNext();
				},disabled:empty},
				{caption:"clear",title:"全て削除",click:function(){self.clear();},disabled:empty},
				{caption:"undo",title:"戻る",click:function(){self.track(true);},disabled:undo},
				{caption:"redo",title:"進む",click:function(){self.track(false);},disabled:redo},
				{caption:"copy",title:"プレイリスト内の動画をコピー",click:function(){
					self.merge=[];
					for(var i=0; i<self.playlist.videos.length; i++) self.merge.push(self.playlist.videos[i]);
					this.nextSibling.disabled=false;
					this.blur();
				},disabled:empty},
				{caption:"merge",title:"コピーした動画を追加",click:function(){
					self.pushVideos(self.merge);
				},disabled:!this.merge[0]},
				{caption:"update",title:"更新",click:function(){self.change(GM_getValue("current"));}}
			];
			for(var i=0; i<btnDef.length; i++){
				var def=btnDef[i];
				if(def.unset) continue;
				var btn=$n("input");
					btn.type="button";
					btn.value=def.caption;
					btn.title=def.title;
					btn.className="_submit";
					btn.style.padding="1px";
				if(def.click) btn.addEventListener("click",def.click,false);
				if(def.disabled) btn.disabled="disabled";
				buttons.appendChild(btn);
			}
			box.appendChild(buttons);

			var listbox=$n("div");
				listbox.style.margin="0px 10px";
				listbox.style.padding="0px";
				listbox.style.border="1px solid #999";
				listbox.style.height="200px";
				listbox.style.backgroundColor="#FFF";
			box.appendChild(listbox);
			var list=$n("ul");
				list.id="listbox";
				list.className="font12";
				list.style.listStyleType="none";
				list.style.margin="0px";
				list.style.padding="0px";
				list.style.width="100%";
				list.style.height="100%";
				list.style.overflow="auto";
			for(var i=0,len=this.playlist.videos.length; i<len; i++){
				var v=this.playlist.videos[i];
				var item=$n("li");
					item.style.padding="2px 5px 2px 0px";
					item.style.whiteSpace="nowrap";

				var linkDef=[
					{caption:"del",title:"左クリック:削除,右クリック:範囲削除",color:"red",click:function(index){
						self.remove(index);
					},context:function(index,e){
						self.remove(index,e.type);
					}},
					{caption:"move",title:"移動",color:"blue",click:function(index){self.move(index);}},
					{caption:"rev",title:"反転",color:"gray",click:function(index){self.reverse(index);}},
					{caption:"rename",title:"リネーム",color:"#804000",click:function(index){
						var ret=prompt("Please enter new video title.",self.playlist.videos[index].title);
						if(ret){
							self.playlist.videos[index].title=ret;
							self.playlist.save();
						}
						self.update();
					}}
				];
				for(var n=0; n<linkDef.length; n++){
					var def=linkDef[n];
					var a=$n("a");
						a.href="javascript:void(0);";
						a.innerHTML="\u2756";
						a.title=def.title;
						a.className=def.caption;
						a.style.color=def.color;
						a.style.display=(self.mark==def.caption)? "inline":"none";
						if(def.click) a.addEventListener("click",(function(f,index){
							return function(){f(index);};
						})(def.click,i),false);
						if(def.context) a.addEventListener("contextmenu",(function(f,index){
							return function(e){
								e.preventDefault();
								e.stopPropagation();
								f(index,e);
							};
						})(def.context,i),true);
					item.appendChild(a);
				}

				var anchor=$n("a");
					anchor.href=v.getPlayUri();
					anchor.innerHTML=v.title;
					anchor.className="watch noadd";
					anchor.style.marginLeft="5px";
					anchor.style.textDecoration="none";
				item.appendChild(anchor);
				if(v.title.indexOf(v.id)>-1||v.title.slice(-3)=="..."){
					anchor.href="javascript:void(0);";
					anchor.title="titleを取得";
					anchor.className+=" fetch";
					anchor.style.color="#A1FE9E";
					anchor.addEventListener("click",(function(index){
						return function(){self.fetch(index);};
					})(i),false);
				}
				list.appendChild(item);
			}

			var checkboxes=$n("div");
				checkboxes.className="font10";
				checkboxes.style.margin="5px 8px 6px 0px";
				checkboxes.style.textAlign="right";
				checkboxes.style.cssFloat="right";
			var checkDef=[
				{caption:"Eco",title:"強制エコノミー",init:"eco",click:function(){
					self._state("eco",this.checked);
				},checked:this.eco},
				{caption:"Com",title:"コメント非表示",init:"com",click:function(){
					self._state("com",this.checked);
				},checked:this.com},
				{caption:"Vol",title:"音量",init:"vol",click:function(){
					self._state("vol",this.checked);
				},checked:this.vol},
				{caption:"Fit",title:"フルスクリーン",init:"fs",click:function(){
					self._state("fs",this.checked);
				},checked:this.fs},
				{caption:"Ran",title:"ランダム",init:"random",click:function(){
					self._state("random",this.checked);
				},checked:this.random},
				{caption:"Loop",title:"ループ",init:"loop",click:function(){
					self._state("loop",this.checked);
				},checked:this.loop},
				{caption:"Cache",title:"キャッシュ削除",init:"cache",click:function(){
					self._state("cache",this.checked);
					if(player) player.state("cache",this.checked);
				},checked:this.cache,unset:!w.NicoCache},
				{caption:"Pause",title:"連続再生の停止",init:"pause",click:function(){
					self.state("pause",this.checked);
				},checked:this.pause},
				{caption:"Pick",title:"リンク抽出",init:"pick",click:function(){
					self.pick=this.checked;
				},checked:this.pick}
			];
			for(var i=0, len=checkDef.length; i<len; i++){
				var def=checkDef[i];
				if(def.unset) continue;
				var chk=$n("input");
					chk.id="playlist_check_"+def.init;
					chk.type="checkbox";
					chk.style.marginTop="0px";
					chk.style.verticalAlign="middle";
				if(def.click) chk.addEventListener("click",def.click,false);
				if(def.disabled) chk.disabled="disabled";
				if(def.checked) chk.checked="checked";
				checkboxes.appendChild(chk);
				var label=$n("label");
					label.htmlFor=chk.id;
					label.innerHTML=def.caption;
					label.style.marginLeft="-2px";
					label.style.marginRight="2px";
					label.style.verticalAlign="middle";
					label.style.border="none";
					label.style.fontWeight="bold";
				checkboxes.appendChild(label);
				chk.title=label.title=def.title;

				if(watchPage&&def.init=="vol"){
					var vol=$n("span");
						vol.className="font12";
						vol.innerHTML="\u266A";
						vol.title="音量を設定";
						vol.style.cursor="pointer";
						vol.addEventListener("click",function(){
							if(player.ext("getVolume")!=false){
								self._state("vl",player.node.ext_getVolume()|0);
								player.driven.vol=true;
								setTimeout(function(){vol.style.color="black";},2000);
								this.style.color="red";
							}
						},true);
					checkboxes.appendChild(vol);
				}
			}
			box.appendChild(checkboxes);
			listbox.appendChild(list);
			if(listScrollTop>0) list.scrollTop=listScrollTop;
		}
	};

var Player=function(){this.initialize.apply(this,arguments);};
	Player.prototype={
		initialize:function(){
			this.driven={id:controller.playlist.id,vol:controller.vol,cache:controller.cache};
			this.vl=GM_getValue("vl_"+controller.playlist.id,false);
			this.node=document.getElementById("flvplayer");
			var self=this;
			w.addEventListener("beforeunload",function(){
				self.hide();
				if(w.NicoCache&&controller.continuity&&self.driven.cache) self.clearCache();
			},true);
			if(controller.extend||controller.continuity) this.ready(arguments[0]);
		},
		ready:function(rc){
			var self=this;
			var timer=setInterval(function(){
				if(self.ext("getLoadedRatio")>0&&self.ext("getPlayheadTime")>=0){
					clearInterval(timer);
					if(controller.scroll!="184") self.scroll(controller.scroll);
					self.defaultVol=self.node.ext_getVolume();
					if(controller.continuity) self.bind();
					if(!controller.auto) setTimeout(function(){self.node.ext_play(1);},controller.delay);
				}
		    },250);
			if(controller.rc>0) this.trap(rc);
			if(controller.continuity) this.observe();
		},
		bind:function(){
			var self=this;
			if(controller.vol) this.node.ext_setVolume(this.vl||this.defaultVol);
			if(controller.com) this.node.ext_setCommentVisible(false);
			if(controller.fs){
				var timer=setInterval(function(){
					if(self.ext("getPlayheadTime")>0){
						clearInterval(timer);
						self.node.ext_setVideoSize("fit");
					}
				},50);
			}
		},
		observe:function(){
			var self=this;
			var timer=setInterval(function(){
				if(controller.pause) return;
				else if(self.ext("getStatus")=="end"&&controller.playlist.id==self.driven.id){
					clearInterval(timer);
					if(controller.playlist.videos.length>0){
						self.hide();
						if(controller.continuity) controller.playNext();
					}
				}
			},1000);
		},
		state:function(e,state){if(controller.playlist.id==this.driven.id) this.driven[e]=state;},
		ext:function(e){
			return (this.node&&typeof this.node["ext_"+e]!="undefined")? this.node["ext_"+e]():false;
		},
		trap:function(rc){
			var self=this,rc=rc*1+1;
			var timer=setInterval(function(){
				if(self.ext("getPlayheadTime")>0) clearInterval(timer);
				else if(rc<=controller.rc&&self.ext("getStatus")=="paused"){
					clearInterval(timer);
					if(controller.continuity) window.name+=" continuity:\""+controller.playlist.id+"\"";
					window.name+=" rc:"+rc;
					self.reload();
				}
		    },50);
		},
		reload:function(){
			this.hide();
			if(w.NicoCache){
				this.driven.cache=false;
				var id=w.Video.id;
				Util.xhr(URL.CACHE+"ajax_rmtmp?"+id,Util.xhr(URL.CACHE+"ajax_rmtmp?"+id+"low"));
			}
			location.reload(true);
		},
		hide:function(){
			if(this.driven.vol&&this.ext("getVolume")!=false){
				if(this.ext("getStatus")=="playing") this.node.ext_play(0);
				this.node.ext_setVolume(this.defaultVol);
			}
			this.node.style.display="none";
		},
		scroll:function(e){
			var target=$x("//div[@id='video_controls']/../..")[0];
			scrollBy(0,Util.getElementPosition(target)-document.documentElement.scrollTop-e);
		},
		clearCache:function(f){
			var next=f? f:function(){},v=w.Video;
			var p=(v.isDeleted||v.isMymemory||w.so.variables.noDeflistAdd||w.gm_CacheProtect)? true:false;
			if(!p) Util.xhr(URL.CACHE+"rm?"+v.id,next());
			else if(w.gm_removeEconomy) Util.xhr(URL.CACHE+"ajax_rm?"+v.id+"low",Util.xhr(URL.CACHE+"ajax_rmtmp?"+v.id+"low",next()));
		}
	};

var login=GM_getValue("login",false);
if(login&&location.href.indexOf("login_form")>-1){
	if($x("//form[@id='login']")[0]&&$("mail").value!=""&&$("password").value!="") $("login").submit();
}
else if(login&&$x("//a[contains(normalize-space(@href),'login_form?')]")[0]){
	location.href=$x("//a[contains(normalize-space(@href),'login_form?')]")[0].href;
}
else{
	var continuity,rc;
	if(continuity=window.name.match(/ continuity:"(.*?)"/)){
		continuity=continuity[1];
		window.name=window.name.replace(/ continuity:".*?"/,"");
	}
	if(rc=window.name.match(/ rc:(\d)/)){
		rc=rc[1];
		window.name=window.name.replace(/ rc:\d/,"");
	}

	var playlistStyle=[
		"#playlistController .font12{font-size:12px; line-height:1.375;}",
		"#playlistController .font10{font-size:10px; line-height:1.25;}",
		"#playlistController a.watch{font-weight:bold;}",
		"#playlistController a.watch:link{color:#666F6F;}",
		"#playlistController a.watch:visited{color:#363F3F;}",
		"#playlistController a.watch:hover,a.watch:active{",
			"color:#FFF; text-decoration:none; background:#666F6F;}",
		"#playlistController a.del,a.move,a.rev,a.rename{margin-left:5px; text-decoration:none; outline:none;",
			"font-weight:bold; font-family:\"ヒラギノ角ゴ Pro W3\",\"メイリオ\",\"ＭＳ Ｐゴシック\",sans-serif;}",
		"#playlistController a.watch.noadd.fetch{color:#A1FE9E !important;}",
		"#playlistController a.watch.noadd.fetch:hover,a.watch.noadd.fetch:active{",
			"color:#FFF !important; background:#A1FE9E !important;}",
		"#playlistController input._submit{",
			"background:#CCC url(http://res.nimg.jp/img/btn/bg_submit_01.gif) repeat-x; font-size:12px;",
			"border-color:#CCC #999 #666; border-style:solid; border-width:1px; margin:0px; padding:2px 4px;}"
	];
	var altStyle=[];
	if(chPage||comPage) altStyle=[
		".playlist-header{padding:3px 0px;}",
		"#playlistController input[type=\"checkbox\"]{margin-left:4px !important;}",
		"#playlistController label{margin-left:2px !important;}"
	];
	else if(dicPage) altStyle=[
		".playlist-header{padding:3px 0px;}",
		"#playlistController p,#playlistController li{text-align:left;}"
	];
	for(var i=0; i<altStyle.length; i++) playlistStyle.push(altStyle[i]);
	GM_addStyle(playlistStyle.join("\n"));

	var playlist=new PlayList(continuity? continuity:GM_getValue("current","default"));
	var controller=new PlayListController(playlist,!!continuity);
	if(watchPage){
		var player=new Player(rc? rc:0);
		w.toggleMaximizePlayer=function(){
			var c=$("playlistController");
			if(w.playerMaximized){
				w.restorePlayer();
				c.style.display="";
			}
			else{
				c.style.display="none";
				w.maximizePlayer();
			}
			w.playerMaximized=!w.playerMaximized;
		};
	}

	w.gm_playlistController={
		getName:function(){return unescape(controller.playlist.id);},
		pushVideo:function(video,title,opt){setTimeout(function(){controller.pushVideo(video,title,opt);},0);},
		pushVideos:function(videos,opt){setTimeout(function(){controller.pushVideos(videos,opt);},0);},
		addParam:function(obj){
			var lp=[];
			for(var i in obj) lp.push(i+"="+obj[i]);
			for(var i=0; i<lp.length; i++) if(controller.lp.indexOf(lp[i])==-1) controller.lp.push(lp[i]);
		},
		state:function(e,opt){
			var id="playlist_check_"+e,check=(typeof opt=="undefined")? !$(id).checked:!!opt;
			controller[e]=$(id).checked=check;
			if(player&&e=="cache") player.state(e,check);
		}
	};

	if(w.$key){
		var keys=[
			{code:78,f:function(){
				if(controller.playlist.videos[0]){
					if(player) player.hide();
					setTimeout(function(){controller.playNext();},0);
				}
			},mod:"shift"},
			{code:80,f:function(){w.gm_playlistController.state("pause");}},
			{code:82,f:function(){
				if(player.node) player.hide();
				if(controller.continuity) window.name+=" continuity:\""+controller.playlist.id+"\"";
				location.reload(true);
			}},
			{code:90,f:function(){
				if(controller.pin) return;
				var c=$("playlistController");
				c.style.left=(c.style.left=="0px")? 4-playlistWidth+"px":"0px";
			}}
		];
		w.$key.unshift("down",keys);
	}
}
