
--
--  Overachiever - Tabs: Watch.lua
--    by Tuhljin
--
--  If you don't wish to use the watch tab, feel free to delete this file or rename it (e.g. to Watch_unused.lua).
--  The addon's other features will work regardless.
--

local L = OVERACHIEVER_STRINGS
local GetAchievementInfo = GetAchievementInfo


local VARS, CurrentWatchList, DestinationWatchList
local frame, panel, sortdrop, listdrop, deflistdrop, destlistdrop
local listdrop_menu, deflistdrop_menu, destlistdrop_menu, listdrop_OnSelect, deflistdrop_OnSelect, destlistdrop_OnSelect
local NewListBtn, DeleteListBtn, CopyDestCheckbox, CopyDestAchAnim, NoneWatchedLabel
local Refresh, skipRefresh

local THIS_REALM, THIS_PC = GetRealmName(), UnitName("player")

local function SortDrop_OnSelect(self, value)
  VARS.WatchSort = value
  frame.sort = value
  frame:ForceUpdate(true)
end

local function OnLoad(v, oldver)
  VARS = v
  sortdrop:SetSelectedValue(VARS.WatchSort or 0)

  -- Create standard lists:
  VARS.WatchLists_Realms = VARS.WatchLists_Realms or {}
  VARS.WatchLists_Realms[THIS_REALM] = VARS.WatchLists_Realms[THIS_REALM] or {}
  VARS.WatchLists_Realms[THIS_REALM][THIS_PC] = VARS.WatchLists_Realms[THIS_REALM][THIS_PC] or {}
  VARS.WatchLists_General = VARS.WatchLists_General or {}
  VARS.WatchLists = VARS.WatchLists or {}
  if (not VARS.WatchedList) then
    VARS.WatchedList = 0
  elseif (oldver == nil) then  -- If prior to v0.56 (which introduced version tracking for these settings):
    for id in pairs(VARS.WatchedList) do  -- Migrate to new table:
      VARS.WatchLists_General[id] = true
    end
    VARS.WatchedList = 0
  end
  
  -- Look for watched achievements that no longer exist (or are otherwise invalid):
  for realm,rtab in pairs(VARS.WatchLists_Realms) do
    for char,ctab in pairs(rtab) do
      for id in pairs(ctab) do
        if (not GetAchievementInfo(id)) then
          ctab[id] = nil
          Overachiever.chatprint( L.WATCH_ERR_INVALIDID:format(id) )
        end
      end
    end
  end
  for id in pairs(VARS.WatchLists_General) do
    if (not GetAchievementInfo(id)) then
      VARS.WatchLists_General[id] = nil
      Overachiever.chatprint( L.WATCH_ERR_INVALIDID:format(id) )
    end
  end
  for name,tab in pairs(VARS.WatchLists) do
    for id in pairs(tab) do
      if (not GetAchievementInfo(id)) then
        tab[id] = nil
        Overachiever.chatprint( L.WATCH_ERR_INVALIDID:format(id) )
      end
    end
  end

  -- With existing items, set usable references for menu's OnSelect function (for listdrop_menu) and the GameTooltip_AddNewbieTip hook below (for listdrop_menu and deflist_menu):
  listdrop_menu[1].Overachiever_list = VARS.WatchLists_General
  listdrop_menu[2].Overachiever_list = VARS.WatchLists_Realms[THIS_REALM][THIS_PC]
  deflistdrop_menu[1].Overachiever_list = VARS.WatchLists_General
  deflistdrop_menu[2].Overachiever_list = VARS.WatchLists_Realms[THIS_REALM][THIS_PC]
  -- Add custom lists to dropdown menus:
  for name,tab in pairs(VARS.WatchLists) do
    tinsert(listdrop_menu, {  text = name, value = name, tooltipTitle = name, Overachiever_list = tab  })
    tinsert(deflistdrop_menu, {  text = name, value = name, tooltipTitle = name, Overachiever_list = tab  })
  end
  -- Add realm/character lists to dropdown menu:
  for rname,rtab in pairs(VARS.WatchLists_Realms) do
    local menuList, num = {}, 0
    for cname,tab in pairs(rtab) do
      if (rname ~= THIS_REALM or cname ~= THIS_PC) then  -- Don't add the current character; it is included in the first tier of the menu.
        tinsert(menuList, {  text = cname, value = "~^"..rname..":"..cname, tooltipTitle = cname, Overachiever_list = tab  })
        num = num + 1
      end
    end
    if (num > 0) then
      tinsert(listdrop_menu, {  text = rname, value = "~^"..rname, hasArrow = true, TjDDM_notCheckable = 1, keepShownOnClick = 1, menuList = menuList  })
    end
  end

  skipRefresh = true
  listdrop:SetMenu()
  deflistdrop:SetMenu()
  listdrop:OnSelect(listdrop_OnSelect) -- Put before SetSelectedValue so CurrentWatchList can be set easily.
  listdrop:SetSelectedValue(VARS.WatchedList == 1 and "~^"..THIS_REALM..":"..THIS_PC or VARS.WatchedList)
  deflistdrop:SetSelectedValue(VARS.WatchedList)
  deflistdrop:OnSelect(deflistdrop_OnSelect)
  destlistdrop:OnSelect(destlistdrop_OnSelect)
  skipRefresh = nil

  CurrentWatchList = CurrentWatchList or VARS.WatchLists_General  -- Default, in case the list we looked for doesn't exist.
  DestinationWatchList = VARS.WatchLists_General

  -- If using Global or the current character's character-specific list, it cannot be deleted, only cleared:
  if (VARS.WatchedList == 0 or VARS.WatchedList == 1) then  DeleteListBtn:SetText(L.WATCH_CLEAR);  end

  frame:RegisterEvent("PLAYER_LOGOUT")
