-- Copyright (C) 2010 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 L = shared.L

local locations = shared.createLocationList()

local objective_map = CreateFrame("ScrollFrame", nil, UIParent)
objective_map:Hide()

local map_display = CreateFrame("Frame", nil, objective_map)
objective_map:SetScrollChild(map_display)

local inside_overlay = map_display:CreateTexture(nil, "ARTWORK")
inside_overlay:SetTexture("Interface\\AddOns\\QuestHelper2\\MapOverlay\\InsideMask.tga")
inside_overlay:SetAllPoints()
inside_overlay:SetAlpha(0.7)

local no_map_background = objective_map:CreateTexture(nil, "BACKGROUND")
no_map_background:SetTexture(.3,.3,.3)
no_map_background:SetAllPoints()

local area_title = map_display:CreateFontString(nil, "OVERLAY", "GameFontHighlightExtraSmall")
area_title:SetPoint("TOP", 0, -3)

local area_title_shadow = map_display:CreateTexture(nil, "OVERLAY")
area_title_shadow:SetPoint("BOTTOMLEFT", area_title, -4, -4)
area_title_shadow:SetPoint("TOPRIGHT", area_title, 4, 4)
area_title_shadow:SetTexture("Interface\\AddOns\\QuestHelper2\\MapOverlay\\Shadow.tga")
area_title_shadow:SetVertexColor(0,0,0,.5)

local no_map_text = objective_map:CreateFontString(nil, "OVERLAY", "GameFontHighlightHuge")
no_map_text:SetAllPoints()
shared.onSetting("locale", function () no_map_text:SetText(L.OBJECTIVE_NO_MAP_AVAILABLE) end)

local textures, target_textures, unused_textures, player_blip = {}, {}, {}, nil

local function getTexture(layer)
  local texture = next(unused_textures)
  
  if texture then
    unused_textures[texture] = nil
    texture:SetDrawLayer(layer)
    texture:ClearAllPoints()
    texture:SetTexCoord(0,1,0,1)
    texture:SetAlpha(1)
    texture:SetVertexColor(1,1,1)
    texture:Show()
  else
    texture = map_display:CreateTexture(nil, layer)
  end
  
  return texture
end

local block_size = 533.33333

local pixels_per_block = 80

local function mapPositionToBlockPosition(x, y)
  return (x+block_size*32)/block_size, (y+block_size*32)/block_size
end

-- This function gets created/updated by updateObjectiveMap
local addBlip

local function updateBlipTargets()
  local angle = GetTime()*-2
  
  for texture, theta in pairs(target_textures) do
    texture:SetRotation(angle+theta)
  end
  
  if player_blip then
    -- Remove the old player blip if there is one.
    player_blip:Hide()
    textures[player_blip] = nil
    unused_textures[player_blip] = true
  end
  
  -- Create a new blip for the player.
  player_blip = addBlip(1,1,1, false, shared.whereAmI())
  
  if player_blip then
    -- If we created one, customize it.
    player_blip:SetWidth(22)
    player_blip:SetHeight(22)
    player_blip:SetTexture("Interface\\AddOns\\QuestHelper2\\MapOverlay\\Player.tga")
    player_blip:SetRotation(GetPlayerFacing())
  end
  
  return 0.1
end

