-------------------------------------------------------------------------------
-- Module Declaration
--

local plugin = BigWigs:NewPlugin("Messages")
if not plugin then return end

-------------------------------------------------------------------------------
-- Locals
--

local media = LibStub("LibSharedMedia-3.0")
local sink = LibStub("LibSink-2.0")
local FONT = media.MediaType and media.MediaType.FONT or "font"

local labels = {}

local colorModule = nil

local normalMessageAnchor, normalMessageFrame = nil, nil
local emphMessageAnchor, emphMessageFrame, emphMessageText = nil, nil, nil

local labelsPrimaryPoint, labelsSecondaryPoint = nil, nil

local db = nil

local L = BigWigsAPI:GetLocale("BigWigs: Plugins")
plugin.displayName = L.messages

--------------------------------------------------------------------------------
-- Profile
--

plugin.defaultDB = {
	fontName = plugin:GetDefaultFont(),
	emphFontName = plugin:GetDefaultFont(),
	monochrome = false,
	emphMonochrome = false,
	outline = "THICKOUTLINE",
	emphOutline = "THICKOUTLINE",
	align = "CENTER",
	fontSize = 20,
	emphFontSize = 48,
	chat = false,
	useicons = true,
	classcolor = true,
	growUpwards = true,
	displaytime = 3,
	fadetime = 2,
	emphUppercase = true,
	disabled = false,
	emphDisabled = false,
	-- Designed by default to grow up into the errors frame (which should be disabled in the BossBlock plugin in 99% of situations)
	-- Should not enter the RaidWarningFrame by default (since we grow upwards), which we don't want to block view of
	-- By order from top to bottom:
	-- >> UIErrorsFrame (anchored to top of UIParent)
	-- >> Our message frame (placed at bottom of UIErrorsFrame, growing upwards)
	-- >> RaidWarningFrame (anchored to bottom of UIErrorsFrame)
	-- >> RaidBossEmoteFrame (anchored to bottom of RaidWarningFrame)
	-- 122 (UIErrorsFrame Y position) + 60 (UIErrorsFrame height) = 182
	-- Worth noting: RaidWarningFrame height = 70 & RaidBossEmoteFrame height = 80
	normalPosition = {"TOP", "TOP", 0, -182},
	emphPosition = {"CENTER", "CENTER", 0, 0},
}

local function updateProfile()
	db = plugin.db.profile

	for k, v in next, db do
		local defaultType = type(plugin.defaultDB[k])
		if defaultType == "nil" then
			db[k] = nil
		elseif type(v) ~= defaultType then
			db[k] = plugin.defaultDB[k]
		end
	end

	if db.outline ~= "NONE" and db.outline ~= "OUTLINE" and db.outline ~= "THICKOUTLINE" then
		db.outline = plugin.defaultDB.outline
	end
	if db.emphOutline ~= "NONE" and db.emphOutline ~= "OUTLINE" and db.emphOutline ~= "THICKOUTLINE" then
		db.emphOutline = plugin.defaultDB.emphOutline
	end
	if db.align ~= "LEFT" and db.align ~= "CENTER" and db.align ~= "RIGHT" then
		db.align = plugin.defaultDB.align
	end
	if db.fontSize < 10 or db.fontSize > 200 then
		db.fontSize = plugin.defaultDB.fontSize
	end
	if db.emphFontSize < 20 or db.emphFontSize > 200 then
		db.emphFontSize = plugin.defaultDB.emphFontSize
	end
	if db.displaytime < 1 or db.displaytime > 10 then
		db.displaytime = plugin.defaultDB.displaytime
	end
	if db.fadetime < 1 or db.fadetime > 10 then
		db.fadetime = plugin.defaultDB.fadetime
	end

	local emphFlags = nil
	if db.emphMonochrome and db.emphOutline ~= "NONE" then
		emphFlags = "MONOCHROME," .. db.emphOutline
	elseif db.emphMonochrome then
		emphFlags = "MONOCHROME"
	elseif db.emphOutline ~= "NONE" then
		emphFlags = db.emphOutline
	end
	emphMessageText:SetFont(media:Fetch(FONT, db.emphFontName), db.emphFontSize, emphFlags)

	normalMessageAnchor:RefixPosition()
	emphMessageAnchor:RefixPosition()
	normalMessageFrame:ClearAllPoints()
	local align = db.align == "CENTER" and "" or db.align
	if db.growUpwards then
		labelsPrimaryPoint, labelsSecondaryPoint = "BOTTOM"..align, "TOP"..align
	else
		labelsPrimaryPoint, labelsSecondaryPoint = "TOP"..align, "BOTTOM"..align
	end
	normalMessageFrame:SetPoint(labelsPrimaryPoint, normalMessageAnchor, labelsSecondaryPoint)

	local flags = nil
	if db.monochrome and db.outline ~= "NONE" then
		flags = "MONOCHROME," .. db.outline
	elseif db.monochrome then
		flags = "MONOCHROME"
	elseif db.outline ~= "NONE" then
		flags = db.outline
	end
	for i = 1, 4 do
		local font = labels[i]
		font.animFade:SetStartDelay(db.displaytime)
		font.icon.animFade:SetStartDelay(db.displaytime)
		font.animFade:SetDuration(db.fadetime)
		font.icon.animFade:SetDuration(db.fadetime)
		font.icon:SetSize(db.fontSize, db.fontSize)
		font:SetHeight(db.fontSize)
		font:SetFont(media:Fetch(FONT, db.fontName), db.fontSize, flags)
	end
