local Postal = LibStub("AceAddon-3.0"):NewAddon("Postal", "AceEvent-3.0", "AceHook-3.0")
local L = LibStub("AceLocale-3.0"):GetLocale("Postal")
_G["Postal"] = Postal

-- defaults for storage
local defaults = {
	profile = {
		ModuleEnabledState = {
			["*"] = true
		},
		OpenSpeed = 0.50,
		ChatOutput = 1,
		Select = {
			SpamChat = true,
			KeepFreeSpace = 1,
		},
		OpenAll = {
			AHCancelled = true,
			AHExpired = true,
			AHOutbid = true,
			AHSuccess = true,
			AHWon = true,
			NeutralAHCancelled = true,
			NeutralAHExpired = true,
			NeutralAHOutbid = true,
			NeutralAHSuccess = true,
			NeutralAHWon = true,
			Postmaster = true,
			Attachments = true,
			SpamChat = true,
			KeepFreeSpace = 1,
		},
		Express = {
			EnableAltClick = true,
			AutoSend = true,
			BulkSend = true,
			MouseWheel = true,
			MultiItemTooltip = true,
		},
		BlackBook = {
			AutoFill = true,
			contacts = {},
			recent = {},
			AutoCompleteAlts = true,
			AutoCompleteAllAlts = true,
			AutoCompleteRecent = true,
			AutoCompleteContacts = true,
			AutoCompleteFriends = true,
			AutoCompleteGuild = true,
			ExcludeRandoms = true,
			DisableBlizzardAutoComplete = false,
			UseAutoComplete = true,
		},
	},
	global = {
		BlackBook = {
			alts = {},
		},
	},
}
local _G = getfenv(0)
local t = {}
Postal.keepFreeOptions = {0, 1, 2, 3, 5, 10, 15, 20, 25, 30}

-- Use a common frame and setup some common functions for the Postal dropdown menus
local Postal_DropDownMenu = CreateFrame("Frame", "Postal_DropDownMenu")
Postal_DropDownMenu.displayMode = "MENU"
Postal_DropDownMenu.info = {}
Postal_DropDownMenu.levelAdjust = 0
Postal_DropDownMenu.UncheckHack = function(dropdownbutton)
	_G[dropdownbutton:GetName().."Check"]:Hide()
	_G[dropdownbutton:GetName().."UnCheck"]:Hide()
end
Postal_DropDownMenu.HideMenu = function()
	if UIDROPDOWNMENU_OPEN_MENU == Postal_DropDownMenu then
		CloseDropDownMenus()
	end
end

-- Functions for long subject mouseover
local function subjectHoverIn(self)
	local s = _G["MailItem"..self:GetID().."Subject"]
	if s:GetStringWidth() + 25 > s:GetWidth() then
		GameTooltip:SetOwner(self, "ANCHOR_RIGHT")
		GameTooltip:SetText(s:GetText())
		GameTooltip:Show()
	end
end
local function subjectHoverOut(self)
	GameTooltip:Hide()
end


---------------------------
-- Postal Core Functions --
---------------------------

function Postal:OnInitialize()
	-- Version number
	if not self.version then self.version = GetAddOnMetadata("Postal", "Version") end

	-- Initialize database
	self.db = LibStub("AceDB-3.0"):New("Postal3DB", defaults)
	self.db.RegisterCallback(self, "OnProfileChanged", "OnProfileChanged")
	self.db.RegisterCallback(self, "OnProfileCopied", "OnProfileChanged")
	self.db.RegisterCallback(self, "OnProfileReset", "OnProfileChanged")

	-- Enable/disable modules based on saved settings
	for name, module in self:IterateModules() do
		module:SetEnabledState(self.db.profile.ModuleEnabledState[name] or false)
		if module.OnEnable then
			hooksecurefunc(module, "OnEnable", self.OnModuleEnable_Common) -- Posthook
		end
	end

	-- Register events
	self:RegisterEvent("MAIL_CLOSED")

	-- Create the Menu Button
	local Postal_ModuleMenuButton = CreateFrame("Button", "Postal_ModuleMenuButton", MailFrame)
	Postal_ModuleMenuButton:SetWidth(25)
	Postal_ModuleMenuButton:SetHeight(25)
	Postal_ModuleMenuButton:SetPoint("TOPRIGHT", -22, 2)
	Postal_ModuleMenuButton:SetNormalTexture("Interface\\ChatFrame\\UI-ChatIcon-ScrollDown-Up")
	Postal_ModuleMenuButton:SetHighlightTexture("Interface\\Buttons\\ButtonHilight-Round")
	Postal_ModuleMenuButton:SetDisabledTexture("Interface\\ChatFrame\\UI-ChatIcon-ScrollDown-Disabled")
	Postal_ModuleMenuButton:SetPushedTexture("Interface\\ChatFrame\\UI-ChatIcon-ScrollDown-Down")
	Postal_ModuleMenuButton:SetScript("OnClick", function(self, button, down)
		if Postal_DropDownMenu.initialize ~= Postal.Menu then
			CloseDropDownMenus()
			Postal_DropDownMenu.initialize = Postal.Menu
		end
		ToggleDropDownMenu(1, nil, Postal_DropDownMenu, self:GetName(), 0, 0)
	end)
	Postal_ModuleMenuButton:SetScript("OnHide", Postal_DropDownMenu.HideMenu)

	-- Create 7 buttons for mouseover on long subject lines
	for i = 1, 7 do
		local b = CreateFrame("Button", "PostalSubjectHover"..i, _G["MailItem"..i])
		b:SetID(i)
		b:SetAllPoints(_G["MailItem"..i.."Subject"])
		b:SetScript("OnEnter", subjectHoverIn)
		b:SetScript("OnLeave", subjectHoverOut)
	end
	self.OnInitialize = nil
