/**
 * Mylist Page
 * 
 * @requires jQuery
 * @requires jQuery.history
 * @requires Prototype
 * @requires Jarty
 * @requires NicoAPI
 * @copyright 2009 DWANGO Co., LTD. All rights reserved.
 */

(function($) {

  /* 設定 */

  var config = {
    items_per_page : 100, // 1ページあたりのアイテム数.
    new_mylistgroup_name : "新しいマイリスト(%d)", // マイリストグループを新規作成する時の名前.
    first_mylistgroup_description : // 初めてマイリストグループを作った時のメモ.
    "このスペースはメモ欄としてご自由にお使い下さい！",
    title_matcher : /( ?‐[^‐]+)$/, // タイトルを置き換えるための正規表現.
    title_mylist_prefix : "", // マイリストのタイトルのプリフィクス.
    message_stay_time : 3000
  // メッセージを表示する時間 (msec)
  };

  var messages = {
    confirm_remove_mylistgroup : "本当にこのマイリストを削除してもよろしいですか？\n\n※この操作は取り消せません！",
    confirm_remove_mylist : "本当に削除してもよろしいですか？",
    copy_success : Jarty.compile("{$total} 件をコピーしました"),
    copy_duplicates : Jarty.compile("{if $total > $dup}{$total} 件のうち {$dup} 件は{/if}すでにコピー先に存在しているためコピーできませんでした"),
    move_success : Jarty.compile("{$total} 件を移動しました"),
    move_duplicates : Jarty.compile("{if $total > $dup}{$total} 件のうち {$dup} 件は{/if}すでに移動先に存在しているため移動できませんでした"),
    remove_success : Jarty.compile("{$total} 件を削除しました"),
    nonexist_mylistgroup : "このマイリストは削除されています",
    maintenance : "メンテナンス中により利用できません",
    api_failure : {
      "NOAUTH" : "ログインしていません\nお手数ですがページを更新してください",
      "INVALIDTOKEN" : "別のユーザーでログインしています\nお手数ですがページを更新してください",
      "EXPIRETOKEN" : "ページの有効期限が過ぎました\nお手数ですがページを更新してください",
      "INTERNALERROR" : "処理の実行に失敗しました\nお手数ですがページを更新してください",
      "NONEXIST" : "存在しないデータに対して処理を行いました\nお手数ですがページを更新してください",
      "EXIST" : "すでに登録されています",
      "MAXERROR" : "上限に達しているため処理を実行できませんでした",
      "UNKNOWN" : "通信中にエラーが発生しました\nお手数ですがページを更新してください"
    }
  };

  // アイテム種別 (ID => 種別名)
  var itemtypes = {
    0 : "video",
    5 : "seiga",
    6 : 'book',
	13: 'blomagaarticle'
  };

  // マイリストアイテムのソート順 (ID => 比較関数)
  var itemsorts = {
    0 : item_comparer([ "create_time" ], false, true),
    1 : item_comparer([ "create_time" ], true, true),
    2 : item_comparer([ "description" ], false),
    3 : item_comparer([ "description" ], true),
    4 : item_comparer([ "title" ], false),
    5 : item_comparer([ "title" ], true),
    6 : item_comparer([ "item_data.first_retrieve", "item_data.create_time" ], true, true),
    7 : item_comparer([ "item_data.first_retrieve", "item_data.create_time" ], false, true),
    8 : item_comparer([ "item_data.view_counter", "item_data.view_count" ], true, true),
    9 : item_comparer([ "item_data.view_counter", "item_data.view_count" ], false, true),
    10 : item_comparer([ "item_data.update_time", 'item_data.commented_time' ], true, true),
    11 : item_comparer([ "item_data.update_time", 'item_data.commented_time' ], false, true),
    12 : item_comparer([ "item_data.num_res", "item_data.comment_count" ], true, true),
    13 : item_comparer([ "item_data.num_res", "item_data.comment_count" ], false, true),
    14 : item_comparer([ "item_data.mylist_counter", "item_data.mylist_count" ], true, true),
    15 : item_comparer([ "item_data.mylist_counter", "item_data.mylist_count" ], false, true),
    16 : item_comparer([ "item_data.length_seconds" ], true, true),
    17 : item_comparer([ "item_data.length_seconds" ], false, true),
    18 : item_comparer([ "item_data.res_count" ], true, true),
    19 : item_comparer([ "item_data.res_count" ], false, true)
  };

  // マイリストグループのソート順 (ID => 比較関数)
  var groupsorts = {
    0 : group_comparer("create_time", false),
    1 : group_comparer("create_time", true),
    2 : group_comparer("name", true),
    3 : group_comparer("name", false),
    4 : group_comparer("description", true),
    5 : group_comparer("description", false)
  };

  /* グローバル変数 */

  var my = {
    groups : undefined, // マイリストグループの配列
    group : {}, // グループIDをキー, マイリストグループを値とする辞書
    deflist : undefined, // デフォルトマイリストのアイテム配列
    mylist : {}, // グループIDをキー, マイリストのアイテム配列を値とする辞書
    mymemory : undefined, // マイメモリーのアイテム配列
    currentPage : undefined, // 現在のページ
    currentGroup : undefined, // 現在選択されているマイリストグループ
    currentItems : undefined
  // 現在選択されているグループのアイテム配列
  };

  Jarty.globals({
    config : config,
    my : my
  });

  /* グローバルイベント */

  $.each([ "nicoPageChanged", // 表示しているページが変更された
  "nicoMylistGroupListLoaded", // マイリストグループの一覧が読み込まれた
  "nicoMylistGroupListSorted", // マイリストグループの一覧がソートされた
  "nicoMylistGroupCreated", // マイリストグループが新規作成された
  "nicoMylistGroupUpdated", // マイリストグループが更新された
  "nicoMylistGroupRemoved", // マイリストグループが削除された
  "nicoMylistGroupEditOpened", // マイリストグループの編集フォームが開かれた
  "nicoMylistGroupEditClosed", // マイリストグループの編集フォームが閉じられた
  "nicoDeflistLoaded", // デフォルトマイリストが読み込まれた
  "nicoDeflistUpdated", // デフォルトマイリストのアイテムが更新された
  "nicoDeflistMoved", // デフォルトマイリストのアイテムが移動された
  "nicoDeflistCopied", // デフォルトマイリストのアイテムがコピーされた
  "nicoDeflistRemoved", // デフォルトマイリストのアイテムが削除された
  "nicoMylistLoaded", // マイリストが読み込まれた
  "nicoMylistUpdated", // マイリストのアイテムが更新された
  "nicoMylistMoved", // マイリストのアイテムが移動された
  "nicoMylistCopied", // マイリストのアイテムがコピーされた
  "nicoMylistRemoved", // マイリストのアイテムが削除された
  "nicoMymemoryLoaded", // マイメモリーが読み込まれた
  "nicoMymemoryRemoved", // マイメモリーのアイテムが削除された
  "nicoGlobalError", // エラーが発生した
  "nicoGlobalWarning", // 警告が発生した
  "nicoGlobalInfo" // 情報が発生した
  ], function(i, evt) {
    $.fn[evt] = function(f) {
      return this.bind(evt, f);
    };
  });

  /* モデル */

  var MylistGroup = {

    preload : function(mylistgroups) {
      var self = this;
      my.groups = mylistgroups;
      $.each(my.groups, function(i, group) {
        my.group[group.id] = self._fixGroup(group);
      });
      $(function() {
        $.event.trigger("nicoMylistGroupListLoaded", [ my.groups ]);
      });
    },

    preloadSingle : function(groupId, group) {
      my.group[groupId] = this._fixGroup(group);
    },

    load : function(callback) {
      if (this.maintenance) {
        my.groups = [];
        $.event.trigger("nicoMylistGroupListLoaded", [ my.groups ]);
        callback && callback(my.groups);
        return;
      }
      var self = this;
      if (!my.groups) {
        NicoAPI.MylistGroup.list().success(function(data) {
          my.groups = data.mylistgroup;
          $.each(my.groups, function(i, group) {
            my.group[group.id] = self._fixGroup(group);
          });
          $.event.trigger("nicoMylistGroupListLoaded", [ my.groups ]);
          callback && callback(my.groups);
        });
      } else {
        callback && callback(my.groups);
      }
    },

    get : function(groupId, callback) {
      if (this.maintenance) {
        $.event.trigger("nicoGlobalError", [ messages.maintenance ]);
        return;
      }
      if (!my.group[groupId]) {
        var self = this;
        NicoAPI.MylistGroup.get(groupId).success(function(data) {
          var group = self._fixGroup(data.mylistgroup);
          self._updateGroup(group);
          callback && callback(group);
        });
      } else {
        callback && callback(my.group[groupId]);
      }
    },

    update : function(groupId, dict, callback) {
      if (this.maintenance) {
        $.event.trigger("nicoGlobalError", [ messages.maintenance ]);
        return;
      }
      var self = this;
      NicoAPI.MylistGroup.update(groupId, dict.name, dict.description, dict["public"], dict.default_sort, dict.icon_id)
          .success(function() {
            var newGroup = my.group[groupId] || {};
            for ( var key in dict) {
              if (dict[key] === undefined)
                continue;
              if (key == "name" || key == "description")
                newGroup[key] = String.interpret(dict[key]).escapeHTML();
              else
                newGroup[key] = dict[key];
            }
            newGroup = self._fixGroup(newGroup);
            self._updateGroup(newGroup);
            callback && callback(newGroup);
          });
    },

    create : function(callback) {
      if (this.maintenance) {
        $.event.trigger("nicoGlobalError", [ messages.maintenance ]);
        return;
      }
      var self = this;
      this.load(function(groups) {
        var maximum = 0, sort_order = 0, match;
        var mask = new RegExp(config.new_mylistgroup_name.replace(/([.\\+*?\[^\]$(){}=!<>|:-])/g, "\\$1").replace(/%d/,
            "(\\d+)"));
        $.each(groups, function(i, group) {
          if (match = mask.exec(group.name)) {
            maximum = Math.max(maximum, parseInt(match[1]));
          }
          sort_order = Math.max(sort_order, parseInt(group.sort_order) || 0);
        });
        var name = config.new_mylistgroup_name.replace(/%d/, maximum + 1);
        var description;
        if (!my.groups || my.groups.length == 0) {
          description = config.first_mylistgroup_description;
        }
        NicoAPI.MylistGroup.add(name, description, undefined, 1).success(function(data) {
          var newGroup = {
            id : data.id,
            name : name,
            description : description,
            public : 0,
            default_sort : 1,
            create_time : (new Date()).getTime(),
            update_time : (new Date()).getTime(),
            sort_order : sort_order + 1,
            icon_id : 0
          };
          my.groups.unshift(newGroup);
          my.group[newGroup.id] = newGroup;
          $.event.trigger("nicoMylistGroupCreated", [ newGroup ]);
          callback && callback(newGroup);
        });
      });
    },

    remove : function(groupId, callback) {
      if (this.maintenance) {
        $.event.trigger("nicoGlobalError", [ messages.maintenance ]);
        return;
      }
      var self = this;
      this.load(function(groups) {
        NicoAPI.MylistGroup.remove(groupId).success(function(data) {
          my.groups = $.grep(my.groups, function(group) {
            return (group.id != groupId);
          });
          delete my.group[groupId];
          $.event.trigger("nicoMylistGroupRemoved", [ groupId ]);
          callback && callback();
        });
      });
    },

    updateOrder : function(groupIds, callback) {
      if (this.maintenance) {
        $.event.trigger("nicoGlobalError", [ messages.maintenance ]);
        return;
      }
      if (groupIds.length == 0) {
        return callback && callback();
      }
      NicoAPI.MylistGroup.sort(groupIds).success(function(data) {
        if (my.groups) {
          var newGroups = [];
          $.each(groupIds, function(i, groupId) {
            newGroups.push(my.group[groupId]);
          });
          my.groups = newGroups;
          $.event.trigger("nicoMylistGroupListSorted", [ my.groups ]);
        }
        callback && callback();
      });
    },

    _updateGroup : function(group) {
      my.group[group.id] = group;
      $.each(my.groups, function(i, g) {
        if (g.id == group.id) {
          my.groups[i] = group;
          return false;
        }
      });
      $.event.trigger("nicoMylistGroupUpdated", [ group ]);
    },

    _fixGroup : function(group) {
      group["id"] = parseInt(group["id"]);
      group["public"] = (group["public"] == "1");
      group["default_sort"] = parseInt(group["default_sort"]) || 0;
      group["sort_order"] = parseInt(group["sort_order"]) || 0;
      group["icon_id"] = parseInt(group["icon_id"]) || 0;
      return group;
    }

  };

  var Deflist = {

    preload : function(deflist) {
      my.deflist = deflist;
      $(function() {
        $.event.trigger("nicoDeflistLoaded", [ my.deflist ]);
      });
    },

    list : function(callback) {
      if (this.maintenance) {
        callback && callback(my.deflist = []);
        return;
      }
      if (!my.deflist) {
        NicoAPI.Deflist.list().success(function(data) {
          my.deflist = data.mylistitem;
          $.event.trigger("nicoDeflistLoaded", [ my.deflist ]);
          callback && callback(my.deflist);
        });
      } else {
        callback && callback(my.deflist);
      }
    },

    update : function(itemType, itemId, description, callback) {
      if (this.maintenance) {
        $.event.trigger("nicoGlobalError", [ messages.maintenance ]);
        return;
      }
      NicoAPI.Deflist.update(itemType, itemId, description).success(function(data) {
        description = String.interpret(description).escapeHTML();
        if (my.deflist) {
          $.each(my.deflist, function(i, item) {
            if (item.item_type == itemType && item.item_id == itemId) {
              item.description = description;
              return false;
            }
          });
        }
        $.event.trigger("nicoDeflistUpdated", [ itemType, itemId, description ]);
        callback && callback();
      });
    },

    move : function(targetGroupId, idMap, callback) {
      if (this.maintenance) {
        $.event.trigger("nicoGlobalError", [ messages.maintenance ]);
        return;
      }
      this.list(function(items) {
        NicoAPI.Deflist.moveMulti(targetGroupId, idMap).success(function(data) {
          my.deflist = removeItemsByAPIResult(items, data);
          if (my.currentItems === items)
            my.currentItems = my.deflist;
          delete my.mylist[targetGroupId];
          $.event.trigger("nicoDeflistMoved", [ targetGroupId, idMap, data ]);
          callback && callback(data);
        });
      });
    },

    copy : function(targetGroupId, idMap, callback) {
      if (this.maintenance) {
        $.event.trigger("nicoGlobalError", [ messages.maintenance ]);
        return;
      }
      this.list(function(items) {
        NicoAPI.Deflist.copyMulti(targetGroupId, idMap).success(function(data) {
          delete my.mylist[targetGroupId];
          $.event.trigger("nicoDeflistCopied", [ targetGroupId, idMap, data ]);
          callback && callback(data);
        });
      });
    },

    remove : function(idMap, callback) {
      if (this.maintenance) {
        $.event.trigger("nicoGlobalError", [ messages.maintenance ]);
        return;
      }
      this.list(function(items) {
        NicoAPI.Deflist.removeMulti(idMap).success(function(data) {
          my.deflist = removeItemsByIdMap(items, idMap);
          if (my.currentItems === items)
            my.currentItems = my.deflist;
          $.event.trigger("nicoDeflistRemoved", [ idMap ]);
          callback && callback(data.delete_count);
        });
      });
    }

  };

  var Mylist = {

    preload : function(groupId, mylistitem) {
      my.mylist[groupId] = mylistitem;
      $(function() {
        $.event.trigger("nicoMylistLoaded", [ mylistitem ]);
      });
    },

    list : function(groupId, callback) {
      if (this.maintenance) {
        callback && callback(my.mylist[groupId] = []);
        return;
      }
      if (!my.mylist[groupId]) {
        NicoAPI.Mylist.list(groupId).success(function(data) {
          my.mylist[groupId] = data.mylistitem;
          $.event.trigger("nicoMylistLoaded", [ data.mylistitem ]);
          callback && callback(data.mylistitem);
        });
      } else {
        callback && callback(my.mylist[groupId]);
      }
    },

    update : function(groupId, itemType, itemId, description, callback) {
      if (this.maintenance) {
        $.event.trigger("nicoGlobalError", [ messages.maintenance ]);
        return;
      }
      NicoAPI.Mylist.update(groupId, itemType, itemId, description).success(function(data) {
        description = String.interpret(description).escapeHTML();
        if (my.mylist[groupId]) {
          $.each(my.mylist[groupId], function(i, item) {
            if (item.item_type == itemType && item.item_id == itemId) {
              item.description = description;
              return false;
            }
          });
        }
        $.event.trigger("nicoMylistUpdated", [ groupId, itemType, itemId, description ]);
        callback && callback();
      });
    },

    move : function(groupId, targetGroupId, idMap, callback) {
      if (this.maintenance) {
        $.event.trigger("nicoGlobalError", [ messages.maintenance ]);
        return;
      }
      this.list(groupId, function(items) {
        NicoAPI.Mylist.moveMulti(groupId, targetGroupId, idMap).success(function(data) {
          my.mylist[groupId] = removeItemsByAPIResult(items, data);
          if (my.currentItems === items)
            my.currentItems = my.mylist[groupId];
          delete my.mylist[targetGroupId];
          $.event.trigger("nicoMylistMoved", [ groupId, targetGroupId, idMap, data ]);
          callback && callback(data);
        });
      });
    },

    copy : function(groupId, targetGroupId, idMap, callback) {
      if (this.maintenance) {
        $.event.trigger("nicoGlobalError", [ messages.maintenance ]);
        return;
      }
      this.list(groupId, function(items) {
        NicoAPI.Mylist.copyMulti(groupId, targetGroupId, idMap).success(function(data) {
          delete my.mylist[targetGroupId];
          $.event.trigger("nicoMylistCopied", [ groupId, targetGroupId, idMap, data ]);
          callback && callback(data);
        });
      });
    },

    remove : function(groupId, idMap, callback) {
      if (this.maintenance) {
        $.event.trigger("nicoGlobalError", [ messages.maintenance ]);
        return;
      }
      this.list(groupId, function(items) {
        NicoAPI.Mylist.removeMulti(groupId, idMap).success(function(data) {
          my.mylist[groupId] = removeItemsByIdMap(items, idMap);
          if (my.currentItems === items)
            my.currentItems = my.mylist[groupId];
          $.event.trigger("nicoMylistRemoved", [ groupId, idMap ]);
          callback && callback(data.delete_count);
        });
      });
    }

  };

  var Mymemory = {

    preload : function(mymemory) {
      my.mymemory = mymemory;
      $(function() {
        $.event.trigger("nicoMymemoryLoaded", [ my.mymemory ]);
      });
    },

    list : function(callback) {
      if (!my.mymemory) {
        NicoAPI.Mymemory.list().success(function(data) {
          my.mymemory = data.mymemory;
          $.event.trigger("nicoMymemoryLoaded", [ my.mymemory ]);
          callback && callback(my.mymemory);
        });
      } else {
        callback && callback(my.mymemory);
      }
    },

    remove : function(idMap, callback) {
      this.list(function(items) {
        NicoAPI.Mymemory.removeMulti(idMap).success(function(data) {
          my.mymemory = removeItemsByIdMap(items, idMap);
          if (my.currentItems === items)
            my.currentItems = my.mymemory;
          $.event.trigger("nicoMymemoryRemoved", [ idMap ]);
          callback && callback(data.delete_count);
        });
      });
    },

    gotoEditPage : function(itemId) {
      location.href = "/watch/" + itemId + "?edit=mymemory";
    }

  };

  /* コントローラ */

  var Page = {

    MYLIST : "mylist",
    DEFLIST : "deflist",
    MYMEMORY : "mymemory",
    MAINTENANCE : "maintenance",

    dispatch : function(path) {
      if (!my.groups) {
        MylistGroup.load(function() {
          Page.dispatch(path);
        });
        return;
      }

      var pageId = path[0] || "home", groupId;
      switch (pageId) {
      case "home":
      case Page.DEFLIST:
        this.showDeflist();
        return;
      case Page.MYMEMORY:
        this.showMymemory();
        return;
      }

      if (groupId = parseInt(pageId)) {
        switch (path[1]) {
        case 'edit':
          this.editMylist(groupId);
          break;
        default:
          this.showMylist(groupId);
          break;
        }
      }
    },

    showDeflist : function(callback) {
      if (Deflist.maintenance) {
        $.event.trigger("nicoPageChanged", [ Page.MAINTENANCE, null, null ]);
        return;
      }
      Deflist.list(function(items) {
        my.currentPage = Page.DEFLIST;
        my.currentGroup = null;
        my.currentItems = items;
        $.event.trigger("nicoPageChanged", [ Page.DEFLIST, null, items ]);
        callback && callback(items);
      });
    },

    showMymemory : function(callback) {
      Mymemory.list(function(items) {
        my.currentPage = Page.MYMEMORY;
        my.currentGroup = null;
        my.currentItems = items;
        $.event.trigger("nicoPageChanged", [ Page.MYMEMORY, null, items ])
        callback && callback(items);
      });
    },

    showMylist : function(groupId, callback) {
      if (Mylist.maintenance) {
        $.event.trigger("nicoPageChanged", [ Page.MAINTENANCE, null, null ]);
        return;
      }
      Mylist.list(groupId, function(items) {
        if (!my.group[groupId]) {
          $.event.trigger("nicoGlobalError", [ messages.nonexist_mylistgroup ]);
          return;
        }
        my.currentPage = Page.MYLIST;
        my.currentItems = items;
        var group = my.currentGroup = my.group[groupId];
        $.event.trigger("nicoPageChanged", [ Page.MYLIST, group, items ]);
        callback && callback(group, items);
      });
    },

    gotoShowDeflist : function() {
      $.hashpath([ "home" ]);
    },
    gotoShowMylist : function(groupId) {
      $.hashpath([ groupId ]);
    },
    gotoEditMylist : function(groupId, create) {
      $.hashpath([ groupId, "edit" ], {
        create : create ? 1 : ""
      });
    },

    editMylist : function(groupId) {
      this.showMylist(groupId, function(group) {
        $.event.trigger("nicoMylistGroupEditOpened", [ group ]);
      });
    },
    closeEditMylist : function(group) {
      $.event.trigger("nicoMylistGroupEditClosed", [ group ]);
      this.gotoShowMylist(group.id);
    }

  };

  /* 初期化処理 */

  $(function() {

    $("#SYS_box_loading").nicoApiStart(function() {
      $(this).show();
    }).nicoApiStop(function() {
      $(this).hide();
    });

    var timer = null;
    function updateMessage(message, className, nearbyElement) {
      if (className == "error") {
        alert(message);
      } else {
        if (timer)
          clearTimeout(timer);
        var box = $("#SYS_box_message");
        $("p", box).html(message.replace(/\n/g, "<br>"));
        box.attr("class", className);

        if (nearbyElement) {
          var top = $(nearbyElement).offset().top - box.parent().offset().top;
          box.css({
            top : top + "px"
          });
        } else {
          var top = $.scrollTop() - box.parent().offset().top;
          box.css({
            top : Math.max(0, top) + "px"
          });
        }
        box.fadeIn("normal");
        timer = setTimeout(function() {
          box.fadeOut("normal");
        }, config.message_stay_time);
      }
    }
    $("#SYS_box_message").nicoGlobalError(function(e, message, nearbyElement) {
      updateMessage(message, "error", nearbyElement);
    }).nicoGlobalWarning(function(e, message, nearbyElement) {
      updateMessage(message, "warning", nearbyElement);
    }).nicoGlobalInfo(function(e, message, nearbyElement) {
      updateMessage(message, "info", nearbyElement);
    }).nicoApiError(function() {
      var message = messages.api_failure["UNKNOWN"];
      updateMessage(message, "error");
    }).nicoApiFail(function(event, xhr, data, code) {
      var message = messages.api_failure[code] || messages.api_failure["UNKNOWN"];
      updateMessage(message, "error");
    });

    $("#SYS_box_mylistgroups").nicoMylistGroupListLoaded(
        function(event, groups) {
          $(this).html($("#tpl_mylistgroups").jarty()).trigger("nicoPageChanged", [ my.currentPage, my.currentGroup ]);
          $("#SYS_btn_create_group").click(function() {
            MylistGroup.create(function(group) {
              Page.gotoEditMylist(group.id, true);
            });
          });
          $("#SYS_btn_sort_groups").click(
              function() {
                $("#SYS_box_mylistgroups").html($("#tpl_mylistgroups_order").jarty()).trigger("nicoPageChanged",
                    [ my.currentPage, my.currentGroup ]);
                var g = $("#SYS_box_order_mylistgroups");
                $("select[name='sort']", g).change(function() {
                  var value = parseInt($(this).val());
                  if (!isNaN(value) && groupsorts[value]) {
                    var tmpGroups = Array.prototype.slice.apply(groups);
                    tmpGroups.sort(groupsorts[value]);
                    $.each(tmpGroups, function(i, group) {
                      var item = $("#SYS_box_group_" + group.id);
                      item.appendTo(item.get(0).parentNode);
                    });
                  }
                });
                g.submit(function() {
                  var groupIds = [];
                  $("#SYS_box_group_table tr").each(function() {
                    var match = /^SYS_box_group_(\d+)$/.exec(this.id);
                    if (match)
                      groupIds.push(match[1]);
                  });
                  MylistGroup.updateOrder(groupIds);
                  return false;
                }).find("input[name='cancel']").click(function() {
                  $("#SYS_box_mylistgroups").trigger("nicoMylistGroupListLoaded", [ my.groups ]);
                });
                $("#SYS_box_group_table").tableDnD({
                  onDragClass : "dragging"
                });
              });
        }).nicoPageChanged(function(event, p, group) {
      $("a.menu1_in", this).removeClass("menu1_in").addClass("menu1");
      $("a.folder_1", this).removeClass("folder_1").addClass("folder_0");
      switch (p) {
      case Page.MYLIST:
        $("#SYS_box_group_" + group.id + " a.folder_0").removeClass("folder_0").addClass("folder_1");
        break;
      case Page.DEFLIST:
        $("#SYS_btn_deflist").removeClass("menu1").addClass("menu1_in");
        break;
      case Page.MYMEMORY:
        $("#SYS_btn_mymemory").removeClass("menu1").addClass("menu1_in");
        break;
      }
    }).nicoMylistGroupUpdated(function(event, group) {
      $(this).trigger("nicoMylistGroupListLoaded", [ my.groups ]);
      if (my.currentGroup && group.id == my.currentGroup.id)
        $(this).trigger("nicoPageChanged", [ my.currentPage, my.currentGroup, my.currentItems ]);
    }).nicoMylistGroupCreated(function(event, group) {
      $(this).trigger("nicoMylistGroupUpdated", [ group ]);
    }).nicoMylistGroupRemoved(function(event, groupId) {
      $(this).trigger("nicoMylistGroupListLoaded", [ my.groups ]);
    }).nicoMylistGroupListSorted(function(event, groups) {
      $(this).trigger("nicoMylistGroupListLoaded", [ groups ]);
    });

    $("#SYS_box_mylist_header").nicoPageChanged(function(event, p, group, items) {
      var template;
      switch (p) {
      case Page.MYLIST:
        template = "#tpl_mylist_header";
        break;
      case Page.DEFLIST:
        template = "#tpl_deflist_header";
        break;
      case Page.MYMEMORY:
        template = "#tpl_mymemory_header";
        break;
      case Page.MAINTENANCE:
        template = "#tpl_maintenance_header";
        break;
      }
      if (!template)
        return;

      var query = $.hashquerydict();
      var page = Math.max(1, parseInt(query.page) || 1);
      var sort = parseInt(query.sort);

      if (isNaN(sort)) {
        sort = (group ? group.default_sort : 1) || 0;
      }

      $(this).html($(template).jarty({
        group : group,
        items : items,
        page_name : p,
        sort : sort
      }));
      if (group) {
        $("#SYS_btn_edit_group").click(function() {
          Page.gotoEditMylist(group.id);
          return false;
        });
        $("#SYS_btn_remove_group").click(function() {
          if (!confirm(messages.confirm_remove_mylistgroup))
            return false;
          MylistGroup.remove(group.id, function() {
            Page.gotoShowDeflist();
          });
          return false;
        });
      }

      var headerY = $(this).offset().top;
      if (headerY < $.scrollTop()) {
        window.scrollTo(0, headerY);
      }

      var pageTitle = $("h1", this).text() || "";
      if (matches = config.title_matcher.exec(document.title)) {
        if (p == Page.MYLIST)
          pageTitle = config.title_mylist_prefix + pageTitle;
        document.title = pageTitle + matches[1];
      }
    }).nicoMylistGroupEditClosed(function(event, group) {
      $(this).trigger("nicoPageChanged", [ my.currentPage, my.currentGroup, my.currentItems ]);
    }).nicoMylistGroupEditOpened(function(event, group) {
      var create = $.hashquery("create");
      var icon_id = group.icon_id || 0;
      $(this).html($("#tpl_mylist_header_edit").jarty({
        group : group,
        items : my.mylist[group.id],
        create : create
      }));
      $("#SYS_box_group_edit").submit(function() {
        var dict = {
          icon_id : icon_id
        };
        $.each($(this).serializeArray(), function(i, p) {
          dict[p.name] = p.value;
        });
        MylistGroup.update(group.id, dict, function() {
          Page.closeEditMylist(group);
        });
        return false;
      });
      $("#SYS_btn_cancel_edit_group").click(function() {
        Page.closeEditMylist(group);
      });
      $("#SYS_box_choose_icon a.folder_0").click(function() {
        icon_id = $(this).prevAll().length;
        $("#SYS_box_choose_icon a.folder_0").removeClass("selected");
        $(this).addClass("selected");
      });
      if (create)
        $("#SYS_box_group_edit input[name='name']").focus();
    });

    var page_load_timer;

    $("#SYS_box_mylist_body").nicoPageChanged(
        function(event, p, group, items) {
          if (page_load_timer) {
            clearInterval(page_load_timer);
            delete page_load_timer;
          }
          if (p == Page.MAINTENANCE) {
            $(this).empty();
            return;
          }

          var query = $.hashquerydict();
          var page = Math.max(1, parseInt(query.page) || 1);
          var sort = parseInt(query.sort);

          var filters = [];
          $.each((query.filters || "").split(/,/), function(i, k) {
            k = parseInt(k);
            if (itemtypes[k])
              filters[k] = true;
          });
          if (filters.length > 0) {
            items = $.grep(items, function(item) {
              return !filters[item.item_type];
            });
          }

          if (isNaN(sort)) {
            sort = (group ? group.default_sort : 1) || 0;
          }

          if (items.sortedBy !== sort && itemsorts[sort]) {
            items.sort(itemsorts[sort]);
            items.sortedBy = sort;
          }

          var page_items = items.slice((page - 1) * config.items_per_page, page * config.items_per_page);
          if (page_items.length == 0 && page > 1) {
            var new_page = Math.max(1, Math.ceil(items.length / config.items_per_page));
            $.hashquery("page", new_page);
            return false;
          }

          if (document.getElementById("BTN_playlist_play_all")) {
            for ( var i = 0; i < items.length; i++) {
              if (items[i]['item_type'] == 0) {
                document.getElementById("BTN_playlist_play_all").href += '&from=' + items[i]['item_id'];
                break;
              }
            }
          }

          var template;
          switch (p) {
          case Page.MYLIST:
            template = "#tpl_mylist_body";
            break;
          case Page.DEFLIST:
            template = "#tpl_deflist_body";
            break;
          case Page.MYMEMORY:
            template = "#tpl_mymemory_body";
            break;
          }
          if (!template)
            return;

          $(this).html($(template).jarty({
            group : group,
            items : items,
            any_page_items : (page_items.length > 0),
            page : page,
            sort : sort,
            filters : filters,
            page_name : p
          }));

          var self = this;
          $(".SYS_btn_pager", this).click(function() {
            $.hashquery("page", $(this).text());
            return false;
          });
          $(".SYS_btn_pager_prev", this).click(function() {
            $.hashquery("page", Math.max(page - 1, 1));
            return false;
          });
          $(".SYS_btn_pager_next", this).click(function() {
            $.hashquery("page", page + 1);
            return false;
          });
          $(".SYS_box_filter", this).each(function() {
            var form = $(this);
            $("input[name='filters']", this).click(function() {
              var filters = form.find("input[name='filters']").not(":checked").map(function() {
                return $(this).val()
              });
              $.hashquerydict({
                filters : $.makeArray(filters).join(","),
                page : ""
              }, true);
            });
          });
          $(".SYS_box_sort", this).each(function() {
            var form = $(this);
            $("select[name='sort']", this).change(function() {
              var sort = $(this).val();
              $.hashquerydict({
                sort : sort,
                page : ""
              }, true);
            });
          });
          $("#SYS_box_check_editor", this).each(function() {
            var form = this;
            var collectIdMap = function() {
              var checked = $(".SYS_box_item:has(input[name='checkbox']:checked)", self);
              if (checked.length == 0)
                return false;

              var id_map = {};
              checked.each(function() {
                var match = this.id.match(/^SYS_box_item_(\d+)_(.+)$/);
                if (match) {
                  id_map[match[1]] = id_map[match[1]] || [];
                  id_map[match[1]].push(match[2]);
                }
              });
              return id_map;
            };
            var moveCallback = function(data) {
              var dups = data.duplicates.item || [], mats = data.matches.item || [], tags = data.targets.item || [];
              if (mats.length > 0 && mats.length == dups.length) {
                var message = messages.move_duplicates({
                  total : mats.length,
                  dup : dups.length
                });
                $.event.trigger("nicoGlobalError", [ message, "#SYS_box_check_editor" ]);
              } else if (dups.length > 0) {
                var message = messages.move_duplicates({
                  total : mats.length,
                  dup : dups.length
                });
                $.event.trigger("nicoGlobalWarning", [ message, "#SYS_box_check_editor" ]);
              } else if (tags.length > 0) {
                var message = messages.move_success({
                  total : tags.length
                });
                $.event.trigger("nicoGlobalInfo", [ message, "#SYS_box_check_editor" ]);
              }
              $("input[name='checkbox']:checked", self).attr("checked", false);
            };
            var copyCallback = function(data) {
              var dups = data.duplicates.item || [], mats = data.matches.item || [], tags = data.targets.item || [];
              if (mats.length > 0 && mats.length == dups.length) {
                var message = messages.copy_duplicates({
                  total : mats.length,
                  dup : dups.length
                });
                $.event.trigger("nicoGlobalError", [ message, "#SYS_box_check_editor" ]);
              } else if (dups.length > 0) {
                var message = messages.copy_duplicates({
                  total : mats.length,
                  dup : dups.length
                });
                $.event.trigger("nicoGlobalWarning", [ message, "#SYS_box_check_editor" ]);
              } else if (tags.length > 0) {
                var message = messages.copy_success({
                  total : tags.length
                });
                $.event.trigger("nicoGlobalInfo", [ message, "#SYS_box_check_editor" ]);
              }
              $("input[name='checkbox']:checked", self).attr("checked", false);
            };
            var removeCallback = function(count) {
              if (count > 0) {
                var message = messages.remove_success({
                  total : count
                });
                $.event.trigger("nicoGlobalInfo", [ message, "#SYS_box_check_editor" ]);
              }
            };
            $("#SYS_btn_move_mylist", this).click(function() {
              var id_map = collectIdMap();
              if (id_map) {
                var group_id = $("[name='target_group_id']", form).val();
                switch (p) {
                case Page.MYLIST:
                  Mylist.move(my.currentGroup.id, group_id, id_map, moveCallback);
                  break;
                case Page.DEFLIST:
                  Deflist.move(group_id, id_map, moveCallback);
                  break;
                }
              }
              return false;
            });
            $("#SYS_btn_copy_mylist", this).click(function() {
              var id_map = collectIdMap();
              if (id_map) {
                var group_id = $("[name='target_group_id']", form).val();
                switch (p) {
                case Page.MYLIST:
                  Mylist.copy(my.currentGroup.id, group_id, id_map, copyCallback);
                  break;
                case Page.DEFLIST:
                  Deflist.copy(group_id, id_map, copyCallback);
                  break;
                }
              }
              return false;
            });
            $("#SYS_btn_remove_mylist", this).click(function() {
              if (!confirm(messages.confirm_remove_mylist))
                return false;
              var id_map = collectIdMap();
              if (id_map) {
                switch (p) {
                case Page.MYLIST:
                  Mylist.remove(my.currentGroup.id, id_map, removeCallback);
                  break;
                case Page.DEFLIST:
                  Deflist.remove(id_map, removeCallback);
                  break;
                case Page.MYMEMORY:
                  Mymemory.remove(id_map, removeCallback);
                  break;
                }
              }
              return false;
            });
          });

          var list_box = $("#SYS_page_items", this);
          list_box.empty();

          var appendItem = function(index) {
            list_box.append($("#tpl_mylistitem").jarty({
              item : page_items[index],
              show_memo : (p != Page.MYMEMORY),
              group : group,
              items : items,
              page : page,
              sort : sort,
              page_name : p
            }));
            var item = list_box.children(":last").get(0);
            $("input[name='checkbox']", item).click(function() {
              if (this.checked) {
                $("#SYS_box_check_editor input:button").removeClass("disabled");
              } else {
                if ($("input[name='checkbox']:checked", self).length == 0)
                  $("#SYS_box_check_editor input:button").addClass("disabled");
              }
            });
            var editting = false;
            $(".SYS_box_item_data", item).hover(function() {
              if (!editting)
                $(".SYS_box_item_buttons", item).show();
            }, function() {
              if (!editting)
                $(".SYS_box_item_buttons", item).hide();
            });
            $(".SYS_btn_edit_memo", item).click(function() {
              editting = true;
              $(".SYS_box_item_buttons", item).hide();
              $(".SYS_box_memo_normal", item).hide();
              $(".SYS_box_memo_edit", item).show().find("textarea[name='memo']").focus();
            });
            $(".SYS_box_memo_edit", item).submit(
                function() {
                  var match = item.id.match(/^SYS_box_item_(\d+)_(.+)$/);
                  if (!match)
                    return;
                  var newDescription = $("textarea[name='memo']", item).val();
                  var commitCallback = function() {
                    editting = false;
                    var pipe = new Jarty.Pipe(newDescription.escapeHTML());
                    var htmlDescription = pipe.nicoFormat().nl2br().wordWrap().value;
                    $(".SYS_box_memo_edit", item).hide();
                    $(".SYS_box_memo_normal", item).toggle(htmlDescription != "").find(".SYS_box_memo").html(
                        htmlDescription).toggle(htmlDescription.length > 0);
                  };
                  switch (p) {
                  case Page.MYLIST:
                    Mylist.update(my.currentGroup.id, match[1], match[2], newDescription, commitCallback);
                    break;
                  case Page.DEFLIST:
                    Deflist.update(match[1], match[2], newDescription, commitCallback);
                    break;
                  }
                  return false;
                });
            $(".SYS_btn_cancel_edit_memo", item).click(function() {
              editting = false;
              $(".SYS_box_memo_edit", item).hide();
              $(".SYS_box_memo_normal", item).toggle($(".SYS_box_memo", item).text() != "");
            });
            $(".SYS_btn_remove_item", item).click(function() {
              if (!confirm(messages.confirm_remove_mylist))
                return;
              var match = item.id.match(/^SYS_box_item_(\d+)_(.+)$/);
              if (match) {
                var removeCallback = function(count) {
                  if (count > 0) {
                    var message = messages.remove_success({
                      total : count
                    });
                    $.event.trigger("nicoGlobalInfo", [ message ]);
                  }
                };
                var id_map = {};
                id_map[match[1]] = [ match[2] ];
                switch (p) {
                case Page.MYLIST:
                  Mylist.remove(my.currentGroup.id, id_map, removeCallback);
                  break;
                case Page.DEFLIST:
                  Deflist.remove(id_map, removeCallback);
                  break;
                case Page.MYMEMORY:
                  Mymemory.remove(id_map, removeCallback);
                  break;
                }
              }
            });
            $(".SYS_btn_edit_mymemory", item).click(function() {
              var match = item.id.match(/^SYS_box_item_(\d+)_(.+)$/);
              if (!match)
                return;
              Mymemory.gotoEditPage(match[2]);
            });
            return item;
          }

          var index = 0, images = [], addImage = function(i, img) {
            images.push(img)
          }, isFirst = true;
          page_load_timer = setInterval(function() {
            for ( var i = 0; i < 10; i++) {
              if (index >= page_items.length)
                break;
              var item = appendItem(index++);
              if (!isFirst)
                $("img.lazyimage", item).each(addImage);
            }
            if (isFirst) {
              Nico.LazyImage.reset();
              isFirst = false;
            }
            if (index >= page_items.length) {
              if (images.length > 0) {
                Nico.LazyImage.enqueue(images);
              }
              clearInterval(page_load_timer);
              delete page_load_timer;
              return;
            }
          }, 10);
        }).nicoDeflistMoved(function(event, targetGroupId, idMap, data) {
      $(this).trigger("nicoPageChanged", [ my.currentPage, my.currentGroup, my.currentItems ]);
      if (data && data.duplicates && data.duplicates.item) {
        $.each(data.duplicates.item, function(i, item) {
          $("#SYS_box_item_" + item.type + "_" + item.id + " input[name='checkbox']").attr("checked", true);
        });
      }
    }).nicoDeflistRemoved(function() {
      $(this).trigger("nicoPageChanged", [ my.currentPage, my.currentGroup, my.currentItems ]);
    }).nicoMylistMoved(function(event, groupId, targetGroupId, idMap, data) {
      $(this).trigger("nicoPageChanged", [ my.currentPage, my.currentGroup, my.currentItems ]);
      if (data && data.duplicates && data.duplicates.item) {
        $.each(data.duplicates.item, function(i, item) {
          $("#SYS_box_item_" + item.type + "_" + item.id + " input[name='checkbox']").attr("checked", true);
        });
      }
    }).nicoMylistRemoved(function(event, groupId) {
      $(this).trigger("nicoPageChanged", [ my.currentPage, my.currentGroup, my.currentItems ]);
    }).nicoMymemoryRemoved(function() {
      $(this).trigger("nicoPageChanged", [ my.currentPage, my.currentGroup, my.currentItems ]);
    });

    /* location.hash によるページ遷移 */

    var lastHash;
    $(window).history(function(event, hash) {
      if (lastHash != location.hash) {
        lastHash = location.hash;
        Page.dispatch($.hashpath());
      }
    });
    lastHash = location.hash;
    Page.dispatch($.hashpath());

  });

  /* ユーティリティ関数 */

  $.extend($, {

    // location.hash からパス部分を取得/設定
    hashpath : function(path, query) {
      if (arguments.length == 0) {
        var match = (location.hash || "#").match(/^#\/([^\+]+)/);
        return match ? match[1].split(/\//) : [];
      }

      var hash = "#";
      if ($.isArray(path)) {
        if (path.length > 0)
          hash += "/" + path.join("/");
      } else {
        if (path)
          hash += path;
      }
      if (arguments.length == 2) {
        if (query = $.param(query).replace(/&/g, "+").replace(/[^=\+]+=(?:\+|$)/g, "").replace(/\++$/g, ""))
          hash += "+" + query;
      }
      return location.hash = hash;
    },

    // location.hash のクエリパラメータ部分を取得/設定
    hashquerydict : function(value, merge) {
      if (arguments.length == 0) {
        var hash = location.hash, dict = {}, re = /[\+]([^=]+)=([^\+]*)/g, m;
        while (m = re.exec(hash))
          dict[decodeURIComponent(m[1])] = decodeURIComponent(m[2]);
        return dict;
      } else if (!merge) {
        return $.hashpath($.hashpath(), value);
      } else {
        return $.hashpath($.hashpath(), $.extend($.hashquerydict(), value));
      }
    },

    // location.hash からクエリパラメータの1つを取得/設定
    hashquery : function(name, value) {
      name = encodeURIComponent(name);
      if (arguments.length == 1) {
        var match = (new RegExp('^#[^\\+]*\\+(?:.*\\+)?' + name + '=([^\\+]*)')).exec(location.hash);
        return match ? match[1] : null;
      } else {
        value = encodeURIComponent(value);
        var hash = location.hash || "#";
        var match = (new RegExp('^(#[^\\+]*\\+(?:.*\\+)?)' + name + '=([^\\+]*)')).exec(hash);
        if (match && value === "") {
          hash = match[1] + hash.slice(match[0].length + 1);
        } else if (match) {
          hash = match[1] + name + "=" + value + hash.slice(match[0].length);
        } else if (value !== "") {
          hash += "+" + name + "=" + value;
        }
        return location.hash = hash;
      }
    }

  });

  // アイテム配列から id_map に存在するアイテムを取り除いて返す.
  function removeItemsByIdMap(items, idMap) {
    var linear = {};
    for ( var item_type in idMap) {
      linear[item_type] = {};
      $.each(idMap[item_type], function(i, item_id) {
        linear[item_type][item_id] = true;
      });
    }
    return $.grep(items, function(item) {
      return !linear[item.item_type] || !linear[item.item_type][item.item_id];
    });
  }

  // アイテム配列からAPIの結果で成功したアイテムを取り除いて返す.
  function removeItemsByAPIResult(items, data) {
    if (!data || !data.targets || !data.targets.item)
      return items;
    var linear = {};
    $.each(data.targets.item, function(i, item) {
      linear[item.type] = linear[item.type] || {};
      linear[item.type][item.id] = true;
    });
    return $.grep(items, function(item) {
      return !linear[item.item_type] || !linear[item.item_type][item.item_id];
    });
  }

  // 指定したプロパティで2つのアイテムを比較する関数を返す
  function item_comparer(props, reverse, numeric) {
    var p = function(v) {
      return $.map(props, function(prop) {
        return v + '["' + prop.replace(/\./g, '"]["') + '"]';
      }).join("|");
    };
    return new Function('a,b', 'var x = ' + p('a') + ', y = ' + p('b') + ';'
        + 'if (x === undefined && y !== undefined) return 1;' + 'if (x !== undefined && y === undefined) return -1;'
        + 'if (x === undefined && y === undefined)' + 'x = a.item_data.title || a.create_time,'
        + 'y = b.item_data.title || b.create_time;' + (numeric ? 'else x = parseFloat(x), y = parseFloat(y);' : '')
        + (reverse ? 'return x > y ? -1 : x < y ? 1 : 0;' : 'return x < y ? -1 : x > y ? 1 : 0;'));
  }

  // 指定したプロパティで2つのグループを比較する関数を返す
  function group_comparer(prop, reverse) {
    prop = '["' + prop.replace(/\./g, '"]["') + '"]';
    return new Function(reverse ? 'a,b' : 'b,a', 'return a' + prop + ' < b' + prop + ' ? -1 : a' + prop + ' > b' + prop
        + ' ? 1 : 0;');
  }

  // サイトのURLを取得
  function siteURL(site) {
    if (arguments.callee.mapping[site])
      return arguments.callee.mapping[site];
    else
      return "http://" + location.hostname.replace(/^[a-z]+/, site) + "/";
  }
  ;
  siteURL.mapping = {
    "smile" : "http://www.smilevideo.jp/",
    "commons" : "http://commons.nicovideo.jp/"
  };

  // 説明文の自動リンク用ルール
  var formatRules = [
      [ '\\b(?:sm|nm|so|ca|ax|yo|nl|ig|na|cw|z[a-e]|om|sk|yk)\\d{1,14}\\b', function(s) {
        return "/watch/" + s;
      } ],
      [ '\\b(?:watch|user|myvideo|mylist(?:/\\d{1,10})?)/\\d{1,10}\\b', function(s) {
        return "/" + s;
      } ],
      [ '\\bco\\d{1,14}\\b', function(s) {
        return siteURL("com") + "community/" + s;
      } ],
      [ '\\bch\\d{1,14}\\b', function(s) {
        return siteURL("ch") + "channel/" + s;
      } ],
      [ '\\bar\\d{1,14}\\b', function(s) {
          return siteURL("ch") + "article/" + s;
      } ],
      [ '\\bnc\\d{1,14}\\b', function(s) {
        return siteURL("commons") + "material/" + s;
      } ],
      [
          '\\b(?:dw\\d+|az[A-Z0-9]{10}|ys[a-zA-Z0-9-]+_[a-zA-Z0-9-]+'
              + '|ga\\d+|ip[\\d_]+|gg[a-zA-Z0-9]+-[a-zA-Z0-9-]+)\\b', function(s) {
            return siteURL("ichiba") + "item/" + s;
          } ], [ '\\blv\\d{1,14}\\b', function(s) {
        return siteURL("live") + "watch/" + s;
      } ], [ '\\b[sm]g\\d{1,14}\\b', function(s) {
        return siteURL("seiga") + "watch/" + s;
      } ], [ '\\bbk\\d{1,14}\\b', function(s) {
        return siteURL("seiga") + "watch/" + s;
      } ], [ '\\bim\\d{1,14}\\b', function(s) {
        return siteURL("seiga") + "seiga/" + s;
      } ], [ '\\bch\\.nicovideo\\.jp/[a-zA-Z0-9][-_a-zA-Z0-9]+(?!/)\\b', function(s) {
          return 'http://' + s;
      } ] ];
  var formatRegexp = new RegExp('(' + $.map(formatRules, function(r) {
    return r[0];
  }).join(')|(') + ')');

  // 与えられた文字列内の各種IDをHTMLでリンク化して返す.
  $.nicoFormat = function(str) {
    var length = formatRules.length;
    return String.interpret(str).gsub(formatRegexp, function(match) {
      var i = 1;
      while (!match[i] && i <= length)
        i++;
      return i > length ? '' : '<a href="' + formatRules[i - 1][1](match[i]) + '">' + match[0] + '</a>';
    });
  }
  // 要素の html() 内の各種IDをリンク化する.
  $.fn.nicoFormat = function() {
    return $(this).each(function() {
      $(this).html($.nicoFormat($(this).text()));
    });
  };

  // ウィンドウの垂直スクロール位置を取得.
  $.scrollTop = function() {
    return window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop;
  }

  /* Jarty 拡張 */
  $.extend(Jarty.Pipe.prototype, {
    // 数値フォーマット.
    numberFormat : function(r, decimals, dec_) {
      this.value = this.stringify().value.replace(/(\d)(?=(?:\d{3})+$)/g, "$1,");
      return this;
    },
    // 再生秒数フォーマット
    timeFormat : function(r) {
      this.value = $.nicoFormat(this.stringify().value);
      var num = parseInt(this.value);
      if (!isNaN(num)) {
        var m = Math.floor(num / 60), s = num % 60;
        if (s < 10)
          s = "0" + s;
        this.value = m + ":" + s;
      }
      return this;
    },
    // 自動リンク.
    nicoFormat : function(r) {
      this.value = $.nicoFormat(this.stringify().value);
      return this;
    },
    // ワード折り返し.
    wordWrap : function(r, step) {
      step = parseInt(step) || 5;
      var r = new RegExp('((?:<[^>]+>|&[#a-zA-Z0-9]+;|\\s+|[^<>&]){' + step + '})', 'g'), s = $.browser.opera ? '&shy;'
          : '<wbr>';
      this.value = this.stringify().value.replace(r, '$1' + s);
      return this;
    }
  });
  $.extend(Jarty.Function, {
    // id で指定した要素をテンプレートとして書き込み.
    include : function(r, params) {
      var element = $("#" + (params.file || params.id));
      if (element && element.length > 0) {
        var f = function(o) {
          for ( var k in o)
            this[k] = o[k];
        };
        f.prototype = r.dict;
        r.write(element.jarty(new f(params)));
      }
    }
  });

  // export
  window.my = my;
  window.MylistGroup = MylistGroup;
  window.Deflist = Deflist;
  window.Mylist = Mylist;
  window.Mymemory = Mymemory;

  my.setPageDispatcher = function(dispatcher) {
    Page.dispatch = dispatcher;
  }

})(jQuery);