end

--------------------------------------------------------------------------------
-- Anchors & Frames
--

local function showAnchors()
	normalMessageAnchor:Show()
	emphMessageAnchor:Show()
end

local function hideAnchors()
	normalMessageAnchor:Hide()
	emphMessageAnchor:Hide()
end

do
	local function OnDragStart(self)
		self:StartMoving()
	end
	local function OnDragStop(self)
		self:StopMovingOrSizing()
		local point, _, relPoint, x, y = self:GetPoint()
		plugin.db.profile[self.position] = {point, relPoint, x, y}
		plugin:UpdateGUI() -- Update X/Y if GUI is open.
	end
	local function RefixPosition(self)
		self:ClearAllPoints()
		local point, relPoint = plugin.db.profile[self.position][1], plugin.db.profile[self.position][2]
		local x, y = plugin.db.profile[self.position][3], plugin.db.profile[self.position][4]
		self:SetPoint(point, UIParent, relPoint, x, y)
	end

	local function createAnchor(position, title, titleSize, width, height, saveHeader)
		local display = CreateFrame("Frame", nil, UIParent)
		display:EnableMouse(true)
		display:SetClampedToScreen(true)
		display:SetMovable(true)
		display:RegisterForDrag("LeftButton")
		display:SetWidth(width)
		display:SetHeight(height)
		display:SetFrameStrata("HIGH")
		display:SetFrameLevel(5)
		if display.SetFixedFrameStrata then
			display:SetFixedFrameStrata(true)
			display:SetFixedFrameLevel(true)
		end
		display:SetScript("OnDragStart", OnDragStart)
		display:SetScript("OnDragStop", OnDragStop)
		display.RefixPosition = RefixPosition
		local point, relPoint = plugin.defaultDB[position][1], plugin.defaultDB[position][2]
		local x, y = plugin.defaultDB[position][3], plugin.defaultDB[position][4]
		display:SetPoint(point, UIParent, relPoint, x, y)
		display.position = position
		display:Hide()
		local bg = display:CreateTexture()
		bg:SetAllPoints(display)
		bg:SetColorTexture(0, 0, 0, 0.3)
		local header = display:CreateFontString()
		header:SetFont(plugin:GetDefaultFont(titleSize))
		header:SetShadowOffset(1, -1)
		header:SetTextColor(1,0.82,0,1)
		header:SetText(title)
		header:SetPoint("CENTER", display, "CENTER")
		header:SetJustifyV("MIDDLE")
		header:SetJustifyH("CENTER")
		if saveHeader then
			display.header = header
		end
		return display
	end

	normalMessageAnchor = createAnchor("normalPosition", L.messages, 12, 200, 20)
	emphMessageAnchor = createAnchor("emphPosition", L.emphasizedMessages, 48, 650, 80, true)

	normalMessageFrame = CreateFrame("Frame", nil, UIParent)
	normalMessageFrame:SetWidth(2000)
	normalMessageFrame:SetHeight(80)
	normalMessageFrame:SetFrameStrata("FULLSCREEN_DIALOG")
	normalMessageFrame:SetFrameLevel(20) -- Behind GUI (level 100) & behind emphasized messages (level 30)
	if normalMessageFrame.SetFixedFrameStrata then
		normalMessageFrame:SetFixedFrameStrata(true)
		normalMessageFrame:SetFixedFrameLevel(true)
	end

	local function FontFinish(self)
		self:GetParent():Hide()
		if not labels[1]:IsShown() and not labels[2]:IsShown() and not labels[3]:IsShown() and not labels[4]:IsShown() then
			normalMessageFrame:Hide()
		end
	end
	local function IconFinish(self)
		self:GetParent():Hide()
	end

	for i = 1, 4 do
		local fs = normalMessageFrame:CreateFontString()
		fs:SetWidth(0)
		fs:SetHeight(0)
		fs.elapsed = 0
		fs:Hide()

		fs.anim = fs:CreateAnimationGroup()
		fs.anim:SetScript("OnFinished", FontFinish)
		fs.animFade = fs.anim:CreateAnimation("Alpha")
		fs.animFade:SetFromAlpha(1)
		fs.animFade:SetToAlpha(0)

		local icon = normalMessageFrame:CreateTexture()
		icon:SetPoint("RIGHT", fs, "LEFT")
		icon:SetTexCoord(0.07, 0.93, 0.07, 0.93)
		icon:Hide()
		fs.icon = icon

		icon.anim = icon:CreateAnimationGroup()
		icon.anim:SetScript("OnFinished", IconFinish)
		icon.animFade = icon.anim:CreateAnimation("Alpha")
		icon.animFade:SetFromAlpha(1)
		icon.animFade:SetToAlpha(0)

		labels[i] = fs
	end
