Quantcast
SatchelQueue = LibStub("AceAddon-3.0"):NewAddon("SatchelQueue", "AceConsole-3.0", "AceEvent-3.0", "AceHook-3.0", "AceTimer-3.0")
local SatchelQueue = SatchelQueue

local BUTTON_TEXT = "Satchel"
local BUTTON_QUEUED = "Cancel"

local timer = nil

local sound_all = nil
local sound_bg = nil

local should_auto_loot = false

StaticPopupDialogs["SATCHEL_QUEUE"] = {
	text = "",
    button1 = ACCEPT,
    button2 = DECLINE,
	OnAccept = function(self)
		SetLFGDungeon(LE_LFG_CATEGORY_LFD, self.data)
		JoinLFG(LE_LFG_CATEGORY_LFD)
	end,
	OnShow = function(self)
		local name = GetLFGDungeonInfo(self.data)
		self.text:SetFormattedText(LFG_CALL_TO_ARMS .. name, "\n") -- :(
	end,
    hideOnEscape = 1,
    whileDead = 1
}

function SatchelQueue:OnInitialize()
    local defaults = {
        global = {
            icon = true,
            tooltip = false,
            requeue = false,
            prevent_auto_loot = true,
            fake_auto_loot = false,
            prioritise = false,
            exclusive = false,
            flash_enable = false,
            sound_enable = true,
            sound_force = false,
            sound_file = "Sound/Spells/Clearcasting_Impact_Chest.wav",

            saved_uids = {},
            items = {},
            count = 0,
            coin_min = -1,
            coin_max = -1,
            coin_total = 0
        }
    }
    self.db = LibStub("AceDB-3.0"):New("SatchelQueueDB", defaults, true)

    local option_table = {
        type = "group",
        set = function(info, val) self.db.global[info[#info]] = val end,
        get = function(info) return self.db.global[info[#info]] end,
        args = {
            header_queueing = {
                order = 0,
                type = "header",
                name = "Queueing"
            },
            prioritise = {
                order = 1,
                type = "toggle",
                width = "double",
                name = "Prioritise newer dungeons"
            },
            exclusive = {
                order = 2,
                type = "toggle",
                name = "Exclusively",
                desc = "Oueue exclusively for the newer dungeons when prioritised",
                disabled = function(info) return not self.db.global.prioritise end
            },
            requeue = {
                order = 3,
                type = "toggle",
                width = "full",
                name = "Requeue after leaving"
            },
            icon = {
                order = 4,
                type = "toggle",
                width = "full",
                name = "Enable LFG minimap icon while waiting"
            },
            header_satchel = {
                order = 5,
                type = "header",
                name = "Satchels"
            },
            prevent_auto_loot = {
                order = 6,
                type = "toggle",
                width = "double",
                name = "Prevent auto loot"
            },
            fake_auto_loot = {
                order = 7,
                type = "toggle",
                name = "Only protect BoP items",
                desc = "Non-BoP items will still be looted",
                disabled = function(info) return not self.db.global.prevent_auto_loot end
            },
            tooltip = {
                order = 8,
                type = "toggle",
                width = "double",
                name = "Enable contents in tooltip"
            },
            header_notification = {
                order = 9,
                type = "header",
                name = "Notification"
            },
            flash_enable = {
                order = 10,
                type = "toggle",
                width = "full",
                name = "Enable Screen Flash"
            },
            sound_enable = {
                order = 11,
                type = "toggle",
                width = "double",
                name = "Enable Sound"
            },
            sound_force = {
                order = 12,
                type = "toggle",
                name = "Force unmute for alert",
                disabled = function(info) return not self.db.global.sound_enable end
            },
            sound_file = {
                order = 13,
                type = "input",
                width = "full",
                name = "Sound File",
                disabled = function(info) return not self.db.global.sound_enable end
            }
        }
    }
    local stats_table = {
        type = "group",
        args = {
            text = {
                type = "input",
                name = "Statistics",
                width = "full",
                multiline = 24,
                get = function(info)
                    local s = "total satchels: " .. self.db.global.count .. "\n"
                    if self.db.global.coin_min > 0 then
                        s = s .. "\ntotal gold: " .. GetMoneyString(self.db.global.coin_total)
                        s = s .. "\nmin: " .. GetMoneyString(self.db.global.coin_min)
                        s = s .. "\nmax: " .. GetMoneyString(self.db.global.coin_max)
                        s = s .. "\navg: " .. GetMoneyString(self.db.global.coin_total / self.db.global.count) .. "\n"
                    end
                    s = s .. "\nitems:"
                    local items = {}
                    for k, v in pairs(self.db.global.items) do
                        table.insert(items, { id = k, count = v })
                    end
                    table.sort(items, function(a, b) return a.count > b.count end)
                    for i = 1, #items do
                        s = s .. "\n" .. (GetItemInfo(items[i].id) or "<item not cached>")  .. ": " .. items[i].count
                    end
                    return s
                end,
                set = nil
            },
            reset = {
                type = "execute",
                name = "reload",
                width = "full",
                func = function(info)
                    LibStub("AceConfigRegistry-3.0"):NotifyChange("Statistics")
                end
            }
        }
    }

    LibStub("AceConfig-3.0"):RegisterOptionsTable("SatchelQueue", option_table)
    LibStub("AceConfig-3.0"):RegisterOptionsTable("Statistics", stats_table)
    local option_frame = LibStub("AceConfigDialog-3.0"):AddToBlizOptions("SatchelQueue", "SatchelQueue")
    local stats_frame = LibStub("AceConfigDialog-3.0"):AddToBlizOptions("Statistics", "Stats", "SatchelQueue")
    self:RegisterChatCommand("satchel", function(...) InterfaceOptionsFrame_OpenToCategory(option_frame) end)
    self:RegisterChatCommand("satchelstats", function(...) InterfaceOptionsFrame_OpenToCategory(stats_frame) end)
end

local function SatchelID(link)
	if not link then
		return nil
	end
    return select(3, string.find(link, "|Hitem:69903:0:0:0:0:0:0:(%d+):")) or select(3, string.find(link, "|Hitem:90818:0:0:0:0:0:0:(%d+):")) or select(3, string.find(link, "|Hitem:104260:0:0:0:0:0:0:(%d+):")) or nil
end

local function Satchel_OnLeave(self, motion)
    SatchelQueue:Unhook(self, "OnLeave")
    SatchelQueue:Unhook(self, "PreClick")
    SatchelQueue:Unhook(self, "PostClick")
end

local toggle_loot, default_loot = nil, nil
local function Satchel_PreClick(self, button)
    if button == "RightButton" and GetCVarBool("autoLootDefault") ~= IsModifiedClick("AUTOLOOTTOGGLE") then
        should_auto_loot = true
        toggle_loot, default_loot = true, GetCVarBool("autoLootDefault")
        SetCVar("autoLootDefault", default_loot and 0 or 1)
    end
end

local function Satchel_PostClick(self, button)
    if toggle_loot then
        toggle_loot = false
        SetCVar("autoLootDefault", default_loot)
    end
end

local function Satchel_OnEnter(self, motion)
    if SatchelQueue.db.global.prevent_auto_loot and not SatchelQueue:IsHooked(self, "OnLeave") and SatchelID(GetContainerItemLink(self:GetParent():GetID(), self:GetID())) then
        SatchelQueue:HookScript(self, "OnLeave", Satchel_OnLeave)
        SatchelQueue:HookScript(self, "PreClick", Satchel_PreClick)
        SatchelQueue:HookScript(self, "PostClick", Satchel_PostClick)
    end
end

local function Satchel_TooltipText(self)
    local _, link = self:GetItem()
    local id = link and SatchelID(link) or nil
    if id and SatchelQueue.db.global.tooltip then
        if type(SatchelQueue.db.global.saved_uids[id]) == "table" then
            self:AddLine(" ")
            for slot = 1, #SatchelQueue.db.global.saved_uids[id] do
                local content = SatchelQueue.db.global.saved_uids[id][slot]
                if content < 0 then
                    self:AddLine("|cffffffff" .. GetCoinTextureString(-content) .. "|r")
                else
                    local name, _, quality = GetItemInfo(content)
                    if name then
                        local r, g, b = GetItemQualityColor(quality)
                        self:AddLine(name, r, g, b)
                    end
                end
            end
        end
    end
end

function SatchelQueue:OnEnable()
    self:Hook("ContainerFrameItemButton_OnEnter", Satchel_OnEnter, true)
    self:HookScript(GameTooltip, "OnTooltipSetItem", Satchel_TooltipText)
    self:SecureHook("QueueStatusFrame_Update", "UpdateStatus")
    self:RegisterEvent("LOOT_OPENED")
    self:RegisterEvent("LOOT_SLOT_CLEARED")
    SatchelQueue_Button:Show()
end

function SatchelQueue:OnDisable()
    self:UnregisterEvent("LOOT_OPENED")
    self:UnregisterEvent("LOOT_SLOT_CLEARED")
    self:TimerStop()
    SatchelQueue_Button:Hide()
    self:UnhookAll()
end

function SatchelQueue:Toggle()
    if timer then
        self:TimerStop()
    else
        self:TimerStart()
    end
    self:UpdateStatus(QueueStatusFrame)
end

function SatchelQueue:TimerStart()
    if not timer then
        self:RegisterEvent("LFG_UPDATE_RANDOM_INFO")
        timer = self:ScheduleRepeatingTimer(RequestLFDPlayerLockInfo, LFD_STATISTIC_CHANGE_TIME)
        SatchelQueue_Button:SetText(BUTTON_QUEUED)
        RequestLFDPlayerLockInfo()
    end
end

local function ResetSound()
    if sound_all then
        SetCVar("Sound_EnableAllSound", sound_all)
        SetCVar("Sound_EnableSoundWhenGameIsInBG", sound_bg)

        sound_all = nil
        sound_bg = nil
    end
end

function SatchelQueue:TimerStop()
    if timer then
        self:UnregisterEvent("LFG_UPDATE_RANDOM_INFO")
        self:CancelTimer(timer, true)
        timer = nil;
        SatchelQueue_Button:SetText(BUTTON_TEXT)
        ResetSound()
    end
end

-- code copied from Blizzard's QueueStatusFrame_Update, may be it can be hooked somehow instead?
function SatchelQueue:UpdateStatus(self)
    local showMinimapButton, animateEye;

    local nextEntry = 1;

    local totalHeight = 4; --Add some buffer height

    --Try each LFG type
    for i=1, NUM_LE_LFG_CATEGORYS do
        local mode, submode = GetLFGMode(i);
        if ( mode ) then
            local entry = QueueStatusFrame_GetEntry(self, nextEntry);
            QueueStatusEntry_SetUpLFG(entry, i);
            entry:Show();
            totalHeight = totalHeight + entry:GetHeight();
            nextEntry = nextEntry + 1;

            showMinimapButton = true;
            if ( mode == "queued" ) then
                animateEye = true;
            end
        end
    end

    --Try all PvP queues
    for i=1, GetMaxBattlefieldID() do
        local status, mapName, instanceID, levelRangeMin, levelRangeMax, teamSize, registeredMatch, eligibleInQueue, waitingOnOtherActivity = GetBattlefieldStatus(i);
        if ( status and status ~= "none" ) then
            local entry = QueueStatusFrame_GetEntry(self, nextEntry);
            QueueStatusEntry_SetUpBattlefield(entry, i);
            entry:Show();
            totalHeight = totalHeight + entry:GetHeight();
            nextEntry = nextEntry + 1;

            showMinimapButton = true;
            if ( status == "queued" ) then
                animateEye = true;
            end
        end
    end

    --Try all World PvP queues
    for i=1, MAX_WORLD_PVP_QUEUES do
        local status, mapName, queueID = GetWorldPVPQueueStatus(i);
        if ( status and status ~= "none" ) then
            local entry = QueueStatusFrame_GetEntry(self, nextEntry);
            QueueStatusEntry_SetUpWorldPvP(entry, i);
            entry:Show();
            totalHeight = totalHeight + entry:GetHeight();
            nextEntry = nextEntry + 1;

            showMinimapButton = true;
            if ( status == "queued" ) then
                animateEye = true;
            end
        end
    end

    --World PvP areas we're currently in
    if ( CanHearthAndResurrectFromArea() ) then
        local entry = QueueStatusFrame_GetEntry(self, nextEntry);
        QueueStatusEntry_SetUpActiveWorldPVP(entry);
        entry:Show();
        totalHeight = totalHeight + entry:GetHeight();
        nextEntry = nextEntry + 1;

        showMinimapButton = true;
    end

    --Pet Battle PvP Queue
    local pbStatus = C_PetBattles.GetPVPMatchmakingInfo();
    if ( pbStatus ) then
        local entry = QueueStatusFrame_GetEntry(self, nextEntry);
        QueueStatusEntry_SetUpPetBattlePvP(entry);
        entry:Show();
        totalHeight = totalHeight + entry:GetHeight();
        nextEntry = nextEntry + 1;

        showMinimapButton = true;
        if ( pbStatus == "queued" ) then
            animateEye = true;
        end
    end

    --Satchel Queue
    if SatchelQueue.db.global.icon then
        local mode = GetLFGMode(LE_LFG_CATEGORY_LFD)
        if not mode and timer then
            local entry = QueueStatusFrame_GetEntry(self, nextEntry);
            QueueStatusEntry_SetMinimalDisplay(entry, "SatchelQueue", QUEUED_STATUS_QUEUED);
            entry:Show();
            totalHeight = totalHeight + entry:GetHeight();
            nextEntry = nextEntry + 1;

            showMinimapButton = true;
            animateEye = true;
        end
    end

    --Hide all remaining entries.
    for i=nextEntry, #self.StatusEntries do
        self.StatusEntries[i]:Hide();
    end

    --Update the size of this frame to fit everything
    self:SetHeight(totalHeight);

    --Update the minimap icon
    if ( showMinimapButton ) then
        QueueStatusMinimapButton:Show();
    else
        QueueStatusMinimapButton:Hide();
    end

    if ( animateEye ) then
        EyeTemplate_StartAnimating(QueueStatusMinimapButton.Eye);
    else
        EyeTemplate_StopAnimating(QueueStatusMinimapButton.Eye);
    end
end

local function CheckQueueReward(dungeonID)
    local leaderChecked, tankChecked, healerChecked, damageChecked = LFDQueueFrame_GetRoles()
    local eligible, forTank, forHealer, forDamage, itemCount = GetLFGRoleShortageRewards(dungeonID, LFG_ROLE_SHORTAGE_RARE)
    return eligible and itemCount > 0 and ((tankChecked and forTank) or (healerChecked and forHealer) or (damageChecked and forDamage))
end

local function CheckQueuePopReward(dungeonID, role)
    local eligible, forTank, forHealer, forDamage, itemCount = GetLFGRoleShortageRewards(dungeonID, LFG_ROLE_SHORTAGE_RARE)
    return eligible and itemCount > 0 and ((role == "TANK" and forTank) or (role == "HEALER" and forHealer) or (role == "DAMAGER" and forDamage))
end

function SatchelQueue:LFG_UPDATE_RANDOM_INFO()
    if not timer then
        return
    end

    ResetSound()

    local mode, submode = GetLFGMode(LE_LFG_CATEGORY_LFD)

    if not mode then
        local first, last, step = 1, GetNumRandomDungeons(), 1
        if self.db.global.prioritise then
            first, last, step = last, first, -1

            if self.db.global.exclusive then
                last = first
            end
        end

        for i = first, last, step do
            local id = GetLFGRandomDungeonInfo(i)
            if IsLFGDungeonJoinable(id) and CheckQueueReward(id) then
                --SetLFGDungeon(LE_LFG_CATEGORY_LFD, id)
                --JoinLFG(LE_LFG_CATEGORY_LFD)
				StaticPopup_Show("SATCHEL_QUEUE", nil, nil, id)
                return
            end
        end
		StaticPopup_Hide("SATCHEL_QUEUE")
    elseif mode == "proposal" then
        if submode == "unaccepted" then
            local _, dungeonID, _, _, _, _, role = GetLFGProposal()
            if CheckQueuePopReward(dungeonID, role) then
                if self.db.global.sound_enable then
                    if self.db.global.sound_force then
                        if not sound_all then
                            sound_all = GetCVar("Sound_EnableAllSound")
                            sound_bg = GetCVar("Sound_EnableSoundWhenGameIsInBG")
                        end

                        SetCVar("Sound_EnableAllSound", "1")
                        SetCVar("Sound_EnableSoundWhenGameIsInBG", "1")
                    end
                    PlaySoundFile(self.db.global.sound_file, "MASTER")
                end
                if self.db.global.flash_enable then
                    UIFrameFlash(SatchelQueue_Flash, 0.5, 0.5, 10.0, false, 0.0, 0.0)
                end
            elseif GetNumSubgroupMembers() == 0 then
                RejectProposal()
            end
        end
    elseif mode == "lfgparty" then
        if not self.db.global.requeue then
            self:TimerStop()
        end
    end
end

--hack: if item is locked, it is being used. only check satchels
local function GuessOpenSatchel()
    for bag = 0, NUM_BAG_SLOTS do
        for slot = 1, GetContainerNumSlots(bag) do
            local texture, count, locked, quality, readable, lootable, link = GetContainerItemInfo(bag, slot)
            if locked and lootable then
                local uid = SatchelID(GetContainerItemLink(bag, slot))
                if uid then
                    return uid
                end
            end
        end
    end
    return nil
end

--gold parsing liberated from Wowhead Looter (http://www.wowhead.com/client)
local WL_CURRENCY = {
	["1"] = COPPER_AMOUNT:gsub("%%d ", ""),
	["100"] = SILVER_AMOUNT:gsub("%%d ", ""),
	["10000"] = GOLD_AMOUNT:gsub("%%d ", ""),
};

local function wlParseCoin(strCoin)
	local coin = 0;
	for k, v in pairs(WL_CURRENCY) do
		local found, _, a = strCoin:find("(%d+) "..v);
		if found then
			coin = coin + a * tonumber(k);
		end
	end

	return coin;
end

local function IsBindOnPickup(link)
    SatchelQueue_Tooltip:SetOwner(UIParent, "ANCHOR_NONE")
    SatchelQueue_Tooltip:SetHyperlink(link)

    for _,region in ipairs({SatchelQueue_Tooltip:GetRegions()}) do
        if region and region:GetObjectType() == "FontString" then
            if region:GetText() == ITEM_BIND_ON_PICKUP then
                return 1
            end
        end
    end
    return nil
end

function SatchelQueue:LOOT_OPENED()
    local uid = GuessOpenSatchel()
    if uid then
        if not self.db.global.saved_uids[uid] then
            self.db.global.count = self.db.global.count + 1

            for slot = 1, GetNumLootItems() do
				local slotType = GetLootSlotType(slot)
                local texture, item, quantity, quality, locked = GetLootSlotInfo(slot)

                if slotType == LOOT_SLOT_MONEY then
                    local coin = wlParseCoin(item)
                    self.db.global.coin_total = self.db.global.coin_total + coin

                    if self.db.global.coin_min == -1 then
                        self.db.global.coin_min = coin
                        self.db.global.coin_max = coin
                    else
                        self.db.global.coin_min = min(self.db.global.coin_min, coin)
                        self.db.global.coin_max = max(self.db.global.coin_max, coin)
                    end
                elseif slotType == LOOT_SLOT_ITEM then
                    local _, _, id = string.find(GetLootSlotLink(slot), "|Hitem:(%d+):")
                    self.db.global.items[id] = (self.db.global.items[id] or 0) + quantity
                end
            end
        end

        local closing = nil
        if self.db.global.prevent_auto_loot and self.db.global.fake_auto_loot and should_auto_loot then
            should_auto_loot = false
            closing = true

            for slot = 1, GetNumLootItems() do
                if GetLootSlotType(slot) == LOOT_SLOT_MONEY or not IsBindOnPickup(GetLootSlotLink(slot)) then
                    LootSlot(slot)
                end
            end
            if GetNumLootItems() == 0 then
                self.db.global.saved_uids[uid] = nil
            end
        end

        --rebuild stored items
        if GetNumLootItems() > 0 then
            self.db.global.saved_uids[uid] = {}
            for slot = 1, GetNumLootItems() do
                local _, item = GetLootSlotInfo(slot)
                if item then --we havent just looted the slot
                    self.db.global.saved_uids[uid][slot] = GetLootSlotType(slot) == LOOT_SLOT_MONEY and -wlParseCoin(item) or tonumber(select(3, string.find(GetLootSlotLink(slot), "|Hitem:(%d+):")))
                end
            end
        end

        if closing then
            CloseLoot()
        end
    end
end

function SatchelQueue:LOOT_SLOT_CLEARED()
    local uid = GuessOpenSatchel()
    if uid then
        if GetNumLootItems() == 1 and not GetLootSlotInfo(1) then
            self.db.global.saved_uids[uid] = nil
        end
    end
end