--[[	*** DataStore_Mails ***
Written by : Thaoky, EU-Marécages de Zangar
July 16th, 2009
--]]
if not DataStore then return end

local addonName = "DataStore_Mails"

_G[addonName] = LibStub("AceAddon-3.0"):NewAddon(addonName, "AceConsole-3.0", "AceEvent-3.0")

local addon = _G[addonName]

local THIS_ACCOUNT = "Default"
local MAIL_EXPIRY = 30		-- Mails expire after 30 days

local AddonDB_Defaults = {
	global = {
		Options = {
			ScanMailBody = 1,					-- by default, scan the body of a mail (this action marks it as read)
		},
		Characters = {
			['*'] = {				-- ["Account.Realm.Name"] 
				lastUpdate = nil,		-- last time the mail was checked for this char
				lastVisitDate = nil,			-- in YYYY MM DD  hh:mm, for external apps
				Mails = {
					['*'] = {
						icon = nil,
						link = nil,
						count = nil,
						money = nil,
						lastCheck = 0,		-- last time "THIS" mail was checked (can be different than that of the mailbox)
						text = nil,
						subject = nil,
						sender = nil,
						daysLeft = 0,
						returned = nil,
					}
				},
				MailCache = {				-- same structure as "Mail", but serves as a cache for mails sent by a guildmate, until the mail actually arrives in the real mailbox (1h delay)
					['*'] = {
						icon = nil,
						link = nil,
						count = nil,
						money = nil,
						lastCheck = 0,		-- last time "THIS" mail was checked (can be different than that of the mailbox)
						text = nil,
						subject = nil,
						sender = nil,
						daysLeft = 0,
					}
				},
			}
		}
	}
}

-- *** Utility functions ***
local function GetIDFromLink(link)
	return tonumber(link:match("item:(%d+)"))
end

local function GetMailTable(character, index)
	-- depending on the index passed, returns the right mail entry either from the "Mails" table or the "MailCache" table
	-- The assumption is that the MailCache entries come after the Mails entries
	-- This function is pure utility, and is not made public to client addons
	
	if index <= #character.Mails then
		return character.Mails[index]
	else
		index = index - #character.Mails
		return character.MailCache[index]
	end
end

-- ** Mixins **
local function _GetMailboxLastVisit(character)
	return character.lastUpdate or 0
end

local function _GetMailItemCount(character, searchedID)
	local count = 0
	for _, v in pairs (character.Mails) do
		local link = v.link
		if link and (GetIDFromLink(link) == searchedID) then
			count = count + (v.count or 1)
		end
	end
	
	for _, v in pairs (character.MailCache) do
		local link = v.link
		if link and (GetIDFromLink(link) == searchedID) then
			count = count + (v.count or 1)
		end
	end
	return count
end

local function _GetMailAttachments()
	return addon.Attachments
end

local function _GetNumMails(character)
	return #character.Mails + #character.MailCache
end

local function _GetMailInfo(character, index)
	local data = GetMailTable(character, index)
	return data.icon, data.count, data.link, data.money, data.text, data.returned
end

local function _GetMailSender(character, index)
	local data = GetMailTable(character, index)
	return data.sender
end

local function _GetMailExpiry(character, index)
	local data = GetMailTable(character, index)

	-- return the mail expiry, expressed in days and in seconds
	local diff = time() - data.lastCheck
	local days = data.daysLeft - (diff / 86400)
	local seconds = (data.daysLeft * 86400) - diff
	
	return days, seconds
end

local function _GetMailSubject(character, index)
	local data = GetMailTable(character, index)
	
	if data.subject then			-- if there's a subject, use it
		return data.subject
	end
	-- otherwise, return the name of the item
	
	local name
	local link = data.link
	if link then
		local id = GetIDFromLink(link)
		name = GetItemInfo(id)
	end
	return name
end

local function _GetNumExpiredMails(character, threshold)
	local count = 0
	for i = 1, _GetNumMails(character) do
		if _GetMailExpiry(character, i) < threshold then
			count = count + 1
		end
	end
	return count
end

local function _SaveMailToCache(character, mailMoney, mailBody, mailSubject, mailSender)
	local mailIcon
	if mailMoney > 0 then
		mailIcon = "Interface\\Icons\\INV_Misc_Coin_01"
	else
		mailIcon = "Interface\\Icons\\INV_Misc_Note_01"
	end

	table.insert(character.MailCache, {
		money = mailMoney,
		icon = mailIcon,
		text = mailBody,
		subject = mailSubject,
		sender = mailSender,
		lastCheck = time(),
		daysLeft = MAIL_EXPIRY,
	} )
end

local function _SaveMailAttachmentToCache(character, mailIcon, mailLink, mailCount, mailSender)
	table.insert(character.MailCache, {
		icon = mailIcon,
		link = mailLink,
		count = mailCount,
		sender = mailSender,
		lastCheck = time(),
		daysLeft = MAIL_EXPIRY,
	} )
