--[[
	BeanCounter Addon for World of Warcraft(tm).
	Version: 3.9.0.1257 (Kangaroo)
	Revision: $Id: MailMonitor.lua 1106 2006-12-04 01:05:50Z esamynn $

	MailMonitor - Handles all mailbox interactions

	License:
		This program 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 2
		of the License, or (at your option) any later version.

		This program 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 this program(see GPL.txt); if not, write to the Free Software
		Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

	Note:
		This AddOn's source code is specifically designed to work with
		World of Warcraft's interpreted AddOn system.
		You have an implicit licence to use this AddOn with these facilities
		since that is it's designated purpose as per:
		http://www.fsf.org/licensing/licenses/gpl-faq.html#InterpreterIncompat
--]]

-------------------------------------------------------------------------------
-- Function Imports
-------------------------------------------------------------------------------
local chatPrint = BeanCounter.ChatPrint;
local nilSafe = BeanCounter.NilSafe;

-------------------------------------------------------------------------------
-- Function Prototypes
-------------------------------------------------------------------------------
local addTask;
local processMailMessage;
local isSenderAuctionHouse;
local isSubjectAuctionExpired;
local isSubjectAuctionCancelled;
local isSubjectAuctionWon;
local isSubjectAuctionSuccessful;
local isSubjectOutbidOn;
local getItemNameFromSubject;
local reconcileDatabases;
local debugPrint;

local InboxTask_OnEvent;
local InboxTask_OnUpdate;
local InboxTask_Execute;
local createWaitForInboxUpdateTask;
local WaitForInboxUpdate_OnEvent;
local createWaitForTakeInboxItem;
local WaitForTakeInboxItem_OnEvent;
local createWaitForTakeInboxMoney;
local WaitForTakeInboxMoney_OnEvent;
local createWaitForDeleteInboxItem;
local WaitForDeleteInboxItem_OnEvent;
local createWaitForInvoiceTask;
local WaitForInvoiceTask_OnEvent;
local WaitForInvoiceTask_OnUpdate;
local createWaitForReadMessage;
local WaitForReadMessage_OnEvent;
local createProcessMessageTask;
local ProcessMessageTask_Execute;
local createTakeInboxMoneyTask;
local TakeInboxMoneyTask_Execute;
local createTakeInboxItemTask;
local TakeInboxItemTask_Execute;

-------------------------------------------------------------------------------
-- Constants
-------------------------------------------------------------------------------
local MAX_DAYS_LEFT = 30; -- Maximum number of days left for an unread message

-------------------------------------------------------------------------------
-- Local variables
-------------------------------------------------------------------------------
local InboxTasks = {};
local LastMailCount = 0;
local MailDownloaded = false;
local MailDownloadTime = nil;
local MailBacklog = false;

-------------------------------------------------------------------------------
-- Constants
-------------------------------------------------------------------------------
local MAX_INBOX_MESSAGES = 50;

-------------------------------------------------------------------------------
-------------------------------------------------------------------------------
function MailMonitor_OnLoad()
	-- Hook all the methods we need!
	Stubby.RegisterFunctionHook("TakeInboxItem", -100, MailMonitor_PreTakeInboxItemHook);
	Stubby.RegisterFunctionHook("TakeInboxMoney", -100, MailMonitor_PreTakeInboxMoneyHook);
	Stubby.RegisterFunctionHook("GetInboxText", -100, MailMonitor_PreGetInboxTextHook);
	Stubby.RegisterFunctionHook("GetInboxText", 100, MailMonitor_PostGetInboxTextHook);
	Stubby.RegisterFunctionHook("DeleteInboxItem", -100, MailMonitor_DeleteInboxItemHook);
	
	-- Register for the events we need!
	Stubby.RegisterEventHook("MAIL_INBOX_UPDATE", "BeanCounter_MailMonitor", MailMonitor_OnEventHook);
	Stubby.RegisterEventHook("UI_ERROR_MESSAGE", "BeanCounter_MailMonitor", MailMonitor_OnEventHook);
	Stubby.RegisterEventHook("MAIL_SHOW", "BeanCounter_MailMonitor", MailMonitor_OnEventHook);
	Stubby.RegisterEventHook("MAIL_CLOSED", "BeanCounter_MailMonitor", MailMonitor_OnEventHook);
