Quantcast
--[[-------------------------------------------------------------------
--  TomTomLite - Copyright 2010 - James N. Whitehead II
-------------------------------------------------------------------]]--

local addonName, addon = ...
local L = addon.L

addon.callbacks = LibStub("CallbackHandler-1.0"):New(addon)
addon.libwindow = LibStub("LibWindow-1.1")

-- Set up some tables to track waypoints
do
    local cache = {}
    addon.waypoints = setmetatable({}, {
        __newindex = function(t, k, v)
            if type(v) == "nil" then
                -- A waypoint is being removed
                local oldwaypoint = rawget(t, k)
                rawset(t, k, nil)
                local map = oldwaypoint[1]
                if map then
                    cache[map] = nil
                end
            else
                -- A waypoint is being added
                rawset(t, k, v)
                local map = v[1]
                if map then
                    cache[map] = nil
                end
            end
        end,
    })
    addon.waypointsByMap = setmetatable({}, {
        __index = function(t, k)
            local cached = cache[k]
            if cached then
                return cached
            end
            -- Look up all the waypoints for a given mapId
            local set = {}
            for idx, waypoint in ipairs(addon.waypoints) do
                if waypoint[1] == k then
                    table.insert(set, waypoint)
                end
            end
            rawset(cache, k, set)
            return set
        end
    })
end

function addon:Initialize()
    self.db = LibStub("AceDB-3.0"):New("TomTomLiteDB", self.defaults)

    self.arrow = self:CreateCrazyArrow("TomTomLiteArrow")
    self.arrow:SetPoint("CENTER", 0, 0)
    self.arrow:Hide()

    -- Events for objective tracking
    self:RegisterEvent("QUEST_POI_UPDATE", "OBJECTIVES_CHANGED")
    self:RegisterEvent("QUEST_LOG_UPDATE", "OBJECTIVES_CHANGED")
    self:RegisterMessage("OBJECTIVES_CHANGED")
    hooksecurefunc("WatchFrame_Update", function(self)
        addon:FireMessage("OBJECTIVES_CHANGED")
    end)

    self:RegisterMessage("TOMTOMLITE_WAYPOINT_ADDED")

    -- Events for world map overlays
    self:RegisterEvent("WORLD_MAP_UPDATE")
end

function addon:CreateCrazyArrow(name, parent)
    parent = parent or UIParent
    local frame = CreateFrame("Button", name, parent)

    frame:SetSize(128, 128)
    frame.arrow = frame:CreateTexture("OVERLAY")
    frame.arrow:SetAllPoints()
    frame.arrow:SetTexture("Interface\\Addons\\TomTomLite\\images\\BevArrow")

    frame.title = frame:CreateFontString("OVERLAY", name .. "Title", "GameFontHighlight")
    frame.info = frame:CreateFontString("OVERLAY", name .. "Info", "GameFontHighlight")
    frame.subtitle = frame:CreateFontString("OVERLAY", name .. "Subtitle", "GameFontHighlight")

    frame.title:SetPoint("TOP", frame, "BOTTOM", 0, 0)
    frame.info:SetPoint("TOP", frame.title, "BOTTOM", 0, 0)
    frame.subtitle:SetPoint("TOP", frame.info, "BOTTOM", 0, 0)

    frame:Hide()

    local PI2 = math.pi * 2

    -- Set up the OnUpdate handler
    frame:SetScript("OnUpdate", function(self, elapsed)
        local map, floor, x, y = unpack(self.waypoint)
        local distance, angle = addon:GetVectorFromCurrent(map, floor, x, y)

        local facing = GetPlayerFacing()
        local faceangle = angle - facing

        local perc = math.abs((math.pi - math.abs(faceangle)) / math.pi)
        local gr,gg,gb = unpack(addon.db.profile.goodcolor)
        local mr,mg,mb = unpack(addon.db.profile.middlecolor)
        local br,bg,bb = unpack(addon.db.profile.badcolor)
        local r,g,b = addon:ColorGradient(perc, br, bg, bb, mr, mg, mb, gr, gg, gb)

        self.arrow:SetVertexColor(r,g,b)
        self.arrow:SetRotation(faceangle)

        self.subtitle:SetFormattedText("%.1f yards", distance)
    end)

    self.libwindow.RegisterConfig(frame, self.db.profile.positions)
    self.libwindow.RestorePosition(frame)
    self.libwindow.MakeDraggable(frame)
    self.libwindow.EnableMouseOnAlt(frame)
    self.libwindow.EnableMouseWheelScaling(frame)

    return frame