end

local function _IsMailBoxOpen()
	return addon.isOpen
end

local PublicMethods = {
	GetMailboxLastVisit = _GetMailboxLastVisit,
	GetMailItemCount = _GetMailItemCount,
	GetMailAttachments = _GetMailAttachments,
	GetNumMails = _GetNumMails,
	GetMailInfo = _GetMailInfo,
	GetMailSender = _GetMailSender,
	GetMailExpiry = _GetMailExpiry,
	GetMailSubject = _GetMailSubject,
	GetNumExpiredMails = _GetNumExpiredMails,
	SaveMailToCache = _SaveMailToCache,
	SaveMailAttachmentToCache = _SaveMailAttachmentToCache,
	IsMailBoxOpen = _IsMailBoxOpen,
}

function addon:OnInitialize()
	addon.db = LibStub("AceDB-3.0"):New(addonName .. "DB", AddonDB_Defaults)

	DataStore:RegisterModule(addonName, addon, PublicMethods)
	DataStore:SetCharacterBasedMethod("GetMailboxLastVisit")
	DataStore:SetCharacterBasedMethod("GetMailItemCount")
	DataStore:SetCharacterBasedMethod("GetNumMails")
	DataStore:SetCharacterBasedMethod("GetMailInfo")
	DataStore:SetCharacterBasedMethod("GetMailSender")
	DataStore:SetCharacterBasedMethod("GetMailExpiry")
	DataStore:SetCharacterBasedMethod("GetMailSubject")
	DataStore:SetCharacterBasedMethod("GetNumExpiredMails")
	DataStore:SetCharacterBasedMethod("SaveMailToCache")
	DataStore:SetCharacterBasedMethod("SaveMailAttachmentToCache")
end

function addon:OnEnable()
	addon:RegisterEvent("MAIL_SHOW")
	addon:RegisterEvent("BAG_UPDATE")
end

function addon:OnDisable()
	addon:UnregisterEvent("PLAYER_ALIVE")
	addon:UnregisterEvent("BAG_UPDATE")
end

-- *** Scanning functions ***
local function SaveAttachments(character, index, mailSender, days, wasReturned)
	-- saves attachments of a given mail index into a given character mailbox
	
	for attachmentIndex = 1, 12 do		-- mandatory, loop through all 12 slots, since attachments could be anywhere (ex: slot 4,5,8)
		local item, mailIcon, itemCount = GetInboxItem(index, attachmentIndex)
		if item then
			table.insert(character.Mails, {
				icon = mailIcon,
				count = itemCount,
				link = GetInboxItemLink(index, attachmentIndex),
				sender = mailSender,
				lastCheck = time(),
				daysLeft = days,
				returned = wasReturned,
			} )
		end
	end
end

local function ScanMailbox()
	local character = addon.ThisCharacter
	wipe(character.Mails)
	
	local numItems = GetInboxNumItems();
	if numItems == 0 then
		return
	end
	
	local cache = character.MailCache
	-- check the cache, and clean entries that are about to be replaced in the scan
	for i = #cache, 1, -1 do
		if cache[i].lastCheck then
			local age = time() - cache[i].lastCheck
			
			if age > 3600 then		-- if older than 1 hour, delete this entry
				table.remove(cache, i)
			end
		end
	end
	
	for i = 1, numItems do
		local _, stationaryIcon, mailSender, mailSubject, mailMoney, _, days, numAttachments, _, wasReturned = GetInboxHeaderInfo(i);
		if numAttachments then	-- treat attachments as separate entries
			SaveAttachments(character, i, mailSender, days, wasReturned)
		end

		local inboxText
		if addon.db.global.Options["ScanMailBody"] == 1 then
			inboxText = GetInboxText(i)					-- this marks the mail as read
		end
		
		if (mailMoney > 0) or inboxText then			-- if there's money or text .. save the entry
			local mailIcon
			if mailMoney > 0 then
				mailIcon = "Interface\\Icons\\INV_Misc_Coin_01"
			else
				mailIcon = stationaryIcon
			end
			table.insert(character.Mails, {
				icon = mailIcon,
				money = mailMoney,
				text = inboxText,
				subject = mailSubject,
				sender = mailSender,
				lastCheck = time(),
				daysLeft = days,
				returned = wasReturned,
			} )
		end
	end
	
	-- show mails with the lowest expiry first
	table.sort(character.Mails, function(a, b) return a.daysLeft < b.daysLeft end)
end

-- *** Hooks ***

local Orig_SendMail = SendMail

