local compost = CompostLib:GetInstance('compost-1')
local tablet = TabletLib:GetInstance('1.0')
local dewdrop = DewdropLib:GetInstance('1.0')
local metro = Metrognome:GetInstance('1')

PerformanceFu = FuBarPlugin:new({
	name          = PerformanceFuLocals.NAME,
	description   = PerformanceFuLocals.DESCRIPTION,
	version       = "1.1." .. string.sub("$Revision: 1198 $", 12, -3),
	releaseDate   = string.sub("$Date: 2006-04-30 04:31:40 -0500 (Sun, 30 Apr 2006) $", 8, 17),
	aceCompatible = 103,
	fuCompatible  = 102,
	author        = "Boss",
	email         = "ckknight@gmail.com",
	website       = "http://boss.wowinterface.com/",
	category      = "interface",
	db            = AceDatabase:new("PerformanceFuDB"),
	defaults      = {
		showFramerate = true,
		showLatency = true,
		showMemory = true,
		showRate = true,
		warnGC = false,
	},
	cmd           = AceChatCmd:new(PerformanceFuLocals.COMMANDS, PerformanceFuLocals.CMD_OPTIONS),
	loc           = PerformanceFuLocals,
	hasIcon       = true
})
function PerformanceFu:IsShowingFramerate()
	return self.data.showFramerate
end

function PerformanceFu:ToggleShowingFramerate(loud)
	self.data.showFramerate = not self.data.showFramerate
	if loud then
		self.cmd:status(self.loc.ARGUMENT_FRAMERATE, self.data.showFramerate and 1 or 0, FuBarLocals.MAP_ONOFF)
	end
	self:Update()
	return self.data.showFramerate
end

function PerformanceFu:IsShowingLatency()
	return self.data.showLatency
end

function PerformanceFu:ToggleShowingLatency(loud)
	self.data.showLatency = not self.data.showLatency
	if loud then
		self.cmd:status(self.loc.ARGUMENT_LATENCY, self.data.showLatency and 1 or 0, FuBarLocals.MAP_ONOFF)
	end
	self:Update()
	return self.data.showLatency
end

function PerformanceFu:IsShowingRate()
	return self.data.showRate
end

function PerformanceFu:ToggleShowingRate(loud)
	self.data.showRate = not self.data.showRate
	if loud then
		self.cmd:status(self.loc.ARGUMENT_SHOWRATE, self.data.showRate and 1 or 0, FuBarLocals.MAP_ONOFF)
	end
	self:Update()
	return self.data.showRate
end

function PerformanceFu:IsShowingMemory()
	return self.data.showMemory
end

function PerformanceFu:ToggleShowingMemory(loud)
	self.data.showMemory = not self.data.showMemory
	if loud then
		self.cmd:status(self.loc.ARGUMENT_MEMORY, self.data.showMemory and 1 or 0, FuBarLocals.MAP_ONOFF)
	end
	self:Update()
	return self.data.showMemory
end

function PerformanceFu:IsWarningOnGC()
	return self.data.warnGC
end

function PerformanceFu:ToggleWarningOnGC(loud)
	self.data.warnGC = not self.data.warnGC
	if loud then
		self.cmd:status(self.loc.ARGUMENT_WARNGC, self.data.warnGC and 1 or 0, FuBarLocals.MAP_ONOFF)
	end
	return self.data.warnGC
end

function PerformanceFu:Initialize()
	self.beeps = {
		[5] = 0,
		[15] = 0,
		[30] = 0,
		[60] = 0,
		[180] = 0,
		[600] = 0,
	}
end

function PerformanceFu:Enable()
	self.initialMemory, self.gcThreshold = gcinfo()
	self.currentMemory = self.initialMemory
	self.mem1 = self.currentMemory
	self.mem2 = self.currentMemory
	self.mem3 = self.currentMemory
	self.mem4 = self.currentMemory
	self.mem5 = self.currentMemory
	self.mem6 = self.currentMemory
	self.mem7 = self.currentMemory
	self.mem8 = self.currentMemory
	self.mem9 = self.currentMemory
	self.mem10 = self.currentMemory
	self.timeSinceLastUpdate = 0
	self.gcTime = time()
	self.justEntered = TRUE
	metro:Register(self.name, self.OnUpdate, 1, self)
	metro:Start(self.name)
end

function PerformanceFu:Disable()
	metro:Unregister(self.name)
end

