view Modules/OpenAll.lua @ 116:cff956d6e9d0

Now compatible with my version of QuickAuctions.
author Zerotorescue
date Tue, 12 Oct 2010 11:38:36 +0200
parents 981c6ac45a3f
children f662ee4d9c05
line wrap: on
line source
local MailOpener = LibStub("AceAddon-3.0"):GetAddon("MailOpener");
local mod = MailOpener:NewModule("OpenAll", "AceEvent-3.0", "AceTimer-3.0");
local L = LibStub("AceLocale-3.0"):GetLocale("MailOpener");

mod.moduleDescription = L["The actual mail opening initiated by the core."];
mod.moduleRequired = true;

--[[
Dev notes:
When shift clicking the Open All button it should override all filters.
]]

local MAIL_ITEM_INDEX, MAIL_OPEN_EVERYTHING, mailTimer, inventoryFull, inventoryFullSoundPlayed, inventoryFullSoundPlayedThisVisit, opening, lastSync, numCurrentMail, numHiddenMail, continue, firstOpenThisSync;

function mod:OnInitialize()
	local defaults = {
		profile = {
			speed = 0.05,
			keepFreeSpace = 0,
			filter = {
				AH = {
					canceled = true,
					expired = true,
					outbid = true,
					success = true,
					won = true,
				},
				normalAttachments = false,
				normalMoney = true,
				allowShiftClick = true,
			},
		},
	};
	
	-- Register our saved variables NameSpace
	self.db = MailOpener.db:RegisterNamespace("OpenAll", defaults);
end

