// ==UserScript==
// @name           NicoVideo Additional MyList
// @namespace      http://castor.s26.xrea.com/
// @description    Save the movie list to local settings.
// @include        http://www.nicovideo.jp/watch/*
// @include        http://www.nicovideo.jp/my
// @version        0.7.0.20080105
// @author         castor < castor.4bit@gmail.com>
// 
// mark @title Date:20080120 Temporary, script was modified by andreryu .
// ==/UserScript==

(function(){
    //===============================================================
    var NUM_DISPALY     = 5;		// number of display per page
    var OPEN_NEW_WINDOW = true;		// open new window
    
    
    //===============================================================
    var w = (unsafeWindow || window), document = w.document;
    var tm;		// reload timer
    
    var MyList = function() {
        return MyList.prototype.getInstance();
    }
    MyList.prototype = {
        _inst: null,
        items: null,
        tags:  null,
        pos:     0,
        showtag: 0,
        key_list: 'additional_mylist',
        key_tags: 'additional_mylist_tags',
        
        //-------------------------------------------------
        getInstance: function() {
            if (!MyList.prototype._inst) {
                this.loadList();
                this.loadTags();
                MyList.prototype._inst = this;
            }
            return MyList.prototype._inst;
        },
        
        loadList: function() {
        	this.items = this._load(this.key_list);
        },
        loadTags: function() {
        	this.tags = this._load(this.key_tags);
        },
        
        saveList: function() {
        	this._save(this.key_list, this.items);
        },
        saveTags: function() {
        	this._save(this.key_tags, this.tags);
        },
        
        findItem: function(id) {
            this.loadList();
            return this._find(id, this.items);
        },
        addItem: function(item) {
        	this.loadList();
        	if (this._add(item, this.items)) {
        		this.saveList();
        		return true;
        	}
        	return false;
        },
        deleteItem: function(item) {
        	this.loadList();
        	
        	var len = item.tags.length;
        	for (var i=0; i<len; ++i) {				// delete item tags
        		this.deleteItemTag(item.id, item.tags[i]);
        	}
        	
        	if (this._delete(item, this.items)) {	// delete item
        		this.saveList();
                return true;
        	}
        	return false;
        },
        
        addTag: function(tid, str) {
        	this.loadTags();
        	
        	if (str.length == 0) {	// check string length
        		GM_log('invalid tag name.');
        		return false;
        	}
        	
        	if (this._add(new TagItem(tid, str), this.tags)) {
        		this.saveTags();
        		return true;
        	}
        	return false;
        },
        addItemTag: function(id, str) {
        	var idx = this._find(id, this.items);
        	var max = 100;
        	var tid = -1;
        	var len;
        	
        	len = this.tags.length;
        	for (var i=0; i<len; ++i) {
        		if (max < this.tags[i].id) {
        			max = this.tags[i].id;
        		}
        		if (str == this.tags[i].t) {	// check registered
        			tid = this.tags[i].id;
        			break;
        		}
        	}
        	
        	if (tid < 0) {
        		tid = max + 1;	// set new tag id (current max value + 1)
        		if (!this.addTag(tid, str)) return false;
        	}
        	
        	len = this.items[idx].tags.length;
        	for (var i=0; i<len; ++i) {
        		if (tid == this.items[idx].tags[i]) {
        			return false;
        		}
        	}
        	
        	this.items[idx].tags.push(tid);
    		this.saveList();
    		
    		return true;
        },
        
        deleteTag: function(item) {
        	this.loadTags();
        	
        	if (this._delete(item, this.tags)) {
        		this.saveTags();
                return true;
        	}
        	return false;
        },
        deleteItemTag: function(id, tid) {
        	var cnt = 0;
        	var len = this.items.length;
        	var tlen;
        	
        	this.loadList();
        	for (var i=0; i<len; ++i) {
        		tlen = this.items[i].tags.length;
        		
        		for (var j=0; j<tlen; ++j) {
        			if (tid == this.items[i].tags[j]) {
        				if (id == this.items[i].id) {	// delete from list item
		        			this.items[i].tags.splice(j, 1);
		        			this.saveList();
		        		} else {		// count tag reference
		        			cnt++;
		        		}
		        		break;
		        	}
		        }
		    }
		    
		    if (cnt == 0) {
		    	this.loadTags();
		    	var idx = this._find(tid, this.tags);
		    	if (idx === false) {
		    		return false;
		    	}
	    	    this.tags.splice(idx, 1);
		        this.saveTags();
	        }
	        return true;
        },
        
        getTagName: function(id) {
        	var len = this.tags.length;
        	for (var i=0; i<len; ++i) {
                if (id == this.tags[i].id) {
                    return this.tags[i].t;
                }
            }
        	return false;
        },
        
        _load: function(key, data) {
            var value = GM_getValue(key);
            //GM_log('key:' + key + 'data:' + data + 'value:' + value);    
            
            if (typeof value == 'undefined') {
            	return new Array();
            } else {
            	return eval('('+ value +')');
            }
        },
        _save: function(key, data) {
            GM_setValue(key, data.toSource());
        },
        
        _add: function(item, data) {
            if (this._find(item.id, data) === false) {
                data.push(item);
                return true;
            }
            return false;
        },
        _delete: function(item, data) {
            var idx = this._find(item.id, data);
            if (idx !== false) {
                data.splice(idx, 1);
                return true;
            }
            return false;
        },
        
        _find: function(id, data) {
        	var len = data.length;
            for (var i=0; i<len; ++i) {
                if (id == data[i].id) {
                    return i;
                }
            }
            return false;
        },
    }
    
    var ListItem = function(id, v, title) {
        this.id = id;               // id (watch/XXXXXXXX)
        this.v = v;                 // id (smXXXXXXXX)
        this.t = title;             // title
        this.c = '';                // comment
        this.tags = new Array();    // tags
        this.reg = new Date();      // registered date
    }
    
    var TagItem = function(id, tag) {
        this.id = id;               // id
        this.t  = tag;              // tag name
    }
    
    //===============================================================
    function screenWatch() {
        var mylist_add_status = document.getElementById('mylist_add_status');
        
        if (mylist_add_status) {
            var button = document.createElement('td');
            var input  = document.createElement('input');
            input.setAttribute('type',  'button');
            input.setAttribute('class', 'submit');
            input.setAttribute('value', '拡張リストに登録');
            input.addEventListener("click", addMyList, false);
            button.appendChild(input);
            
            mylist_add_status.parentNode.insertBefore(button, mylist_add_status);
        }
        
        //-----------------------------------------------------------
	    function addMyList() {
	        var item = getVideoInfo();
	        
	        if (item) {
	            if (mylist.addItem(item)) {
	                setMessage("拡張マイリストに登録しました(多分)。");
	            } else {
	                setMessage("すでに登録されています。");
	            }
	        } else {
	            setMessage("登録に失敗しました…orz");
	        }
	    }
	    
	    function setMessage(str) {
	        var mylist_add_status = document.getElementById('mylist_add_status');
	        
	        if (mylist_add_status) {
	            mylist_add_status.innerHTML = str;
	        }
	    }
    }
    
    //===============================================================
    function screenMylist() {
        // Additional List (Header)
        var div  = document.createElement('div');
        
        div.setAttribute('id', 'mylist_additional');
        div.appendChild(makeListHeader());		// header
        div.appendChild(makeListSubHeader());	// sub-header
        div.appendChild(makeListBody());		// body
        
        var mymemory = document.getElementById('MYMEMORY');
        if (mymemory) {
        	mymemory.parentNode.insertBefore(div, mymemory);
        }
        
        updateList();		// Update List
        setReloadTimer();	// reload at regular intervals
    }
    
    //-----------------------------------------------------
    function setReloadTimer() {
    	if (tm == null) {
	    	tm = setInterval(function () {
	            mylist.loadList();
	            mylist.loadTags();
	            updateList();
	        }, 15000);
	    }
    }
    
    //-----------------------------------------------------
    function updateList() {
        var rlist  = ([].concat(mylist.items)).reverse();
        var target = document.getElementById('mylist_additional_body');
        var limit  = NUM_DISPALY;
        
        removeAllChild(target);				// Clear current list
        updateItemCount(rlist.length);		// Update item count
        updateTagSelectBox('mylist_select_tag', true);	// Update tag select box
        
        // tag filtering
        if (mylist.showtag > 0) {
        	var _sel = document.getElementById('mylist_select_tag');
        	var _tid = _sel[_sel.selectedIndex].value;
        	
        	var _len = rlist.length;
        	var _tlen;
        	var _del;
        	for (var i=_len-1; i>=0; --i) {
        		_tlen = rlist[i].tags.length;
        		_del  = true;
        		for (var j=0; j<_tlen; ++j) {
        			if (_tid == rlist[i].tags[j]) {
        				_del = false;
        				break;
        			}
        		}
        		if (_del) {
        			rlist.splice(i, 1);
        		}
        	}
        }
        
        if (rlist.length > 0) {
        	// Redraw item list
        	var len = rlist.length;
            for (var i=0; i<len; ++i) {
                if (i < mylist.pos) continue;      // skip offset
                if (limit <= 0)     break;         // show page limit
                
                target.appendChild(makeVideoThumb(rlist[i], --limit));		// video thumbnail
                updateTagLink(rlist[i]);
            }
            
            // Pagination
            var page_tr = document.createElement('tr');
            var page_td = document.createElement('td');
            
            page_td.setAttribute('colspan', '4');
            page_td.setAttribute('style',   'border-bottom:none');
            
            page_td.appendChild(makePagenation(rlist));
            page_tr.appendChild(page_td);
            target.appendChild(page_tr);
            
        } else {
        	// No Items
            var tr   = document.createElement('tr');
            var td   = document.createElement('td');
            var div  = document.createElement('div');
            
            div.innerHTML = '登録はありません。';
            div.setAttribute('width', '80%');
            div.setAttribute('class', 'TXT12');
            div.setAttribute('style', 'border:1px dotted #888888; background-color:#f8f8f8; marginn:0; padding:1em');
            td.setAttribute('width', '100%');
            td.setAttribute('style', 'text-align: center;');
            td.appendChild(div);
            tr.appendChild(td);
            target.appendChild(tr);
        }
    }
    
    //===============================================================
    function makeListHeader() {
    	var div1 = document.createElement('div');
        var div2 = document.createElement('div');
        var div3 = document.createElement('div');
        
        div2.innerHTML = '<h2>拡張マイリスト</h2>'
                       + '<p class="TXT12">動画のリストをブラウザに保存します。登録数は無制限で、タグによる分類やコメントメモなどが利用できます。</p>';
        
        div1.setAttribute('style', 'padding:4px');
        div2.setAttribute('style', 'padding-bottom:4px; border-bottom:solid 2px #666');
        div3.setAttribute('class', 'mb16p4');
        div1.appendChild(div2);
    	
    	return div1;
    }
    
    function makeListSubHeader() {
    	var table  = document.createElement('table');
        var tbody  = document.createElement('tbody');
        var tr     = document.createElement('tr');
        var td1    = document.createElement('td');
        var td2    = document.createElement('td');
        var tagbox = document.createElement('select');
        
        td1.innerHTML = '登録動画数：&nbsp;<strong id="mylist_items_length">'+ mylist.items.length +'</strong><strong>  ( 最大 ∞ )</strong>';
        td1.setAttribute('class', 'TXT12');
        td1.setAttribute('style', 'vertical-align:top');
        td2.setAttribute('class', 'TXT12');
        td2.setAttribute('nowrap', '');
        td2.setAttribute('align', 'right');
        
        tagbox.setAttribute('id', 'mylist_select_tag');
        tagbox.setAttribute('style', 'border: solid 1px #888;');
        tagbox.addEventListener('change', _change_tag, false);
        
        tbody.setAttribute('id', 'mylist_additional_subheader');
        table.setAttribute('width', '640');
        table.setAttribute('border', '0');
        table.setAttribute('cellpadding', '4');
        table.setAttribute('cellspacing', '0');
        
        td2.appendChild(tagbox);
        tbody.appendChild(td1);
        tbody.appendChild(td2);
        table.appendChild(tbody);
        
        return table;
    }
    
    function makeListBody() {
    	var table = document.createElement('table');
        var tbody = document.createElement('tbody');
        
        table.setAttribute('width', '640');
        table.setAttribute('border', '0');
        table.setAttribute('cellpadding', '4');
        table.setAttribute('cellspacing', '0');
        table.setAttribute('style', 'margin-bottom:16px;');
        table.setAttribute('class', 'sima_table');
        tbody.setAttribute('id', 'mylist_additional_body');
        
        table.appendChild(tbody);
    	
    	return table;
    }
    
    //-----------------------------------------------------
    function makeVideoThumb(item, idx) {
        var tr  = document.createElement('tr');
        var td1 = document.createElement('td');
        var td2 = document.createElement('td');
        var td3 = document.createElement('td');
        var td4 = document.createElement('td');
        
        var vid = item.v || item.id;
        var prefix = vid.substring(0, 2);
        var suffix = vid.substring(2);
        
        var img_banner = 'http://res.nicovideo.jp/img/thumb/from_'+ prefix +'.gif';
        var img_thumb  = 'http://tn-skr.smilevideo.jp/smile?i='+ suffix;
        var img_width  = 62;
        var img_height = 48;
        
        if (vid.indexOf("am") === 0) {
            img_width  = 48;
        }
        var link_target = (OPEN_NEW_WINDOW)? 'target="_blank"' : '';
        
        //-----------------------------------------------------------
        td1.innerHTML = '<p style="margin:4px; width:62px; height:48px">'
                      + '<a href="watch/'+ item.id +'" '+ link_target +'>'
                      + '<img alt="'+ Punycode.decode(item.t) +'" src="'+ img_thumb +'" width="'+ img_width +'" height="'+ img_height +'" class="thumb_img" onerror="this.src=\'img/thumb/del_img.jpg\'">'
                      + '</a></p>';
        td2.innerHTML = '<p><img src="'+ img_banner +'"></p>'
                      + '<p style="margin-top:4px;" class="TXT12">'
                      + '<strong>'+ getDateFormat(item.reg) +'</strong> 登録 &nbsp;&nbsp;'
                      + '<span id="am_tags_'+ item.id +'"></span><br>'
                      + '<h3 style="margin:4px 0px;">'
                      + '<a class="video" href="watch/'+ item.id +'" '+ link_target +'>'+ Punycode.decode(item.t) +'</a>'
                      + '</h3>'
                      + '<p id="am_desc_'+ item.id +'" class="TXT12">'+ escapeString(Punycode.decode(item.c)) +'</p>';
        
        //td2.appendChild(makeEditPanel(item));
        td3.appendChild(makeButton('am_edit_'+ item.id, '編集', _begin_edit));
        td4.appendChild(makeButton('am_del_'+  item.id, '削除', _delete_item));
        
        td2.setAttribute('width', '100%');
        tr.setAttribute('class',  ((idx % 2)? 'odd' : 'even'));
        
        tr.appendChild(td1);
        tr.appendChild(td2);
        tr.appendChild(td3);
        tr.appendChild(td4);
        
        return tr;
    }
    
    //-----------------------------------------------------
    function makeEditPanel(item) {
    	var div_base         = document.createElement('div');		// base
        var div_comment_head = document.createElement('div');		// comment header
        var div_comment      = document.createElement('div');		// comment body
        var div_tags_head    = document.createElement('div');		// tag header
        var div_tags         = document.createElement('div');		// tag body
        var div_footer       = document.createElement('div');		// footer
        
        // base
        div_base.setAttribute('id',    'am_editform_'+ item.id );
        div_base.setAttribute('style', 'margin:4px; padding: 4px; border:solid 1px #666; background-color:#ddd; display:none;');
        
        // comment header
        div_comment_head.innerHTML = 'メモ';
        div_comment_head.setAttribute('style', 'padding-left:2px; border-bottom:solid 1px #666;');
        div_comment_head.setAttribute('class', 'TXT12');
        
        // comment body
        var comment   = document.createElement('textarea');
        comment.value = escapeString(Punycode.decode(item.c));
        comment.setAttribute('id',    'am_edit_comment_'+ item.id );
        comment.setAttribute('style', 'width:95%; margin:2px 2px 0.5em 2px; padding: 2px; border:solid 1px #666; font-size:12px');
        div_comment.appendChild(comment);
        
        // tag header
        div_tags_head.innerHTML = 'タグ';
        div_tags_head.setAttribute('style', 'padding-left: 2px; border-bottom:solid 1px #666;');
        div_tags_head.setAttribute('class', 'TXT12');
        
        // tag body
        var tag_table = document.createElement('table');
        var tag_tbody = document.createElement('tbody');
        tag_tbody.setAttribute('id',    'am_edit_tags_'+ item.id);
        tag_table.setAttribute('style', 'margin: 0 0 1em 1em');
        tag_table.appendChild(tag_tbody);
        div_tags.appendChild(tag_table);
        
        // footer
        var footer_table = document.createElement('table');
        var footer_tbody = document.createElement('tbody');
        var footer_tr    = document.createElement('tr');
        var footer_td1   = document.createElement('td');
        var footer_td2   = document.createElement('td');
        
        var input  = document.createElement('input');
        var button = document.createElement('input');
        var submit = document.createElement('input');
        
        input.setAttribute('id',   'am_append_tag_edit_'+ item.id);
        input.setAttribute('type', 'text');
        button.setAttribute('id',    'am_append_tag_input_'+ item.id);
        button.setAttribute('type',  'button');
        button.setAttribute('class', 'submit');
        button.setAttribute('value', '追加');
        submit.setAttribute('id',    'am_editend_'+ item.id );
        submit.setAttribute('type',  'button');
        submit.setAttribute('class', 'submit');
        submit.setAttribute('style', 'vertical-align:bottom');
        submit.setAttribute('value', '編集を終了する');
        
        button.addEventListener('click', _add_tag, false);
        submit.addEventListener('click', _end_edit, false);
        
        footer_td1.setAttribute('class', 'TXT12');
        footer_td2.setAttribute('class', 'TXT12');
        footer_td1.setAttribute('nowrap', '');
        footer_td2.setAttribute('nowrap', '');
        footer_td1.setAttribute('style', 'border:none;');
        footer_td2.setAttribute('style', 'border:none; width:100%; text-align:right');
        
        footer_td1.appendChild( document.createTextNode('タグ追加：') );
        footer_td1.appendChild( input );
        footer_td1.appendChild( document.createTextNode(' ') );
        footer_td1.appendChild( button );
        
        footer_td2.appendChild(submit);
        footer_tr.appendChild(footer_td1);
        footer_tr.appendChild(footer_td2);
        footer_tbody.appendChild(footer_tr);
        footer_table.appendChild(footer_tbody);
        div_footer.appendChild(footer_table);
        
        div_base.appendChild(div_comment_head);
        div_base.appendChild(div_comment);
        div_base.appendChild(div_tags_head);
        div_base.appendChild(div_tags);
        div_base.appendChild(div_footer);
    	
    	return div_base;
    }
    
    //-----------------------------------------------------
    function makeButton(id, value, func) {
        var form   = document.createElement('form');
        var submit = document.createElement('input');
        var p      = document.createElement('p');
        
        submit.setAttribute('type',  'button');
        submit.setAttribute('class', 'submit');
        submit.setAttribute('value', value);
        submit.setAttribute('id', id);
        
        submit.addEventListener('click', func, false);
        form.addEventListener('submit', function(){ return false; }, false);
        
        p.appendChild(submit);
        form.appendChild(p);
        
        return form;
    }
    
    //-----------------------------------------------------
    function makePagenation(list) {
        var table = document.createElement('table');
        var tbody = document.createElement('tbody');
        var tr  = document.createElement('tr');
        var td1 = document.createElement('td');
        var td2 = document.createElement('td');
        var td3 = document.createElement('td');
        
        var showLink     = 10;
        var showLinkLeft = parseInt(showLink / 2);
        var pos          = mylist.pos;
        
        var pageCurrent = Math.ceil(mylist.pos / NUM_DISPALY);
        var pageBegin   = (pageCurrent - showLinkLeft < 0) ? 0 : (pageCurrent - showLinkLeft);
        var pageMax     = parseInt((list.length - 1) / NUM_DISPALY) + 1;
        var pageEnd     = ((pageBegin + showLink) < pageMax)? (pageBegin + showLink) : pageMax;
        
        if (pageBegin > (pageEnd - showLink)) {
        	pageBegin = ((pageEnd - showLink) < 0)? 0 : (pageEnd - showLink);
        }
        
        var offsetPrev = ((pos - NUM_DISPALY) < 0)? 0 : (pos - NUM_DISPALY);
        var offsetNext = pos + NUM_DISPALY;
        
        td2.setAttribute('class', 'TXT12');
        td1.setAttribute('style', 'border-bottom: none;');
        td2.setAttribute('style', 'border-bottom: none;');
        td3.setAttribute('style', 'border-bottom: none;');
        //table.setAttribute('class', 'mb16auto');
        table.setAttribute('style', 'margin-left:auto; margin-right:0');
        
        td1.appendChild(makeNavigateAllow(offsetPrev, list.length, true));
        td3.appendChild(makeNavigateAllow(offsetNext, list.length, false));
        td2.appendChild(makePaginationList(pageCurrent, pageBegin, pageEnd));
        
        tr.appendChild(td1);
        tr.appendChild(td2);
        tr.appendChild(td3);
        tbody.appendChild(tr);
        table.appendChild(tbody);
        
        return table;
    }
    
    //-----------------------------------------------------
    function makeNavigateAllow(offset, length, prev) {
        var obj;
        
        if (prev) {
            if (mylist.pos > 0) {
                obj = makeActiveLink(offset, 'back');
            } else {
                obj = makeNegativeLink('back');
            }
        } else {
            if ((mylist.pos + NUM_DISPALY) < length) {
                obj = makeActiveLink(offset, 'next');
            } else {
                obj = makeNegativeLink('next');
            }
        }
        
        return obj;
    }
    
    function makeActiveLink(offset, name) {
        var link = document.createElement('a');
        var img  = document.createElement('img');
        
        img.src   = 'http://res.nicovideo.jp/img/common/pager_'+ name +'_on.gif';
        link.href = 'javascript:void(0);';
        link.appendChild(img);
        
        link.addEventListener("click",
            function() {
                mylist.pos = offset;
                updateList();
                setReloadTimer();
            },
            false
        );
        
        return link;
    }
    
    function makeNegativeLink(name) {
        var img = document.createElement('img');
        img.src = 'http://res.nicovideo.jp/img/common/pager_'+ name +'_off.gif';
        
        return img;
    }
    
    //-----------------------------------------------------
    function makePaginationList(curr, begin, end) {
        var base = document.createElement('span');
        var pos  = curr;
        var offset;
        
        for (var i=begin; i<end; ++i) {
            offset = mylist.pos + (i - curr) * NUM_DISPALY;
            offset = (offset < 0)? 0 : offset;
            
            if (i == curr) {
                var span   = document.createElement('span');
                var strong = document.createElement('storong');
                
                strong.innerHTML = (i + 1);
                strong.setAttribute('class', 'pagelink_on');
                span.setAttribute('class',   'pagelink_span');
                span.appendChild(strong);
                base.appendChild(span);
            } else {
                var span = document.createElement('span');
                var a    = document.createElement('a');
                
                a.innerHTML = (i + 1);
                a.setAttribute('href',  'javascript:void(0);');
                a.setAttribute('class', 'pagelink_off');
                span.setAttribute('class', 'pagelink_span');
                span.appendChild(a);
                base.appendChild(span);
                
                a.addEventListener('click',
                    (function(pos) {
                        return function() {
                            mylist.pos = pos;
                            updateList();
                            setReloadTimer();
                        };
                    })(offset),
                    false
                );
            }
        }
        
        return base;
    }
    
    //-----------------------------------------------------
    function updateItemCount(count) {
    	var mylist_items_length = document.getElementById('mylist_items_length');
        if (mylist_items_length) {
        	mylist_items_length.innerHTML = count;
        }
    }
    
    //-----------------------------------------------------
    function updateTagLink(item) {
    	var target = document.getElementById('am_tags_'+ item.id);
        var len    = item.tags.length;
        var str;
        
        removeAllChild(target);
        
        for (var i=0; i<len; ++i) {
        	str = mylist.getTagName(item.tags[i]);
        	if (str) {
        		var a = document.createElement('a');
		    	var t = item.tags[i];
		    	
		    	a.innerHTML = escapeString(str);
		    	a.setAttribute('href',  'javascript:void(0)');
		    	a.setAttribute('style', 'font-size:11px');
		    	a.addEventListener('click',
				    				(function(tid) {
				    					return function() {
				    						_change_tag_click(tid);
				    					}
				    				})(item.tags[i]),
				    				false
								);
		    	
		    	target.appendChild(a);
		    	target.appendChild(document.createTextNode(' '));
        	}
        }
    }
    
    //-----------------------------------------------------
    function updateTagList(id, item) {
        var target = document.getElementById(id);
        var vid    = id.substring( id.lastIndexOf('_') + 1 );
        var len    = item.tags.length;
        
        removeAllChild(target);
        
        if (len > 0) {
        	for (var i=0; i<len; ++i) {
        		var tid   = item.tags[i];
        		var tname = mylist.getTagName(tid);
	        	var tr  = document.createElement('tr');
	        	var td1 = document.createElement('td');
	        	var td2 = document.createElement('td');
	        	var del = document.createElement('input');
	        	
	        	del.setAttribute('id',   'am_delete_tag_'+ vid +'_'+ tid);
	        	del.setAttribute('type', 'button');
	        	del.setAttribute('value', '削除');
	        	del.setAttribute('class', 'submit');
	        	td1.setAttribute('class', 'TXT12');
	        	td2.setAttribute('class', 'TXT12');
	        	td1.setAttribute('style', 'border-bottom:dotted 1px #666; padding: 2px 3em 0 4px;');
	        	td2.setAttribute('style', 'border-bottom:dotted 1px #666;');
	        	
	        	del.addEventListener('click', _delete_tag, false);
	        	
	        	td1.appendChild( document.createTextNode('★ '+ tname) );
	        	td2.appendChild(del);
	        	tr.appendChild(td1);
	        	tr.appendChild(td2);
	        	target.appendChild(tr);
        	}
        } else {
        	var tr = document.createElement('tr');
        	var td = document.createElement('td');
        	
        	td.setAttribute('class', 'TXT12');
        	td.setAttribute('style', 'border-bottom:dotted 1px #666; ');
        	
        	td.appendChild( document.createTextNode('登録されているタグはありません') );
        	tr.appendChild(td);
        	target.appendChild(tr);
        }
    }
    //-----------------------------------------------------
    function updateTagSelectBox(id, setbase) {
    	var target = document.getElementById(id);
    	var vid    = id.substring( id.lastIndexOf('_') + 1 );
    	
        removeAllChild(target);
        
        if (setbase) {
        	var base = document.createElement('option');
	    	base.value = '-1';
	    	base.innerHTML = 'すべて表示';
	    	base.selected  = (mylist.showtag == 0);
	    	target.appendChild(base);
    	} else {
    		if (mylist.tags.length == 0) {
    			target.setAttribute('disable', true);
    		}
    	}
    	
    	var len = mylist.tags.length;
    	for (var i=0; i<len; ++i) {
    		var option = document.createElement('option');
    		option.value     = mylist.tags[i].id;
    		option.innerHTML = mylist.tags[i].t;
    		option.selected  = ((i+1) == mylist.showtag);
    		target.appendChild(option);
    	}
    }
    
    //-----------------------------------------------------
    function enableButtons() {
    	var b = document.evaluate('//input[starts-with(@id, "am_edit_") or starts-with(@id, "am_del_")]', document, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null);
        for (var i=0; i<b.snapshotLength; ++i) {
        	b.snapshotItem(i).removeAttribute('disabled');
        }
    }
    function disableButtons() {
    	var b = document.evaluate('//input[starts-with(@id, "am_edit_") or starts-with(@id, "am_del_")]', document, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null);
        for (var i=0; i<b.snapshotLength; ++i) {
        	b.snapshotItem(i).setAttribute('disabled', true);
        }
    }
    
    
    //===============================================================
    // Event Handler
    //===============================================================
    function _begin_edit() {
    	var id   = this.id.substring( this.id.lastIndexOf('_') + 1 );
        var idx  = mylist.findItem(id);
        var form = document.getElementById('am_editform_'+ id);
        
        if (!form) {
        	var am_desc = document.getElementById('am_desc_'+ id);
        	if (am_desc) {
        		am_desc.parentNode.insertBefore(makeEditPanel(mylist.items[idx]), am_desc.nextSibling);
        		form = document.getElementById('am_editform_'+ id);
        	}
        }
        
        if (form) {
        	clearInterval(tm);		// stop reload timer
        	tm = null;
        	
        	updateTagList('am_edit_tags_'+id, mylist.items[idx]);
        	disableButtons();				// disable actions
	        form.style.display = 'block';	// open edit form
	        
	        document.getElementById('am_append_tag_edit_'+ id).focus();
        }
    };
    
    function _end_edit() {
    	var id   = this.id.substring( this.id.lastIndexOf('_') + 1 );
        var form = document.getElementById('am_editform_'+ id);
        
        if (form) {
        	var idx = mylist.findItem(id);
        	var cm  = document.getElementById('am_edit_comment_'+ id);
        	var dsc = document.getElementById('am_desc_'+ id);
        	
        	if (idx && cm && dsc) {
        		mylist.items[idx].c = Punycode.encode(cm.value);
        		mylist.saveList();
        		
        		dsc.innerHTML = escapeString(cm.value);
        	}
        	
        	form.style.display = 'none';		// close edit form
        	setReloadTimer();					// start reload timer
        	enableButtons();					// enable actions
        	updateTagLink(mylist.items[idx]);	// update tag link
        }
    };
    
    function _delete_item() {
        var id  = this.id.substring( this.id.lastIndexOf('_') + 1 );
        var idx = mylist.findItem(id);
        
        if (w.confirm('本当に削除しますか？')) {
            if (mylist.deleteItem(mylist.items[idx])) {
                updateList();
            } else {
                //GM_log('delete item failed ['+ id +']');
            }
        }
    };
    
    function _add_tag() {
    	var id   = this.id.substring( this.id.lastIndexOf('_') + 1 );
    	var idx  = mylist.findItem(id);
    	var edit = document.getElementById('am_append_tag_edit_'+ id);
    	var tag  = edit.value;
    	
    	if (mylist.addItemTag(id, tag)) {
    		updateTagList('am_edit_tags_'+ id, mylist.items[idx]);
    		updateTagSelectBox('mylist_select_tag', true);
    	} else {
    		GM_log('add item tag failed.');
    	}
    	edit.value = '';
    	edit.focus();
    };
    
    function _delete_tag() {
    	var pos_tid = this.id.lastIndexOf('_');
    	var pos_id  = this.id.lastIndexOf('_', (pos_tid - 1));
    	
    	var tid = this.id.substring( pos_tid + 1 );
    	var id  = this.id.substring( pos_id  + 1, pos_tid );
    	var idx = mylist.findItem(id);
    	
    	if (mylist.deleteItemTag(id, tid)) {
    		updateTagList('am_edit_tags_'+ id, mylist.items[idx]);
    		updateTagSelectBox('mylist_select_tag', true);
    	} else {
    		GM_log("deleteItemTag() failed.");
    	}
    };
    
    function _change_tag() {
    	var t = document.getElementById('mylist_select_tag');
    	if (t) {
	    	mylist.pos = 0;
	    	mylist.showtag = t.selectedIndex;
	    	updateList();
	    	setReloadTimer();
	    }
    };
    function _change_tag_click(id) {
    	var t = document.getElementById('mylist_select_tag');
    	if (t) {
    		var len = t.options.length;
    		for (var i=0; i<len; ++i) {
    			t.options[i].selected = (id == t.options[i].value);
    		}
    	}
    	_change_tag();
    };
    
    //===============================================================
    // Others
    //===============================================================
    function getVideoInfo() {
        var id = '';	// id (watch/XXXXXX)
        var v  = '';	// id (smXXXXXX)
        var t  = '';	// title
        
        var m = w.location.href.match(/^http:\/\/.*?\.nicovideo\.jp\/watch\/([^\/]+)/);
        if (m) {
            id = m[1];
        }
        //var a = document.evaluate('//a[@class=\'video\']', document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null);
        var a = document.evaluate('//input[@name=\'m_title\']', document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null);//@title
        if (a.singleNodeValue) {
            //t = Punycode.encode(a.singleNodeValue.innerHTML);
            t = Punycode.encode(a.singleNodeValue.title);//@title
        //GM_log("t:" + t)
        }
        if (!id || !t) return false;
        
        var at  = document.evaluate('//div[@id=\'video_tags\']/p/a', document, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null);
        var len = at.snapshotLength;
        for (var i=0; i<len; ++i) {
            m = at.snapshotItem(i).href.match(/http:\/\/.*?\.nicovideo\.jp\/tag_edit\/([a-z0-9]+)/);
            if (m && (m[1] != id)) {
                v = m[1];
                break;
            }
        }
        
        return new ListItem(id, v, t);
    }
    
    function getDateFormat(date) {
        var _date = Array();
        _date['year']  = date.getYear();
        _date['month'] = date.getMonth() + 1;
        _date['date']  = date.getDate();
        _date['hour']  = date.getHours();
        _date['min']   = date.getMinutes();
        _date['sec']   = date.getSeconds();
        
        for (var key in _date) {
            if (key != 'year') {
                _date[key]  = (_date[key] < 10)? ('0'+ _date[key]) : _date[key];
            } else {
                _date[key]  = (_date[key] < 2000)? (_date[key] + 1900) : _date[key];
            }
        }
        
        return _date['year']  +'年'
             + _date['month'] +'月'
             + _date['date']  +'日 '
             + _date['hour']  +'：'
             + _date['min']   +'：'
             + _date['sec'];
    }
    
    function removeAllChild(target) {
    	if (!target) return;
    	
        if (target.firstChild) {
            while(target.firstChild) {
                target.removeChild(target.firstChild);
            }
        }
    }
    
    function escapeString(str) {
    	return str.replace(/&/,'&amp;').replace(/</g,'&lt;').replace(/>/,'&gt;');
    }
    
    function removeAd() {
        var ad  = document.evaluate(
            '//div[starts-with(@id, "web_pc_")]',
            document,
            null,
            XPathResult.FIRST_ORDERED_NODE_TYPE,
            null
        );
        
        if (ad.singleNodeValue) {
            ad.singleNodeValue.style.visibility = 'hidden';
            ad.singleNodeValue.style.height  = '0';
            ad.singleNodeValue.style.padding = '0';
            ad.singleNodeValue.style.marginBottom = '0';
        }
    }
    
    
    //===============================================================
    // main
    //===============================================================
    new Punycode;
    var mylist = new MyList();
    
    if (String(document.location).indexOf('www.nicovideo.jp/watch') !=  - 1) {
        screenWatch();
    } else {
        screenMylist();
    }
    //removeAd();  	 // Don't remove the comment indicator :-P
    
    
    //===============================================================
    /*
     * = Punycode for JavaScript
     * 
     * == Abstract
     * 
     * read RFC.
     * http://www.ietf.org/rfc/rfc3492.txt
     * 
     * == Usage
     * 
     * --- Punycode.encode( text )
     * 
     *     encode text to punycode.
     * 
     *      Punycode.encode("\u004D\u0061\u006A\u0069\u3067\u004B\u006F\u0069\u3059\u308B\u0035\u79D2\u524D");
     *      // => 'MajiKoi5-783gue6qz075azm5e'
     * 
     * --- Punycode.decode( punycode )
     * 
     *     decode punycode to text.
     * 
     *      Punycode.encode('MajiKoi5-783gue6qz075azm5e');
     *      // => "\u004D\u0061\u006A\u0069\u3067\u004B\u006F\u0069\u3059\u308B\u0035\u79D2\u524D"
     * 
     * --- Punycode.test( punycode )
     * 
     *     test this software.
     * 
     * Copyright (c)2005 Airemix. All rights reserved.
     * 
     * This software is licensed under BSDL.
     * 
     */
    function Punycode()
    {
      var MAXINT = 0x7fffffff;
      var BASE = 36;
      var TMIN = 1;
      var TMAX = 26;
      var SKEW = 38;
      var DAMP = 700;
      var INITIAL_BIAS = 72;
      var INITIAL_N = 0x80;
      var DELIMITER = 0x2D;
      
      /* basic(cp) tests whether cp is a basic code point: */
      function basic(cp){
        return cp < 0x80;
        // ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789@*_+-./
      }
      
      function decode_digit(cp){
        return  cp < 58 ? cp - 22 :  cp < 91 ? cp - 65 : cp < 123 ? cp - 97 :  BASE;
      }
      
      function encode_digit(d){
        return d < 26 ? d + 97 : d + 22;
      }
      
      /*** Bias adaptation function ***/
      function adapt(delta, numpoints, firsttime){
        var k;
        delta = firsttime ? parseInt(delta / DAMP) : delta >> 1;
        delta += parseInt(delta / numpoints);
        for (k = 0;  delta > ((BASE - TMIN) * TMAX) >> 1;  k += BASE) {
          delta = parseInt(delta / (BASE - TMIN));
        }
        return parseInt(k + (BASE - TMIN + 1) * delta / (delta + SKEW));
      }
      
      /*** Main encode function ***/
      function encode(input){
        if(typeof(input)=='string'){
          var str = input;
          input = new Array;
          for(var i=0; i< str.length; i++)
            input.push(str.charCodeAt(i));
        }
        var output = _encode( input );
        for (var j = 0;  j < output.length;  ++j) {
          var c = output[j];
          if(c >= 0 && c <= 127); else break;
          output[j] = String.fromCharCode(c);
        }
        return output.join('');
      }
      Punycode.encode = encode;
      
      function _encode( input ){
        var output = new Array;
        var n = INITIAL_N;
        var delta = 0;
        var bias = INITIAL_BIAS;
        
        for (j = 0;  j < input.length;  ++j)
          if (basic(input[j])) output.push(input[j]);
        
        var bcp = output.length;
        var handled = bcp;
        if (bcp > 0) output.push(DELIMITER);
        
        while (handled < input.length) {
          var m = MAXINT;
          for (j = 0;  j < input.length;  ++j)
            if (input[j] >= n && input[j] < m) m = input[j];
          if (m - n > (MAXINT - delta) / (handled + 1)) throw 'overflow';
          delta += (m - n) * (handled + 1);
          n = m;
          for (j = 0;  j < input.length;  ++j) {
            if (input[j] < n && ++delta == 0) throw 'overflow';
            if (input[j] == n) {
              var q = delta;
              for (var k = BASE;  ;  k += BASE) {
                var t = k <= bias ? TMIN : k >= bias + TMAX ? TMAX : k - bias;
                if (q < t) break;
                output.push(encode_digit(t + (q - t) % (BASE - t)));
                q = parseInt((q - t) / (BASE - t));
              }
              output.push(encode_digit(q));
              bias = adapt(delta, handled + 1, handled == bcp);
              delta = 0;
              ++handled;
            }
          }
          ++delta, ++n;
        }
        return output;
      }
      
      /*** Main decode function ***/
      function decode(input)
      {
        if(typeof(input)=='string'){
          var str = input;
          input = new Array;
          for(var i=0; i< str.length; i++)
            input.push(str.charCodeAt(i));
        }
        var output = _decode( input );
        for (var j = 0;  j < output.length;  ++j)
          output[j] = String.fromCharCode(output[j]);
        return output.join('');
      }
      Punycode.decode = decode;
      
      function _decode( input )
      {
        var j;
        var output = new Array;
        var n = INITIAL_N;
        var bias = INITIAL_BIAS;
        var bcp = 0;
      
        for (j = input.length;  j >= 0;  --j)
          if (input[j] == DELIMITER){ bcp = j; break; }
        for (j = 0;  j < bcp;  ++j) {
          if (!basic(input[j])) throw 'bad_input';
          output.push( input[j] );
        }
        var i = 0;
        var inp = bcp > 0 ? bcp + 1 : 0;
        while( inp < input.length ){
          var oldi = i;
          for (var w = 1, k = BASE;  ;  k += BASE) {
            if (inp >= input.length) throw 'bad_input';
            var digit = decode_digit(input[inp++]);
            if (digit >= BASE) throw 'bad_input';
            if (digit > (MAXINT - i) / w) throw 'overflow';
            i += digit * w;
            var t = k <= bias ? TMIN : k >= bias + TMAX ? TMAX : k - bias;
            if (digit < t) break;
            if (w > MAXINT / (BASE - t)) throw 'overflow';
            w *= (BASE - t);
          }
          bias = adapt(i - oldi, output.length + 1, oldi == 0);
          oldi = parseInt(i / (output.length + 1));
          if (oldi > MAXINT - n) throw 'overflow';
          n += oldi;
          i %= (output.length + 1);
          output.splice(i++,0,n);
        }
        return output;
      }
    }

})();

//GM_log("is_fns:" + typeof(fns) + "\n");
//fns();
//for (var v in fns.prototype)
//    GM_log("fns:" + v + "\n");