function PerformanceFu:Report()
	local report = compost:Acquire(
		compost:AcquireHash(
			'text', self.loc.ARGUMENT_FRAMERATE,
			'val', self:IsShowingFramerate() and 1 or 0,
			'map', FuBarLocals.MAP_ONOFF
		),
		compost:AcquireHash(
			'text', self.loc.ARGUMENT_LATENCY,
			'val', self:IsShowingLatency() and 1 or 0,
			'map', FuBarLocals.MAP_ONOFF
		),
		compost:AcquireHash(
			'text', self.loc.ARGUMENT_MEMORY,
			'val', self:IsShowingMemory() and 1 or 0,
			'map', FuBarLocals.MAP_ONOFF
		),
		compost:AcquireHash(
			'text', self.loc.ARGUMENT_SHOWRATE,
			'val', self:IsShowingRate() and 1 or 0,
			'map', FuBarLocals.MAP_ONOFF
		),
		compost:AcquireHash(
			'text', self.loc.ARGUMENT_WARNGC,
			'val', self:IsWarningOnGC() and 1 or 0,
			'map', FuBarLocals.MAP_ONOFF
		)
	)
	self.cmd:report(report)
	compost:Reclaim(report, 1)
end

function PerformanceFu:MenuSettings(level, value)
	if level == 1 then
		dewdrop:AddLine(
			'text', self.loc.MENU_SHOW_FRAMERATE,
			'arg1', self,
			'func', "ToggleShowingFramerate",
			'checked', self:IsShowingFramerate()
		)
		
		dewdrop:AddLine(
			'text', self.loc.MENU_SHOW_LATENCY,
			'arg1', self,
			'func', "ToggleShowingLatency",
			'checked', self:IsShowingLatency()
		)
		
		dewdrop:AddLine(
			'text', self.loc.MENU_SHOW_MEMORY,
			'arg1', self,
			'func', "ToggleShowingMemory",
			'checked', self:IsShowingMemory()
		)
		
		dewdrop:AddLine(
			'text', self.loc.MENU_WARN_ON_GC,
			'arg1', self,
			'func', "ToggleWarningOnGC",
			'checked', self:IsWarningOnGC()
		)
		
		dewdrop:AddLine(
			'text', self.loc.MENU_SHOW_INCREASING_MEMORY_USAGE,
			'arg1', self,
			'func', "ToggleShowingRate",
			'checked', self:IsShowingRate()
		)
		
		dewdrop:AddLine()
		
		dewdrop:AddLine(
			'text', self.loc.MENU_FORCE_GC,
			'arg1', self,
			'func', "ForceGC"
		)
	end
end

function PerformanceFu:ForceGC()
	local STRING_LIMIT = 1200
	local current, limit = gcinfo()
	local sMin, sMax = 200, 160000
	local junkSize = (limit - current) * 1024 - STRING_LIMIT
	while junkSize >= sMin do
		if junkSize > sMax then
			junkSize = sMax
			sMax = sMax + 1
		else
			sMin = sMin + 1
		end
		string.rep('!', junkSize)
		current, limit = gcinfo()
		junkSize = (limit - current) * 1024 - STRING_LIMIT
	end
	repeat
		limit, current = current, {}
		current = gcinfo()
	until current < limit
end

