local deformat = AceLibrary("Deformat-2.0")
local S = AceLibrary("Babble-Spell-2.2")
local babbleClass = AceLibrary("Babble-Class-2.2")
local babbleBoss = AceLibrary("Babble-Boss-2.2")
local tablet = AceLibrary("Tablet-2.0")
local dewdrop = AceLibrary("Dewdrop-2.0")
local L = AceLibrary("AceLocale-2.2"):new("TopScoreFu")
local _G = getfenv(0)

local MikSBT

local vulnerableBosses = {
	-- AQ20
	babbleBoss["Ossirian the Unscarred"],
	-- Blackwing Lair
	babbleBoss["Vaelastrasz the Corrupt"],
	babbleBoss["Chromaggus"],
	-- Naxxramas
	babbleBoss["Deathknight Understudy"],
	babbleBoss["Thaddius"],
	-- Karazhan
	babbleBoss["Terestian Illhoof"],
	babbleBoss["The Curator"],
	babbleBoss["Netherspite"],
	-- Magtheridon's Lair
	babbleBoss["Magtheridon"]
}
-- Some bosses are only vulnerable in Heroic Mode
local heroicVulnerableBosses = {
	-- The Mechanar
	babbleBoss["Mechano-Lord Capacitus"],
}

TopScoreFu = AceLibrary("AceAddon-2.0"):new("FuBarPlugin-2.0", "AceDB-2.0", "AceEvent-2.0", "AceConsole-2.0", "Parser-3.0")
TopScoreFu.hasIcon = true
TopScoreFu.canHideText = true
TopScoreFu.clickableTooltip = true
TopScoreFu:RegisterDB("TopScoreFuDB", "TopScoreFuPCDB")
TopScoreFu:RegisterDefaults('profile', {
	miksbt = true,
	splash = true,
	noise = true,
	screenshot = false,
	includeHeals = true,
	includeDamage = true,
	ignoreVulnerable = true,
	onlyPvP = false,
	showTrivial = false,
	posX = 0,
	posY = 0,
	filters = {}
})
TopScoreFu:RegisterDefaults('char', {
	hits = {}
})
TopScoreFu:RegisterChatCommand(L["COMMANDS"], {
	desc = L["DESCRIPTION"],
	type = "group",
	args = {},
})

function TopScoreFu:SetPosition(text)
	local _,_,x,y = string.find(text, "(%-?%d+),? (%-?%d+)")
	x, y = tonumber(x), tonumber(y)
	if x == nil or y == nil then
		self:Print(L["TEXT_SET_POSITION_ERROR"])
	else
		self.db.profile.posX = x
		self.db.profile.posY = y
		TopScoreFuSplash:ClearAllPoints()
		TopScoreFuSplash:SetPoint("CENTER", UIParent, "CENTER", self.db.profile.posX, self.db.profile.posY)
		self:Print(L["PATTERN_SET_POSITION"], x, y)
	end
end

function TopScoreFu:ResetScores()
	self.db.char.hits = {}
	self:Update()
end

function TopScoreFu:ToggleShowingMikSBT()
	self.db.profile.miksbt = not self.db.profile.miksbt
	return self.db.profile.miksbt
end

function TopScoreFu:ToggleShowingSplash()
	self.db.profile.splash = not self.db.profile.splash
	return self.db.profile.splash
end

function TopScoreFu:ToggleShowingTrivial()
	self.db.profile.showTrivial = not self.db.profile.showTrivial
	self:Update()
	return self.db.profile.showTrivial
end

function TopScoreFu:TogglePlayingNoise()
	self.db.profile.noise = not self.db.profile.noise
	return self.db.profile.noise
end

function TopScoreFu:ToggleTakingScreenshots(loud)
	self.db.profile.screenshot = not self.db.profile.screenshot
	return self.db.profile.screenshot
end

function TopScoreFu:ToggleIncludingHeals()
	self.db.profile.includeHeals = not self.db.profile.includeHeals
	self:Update()
	return self.db.profile.includeHeals
end