end

--[[-------------------------------------------------------------------------
--  External API
-------------------------------------------------------------------------]]--

function addon:AddWaypoint(map, floor, x, y, opt)
    assert(type(map) == "number")
    assert(type(floor) == "number")
    assert(type(x) == "number")
    assert(type(y) == "number")

    local waypoint = {map, floor, x, y}
    if type(opt) == "table" then
        for k, v in pairs(opt) do
            if type(k) ~= "number" then
                waypoint[k] = v
            end
        end
    end

    table.insert(self.waypoints, waypoint)
    self:FireMessage("TOMTOMLITE_WAYPOINT_ADDED", waypoint)

    return waypoint
end

function addon:DeleteWaypoint(waypoint)
    for idx, entry in ipairs(self.waypoints) do
        if entry == waypoint then
            table.remove(self.waypoints, waypoint)
            break
        end
    end

    self:FireMessage("TOMTOMLITE_WAYPOINT_DELETED", waypoint)
end

--[[-------------------------------------------------------------------------
--  Private implementation
-------------------------------------------------------------------------]]--
function addon:TOMTOMLITE_WAYPOINT_ADDED(msg, waypoint, ...)
    self:UpdateArrow()
end

function addon:OBJECTIVES_CHANGED()
    self:UpdateQuestObjectives()
    self:UpdateArrow()
end

function addon:UpdateArrow()
    -- local cmap, cfloor = GetCurrentMapAreaID()
    -- local cx, cy = GetPlayerMapPosition("player")

    -- -- Scan the current waypoints and determine which one is closest, regardless
    -- -- of which zone the waypoint is in. This could be altered to only consider
    -- -- waypoints in the current zone, quite easily.
    -- local mindist = math.huge
    -- local closest

    -- for idx, waypoint in ipairs(self.waypoints) do
    --     local map, floor, x, y = unpack(waypoint)
    --     local distance = addon.mapdata:DistanceWithinContinent(cmap, cfloor or 0, cx, cy, map, floor, x, y)
    --     if distance < mindist then
    --         mindist = distance
    --         closest = waypoint
    --     end
    -- end

    local closest = self.arrow.waypoint

    if closest then
        -- Set the crazy arrow to display this waypoint
        local zone, floor, x, y = unpack(closest)
        local lzone = self:GetMapDisplayName(zone)

        self.arrow.waypoint = closest
        self.arrow.title:SetText(closest.title or L["Unknown waypoint"])
        self.arrow.info:SetFormattedText("%.2f, %.2f - %s", x * 100, y * 100, lzone)
        self.arrow:Show()
    else
        self.arrow.waypoint = nil
        self.arrow:Hide()
    end
end

local objectiveWaypoints = {}
function addon:UpdateQuestObjectives()
    local map, floor = GetCurrentMapAreaID()
    floor = floor or 0

    -- Do not set any NEW waypoints if we're on the continent map
    if self:IsContinentMap(map) then
        return
    end

    local cvar = GetCVarBool("questPOI")
    SetCVar("questPOI", 1)

    -- Only do an objective scan if the option is enabled
    if not self.db.profile.trackQuestObjectives then
        return
    end

    QuestPOIUpdateIcons()

    -- Scan through every quest that is being tracked, and create a waypoint
    -- for each of the objectives that are being tracked. These waypoints will
    -- be unordered, and will be sorted or ordered by the user/algorithm
    local closest
    local watchIndex = 1
    while true do
        local questIndex = GetQuestIndexForWatch(watchIndex)
        if not questIndex then
            break
        end

        local title = GetQuestLogTitle(questIndex)
        local qid = select(9, GetQuestLogTitle(questIndex))
        local completed, x, y, objective = QuestPOIGetIconInfo(qid)

        if x and y then
            -- For two waypoints to be equal, their map, floor, x, y should all be
            -- the same, as well as the title and completion set.
            local key = qid + (x * 100) * (y * 100) * map * (floor + 1)
            if not objectiveWaypoints[key] then
                if completed then
                    title = "Turn in: " .. title
                end

                local waypoint = self:AddWaypoint(map, floor, x, y, {title = title})
                objectiveWaypoints[key] = waypoint
                if not closest then
                    closest = objectiveWaypoints[key]
                end
            else
                if not closest then
                    closest = objectiveWaypoints[key]
                end
            end
        end

        watchIndex = watchIndex + 1
    end

    self.arrow.waypoint = closest

    SetCVar("questPOI", cvar and 1 or 0)
