-- Copyright (C) 2010, 2011 Tyson Brown
-- 
-- This file is part QuestHelper2, an AddOn for World of Warcraft.
-- 
-- QuestHelper2 is free software: you can redistribute it and/or modify
-- it under the terms of the GNU General Public License as published by
-- the Free Software Foundation, either version 3 of the License, or
-- (at your option) any later version.
-- 
-- QuestHelper2 is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-- GNU General Public License for more details.
-- 
-- You should have received a copy of the GNU General Public License
-- along with QuestHelper2. If not, see <http://www.gnu.org/licenses/>.

if LibDebug then LibDebug() end
local shared = select(2, ...)

local overlay = CreateFrame("Frame", nil, WorldMapButton)
overlay:SetAllPoints()

-- Need our own tooltip; GameTooltip isn't visible when the world map is full-screen, and
-- WorldMapTooltip's parent can't be depended on to be visible.
local tooltip = CreateFrame("GameTooltip", "QuestHelper2_MapOverlayTooltip", overlay, "GameTooltipTemplate")

local button_base_diameter = 22

local ant_spacing = 100
local ant_speed = 25
local ant_opacity = 1
local ant_scale = 1
local trail_opacity = 1
local button_scale = 1

local unused_textures, active_textures, ant_textures = {}, {}, {}

local unused_objective_buttons, objective_buttons = {}, {}

local unused_link_buttons, link_buttons = {}, {}

local objective_button_tree = shared.createQuadTree()
local link_button_tree = shared.createQuadTree()

local function getTexture(layer, ...)
  local texture = next(unused_textures)
  
  if texture then
    unused_textures[texture] = nil
    texture:SetDrawLayer(layer)
    texture:ClearAllPoints()
    texture:Show()
  else
    texture = overlay:CreateTexture(nil, layer)
  end
  
  texture:SetTexture(...)
  
  -- Note: alpha and texture coordinates should be considered undefined.
  
  return texture
end

local displayed_area, displayed_continent, displayed_level

local ant_lines = {}
local next_antline_offset = 0

local sqrt = math.sqrt

local function mag(x, y)
  return sqrt(x*x+y*y)
end

local function addLine(ox1, oy1, ox2, oy2)
  local x1, y1, x2, y2 = ox1, 1-oy1, ox2, 1-oy2
  
  local dx, dy = x2 - x1, y2 - y1
  local temp = x1*y2 - y1*x2
  local x_at_y0 = temp / dy
  local y_at_x0 = -temp / dx
  local x_at_y1 = (temp - x1 + x2) / dy
  local y_at_x1 = (y2 - temp - y1) / dx
  
  if x1 < 0 and dx ~= 0 then x1, y1 = 0, y_at_x0 end
  if x2 < 0 and dx ~= 0 then x2, y2 = 0, y_at_x0 end
  
  if y1 < 0 and dy ~= 0 then x1, y1 = x_at_y0, 0 end
  if y2 < 0 and dy ~= 0 then x2, y2 = x_at_y0, 0 end
  
  if x1 > 1 and dx ~= 0 then x1, y1 = 1, y_at_x1 end
  if x2 > 1 and dx ~= 0 then x2, y2 = 1, y_at_x1 end
  
  if y1 > 1 and dy ~= 0 then x1, y1 = x_at_y1, 1 end
  if y2 > 1 and dy ~= 0 then x2, y2 = x_at_y1, 1 end
  
  local w, h, offset = overlay:GetWidth(), overlay:GetHeight(), next_antline_offset
  next_antline_offset = next_antline_offset + mag((ox2-ox1)*w, (oy1-oy2)*h)
  
  if x1 < 0 or x1 > 1 or y1 < 0 or y1 > 1 or
     x2 < 0 or x2 > 1 or y2 < 0 or y2 > 1 then
    return
  end
  
  x1, y1, x2, y2 = x1*w, y1*h, x2*w, y2*h
  
  local dx, dy = x2-x1, y2-y1
  local len = mag(dx, dy)
  
  -- Too short to bother drawing.
  if len < 10 then return end
  
  if ant_opacity > 0.01 then
    local insert_pos = #ant_lines
    ant_lines[insert_pos+1] = x1
    ant_lines[insert_pos+2] = y1
    ant_lines[insert_pos+3] = dx/len
    ant_lines[insert_pos+4] = dy/len
    ant_lines[insert_pos+5] = math.atan2(dy, dx)
    ant_lines[insert_pos+6] = (mag(ox1*w-x1, h-oy1*h-y1) - offset)%ant_spacing
    ant_lines[insert_pos+7] = len
  end
  
  if trail_opacity >= 0.01 then
    local texture = getTexture("BACKGROUND", "Interface\\AddOns\\QuestHelper2\\MapOverlay\\Trail.tga")
    active_textures[texture] = true
    
    texture:SetAlpha(math.min(0.8, len*100)*trail_opacity)
    
    -- Texture may have width and height assigned to it if it
    -- was used by an ant, but I think the anchoring done by this
    -- function overrides it, so I'm not going to worry about it.
    DrawRouteLine(texture, overlay, x1, y1, x2, y2, 64)
  end