end

frame, panel = Overachiever.BuildNewTab("Overachiever_WatchFrame", L.WATCH_TAB,
                 "Interface\\AddOns\\Overachiever_Tabs\\WatchWatermark", L.WATCH_HELP, OnLoad)

sortdrop = TjDropDownMenu.CreateDropDown("Overachiever_WatchFrameSortDrop", panel, {
  {
    text = L.TAB_SORT_NAME,
    value = 0
  },
  {
    text = L.TAB_SORT_COMPLETE,
    value = 1
  },
  {
    text = L.TAB_SORT_POINTS,
    value = 2
  },
  {
    text = L.TAB_SORT_ID,
    value = 3
  };
})
sortdrop:SetLabel(L.TAB_SORT, true)
sortdrop:SetPoint("TOPLEFT", panel, "TOPLEFT", -16, -22)
sortdrop:OnSelect(SortDrop_OnSelect)

function frame.SetNumListed(num)
  if (num > 0) then
    if (NoneWatchedLabel) then  NoneWatchedLabel:Hide();  end
  elseif (not NoneWatchedLabel) then
    NoneWatchedLabel = frame:CreateFontString(nil, "ARTWORK", "GameFontHighlight")
    NoneWatchedLabel:SetPoint("TOP", frame, "TOP", 0, -189)
    NoneWatchedLabel:SetText(L.WATCH_EMPTY)
    NoneWatchedLabel:SetWidth(490)
  else
    NoneWatchedLabel:Show()
  end

  local c = #frame.AchList
  if (num < c) then
    frame.frameWarning.label:SetText(L.WATCH_FILTERED_OUT:format(c - num))
	--local w = frame.frameWarning.label:GetStringWidth() + 100
	--if (w > 492) then  w = 492;  end
	--frame.frameWarning:SetWidth(w)
    frame.frameWarning:Show()
  else
    frame.frameWarning:Hide()
  end
end


function Refresh(scrollup)
  if (skipRefresh) then  return;  end
  local list, count = frame.AchList, 0
  wipe(list)
  for id in pairs(CurrentWatchList) do
    count = count + 1
    list[count] = id
  end
  if (scrollup == true) then  Overachiever_WatchFrameContainerScrollBar:SetValue(0);  end
  frame:ForceUpdate(true)
end

panel:SetScript("OnShow", Refresh)