function mod:OnEnable()
	self:RegisterEvent("MAIL_SHOW");
	
	if not self.btnOpenAll then
		-- Open all button
		local button = CreateFrame("Button", "btnMailOpenerOpenAll", InboxFrame, "UIPanelButtonTemplate");
		button:SetText(L["Open all"]);
		button:SetHeight(26);
		button:SetWidth(120);
		button:SetPoint("BOTTOM", InboxFrame, "CENTER", -10, -165);
		button:RegisterForClicks("LeftButtonUp", "RightButtonUp", "MiddleButtonUp");
		button:SetScript("OnClick", function(self, mouseButton)
			local action = "open";
			if mouseButton == "RightButton" then
				action = "menu";
			elseif mouseButton == "MiddleButton" or (mouseButton == "LeftButton" and IsAltKeyDown()) then
				action = "stop";
			end
			
			if action == "menu" then
				-- Hide the gametooltip
				GameTooltip:Hide();
				
				if not mod.ddmFilters then
					-- Build the drop down menu
					local info = {};
					
					local dropDownMenu = CreateFrame("Frame", "MailOpenerFiltersDropDownMenu");
					dropDownMenu.displayMode = "MENU";
					dropDownMenu.initialize = function(s, level)
					    if not level then return; end
					    
					    if level == 1 then
							-- Create the title of the menu
							info.isTitle = true;
							info.text = L["Toggle filters for %s profile."]:format(MailOpener.db:GetCurrentProfile());
							info.notCheckable = true;
							UIDropDownMenu_AddButton(info, level);
							
							-- Reset title specific values
							info.isTitle = nil;
							info.disabled = nil;
							info.notCheckable = nil;
							
							-- We don't want to close the DDM when something is toggled
							info.keepShownOnClick = true;
							
							-- Make Auction canceled option
							info.text = L["Auction canceled"];
							info.func = function(this) mod.db.profile.filter.AH.canceled = this.checked; end;
							info.checked = mod.db.profile.filter.AH.canceled;
							UIDropDownMenu_AddButton(info, level);
							
							-- Make Auction expired option
							info.text = L["Auction expired"];
							info.func = function(this) mod.db.profile.filter.AH.expired = this.checked; end;
							info.checked = mod.db.profile.filter.AH.expired;
							UIDropDownMenu_AddButton(info, level);
							
							-- Make Auction outbid option
							info.text = L["Auction outbid"];
							info.func = function(this) mod.db.profile.filter.AH.outbid = this.checked; end;
							info.checked = mod.db.profile.filter.AH.outbid;
							UIDropDownMenu_AddButton(info, level);
							
							-- Make Auction successful option
							info.text = L["Auction successful"];
							info.func = function(this) mod.db.profile.filter.AH.success = this.checked; end;
							info.checked = mod.db.profile.filter.AH.success;
							UIDropDownMenu_AddButton(info, level);
							
							-- Make Auction won option
							info.text = L["Auction won"];
							info.func = function(this) mod.db.profile.filter.AH.won = this.checked; end;
							info.checked = mod.db.profile.filter.AH.won;
							UIDropDownMenu_AddButton(info, level);
							
							-- Make Other mail with attachments
							info.text = L["Other mail with attachments"];
							info.func = function(this) mod.db.profile.filter.normalAttachments = this.checked; end;
							info.checked = mod.db.profile.filter.normalAttachments;
							UIDropDownMenu_AddButton(info, level);
							
							-- Make Other mail with gold
							info.text = L["Other mail with gold"];
							info.func = function(this) mod.db.profile.filter.normalMoney = this.checked; end;
							info.checked = mod.db.profile.filter.normalMoney;
							UIDropDownMenu_AddButton(info, level);
							
							-- Close link
							info.text = CLOSE;
							info.func = function() CloseDropDownMenus(); end;
							info.checked = nil;
							info.notCheckable = true;
							UIDropDownMenu_AddButton(info, level);
							
							wipe(info);
					    end
					end
					
					mod.ddmFilters = dropDownMenu;
				end
				
				ToggleDropDownMenu(1, nil, mod.ddmFilters, self:GetName(), 0, 0);
			elseif action == "stop" then
				MailOpener:Print(L["Interrupting mail opening as the alt key was held down while clicking the open all button or the middle mouse-button was used on it."]);
				
				mod:StopOpening(true);
			else
				mod:Open(true, IsShiftKeyDown());
			end
		end);
		button.tooltipTitle = L["Open all"];
		button.tooltip = L["Hold |cfffed000shift|r while clicking this button to temporarily override your filters and loot every single mail containing attachments and/or gold.\n\n|cfffed000Right|r click this button to quickly adjust mail opening filters for this profile.\n\n|cfffed000Middle|r click or hold |cfffed000alt|r while clicking this button to interrupt mail opening."];
		button:SetScript("OnEnter", function(self)
			if MailOpener.db.profile.general.showHelpTooltips then
				GameTooltip:SetOwner(self, "ANCHOR_NONE")
				GameTooltip:SetPoint("BOTTOM", self, "TOP")
				GameTooltip:SetText(self.tooltipTitle, 1, .82, 0, 1)
				
				if type(self.tooltip) == "string" then
					GameTooltip:AddLine(self.tooltip, 1, 1, 1, 1);
				end
				
				GameTooltip:Show();
			end
		end);
		button:SetScript("OnLeave", function(self)
			GameTooltip:Hide();
		end);
		
		self.btnOpenAll = button;
	end
	
	self.btnOpenAll:Show();
	
	if not self.timeLeftFrame then
		-- If the timeLeftFrame doesn't exist we will have to build it
		
		self:Debug("Building text frame");
		
		local frame = CreateFrame("Button", "MailOpenerTimeLeftButton", InboxFrame);
		
		-- Mail counter
		frame.text = frame:CreateFontString("MailOpenerTimeLeftFrameMailCount", "OVERLAY", "GameFontHighlight");
		frame.text:SetPoint("CENTER", MailFrame, "TOPLEFT", 40, -35);
		
		-- Long time left indicator
		frame.smallText = frame:CreateFontString("MailOpenerTimeLeftFrameTimeRemaining", "OVERLAY", "GameFontNormal");
		frame.smallText:SetFont(GameFontHighlight:GetFont(), 11, "OUTLINE");
		frame.smallText:SetPoint("TOPLEFT", MailFrame, "TOPLEFT", 75, -38);
		frame.smallText:SetWidth(270);
		frame.smallText:SetJustifyH("LEFT");
		frame.smallText:SetJustifyV("MIDDLE");
		
		frame:SetPoint("TOPLEFT", MailFrame, "TOPLEFT", 75, -38);
		frame:SetWidth(270);
		frame:SetHeight(35);
		frame:SetScript("OnClick", function(self)
			local timeRemainingUntillOpened = frame.smallText:GetText();
			if timeRemainingUntillOpened then
				MailOpener.currentPopupContents = "|cffffd700" .. timeRemainingUntillOpened .. "|r";
				
				StaticPopup_Show("MailOpenerCopyWindow");
			end
		end);
		
		self.timeLeftFrame = frame;
	end
	
	self.timeLeftFrame:Show();
	
	-- Go through all children of the mail frame to find QA's element and hide it
	-- There's no other way to do this because QuickAuctions has a local referrence to it (not as a property of the object like most other frames)
	local kids = { MailFrame:GetChildren() };
	
	for _, child in ipairs(kids) do
		if child and child.text then
			child.text:Hide();
		end
	end
	
	-- If we were toggling this module on while the mailbox is opened we must register all events again
	if MailFrame:IsVisible() then
		self:MAIL_SHOW();
	end
end

function mod:OnDisable()
	self:UnregisterEvent("MAIL_SHOW");
	
	if self.btnOpenAll then
		self.btnOpenAll:Hide();
	end
	
	if self.timeLeftFrame then
		self.timeLeftFrame:Hide();
	end

	if MailOpener.PostalEnabled then
		-- Enable Postal's openers again
		
		MailOpener:TogglePostalModule("OpenAll", true);
		MailOpener:TogglePostalModule("Select", true);
	end
	
	-- Go through all children of the mail frame to find QA's elements and SHOW these
	-- There's no other way to do this because QuickAuctions has a local referrence to it (not as a property of the object like most other frames)
	local kids = { MailFrame:GetChildren() };
	
	for _, child in ipairs(kids) do
		if child and child.text then
			child.text:Show();
		end
	end
	
	self:Stop();
end