end

local path = {}

local function drawPath(src_area, src_level, src_x, src_y, src_ex, src_ey,
                        dest_area, dest_level, dest_x, dest_y, dest_ex, dest_ey)
  table.wipe(path)
  
  if shared.calculateTravelTime(src_area, src_level, src_x, src_y, src_ex, src_ey,
                                dest_area, dest_level, dest_x, dest_y, dest_ex, dest_ey,
                                path) and path[1] then
    local sx, sy = shared.areaPosition(path[1], path[2], path[3], displayed_area, displayed_level)
    
    for i=4,#path,3 do
      local dx, dy = shared.areaPosition(path[i+0], path[i+1], path[i+2], displayed_area, displayed_level)
      
      if sx and sy and dx and dy then
        addLine(sx, sy, dx, dy)
      end
      
      sx, sy = dx, dy
    end
  end
end

local function redrawAnts()
  for texture in pairs(ant_textures) do
    ant_textures[texture] = nil
    unused_textures[texture] = true
    texture:Hide()
  end
  
  if ant_opacity < 0.01 then
    -- Nothing to draw right now.
    return 0.5
  end
  
  local base_offset = (GetTime()*ant_speed)%ant_spacing
  
  local size = 24*ant_scale
  
  for i = 1,#ant_lines,7 do
    local x1, y1, dx, dy, angle, distance_offset, len = unpack(ant_lines, i, i+6)
    
    for offset=(base_offset+distance_offset)%ant_spacing,len,ant_spacing do
      local texture = getTexture("ARTWORK", "Interface\\AddOns\\QuestHelper2\\MapOverlay\\Ant.tga")
      local pct = offset/len
      texture:SetAlpha(sqrt(pct-pct*pct)*2*ant_opacity)
      texture:SetRotation(angle)
      
      texture:SetWidth(size)
      texture:SetHeight(size)
      texture:SetPoint("CENTER", overlay, "BOTTOMLEFT", x1+dx*offset, y1+dy*offset)
      
      ant_textures[texture] = true
    end
  end
  
  return math.max(math.min(2/ant_speed, 1.0), 0.05)
end

local function buttonClick(button, mb)
  if mb == "RightButton" then
    shared.showObjectiveListMenu(button, button.objectives)
  end
end

local function buttonEnter(button)
  button.highlight:Show()
  
  for texture in pairs(button.textures) do
    texture:Show()
  end
  
  -- The ScrollFrame attached to the tooltip doesn't behave correctly when the tooltip is anchored to the cursor.
  tooltip:SetOwner(button, "ANCHOR_TOP")
  
  shared.setObjectiveListTooltip(button.objectives, tooltip)
  
  tooltip:Show()
end

local function buttonLeave(button)
  button.highlight:Hide()
  
  for texture in pairs(button.textures) do
    texture:Hide()
  end
  
  tooltip:Hide()
end

local priority_colour = {
  [false] = {.5,.5,.5}, -- Use for disabled objectives.
  [0] = {0,.5,1},
  [1] = {0,1,0},
  [2] = {.5,1,0},
  [3] = {1,1,1},
  [4] = {1,.5,0},
  [5] = {1,0,0}
}

local function redrawButton(button)
  local objective = button.objectives[button.objective]
  
  objective:setTextureToIcon(button.icon)
  
  if objective.obtainable then
    button.ring:SetVertexColor(unpack(priority_colour[objective.priority]))
  else
    button.ring:SetVertexColor(unpack(priority_colour[false]))
  end
end

local transitionButton

