local enableClicks = true       -- True if waypoint-clicking is enabled to set points
local enableClosest = true      -- True if 'Automatic' quest waypoints are enabled
local modifier                  -- A string representing click-modifiers "CAS", etc.

local modTbl = {
    C = IsControlKeyDown,
    A = IsAltKeyDown,
    S = IsShiftKeyDown,
}

local L = TomTomLocals
local astrolabe = DongleStub("Astrolabe-1.0")

-- This function and the related events/hooks are used to automatically
-- update the crazy arrow to the closest quest waypoint.
local lastWaypoint
local scanning          -- This function is not re-entrant, stop that

local function ObjectivesChanged()
    -- This function should only run if enableClosest is set
    if not enableClosest then
        return
    end

    -- This function may be called while we are processing this function
    -- so stop that from happening.
    if scanning then
        return
    else
        scanning = true
    end

    local map, floor = GetCurrentMapAreaID()
    local floors = astrolabe:GetNumFloors(map)
    floor = floor and floor or (floors == 0 and 0 or 1)

    local px, py = GetPlayerMapPosition("player")

    -- Bail out if we can't get the player's position
    if not px or not py or px <= 0 or py <= 0 then
        scanning = false
        return
    end

    -- THIS CVAR MUST BE CHANGED BACK!
    local cvar = GetCVarBool("questPOI")
    SetCVar("questPOI", 1)

    local closest
    local closestdist = math.huge

    -- This function relies on the above CVar being set, and updates the icon
    -- position information so it can be queries via the API
    QuestPOIUpdateIcons()

    -- Scan through every quest that is tracked, and find the closest one
    local watchIndex = 1
    while true do
        local questIndex = GetQuestIndexForWatch(watchIndex)

        if not questIndex then
            break
        end

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

        if x and y then
            local dist, xd, yd = astrolabe:ComputeDistance(map, floor, px, py, map, floor, x, y)
            if dist < closestdist then
                closest = watchIndex
                closestdist = dist
            end
        end
        watchIndex = watchIndex + 1
    end

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

        if completed then
            title = "Turn in: " .. title
        end

        local setWaypoint = true
        if lastWaypoint then
            -- This is a hack that relies on the UID format, do not use this
            -- in your addons, please.
            local pm, pf, px, py = unpack(lastWaypoint)
            if map == pm and floor == pf and x == px and y == py and lastWaypoint.title == title then
                -- This is the same waypoint, do nothing
                setWaypoint = false
            else
                -- This is a new waypoint, clear the previous one
                TomTom:RemoveWaypoint(lastWaypoint)
            end
        end

        if setWaypoint then
            -- Set the new waypoint
            lastWaypoint = TomTom:AddMFWaypoint(map, floor, x, y, {
                title = title,
                persistent = false,
                arrivaldistance = TomTom.profile.poi.arrival,
            })

            -- Check and see if the Crazy arrow is empty, and use it if so
            if TomTom:IsCrazyArrowEmpty() then
                TomTom:SetCrazyArrow(lastWaypoint, TomTom.profile.poi.arrival, title)
            end
        end
    else
        -- No closest waypoint was found, so remove one if its already set
        if lastWaypoint then
            TomTom:RemoveWaypoint(lastWaypoint)
            lastWaypoint = nil
        end
    end

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

local eventFrame = CreateFrame("Frame")
eventFrame:RegisterEvent("QUEST_POI_UPDATE")
eventFrame:RegisterEvent("QUEST_LOG_UPDATE")
hooksecurefunc("WatchFrame_Update", function(self)
    ObjectivesChanged()
end)

eventFrame:SetScript("OnEvent", function(self, event, ...)
    if event == "QUEST_POI_UPDATE" then
        ObjectivesChanged()
    elseif event == "QUEST_LOG_UPDATE" then
        ObjectivesChanged()
    end
end)

local poiclickwaypoints = {}
local function poi_OnClick(self, button)
    if not enableClicks then
        return
    end

    if button == "RightButton" then
        for i = 1, #modifier do
            local mod = modifier:sub(i, i)
            local func = modTbl[mod]
            if not func() then
                return
            end
        end
    else
        return
    end

    -- Run our logic, and set a waypoint for this button
    local m, f = GetCurrentMapAreaID()

    local questIndex = self.quest and self.quest.questLogIndex
    if not questIndex and self.questId then
        -- Lookup the questIndex for the given questId
        for idx = 1, GetNumQuestLogEntries(), 1 do
            local qid = select(9, GetQuestLogTitle(idx))
            if qid == self.questId then
                questIndex = idx
            end
        end
    end

    if not questIndex and self.index then
        questIndex = GetQuestIndexForWatch(self.index)
    end

    local title = GetQuestLogTitle(questIndex)
    local qid = select(9, GetQuestLogTitle(questIndex))
    local completed, x, y, objective = QuestPOIGetIconInfo(qid)
    if completed then
        title = "Turn in: " .. title
    end

    if not x or not y then
        -- No coordinate information for this quest/objective
        local header = "|cFF33FF99TomTom|r"
        print(L["%s: No coordinate information found for '%s' at this map level"]:format(header, title))
        return
    end

    local key = TomTom:GetKeyArgs(m, f, x, y, title)

    local alreadySet = false
    if poiclickwaypoints[key] then
        local uid = poiclickwaypoints[key]
        -- Check to see if it has been removed by the user
        if TomTom:IsValidWaypoint(uid) then
            alreadySet = true
        end
    end

    if not alreadySet then
        local uid = TomTom:AddMFWaypoint(m, f, x, y, {
            title = title,
            arrivaldistance = TomTom.profile.poi.arrival,
        })
        poiclickwaypoints[key] = uid
    end
end

local hooked = {}
hooksecurefunc("QuestPOI_DisplayButton", function(parentName, buttonType, buttonIndex, questId)
    local buttonName = "poi"..tostring(parentName)..tostring(buttonType).."_"..tostring(buttonIndex);
    local poiButton = _G[buttonName];

    if not hooked[buttonName] then
        poiButton:HookScript("OnClick", poi_OnClick)
        poiButton:RegisterForClicks("AnyUp")
        hooked[buttonName] = true
    end

    -- Check to see if there is a swap button
    local swapName = "poi" .. parentName .. "_Swap"
    local swapButton = _G[swapName]

    if not hooked[swapName] and swapButton then
        swapButton:HookScript("OnClick", poi_OnClick)
        swapButton:RegisterForClicks("AnyUp")
        hooked[swapName] = true
    end
end)

function TomTom:EnableDisablePOIIntegration()
    enableClicks= TomTom.profile.poi.enable
    modifier = TomTom.profile.poi.modifier
    enableClosest = TomTom.profile.poi.setClosest

    if not enableClosest and lastWaypoint then
        TomTom:RemoveWaypoint(lastWaypoint)
        lastWaypoint = nil
    elseif enableClosest then
        ObjectivesChanged()
    end
end