end

--------------------------------------------------------------------------------
-- Options
--

plugin.pluginOptions = {
	type = "group",
	name = "|TInterface\\AddOns\\BigWigs\\Media\\Icons\\Menus\\Messages:20|t ".. L.messages,
	childGroups = "tab",
	get = function(info) return plugin.db.profile[info[#info]] end,
	set = function(info, value)
		plugin.db.profile[info[#info]] = value
		updateProfile()
	end,
	order = 2,
	args = {
		anchorsButton = {
			type = "execute",
			name = function()
				local BL = BigWigsAPI:GetLocale("BigWigs")
				if BigWigsOptions:InConfigureMode() then
					return BL.toggleAnchorsBtnHide
				else
					return BL.toggleAnchorsBtnShow
				end
			end,
			desc = function()
				local BL = BigWigsAPI:GetLocale("BigWigs")
				if BigWigsOptions:InConfigureMode() then
					return BL.toggleAnchorsBtnHide_desc
				else
					return BL.toggleAnchorsBtnShow_desc
				end
			end,
			func = function() 
				if not BigWigs:IsEnabled() then BigWigs:Enable() end
				if BigWigsOptions:InConfigureMode() then
					plugin:SendMessage("BigWigs_StopConfigureMode")
				else
					plugin:SendMessage("BigWigs_StartConfigureMode")
				end
			end,
			width = 1.5,
			order = 0.2,
		},
		testButton = {
			type = "execute",
			name = BigWigsAPI:GetLocale("BigWigs").testBarsBtn,
			desc = BigWigsAPI:GetLocale("BigWigs").testBarsBtn_desc,
			func = function() 
				BigWigs:Test()
			end,
			width = 1.5,
			order = 0.4,
		},
		normal = {
			type = "group",
			name = L.messages,
			order = 1,
			args = {
				fontName = {
					type = "select",
					name = L.font,
					order = 1,
					values = media:List(FONT),
					itemControl = "DDI-Font",
					get = function()
						for i, v in next, media:List(FONT) do
							if v == plugin.db.profile.fontName then return i end
						end
					end,
					set = function(_, value)
						local list = media:List(FONT)
						plugin.db.profile.fontName = list[value]
						updateProfile()
					end,
					width = 2,
				},
				outline = {
					type = "select",
					name = L.outline,
					order = 2,
					values = {
						NONE = L.none,
						OUTLINE = L.thin,
						THICKOUTLINE = L.thick,
					},
				},
				fontSize = {
					type = "range",
					name = L.fontSize,
					desc = L.fontSizeDesc,
					order = 3,
					max = 200, softMax = 72,
					min = 10,
					step = 1,
					width = 2,
				},
				monochrome = {
					type = "toggle",
					name = L.monochrome,
					desc = L.monochromeDesc,
					order = 4,
				},
				align = {
					type = "select",
					name = L.align,
					values = {
						L.left,
						L.center,
						L.right,
					},
					style = "radio",
					order = 5,
					get = function() return plugin.db.profile.align == "LEFT" and 1 or plugin.db.profile.align == "RIGHT" and 3 or 2 end,
					set = function(_, value)
						plugin.db.profile.align = value == 1 and "LEFT" or value == 3 and "RIGHT" or "CENTER"
						updateProfile()
					end,
				},
				useicons = {
					type = "toggle",
					name = L.useIcons,
					desc = L.useIconsDesc,
					order = 6,
				},
				classcolor = {
					type = "toggle",
					name = L.classColors,
					desc = L.classColorsDesc,
					order = 7,
				},
				growUpwards = {
					type = "toggle",
					name = L.growingUpwards,
					desc = L.growingUpwardsDesc,
					order = 8,
				},
				displaytime = {
					type = "range",
					name = L.displayTime,
					desc = L.displayTimeDesc,
					min = 1,
					max = 10,
					step = 0.5,
					order = 9,
				},
				fadetime = {
					type = "range",
					name = L.fadeTime,
					desc = L.fadeTimeDesc,
					min = 1,
					max = 10,
					step = 0.5,
					order = 10,
				},
				newline1 = {
					type = "description",
					name = "\n",
					order = 11,
				},
				chat = {
					type = "toggle",
					name = L.chatFrameMessages,
					desc = L.chatFrameMessagesDesc,
					order = 12,
					width = 2,
				},
				disabled = {
					type = "toggle",
					name = L.disabled,
					--desc = "XXX",
					order = 13,
					confirm = function(_, value)
						if value then
							return L.disableDesc:format(L.messages)
						end
					end,
				},
				header1 = {
					type = "header",
					name = "",
					order = 14,
				},
				reset = {
					type = "execute",
					name = L.resetAll,
					desc = L.resetMessagesDesc,
					func = function() plugin.db:ResetProfile() end,
					order = 15,
				},
			},
		},
		emphasized = {
			type = "group",
			name = L.emphasizedMessages,
			order = 2,
			args = {
				heading = {
					type = "description",
					name = L.emphasizedDesc.. "\n\n",
					order = 1,
					width = "full",
					fontSize = "medium",
				},
				emphFontName = {
					type = "select",
					name = L.font,
					order = 2,
					values = media:List(FONT),
					itemControl = "DDI-Font",
					get = function()
						for i, v in next, media:List(FONT) do
							if v == plugin.db.profile.emphFontName then return i end
						end
					end,
					set = function(_, value)
						local list = media:List(FONT)
						plugin.db.profile.emphFontName = list[value]
						updateProfile()
					end,
				},
				emphOutline = {
					type = "select",
					name = L.outline,
					order = 3,
					values = {
						NONE = L.none,
						OUTLINE = L.thin,
						THICKOUTLINE = L.thick,
					},
				},
				emphFontSize = {
					type = "range",
					name = L.fontSize,
					desc = L.fontSizeDesc,
					order = 4,
					softMax = 100, max = 200, min = 20, step = 1,
				},
				emphMonochrome = {
					type = "toggle",
					name = L.monochrome,
					desc = L.monochromeDesc,
					order = 5,
				},
				emphUppercase = {
					type = "toggle",
					name = L.uppercase,
					desc = L.uppercaseDesc,
					order = 6,
					width = 2,
					hidden = function() -- Hide this option for CJK languages
						local loc = GetLocale()
						if loc == "zhCN" or loc == "zhTW" or loc == "koKR" then
							return true
						end
					end,
				},
				emphDisabled = {
					type = "toggle",
					name = L.disabled,
					--desc = "XXX",
					order = 7,
					confirm = function(_, value)
						if value then
							return L.disableDesc:format(L.emphasizedMessages)
						end
					end,
				},
			},
		},
		exactPositioning = {
			type = "group",
			name = L.positionExact,
			order = 3,
			childGroups = "tab",
			args = {
				normalPositioning = {
					type = "group",
					name = L.messages,
					order = 1,
					inline = true,
					args = {
						posx = {
							type = "range",
							name = L.positionX,
							desc = L.positionDesc,
							min = -2048,
							max = 2048,
							step = 1,
							order = 1,
							width = "full",
							get = function()
								return plugin.db.profile.normalPosition[3]
							end,
							set = function(_, value)
								plugin.db.profile.normalPosition[3] = value
								normalMessageAnchor:RefixPosition()
							end,
						},
						posy = {
							type = "range",
							name = L.positionY,
							desc = L.positionDesc,
							min = -2048,
							max = 2048,
							step = 1,
							order = 2,
							width = "full",
							get = function()
								return plugin.db.profile.normalPosition[4]
							end,
							set = function(_, value)
								plugin.db.profile.normalPosition[4] = value
								normalMessageAnchor:RefixPosition()
							end,
						},
					},
				},
				emphPositioning = {
					type = "group",
					name = L.emphasizedMessages,
					order = 2,
					inline = true,
					args = {
						posx = {
							type = "range",
							name = L.positionX,
							desc = L.positionDesc,
							min = -2048,
							max = 2048,
							step = 1,
							order = 1,
							width = "full",
							get = function()
								return plugin.db.profile.emphPosition[3]
							end,
							set = function(_, value)
								plugin.db.profile.emphPosition[3] = value
								emphMessageAnchor:RefixPosition()
							end,
						},
						posy = {
							type = "range",
							name = L.positionY,
							desc = L.positionDesc,
							min = -2048,
							max = 2048,
							step = 1,
							order = 2,
							width = "full",
							get = function()
								return plugin.db.profile.emphPosition[4]
							end,
							set = function(_, value)
								plugin.db.profile.emphPosition[4] = value
								emphMessageAnchor:RefixPosition()
							end,
						},
					},
				},
			},
		},
	},
}

-------------------------------------------------------------------------------
-- Initialization
--

function plugin:OnRegister()
	sink.RegisterSink(self, "BigWigsEmphasized", L.bwEmphasized, L.emphasizedSinkDescription, "EmphasizedPrint")
	sink.RegisterSink(self, "BigWigs", "BigWigs", L.sinkDescription, "Print")
end

function plugin:OnPluginEnable()
	colorModule = BigWigs:GetPlugin("Colors", true)

	self:RegisterMessage("BigWigs_ProfileUpdate", updateProfile)
	updateProfile()

	self:RegisterMessage("BigWigs_Message")
	self:RegisterMessage("BigWigs_StartConfigureMode", showAnchors)
	self:RegisterMessage("BigWigs_StopConfigureMode", hideAnchors)
end

-------------------------------------------------------------------------------
-- Event Handlers
--

do
	local floor = math.floor
	local scaleUpTime, scaleDownTime = 0.2, 0.4
	local function bounceAnimation(anim, elapsed)
		local self = anim:GetParent()
		self.elapsed = self.elapsed + elapsed
		local min = db.fontSize
		local max = min + 10
		if self.elapsed <= scaleUpTime then
			self:SetTextHeight(floor(min + ((max - min) * self.elapsed / scaleUpTime)))
		elseif self.elapsed <= scaleDownTime then
			self:SetTextHeight(floor(max - ((max - min) * (self.elapsed - scaleUpTime) / (scaleDownTime - scaleUpTime))))
		else
			self:SetTextHeight(min)
			anim:SetScript("OnUpdate", nil)
		end
	end

	local function getNextSlotDown()
		for i = 4, 1, -1 do
			labels[i]:ClearAllPoints()
		end
		-- move 4 -> 1
		local old = labels[4]
		labels[4] = labels[3]
		labels[3] = labels[2]
		labels[2] = labels[1]
		labels[1] = old
		-- reposition
		old:SetPoint(labelsPrimaryPoint)
		for i = 2, 4 do
			labels[i]:SetPoint(labelsPrimaryPoint, labels[i - 1], labelsSecondaryPoint)
		end
		-- new message at 1
		return old
	end

	local function getNextSlotUp()
		for i = 1, 4 do
			labels[i]:ClearAllPoints()
		end
		-- move 1 -> 4
		local old = labels[1]
		labels[1] = labels[2]
		labels[2] = labels[3]
		labels[3] = labels[4]
		labels[4] = old
		-- reposition
		old:SetPoint(labelsPrimaryPoint)
		for i = 3, 1, -1 do
			labels[i]:SetPoint(labelsPrimaryPoint, labels[i + 1], labelsSecondaryPoint)
		end
		-- new message at 4
		return old
	end

	function plugin:Print(_, text, r, g, b, _, _, _, _, _, icon)
		normalMessageFrame:Show()

		local slot = db.growUpwards and getNextSlotUp() or getNextSlotDown()
		local slotIcon = slot.icon
		slot:SetText(text)
		slot:SetTextColor(r, g, b, 1)

		if icon then
			slotIcon:SetTexture(icon)
			slotIcon.anim:Stop()
			slotIcon:Show()
			slotIcon.anim:Play()
		else
			slotIcon:Hide()
		end
		slot.anim:Stop()
		slot:SetAlpha(1)
		slotIcon:SetAlpha(1)
		slot.elapsed = 0
		slot.anim:SetScript("OnUpdate", bounceAnimation)
		slot:Show()
		slot.anim:Play()
	end
end

do
	emphMessageFrame = CreateFrame("Frame", nil, UIParent)
	emphMessageFrame:SetFrameStrata("FULLSCREEN_DIALOG")
	emphMessageFrame:SetFrameLevel(30) -- Behind GUI (level 100)
	if emphMessageFrame.SetFixedFrameStrata then
		emphMessageFrame:SetFixedFrameStrata(true)
		emphMessageFrame:SetFixedFrameLevel(true)
	end
	emphMessageFrame:SetPoint("CENTER", emphMessageAnchor, "CENTER")
	emphMessageFrame:SetWidth(2000)
	emphMessageFrame:SetHeight(80)
	emphMessageFrame:Hide()

	emphMessageText = emphMessageFrame:CreateFontString()
	emphMessageText:SetPoint("CENTER", emphMessageFrame, "CENTER")

	local updater = emphMessageFrame:CreateAnimationGroup()
	updater:SetScript("OnFinished", function()
		emphMessageFrame:Hide()
		emphMessageAnchor.header:Show() -- Show the header again, for config mode
	end)

	local anim = updater:CreateAnimation("Alpha")
	anim:SetFromAlpha(1)
	anim:SetToAlpha(0)
	anim:SetDuration(2)
	anim:SetStartDelay(1.1)

	function plugin:EmphasizedPrint(_, text, r, g, b)
		emphMessageAnchor.header:Hide() -- Hide the header, for config mode
		emphMessageText:SetText(text)
		emphMessageText:SetTextColor(r, g, b)
		updater:Stop()
		emphMessageFrame:Show()
		updater:Play()
	end
end

do
	local unpack, type = unpack, type
	local format, upper, gsub = string.format, string.upper, string.gsub
	function plugin:BigWigs_Message(event, module, key, text, color, icon, emphasized)
		if not text then return end

		local r, g, b = 1, 1, 1 -- Default to white.
		if type(color) == "table" then
			if color.r and color.g and color.b then
				r, g, b = color.r, color.g, color.b
			else
				r, g, b = unpack(color)
			end
		elseif colorModule then
			r, g, b = colorModule:GetColor(color, module, key)
		end

		if not db.useicons then icon = nil end

		if emphasized and not db.emphDisabled then
			if db.emphUppercase then
				text = upper(text)
				text = gsub(text, "(:%d+|)T", "%1t") -- Fix texture paths that need to end in lowercase |t
			end
			self:EmphasizedPrint(nil, text, r, g, b)
		elseif not db.disabled then
			self:Print(nil, text, r, g, b, nil, nil, nil, nil, nil, icon)
		end
		if db.chat then
			-- http://www.wowpedia.org/UI_escape_sequences
			-- |TTexturePath:size1:size2:xoffset:yoffset:dimx:dimy:coordx1:coordx2:coordy1:coordy2:red:green:blue|t
			if icon then
				text = format("|T%s:15:15:0:0:64:64:4:60:4:60|t%s", icon, text)
			end
			DEFAULT_CHAT_FRAME:AddMessage(text, r, g, b)
		end
	end
end