function frame.SetAchWatchList(id, add)
  if (not add and frame:IsVisible() and IsShiftKeyDown()) then
    if (CopyDestCheckbox:GetChecked()) then
      DestinationWatchList[id] = true
      PlaySound(SOUNDKIT.IG_MAINMENU_OPTION_CHECKBOX_ON)
      if (not CopyDestAchAnim) then
        CopyDestAchAnim = CreateFrame("Model", "Overachiever_WatchFrameModelCopying", panel)
        CopyDestAchAnim:Hide()
        CopyDestAchAnim:SetModel("Interface\\ItemAnimations\\ForcedBackpackItem.mdx")
        CopyDestAchAnim:SetScript("OnAnimFinished", function(self)  self:Hide();  end);
        CopyDestAchAnim:SetPoint("BOTTOMRIGHT", destlistdrop, 5, -14)
		-- When you're up to trying to fix this again, a good place to look might be the reaction to event "ITEM_PUSH" in MainMenuBarBagButtons.lua.
		-- It seemed like the dropdown above it was causing problems, but on further testing, that dropdown can be hidden entirely; it seems the animation is actually cut off on the top for some reason.
		--CopyDestAchAnim:SetFrameStrata("TOOLTIP") -- tried to get this animation to appear above the dropdown, but it gets cut off anyway; problem with the animation itself?
		--CopyDestAchAnim:SetFrameStrata("HIGH") -- also consider :SetFrameLevel
        --CopyDestAchAnim:SetWidth(30);  CopyDestAchAnim:SetHeight(30);
      end
      local texture = select(10, GetAchievementInfo(id))
      if (texture) then
      	CopyDestAchAnim:ReplaceIconTexture(texture);
      	CopyDestAchAnim:SetSequence(0);
      	CopyDestAchAnim:SetSequenceTime(0, 0);
      	CopyDestAchAnim:Show();
      end
    end
    return;
  end
  if (add) then
    CurrentWatchList[id] = true
    PlaySound(SOUNDKIT.IG_MAINMENU_OPTION_CHECKBOX_ON)
  else
    CurrentWatchList[id] = nil
    PlaySound(SOUNDKIT.IG_MAINMENU_OPTION_CHECKBOX_OFF)
  end
  if (frame:IsShown()) then
    Refresh() --Refresh(true)
	Overachiever.AchBtnRedisplayTooltip()
  else
    frame.AchList_sorted = nil
    Overachiever.FlashTab(frame.tab)
  end
end

local orig_AchievementButton_OnClick = AchievementButton_OnClick
AchievementButton_OnClick = function(self, button, down, ignoreModifiers, ...)
  if (not ignoreModifiers and IsAltKeyDown()) then
    frame.SetAchWatchList(self.id, true)
    return;
  end
  return orig_AchievementButton_OnClick(self, button, down, ignoreModifiers, ...)
end



-- SUPPORT MULTIPLE WATCH LISTS
----------------------------------------------------

local function disableListChange()
  listdrop:Disable()
  NewListBtn:Disable()
  DeleteListBtn:Disable()
end

local function enableListChange()
  listdrop:Enable()
  NewListBtn:Enable()
  DeleteListBtn:Enable()
end

local function TjPopupHelper_EditBoxOnEnterPressed(self, ...)
-- Tj: Even if enterClicksFirstButton is 1, OnAccept isn't used when enter is pressed when using an editbox. This
-- function should help prune redundant code (a problem you can see in spades if you examine FrameXML\StaticPopup.lua).
-- To use, set this function to be your popup dialog's EditBoxOnEnterPressed value. Then, use OnAccept as normal.
	local frame = self:GetParent()
	if (not frame.button1:IsEnabled()) then  return;  end  -- Don't proceed if the button is disabled.
	local OnAccept = StaticPopupDialogs[frame.which].OnAccept
	local ret = OnAccept(frame, ...)
	frame:Hide()
	return ret
end

StaticPopupDialogs["OVERACHIEVER_WATCH_NEWLIST"] = {
	text = L.WATCH_POPUP_NEWLIST,
	button1 = ACCEPT,
	button2 = CANCEL,
	hasEditBox = 1,
	enterClicksFirstButton = 1,
	OnAccept = function(self)
		local name = strtrim(self.editBox:GetText())
		VARS.WatchLists[name] = {}
		tinsert(listdrop_menu, {  text = name, value = name, tooltipTitle = name, Overachiever_list = VARS.WatchLists[name]  })
		tinsert(deflistdrop_menu, {  text = name, value = name, tooltipTitle = name, Overachiever_list = VARS.WatchLists[name]  })
		listdrop:SetMenu()
		deflistdrop:SetMenu()
		listdrop:SetSelectedValue(name)
	end,
	OnShow = function(self)
		self.button1:Disable();
		self.editBox:SetFocus();
	end,
	OnHide = function(self)
		ChatEdit_FocusActiveWindow();
		self.editBox:SetText("");
	end,
	EditBoxOnEnterPressed = TjPopupHelper_EditBoxOnEnterPressed,
	EditBoxOnEscapePressed = function(self)
		self:GetParent():Hide();
	end,
	EditBoxOnTextChanged = function (self)
		local parent = self:GetParent()
		local name = strtrim(parent.editBox:GetText())
		-- Disallow strings that are empty, have already been used, or start with characters reserved for our character-specific indicator:
		if (name == "" or VARS.WatchLists[name] or strsub(name, 1, 2) == "~^") then
			parent.button1:Disable()
		else
			parent.button1:Enable()
		end
	end,
	timeout = 0,
	exclusive = 1,
	whileDead = 1,
	hideOnEscape = 1
};