function TopScoreFu:ToggleIncludingDamage()
	self.db.profile.includeDamage = not self.db.profile.includeDamage
	self:Update()
	return self.db.profile.includeDamage
end

function TopScoreFu:ToggleOnlyPvP()
	self.db.profile.onlyPvP = not self.db.profile.onlyPvP
	self:Update()
	return self.db.profile.onlyPvP
end

function TopScoreFu:IsFiltering(spell)
	return self.db.profile.filters[spell]
end

function TopScoreFu:ToggleFiltering(spell)
	self.db.profile.filters[spell] = not self.db.profile.filters[spell]
	self:Update()
	return self.db.profile.filters[spell]
end

function TopScoreFu:IsIgnoringVulnerable()
	return self.db.profile.ignoreVulnerable
end

function TopScoreFu:ToggleIgnoreVulnerable()
	self.db.profile.ignoreVulnerable = not self.db.profile.ignoreVulnerable
	self:Update()
	return self.db.profile.ignoreVulnerable
end

function TopScoreFu:Purge(spell)
	self.db.profile.filters[spell] = nil
	self.db.char.hits[spell] = nil
	if not next(self.db.char.hits) then
		dewdrop:Close(2)
		dewdrop:Refresh(1)
	end
	self:Update()
end

function TopScoreFu:OnInitialize()
	local frame = CreateFrame("MessageFrame", "TopScoreFuSplash", UIParent)
	frame:SetWidth(GetScreenWidth())
	frame:SetHeight(100)
	frame:SetPoint("CENTER", UIParent, "CENTER")
	frame:SetFontObject(NumberFontNormalHuge)
	frame:SetFrameStrata("LOW")
	
	for spell, _ in pairs(self.db.char.hits) do
		if not self:DoesSpellExist(spell) then
			self.db.char.hits[spell] = false
		end
	end
end

function TopScoreFu:OnEnable()
	MikSBT = _G.MikSBT
	
	self:RegisterParserEvent({
		eventType = "Damage",
		sourceID = "player",
	}, "OnDamage")
	self:RegisterParserEvent({
		eventType = "Heal",
		sourceID = "player",
	}, "OnHeal")
	
	TopScoreFuSplash:ClearAllPoints()
	TopScoreFuSplash:SetPoint("CENTER", UIParent, "CENTER", self.db.profile.posX, self.db.profile.posY)
end

function TopScoreFu:OnDamage(t)
	self:RecordHit(t.abilityName or S["Attack"], t.amount, t.recipientName, t.recipientID, t.isCrit, false, t.recipientPvP)
end

function TopScoreFu:OnHeal(t)
	self:RecordHit(t.abilityName, t.amount, t.recipientName, t.recipientID, t.isCrit, true, true) -- the final true implies that the target is pvp enabled, which is not necessarily the case
end