end

function Postal:OnProfileChanged(event, database, newProfileKey)
	for name, module in self:IterateModules() do
		if self.db.profile.ModuleEnabledState[name] then
			module:Enable()
		else
			module:Disable()
		end
	end
end

function Postal:OnModuleEnable_Common()
	-- If the module is enabled with the MailFrame open (at mailbox)
	-- run the MAIL_SHOW() event function
	if self.MAIL_SHOW and MailFrame:IsVisible() then
		self:MAIL_SHOW()
	end
end

-- Hides the minimap unread mail button if there are no unread mail on closing the mailbox.
-- Does not scan past the first 50 items since only the first 50 are viewable.
function Postal:MAIL_CLOSED()
	for i = 1, GetInboxNumItems() do
		if not select(9, GetInboxHeaderInfo(i)) then return end
	end
	MiniMapMailFrame:Hide()
end

function Postal:Print(...)
	local text = "|cff33ff99Postal|r:"
	for i = 1, select("#", ...) do
		text = text.." "..tostring(select(i, ...))
	end

	if not self:IsChatFrameActive(self.db.profile.ChatOutput) then
		self.db.profile.ChatOutput = 1
	end
	local chatFrame = _G["ChatFrame"..self.db.profile.ChatOutput]
	if chatFrame then
		chatFrame:AddMessage(text)
	end
end

function Postal:IsChatFrameActive(i)
	local _, _, _, _, _, _, shown = FCF_GetChatWindowInfo(i);
	local chatFrame = _G["ChatFrame"..i]
	if chatFrame then
		if shown or chatFrame.isDocked then
			return true
		end
	end
	return false
end

function Postal.SaveOption(dropdownbutton, arg1, arg2, checked)
	Postal.db.profile[arg1][arg2] = checked
end

function Postal.ToggleModule(dropdownbutton, arg1, arg2, checked)
	Postal.db.profile.ModuleEnabledState[arg1] = checked
	if checked then arg2:Enable() else arg2:Disable() end
end

function Postal.SetOpenSpeed(dropdownbutton, arg1, arg2, checked)
	Postal.db.profile.OpenSpeed = arg1
end

function Postal.SetChatOutput(dropdownbutton, arg1, arg2, checked)
	Postal.db.profile.ChatOutput = arg1
end

function Postal.ProfileFunc(dropdownbutton, arg1, arg2, checked)
	if arg1 == "NewProfile" then
		StaticPopup_Show("POSTAL_NEW_PROFILE")
	else
		Postal.db[arg1](Postal.db, arg2)
	end
	CloseDropDownMenus()
end

StaticPopupDialogs["POSTAL_NEW_PROFILE"] = {
	text = L["New Profile Name:"],
	button1 = ACCEPT,
	button2 = CANCEL,
	hasEditBox = 1,
	maxLetters = 128,
	editBoxWidth = 350,  -- Needed in Cata
	OnAccept = function(self)
		Postal.db:SetProfile(strtrim(self.editBox:GetText()))
	end,
	OnShow = function(self)
		self.editBox:SetText(Postal.db:GetCurrentProfile())
		self.editBox:SetFocus()
	end,
	OnHide = StaticPopupDialogs["SET_GUILDPLAYERNOTE"].OnHide,
	EditBoxOnEnterPressed = function(self)
		local parent = self:GetParent()
		Postal.db:SetProfile(strtrim(parent.editBox:GetText()))
		parent:Hide()
	end,
	EditBoxOnEscapePressed = StaticPopupDialogs["SET_GUILDPLAYERNOTE"].EditBoxOnEscapePressed,
	timeout = 0,
	exclusive = 1,
	whileDead = 1,
	hideOnEscape = 1
}