function mod:MAIL_SHOW()
	self:Debug("MAIL_SHOW");
	
	self:StopOpening(false);

	inventoryFullSoundPlayedThisVisit = nil;
	
	if MailOpener.PostalEnabled then
		-- Disable Postal's openers so we can do it ourselves
		
		MailOpener:TogglePostalModule("OpenAll", false);
		MailOpener:TogglePostalModule("Select", false);
	end
	
	-- Keep an eye for closing of the mailbox
	self:RegisterEvent("MAIL_CLOSED", "Stop");
	self:RegisterEvent("PLAYER_LEAVING_WORLD", "Stop");
	
	-- Look if the mailbox is full
    self:RegisterEvent("UI_ERROR_MESSAGE");
    -- Only look again after bags updated
	self:RegisterEvent("BAG_UPDATE");
    
    -- We need to know when to start opening
	self:RegisterMessage("MO_OPEN_MAIL", "Open");
	self:RegisterMessage("MO_SERVER_SYNCED");
	self:RegisterMessage("MO_MAIL_EMPTIED");
	self:RegisterMessage("MO_STOP_MAIL_OPENING");
	
	self:CancelTimer(self.tmrTimeRemaining, true);
	self.tmrTimeRemaining = self:ScheduleRepeatingTimer("UpdateTimer", 1);
	self:UpdateTimer();
end

function mod:Stop()
	self:Debug("Stop");
	
	-- We shutdown, so nothing to do when the mailbox is closed anymore
    self:UnregisterEvent("MAIL_CLOSED");
    self:UnregisterEvent("PLAYER_LEAVING_WORLD");
    
    -- We care about a full inventory just a little
    self:UnregisterEvent("UI_ERROR_MESSAGE");
    self:UnregisterEvent("BAG_UPDATE");
	
	-- We no longer care
	self:UnregisterMessage("MO_OPEN_MAIL");
	self:UnregisterMessage("MO_SERVER_SYNCED");
	self:UnregisterMessage("MO_MAIL_EMPTIED");
	self:UnregisterMessage("MO_STOP_MAIL_OPENING");
    
	self:CancelTimer(self.tmrMailOpener, true);
	self:CancelTimer(self.tmrTimeRemaining, true);
	
	self:SetOpeningStatus(false);
end

function mod:MO_SERVER_SYNCED()
	self:Debug("MO_SERVER_SYNCED");
	
	-- Stop opening now to prevent the opener from continueing while BeanCounter is counting
	self:StopOpening(false);
	
	lastSync = GetTime();
	
	self:UpdateMailCount();
	
	-- While this is true, we'll announce mail skipped - will be set to false after opening happened once
	firstOpenThisSync = true;
end

function mod:MO_MAIL_EMPTIED()
	-- A mail has been processed so we can process the next
	continue = true;
	
	self:UpdateTimer();
end

function mod:MO_STOP_MAIL_OPENING()
	self:StopOpening(true);
end

function mod:UpdateMailCount()
	local numItems, totalItems = GetInboxNumItems();
	
	numCurrentMail = numItems;
	numHiddenMail = ( totalItems - numItems );
end

function mod:BAG_UPDATE()
	-- If the bags are updated we should check if the inventory is full again
	inventoryFull = false;
	-- Replay sound
	inventoryFullSoundPlayed = nil;
end

-- We registered this event to look for the inventory full error message because this is faster than counting the amount of items in the inventory all the time
function mod:UI_ERROR_MESSAGE(e, errorMessage)
	if errorMessage == ERR_INV_FULL then
		-- Inventory is full.
		
		if not inventoryFull then
			inventoryFull = true;
			
			-- Play the sound
			if MailOpener.db.profile.notifications.bagsFullSound and (not MailOpener.db.profile.notifications.bagsFullSoundOnlyOnce or not inventoryFullSoundPlayed) and (not MailOpener.db.profile.notifications.bagsFullSoundOnlyOncePerMailboxVisit or not inventoryFullSoundPlayedThisVisit) then
				PlaySoundFile(MailOpener.db.profile.notifications.bagsFullSoundFile);
				inventoryFullSoundPlayed = true;
				inventoryFullSoundPlayedThisVisit = true;
			end
		end
		
		-- Continue opening mail (we still want to open gold mail)
		continue = true;
	elseif errorMessage == ERR_ITEM_MAX_COUNT or errorMessage == ERR_MAIL_DATABASE_ERROR then
		-- Can't carry more of this item OR mail database error
		
		-- Continue opening mail (we still want to retrieve other items or gold)
		continue = true;
	end
end