local function updateObjectiveMap()
  shared.cancelDelayed(updateBlipTargets)
  
  if objective_map:IsVisible() then
    local map, mapname
    local area, level, x, y, ex, ey = locations:nearestLocation()
    
    if area then
      x, y, map = shared.mapPosition(area, level, x, y)
      
      if map then
        mapname = shared.static.map_names[map]
      end
    end
    
    player_blip = nil
    
    for texture in pairs(textures) do
      texture:Hide()
      textures[texture] = nil
      unused_textures[texture] = true
    end
    
    for texture in pairs(target_textures) do
      texture:Hide()
      target_textures[texture] = nil
      unused_textures[texture] = true
    end
    
    if mapname then
      area_title:SetText(shared.areaName(area, level))
      
      local pixels_per_block = pixels_per_block * (ex and 5 or 1)
      
      local w, h = map_display:GetWidth(), map_display:GetHeight()
      
      x, y = mapPositionToBlockPosition(x, y)
      
      local function blockPositionToFramePosition(bx, by)
        return (bx-x)*pixels_per_block+w/2, (y-by)*pixels_per_block-h/2
      end
      
      local function mapPositionToFramePosition(mx, my)
        return blockPositionToFramePosition(mapPositionToBlockPosition(mx, my))
      end
      
      for v = math.floor(y-h/pixels_per_block/2),math.ceil(y+h/pixels_per_block/2) do
        for u = math.floor(x-w/pixels_per_block/2),math.ceil(x+w/pixels_per_block/2) do
          local texture = getTexture("BACKGROUND")
          
          texture:SetTexture(0,0,0) -- Set the texture to black first, so that it will show up as black if the minimap texture fails to load.
          texture:SetTexture(("World\\Minimaps\\%s\\map%d_%d.blp"):format(mapname, u, v))
          texture:SetHeight(pixels_per_block)
          texture:SetWidth(pixels_per_block)
          texture:SetPoint("TOPLEFT", map_display, blockPositionToFramePosition(u, v))
          textures[texture] = true
        end
      end
      
      local function addRawBlip(r, g, b, a, target, x, y)
        local blip = getTexture("ARTWORK")
        textures[blip] = true
        blip:SetTexture("Interface\\AddOns\\QuestHelper2\\MapOverlay\\PointShadow.tga")
        blip:SetVertexColor(r, g, b, a)
        blip:SetWidth(12)
        blip:SetHeight(12)
        blip:SetPoint("CENTER", map_display, "TOPLEFT", x, y)
        
        if target then
          local target = getTexture("OVERLAY")
          target:SetTexture("Interface\\AddOns\\QuestHelper2\\MapOverlay\\Target.tga")
          target:SetVertexColor(r, g, b, a)
          target:SetWidth(20)
          target:SetHeight(20)
          target:SetPoint("CENTER", map_display, "TOPLEFT", x, y)
          
          -- This value we're assigning gets added to its angle, so that all the little targets don't look exactly the same.
          target_textures[target] = math.random()*math.pi*2
        end
        
        return blip
      end
      
      -- This function gets used by updateBlipTargets to keep the player's blip up-to-date.
      addBlip = function(r, g, b, target, area, level, ox, oy, obj_inside)
        if area then
          local x, y, m = shared.mapPosition(area, level, ox, oy)
          
          if m == map then
            local alpha = ((ex and not obj_inside) or (not ex and obj_inside)) and .5 or 1
            
            return addRawBlip(r, g, b, alpha, target, mapPositionToFramePosition(x, y))
          end
        end
      end
      
      if ex then
        inside_overlay:Show()
        
        -- Draw a green blip for the entrance.
        addBlip(0,1,0, false, area, level, ex, ey)
      else
        inside_overlay:Hide()
      end
      
      local positions = {}
      
      -- Collect locations, and merge any that are going to end up overlapping on the map so that it doesn't look ugly.
      for area, level, ox, oy, oex, oey, ow in locations:iterate() do
        local x, y, m = shared.mapPosition(area, level, ox, oy)
        
        if m == map then
          x, y = mapPositionToFramePosition(x, y)
          local a = ((ex and not oex) or (not ex and oex)) and .5 or 1
          
          local best, best_d2
          
          for i=1,#positions,4 do
            local dx, dy = positions[i]-x, positions[i+1]-y
            local d2 = dx*dx + dy*dy
            
            if d2 < 20 and (not best or d2 < best_d2) then
              best, best_d2 = i, d2
            end
          end
          
          if best then
            local w = positions[best+3]
            positions[best  ] = (positions[best  ]*w+x*ow)/(w+ow)
            positions[best+1] = (positions[best+1]*w+y*ow)/(w+ow)
            positions[best+2] = (positions[best+2]*w+a*ow)/(w+ow)
            positions[best+3] = w+ow
          else
            local offset=#positions+1
            positions[offset  ] = x
            positions[offset+1] = y
            positions[offset+2] = a
            positions[offset+3] = ow
          end
        end
      end
      
      -- Draw a white blip for each of the locations we ended up creating.
      for i=1,#positions,4 do
        addRawBlip(1,1,1, positions[i+2], true, positions[i], positions[i+1])
      end
      
      updateBlipTargets()
      
      no_map_background:Hide()
      no_map_text:Hide()
      map_display:SetAllPoints()
      map_display:Show()
      
      shared.onDelay(0.1, updateBlipTargets)
    else
      no_map_background:Show()
      no_map_text:Show()
      map_display:Hide()
    end
  end
end

objective_map:SetScript("OnShow", updateObjectiveMap)
objective_map:SetScript("OnSizeChanged", updateObjectiveMap)

local timer_objective
local creation_time_string

local function updateCreationTimeString()
  creation_time_string:SetText(timer_objective.creation_time)
  return 1
end

