local L = AceLibrary("AceLocale-2.0"):new("FuBar_PerformanceFu")
local Tablet = AceLibrary("Tablet-2.0")
local Abacus = AceLibrary("Abacus-2.0")
local Crayon = AceLibrary("Crayon-2.0")

PerformanceFu = AceLibrary("AceAddon-2.0"):new("FuBarPlugin-2.0", "AceConsole-2.0", "AceEvent-2.0", "AceDB-2.0")

PerformanceFu.version = "2.0" .. string.sub("$Revision: 7559 $", 12, -3)
PerformanceFu.date = string.sub("$Date: 2006-08-10 04:43:29 -0400 (Thu, 10 Aug 2006) $", 8, 17)
PerformanceFu.hasIcon = true

local string_format = string.format
local table_concat = table.concat
local table_insert = table.insert
local table_setn = table.setn

PerformanceFu:RegisterDB("PerformanceFuDB")
PerformanceFu:RegisterDefaults("profile", {
	showFramerate = true,
	showLatency = true,
	showMemory = true,
	showRate = true,
	warnGC = false,
})

function PerformanceFu:IsShowingFramerate()
	return self.db.profile.showFramerate
end

function PerformanceFu:ToggleShowingFramerate()
	self.db.profile.showFramerate = not self.db.profile.showFramerate
	self:Update()
end

function PerformanceFu:IsShowingLatency()
	return self.db.profile.showLatency
end

function PerformanceFu:ToggleShowingLatency()
	self.db.profile.showLatency = not self.db.profile.showLatency
	self:Update()
end

function PerformanceFu:IsShowingRate()
	return self.db.profile.showRate
end

function PerformanceFu:ToggleShowingRate()
	self.db.profile.showRate = not self.db.profile.showRate
	self:Update()
end

function PerformanceFu:IsShowingMemory()
	return self.db.profile.showMemory
end

function PerformanceFu:ToggleShowingMemory()
	self.db.profile.showMemory = not self.db.profile.showMemory
	self:Update()
end

function PerformanceFu:IsWarningOnGC()
	return self.db.profile.warnGC
end

function PerformanceFu:ToggleWarningOnGC()
	self.db.profile.warnGC = not self.db.profile.warnGC
end

local beeps5 = 0
local beeps15 = 0
local beeps30 = 0
local beeps60 = 0
local beeps180 = 0
local beeps600 = 0

local initialMemory, gcThreshold, currentMemory
local mem1, mem2, mem3, mem4, mem5, mem6, mem7, mem8, mem9, mem10
local timeSinceLastUpdate
local justEntered
local gcTime

function PerformanceFu:OnEnable()
	initialMemory, gcThreshold = gcinfo()
	currentMemory = initialMemory
	mem1 = currentMemory
	mem2 = currentMemory
	mem3 = currentMemory
	mem4 = currentMemory
	mem5 = currentMemory
	mem6 = currentMemory
	mem7 = currentMemory
	mem8 = currentMemory
	mem9 = currentMemory
	mem10 = currentMemory
	timeSinceLastUpdate = 0
	gcTime = time()
	justEntered = true
	self:ScheduleRepeatingEvent(self.OnUpdate, 1, self)
end

local options = {
	type = 'group',
	args = {
		framerate = {
			type = 'toggle',
			name = L"Show framerate",
			desc = L"Toggle whether to framerate",
			get = "IsShowingFramerate",
			set = "ToggleShowingFramerate",
		},
		latency = {
			type = 'toggle',
			name = L"Show latency",
			desc = L"Toggle whether to latency (lag)",
			get = "IsShowingLatency",
			set = "ToggleShowingLatency",
		},
		memory = {
			type = 'toggle',
			name = L"Show memory usage",
			desc = L"Toggle whether to show current memory usage",
			get = "IsShowingMemory",
			set = "ToggleShowingMemory",
		},
		rate = {
			type = 'toggle',
			name = L"Show rate of increasing memory usage",
			desc = L"Toggle whether to show increasing rate of memory",
			get = "IsShowingRate",
			set = "ToggleShowingRate",
		},
		warn = {
			type = 'toggle',
			name = L"Warn on garbage collection",
			desc = L"Toggle whether to warn on an upcoming garbage collection",
			get = "IsWarningOnGC",
			set = "ToggleWarningOnGC",
		},
		forcegc = {
			type = 'execute',
			name = L"Force garbage collection",
			desc = L"Force a garbage collection to happen",
			order = 101,
			func = collectgarbage
		}
	}
}
PerformanceFu:RegisterChatCommand(L:GetTable("AceConsole-options"), options)
PerformanceFu.OnMenuRequest = options

