TablePoolMonitor = class("TablePoolMonitor")

local function InfoFormat(fmt, ... )
	local msg = String.Format(fmt, ...)
	if LogUtility.traceEnable then
		msg = String.Format("{0}\n{1}", msg, debug.traceback())
	end
	Debug.Log(msg)
end

local function WarningFormat(fmt, ... )
	local msg = String.Format(fmt, ...)
	if LogUtility.traceEnable then
		msg = String.Format("{0}\n{1}", msg, debug.traceback())
	end
	Debug.LogWarning(msg)
end

local function ErrorFormat(fmt, ... )
	local msg = String.Format(fmt, ...)
	if LogUtility.traceEnable then
		msg = String.Format("{0}\n{1}", msg, debug.traceback())
	end
	Debug.LogError(msg)
end

function TablePoolMonitor.Me()
	if nil == TablePoolMonitor.me then
		TablePoolMonitor.me = TablePoolMonitor.new()
	end
	return TablePoolMonitor.me
end

function TablePoolMonitor:ctor()
	self.infos = {}
	self.removedInfos = {}
	self.AllPools = {}
	-- 最多允許5%不能放回池子
	self.MaxLeak = 0.05
	-- 每10s輸出一次
	self.PrintIntevals = 10
	self.lasttime = 0
	-- %s處為產生日期 mmddHHMM 格式
	self.PoolSizeLogFilePath = './Assets/Resources/PoolSizeLog%s.csv'
end

function TablePoolMonitor:GetTracebackInfo(level)
	local ar = nil
	while not ar do
		ar = debug.getinfo(level, "lnS")
		level = level - 1
	end
	return ar.short_src  .. ":" .. ar.currentline
end

function TablePoolMonitor:AddItem(tag, obj)
	if self.AutoSize then
		self:AutoSizePoolRemove(tag)
	end
	if self.checkPoolLeak then
		self:Add(obj)
	end
end

function TablePoolMonitor:RemoveItem(tag, obj)
	if self.AutoSize then
		self:AutoSizePoolAdd(tag)
	end
	if self.checkPoolLeak then
		self:Remove(obj)
	end
end