function SendMail(recipient, subject, body, ...)
	-- this function takes care of saving mails sent to alts directly into their mailbox, so that client addons don't have to take care about it
	for characterName, characterKey in pairs(DataStore:GetCharacters()) do		-- browse alts on current realm
		if strlower(characterName) == strlower(recipient) then						-- if recipient is a known alt ..
			local character = addon.db.global.Characters[characterKey]
			
			for k, v in pairs(addon.Attachments) do		--  .. save attachments into his mailbox
				table.insert(character.Mails, {				-- not in the mail cache, since they arrive directly in an alt's mailbox
					icon = v.icon,
					link = v.link,
					count = v.count,
					sender = UnitName("player"),
					lastCheck = time(),
					daysLeft = MAIL_EXPIRY,
				} )
			end
			
			-- .. then save the mail itself + gold if any
			local moneySent = GetSendMailMoney()
			if (moneySent > 0) or (strlen(body) > 0) then
				local mailIcon
				if moneySent > 0 then
					mailIcon = "Interface\\Icons\\INV_Misc_Coin_01"
				else
					mailIcon = "Interface\\Icons\\INV_Misc_Note_01"
				end
				table.insert(character.Mails, {
					money = moneySent,
					icon = mailIcon,
					text = body,
					subject = subject,
					sender = UnitName("player"),
					lastCheck = time(),
					daysLeft = MAIL_EXPIRY,
				} )
			end
			
			-- if the alt has never checked his mail before, this value won't be correct, so set it to make sure expiry returns proper results.
			character.lastUpdate = time()
			
			table.sort(character.Mails, function(a, b)		-- show mails with the lowest expiry first
				return a.daysLeft < b.daysLeft
			end)
			
			break
		end
	end
		
	Orig_SendMail(recipient, subject, body, ...)
end

local Orig_ReturnInboxItem = ReturnInboxItem

function ReturnInboxItem(index, ...)
	local _, stationaryIcon, mailSender, mailSubject, mailMoney, _, _, numAttachments = GetInboxHeaderInfo(index)

	for characterName, characterKey in pairs(DataStore:GetCharacters()) do		-- browse alts on current realm
		if strlower(characterName) == strlower(mailSender) then						-- if recipient is a known alt ..
			local character = addon.db.global.Characters[characterKey]

			if numAttachments then	-- treat attachments as separate entries
				SaveAttachments(character, index, UnitName("player"), MAIL_EXPIRY, true)
			end

			local inboxText = GetInboxText(index)		-- this marks the mail as read, no problem here since the mail is returned anyway
			
			if (mailMoney > 0) or inboxText then			-- if there's money or text .. save the entry
				local mailIcon
				if mailMoney > 0 then
					mailIcon = "Interface\\Icons\\INV_Misc_Coin_01"
				else
					mailIcon = stationaryIcon
				end
				table.insert(character.Mails, {
					icon = mailIcon,
					money = mailMoney,
					text = inboxText,
					subject = mailSubject,
					sender = UnitName("player"),
					lastCheck = time(),
					daysLeft = MAIL_EXPIRY,
					returned = true,				-- this is the mail we're returning, so true
				} )
			end
			
			break
		end
	end

	Orig_ReturnInboxItem(index, ...)
end


-- *** EVENT HANDLERS ***
function addon:BAG_UPDATE(event, bag)
	if addon.isOpen then	-- if a bag is updated while the mailbox is opened, this means an attachment has been taken.
		ScanMailbox()		-- I could not hook TakeInboxItem because mailbox content is not updated yet
	end
end

function addon:MAIL_SHOW()
	-- the event may be triggered multiple times, exit if the mailbox is already open
	if addon.isOpen then return end	
	
	CheckInbox()
	addon:RegisterEvent("MAIL_CLOSED")
	addon:RegisterEvent("MAIL_INBOX_UPDATE")
	addon:RegisterEvent("MAIL_SEND_INFO_UPDATE", self.OnSendInfoUpdate)

	-- create a temporary table to hold the attachments that will be sent, keep it local since the event is rare
	addon.Attachments = addon.Attachments or {}
	addon.isOpen = true
end

function addon:MAIL_CLOSED()
	addon.isOpen = nil
	addon:UnregisterEvent("MAIL_CLOSED")
	ScanMailbox()
	
	local character = addon.ThisCharacter
	character.lastUpdate = time()
	character.lastVisitDate = date("%Y/%m/%d %H:%M")
	
	addon:UnregisterEvent("MAIL_SEND_INFO_UPDATE")
	wipe(addon.Attachments)
end

function addon:MAIL_INBOX_UPDATE()
	-- process only one occurence of the event, right after MAIL_SHOW
	addon:UnregisterEvent("MAIL_INBOX_UPDATE")
	ScanMailbox()
end

function addon:MAIL_SEND_INFO_UPDATE()
	wipe(addon.Attachments)

	for i=1, 12 do
		local name, itemIcon, itemCount = GetSendMailItem(i)
		if name then			-- if attachment slot is not empty .. save it
			table.insert(addon.Attachments, {
				icon = itemIcon,
				link = GetSendMailItemLink(i),
				count = itemCount
			} )
		end
	end
end