StaticPopupDialogs["OVERACHIEVER_WATCH_DELETELIST"] = {
	text = L.WATCH_POPUP_DELETELIST,
	button1 = YES,
	button2 = NO,
	OnShow = function(self)
		disableListChange()
		-- If using Global or the current character's character-specific list, it cannot be deleted, only cleared:
		if (CurrentWatchList == VARS.WatchLists_General or CurrentWatchList == VARS.WatchLists_Realms[THIS_REALM][THIS_PC]) then
			self.text:SetText(L.WATCH_POPUP_CLEARLIST)
		else
			self.text:SetText(L.WATCH_POPUP_DELETELIST)
		end
	end,
	OnHide = enableListChange,
	OnAccept = function(self)
		-- If using Global or the current character's character-specific list, it cannot be deleted, only cleared:
		if (CurrentWatchList == VARS.WatchLists_General or CurrentWatchList == VARS.WatchLists_Realms[THIS_REALM][THIS_PC]) then
			wipe(CurrentWatchList)
			Refresh(true)
		else
			local deltab, delval = CurrentWatchList, listdrop:GetSelectedValue()
			if (deflistdrop:GetSelectedValue() == delval) then
				deflistdrop:SetSelectedValue(0)
			end
			if (destlistdrop:GetSelectedValue() == delval) then
				CopyDestCheckbox:SetChecked(false)
				destlistdrop:Disable()
				destlistdrop:SetSelectedValue(0)
			end
			listdrop:SetSelectedValue(VARS.WatchedList == 1 and "~^"..THIS_REALM..":"..THIS_PC or VARS.WatchedList)
			if (strsub(delval, 1, 2) == "~^") then  -- If this is a character-specific list:
				local rname, cname = strsplit(":", delval, 2)
				local realm = strsub(rname, 3)
				-- Remove actual list:
				VARS.WatchLists_Realms[realm][cname] = nil
				-- If there aren't any keys remaining for that realm, remove its table:
				if (next(VARS.WatchLists_Realms[realm]) == nil) then
					VARS.WatchLists_Realms[realm] = nil
				end
				-- Remove menu item for the list:
				for i,tab in ipairs(listdrop_menu) do
					if (tab.value == rname) then  -- Find realm listing with its submenu
						local cnum = 0
						for n,rtab in ipairs(tab.menuList) do  -- Find character in that realm
							if (rtab.value == delval) then
								tremove(tab.menuList, n)
							else
								cnum = cnum + 1
							end
						end
						-- If there aren't any items remaining for that realm, remove the parent item:
						if (cnum == 0) then
							tremove(listdrop_menu, i)
						end
						break;
					end
				end
			else
				-- Remove actual list:
				VARS.WatchLists[delval] = nil
				-- Remove menu item for the list:
				for i,tab in ipairs(listdrop_menu) do
					if (tab.value == delval) then
						tremove(listdrop_menu, i)
						break;
					end
				end
				-- Remove option for setting as default:
				for i,tab in ipairs(deflistdrop_menu) do
					if (tab.value == delval) then
						tremove(deflistdrop_menu, i)
						break;
					end
				end
				deflistdrop:SetMenu()
			end
			listdrop:SetMenu()
		end
	end,
	timeout = 0,
	exclusive = 1,
	whileDead = 1,
	hideOnEscape = 1
};