function TablePoolMonitor:Add(obj)
	local objInfo = {}
	setmetatable(objInfo, {__mode = "v"})
	objInfo[1] = obj

	local debugInfo = self:GetTracebackInfo(6)--debug.traceback()

	local info = {
		[1] = objInfo, 
		[2] = debugInfo
	}
	self.infos[#self.infos+1] = info

	-- InfoFormat("Table Created: obj={0}, debugInfo={1}", 
	-- 		obj,
	-- 		debugInfo)
end

function TablePoolMonitor:Remove(obj)
	for i=1, #self.infos do
		local info = self.infos[i]
		if obj == info[1][1] then
			if self.checkRemove then
				info[3] = self:GetTracebackInfo(6)--debug.traceback()
				self.removedInfos[#self.removedInfos+1] = info
			end
			table.remove(self.infos, i)
			-- InfoFormat("Table Remove: obj={0}, debugInfo={1}", 
			-- 	obj,
			-- 	info[2])
			break
		end
	end
end

function TablePoolMonitor:Check()
	collectgarbage("collect")

	local activeCount = 0

	for i=#self.removedInfos, 1, -1 do
		if self.checkRemove then
			local info = self.removedInfos[i]
			if nil ~= info[1][1] then
				ErrorFormat("Table Invalid Referenced: obj={0}\n\ncreate={1}\n\ndestroy={2}", 
					tostring(info[1][1]), info[2], info[3])
			end
		end
		self.removedInfos[i] = nil
	end

	for i=#self.infos, 1, -1 do
		local info = self.infos[i]
		if nil ~= info[1][1] then
			activeCount = activeCount + 1
		else
			if not self.checkRemove then
				local debugInfo = info[2]
				ErrorFormat("Table Leaked: debugInfo={0}", debugInfo)
			end
			table.remove(self.infos, i)
		end
	end

	return activeCount
end

function TablePoolMonitor:StartAutoSize()
	self.AutoSizeStartTime = os.date("%m-%d %H:%M", os.time())
	self.AutoSize = true
end

function TablePoolMonitor:StopAutoSize()
	self:ReportAutoSize()
	self.AutoSize = false
	self.AllPools = {}
end

function TablePoolMonitor:ReportAutoSize()
	if self.AutoSize then
		-- 標題為採樣開始時間=>報告時間 mm-dd HH:MM => mm-dd HH:MM
		local time = os.time()
		local date = os.date("%m-%d %H:%M", time)
		local title = self.AutoSizeStartTime .. " => " .. date .. "\n"
		local str = title .. "Key,AddTimes,MaxPoolSize,PoolSize\n"
		for tag,v in pairs(self.AllPools) do
			str = str .. v:PrintSelf(tag)
		end
		self:SavePoolSizeLogFile(os.date("%m%d%H%M", time), str)
	end
end

function TablePoolMonitor:CreateAutoPool()
	return 
	{
		CurrentCount = 0,
		RealCurrent = 0,
		PoolSize = 0,
		MaxPoolSize = 0,
		PoolLeak = {},
		AddTimes = 0, 
		PrintSelf = function(v, tag)
			local key = TablePoolMonitor.GetKeyByTag(tag)
			local str = string.format("%s,%s,%s,%s\n", (type(key) == "table" and key.__cname or tostring(key)), v.AddTimes, v.MaxPoolSize, v.PoolSize)
			-- Debug.LogWarning(str)
			return str
		end
	}
end

function TablePoolMonitor:GetAutoPoolByTag(tag)
	if not self.AllPools[tag] then
		self.AllPools[tag] = self:CreateAutoPool()
	end
	return self.AllPools[tag]
end

function TablePoolMonitor.GetKeyByTag(tag)
	for k,v in pairs(ReusableTable.pool.pool) do
		if v == tag then return k end
	end
	for k,v in pairs(ReusableObject.pool.pool) do
		if v == tag then return k end
	end
end

function TablePoolMonitor:SavePoolSizeLogFile(date, contents)
	local path = string.format(self.PoolSizeLogFilePath, date)
	local f = io.open(path, 'w+')
	f:write(contents)
	f:close()
end

function TablePoolMonitor:ExpandAutoPoolSize(autopool)
	autopool.PoolSize = autopool.PoolSize * 1.1 + 10
end

function TablePoolMonitor:RemoveRangeAutoPoolLeak(autopool)
	-- 升序排列
	table.sort( autopool.PoolLeak, function(a, b) return a < b end )
	-- 小於autopool.PoolSize的被刪除
	for i=1,#autopool.PoolLeak do
		if autopool.PoolLeak[i] >= autopool.PoolSize then
			for k=i, #autopool.PoolLeak do
				table.remove(autopool.PoolLeak)
			end
			break
		end
	end
end

function TablePoolMonitor:ReSizeAutoPool(autopool)
	self:ExpandAutoPoolSize(autopool)
	self:RemoveRangeAutoPoolLeak(autopool)
end

function TablePoolMonitor:AutoSizePoolAdd(tag)
	if tag then
		local autopool = self:GetAutoPoolByTag(tag)
		if autopool.RealCurrent >= autopool.MaxPoolSize then
			autopool.MaxPoolSize = autopool.MaxPoolSize + 1
		end
		autopool.AddTimes = autopool.AddTimes + 1
		autopool.RealCurrent = autopool.RealCurrent + 1

		if autopool.CurrentCount >= autopool.PoolSize then
			table.insert(autopool.PoolLeak, autopool.MaxPoolSize)
			if #autopool.PoolLeak / autopool.AddTimes > self.MaxLeak then
				self:ReSizeAutoPool(autopool)
			end
		else
			autopool.CurrentCount = autopool.CurrentCount + 1
		end
	end
end

function TablePoolMonitor:AutoSizePoolRemove(tag)
	if tag then
		local autopool = self:GetAutoPoolByTag(tag)
		if autopool.CurrentCount > 0 then
			autopool.CurrentCount = autopool.CurrentCount - 1
		end
		if autopool.RealCurrent > 0 then
			autopool.RealCurrent = autopool.RealCurrent - 1
		end
	end
end