function Postal.Menu(self, level)
	if not level then return end
	local info = self.info
	wipe(info)
	if level == 1 then
		info.isTitle = 1
		info.text = "Postal"
		info.notCheckable = 1
		UIDropDownMenu_AddButton(info, level)

		info.disabled = nil
		info.isTitle = nil
		info.notCheckable = nil

		info.keepShownOnClick = 1
		info.isNotRadio = 1
		for name, module in Postal:IterateModules() do
			info.text = L[name]
			info.func = Postal.ToggleModule
			info.arg1 = name
			info.arg2 = module
			info.checked = module:IsEnabled()
			info.hasArrow = module.ModuleMenu ~= nil
			info.value = module
			UIDropDownMenu_AddButton(info, level)
		end

		wipe(info)
		info.disabled = 1
		UIDropDownMenu_AddButton(info, level)
		info.disabled = nil

		info.text = L["Opening Speed"]
		info.func = self.UncheckHack
		info.notCheckable = 1
		info.keepShownOnClick = 1
		info.hasArrow = 1
		info.value = "OpenSpeed"
		UIDropDownMenu_AddButton(info, level)

		info.text = L["Chat Output"]
		info.func = self.UncheckHack
		info.notCheckable = 1
		info.keepShownOnClick = 1
		info.hasArrow = 1
		info.value = "ChatOutput"
		UIDropDownMenu_AddButton(info, level)

		info.text = L["Profile"]
		info.func = self.UncheckHack
		info.value = "Profile"
		UIDropDownMenu_AddButton(info, level)

		wipe(info)
		info.notCheckable = 1
		info.text = L["Help"]
		info.func = Postal.About
		UIDropDownMenu_AddButton(info, level)

		info.disabled = 1
		info.text = nil
		info.func = nil
		UIDropDownMenu_AddButton(info, level)

		info.disabled = nil
		info.text = CLOSE
		info.func = self.HideMenu
		info.tooltipTitle = CLOSE
		UIDropDownMenu_AddButton(info, level)

	elseif level == 2 then
		if UIDROPDOWNMENU_MENU_VALUE == "OpenSpeed" then
			local speed = Postal.db.profile.OpenSpeed
			for i = 0, 13 do
				local s = 0.3 + i*0.05
				info.text = format("%0.2f", s)
				info.func = Postal.SetOpenSpeed
				info.checked = s == speed
				info.arg1 = s
				UIDropDownMenu_AddButton(info, level)
			end
			for i = 0, 8 do
				local s = 1 + i*0.5
				info.text = format("%0.2f", s)
				info.func = Postal.SetOpenSpeed
				info.checked = s == speed
				info.arg1 = s
				UIDropDownMenu_AddButton(info, level)
			end

		elseif UIDROPDOWNMENU_MENU_VALUE == "ChatOutput" then
			local selectedFrame = Postal.db.profile.ChatOutput
			for i = 1, NUM_CHAT_WINDOWS do
				if Postal:IsChatFrameActive(i) then
					info.text = format("%d. %s", i, _G["ChatFrame"..i.."Tab"]:GetText())
					info.func = Postal.SetChatOutput
					info.checked = i == selectedFrame
					info.arg1 = i
					UIDropDownMenu_AddButton(info, level)
				end
			end

		elseif UIDROPDOWNMENU_MENU_VALUE == "Profile" then
			-- Profile stuff
			info.hasArrow = 1
			info.keepShownOnClick = 1
			info.func = self.UncheckHack
			info.notCheckable = 1

			info.text = L["Choose"]
			info.value = "SetProfile"
			UIDropDownMenu_AddButton(info, level)

			info.text = L["Copy From"]
			info.value = "CopyProfile"
			UIDropDownMenu_AddButton(info, level)

			info.text = L["Delete"]
			info.value = "DeleteProfile"
			UIDropDownMenu_AddButton(info, level)

			info.hasArrow = nil
			info.keepShownOnClick = nil
			info.func = Postal.ProfileFunc
			info.arg1 = "NewProfile"
			info.text = L["New Profile"]
			UIDropDownMenu_AddButton(info, level)

			info.text = L["Reset Profile"]
			info.func = Postal.ProfileFunc
			info.arg1 = "ResetProfile"
			info.arg2 = nil
			UIDropDownMenu_AddButton(info, level)

		elseif type(UIDROPDOWNMENU_MENU_VALUE) == "table" and UIDROPDOWNMENU_MENU_VALUE.ModuleMenu then
			-- Submenus for modules
			self.levelAdjust = 1
			UIDROPDOWNMENU_MENU_VALUE.ModuleMenu(self, level)
			self.levelAdjust = 0
			self.module = UIDROPDOWNMENU_MENU_VALUE
		end

	elseif level == 3 then
		if UIDROPDOWNMENU_MENU_VALUE == "SetProfile" then
			local cur = Postal.db:GetCurrentProfile()
			Postal.db:GetProfiles(t)
			table.sort(t)
			info.func = Postal.ProfileFunc
			info.arg1 = "SetProfile"
			for i = 1, #t do
				local s = t[i]
				info.text = s
				info.arg2 = s
				info.checked = cur == s
				UIDropDownMenu_AddButton(info, level)
			end

		elseif UIDROPDOWNMENU_MENU_VALUE == "CopyProfile" or UIDROPDOWNMENU_MENU_VALUE == "DeleteProfile" then
			local cur = Postal.db:GetCurrentProfile()
			Postal.db:GetProfiles(t)
			table.sort(t)
			info.func = Postal.ProfileFunc
			info.arg1 = UIDROPDOWNMENU_MENU_VALUE
			info.notCheckable = 1
			for i = 1, #t do
				local s = t[i]
				if s ~= cur then
					info.text = s
					info.arg2 = s
					UIDropDownMenu_AddButton(info, level)
				end
			end

		elseif self.module and self.module.ModuleMenu then
			self.levelAdjust = 1
			self.module.ModuleMenu(self, level)
			self.levelAdjust = 0
		end

	elseif level > 3 then
		if self.module and self.module.ModuleMenu then
			self.levelAdjust = 1
			self.module.ModuleMenu(self, level)
			self.levelAdjust = 0
		end

	end