local gccheck = setmetatable({[{}]=true}, {__mode = "k"})

function PerformanceFu:OnUpdate()
	if justEntered then
		if not timeSinceLastUpdate then
			timeSinceLastUpdate = 0
		end
		timeSinceLastUpdate = timeSinceLastUpdate + 1
		if timeSinceLastUpdate >= 10 then
			initialMemory, gcThreshold = gcinfo()
			currentMemory = initialMemory
			mem1 = currentMemory
			mem2 = currentMemory
			mem3 = currentMemory
			mem4 = currentMemory
			mem5 = currentMemory
			mem6 = currentMemory
			mem7 = currentMemory
			mem8 = currentMemory
			mem9 = currentMemory
			mem10 = currentMemory
			timeSinceLastUpdate = nil
			gcTime = time()
			justEntered = false
		end
	else
		local t = time()
		timeSinceLastUpdate = 0
		mem1, mem2, mem3, mem4, mem5, mem6, mem7, mem8, mem9, mem10 =
			currentMemory, mem1, mem2, mem3, mem4, mem5, mem6, mem7, mem8, mem9
		currentMemory, gcThreshold = gcinfo()
		if not next(gccheck) then
			gccheck[{}] = true
			initialMemory = currentMemory
			gcTime = t
			mem10, mem9, mem9, mem8, mem7, mem6, mem5, mem4, mem3, mem2, mem1 =
				currentMemory, currentMemory, currentMemory, currentMemory, currentMemory, currentMemory, currentMemory, currentMemory, currentMemory, currentMemory
			beeps5 = 0
			beeps5 = 0
			beeps15 = 0
			beeps30 = 0
			beeps60 = 0
			beeps180 = 0
			beeps600 = 0
			if self:IsWarningOnGC() then
				self:Print(L"Garbage collection occurred")
			end
		end
		local averageRate = (currentMemory - initialMemory) / (t - gcTime)
		local totalSecs = (gcThreshold - currentMemory) / averageRate
		if self:IsWarningOnGC() then
			if totalSecs <= 5 and beeps5 < t - 15 then
				self:Print(L"Garbage collection in %s", Abacus:FormatDurationShort(totalSecs, false, true))
				beeps600 = t
				beeps180 = t
				beeps60 = t
				beeps30 = t
				beeps15 = t
				beeps5 = t
			elseif totalSecs <= 15 and beeps15 < t - 30 then
				self:Print(L"Garbage collection in %s", Abacus:FormatDurationShort(totalSecs, false, true))
				beeps600 = t
				beeps180 = t
				beeps60 = t
				beeps30 = t
				beeps15 = t
			elseif totalSecs <= 30 and beeps30 < t - 45 then
				self:Print(L"Garbage collection in %s", Abacus:FormatDurationShort(totalSecs, false, true))
				beeps600 = t
				beeps180 = t
				beeps60 = t
				beeps30 = t
			elseif totalSecs <= 60 and beeps60 < t - 90 then
				self:Print(L"Garbage collection in %s", Abacus:FormatDurationShort(totalSecs, false, true))
				beeps600 = t
				beeps180 = t
				beeps60 = t
			elseif totalSecs <= 180 and beeps180 < t - 270 then
				self:Print(L"Garbage collection in %s", Abacus:FormatDurationShort(totalSecs, false, true))
				beeps600 = t
				beeps180 = t
			elseif totalSecs <= 600 and beeps600 < t - 900 then
				self:Print(L"Garbage collection in %s", Abacus:FormatDurationShort(totalSecs, false, true))
				beeps600 = t
			end
		end
		self:Update()
	end
end

local t = {}
function PerformanceFu:OnTextUpdate()
	if not mem10 then
		mem10 = currentMemory
	end
	local currentRate = (currentMemory - mem10) / 10
	
	if self:IsShowingFramerate() then
		local framerate = floor(GetFramerate() + 0.5)
		table_insert(t, format("|cff%s%d|r fps", Crayon:GetThresholdHexColor(framerate / 60), framerate))
	end
	if self:IsShowingLatency() then
		local _,_,latency = GetNetStats()
		table_insert(t, format("|cff%s%d|r ms", Crayon:GetThresholdHexColor(latency, 1000, 500, 250, 100, 0), latency))
	end
	if self:IsShowingMemory() then
		table_insert(t, format("|cff%s%.1f|r MiB", Crayon:GetThresholdHexColor(currentMemory, 51200, 40960, 30520, 20480, 10240), currentMemory / 1024))
	end
	if self:IsShowingRate() then
		table_insert(t, format("|cff%s%.1f|r KiB/s", Crayon:GetThresholdHexColor(currentRate, 30, 10, 3, 1, 0), currentRate))
	end
	self:SetText(table_concat(t, " "))
	for k in pairs(t) do
		t[k] = nil
		k = nil
	end
	table_setn(t, 0)