function TopScoreFu:OnMenuRequest(level, value)
	if level == 1 then
		dewdrop:AddLine(
			'text', L["MENU_SHOW_SPLASH"],
			'arg1', self,
			'func', "ToggleShowingSplash",
			'checked', self.db.profile.splash
		)
		
		if MikSBT then
			dewdrop:AddLine(
				'text', L["MENU_SHOW_MIKSBT"],
				'arg1', self,
				'func', "ToggleShowingMikSBT",
				'checked', self.db.profile.miksbt
			)		
		end
		
		dewdrop:AddLine(
			'text', L["MENU_PLAY_NOISE"],
			'arg1', self,
			'func', "TogglePlayingNoise",
			'checked', self.db.profile.noise
		)
		
		dewdrop:AddLine(
			'text', L["MENU_TAKE_SCREENSHOTS"],
			'arg1', self,
			'func', "ToggleTakingScreenshots",
			'checked', self.db.profile.screenshot
		)
		
		dewdrop:AddLine(
			'text', L["MENU_RESET_SCORES"],
			'arg1', self,
			'func', "ResetScores",
			'closeWhenClicked', true
		)
		
		dewdrop:AddLine()
		
		dewdrop:AddLine(
			'text', L["MENU_FILTER"],
			'hasArrow', true,
			'value', "filter"
		)
		
		dewdrop:AddLine(
			'text', L["MENU_PURGE"],
			'hasArrow', next(self.db.char.hits),
			'value', "purge"
		)
	elseif level == 2 then
		if value == "filter" then
			dewdrop:AddLine(
				'text', L["MENU_VS_MONSTERS"],
				'arg1', self,
				'func', "ToggleOnlyPvP",
				'checked', not self.db.profile.onlyPvP
			)
			
			dewdrop:AddLine(
				'text', L["MENU_INCLUDE_HEALING"],
				'arg1', self,
				'func', "ToggleIncludingHeals",
				'checked', self.db.profile.includeHeals
			)
			
			dewdrop:AddLine(
				'text', L["MENU_INCLUDE_DAMAGE"],
				'arg1', self,
				'func', "ToggleIncludingDamage",
				'checked', self.db.profile.includeDamage
			)
			
			dewdrop:AddLine(
				'text', L["MENU_SHOW_TRIVIAL"],
				'arg1', self,
				'func', "ToggleShowingTrivial",
				'checked', self.db.profile.showTrivial
			)

			dewdrop:AddLine(
				'text', L["MENU_IGNORE_VULNERABLE"],
				'arg1', self,
				'func', "ToggleIgnoreVulnerable",
				'checked', self.db.profile.ignoreVulnerable
			)
			
			dewdrop:AddLine()
			
			for spell, t in pairs(self.db.char.hits) do
				dewdrop:AddLine(
					'text', spell,
					'arg1', self,
					'func', "ToggleFiltering",
					'arg2', spell,
					'checked', not self:IsFiltering(spell)
				)
			end
		elseif value == "purge" then
			for spell, t in pairs(self.db.char.hits) do
				dewdrop:AddLine(
					'text', spell,
					'arg1', self,
					'func', "Purge",
					'arg2', spell
				)
			end
		end
	end
end

local _,class = UnitClass("player")
function TopScoreFu:DoesSpellExist(spell)
	if spell == S["Attack"] then
		return true
	elseif class == "PALADIN" then
		if spell == S["Judgement of Light"] then
			spell = S["Seal of Light"]
		elseif spell == S["Judgement of Righteousness"] then
			spell = S["Seal of Righteousness"]
		elseif spell == S["Judgement of Wisdom"] then
			spell = S["Seal of Wisdom"]
		elseif spell == S["Judgement of Justice"] then
			spell = S["Seal of Justice"]
		elseif spell == S["Judgement of Command"] then
			spell = S["Seal of Command"]
		elseif spell == S["Judgement of the Crusader"] then
			spell = S["Seal of the Crusader"]
		end
	elseif class == "SHAMAN" then
		if spell == L["Windfury Attack"] then
			spell = S["Windfury Weapon"]
		elseif spell == L["Frostbrand Attack"] then
			spell = S["Frostbrand Weapon"]
		elseif spell == L["Flametongue Attack"] then
			spell = S["Flametongue Weapon"]
		end
	end
	local i = 1
	while true do
		local name = GetSpellName(i, "spell")
		if name == nil or name == "" then
			return false
		elseif name == spell then
			return true
		end
		i = i + 1
	end
end

local function copyTable(dest,source)
	for k,v in pairs(source) do
		dest[k] = v
	end
	setmetatable(dest, getmetatable(source))
end