local function startButtonTransition(button)
  local objectives = button.objectives
  local cur_objective = objectives[button.objective]
  local next_objective = objectives[button.objective%#objectives+1]
  
  next_objective:setTextureToIcon(button.next_icon)
  
  button.ring_start = cur_objective.obtainable  and priority_colour[cur_objective.priority]  or priority_colour[false]
  button.ring_end   = next_objective.obtainable and priority_colour[next_objective.priority] or priority_colour[false]
  
  button.a = 0
  button.next_icon:SetAlpha(0)
  button.next_icon:Show()
  button:SetScript("OnUpdate", transitionButton)
end

transitionButton = function(self, delta)
  local a = self.a + delta
  
  if a > 1 then
    self.a = 0
    self.objective = self.objective % #self.objectives + 1
    self.next_icon:Hide()
    
    redrawButton(self)
    
    self:SetScript("OnUpdate", nil)
    shared.onDelay(1, startButtonTransition, self)
  else
    self.a = a
    
    local ia = 1-a
    
    self.ring:SetVertexColor(
      self.ring_start[1]*ia + self.ring_end[1]*a,
      self.ring_start[2]*ia + self.ring_end[2]*a,
      self.ring_start[3]*ia + self.ring_end[3]*a
    )
    
    self.next_icon:SetAlpha(a)
  end
end

local function countKeys(tbl)
  local count = 0
  for key in pairs(tbl) do count = count + 1 end
  return count
end

local function setMap(unused, area_id, level)
  SetMapByID(area_id)
  SetDungeonMapLevel(level)
end

local function sortedPairs(tbl)
  local list = {}
  
  for key in pairs(tbl) do table.insert(list, key) end
  table.sort(list)
  
  local i = 0
  return function ()
    i = i + 1
    return list[i], tbl[list[i]]
  end
end

local function linkButtonClick(self)
  local areas = {}
  
  local function addTarget(area_id, level)
    local levels = areas[area_id]
    
    if not levels then
      levels = {}
      areas[area_id] = levels
    end
    
    levels[level] = true
  end
  
  for link in pairs(self.links) do
    local a1, l1 = link:getOrigin()
    local a2, l2 = link:getDestination()
    
    if link.forward_time and a1 == displayed_area and l1 == displayed_level then
      addTarget(a2, l2)
    elseif link.reverse_time and a2 == displayed_area and l2 == displayed_level then
      addTarget(a1, l1)
    end
  end
  
  if countKeys(areas) == 1 and countKeys(areas[next(areas)]) == 1 then
    local area_id, levels = next(areas)
    setMap(self, area_id, next(levels))
  else
    shared.showMenu(function (frame, frame_level)
      local value = _G.UIDROPDOWNMENU_MENU_VALUE
      
      if value then
        if value == areas then
          -- Listing areas
          for area_id, levels in sortedPairs(value) do
            local info = UIDropDownMenu_CreateInfo()
            
            info.notCheckable = true
            
            if countKeys(levels) == 1 then
              -- Go directly there
              info.text = shared.areaName(area_id, next(levels))
              info.func = setMap
              info.arg1 = area_id
              info.arg2 = next(levels)
            else
              -- Open a sub-menu for each level.
              info.text = shared.areaName(area_id)
              info.hasArrow = true
              info.value = area_id
            end
            
            UIDropDownMenu_AddButton(info, frame_level)
          end
        else
          -- Listing levels within an area.
          for level in sortedPairs(areas[value]) do
            local info = UIDropDownMenu_CreateInfo()
            info.text = shared.areaName(value, level)
            info.notCheckable = true
            info.func = setMap
            info.arg1 = value
            info.arg2 = level
            UIDropDownMenu_AddButton(info, frame_level)
          end
        end
      end
    end, areas, self)
  end
end

local function createLinkButton(x, y, link)
  local button
  
  local diameter = button_base_diameter*button_scale*0.8
  local w, h = overlay:GetWidth(), overlay:GetHeight()
  local xr, yr = diameter/w, diameter/h
  
  for existing_button in link_button_tree:iterateObjects(x-xr, y-yr, x+xr, y+yr) do
    local dx, dy = (x-existing_button.x)*w, (y-existing_button.y)*h
    local distance = (dx*dx+dy*dy)^.5
    if distance < diameter then
      button = existing_button
      break
    end
  end
  
  if not button then
    button = next(unused_link_buttons)
    
    if button then
      unused_link_buttons[button] = nil
      button:Show()
    else
      button = CreateFrame("Button", nil, overlay)
      button.links = {}
      button:SetScript("OnClick", linkButtonClick)
      button:SetNormalTexture("Interface\\AddOns\\QuestHelper2\\Icons\\Link.tga")
      button:SetHighlightTexture("Interface\\AddOns\\QuestHelper2\\Icons\\Highlight.tga", "ADD")
    end
    
    link_buttons[button] = true
    button:SetWidth(diameter)
    button:SetHeight(diameter)
    button:SetPoint("CENTER", overlay, "BOTTOMLEFT", x*w, h-y*h)
    
    button.x, button.y = x, y
    link_button_tree:addObject(button, x-xr, y-yr, x+xr, y+yr)
  end
  
  button.links[link] = true
end

local function createObjectiveButton(x, y, objective)
  local button
  
  local diameter = button_base_diameter*button_scale
  local w, h = overlay:GetWidth(), overlay:GetHeight()
  local xr, yr = diameter/w, diameter/h
  
  for existing_button in objective_button_tree:iterateObjects(x-xr, y-yr, x+xr, y+yr) do
    local dx, dy = (x-existing_button.x)*w, (y-existing_button.y)*h
    local distance = (dx*dx+dy*dy)^.5
    if distance < diameter then
      button = existing_button
      break
    end
  end
  
  if not button then
    button = next(unused_objective_buttons)
    
    if button then
      unused_objective_buttons[button] = nil
      button:Show()
    else
      button = CreateFrame("Frame", nil, overlay)
      button.objectives = {}
      button.textures = {}
      
      button:EnableMouse(true)
      button:SetScript("OnMouseUp", buttonClick)
      button:SetScript("OnEnter", buttonEnter)
      button:SetScript("OnLeave", buttonLeave)
      
      local highlight = button:CreateTexture(nil, "OVERLAY")
      highlight:SetBlendMode("ADD")
      highlight:SetTexture("Interface\\AddOns\\QuestHelper2\\Icons\\Highlight.tga")
      highlight:SetAllPoints()
      button.highlight = highlight
      
      local ring = button:CreateTexture(nil, "ARTWORK")
      ring:SetTexture("Interface\\AddOns\\QuestHelper2\\Icons\\Ring.tga")
      ring:SetAllPoints()
      button.ring = ring
      
      local icon = button:CreateTexture(nil, "BACKGROUND")
      icon:SetAllPoints()
      button.icon = icon
      
      local next_icon = button:CreateTexture(nil, "BORDER")
      next_icon:SetAllPoints()
      next_icon:Hide()
      button.next_icon = next_icon
    end
    
    button:SetWidth(diameter)
    button:SetHeight(diameter)
    button:SetFrameStrata(overlay:GetFrameStrata())
    button:SetFrameLevel(overlay:GetFrameLevel()+1)
    
    button.objective = 1
    
    objective_buttons[button] = true
    button.highlight:Hide()
    
    button:SetPoint("CENTER", overlay, "BOTTOMLEFT", x*w, h-y*h)
    button:SetFrameLevel(overlay:GetFrameLevel()+1)
    button:EnableMouse(true)
    
    button.x, button.y = x, y
    objective_button_tree:addObject(button, x-xr, y-yr, x+xr, y+yr)
  end
  
  table.insert(button.objectives, objective)
  
  if #button.objectives == 2 then
    shared.onDelay(1, startButtonTransition, button)
  end
  
  redrawButton(button)
  
  return button
end

local links = {}

local function redrawRoute()
  if not overlay:IsVisible() then
    return
  end
  
  shared.cancelDelayed(startButtonTransition)
  
  for button in pairs(link_buttons) do
    button:Hide()
    button:ClearAllPoints()
    
    unused_link_buttons[button] = true
    link_buttons[button] = nil
    table.wipe(button.links)
  end
  
  link_button_tree:clear()
  
  for button in pairs(objective_buttons) do
    button:Hide()
    button:ClearAllPoints()
    
    button:SetScript("OnUpdate", nil)
    button.next_icon:Hide()
    
    unused_objective_buttons[button] = true
    objective_buttons[button] = nil
    table.wipe(button.objectives)
    table.wipe(button.textures)
  end
  
  objective_button_tree:clear()
  
  for texture in pairs(active_textures) do
    active_textures[texture] = nil
    unused_textures[texture] = true
    texture:Hide()
  end
  
  table.wipe(ant_lines)
  next_antline_offset = 0
  
  local w, h = overlay:GetWidth(), overlay:GetHeight()
  
  displayed_area, displayed_continent, displayed_level = GetCurrentMapAreaID(), GetCurrentMapContinent(), GetCurrentMapDungeonLevel()
  
  if displayed_area == -1 and displayed_continent ~= 0 then
    -- Probably looking at the cosmic map, or some area that doesn't have a map.
    -- In any case, don't display anything.
    return
  end
  
  local prev_area, prev_level, prev_x, prev_y, prev_ex, prev_ey = shared.whereAmI()
  
  local player_inside = prev_ex and true or false
  
  for link in pairs(links) do
    if link.forward_time then
      local a, l, x, y = link:getOrigin()
      
      if a == displayed_area and l == displayed_level then
        createLinkButton(x, y, link)
      end
    end
    
    if link.reverse_time then
      local a, l, x, y = link:getDestination()
      
      if a == displayed_area and l == displayed_level then
        createLinkButton(x, y, link)
      end
    end
  end
  
  for i, objective in ipairs(shared.route) do
    local area, level, x, y, ex, ey = objective:getRouteLocation()
    
    local button
    
    if area then
      local x, y = shared.convertCoords(area, level, x, y, displayed_area, displayed_level)
      
      local radius = button_base_diameter/2*button_scale
      local x_gap, y_gap = radius/w, radius/h
      
      if x and y and x > x_gap and y > y_gap and x < 1-x_gap and y < 1-y_gap then
        button = createObjectiveButton(x, y, objective)
      end
    end
    
    if button then
      for area, level, x, y, ex, ey in objective:iterate() do
        local x, y = shared.convertCoords(area, level, x, y, displayed_area, displayed_level)
        
        if x and y and x > 0 and y > 0 and x < 1 and y < 1 then
          local location_inside = ex and true or false
          local texture = getTexture("OVERLAY", "Interface\\AddOns\\QuestHelper2\\MapOverlay\\PointShadow.tga")
          texture:SetWidth(12)
          texture:SetHeight(12)
          texture:SetAlpha(player_inside == location_inside and 1 or 0.4)
          texture:SetTexCoord(0,1,0,1)
          texture:SetPoint("CENTER", overlay, "BOTTOMLEFT", x*w, h-y*h)
          active_textures[texture] = true
          texture:Hide()
          button.textures[texture] = true
        end
      end
    end
    
    if area and prev_area then
      drawPath(prev_area, prev_level, prev_x, prev_y, prev_ex, prev_ey,
              area, level, x, y, ex, ey)
    end
    
    if area then
      prev_area, prev_level, prev_x, prev_y, prev_ex, prev_ey = area, level, x, y, ex, ey
    end
  end
  
  redrawAnts()
end

shared.onMapLinkDisabled(function (link)
  if links[link] then
    links[link] = nil
    shared.callOnce(redrawRoute)
  end
end)

shared.onMapLinkEnabled(function (link)
  local area1, level1 = link:getOrigin()
  local area2, level2 = link:getDestination()
  
  if area1 ~= area2 or level1 ~= level2 then
    links[link] = true
    shared.callOnce(redrawRoute)
  end
end)

-- Maps a frame we're allowed to use as a parent to the level offset to use when attaching.
local parent_frames = {}

-- A set of watcher frames that aren't being used at the moment.
local unused_watchers = {}

-- Maps a frame to the watcher responsible for keeping an eye on it.
local frame_watcher_map = {}

local function updateFrameLevel()
  local parent = overlay:GetParent()
  local level = parent:GetFrameLevel()+parent_frames[parent]
  overlay:SetFrameLevel(level)
  
  for button in pairs(objective_buttons) do
    button:SetFrameStrata(parent:GetFrameStrata())
    button:SetFrameLevel(level+1)
  end
end

local function switchParent(parent)
  if parent ~= overlay:GetParent() then
    overlay:ClearAllPoints()
    overlay:SetParent(parent)
    overlay:SetFrameStrata(parent:GetFrameStrata())
    updateFrameLevel()
    overlay:SetAllPoints()
    redrawRoute()
  end
end

local visible_parents = {}

overlay:SetScript("OnShow", function ()
  redrawRoute()
  shared.onDelay(.1, redrawAnts)
end)

overlay:SetScript("OnHide", function ()
  if overlay:IsShown() then
    -- This is processed by callOnce, so that the actual changing of parents
    -- happens outside of the OnHide event. Failure to do this can
    -- trigger a bug in WoW that can cause the game to crash.
    shared.callOnce(function ()
      for frame in pairs(visible_parents) do
        -- We have to check if its visible, because one of these
        -- might be our current parent, for which to visibility watcher
        -- hasn't had its OnHide callback invoked yet.
        if frame:IsVisible() then
          switchParent(frame)
          return
        end
      end
    end)
  end
  
  shared.cancelDelayed(redrawAnts)
end)

shared.onEvent("WORLD_MAP_UPDATE", shared.callOnce, function ()
  if overlay:IsVisible() then
    if displayed_area ~= GetCurrentMapAreaID() or
       displayed_continent ~= GetCurrentMapContinent() or
       displayed_level ~= GetCurrentMapDungeonLevel() then
      redrawRoute()
    end
  end
end)

shared.onSetting("overlay_shown", function (enabled)
  overlay[enabled and "Show" or "Hide"](overlay)
end)

local function settingsChanged()
  ant_spacing = shared.settings.overlay_ant_spacing
  ant_speed = shared.settings.overlay_ant_speed
  ant_opacity = shared.settings.overlay_ant_opacity
  ant_scale = shared.settings.overlay_ant_scale
  trail_opacity = shared.settings.overlay_trail_opacity
  button_scale = shared.settings.overlay_button_scale
  shared.callOnce(redrawRoute)
end

shared.onSetting("overlay_shown", settingsChanged)
shared.onSetting("overlay_ant_spacing", settingsChanged)
shared.onSetting("overlay_ant_speed", settingsChanged)
shared.onSetting("overlay_ant_opacity", settingsChanged)
shared.onSetting("overlay_ant_scale", settingsChanged)
shared.onSetting("overlay_trail_opacity", settingsChanged)
shared.onSetting("overlay_button_scale", settingsChanged)

shared.onRouteChanged(function ()
  if overlay:IsVisible() then
    redrawRoute()
  end
end)

local function visibilityWatcherHidden(self)
  if not unused_watchers[self] then
    -- Our parent is now invisible.
    visible_parents[self:GetParent()] = nil
  end
end

local function visibilityWatcherShown(self)
  if not unused_watchers[self] then
    -- Our parent is now invisible.
    local parent =self:GetParent()
    visible_parents[parent] = true
    
    if not overlay:IsVisible() then
      -- The overlay isn't visible, so move it to this new visible parent.
      shared.callOnce(switchParent, 0, parent)
    end
  end
end

function shared.addOverlayToFrame(frame, level)
  level = level or 1
  
  local old_level = parent_frames[frame]
  
  if old_level then
    -- Simply updating the frame level; not adding a new frame.
    
    if level ~= old_level then
      parent_frames[frame] = level
      
      -- If this is the frame we're already attached to, update our level.
      if overlay:GetParent() == frame then
        updateFrameLevel()
      end
    end
  else
    -- Adding a new frame.
    parent_frames[frame] = level
    
    if frame:IsVisible() then
      visible_parents[frame] = true
    end
    
    -- Create a tiny little invisible frame to keep an eye on when the parent frame changes visibility.
    local visibility_watcher = next(unused_watchers)
    
    if visibility_watcher then
      visibility_watcher:SetParent(frame)
      unused_watchers[visibility_watcher] = nil
    else
      visibility_watcher = CreateFrame("Frame", nil, frame)
    end
    
    frame_watcher_map[frame] = visibility_watcher
    
    visibility_watcher:SetScript("OnHide", visibilityWatcherHidden)
    visibility_watcher:SetScript("OnShow", visibilityWatcherShown)
    
    -- Use this new frame for our parent, as it's visible and our current parent isn't.
    if frame:IsVisible() and not overlay:IsVisible() then
      switchParent(frame)
    end
  end
end

function shared.removeOverlayFromFrame(frame)
  if parent_frames[frame] then
    local visibility_watcher = assert(frame_watcher_map[frame])
    unused_watchers[visibility_watcher] = true
    frame_watcher_map[frame] = nil
    visible_parents[frame] = nil
    parent_frames[frame] = nil
    
    if frame == overlay:GetParent() then
      switchParent(next(visible_parents) or next(parent_frames))
    end
  end
end

shared.addOverlayToFrame(WorldMapButton, 1)