listdrop_menu = {  {  text = L.WATCH_LIST_GLOBAL, value = 0, tooltipTitle = L.WATCH_LIST_GLOBAL  },  {  text = THIS_PC, value = "~^"..THIS_REALM..":"..THIS_PC, tooltipTitle = THIS_PC  };  }
listdrop = TjDropDownMenu.CreateDropDown("Overachiever_WatchFrameListDrop", panel, listdrop_menu)
listdrop:SetLabel(L.WATCH_DISPLAYEDLIST, true)
listdrop:SetPoint("TOPLEFT", sortdrop, "BOTTOMLEFT", 0, -18)
listdrop:SetDropDownWidth(158)
listdrop.tooltip = 0

NewListBtn = CreateFrame("Button", nil, panel, "UIPanelButtonTemplate")
NewListBtn:SetWidth(43); NewListBtn:SetHeight(21)
NewListBtn:SetPoint("TOPLEFT", listdrop, "BOTTOMLEFT", 16, -3)
NewListBtn:SetText(L.WATCH_NEW)
NewListBtn:SetScript("OnClick", function()
  StaticPopup_Show("OVERACHIEVER_WATCH_NEWLIST")
end);

DeleteListBtn = CreateFrame("Button", nil, panel, "UIPanelButtonTemplate")
DeleteListBtn:SetWidth(51); DeleteListBtn:SetHeight(21)
DeleteListBtn:SetPoint("LEFT", NewListBtn, "RIGHT", 4, 0)
DeleteListBtn:SetText(L.WATCH_DELETE)
DeleteListBtn:SetScript("OnClick", function()
  StaticPopup_Show("OVERACHIEVER_WATCH_DELETELIST")
end);

deflistdrop_menu = {  {  text = L.WATCH_LIST_GLOBAL, value = 0, tooltipTitle = L.WATCH_LIST_GLOBAL  },  {  text = L.WATCH_LIST_PERCHAR, value = 1, tooltipTitle = THIS_PC  };  }
deflistdrop = TjDropDownMenu.CreateDropDown("Overachiever_WatchFrameDefListDrop", panel, deflistdrop_menu)
deflistdrop:SetLabel(L.WATCH_DEFAULTLIST, true)
--deflistdrop:SetPoint("TOPLEFT", listdrop, "BOTTOMLEFT", 0, -18)
deflistdrop:SetPoint("TOPLEFT", NewListBtn, "BOTTOMLEFT", -16, -22)
deflistdrop:SetDropDownWidth(158)
deflistdrop.tooltip = L.WATCH_DEFAULTLIST_TIP

CopyDestCheckbox = CreateFrame("CheckButton", "Overachiever_WatchFrameCopyDestCheckbox", panel, "InterfaceOptionsCheckButtonTemplate")
CopyDestCheckbox:SetPoint("TOPLEFT", deflistdrop, "BOTTOMLEFT", 14, -12)
Overachiever_WatchFrameCopyDestCheckboxText:SetText(L.WATCH_COPY)
CopyDestCheckbox:SetHitRectInsets(0, -1 * min(Overachiever_WatchFrameCopyDestCheckboxText:GetWidth() + 8, 155), 0, 0)
CopyDestCheckbox:SetScript("OnEnter", function(self)
  GameTooltip:SetOwner(self, "ANCHOR_RIGHT")
  GameTooltip:SetBackdropColor(TOOLTIP_DEFAULT_BACKGROUND_COLOR.r, TOOLTIP_DEFAULT_BACKGROUND_COLOR.g, TOOLTIP_DEFAULT_BACKGROUND_COLOR.b)
  GameTooltip:SetText(L.WATCH_COPY_TIP, nil, nil, nil, nil, 1)
end)
CopyDestCheckbox:SetScript("OnLeave", GameTooltip_Hide)
CopyDestCheckbox:SetScript("OnClick", function(self)
  if (self:GetChecked()) then
    PlaySound(SOUNDKIT.IG_MAINMENU_OPTION_CHECKBOX_ON);
    destlistdrop:Enable()
  else
    PlaySound(SOUNDKIT.IG_MAINMENU_OPTION_CHECKBOX_OFF);
    destlistdrop:Disable()
  end
end);

destlistdrop_menu = {  {  text = L.WATCH_LIST_GLOBAL, value = 0, tooltipTitle = L.WATCH_LIST_GLOBAL  };  }
destlistdrop = TjDropDownMenu.CreateDropDown("Overachiever_WatchFrameDestinationListDrop", panel, destlistdrop_menu)
destlistdrop:SetPoint("TOPLEFT", CopyDestCheckbox, "BOTTOMLEFT", -14, 2)
destlistdrop:SetDropDownWidth(158)
destlistdrop.tooltip = 0
destlistdrop:Disable()