function TopScoreFu:RecordHit(spell, amount, target, targetid, isCritical, isHeal, pvp)
	if UnitIsCharmed("player") then
		return
	end
	local alpha = string.find(spell, "%s*%(")
	if alpha ~= nil then
		spell = string.sub(spell, alpha - 1)
	end
	if self.db.char.hits[spell] == false then
		return
	elseif self.db.char.hits[spell] == nil then
		if not self:DoesSpellExist(spell) then
			self.db.char.hits[spell] = false
			return
		end
		self.db.char.hits[spell] = {
			crit = {},
			critPvP = {},
			normal = {},
			normalPvP = {},
			isHeal = isHeal,
		}
	end
	local t
	local tPvP
	if isCritical then
		t = self.db.char.hits[spell].crit
		tPvP = self.db.char.hits[spell].critPvP
	else
		t = self.db.char.hits[spell].normal
		tPvP = self.db.char.hits[spell].normalPvP
	end
	if t.amount == nil or t.amount < amount or (pvp and (tPvP.amount == nil or tPvP.amount < amount)) then
		if t.amount and t.amount > amount then
			-- this is a pvp record but not a pve record; don't overwrite the savedvars for pve
			-- hacky solution that creates an extra table :(
			t = {}
		end
		local level
		local class
		if not targetid then
			if UnitName("target") == target then
				targetid = "target"
			else
				return
			end
		end
		if targetid ~= nil then
			level = UnitLevel(targetid)
			_,class = UnitClass(targetid)
			if not self.db.profile.showTrivial and UnitIsTrivial(targetid) then
				return
			end
			if self:IsIgnoringVulnerable() and self:IsVulnerable(target) then
				return
			end
			t.amount = amount
			t.target = target
			t.level = level
			t.class = class
			if pvp and UnitPlayerControlled(targetid) then
				copyTable(tPvP, t)
			end
			if not self:IsFiltering(spell) and (not isHeal or self.db.profile.includeHeals) and (isHeal or self.db.profile.includeDamage) and (pvp or not self.db.profile.onlyPvP) then
				if MikSBT and self.db.profile.miksbt then
					if isCritical then
						MikSBT.DisplayMessage(string.format(L["PATTERN_NEW_CRITICAL_RECORD"], spell, amount),MikSBT.DISPLAYTYPE_NOTIFICATION,true,255,255,0);
					else
						MikSBT.DisplayMessage(string.format(L["PATTERN_NEW_NORMAL_RECORD"], spell, amount),MikSBT.DISPLAYTYPE_NOTIFICATION,false,255,255,0);
					end
				end
				if self.db.profile.splash then
					if isCritical then
						TopScoreFuSplash:AddMessage(string.format(L["PATTERN_NEW_CRITICAL_RECORD"], spell, amount), 1, 1, 0, 1, 3)
					else
						TopScoreFuSplash:AddMessage(string.format(L["PATTERN_NEW_NORMAL_RECORD"], spell, amount), 1, 1, 0, 1, 3)
					end
				end
				if self.db.profile.noise then
					PlaySound("LEVELUP")
				end
				if self.db.profile.screenshot then
					TakeScreenshot()
				end
				self:Update()
			end
		end
	end
end

function TopScoreFu:UpdateData()
	self.highCrit = 0
	self.highNormal = 0
	for spell, t in pairs(self.db.char.hits) do
		if t and not self:IsFiltering(spell) and (not t.isHeal or self.db.profile.includeHeals) and (t.isHeal or self.db.profile.includeDamage) then
			local crit = t.crit
			local normal = t.normal
			if self.db.profile.onlyPvP then
				crit = t.critPvP
				normal = t.normalPvP
			end
			if crit ~= nil and crit.amount ~= nil and self.highCrit < crit.amount then
				self.highCrit = crit.amount
			end
			if normal ~= nil and normal.amount ~= nil and self.highNormal < normal.amount then
				self.highNormal = normal.amount
			end
		end
	end
end

function TopScoreFu:UpdateText()
	self:SetText(string.format("|cffffffff%d|r/|cffffffff%d|r", self.highCrit, self.highNormal))
end

function TopScoreFu:OnTooltipUpdate()
	local addedHint = false
	for spell, t in pairs(self.db.char.hits) do
		if t and not self:IsFiltering(spell) and (not t.isHeal or self.db.profile.includeHeals) and (t.isHeal or self.db.profile.includeDamage) then
			local crit = t.crit
			local normal = t.normal
			if self.db.profile.onlyPvP then
				crit = t.critPvP
				normal = t.normalPvP
			end
			if (crit and crit.amount) or (normal and normal.amount) then
				local cat = tablet:AddCategory(
					'text', spell,
					'columns', 2,
					'child_textR', 1,
					'child_textG', 1,
					'child_textB', 0,
					'func', "SpamSingleScore",
					'arg1', self,
					'arg2', spell,
					'arg3', normal.amount or 0,
					'arg4', crit.amount or 0
				)
				
				if crit ~= nil and crit.amount ~= nil then
					local color = "ffffff"
					if crit.amount == self.highCrit then
						color = "00ff00"
					end
					local target = crit.target
					if target == "player" then
						target = UnitName("player")
					end
					local r2, g2, b2 = babbleClass:GetColor(crit.class)
					cat:AddLine(
						'text', L["TEXT_CRITICAL"] .. " [|cff" .. color .. crit.amount .. "|r]",
						'text2', target .. " [|cffffffff" .. crit.level .. "|r]",
						'text2R', r2,
						'text2G', g2,
						'text2B', b2
					)
				end
				if normal ~= nil and normal.amount ~= nil then
					local color = "ffffff"
					if normal.amount == self.highNormal then
						color = "00ff00"
					end
					local target = normal.target
					if target == "player" then
						target = UnitName("player")
					end
					local r2, g2, b2 = babbleClass:GetColor(normal.class)
					cat:AddLine(
						'text', L["TEXT_NORMAL"] .. " [|cff" .. color .. normal.amount .. "|r]",
						'text2', target .. " [|cffffffff" .. normal.level .. "|r]",
						'text2R', r2,
						'text2G', g2,
						'text2B', b2
					)
				end
			end
			if not addedHint then
				addedHint = true
				tablet:SetHint(L["HINT"])
			end
		end
	end
end
	
function TopScoreFu:OnClick()
	if IsShiftKeyDown() and ChatFrameEditBox:IsVisible() then
		local critSpell, critAmount, normalSpell, normalAmount
		self:UpdateData()
		for spell, t in pairs(self.db.char.hits) do
			if t and not self:IsFiltering(t) and (not t.isHeal or self.db.profile.includeHeals) and (t.isHeal or self.db.profile.includeDamage) then
				local crit = t.crit
				local normal = t.normal
				if self.db.profile.onlyPvP then
					crit = t.critPvP
					normal = t.normalPvP
				end
				if crit.amount == self.highCrit then
					critSpell = spell
					critAmount = crit.amount
				end
				if normal.amount == self.highNormal then
					normalSpell = spell
					normalAmount = normal.amount
				end
			end
		end
		local s = ""
		if normalSpell ~= nil then
			s = s .. string.format(L["PATTERN_NORMAL_SPELL"], normalSpell) .. ": " .. normalAmount
		end
		if critSpell ~= nil then
			if s ~= "" then
				s = s .. " || "
			end
			s = s .. string.format(L["PATTERN_CRITICAL_SPELL"], critSpell) .. ": " .. critAmount
		end
		ChatFrameEditBox:Insert(s)
	end
end

-- Credit to Aileen for this.
function TopScoreFu:IsVulnerable(target)
	-- Check for Heroic bosses first.
	if IsInInstance() and (GetInstanceDifficulty() == 2) then
		for _, heroicBoss in ipairs(heroicVulnerableBosses) do
			if target == heroicBoss then
				return true
			end
		end
	end
	-- Regular bosses check.
	for _, boss in ipairs(vulnerableBosses) do
		if target == boss then
			return true
		end
	end
	-- Vulnerable mobs check
	for _, mob in ipairs(L["VULNERABLE_MOBS"]) do
		if target == mob then
			return true
		end
	end
	return false
end

-- Credit to TheKarn for this function.
function TopScoreFu:SpamSingleScore(spell, normal, crit)
	if spell and (normal or crit) then
		local str = spell
		if normal ~= 0 then str = str.." - "..L["TEXT_NORMAL"]..": "..normal end
		if crit ~= 0 then str = str.." - "..L["TEXT_CRITICAL"]..": "..crit end
		if not ChatFrameEditBox:IsVisible() then ChatFrameEditBox:Show() end
		ChatFrameEditBox:Insert(str.." ")
	end
end