end

-------------------------------------------------------------------------------
-------------------------------------------------------------------------------
function MailMonitor_OnUpdate()
	-- Check if we satisfied an event task.
	if (#InboxTasks > 0) then
		local task = InboxTasks[1];
		table.remove(InboxTasks, 1);
		if (task:OnUpdate()) then
			debugPrint("Timed out event task: "..task.name);
		else
			table.insert(InboxTasks, 1, task);
		end
	end

	-- Check if we've got function tasks to perform.
	local executed = true;
	while (#InboxTasks > 0 and executed) do
		local task = InboxTasks[1];
		table.remove(InboxTasks, 1);
		executed = task:Execute();
		if (executed) then
			debugPrint("Executed task: "..task.name);
		else
			table.insert(InboxTasks, 1, task);
		end
	end
end

-------------------------------------------------------------------------------
-------------------------------------------------------------------------------
function MailMonitor_OnEventHook(_, event, arg1)
	debugPrint(event);
	
	-- Check if we satisfied an event task.
	if (#InboxTasks > 0) then
		local task = InboxTasks[1];
		table.remove(InboxTasks, 1);
		if (task:OnEvent(event, arg1)) then
			debugPrint("Satisfied event task: "..task.name);
		else
			table.insert(InboxTasks, 1, task);
		end
	end

	-- Check if we've got function tasks to perform.
	local executed = true;
	while (#InboxTasks > 0 and executed) do
		local task = InboxTasks[1];
		table.remove(InboxTasks, 1);
		executed = task:Execute();
		if (executed) then
			debugPrint("Executed task: "..task.name);
		else
			table.insert(InboxTasks, 1, task);
		end
	end

	-- Reconcilation management.
	if (event == "MAIL_SHOW") then
		LastMailCount = GetInboxNumItems();
		MailDownloaded = false;
		MailDownloadTime = nil;
	elseif (event == "MAIL_INBOX_UPDATE") then
		local count = GetInboxNumItems();
		if (count > LastMailCount) then
			MailDownloaded = true;
			MailDownloadTime = time();
			MailBacklog = (count == MAX_INBOX_MESSAGES);
			if (MailBacklog) then
				debugPrint("Mail downloaded from server at "..date("%c", MailDownloadTime).." ("..count.." messages - backlog)");
			else
				debugPrint("Mail downloaded from server at "..date("%c", MailDownloadTime).." ("..count.." messages)");
			end			
		end
		LastMailCount = count;
	elseif (event == "MAIL_CLOSED") then
		-- Reconcile if we can.
		if (MailDownloaded and MailDownloadTime ~= nil and not MailBacklog) then
			reconcileDatabases(MailDownloadTime);
		end
		MailDownloaded = false;
		MailDownloadTime = nil;
		-- Toss the existing task queue.
		if (#InboxTasks > 0) then
			debugPrint("Clearing the task queue");
			InboxTasks = {};
		end
	end
end

-------------------------------------------------------------------------------
-- Hooks taking items from messages.
-------------------------------------------------------------------------------
function MailMonitor_PreTakeInboxItemHook(funcArgs, retVal, index)
	debugPrint("MailMonitor_PreTakeInboxItemHook("..nilSafe(index)..") called");

	if (index ~= nil and index > 0) then
		-- Allow this method call if there are no pending tasks or if the current
		-- pending task is to wait for the invoice and process the message.
		local isWaitingForInvoice = (
			#InboxTasks == 2 and 
			InboxTasks[1].index == index and
			InboxTasks[1].name == "WaitForInvoiceTask" and
			InboxTasks[2].index == index and
			InboxTasks[2].name == "ProcessMessageTask");
		if (#InboxTasks == 0 or isWaitingForInvoice) then
			-- Read the message, before allowing the TakeInboxItem() call.
			local packageIcon, stationeryIcon, sender, subject, money, CODAmount, daysLeft, hasItem, wasRead, wasReturned, textCreated, canReply = GetInboxHeaderInfo(index);
			if (not wasRead and isSenderAuctionHouse(sender) and not isWaitingForInvoice) then
				debugPrint("Calling GetInboxText() on the message");
				GetInboxText(index);
			else
				debugPrint("Already called GetInboxText() on the message");
			end

			-- If there are pending tasks, we must delay the execution of
			-- TakeInboxItem().
			if (#InboxTasks > 0) then
				-- Queue a task for taking the inbox item.
				addTask(createTakeInboxItemTask(index));

				-- Abort the execution of TakeInboxItem() for now...
				debugPrint("Delaying TakeInboxItem() call");
				return "abort";
			else
				-- Wait for the item to be taken.
				addTask(createWaitForTakeInboxItem(index));
			end
		else
			debugPrint("Ignoring TakeInboxItem() call");
			return "abort";
		end
	end
end

-------------------------------------------------------------------------------
-- Hooks taking money from messages.
-------------------------------------------------------------------------------
function MailMonitor_PreTakeInboxMoneyHook(funcArgs, retVal, index)
	debugPrint("MailMonitor_PreTakeInboxMoneyHook("..nilSafe(index)..") called");

	if (index ~= nil and index > 0) then
		-- Allow this method call if there are no pending tasks or if the current
		-- pending task is to wait for the invoice and process the message.
		local isWaitingForInvoice = (
			#InboxTasks == 2 and 
			InboxTasks[1].index == index and
			InboxTasks[1].name == "WaitForInvoiceTask" and
			InboxTasks[2].index == index and
			InboxTasks[2].name == "ProcessMessageTask");
		if (#InboxTasks == 0 or isWaitingForInvoice) then
			-- Read the message, before allowing the TakeInboxMoney() call.
			local packageIcon, stationeryIcon, sender, subject, money, CODAmount, daysLeft, hasItem, wasRead, wasReturned, textCreated, canReply = GetInboxHeaderInfo(index);
			if (not wasRead and isSenderAuctionHouse(sender) and not isWaitingForInvoice) then
				debugPrint("Calling GetInboxText() on the message");
				GetInboxText(index);
			else
				debugPrint("Already called GetInboxText() on the message");
			end

			-- If there are pending tasks, we must delay the execution of
			-- TakeInboxMoney().
			if (#InboxTasks > 0) then
				-- Queue a task for taking the inbox money.
				addTask(createTakeInboxMoneyTask(index));

				-- Abort the execution of TakeInboxMoney() for now...
				debugPrint("Delaying TakeInboxMoney() call");
				return "abort";
			else
				-- Wait for the money to be taken.
				addTask(createWaitForTakeInboxMoney(index));
			end
		else
			debugPrint("Ignoring TakeInboxMoney() call");
			return "abort";
		end
	end
end

-------------------------------------------------------------------------------
-- Hooks before reading a message.
-------------------------------------------------------------------------------
local getInboxTextRecursionLevel = 0;
local messageWasRead = true;
local messageDaysLeft = 0;
function MailMonitor_PreGetInboxTextHook(funcArgs, retVal, index)
	debugPrint("MailMonitor_PreGetInboxTextHook("..nilSafe(index)..") called");

	-- The GetInboxText is called re-entrantly in some cases. We only care
	-- about the first (outermost) call.
	getInboxTextRecursionLevel = getInboxTextRecursionLevel + 1;
	if (index > 0 and index <= GetInboxNumItems() and getInboxTextRecursionLevel == 1) then
		-- Check if we are reading the message for the first time. If so, this
		-- results in in an immediate client side MAIL_INBOX_UPDATE event to change
		-- the message to read.
		local packageIcon, stationeryIcon, sender, subject, money, CODAmount, daysLeft, hasItem, wasRead, wasReturned, textCreated, canReply = GetInboxHeaderInfo(index);
		messageWasRead = wasRead;
		messageDaysLeft = daysLeft;
		if (not messageWasRead) then
			-- Queue a task for marking the message as read. We add this to the
			-- front of the queue since its a client side operation.
			addTask(createWaitForReadMessage(index), true);
		end
	end
end

-------------------------------------------------------------------------------
-- Hooks after reading a message.
-------------------------------------------------------------------------------
function MailMonitor_PostGetInboxTextHook(funcArgs, retVal, index)
	debugPrint("MailMonitor_PostGetInboxTextHook("..nilSafe(index)..") called");

	-- We are only interested in unread messages.
	getInboxTextRecursionLevel = getInboxTextRecursionLevel - 1;
	if (index ~= nil and index > 0 and index <= GetInboxNumItems() and getInboxTextRecursionLevel == 0 and not messageWasRead) then
		local isInvoice = retVal[4];

		-- If this task has an invoice, get it.
		if (isInvoice) then
			-- Wait for the invoice if we don't have it yet.
			local invoiceType, itemName, playerName, bid, buyout, deposit, consignment = GetInboxInvoiceInfo(index);
			if (not playerName) then
				addTask(createWaitForInvoiceTask(index));
			end
		end

		-- Process the message.
		local messageAgeInSeconds = math.floor((MAX_DAYS_LEFT - messageDaysLeft) * 24 * 60 * 60);
		if (#InboxTasks > 0) then
			addTask(createProcessMessageTask(index, messageAgeInSeconds));
		else
			processMailMessage(index, messageAgeInSeconds);
		end

		-- Consider the message read now.
		messageWasRead = true;
	end
end

-------------------------------------------------------------------------------
-- Hooks deleting a message.
-------------------------------------------------------------------------------
function MailMonitor_DeleteInboxItemHook(funcArgs, retVal, index)
	debugPrint("MailMonitor_DeleteInboxItemHook("..nilSafe(index)..") called");
end

-------------------------------------------------------------------------------
-- Adds a task.
-------------------------------------------------------------------------------
function addTask(task, front)
	if (front) then
		debugPrint("Added task to front: "..task.name);
		table.insert(InboxTasks, 1, task);
	else
		debugPrint("Added task to back: "..task.name);
		table.insert(InboxTasks, task);
	end
end

-------------------------------------------------------------------------------
-- Hooks reading a message.
-------------------------------------------------------------------------------
function processMailMessage(index, messageAgeInSeconds)
	if (index > 0) then
		debugPrint("Processing message "..index);
		local packageIcon, stationeryIcon, sender, subject, money, CODAmount, daysLeft, hasItem, wasRead, wasReturned, textCreated, canReply = GetInboxHeaderInfo(index);
		if (sender and isSenderAuctionHouse(sender)) then
			local timestamp = (time() - messageAgeInSeconds);
			debugPrint("Message "..index.." was from an auction house (delivered at "..date("%c", timestamp)..")");
			if (isSubjectAuctionSuccessful(subject) and money) then
				local itemName = getItemNameFromSubject(subject);
				debugPrint("Message "..index.." is an auction successful message: "..nilSafe(itemName));
				if (itemName) then
					-- Get the invoice info, which may or may not be present
					-- depending on if it was fetched from the server.
					local invoiceType, _, playerName, bid, buyout, deposit, consignment = GetInboxInvoiceInfo(index);
					local isBuyout = nil;
					if (bid ~= nil and buyout ~= nil) then
						isBuyout = (bid == buyout);
					end
					debugPrint("Auction Successful: "..
						itemName..", "..
						nilSafe(nil)..", "..
						nilSafe(invoiceType)..", "..
						nilSafe(playerName)..", "..
						nilSafe(bid)..", "..
						nilSafe(buyout)..", "..
						nilSafe(deposit)..", "..
						nilSafe(consignment));
					BeanCounter.Sales.AddSuccessfulAuction(timestamp, itemName, money, bid, (bid == buyout), deposit, consignment, playerName);
				end
			elseif (isSubjectAuctionExpired(subject) and hasItem) then
				local itemName, _, itemCount = GetInboxItem(index);
				debugPrint("Message "..index.." is an auction expired message: "..nilSafe(itemName).. ", "..nilSafe(itemCount));
				if (itemName and itemCount) then
					debugPrint("Auction Expired: "..
						itemName..", "..
						nilSafe(itemCount));
					BeanCounter.Sales.AddExpiredAuction(timestamp, itemName, itemCount);
				end
			elseif (isSubjectAuctionCancelled(subject) and hasItem) then
				local itemName, _, itemCount = GetInboxItem(index);
				debugPrint("Message "..index.." is an auction cancelled message: "..nilSafe(itemName).. ", "..nilSafe(itemCount));
				if (itemName and itemCount) then
					debugPrint("Auction Cancelled: "..
						itemName..", "..
						nilSafe(itemCount));
					BeanCounter.Sales.AddExpiredAuction(timestamp, itemName, itemCount);
				end
			elseif (isSubjectAuctionWon(subject) and hasItem) then
				local itemName, _, itemCount = GetInboxItem(index);
				debugPrint("Message "..index.." is an auction won message: "..nilSafe(itemName).. ", "..itemCount);
				if (itemName and itemCount) then
					-- Get the invoice info, which may or may not be present
					-- depending on if it was fetched from the server.
					local invoiceType, _, playerName, bid, buyout, deposit, consignment = GetInboxInvoiceInfo(index);
					local isBuyout = nil;
					if (bid ~= nil and buyout ~= nil) then
						isBuyout = (bid == buyout);
					end
					debugPrint("Auction Won: "..
						itemName..", "..
						itemCount..", "..
						nilSafe(invoiceType)..", "..
						nilSafe(playerName)..", "..
						nilSafe(bid)..", "..
						nilSafe(buyout)..", "..
						nilSafe(deposit)..", "..
						nilSafe(consignment));
					BeanCounter.Purchases.AddSuccessfulBid(timestamp, itemName, itemCount, bid, playerName, isBuyout);
				end
			elseif (isSubjectOutbidOn(subject) and money) then
				local itemName = getItemNameFromSubject(subject);
				debugPrint("Message "..index.." is an auction lost message: "..nilSafe(itemName).. ", "..money);
				if (itemName and money) then
					debugPrint("Out bid on: "..
						itemName..", "..
						nilSafe(money));
					BeanCounter.Purchases.AddFailedBid(timestamp, itemName, money);
				end
			elseif (isSubjectAuctionCancelled(subject) and money) then
				local itemName = getItemNameFromSubject(subject);
				debugPrint("Message "..index.." is an auction canceled message: "..nilSafe(itemName).. ", "..money);
				if (itemName and money) then
					debugPrint("Auction Cancelled: "..
						itemName..", "..
						nilSafe(money));
					BeanCounter.Purchases.AddFailedBid(timestamp, itemName, money);
				end
			else
				debugPrint("Unknown subject: "..subject);
			end
		else
			debugPrint("Message "..index.." was not from an auction house ("..nilSafe(sender)..")");
		end
	end
end

-------------------------------------------------------------------------------
-- Checks if the sender is the auction house
-------------------------------------------------------------------------------
function isSenderAuctionHouse(sender)
	return (sender and (sender == _BC('MailAllianceAuctionHouse') or sender == _BC('MailHordeAuctionHouse')));
end

-------------------------------------------------------------------------------
-- Functions that check the subject
-------------------------------------------------------------------------------
function isSubjectAuctionExpired(subject)
	return (subject:find("^".._BC('MailAuctionExpiredSubject')..": .*") ~= nil);
end

function isSubjectAuctionCancelled(subject)
	return (subject:find("^".._BC('MailAuctionCancelledSubject')..": .*") ~= nil);
end

function isSubjectAuctionWon(subject)
	return (subject:find("^".._BC('MailAuctionWonSubject')..": .*") ~= nil);
end

function isSubjectAuctionSuccessful(subject)
	return (subject:find("^".._BC('MailAuctionSuccessfulSubject')..": .*") ~= nil);
end

function isSubjectOutbidOn(subject)
	return (subject:find("^".._BC('MailOutbidOnSubject').." .*") ~= nil);
end

-------------------------------------------------------------------------------
-- Gets the item name from the subject
-------------------------------------------------------------------------------
function getItemNameFromSubject(subject)
	local itemName = nil;
	if (isSubjectAuctionExpired(subject)) then
		_, _, itemName = subject:find("^".._BC('MailAuctionExpiredSubject')..": (.*)");
	elseif (isSubjectAuctionCancelled(subject)) then
		_, _, itemName = subject:find("^".._BC('MailAuctionCancelledSubject')..": (.*)");
	elseif (isSubjectAuctionWon(subject)) then
		_, _, itemName = subject:find("^".._BC('MailAuctionWonSubject')..": (.*)");
	elseif (isSubjectAuctionSuccessful(subject)) then
		_, _, itemName = subject:find("^".._BC('MailAuctionSuccessfulSubject')..": (.*)");
	elseif (isSubjectOutbidOn(subject)) then
		_, _, itemName = subject:find("^".._BC('MailOutbidOnSubject').." (.*)");
	end
	return itemName;
end

-------------------------------------------------------------------------------
-- Reconcile the purchase and sale databases. This should only be called when
-- we know that all pending mail is currently in the inbox. In otherwords there
-- is no server backlog of messages nor any pending messages yet to arrive in
-- the inbox.
-------------------------------------------------------------------------------
function reconcileDatabases(reconcileTime)
	debugPrint("reconcileDatabases("..date("%c", reconcileTime)..") called");

	-- Tally up the number of pending e-mails in the inbox for each item. We
	-- only care about unread messages since the read messages have already
	-- been processed.
	local pendingBidsForItem = {};
	local pendingSalesForItem = {};
	for index = 1, GetInboxNumItems() do
		local packageIcon, stationeryIcon, sender, subject, money, CODAmount, daysLeft, hasItem, wasRead, wasReturned, textCreated, canReply = GetInboxHeaderInfo(index);
		if (not wasRead and sender and isSenderAuctionHouse(sender)) then
			if (isSubjectAuctionSuccessful(subject) and money) then
				local itemName = getItemNameFromSubject(subject);
				pendingSalesForItem[itemName] = true;
			elseif (isSubjectAuctionExpired(subject) and hasItem) then
				local itemName = GetInboxItem(index);
				pendingSalesForItem[itemName] = true;
			elseif (isSubjectAuctionCancelled(subject) and hasItem) then
				local itemName = GetInboxItem(index);
				pendingSalesForItem[itemName] = true;
			elseif (isSubjectAuctionWon(subject) and hasItem) then
				local itemName = GetInboxItem(index);
				pendingBidsForItem[itemName] = true;
			elseif (isSubjectOutbidOn(subject) and money) then
				local itemName = getItemNameFromSubject(subject);
				pendingBidsForItem[itemName] = true;
			elseif (isSubjectAuctionCancelled(subject) and money) then
				local itemName = getItemNameFromSubject(subject);
				pendingBidsForItem[itemName] = true;
			end
		end
	end

	-- Perform the purchases database reconcilation.
	local reconciledBids, pendingBidsDiscarded, completedBidsDiscarded = BeanCounter.Purchases.ReconcileBids(reconcileTime, pendingBidsForItem);
	if (reconciledBids > 0 or pendingBidsDiscarded > 0) then
		chatPrint("Reconciled "..reconciledBids.." auctions ("..pendingBidsDiscarded.. " discrepencies)");
	end

	-- Perform the sales database reconcilation.
	local reconciledAuctions, pendingAuctionsDiscarded, completedAuctionsDiscarded = BeanCounter.Sales.ReconcileAuctions(reconcileTime, pendingSalesForItem);
	if (reconciledAuctions > 0 or pendingAuctionsDiscarded > 0) then
		chatPrint("Reconciled "..reconciledAuctions.." auctions ("..pendingAuctionsDiscarded.. " discrepencies)");
	end
end

-------------------------------------------------------------------------------
-------------------------------------------------------------------------------
function debugPrint(message)
	BeanCounter.DebugPrint("[BeanCounter.MailMonitor] "..message);
end

-------------------------------------------------------------------------------
-- InboxTask::OnEvent
-------------------------------------------------------------------------------
function InboxTask_OnEvent(this, event)
	return false;
end

-------------------------------------------------------------------------------
-- InboxTask::OnUpdate
-------------------------------------------------------------------------------
function InboxTask_OnUpdate(this)
	return false;
end

-------------------------------------------------------------------------------
-- InboxTask::Execute
-------------------------------------------------------------------------------
function InboxTask_Execute(this)
	return false;
end

-------------------------------------------------------------------------------
-- WaitForInboxUpdate constructor
-------------------------------------------------------------------------------
function createWaitForInboxUpdateTask()
	local task = {};
	task.name = "WaitForInboxUpdate";
	task.OnEvent = WaitForInboxUpdate_OnEvent;
	task.OnUpdate = InboxTask_OnUpdate;
	task.Execute = InboxTask_Execute;
	return task;
end

-------------------------------------------------------------------------------
-- WaitForInboxUpdate::OnEvent
-------------------------------------------------------------------------------
function WaitForInboxUpdate_OnEvent(this, event)
	local satisfied = false;
	if (event == "MAIL_INBOX_UPDATE") then
		satisfied = true;
	end
	return satisfied;
end

-------------------------------------------------------------------------------
-- WaitForTakeInboxItem constructor
-------------------------------------------------------------------------------
function createWaitForTakeInboxItem(index)
	local packageIcon, stationeryIcon, sender, subject, money, CODAmount, daysLeft, hasItem, wasRead, wasReturned, textCreated, canReply = GetInboxHeaderInfo(index);
	local task = {};
	task.index = index;
	task.isSenderAH = isSenderAuctionHouse(sender);
	task.name = "WaitForTakeInboxItem";
	task.OnEvent = WaitForTakeInboxItem_OnEvent;
	task.OnUpdate = InboxTask_OnUpdate;
	task.Execute = InboxTask_Execute;
	return task;
end

-------------------------------------------------------------------------------
-- WaitForTakeInboxItem::OnEvent
-------------------------------------------------------------------------------
function WaitForTakeInboxItem_OnEvent(this, event, arg1)
	local satisfied = false;
	if (event == "MAIL_INBOX_UPDATE") then
		-- TODO: Verify that the item was looted.
		satisfied = true;
		-- If the message was from the AH, it will be deleted automatically.
		if (this.isSenderAH) then
			addTask(createWaitForDeleteInboxItem(this.index), true);
		end
	elseif (event == "UI_ERROR_MESSAGE" and arg1) then
		-- Check for errors in taking the inbox item.
		if (arg1 == ERR_ITEM_MAX_COUNT or arg1 == ERR_INV_FULL) then
			satisfied = true;
		end
	end
	return satisfied;
end

-------------------------------------------------------------------------------
-- WaitForTakeInboxMoney constructor
-------------------------------------------------------------------------------
function createWaitForTakeInboxMoney(index)
	local packageIcon, stationeryIcon, sender, subject, money, CODAmount, daysLeft, hasItem, wasRead, wasReturned, textCreated, canReply = GetInboxHeaderInfo(index);
	local task = {};
	task.index = index;
	task.isSenderAH = isSenderAuctionHouse(sender);
	task.name = "WaitForTakeInboxMoney";
	task.OnEvent = WaitForTakeInboxMoney_OnEvent;
	task.OnUpdate = InboxTask_OnUpdate;
	task.Execute = InboxTask_Execute;
	return task;
end

-------------------------------------------------------------------------------
-- WaitForTakeInboxMoney::OnEvent
-------------------------------------------------------------------------------
function WaitForTakeInboxMoney_OnEvent(this, event)
	local satisfied = false;
	if (event == "MAIL_INBOX_UPDATE") then
		-- TODO: Verify that the money was looted.
		satisfied = true;
		-- If the message was from the AH, it will be deleted automatically.
		if (this.isSenderAH) then
			addTask(createWaitForDeleteInboxItem(this.index), true);
		end
	end
	return satisfied;
end

-------------------------------------------------------------------------------
-- WaitForDeleteInboxItem constructor
-------------------------------------------------------------------------------
function createWaitForDeleteInboxItem(index)
	local task = {};
	task.index = index;
	task.targetMessageCount = GetInboxNumItems() - 1;
	task.name = "WaitForDeleteInboxItem";
	task.OnEvent = WaitForDeleteInboxItem_OnEvent;
	task.OnUpdate = InboxTask_OnUpdate;
	task.Execute = InboxTask_Execute;
	return task;
end

-------------------------------------------------------------------------------
-- WaitForDeleteInboxItem::OnEvent
-------------------------------------------------------------------------------
function WaitForDeleteInboxItem_OnEvent(this, event)
	local satisfied = false;
	if (event == "MAIL_INBOX_UPDATE") then
		--  Check if the message was deleted
		satisfied = (this.targetMessageCount == GetInboxNumItems());
	end
	return satisfied;
end

-------------------------------------------------------------------------------
-- WaitForInvoiceTask constructor
-------------------------------------------------------------------------------
function createWaitForInvoiceTask(index)
	local task = {};
	task.index = index;
	task.name = "WaitForInvoiceTask";
	task.start = time();
	task.OnEvent = WaitForInvoiceTask_OnEvent;
	task.OnUpdate = WaitForInvoiceTask_OnUpdate;
	task.Execute = InboxTask_Execute;
	return task;
end

-------------------------------------------------------------------------------
-- WaitForInvoiceTask::OnEvent
-------------------------------------------------------------------------------
function WaitForInvoiceTask_OnEvent(this, event)
	local satisfied = false;
	if (event == "MAIL_INBOX_UPDATE") then
		local invoiceType, itemName, playerName, bid, buyout, deposit, consignment = GetInboxInvoiceInfo(this.index);
		if (playerName) then
			satisfied = true;
		end
	end
	return satisfied;
end

-------------------------------------------------------------------------------
-- WaitForInvoiceTask:OnUpdate
-------------------------------------------------------------------------------
function WaitForInvoiceTask_OnUpdate(this)
	local satisfied = false;
	if (this.start + 30 > time()) then
		statisfied = true;
	end
	return satisfied;
end

-------------------------------------------------------------------------------
-- WaitForReadMessage constructor
-------------------------------------------------------------------------------
function createWaitForReadMessage(index)
	local task = {};
	task.index = index;
	task.name = "WaitForReadMessage";
	task.OnEvent = WaitForReadMessage_OnEvent;
	task.OnUpdate = InboxTask_OnUpdate;
	task.Execute = InboxTask_Execute;
	return task;
end

-------------------------------------------------------------------------------
-- WaitForReadMessage::OnEvent
-------------------------------------------------------------------------------
function WaitForReadMessage_OnEvent(this, event)
	local satisfied = false;
	if (event == "MAIL_INBOX_UPDATE") then
		local packageIcon, stationeryIcon, sender, subject, money, CODAmount, daysLeft, hasItem, wasRead, wasReturned, textCreated, canReply = GetInboxHeaderInfo(this.index);
		if (wasRead) then
			satisfied = true;
		end
	end
	return satisfied;
end

-------------------------------------------------------------------------------
-- ProcessMessageTask constructor
-------------------------------------------------------------------------------
function createProcessMessageTask(index, messageAgeInSeconds)
	local task = {};
	task.index = index;
	task.messageAgeInSeconds = messageAgeInSeconds;
	task.name = "ProcessMessageTask";
	task.OnEvent = InboxTask_OnEvent;
	task.OnUpdate = InboxTask_OnUpdate;
	task.Execute = ProcessMessageTask_Execute;
	return task;
end

-------------------------------------------------------------------------------
-- ProcessMessageTask::Execute
-------------------------------------------------------------------------------
function ProcessMessageTask_Execute(this)
	processMailMessage(this.index, this.messageAgeInSeconds);
	return true;
end

-------------------------------------------------------------------------------
-- TakeInboxMoneyTask constructor
-------------------------------------------------------------------------------
function createTakeInboxMoneyTask(index)
	local task = {};
	task.index = index;
	task.name = "TakeInboxMoneyTask";
	task.OnEvent = InboxTask_OnEvent;
	task.OnUpdate = InboxTask_OnUpdate;
	task.Execute = TakeInboxMoneyTask_Execute;
	return task;
end

-------------------------------------------------------------------------------
-- TakeInboxMoneyTask::Execute
-------------------------------------------------------------------------------
function TakeInboxMoneyTask_Execute(this)
	TakeInboxMoney(this.index);
	return true;
end

-------------------------------------------------------------------------------
-- TakeInboxItemTask constructor
-------------------------------------------------------------------------------
function createTakeInboxItemTask(index)
	local task = {};
	task.index = index;
	task.name = "TakeInboxItemTask";
	task.OnEvent = InboxTask_OnEvent;
	task.OnUpdate = InboxTask_OnUpdate;
	task.Execute = TakeInboxItemTask_Execute;
	return task;
end

-------------------------------------------------------------------------------
-- TakeInboxItemTask::Execute
-------------------------------------------------------------------------------
function TakeInboxItemTask_Execute(this)
	TakeInboxItem(this.index);
	return true;
end