destlistdrop:OnMenuOpen(function(self, menuList)
  wipe(menuList)
  for i,tab in ipairs(listdrop_menu) do
    if (tab.menuList) then
      local sub = {}
      menuList[i] = {  text = tab.text, value = tab.value, hasArrow = true, TjDDM_notCheckable = 1, keepShownOnClick = 1, menuList = sub  }
      for k,v in ipairs(tab.menuList) do
        sub[k] = {  text = v.text, value = v.value, tooltipTitle = v.tooltipTitle, Overachiever_list = v.Overachiever_list  };
      end
    else
      menuList[i] = {  text = tab.text, value = tab.value, tooltipTitle = tab.tooltipTitle, Overachiever_list = tab.Overachiever_list  }
    end
  end
  if (not self:SetMenu()) then
    local val = self:GetSelectedValue() or 0
    if (not self:SetSelectedValue(val)) then
      self:SetSelectedValue(0)
    end
  end
end);


function listdrop_OnSelect(self, value, oldvalue, tab)
  CurrentWatchList = tab.Overachiever_list
  -- If using Global or the current character's character-specific list, it cannot be deleted, only cleared:
  if (CurrentWatchList == VARS.WatchLists_General or CurrentWatchList == VARS.WatchLists_Realms[THIS_REALM][THIS_PC]) then
    DeleteListBtn:SetText(L.WATCH_CLEAR)
  else
    DeleteListBtn:SetText(L.WATCH_DELETE)
  end
  Refresh(true)
end

function deflistdrop_OnSelect(self, value)
  VARS.WatchedList = value
end

function destlistdrop_OnSelect(self, value, oldvalue, tab)
  DestinationWatchList = tab.Overachiever_list
end

-- A trick to get default-UI-style tooltip like the function AchievementFrameCategory_StatusBarTooltip() displays:
hooksecurefunc("GameTooltip_AddNewbieTip", function(self, text, ...)
  if (not self.arg1 or (type(self.arg1) ~= "table") or not self.arg1.Overachiever_list) then  return;  end
  GameTooltip:Hide()

  local numAchievements, numCompleted, _, completed = 0, 0
  for id in pairs(self.arg1.Overachiever_list) do
    _, _, _, completed = GetAchievementInfo(id)
    if (completed) then  numCompleted = numCompleted + 1;  end
    numAchievements = numAchievements + 1
  end
  local numCompletedText = numAchievements > 0 and numCompleted.."/"..numAchievements or L.WATCH_EMPTY_SHORT

  -- Based on AchievementFrameCategory_StatusBarTooltip():
  --GameTooltip_SetDefaultAnchor(GameTooltip, self);
  GameTooltip:SetOwner(self, "ANCHOR_NONE")
  GameTooltip:SetPoint("TOPLEFT", self, "TOPRIGHT", 10, 9)
  GameTooltip:SetMinimumWidth(128, 1);
  GameTooltip:SetText(text, 1, 1, 1, nil, 1);
  GameTooltip_ShowStatusBar(GameTooltip, 0, numAchievements, numCompleted, numCompletedText);
  GameTooltip:Show();
end);

frame:SetScript("OnEvent", function()  -- React to "PLAYER_LOGOUT" event:
  -- Remove unused character-specific watch lists:
  local cnum
  for realm,rtab in pairs(VARS.WatchLists_Realms) do
    cnum = 0
    for char,ctab in pairs (rtab) do
      if (next(ctab) == nil) then  -- If there aren't any keys in the table, remove it.
        rtab[char] = nil
      else
        cnum = cnum + 1
      end
    end
    if (cnum == 0) then  VARS.WatchLists_Realms[realm] = nil;  end
  end
end);


--[[  USE Overachiever.Debug_DumpTab INSTEAD.
-- /run Overachiever.Debug_DumpWatch()
function Overachiever.Debug_DumpWatch()
  local tab = {}
  for id in pairs(CurrentWatchList) do
    tab[#tab+1] = id
  end
  sort(tab)
  local s = "{ "..strjoin(", ", unpack(tab)).." }"
  print(s)
  error(s)
end
--]]