function mod:Open(forced, everything)
	self:Debug("Open (" .. ((everything and "1") or "0") .. ")");
	
	if not opening or forced == true then
		local numItems, totalItems = GetInboxNumItems();
		-- Start at the end, add one because OpenNext will take it away again
		local newMailItemIndex = ( ( numItems or 0 ) + 1 );
		
		if newMailItemIndex > 1 then
			self:Debug("Open succes");
			
			-- Stop the previous opening and restart
			if forced == true then
				-- Show skips again
				firstOpenThisSync = true;
				
				self:StopOpening(false); -- this is not a "simple" stop, so also reset inventory full warning
			else
				self:StopOpening(true); -- forced is false - automated action - simple reset, skip inventory full reset to avoid sound spam
			end
			
			-- Update the caret
			MAIL_ITEM_INDEX = newMailItemIndex;
			-- Do we want to override filters and open every single mail?
			if everything and self.db.profile.filter.allowShiftClick then
				MAIL_OPEN_EVERYTHING = true;
				
				MailOpener:Print(L["Shift key was held while pressing the open all button. Temporarily overriding filters; going to open every mail with attachments."]);
			else
				MAIL_OPEN_EVERYTHING = nil;
			end
			
			-- We're now going to be busy again
			self:SetOpeningStatus(true);
			
			-- Open the next mail in line
			self:OpenNext();
		else
			if MailOpener.db.profile.notifications.mailboxIsEmpty then
				print(L["|cffff0000There is currently no mail available.|r"]);
			end
			
			self:Debug("MO_OPEN_COMPLETE");
			
			-- Report that we're all done
			self:SendMessage("MO_OPEN_COMPLETE");
		end
	end
end

