// ==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/mylist*
// @version        0.9.9d.20100125
// @author         castor <castor.4bit@gmail.com>
// ==/UserScript==

(function(){
    //===============================================================
    var NUM_DISPALY     = 100;     // number of display per page

    //===============================================================
    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);

            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 initWatch() {
      var target = $x('//div[@id="WATCHHEADER"]/table//table/tbody/tr');
      
      if (target) {
        var td  = $e('td');
        var p   = $e('p');
        
        var input  = $e('input');
        input.setAttribute('type',  'image');
        input.setAttribute('src',  buttonImage());
        input.addEventListener("click", addMyList, false);
        
        p.setAttribute('style', 'padding-right: 8px;');
        p.appendChild(input);
        td.appendChild(p);
        target.appendChild(td);
        
        // MessageWindow
        var MSG_deflist = $('MSG_deflist');
        var MSG_extlist_message = $e('p');
        
        MSG_extlist_message.setAttribute('id', 'MSG_extlist_message');
        MSG_extlist_message.setAttribute('style', 'background-color: rgb(102, 102, 102); font-size:12px; line-height:1; padding:4px;');
        $('MSG_deflist').appendChild(MSG_extlist_message);
      }
      
      //-----------------------------------------------------------
	    function addMyList() {
        var item = getVideoInfo();
        
        if (item) {
          if (mylist.addItem(item)) {
            popupMessage("拡張マイリストに登録しました(多分)。");
          } else {
            popupMessage("すでに登録されています。");
          }
        } else {
          popupMessage("登録に失敗しました…orz");
        }
	    }

	    function popupMessage(str) {
        $('MSG_deflist_loading').style.display = "none";
        $('MSG_deflist_success').style.display = "none";
        $('MSG_deflist_error'  ).style.display = "none";
        
        $('MSG_extlist_message').innerHTML = str;
        $('MSG_extlist_message').style.display = "block";
        $('MSG_deflist').style.display = "block";
        
        setTimeout(function () {
          $('MSG_deflist').style.display = "none";
          $('MSG_extlist_message').style.display = "none";
          $('MSG_extlist_message').innerHTML = "";
        }, 3000);
	    }
    }

    //===============================================================
    function initMylist() {
      var p = $e('p');
      p.setAttribute('style', 'padding:4px');
      p.setAttribute('class', 'font12');
      p.innerHTML = '<strong>拡張マイリスト</strong>';
      
      var table = $e('table');
      table.setAttribute('width', '288');
      table.setAttribute('cellspacing', '4');
      table.setAttribute('cellpadding', '0');
      table.setAttribute('border', '0');
      table.setAttribute('class', 'font12');
      table.innerHTML = '<tbody><tr>'+
        '<td><a class="folder_1" href="javascript:void(0);" class="extlist_open"><img class="defa1" alt="" src="http://res.nimg.jp/img/_.gif"/></a></td>'+
        '<td width="100%"><a style="text-decoration: none; color: rgb(204, 0, 0);" href="javascript:void(0);" class="extlist_open">拡張マイリスト</a></td>'+
        '</tr>';
      
      var border = $e('div');
      border.setAttribute('style', 'padding: 4px;');
      border.innerHTML = '<p class="dot_2"><img alt="" src="http://res.nimg.jp/img/_.gif"/></p>';
      
      var target = $x('//div[@id="SYS_box_mylistgroups"]/div[2]');
      
      $('SYS_box_mylistgroups').insertBefore(border, target);
      $('SYS_box_mylistgroups').insertBefore(p, target);
      $('SYS_box_mylistgroups').insertBefore(table, target);
      
      var as = $xa('//a[@class="extlist_open"]');
      for (var i in as) {
        as[i].addEventListener('click', loadMyList, false);
      }
      setReloadTimer();
    }

    //-----------------------------------------------------
    function loadMyList() {
      // delete elements
      var _header = $('SYS_box_mylist_header');
      var _body   = $('SYS_box_mylist_body');
      
      removeAllChild(_header);
      removeAllChild(_body);
      
      // title
      document.title = "拡張マイリスト‐ニコニコ動画(9)";
      
      // header
      _header.innerHTML = '<div class="mb8p4">'+
        '<h1>拡張マイリスト</h1>'+
        '<p class="font12">500件を超えても消えてしまいませんが、ニコニコ動画のデザインが変わると悲しいことになります。</p>'+
        '</div>';
      
      
      var list = getFilteredList();
      if (list.length > 0) {
        // navi (pager + tag_list)
        var download = '';
        var item;
        for (var i in mylist.items) {
          item = mylist.items[i];
          download += item.id.match(/^[a-z0-9]+/) +"\t"+ Punycode.decode(item.t) +"\r\n";
        }
        var navi_html = function(c) {
          var _html = '';
          if (c) {
            _html = '<p style="padding: 4px;" class="font12">'
                  +   '<strong>全<span id="mylist_items_length">0</span>件</strong>'
                  +   '&nbsp;'
                  +   '<a style="color:white" href="data:application/octet-stream;base64,'+ Base64.encode(download) +'">ダウンロード</a>'
                  + '</p>';
          }
          _html += '<table width="672" cellspacing="0" cellpadding="4" border="0" class="font12"><tbody><tr>'+
                   '<td>'+
                     '<select class="mylist_select_sort"><option value="1" selected>登録が新しい順</option><option value="2">登録が古い順</option></select> &nbsp;'+
                     'タグ：<select style="width: 120px;" class="mylist_select_tag"></select> &nbsp;'+
                   '</td>'+
                   '<td align="right"><p class="pager"></p>'+
                   '</td></tr>'+
                   '</tbody></table>';
          return _html;
        };
        
        var _navi_header = $e('form');
        _navi_header.setAttribute('class', 'SYS_box_sortpager');
        _navi_header.innerHTML = navi_html(true);
        
        var _navi_footer = $e('form');
        _navi_footer.setAttribute('class', 'SYS_box_sortpager');
        _navi_footer.innerHTML = navi_html(false);
        
        // main list
        var _main = $e('div');
        _main.setAttribute('id', 'extlist_item_list');
        
        _body.appendChild(_navi_header);
        _body.appendChild(_main);
        _body.appendChild(_navi_footer);
        
        // selectbox event handler
        var ts;
        ts = $xa('//select[@class="mylist_select_tag"]');
        for (var i in ts) {
          ts[i].addEventListener('change', onSelectTag, false);
        }
        ts = $xa('//select[@class="mylist_select_sort"]');
        for (var i in ts) {
          ts[i].addEventListener('change', onSelectSort, false);
        }
        
        // reflesh
        renderAll();
        
      } else {
        // no items
        var div = $e('div');
        div.setAttribute('class', 'mb16p4');
        div.innerHTML = '<p class="main_message">拡張マイリストに動画は登録されていません。<br/>好きな動画を登録しましょう！</p>';
        
        _body.appendChild(div);
      }
    }
    
    //-----------------------------------------------------
    function renderAll() {
      var list = getFilteredList();
      
      renderItemList(list);
      renderTagList(list);
      renderPager(list);
      updateItemCount(list.length);
    }
    
    function renderItemList(_list) {
      var list = _list || getFilteredList();
      
      // sort
      var t = $x('//select[@class="mylist_select_sort"]');
      if (t && (t.selectedIndex == 1)) {
        list.reverse();
      }
      
      var target = $('extlist_item_list');
      var limit  = NUM_DISPALY;
      var len    = list.length;
      
      removeAllChild(target);
      for (var i=0; i<len; ++i) {
          if (i < mylist.pos) continue;      // skip offset
          if (limit <= 0)     break;         // show page limit
          
          target.appendChild(getItemInfo(list[i]));
          updateTagLink(list[i]);
          limit--;
      }
      
      // event binding
      var tds = $xa('//td[@class="SYS_box_item_data"]');
      for (var i in tds) {
        var node;
        var n = tds[i];
        
        // popup buttons
        n.addEventListener('mouseover', function() {
            if (!isEditting) {
                var node = $x('div[@class="SYS_box_item_buttons"]', this);
                if (node) {
                    node.style.display = "block";
                }
            }
        }, false);
        // hide buttons
        n.addEventListener('mouseout', function() {
            if (!isEditting) {
                var node = $x('div[@class="SYS_box_item_buttons"]', this);
                if (node) {
                    node.style.display = "none";
                }
            }
        }, false);
        // edit action
        node = $x('div[@class="SYS_box_item_buttons"]//img[@class="SYS_btn_edit_memo"]', n);
        if (node) {
          node.addEventListener('click', beginEdit, false);
        }
        // delete action
        node = $x('div[@class="SYS_box_item_buttons"]//img[@class="SYS_btn_remove_item"]', n);
        if (node) {
          node.addEventListener('click', deleteItem, false);
        }
      }
      isEditting = false;
    }
    
    function renderTagList() {
      var ts = $xa('//select[@class="mylist_select_tag"]');
      
      for (var i in ts) {
        var target = ts[i];
        removeAllChild(target);
        
        // base
        var obj = $e('option');
        obj.value = '-1';
        obj.innerHTML = 'すべて表示';
        obj.selected  = (mylist.showtag == 0);
        target.appendChild(obj);
        
        // tags
      	var len = mylist.tags.length;
      	for (var i=0; i<len; ++i) {
      		obj = $e('option');
      		obj.value     = mylist.tags[i].id;
      		obj.innerHTML = mylist.tags[i].t;
      		obj.selected  = ((i+1) == mylist.showtag);
      		target.appendChild(obj);
      	}
      }
    }
    
    function renderPager(list) {
      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;
      
      //
      var ps = $xa('//p[@class="pager"]');
      for (var i in ps) {
        var node = ps[i];
        removeAllChild(node);
        node.appendChild(getNavigateAllow(offsetPrev, list.length, true));
        node.appendChild( $t(' | ') );
        for (var i=pageBegin; i<pageEnd; ++i) {
          node.appendChild(getPagerItem(i, pageCurrent));
          node.appendChild( $t(' | ') );
        }
        node.appendChild(getNavigateAllow(offsetNext, list.length, false));
      }
    }
    
    //-----------------------------------------------------
    function getFilteredList() {
      var list = ([].concat(mylist.items)).reverse();
      var selected = selectedTagIndex();
      if (selected == null) return list;
      
      var _list = [];
      var len = list.length - 1;
      var tlen;
      var hide;
      
      for (var i=len; i>=0; --i) {
        tlen = list[i].tags.length;
        
        for (var j=0; j<tlen; ++j) {
          if (selected == list[i].tags[j]) {
            _list.unshift(list[i]);
            break;
          }
        }
      }
      return _list;
    }
    
    function selectedTagIndex() {
      if (mylist.showtag > 0) {
          var t = $x('//select[@class="mylist_select_tag"]');
          if (t && (t.selectedIndex >= 0)) {
            return t[t.selectedIndex].value;
          } else {
            var len = mylist.tags.length;
          	for (var i=0; i<len; ++i) {
              if (mylist.showtag == (i+1)) {
                return mylist.tags[i].id;
              }
            }
          }
      }
      return null;
    }
    
    //-----------------------------------------------------
    function setReloadTimer() {
    	if (tm == null) {
	    	tm = setInterval(function () {
          if ($x('//select[@class="mylist_select_tag"]')) {
            mylist.loadList();
            mylist.loadTags();
            renderItemList();
          } else {
            clearInterval(tm);
            tm = null;
          }
        }, 15000);
	    }
    }

    //-----------------------------------------------------
    function getItemInfo(item) {
      // item info
      var vid = item.v || item.id;
      var prefix = vid.substring(0, 2);
      var suffix = vid.substring(2).match(/^[0-9]+/);
      
      var title = Punycode.decode(item.t);
      var comment = escapeString(Punycode.decode(item.c));
      var server = (parseInt(suffix) % 4) + 1;
      
      var img_banner = 'http://res.nicovideo.jp/img/cmn_thumb/from/'+ prefix +'.gif';
      var img_thumb  = 'http://tn-skr'+ server +'.smilevideo.jp/smile?i='+ suffix;
      var img_width  = 96;
      var img_height = 72;
      
      // html
      var div = $e('div');
      div.setAttribute('class', 'SYS_box_item');
      
      div.innerHTML = '<table width="672" cellspacing="0" cellpadding="4" border="0">'+
        '<tbody><tr valign="top"><td><input type="checkbox" name="checkbox" disabled="disabled"/></td>'+
        '<td><p><a href="/watch/'+ vid +'"><img class="video_w96" alt="" src="'+ img_thumb +'"/></a></p></td>'+
        '<td width="100%" class="SYS_box_item_data">'+
        '<div style="position: relative; display: none;" class="SYS_box_item_buttons" id="SYS_box_item_buttons_'+ item.id +'">'+
        '<div style="position: absolute; right: 0pt; top: 0pt;">'+
          '<img title="マイリストコメントを編集" alt="マイリストコメントを編集" class="SYS_btn_edit_memo" id="SYS_btn_edit_memo_'+ item.id +'" src="http://res.nimg.jp/img/common/tilebtn/edit_mylistcomment.png"/>'+
          '<img title="削除" alt="削除" class="SYS_btn_remove_item" id="SYS_btn_remove_item_'+ item.id +'" src="http://res.nimg.jp/img/common/tilebtn/delete.png"/>'+
        '</div></div>'+
        '<p class="font12">'+
          '<strong>'+ getDateFormat(item.reg) +'</strong> 追加 &nbsp; <span id="am_tags_'+ item.id +'"></span>'+
        '</p>'+
        '<h3><a class="video" href="/watch/'+ vid +'">'+ title +'</a></h3>'+
        '<div '+ ((comment.length > 0)? '':'style="display:none" ') +'class="SYS_box_memo_normal" id="SYS_box_memo_normal_'+ item.id +'"><p class="SYS_box_memo">'+ comment +'</p></div>'+
        '</td></tr></tbody></table>'+
        '<div style="padding: 4px;"><p class="dot_2"><img alt="" src="http://res.nimg.jp/img/_.gif"/></p></div>';
      
      return div;
    }

    //-----------------------------------------------------
    function makeEditPanel(item) {
      var html = '<div class="TXT12" style="padding-left:2px; border-bottom:solid 1px #666;">メモ</div>'
               + '<textarea id="am_edit_comment_'+ item.id +'" style="width:98%; margin:2px 0 0.5em 0; padding:2px; border:solid 1px #666; font-size:12px">'
               + escapeString(Punycode.decode(item.c))
               + '</textarea>'
               + '<div class="TXT12" style="padding-left:2px; border-bottom:solid 1px #666;">タグ</div>'
               + '<div><table style="margin: 0 0 1em 1em"><tbody id="am_edit_tags_'+ item.id +'">'
               + '</tbody></table></div>'
               + '<div><table><tbody>'
               +  '<tr>'
               +    '<td class="TXT12" style="border:none; white-space:nowrap;">'
               +      'タグ追加：<input type="text" id="am_append_tag_text_'+ item.id +'" style="border:solid 1px #666;"> '
               +      '<input type="button" id="am_append_tag_button_'+ item.id +'" class="submit" value="追加">'
               +    '</td>'
               +    '<td class="TXT12" style="border:none; white-space:nowrap; width:100%; text-align:right">'
               +      '<input type="button" id="am_editend_'+ item.id +'" style="vertical-align:bottom" class="submit" value="編集を終了する">'
               +    '</td>'
               +  '</tr>'
               + '</tbody></table></div>';
      
  	  var div = $e('div');
      div.setAttribute('id',    'am_editform_'+ item.id );
      div.setAttribute('style', 'margin:4px; padding:4px; border:dotted 1px #666; background-color:#eee; display:none;');
      div.innerHTML = html;
      
    	return div;
    }

    //-----------------------------------------------------
    function _updatePager(list, node) {
        //
        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;
        
        //
        removeAllChild(node);
        node.appendChild(getNavigateAllow(offsetPrev, list.length, true));
        node.appendChild( $t(' | ') );
        for (var i=pageBegin; i<pageEnd; ++i) {
          node.appendChild(getPagerItem(i, pageCurrent));
          node.appendChild( $t(' | ') );
        }
        node.appendChild(getNavigateAllow(offsetNext, list.length, false));
    }

    //-----------------------------------------------------
    function getNavigateAllow(offset, length, prev) {
        var obj;
        
        if (prev) {
            if (mylist.pos > 0) {
                obj = makeActiveLink(offset, '&lt;&lt;', 'SYS_btn_pager_prev');
            } else {
                obj = makeNegativeLink('&lt;&lt;');
           }
        } else {
            if ((mylist.pos + NUM_DISPALY) < length) {
                obj = makeActiveLink(offset, '&gt;&gt;', 'SYS_btn_pager_next');
            } else {
                obj = makeNegativeLink('&gt;&gt;');
            }
        }
        return obj;
    }

    function makeActiveLink(offset, name, cname) {
        var link = $e('a');
        
        link.setAttribute('class', cname);
        link.setAttribute('href', 'javascript:void(0);');
        link.innerHTML = name;
        
        link.addEventListener("click",
            function() {
                mylist.pos = offset;
                renderAll();
                setReloadTimer();
            },
            false
        );
        return link;
    }

    function makeNegativeLink(text) {
        var strong = $e('strong');
        strong.innerHTML = text;
        
        return strong;
    }

    //-----------------------------------------------------
    function getPagerItem(num, curr) {
      var obj;
      var pos = curr;
      
      var offset = mylist.pos + (num - curr) * NUM_DISPALY;
      offset = (offset < 0)? 0 : offset;
      
      if (num == curr) {
        obj = $e('span');
        obj.innerHTML = (num + 1);
      } else {
        obj = $e('a');
        obj.innerHTML = (num + 1);
        obj.setAttribute('href',  'javascript:void(0);');
        obj.setAttribute('class', 'SYS_btn_pager');
        
        obj.addEventListener('click',
            (function(pos) {
                return function() {
                    mylist.pos = pos;
                    renderAll();
                    setReloadTimer();
                };
            })(offset),
            false
        );
      }
      return obj;
    }

    //-----------------------------------------------------
    function updateItemCount(count) {
    	var mylist_items_length = $('mylist_items_length');
        if (mylist_items_length) {
        	mylist_items_length.innerHTML = count;
        }
    }

    //-----------------------------------------------------
    function updateTagLink(item) {
  	  var target = $('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 = $e('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() {
	    						onClickTag(tid);
	    					}
	    				})(item.tags[i]),
	    				false
					);
          
		    	target.appendChild(a);
		    	target.appendChild($t(' '));
      	}
      }
    }

    //-----------------------------------------------------
    function drawEditTagList(item) {
      var id     = item.id;
      var len    = item.tags.length;
      var target = $('am_edit_tags_'+ id);
      
      removeAllChild(target);
      
      if (len > 0) {
      	for (var i=0; i<len; ++i) {
      		var tid   = item.tags[i];
      		var tname = mylist.getTagName(tid);
        	
        	var tr  = $e('tr');
          var td1 = $e('td');
          var td2 = $e('td');
          
          td1.setAttribute('class', 'TXT12');
      	  td1.setAttribute('style', 'border-bottom:dotted 1px #666; padding: 2px 3em 0 4px;');
          td1.innerHTML = '★ '+ tname;
          td2.setAttribute('class', 'TXT12');
      	  td2.setAttribute('style', 'border-bottom:dotted 1px #666;');
          td2.innerHTML = '<input type="button" id="am_delete_tag_'+ id +'_'+ tid +'" class="submit" value="削除">';
          
          tr.appendChild(td1);
          tr.appendChild(td2);
        	target.appendChild(tr);
          $('am_delete_tag_'+ id +'_'+ tid).addEventListener('click', deleteTag, false);
      	}
      } else {
      	var tr = $e('tr');
      	var td = $e('td');
        
      	td.setAttribute('class', 'TXT12');
      	td.setAttribute('style', 'border-bottom:dotted 1px #666; ');
        td.innerHTML = '登録されているタグはありません';
        
      	tr.appendChild(td);
      	target.appendChild(tr);
      }
    }
    
    //-----------------------------------------------------
    function enableButtons() {
      isEditting = false;
    }
    function disableButtons() {
      isEditting = true;
    }


    //===============================================================
    // Event Handler
    //===============================================================
    function beginEdit() {
      var id   = this.id.substring( this.id.lastIndexOf('_') + 1 );
      var idx  = mylist.findItem(id);
      var form = $('am_editform_'+ id);
      
      $('SYS_box_item_buttons_'+ id).style.display = "none";
      $('SYS_box_memo_normal_'+  id).style.display = "none";
      
      
      if (!form) {
      	var memo = $('SYS_box_memo_normal_'+ id);
      	if (memo) {
      		memo.parentNode.insertBefore(makeEditPanel(mylist.items[idx]), memo.nextSibling);
          
          $('am_append_tag_button_'+ id).addEventListener('click', addTag, false);
          $('am_editend_'+           id).addEventListener('click', endEdit, false);
      		form = $('am_editform_'+ id);
      	}
      }
      
      if (form) {
      	clearInterval(tm);		// stop reload timer
      	tm = null;
        
      	drawEditTagList(mylist.items[idx]);
      	disableButtons();				      // disable actions
        form.style.display = 'block';	// open edit form
        
        $('am_append_tag_text_'+ id).focus();
      }
    };

    function endEdit() {
    	var id   = this.id.substring( this.id.lastIndexOf('_') + 1 );
      var form = $('am_editform_'+ id);
      if (form) {
      	var idx = mylist.findItem(id);
      	var cm  = $('am_edit_comment_'+ id);
      	var dsc = $x('//div[@id="SYS_box_memo_normal_'+ id +'"]/p[@class="SYS_box_memo"]');
        
      	if ((idx !== false) && cm && dsc) {
      		mylist.items[idx].c = Punycode.encode(cm.value);
      		mylist.saveList();
          
      		dsc.innerHTML = escapeString(cm.value);
          if (cm.value.length > 0) {
            $('SYS_box_memo_normal_'+ id).style.display = "block";
          }
      	}
        
      	form.style.display = 'none';		// close edit form
      	setReloadTimer();   // start reload timer
      	enableButtons();		// enable actions
      	updateTagLink(mylist.items[idx]);	// update tag link
      }
    };

    function deleteItem() {
        var id  = this.id.substring( this.id.lastIndexOf('_') + 1 );
        var idx = mylist.findItem(id);
        var item = mylist.items[idx];
        var title = Punycode.decode(item.t);
        
        if (w.confirm('「'+ title +'」を削除します。\r\n本当によろしいですか？')) {
            if (mylist.deleteItem(item)) {
                renderItemList();
            }
        }
    };

    function addTag() {
    	var id   = this.id.substring( this.id.lastIndexOf('_') + 1 );
    	var idx  = mylist.findItem(id);
    	var text = $('am_append_tag_text_'+ id);
    	var tag  = text.value;

    	if (mylist.addItemTag(id, tag)) {
    		drawEditTagList(mylist.items[idx]);
        renderTagList();
    	}
    	text.value = '';
    	text.focus();
    };

    function deleteTag() {
    	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)) {
    		drawEditTagList(mylist.items[idx]);
        renderTagList();
    	}
    };

    function onSelectSort() {
      renderItemList();
    }
    function onSelectTag() {
      var t = $x('//select[@class="mylist_select_tag"]');
      if (t) {
	    	mylist.pos = 0;
	    	mylist.showtag = t.selectedIndex;
	    	renderItemList();
	    	setReloadTimer();
      }
    };
    function onClickTag(id) {
      var ts = $xa('//select[@class="mylist_select_tag"]');
      
      for (var i in ts) {
        var t = ts[i];
        
      	if (t) {
      		var len = t.options.length;
      		for (var i=0; i<len; ++i) {
      			t.options[i].selected = (id == t.options[i].value);
      		}
      	}
      }
      onSelectTag();
    };

    //===============================================================
    // Others
    //===============================================================
    function getVideoInfo() {
        var id = '';	// id (watch/XXXXXX)
        var v  = '';	// id (smXXXXXX)
        var t  = '';	// title
        var p  = [      // title tag pattern
            '//h1/a[@class=\'video\']',     // default
            '//input[@name=\'m_title\']',   // nicopon
            '//h1/a'                        // NicoVideo: add download link
        ];

        var m = w.location.href.match(/^http:\/\/.*?\.nicovideo\.jp\/watch\/([a-z0-9]+)/);
        if (m) {
            id = m[1];
        }
        for (var i=0; i<p.length; ++i) {
            var a = document.evaluate(p[i], document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null);
            if (a.singleNodeValue) {
                t = Punycode.encode(a.singleNodeValue.innerHTML);
                if (!t) t = Punycode.encode(a.singleNodeValue.title);
                break;
            }
        }
        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'];
    }

    function removeAllChild(target) {
    	if (!target) return;

        if (target.firstChild) {
            while(target.firstChild) {
                target.removeChild(target.firstChild);
            }
        }
    }
    
    function appendNext(target, element) {
      target.parentNode.insertBefore(element, target.nextSibling);
    }
    
    function escapeString(str) {
    	return str.replace(/&/,'&amp;').replace(/</g,'&lt;').replace(/>/,'&gt;');
    }

    //===============================================================
    // main
    //===============================================================
    new Punycode;
    var mylist = new MyList();
    var isEditting = false;

    if (document.location.href.indexOf('.nicovideo.jp/watch') !=  - 1) {
        initWatch();
    } else {
        initMylist();
    }

    //===============================================================
    function buttonImage() {
      return 'data:image/png;base64,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'+
        'WL76gOHwN6MbY65KEStaAAAAAElFTkSuQmCC';
    }

    //===============================================================
    function $(id) {
      return document.getElementById(id);
    }
    function $e(type) {
      return document.createElement(type);
    }
    function $t(text) {
      return document.createTextNode(text);
    }
    function $x(xpath, node) {
      node = node || document;
      var result = document.evaluate(xpath, node, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null);
      return result.singleNodeValue? result.singleNodeValue : null;
    }
    function $xa(exp, context) { // reference from http://d.hatena.ne.jp/os0x/20081024/1224863727
      context || (context = document);
      var expr = (context.ownerDocument || context).createExpression(exp, function (prefix) {
        return document.createNSResolver(context.documentElement || context).lookupNamespaceURI(prefix) ||
          context.namespaceURI || document.documentElement.namespaceURI || "";
      });
      var result = expr.evaluate(context, XPathResult.ANY_TYPE, null);
      switch (result.resultType) {
      case XPathResult.STRING_TYPE : return result.stringValue;
      case XPathResult.NUMBER_TYPE : return result.numberValue;
      case XPathResult.BOOLEAN_TYPE: return result.booleanValue;
      case XPathResult.UNORDERED_NODE_ITERATOR_TYPE:
        var ret = [], i = null;
				while (i = result.iterateNext()) ret.push(i);
				return ret;
      }
      return null;
    }
    
    //===============================================================
    // http://coderepos.org/share/browser/lang/javascript/Base64/trunk/base64.js
    Base64 = {
      utob: function(uni){
        return uni.replace(/[^\x00-\xFF]/g,
          function(m){
            var n = m.charCodeAt(0);
            return n < 0x800 ? String.fromCharCode(0xc0 | (n >>>  6))
                             + String.fromCharCode(0x80 | (n & 0x3f))
                :              String.fromCharCode(0xe0 | ((n >>> 12) & 0x0f))
                             + String.fromCharCode(0x80 | ((n >>>  6) & 0x3f))
                             + String.fromCharCode(0x80 |  (n         & 0x3f))
                ;
          }
        );
      },
      encode: function(u){ return btoa(Base64.utob(u)) }
    };
    
    //===============================================================
    /*
     * = 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;
      }
    }

})();