end

function PerformanceFu:OnTooltipUpdate()
	local cat = Tablet:AddCategory(
		'columns', 2,
		'child_textR', 1,
		'child_textG', 1,
		'child_textB', 0
	)
	local framerate = GetFramerate()
	local r, g, b = Crayon:GetThresholdColor(framerate / 60)
	cat:AddLine(
		'text', L"Framerate:",
		'text2', string_format("%.1f fps", framerate),
		'text2R', r,
		'text2G', g,
		'text2B', b
	)
	
	cat = Tablet:AddCategory(
		'text', L"Network status",
		'columns', 2,
		'child_textR', 1,
		'child_textG', 1,
		'child_textB', 0,
		'child_text2R', 1,
		'child_text2G', 1,
		'child_text2B', 1
	)
	local bandwidthIn, bandwidthOut, latency = GetNetStats()
	bandwidthIn, bandwidthOut = bandwidthIn * 1024, bandwidthOut * 1024
	local r, g, b = Crayon:GetThresholdColor(latency, 3000, 1000, 250, 100, 0)
	cat:AddLine(
		'text', L"Latency:",
		'text2', string_format("%d ms", latency),
		'text2R', r,
		'text2G', g,
		'text2B', b
	)
	
	cat:AddLine(
		'text', L"Bandwidth in:",
		'text2', string_format("%d B/s", bandwidthIn)
	)
	
	cat:AddLine(
		'text', L"Bandwidth out:",
		'text2', string_format("%d B/s", bandwidthOut)
	)
	
	local averageRate = (currentMemory - initialMemory) / (time() - gcTime)
	cat = Tablet:AddCategory(
		'text', L"Memory usage",
		'columns', 2,
		'child_textR', 1,
		'child_textG', 1,
		'child_textB', 0,
		'child_text2R', 1,
		'child_text2G', 1,
		'child_text2B', 1
	)
	local currentRate = (currentMemory - mem10) / 10
	
	local r, g, b = Crayon:GetThresholdColor(currentMemory, 51200, 40960, 30520, 20480, 10240)
	cat:AddLine(
		'text', L"Current memory:",
		'text2', string_format("%.1f MiB", currentMemory / 1024),
		'text2R', r,
		'text2G', g,
		'text2B', b
	)
	
	r, g, b = Crayon:GetThresholdColor(initialMemory, 51200, 40960, 30520, 20480, 10240)
	cat:AddLine(
		'text', L"Initial memory:",
		'text2', string_format("%.1f MiB", initialMemory / 1024),
		'text2R', r,
		'text2G', g,
		'text2B', b
	)
	
	r, g, b = Crayon:GetThresholdColor(currentRate, 30, 10, 3, 1, 0)
	cat:AddLine(
		'text', L"Increasing rate:",
		'text2', string_format("%.1f KiB/s", currentRate),
		'text2R', r,
		'text2G', g,
		'text2B', b
	)
	
	r, g, b = Crayon:GetThresholdColor(averageRate, 30, 10, 3, 1, 0)
	cat:AddLine(
		'text', L"Average increasing rate:",
		'text2', format("%.1f KiB/s", averageRate),
		'text2R', r,
		'text2G', g,
		'text2B', b
	)
	
	cat = Tablet:AddCategory(
		'text', L"Garbage collection",
		'columns', 2,
		'child_textR', 1,
		'child_textG', 1,
		'child_textB', 0,
		'child_text2R', 1,
		'child_text2G', 1,
		'child_text2B', 1
	)
	
	local totalSecs = (gcThreshold - currentMemory) / averageRate
	local timeToNext = Abacus:FormatDurationFull(totalSecs)
	
	local r, g, b = Crayon:GetThresholdColor(gcThreshold, 51200, 40960, 30520, 20480, 10240)
	cat:AddLine(
		'text', L"Threshold:",
		'text2', format("%.1f MiB", gcThreshold / 1024),
		'text2R', r,
		'text2G', g,
		'text2B', b
	)
	
	local r, g, b = Crayon:GetThresholdColor(totalSecs, 0, 900, 1800, 2700, 3600)
	cat:AddLine(
		'text', L"Time to next:",
		'text2', timeToNext,
		'text2R', r,
		'text2G', g,
		'text2B', b
	)
end