end

function Postal:CreateAboutFrame()
	local aboutFrame = Postal.aboutFrame
	if not aboutFrame and Chatter and ChatterCopyFrame then
		aboutFrame = ChatterCopyFrame
		aboutFrame.editBox = Chatter:GetModule("Chat Copy").editBox
	end
	if not aboutFrame or not aboutFrame.editBox then
		aboutFrame = CreateFrame("Frame", "PostalAboutFrame", UIParent)
		tinsert(UISpecialFrames, "PostalAboutFrame")
		aboutFrame:SetBackdrop({
			bgFile = [[Interface\DialogFrame\UI-DialogBox-Background]],
			edgeFile = [[Interface\DialogFrame\UI-DialogBox-Border]],
			tile = true, tileSize = 16, edgeSize = 16,
			insets = { left = 3, right = 3, top = 5, bottom = 3 }
		})
		aboutFrame:SetBackdropColor(0,0,0,1)
		aboutFrame:SetWidth(500)
		aboutFrame:SetHeight(400)
		aboutFrame:SetPoint("CENTER", UIParent, "CENTER")
		aboutFrame:Hide()
		aboutFrame:SetFrameStrata("DIALOG")
		aboutFrame:SetToplevel(true)

		local scrollArea = CreateFrame("ScrollFrame", "PostalAboutScroll", aboutFrame, "UIPanelScrollFrameTemplate")
		scrollArea:SetPoint("TOPLEFT", aboutFrame, "TOPLEFT", 8, -30)
		scrollArea:SetPoint("BOTTOMRIGHT", aboutFrame, "BOTTOMRIGHT", -30, 8)

		local editBox = CreateFrame("EditBox", nil, aboutFrame)
		editBox:SetMultiLine(true)
		editBox:SetMaxLetters(99999)
		editBox:EnableMouse(true)
		editBox:SetAutoFocus(false)
		editBox:SetFontObject(ChatFontNormal)
		editBox:SetWidth(400)
		editBox:SetHeight(270)
		editBox:SetScript("OnEscapePressed", function() aboutFrame:Hide() end)
		aboutFrame.editBox = editBox

		scrollArea:SetScrollChild(editBox)

		local close = CreateFrame("Button", nil, aboutFrame, "UIPanelCloseButton")
		close:SetPoint("TOPRIGHT", aboutFrame, "TOPRIGHT")
	end
	Postal.aboutFrame = aboutFrame
	Postal.CreateAboutFrame = nil -- Kill ourselves