-- Return the type of mail a message subject is
local knownAHSubjectPatterns = {
	canceled = AUCTION_REMOVED_MAIL_SUBJECT:replace("%s", ""),
	expired = AUCTION_EXPIRED_MAIL_SUBJECT:replace("%s", ""),
	outbid = AUCTION_OUTBID_MAIL_SUBJECT:replace("%s", ""),
	success = AUCTION_SOLD_MAIL_SUBJECT:replace("%s", ""),
	won = AUCTION_WON_MAIL_SUBJECT:replace("%s", ""),
};
function mod:GetAuctionMailType(subject)
	if subject then
		-- Check if any of our patterns match, sorted by most likely matches first (if one is true the rest shouldn't be evaluated)
		if subject:find(knownAHSubjectPatterns.expired) then
			return "expired";
		elseif subject:find(knownAHSubjectPatterns.success) then
			return "success";
		elseif subject:find(knownAHSubjectPatterns.won) then
			return "won";
		elseif subject:find(knownAHSubjectPatterns.canceled) then
			return "canceled";
		elseif subject:find(knownAHSubjectPatterns.outbid) then
			return "outbid";
		end
	end
	
	return; -- not auction mail
end

function mod:OpenMail(index)
	if index and index > 0 then
		-- LUA arrays start at 1, so mail with index 0 doesn't exist, so we're finished
		
		local sender, subject, gold, cod, _, items, _, _, _, _, isGM = select(3, GetInboxHeaderInfo(index));
		local auctionMailType = self:GetAuctionMailType(subject);
		
		if not subject then subject = ""; end
		
		local onlyShowOnceCheck = firstOpenThisSync;
		
		local skippingString = L["Skipping %d: %s (%s)"];
		
		if isGM then
			-- Blizzard Mail
			if MailOpener.db.profile.notifications.skipped.all and onlyShowOnceCheck and MailOpener.db.profile.notifications.skipped.GMMail then
				print(skippingString:format(index, subject, L["Blizzard mail"]));
			end
			
			self:OpenNext();
			
			return;
		elseif cod and cod > 0 then
			-- Cost on delivery
			if MailOpener.db.profile.notifications.skipped.all and onlyShowOnceCheck and MailOpener.db.profile.notifications.skipped.COD then
				print(skippingString:format(index, subject, L["C.O.D."]));
			end
			
			self:OpenNext();
			
			return;
		elseif ((gold and gold > 0) or (items and items > 0)) then
			-- Mail with some sort of attachments
			
			local slotsAvailable;
			if self.db.profile.keepFreeSpace > 0 then
				slotsAvailable = 0;
				
				-- First find out the amount of empty bag slots
				for bag = 0, NUM_BAG_SLOTS do
					local numberOfFreeSlots, _ = GetContainerNumFreeSlots(bag);
					slotsAvailable = ( slotsAvailable + numberOfFreeSlots );
				end
				
				-- Then calculate how much is available after the space we need to leave empty
				slotsAvailable = ( slotsAvailable - self.db.profile.keepFreeSpace );
			end
			
			-- and not MAIL_OPEN_EVERYTHING
			-- Removed above part from below if statement, I forgot why I put it here and now it makes no sense
			if inventoryFull and not MailOpener.db.profile.general.continueOpeningStackableItems and items and items > 0 then
				if MailOpener.db.profile.notifications.skipped.all and onlyShowOnceCheck and MailOpener.db.profile.notifications.skipped.inventoryFull then
					print(skippingString:format(index, subject, L["inventory is full"]));
				end
				
				self:OpenNext();
				
				return;
			elseif self.db.profile.keepFreeSpace > 0 and items and slotsAvailable ~= nil and slotsAvailable <= 0 then
				if MailOpener.db.profile.notifications.skipped.all and onlyShowOnceCheck and MailOpener.db.profile.notifications.skipped.keepFreeSpaceLimit then
					print(skippingString:format(index, subject, L["keep free space limit"]));
				end
				
				self:OpenNext();
				
				return;
			else
				-- This string will hold the mailtype, MailOpener.db.profile.notifications.skipped/processed[mailType] will be checked if this should be announced
				local mailType = "other";
				
				if not auctionMailType then
					-- This is a normal mail
					
					if gold and gold > 0 then
						mailType = "normalGoldMail";
					elseif items and items > 0 then
						mailType = "normalItemsMail";
					end
				else
					-- This is an auction house mail
					
					mailType = "AH" .. auctionMailType;
				end
				
				if not MAIL_OPEN_EVERYTHING and not self.db.profile.filter.normalMoney and mailType == "normalGoldMail" then
					if MailOpener.db.profile.notifications.skipped.all and onlyShowOnceCheck and MailOpener.db.profile.notifications.skipped[mailType] then
						print(skippingString:format(index, subject, L["normal mail with gold"]));
					end
					
					self:OpenNext();
					
					return;
				elseif not MAIL_OPEN_EVERYTHING and not self.db.profile.filter.normalAttachments and mailType == "normalItemsMail" then
					if MailOpener.db.profile.notifications.skipped.all and onlyShowOnceCheck and MailOpener.db.profile.notifications.skipped[mailType] then
						print(skippingString:format(index, subject, L["normal mail with attachments"]));
					end
					
					self:OpenNext();
					
					return;
				elseif not MAIL_OPEN_EVERYTHING and not self.db.profile.filter.AH.expired and mailType == "AHexpired" then
					if MailOpener.db.profile.notifications.skipped.all and onlyShowOnceCheck and MailOpener.db.profile.notifications.skipped[mailType] then
						print(skippingString:format(index, subject, L["expired auction"]));
					end
					
					self:OpenNext();
					
					return;
				elseif not MAIL_OPEN_EVERYTHING and not self.db.profile.filter.AH.success and mailType == "AHsuccess" then
					if MailOpener.db.profile.notifications.skipped.all and onlyShowOnceCheck and MailOpener.db.profile.notifications.skipped[mailType] then
						print(skippingString:format(index, subject, L["successful auction"]));
					end
					
					self:OpenNext();
					
					return;
				elseif not MAIL_OPEN_EVERYTHING and not self.db.profile.filter.AH.won and mailType == "AHwon" then
					if MailOpener.db.profile.notifications.skipped.all and onlyShowOnceCheck and MailOpener.db.profile.notifications.skipped[mailType] then
						print(skippingString:format(index, subject, L["auction won"]));
					end
					
					self:OpenNext();
					
					return;
				elseif not MAIL_OPEN_EVERYTHING and not self.db.profile.filter.AH.canceled and mailType == "AHcanceled" then
					if MailOpener.db.profile.notifications.skipped.all and onlyShowOnceCheck and MailOpener.db.profile.notifications.skipped[mailType] then
						print(skippingString:format(index, subject, L["canceled auction"]));
					end
					
					self:OpenNext();
					
					return;
				elseif not MAIL_OPEN_EVERYTHING and not self.db.profile.filter.AH.outbid and mailType == "AHoutbid" then
					if MailOpener.db.profile.notifications.skipped.all and onlyShowOnceCheck and MailOpener.db.profile.notifications.skipped[mailType] then
						print(skippingString:format(index, subject, L["outbid on auction"]));
					end
					
					self:OpenNext();
					
					return;
				else
					continue = false;
			
					self:Debug("MO_OPENING_MAIL (#" .. index .. ")");
					
					-- Notifiy other modules of opening
					self:SendMessage("MO_OPENING_MAIL");
					
					if self.db.profile.keepFreeSpace > 0 and items and slotsAvailable and items > slotsAvailable then
						-- If this mail contains more items than the space available, we must only take a few attachments
						
						for attachIndex = 1, ATTACHMENTS_MAX_RECEIVE do
							if GetInboxItemLink(index, attachIndex) then
								-- If this attachment actually exists
								
								if slotsAvailable > 0 then
									-- If we still have slots available, then loot this one item
									
									self:Debug("Taking attachment " .. attachIndex);
									
									TakeInboxItem(index, attachIndex);
									
									-- Gained an item, lost an available slot
									slotsAvailable = ( slotsAvailable - 1 );
								else
									-- No more room available, announce and go to next item
									if MailOpener.db.profile.notifications.skipped.all and onlyShowOnceCheck and MailOpener.db.profile.notifications.skipped.keepFreeSpaceLimit then
										print(skippingString:format(index, subject, L["keep free space limit"]));
									end
									
									-- We're done with this mail, it isn't empty so that event won't be triggered, but we may still continue
									continue = true;
									
									self:OpenNext();
									
									return;
								end
							end
						end
					else
						-- Take everything from this mail
						AutoLootMailItem(index);
					end
					
					if MailOpener.db.profile.notifications.processed.all and MailOpener.db.profile.notifications.processed[mailType] then
						if gold and gold > 0 then
							print(L["Processing %d: %s (%s)"]:format(index, subject, MailOpener:FormatMoney(gold)));
						else
							print(L["Processing %d: %s"]:format(index, subject));
						end
					end
					
					-- And prepare for the next
					self.tmrMailOpener = self:ScheduleTimer("OpenNext", self.db.profile.speed);
				end
			end
		else
			-- Unknown, probably just text
			if MailOpener.db.profile.notifications.skipped.all and onlyShowOnceCheck and MailOpener.db.profile.notifications.skipped.other then
				print(L["Skipping %d: %s"]:format(index, subject));
			end
			
			self:OpenNext();
		end
	else
		-- Finished!
		if MailOpener.db.profile.notifications.finishedCurrentBatch and firstOpenThisSync then
			print(L["Finished opening the current batch."]);
		end
		
		-- We have opened mail once this batch, so quit notifying
		firstOpenThisSync = nil;
		
		self:SetOpeningStatus(false);
		
		self:Debug("MO_OPEN_COMPLETE");
		
		-- Report that we're all done
		self:SendMessage("MO_OPEN_COMPLETE");
	end
end

function mod:OpenNext()
	if continue then
		-- If the previous mail was opened successful, open the next
		
		-- Next mail in line
		MAIL_ITEM_INDEX = ( MAIL_ITEM_INDEX - 1 );
		
		-- Open it
		self:OpenMail(MAIL_ITEM_INDEX);
	else
		-- Try again at the next interval
		
		self.tmrMailOpener = self:ScheduleTimer("OpenNext", self.db.profile.speed);
	end
end

function mod:Continue()
	continue = true;
	self:OpenNext();
end

local mailRemainingPatterns = {
	minutesSeconds = L["|cffffffff%d|r/|cffffffff%d|r mail remaining, opening everything will take about |cffffffff%d|r minutes and |cffffffff%d|r seconds (next refresh in |cffffffff%d|r seconds)."],
	minutes = L["|cffffffff%d|r/|cffffffff%d|r mail remaining, opening everything will take about |cffffffff%d|r minutes (next refresh in |cffffffff%d|r seconds)."],
	seconds = L["|cffffffff%d|r/|cffffffff%d|r mail remaining, opening everything will take about |cffffffff%d|r seconds (next refresh in |cffffffff%d|r seconds)."],
	nextRefresh = L["|cffffffff%d|r/|cffffffff%d|r mail remaining, next refresh in |cffffffff%d|r seconds."],
	waitingBatch = L["|cffffffff%d|r/|cffffffff%d|r mail remaining - waiting for something from the current batch to be opened..."],
	waitingSync = L["|cffffffff%d|r/|cffffffff%d|r mail remaining - waiting for the next mailbox refresh..."],
	soon = L["|cffffffff%d|r/|cffffffff%d|r mail remaining - everything will be opened soon..."],
};

function mod:UpdateTimer()
	if lastSync then
		self:UpdateMailCount();
		
		-- Calculate the total amount of mail waiting
		local numTotalMail = ( numHiddenMail + numCurrentMail );
		
		-- Resize the font based on mail left so the counter always fits perfectly
		if numTotalMail < 100 then
			self.timeLeftFrame.text:SetFont(GameFontHighlight:GetFont(), 30, "THICKOUTLINE");
		elseif numTotalMail < 1000 then
			self.timeLeftFrame.text:SetFont(GameFontHighlight:GetFont(), 24, "THICKOUTLINE");
		else
			self.timeLeftFrame.text:SetFont(GameFontHighlight:GetFont(), 18, "THICKOUTLINE");
		end
		self.timeLeftFrame.text:SetText(numTotalMail);
		
		-- Calculate the next server sync based on the last server sync plus sync interval
		local nextSync = ( lastSync + 61 );
	
		-- Calculate the timer remaining untill the next sync
		local timeRemaining = floor( nextSync - GetTime() );
		
		if numHiddenMail > 0 or timeRemaining > 0 then
			-- If there is still mail being hidden or the timer is still know, display stuff
			
			-- If the next sync was already due, our nextSync calculation was wrong and we must wait a little longer (lag?)
			local syncTimeOut = false;
			-- If time remaining is below 0, next sync should be soon
			if timeRemaining < 0 then
				timeRemaining = 0;
				syncTimeOut = true;
			end
			
			-- Calculate the amount of server syncs required to open all mail
			local syncsRequired = ceil( numHiddenMail / 50 );
			-- Calculate the time required to execute all these syncs
			local timeRequired = ( ( syncsRequired - 1 ) * 61 ) + timeRemaining;
			
			local minutes = floor( timeRequired / 60 );
			local seconds = floor( timeRequired % 60 );
			
			local remainingText;
			if syncTimeOut then
				-- Previous server sync was expected earlier, notify user
				
				if numCurrentMail == 50 then
					-- Sync couldn't occur because we were still waiting for the current batch to be opened
					remainingText = format(mailRemainingPatterns.waitingBatch, numCurrentMail, numTotalMail);
				else
					remainingText = format(mailRemainingPatterns.waitingSync, numCurrentMail, numTotalMail);
				end
			elseif numHiddenMail == 0 then
				-- If no hidden mail is remaining, only show the timer for as long as we can be sure
				
				remainingText = format(mailRemainingPatterns.nextRefresh, numCurrentMail, numTotalMail, timeRemaining);
			elseif minutes ~= 0 then
				if seconds ~= 0 then
					remainingText = format(mailRemainingPatterns.minutesSeconds, numCurrentMail, numTotalMail, minutes, seconds, timeRemaining);
				else
					remainingText = format(mailRemainingPatterns.minutes, numCurrentMail, numTotalMail, minutes, timeRemaining);
				end
			elseif seconds ~= 0 then
				remainingText = format(mailRemainingPatterns.seconds, numCurrentMail, numTotalMail, seconds, timeRemaining);
			else
				remainingText = format(mailRemainingPatterns.soon, numCurrentMail, numTotalMail);
			end
			
			self.timeLeftFrame.smallText:SetText(remainingText);
		else
			self.timeLeftFrame.smallText:SetText("");
		end
	end
end

function mod:StopOpening(simple)
	-- Stop opener timer
	self:CancelTimer(self.tmrMailOpener, true);
	
	if not simple then
		-- A simple stop is an automated stop, an advanced stop is one manually or after a sever sync
		
		-- Recheck inventory full
		inventoryFull = false;
		-- Replay sound
		inventoryFullSoundPlayed = nil;
	end
	
	-- Reset opener position
	MAIL_ITEM_INDEX = 0;
	-- Reset open everything state
	MAIL_OPEN_EVERYTHING = nil;
	-- Stopped opening, so allow to continue
	continue = true;
	
	self:SetOpeningStatus(false);
end

function mod:SetOpeningStatus(openingStatus)
	opening = openingStatus;
	
	if openingStatus then
		self.btnOpenAll:SetText(L["Opening..."]);
	else
		self.btnOpenAll:SetText(L["Open all"]);
	end
end

function mod:GetOptionsGroup()
	local configGroup = {
		order = 300,
		type = "group",
		name = L["Open All"],
		desc = L["Change open all settings."],
		args = {
			filters = {
				order = 10,
				type = "group",
				inline = true,
				name = L["Filters"],
				args = {
					description = {
						order = 10,
						type = "description",
						name = L["Toggle which mail the opener should autoloot."],
					},
					AHHeader = {
						order = 15,
						type = "header",
						name = L["Auction House Mail"],
					},
					canceled = {
						order = 20,
						type = "toggle",
						name = L["Open all |cfffed000auction canceled|r mail"],
						desc = L["Automatically loot all auction canceled mail from the auction house."],
						set = function(i, v) self.db.profile.filter.AH.canceled = v; end,
						get = function() return self.db.profile.filter.AH.canceled; end,
						width = "double",
					},
					expired = {
						order = 21,
						type = "toggle",
						name = L["Open all |cfffed000auction expired|r mail"],
						desc = L["Automatically loot all auction expired mail from the auction house."],
						set = function(i, v) self.db.profile.filter.AH.expired = v; end,
						get = function() return self.db.profile.filter.AH.expired; end,
						width = "double",
					},
					outbid = {
						order = 22,
						type = "toggle",
						name = L["Open all |cfffed000outbid on|r mail"],
						desc = L["Automatically loot all auction outbid mail from the auction house."],
						set = function(i, v) self.db.profile.filter.AH.outbid = v; end,
						get = function() return self.db.profile.filter.AH.outbid; end,
						width = "double",
					},
					success = {
						order = 23,
						type = "toggle",
						name = L["Open all |cfffed000auction successful|r mail"],
						desc = L["Automatically loot all auction successful mail from the auction house."],
						set = function(i, v) self.db.profile.filter.AH.success = v; end,
						get = function() return self.db.profile.filter.AH.success; end,
						width = "double",
					},
					won = {
						order = 24,
						type = "toggle",
						name = L["Open all |cfffed000auction won|r mail"],
						desc = L["Automatically loot all auction won mail from the auction house."],
						set = function(i, v) self.db.profile.filter.AH.won = v; end,
						get = function() return self.db.profile.filter.AH.won; end,
						width = "double",
					},
					normalHeader = {
						order = 30,
						type = "header",
						name = L["Remaining Mail"],
					},
					normalAttachments = {
						order = 40,
						type = "toggle",
						name = L["Normal mail with |cfffed000attachments|r"],
						desc = L["Automatically loot all normal mail containing attachments (CoDs and Blizzard mail will be skipped)."],
						set = function(i, v) self.db.profile.filter.normalAttachments = v; end,
						get = function() return self.db.profile.filter.normalAttachments; end,
						width = "double",
					},
					normalMoney = {
						order = 50,
						type = "toggle",
						name = L["Normal mail with |cfffed000gold|r"],
						desc = L["Automatically loot all normal mail containing gold (CoDs and Blizzard mail will be skipped)."],
						set = function(i, v) self.db.profile.filter.normalMoney = v; end,
						get = function() return self.db.profile.filter.normalMoney; end,
						width = "double",
					},
					allowShiftClick = {
						order = 60,
						type = "toggle",
						name = L["Allow shift-clicking of the |cfffed000Open All|r button to autoloot |cfffed000everything|r, regardless of the above filters."],
						desc = L["Allow shift-clicking of the |cfffed000Open All|r button to autoloot |cfffed000everything|r with attachments, temporarily overriding all filters."],
						set = function(i, v) self.db.profile.filter.allowShiftClick = v; end,
						get = function() return self.db.profile.filter.allowShiftClick; end,
						width = "full",
					},
				},
			},
			-- Continuous opening config inline group
			continuousOpening = {
				order = 20,
				type = "group",
				inline = true,
				name = L["Opening Interval"],
				args = {
					description = {
						order = 10,
						type = "description",
						name = function()
							local defaultString = L["The default behavior for opening mail is to only do so right after new mail was received from the server. If you close the mailbox or your inventory is full before everything is opened you will have to click the open all button manually or wait for a next mailbox refresh."] .. "\n\n";
							
							local currentSettings = "";
							if MailOpener.db.profile.general.continueOpening then
								currentSettings = currentSettings .. L["Mail Opener will |cff00ff00continue|r to attempt to open the remaining mail every |cfffed000%d seconds|r."]:format(MailOpener.db.profile.general.waitTime) .. " ";
							else
								currentSettings = currentSettings .. L["Mail Opener will |cffff0000not|r continue to attempt to open the remaining mail and will only start opening when new mail has just been received from the server."] .. " ";
							end
							currentSettings = currentSettings .. L["The first batch after each server refresh will be opened after waiting |cfffed000%d seconds|r."]:format(MailOpener.db.profile.general.initialDelay);
							return defaultString .. currentSettings;
						end,
					},
					header = {
						order = 15,
						type = "header",
						name = "",
					},
					continueOpening = {
						order = 20,
						type = "toggle",
						name = L["Continue opening mail"],
						desc = L["Continue opening mail at the interval set below, even if the mailbox wasn't refreshed recently."],
						width = "full",
						get =  function() return MailOpener.db.profile.general.continueOpening; end,
						set = function(i, v) MailOpener.db.profile.general.continueOpening = v; end,
					},
					waitTime = {
						order = 30,
						type = "range",
						width = "double",
						min = 0.5,
						max = 60,
						step = 0.5,
						name = L["Continued Mail Opening Interval"],
						desc = L["Change the interval at which Mail Opener tries to continue opening mail after opening the mailbox. Please note that this setting does not reduce the game's normal 60 seconds wait time for mail.\n\nThe default value is 5 seconds."],
						get = function() return MailOpener.db.profile.general.waitTime; end,
						set = function(i, v) MailOpener.db.profile.general.waitTime = v; end,
						disabled = function() return (not MailOpener.db.profile.general.continueOpening); end,
					},
					initialDelay = {
						order = 40,
						type = "range",
						width = "double",
						min = 0.5,
						max = 60,
						step = 0.5,
						name = L["Initial Mail Opening Delay"],
						desc = L["Change the delay before Mail Opener tries opening mail after opening the mailbox or new mail has been received from the server.\n\nThe default value is 0.5 seconds."],
						get = function() return MailOpener.db.profile.general.initialDelay; end,
						set = function(i, v) MailOpener.db.profile.general.initialDelay = v; end,
					},
				},
			}, -- end Continuous opening config inline group
			keepFree = {
				order = 30,
				type = "group",
				inline = true,
				name = L["Keep Free Space"],
				args = {
					description = {
						order = 10,
						type = "description",
						name = L["You can set an amount of bag space you wish to reserve when opening mail."],
					},
					header = {
						order = 15,
						type = "header",
						name = "",
					},
					keepFreeSpace = {
						order = 20,
						type = "range",
						min = 0,
						max = 100,
						step = 1,
						width = "double",
						name = L["Keep free space"],
						desc = L["Change the amount of space to reserve for other things when opening mail.\n\nEnabling this functionality by setting this value above 0 may increase resource usage slightly."],
						set = function(i, v) self.db.profile.keepFreeSpace = v; end,
						get = function() return self.db.profile.keepFreeSpace; end,
					},
				},
			},
			speed = {
				order = 40,
				type = "group",
				inline = true,
				name = "Opening Speed",
				args = {
					description = {
						order = 10,
						type = "description",
						name = L["Change the speed at which mail is opened. You should set the opening speed to the lowest latency you have in a city or experiment with setting it to the minimum."],
					},
					header = {
						order = 15,
						type = "header",
						name = "",
					},
					openMailInterval = {
						order = 20,
						type = "range",
						min = 5,
						max = 2500,
						step = 5,
						width = "double",
						name = L["Open single mail interval"],
						desc = L["Change the mail opening speed (in microseconds) for each mail. Lower may not always be faster."],
						get = function() return ( self.db.profile.speed * 1000 ); end,
						set = function(i, v) self.db.profile.speed = ( v / 1000 ); end,
					},
				},
			},
		},
	};
	
	return configGroup;
end

function mod:Debug(t)
	return MailOpener:Debug(("|cff00ff00OpenAll|r:%s"):format(t));
end