function PerformanceFu:OnUpdate(difference)
	if self.justEntered then
		if self.timeSinceLastUpdate == nil then
			self.timeSinceLastUpdate = 0
		end
		self.timeSinceLastUpdate = self.timeSinceLastUpdate + difference
		if self.timeSinceLastUpdate >= 10 then
			self.initialMemory, self.gcThreshold = gcinfo()
			self.currentMemory = self.initialMemory
			self.mem1 = self.currentMemory
			self.mem2 = self.currentMemory
			self.mem3 = self.currentMemory
			self.mem4 = self.currentMemory
			self.mem5 = self.currentMemory
			self.mem6 = self.currentMemory
			self.mem7 = self.currentMemory
			self.mem8 = self.currentMemory
			self.mem9 = self.currentMemory
			self.mem10 = self.currentMemory
			self.timeSinceLastUpdate = nil
			self.gcTime = time()
			self.justEntered = FALSE
		end
	else
		local t = time()
		self.timeSinceLastUpdate = 0
		self.mem1, self.mem2, self.mem3, self.mem4, self.mem5, self.mem6, self.mem7, self.mem8, self.mem9, self.mem10 =
			self.currentMemory, self.mem1, self.mem2, self.mem3, self.mem4, self.mem5, self.mem6, self.mem7, self.mem8, self.mem9
		self.currentMemory, self.gcThreshold = gcinfo()
		if self.mem1 > self.currentMemory then
			self.initialMemory = self.currentMemory
			self.gcTime = t
			self.mem10, self.mem9, self.mem9, self.mem8, self.mem7, self.mem6, self.mem5, self.mem4, self.mem3, self.mem2, self.mem1 =
				self.currentMemory, self.currentMemory, self.currentMemory, self.currentMemory, self.currentMemory, self.currentMemory, self.currentMemory, self.currentMemory, self.currentMemory, self.currentMemory
			self.beeps[5] = 0
			self.beeps[5] = 0
			self.beeps[15] = 0
			self.beeps[30] = 0
			self.beeps[60] = 0
			self.beeps[180] = 0
			self.beeps[600] = 0
			if self:IsWarningOnGC() then
				self.cmd:msg(self.loc.TEXT_GARBAGE_COLLECTION_OCCURED)
			end
		end
		local averageRate = (self.currentMemory - self.initialMemory) / (t - self.gcTime)
		local totalSecs = (self.gcThreshold - self.currentMemory) / averageRate
		if self:IsWarningOnGC() then
			if totalSecs <= 5 and self.beeps[5] < t - 15 then
				self.cmd:msg(self.loc.PATTERN_GARBAGE_COLLECTION_IN_TIME, FuBarUtils.FormatDurationShort(totalSecs, FALSE, TRUE))
				self.beeps[600] = t
				self.beeps[180] = t
				self.beeps[60] = t
				self.beeps[30] = t
				self.beeps[15] = t
				self.beeps[5] = t
			elseif totalSecs <= 15 and self.beeps[15] < t - 30 then
				self.cmd:msg(self.loc.PATTERN_GARBAGE_COLLECTION_IN_TIME, FuBarUtils.FormatDurationShort(totalSecs, FALSE, TRUE))
				self.beeps[600] = t
				self.beeps[180] = t
				self.beeps[60] = t
				self.beeps[30] = t
				self.beeps[15] = t
			elseif totalSecs <= 30 and self.beeps[30] < t - 45 then
				self.cmd:msg(self.loc.PATTERN_GARBAGE_COLLECTION_IN_TIME, FuBarUtils.FormatDurationShort(totalSecs, FALSE, TRUE))
				self.beeps[600] = t
				self.beeps[180] = t
				self.beeps[60] = t
				self.beeps[30] = t
			elseif totalSecs <= 60 and self.beeps[60] < t - 90 then
				self.cmd:msg(self.loc.PATTERN_GARBAGE_COLLECTION_IN_TIME, FuBarUtils.FormatDurationShort(totalSecs, FALSE, TRUE))
				self.beeps[600] = t
				self.beeps[180] = t
				self.beeps[60] = t
			elseif totalSecs <= 180 and self.beeps[180] < t - 270 then
				self.cmd:msg(self.loc.PATTERN_GARBAGE_COLLECTION_IN_TIME, FuBarUtils.FormatDurationShort(totalSecs, FALSE, TRUE))
				self.beeps[600] = t
				self.beeps[180] = t
			elseif totalSecs <= 600 and self.beeps[600] < t - 900 then
				self.cmd:msg(self.loc.PATTERN_GARBAGE_COLLECTION_IN_TIME, FuBarUtils.FormatDurationShort(totalSecs, FALSE, TRUE))
				self.beeps[600] = t
			end
		end
		self:Update()
	end
end

function PerformanceFu:UpdateText()
	if self.mem10 == nil then
		self.mem10 = self.currentMemory
	end
	local currentRate = (self.currentMemory - self.mem10) / 10
	
	local s = ""
	if self:IsShowingFramerate() then
		local framerate = floor(GetFramerate() + 0.5)
		s = s .. format("|cff%s%d|r fps", FuBarUtils.GetThresholdHexColor(framerate / 60), framerate)
	end
	if self:IsShowingLatency() then
		local _,_,latency = GetNetStats()
		if string.len(s) > 0 then
			s = s .. " "
		end
		s = s .. format("|cff%s%d|r ms", FuBarUtils.GetThresholdHexColor(latency, 1000, 500, 250, 100, 0), latency)
	end
	if self:IsShowingMemory() then
		if string.len(s) > 0 then
			s = s .. " "
		end
		s = s .. format("|cff%s%.1f|r MiB", FuBarUtils.GetThresholdHexColor(self.currentMemory, 51200, 40960, 30520, 20480, 10240), self.currentMemory / 1024)
	end
	if self:IsShowingRate() then
		if string.len(s) > 0 then
			s = s .. " "
		end
		s = s .. format("|cff%s%.1f|r KiB/s", FuBarUtils.GetThresholdHexColor(currentRate, 30, 10, 3, 1, 0), currentRate)
	end
	self:SetText(s)