objective_map:SetScript("OnHide", function ()
  -- This is way more complicated than it needs to be, to work around a WoW bug.
  -- The game will crash if objective_map is hidden immediately.
  -- http://us.battle.net/wow/en/forum/topic/1207760776
  shared.callOnce(function ()
    if not objective_map:IsVisible() then
      local tooltip = objective_map:GetParent()
      
      if tooltip ~= UIParent then
      -- Tooltips don't normally reset their padding, which we set to make space for the map.
        tooltip:SetPadding(0)
        
        -- This map is now unused until the next time a tooltip is needed for an objective.
        objective_map:SetParent(UIParent)
        objective_map:Hide()
        objective_map:ClearAllPoints()
        
        shared.cancelDelayed(updateCreationTimeString)
        shared.cancelDelayed(updateBlipTargets)
      end
    end
  end)
end)

local function addObjectiveMapToTooltip(tooltip)
  if objective_map:GetParent() ~= UIParent then
    shared.cancelDelayed(updateCreationTimeString)
    shared.cancelDelayed(updateBlipTargets)
  end
  
  tooltip:SetPadding(160)
  
  objective_map:SetParent(tooltip)
  objective_map:SetPoint("TOPRIGHT", tooltip, "TOPRIGHT", -5, -5)
  objective_map:SetPoint("BOTTOMLEFT", tooltip, "BOTTOMRIGHT", -162, 5)
  objective_map:Show()
end

function shared.setObjectiveTooltip(objective, tooltip)
  objective, tooltip = assert(objective), tooltip or GameTooltip
  
  tooltip:SetText(objective.formatted_text)
  
  if objective.comment ~= "" then
    tooltip:AddLine(objective.comment, 1, 1, 1, true)
  end
  
  tooltip:AddDoubleLine(L.OBJECTIVE_PRIORITY, objective.priority_text, nil, nil, nil, 1, 1, 1)
  
  if objective.owned then
    if not objective.obtainable then
      tooltip:AddLine(L.OBJECTIVE_CANT_COMPLETE, 1, .2, 0, true)
    elseif objective.complete then
      tooltip:AddLine(L.OBJECTIVE_COMPLETE, 0, 1, 0, true)
    end
  else
    tooltip:AddLine(L.OBJECTIVE_DEFUNCT, .8, 0, 0, true)
  end
  
  local first=true
  for dep in objective:fwdDependencies() do
    -- Need to use a space rather than an empty string, an empty string has no height,
    -- and tooltips only consider the left string for calculating height.
    tooltip:AddDoubleLine(first and L.OBJECTIVE_REQUIRES or " ", dep.text, nil, nil, nil, 1, 1, 1)
    first = false
  end
  
  first = true
  for dep in objective:revDependencies() do
    tooltip:AddDoubleLine(first and L.OBJECTIVE_REQUIRED_BY or " ", dep.text, nil, nil, nil, 1, 1, 1)
    first = false
  end
  
  if objective.owned then
    tooltip:AddDoubleLine(L.OBJECTIVE_CREATOR, objective.creator, nil, nil, nil, 1, 1, 1)
  end
  
  tooltip:AddDoubleLine(L.OBJECTIVE_CREATION_TIME, objective.creation_time, nil, nil, nil, 1, 1, 1)
  
  creation_time_string = _G[tooltip:GetName().."TextRight"..tooltip:NumLines()]
  
  timer_objective = objective
  shared.onDelay(1, updateCreationTimeString)
  
  locations:clear()
  
  for area, level, x, y, ex, ey, w in objective:iterate() do
    locations:addLocation(area, level, x, y, ex, ey, w)
  end
  
  addObjectiveMapToTooltip(tooltip)
end

function shared.setObjectiveListTooltip(objective_list, tooltip)
  if #objective_list == 1 then
    -- If there is only one objective, only show it.
    shared.setObjectiveTooltip(objective_list[1], tooltip)
  elseif #objective_list > 1 then
    tooltip = tooltip or GameTooltip
    
    tooltip:SetText(L.OBJECTIVE_MULTIPLE)
    
    locations:clear()
    
    for i, objective in ipairs(objective_list) do
      if objective.obtainable then
        tooltip:AddDoubleLine("|T"..objective.icon..":20:20|t"..objective.text, objective.formatted_progress, 1, 1, 1, 1, 1, 1)
      else
        -- If only it were possible to embed a desaturated texture in a string...
        -- Hell, I'd even settle for transparent.
        tooltip:AddLine("|TInterface\\AddOns\\QuestHelper2\\Icons\\Waypoint.tga:20:20|t"..objective.text, .7, .7, .7)
      end
      
      for area, level, x, y, ex, ey, w in objective:iterate() do
        locations:addLocation(area, level, x, y, ex, ey, w)
      end
      
      locations:pruneLocations(8)
    end
    
    addObjectiveMapToTooltip(tooltip)
  end
end