end

function Postal.About()
	if Postal.CreateAboutFrame then Postal:CreateAboutFrame() end
	local version = GetAddOnMetadata("Postal", "Version")
	wipe(t)
	tinsert(t, "|cFFFFCC00"..GetAddOnMetadata("Postal", "Title").." v"..version.."|r")
	tinsert(t, "-----")
	tinsert(t, "")
	for name, module in Postal:IterateModules() do
		tinsert(t, "|cffffcc00"..L[name].."|r")
		if module.description then
			tinsert(t, module.description)
		end
		if module.description2 then
			tinsert(t, "")
			tinsert(t, module.description2)
		end
		tinsert(t, "")
	end
	tinsert(t, "-----")
	tinsert(t, L["Please post bugs or suggestions at the wowace forums thread at |cFF00FFFFhttp://forums.wowace.com/showthread.php?t=3909|r. When posting bugs, indicate your locale and Postal's version number v%s."]:format(version))
	tinsert(t, "")
	tinsert(t, "- Xinhuan (Blackrock/Barthilas US Alliance)")
	tinsert(t, "")
	Postal.aboutFrame.editBox:SetText(table.concat(t, "\n"))
	Postal.aboutFrame:Show()
	wipe(t) -- For garbage collection
end

---------------------------
-- Common Mail Functions --
---------------------------

-- Disable Inbox Clicks
local function noop() end
function Postal:DisableInbox(disable)
	if disable then
		if not self:IsHooked("InboxFrame_OnClick") then
			self:RawHook("InboxFrame_OnClick", noop, true)
			for i = 1, 7 do
				_G["MailItem" .. i .. "ButtonIcon"]:SetDesaturated(true)
			end
		end
	else
		if self:IsHooked("InboxFrame_OnClick") then
			self:Unhook("InboxFrame_OnClick")
			for i = 1, 7 do
				_G["MailItem" .. i .. "ButtonIcon"]:SetDesaturated(false)
			end
		end
	end
end

-- Return the type of mail a message subject is
local SubjectPatterns = {
	AHCancelled = gsub(AUCTION_REMOVED_MAIL_SUBJECT, "%%s", ".*"),
	AHExpired = gsub(AUCTION_EXPIRED_MAIL_SUBJECT, "%%s", ".*"),
	AHOutbid = gsub(AUCTION_OUTBID_MAIL_SUBJECT, "%%s", ".*"),
	AHSuccess = gsub(AUCTION_SOLD_MAIL_SUBJECT, "%%s", ".*"),
	AHWon = gsub(AUCTION_WON_MAIL_SUBJECT, "%%s", ".*"),
}
function Postal:GetMailType(msgSubject)
	if msgSubject then
		for k, v in pairs(SubjectPatterns) do
			if msgSubject:find(v) then return k end
		end
	end
	return "NonAHMail"
end

function Postal:GetMoneyString(money)
	local gold = floor(money / 10000)
	local silver = floor((money - gold * 10000) / 100)
	local copper = mod(money, 100)
	if gold > 0 then
		return format(GOLD_AMOUNT_TEXTURE.." "..SILVER_AMOUNT_TEXTURE.." "..COPPER_AMOUNT_TEXTURE, gold, 0, 0, silver, 0, 0, copper, 0, 0)
	elseif silver > 0 then
		return format(SILVER_AMOUNT_TEXTURE.." "..COPPER_AMOUNT_TEXTURE, silver, 0, 0, copper, 0, 0)
	else
		return format(COPPER_AMOUNT_TEXTURE, copper, 0, 0)
	end
end

function Postal:GetMoneyStringPlain(money)
	local gold = floor(money / 10000)
	local silver = floor((money - gold * 10000) / 100)
	local copper = mod(money, 100)
	if gold > 0 then
		return gold..GOLD_AMOUNT_SYMBOL.." "..silver..SILVER_AMOUNT_SYMBOL.." "..copper..COPPER_AMOUNT_SYMBOL
	elseif silver > 0 then
		return silver..SILVER_AMOUNT_SYMBOL.." "..copper..COPPER_AMOUNT_SYMBOL
	else
		return copper..COPPER_AMOUNT_SYMBOL
	end
end

function Postal:CountItemsAndMoney()
	local numAttach = 0
	local numGold = 0
	for i = 1, GetInboxNumItems() do
		local msgMoney, _, _, msgItem = select(5, GetInboxHeaderInfo(i))
		numAttach = numAttach + (msgItem or 0)
		numGold = numGold + msgMoney
	end
	return numAttach, numGold
end