end

function PerformanceFu:UpdateTooltip()
	local cat = tablet:AddCategory(
		'columns', 2,
		'child_textR', 1,
		'child_textG', 1,
		'child_textB', 0
	)
	local framerate = floor(GetFramerate() + 0.5)
	local r, g, b = FuBarUtils.GetThresholdColor(framerate / 60)
	cat:AddLine(
		'text', self.loc.TEXT_FRAMERATE .. ":",
		'text2', framerate .. " fps",
		'text2R', r,
		'text2G', g,
		'text2B', b
	)
	
	cat = tablet:AddCategory(
		'text', self.loc.TEXT_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 = floor(bandwidthIn * 1024), floor(bandwidthOut * 1024)
	local r, g, b = FuBarUtils.GetThresholdColor(latency, 3000, 1000, 250, 100, 0)
	cat:AddLine(
		'text', self.loc.TEXT_LATENCY .. ":",
		'text2', latency .. " ms",
		'text2R', r,
		'text2G', g,
		'text2B', b
	)
	
	cat:AddLine(
		'text', self.loc.TEXT_BANDWIDTH_IN .. ":",
		'text2', bandwidthIn .. " B/s"
	)
	
	cat:AddLine(
		'text', self.loc.TEXT_BANDWIDTH_OUT .. ":",
		'text2', bandwidthOut .. " B/s"
	)
	
	local averageRate = (self.currentMemory - self.initialMemory) / (time() - self.gcTime)
	cat = tablet:AddCategory(
		'text', self.loc.TEXT_MEMORY_USAGE,
		'columns', 2,
		'child_textR', 1,
		'child_textG', 1,
		'child_textB', 0,
		'child_text2R', 1,
		'child_text2G', 1,
		'child_text2B', 1
	)
	local currentRate = (self.currentMemory - self.mem10) / 10
	
	local r, g, b = FuBarUtils.GetThresholdColor(self.currentMemory, 51200, 40960, 30520, 20480, 10240)
	cat:AddLine(
		'text', self.loc.TEXT_CURRENT_MEMORY .. ":",
		'text2', format("%.1f MiB", self.currentMemory / 1024),
		'text2R', r,
		'text2G', g,
		'text2B', b
	)
	
	r, g, b = FuBarUtils.GetThresholdColor(self.initialMemory, 51200, 40960, 30520, 20480, 10240)
	cat:AddLine(
		'text', self.loc.TEXT_INITIAL_MEMORY .. ":",
		'text2', format("%.1f MiB", self.initialMemory / 1024),
		'text2R', r,
		'text2G', g,
		'text2B', b
	)
	
	r, g, b = FuBarUtils.GetThresholdColor(currentRate, 30, 10, 3, 1, 0)
	cat:AddLine(
		'text', self.loc.TEXT_CURRENT_INCREASING_RATE .. ":",
		'text2', format("%.1f KiB/s", currentRate),
		'text2R', r,
		'text2G', g,
		'text2B', b
	)
	
	r, g, b = FuBarUtils.GetThresholdColor(averageRate, 30, 10, 3, 1, 0)
	cat:AddLine(
		'text', self.loc.TEXT_AVERAGE_INCREASING_RATE .. ":",
		'text2', format("%.1f KiB/s", averageRate),
		'text2R', r,
		'text2G', g,
		'text2B', b
	)
	
	cat = tablet:AddCategory(
		'text', self.loc.TEXT_GARBAGE_COLLECTION,
		'columns', 2,
		'child_textR', 1,
		'child_textG', 1,
		'child_textB', 0,
		'child_text2R', 1,
		'child_text2G', 1,
		'child_text2B', 1
	)
	
	local totalSecs = (self.gcThreshold - self.currentMemory) / averageRate
	local timeToNext = FuBarUtils.FormatDurationFull(totalSecs)
	
	local r, g, b = FuBarUtils.GetThresholdColor(self.gcThreshold, 51200, 40960, 30520, 20480, 10240)
	cat:AddLine(
		'text', self.loc.TEXT_THRESHOLD .. ":",
		'text2', format("%.1f MiB", self.gcThreshold / 1024),
		'text2R', r,
		'text2G', g,
		'text2B', b
	)
	
	local r, g, b = FuBarUtils.GetThresholdColor(totalSecs, 0, 900, 1800, 2700, 3600)
	cat:AddLine(
		'text', self.loc.TEXT_TIME_TO_NEXT .. ":",
		'text2', timeToNext,
		'text2R', r,
		'text2G', g,
		'text2B', b
	)
end

PerformanceFu:RegisterForLoad()