end

--[[-------------------------------------------------------------------------
--  World map support, displaying waypoint overlays
-------------------------------------------------------------------------]]--

-- Create an overlay that we can use to parent our world map icons
addon.overlay = CreateFrame("Frame", addonName .. "MapOverlay", WorldMapButton)
addon.overlay:SetAllPoints()
addon.overlay:Show()

-- Metatable that stores world map icons indexed by number, in array form
local worldmapIcons = setmetatable({}, {
    __index = function(t, k)
        local name = addonName .. "MapIcon" .. tostring(k)
        local button = CreateFrame("Button", name, addon.overlay)
        button:SetSize(64, 64)
        button:SetHitRectInsets(12, 12, 5, 2)

        button.icon = button:CreateTexture("BACKGROUND")
        button.icon:SetTexture("Interface\\AddOns\\TomTomLite\\images\\MapPointer")
        button.icon:SetVertexColor(0.3, 1.0, 0.3)
        button.icon:SetAllPoints()
        button.glow = button:CreateTexture("BACKGROUND")
        button.glow:SetTexture("Interface\\AddOns\\TomTomLite\\images\\MapPointerGlow")
        button.glow:SetAllPoints()
        button.glow:Hide()
        button.number = button:CreateTexture("OVERLAY", name .. "Number")
        button.number:SetSize(50, 50)
        button.number:SetTexture("Interface\\WorldMap\\UI-QuestPoi-NumberIcons")
        button.number:SetPoint("CENTER", button, "CENTER", 0, 8)
        button.number:SetDrawLayer("OVERLAY", 7)

        rawset(t, k, button)
        return button
    end,
})

function addon:WORLD_MAP_UPDATE()
    -- Display any waypoints overlaid on the world map. Specifically, if
    -- the map zoom is set to a continent or cosmic map, then display all
    -- waypoints, otherwise display only the waypoints for the currently
    -- displayed zone. If there are waypoints on a floor other than the
    -- one the player is currently on, they will be displayed as well, and
    -- will be distinguishable from waypoints on the current floor.

    -- If the map isn't shown, do nothing
    if not addon.overlay:IsVisible() then
        return
    end

    -- Check the options to see what should be displayed
    if not self.db.profile.showMapIconsZone then
        return
    end

    -- TODO: Handle 'showMapIconsContinent' option

    local continent = GetCurrentMapContinent()
    local map, floor = GetCurrentMapAreaID()
    local waypoints = addon.waypointsByMap[map]

    for idx = 1, math.max(#waypoints, #worldmapIcons), 1 do
        local icon = worldmapIcons[idx]
        local waypoint = waypoints[idx]

        if waypoint then
            local width, height = addon.overlay:GetSize()

            icon:ClearAllPoints()

            local x = waypoint[3] * width
            local y = waypoint[4] * height

            -- Set the number to be displayed
            local buttonIndex = idx - 1
            local yOffset = 0.5 + math.floor(buttonIndex / QUEST_POI_ICONS_PER_ROW) * QUEST_POI_ICON_SIZE;
            local xOffset = mod(buttonIndex, QUEST_POI_ICONS_PER_ROW) * QUEST_POI_ICON_SIZE
            icon.number:SetTexCoord(xOffset, xOffset + QUEST_POI_ICON_SIZE, yOffset, yOffset + QUEST_POI_ICON_SIZE)

            -- Nudge position so arrow appears centered on POI
            icon:SetPoint("BOTTOM", addon.overlay, "TOPLEFT", x + 1, -y - 5)

            if (floor or 0) == waypoint[2] then
                icon:SetAlpha(1.0)
            else
                icon:SetAlpha(0.6)
            end

            icon:Show()
        else
            icon:Hide()
        end
    end
end