-- ------------------------------------------------------------------------------ --
--                                TradeSkillMaster                                --
--                          https://tradeskillmaster.com                          --
--    All Rights Reserved - Detailed license information included with addon.     --
-- ------------------------------------------------------------------------------ --

local TSM_NAME, TSM = ...
local Analytics = TSM.Include("Util.Analytics")
local Event = TSM.Include("Util.Event")
local Log = TSM.Include("Util.Log")
local LibTSMClass = TSM.Include("LibTSMClass")
local private = {
	eventFrames = {},
	initializeQueue = {},
	enableQueue = {},
	disableQueue = {},
	totalInitializeTime = 0,
	totalEnableTime = 0,
}
local TIME_WARNING_THRESHOLD_MS = 20
local MAX_TIME_PER_EVENT_MS = 12000
local NUM_EVENT_FRAMES = 10



-- ============================================================================
-- Event Handling
-- ============================================================================

function private.DoInitialize()
	local eventStartTime = debugprofilestop()
	while #private.initializeQueue > 0 and debugprofilestop() < (eventStartTime + MAX_TIME_PER_EVENT_MS) do
		local addon = tremove(private.initializeQueue, 1)
		if addon.OnInitialize then
			local addonStartTime = debugprofilestop()
			addon.OnInitialize()
			local addonTimeTaken = debugprofilestop() - addonStartTime
			if addonTimeTaken > TIME_WARNING_THRESHOLD_MS then
				Log.Warn("OnInitialize (%s) took %0.2fms", addon, addonTimeTaken)
			end
		end
		tinsert(private.enableQueue, addon)
	end
	if private.totalInitializeTime == 0 then
		for _, path, moduleLoadTime, settingsLoadTime in TSM.ModuleInfoIterator() do
			if moduleLoadTime > TIME_WARNING_THRESHOLD_MS then
				Log.Warn("Loading module %s took %0.2fms", path, moduleLoadTime)
			end
			if settingsLoadTime > TIME_WARNING_THRESHOLD_MS then
				Log.Warn("Loading settings for %s took %0.2fms", path, settingsLoadTime)
			end
		end
	end
	private.totalInitializeTime = private.totalInitializeTime + debugprofilestop() - eventStartTime
	return #private.initializeQueue == 0
end

function private.DoEnable()
	local eventStartTime = debugprofilestop()
	while #private.enableQueue > 0 and debugprofilestop() < (eventStartTime + MAX_TIME_PER_EVENT_MS) do
		local addon = tremove(private.enableQueue, 1)
		if addon.OnEnable then
			local addonStartTime = debugprofilestop()
			addon.OnEnable()
			local addonTimeTaken = debugprofilestop() - addonStartTime
			if addonTimeTaken > TIME_WARNING_THRESHOLD_MS then
				Log.Warn("OnEnable (%s) took %0.2fms", addon, addonTimeTaken)
			end
		end
		tinsert(private.disableQueue, addon)
	end
	if private.totalEnableTime == 0 then
		for _, path, _, _, gameDataLoadTime in TSM.ModuleInfoIterator() do
			if (gameDataLoadTime or 0) > TIME_WARNING_THRESHOLD_MS then
				Log.Warn("Loading game data for %s took %0.2fms", path, gameDataLoadTime)
			end
		end
	end
	private.totalEnableTime = private.totalEnableTime + debugprofilestop() - eventStartTime
	return #private.enableQueue == 0
end

function private.PlayerLogoutHandler()
	private.OnDisableHelper()
	wipe(private.disableQueue)
end

function private.OnDisableHelper()
	local disableStartTime = debugprofilestop()
	for _, addon in ipairs(private.disableQueue) do
		-- defer the main TSM.OnDisable() call to the very end
		if addon.OnDisable and addon ~= TSM then
			local startTime = debugprofilestop()
			addon.OnDisable()
			local timeTaken = debugprofilestop() - startTime
			if timeTaken > TIME_WARNING_THRESHOLD_MS then
				Log.Warn("OnDisable (%s) took %0.2fms", addon, timeTaken)
			end
		end
	end
	local totalDisableTime = debugprofilestop() - disableStartTime
	Analytics.Action("ADDON_DISABLE", floor(totalDisableTime))
	if TSM.OnDisable then
		TSM.OnDisable()
	end
end

do
	-- Blizzard did something silly in 8.1 where scripts time throw an error after 19 seconds, but nothing prevents us
	-- from just splitting the processing across multiple script handlers, so we do that here.
	local function EventHandler(self, event, arg)
		if event == "ADDON_LOADED" and arg == "TradeSkillMaster" then
			if private.DoInitialize() then
				-- we're done
				for _, frame in ipairs(private.eventFrames) do
					frame:UnregisterEvent(event)
				end
				Analytics.Action("ADDON_INITIALIZE", floor(private.totalInitializeTime))
			elseif self == private.eventFrames[#private.eventFrames] then
				error("Ran out of event frames to initialize TSM")
			end
		elseif event == "PLAYER_LOGIN" then
			if private.DoEnable() then
				-- we're done
				for _, frame in ipairs(private.eventFrames) do
					frame:UnregisterEvent(event)
				end
				Analytics.Action("ADDON_ENABLE", floor(private.totalEnableTime))
			elseif self == private.eventFrames[#private.eventFrames] then
				error("Ran out of event frames to enable TSM")
			end
		end
	end
	for _ = 1, NUM_EVENT_FRAMES do
		local frame = CreateFrame("Frame")
		frame:SetScript("OnEvent", EventHandler)
		frame:RegisterEvent("ADDON_LOADED")
		frame:RegisterEvent("PLAYER_LOGIN")
		tinsert(private.eventFrames, frame)
	end
	Event.Register("PLAYER_LOGOUT", private.PlayerLogoutHandler)
end



-- ============================================================================
-- AddonPackage Class
-- ============================================================================

local AddonPackage = LibTSMClass.DefineClass("AddonPackage")

function AddonPackage.__init(self, name)
	self.name = name
	tinsert(private.initializeQueue, self)
end

function AddonPackage.__tostring(self)
	return self.name
end

function AddonPackage.NewPackage(self, name)
	local package = AddonPackage(name)
	assert(not self[name])
	self[name] = package
	return package
end



-- ============================================================================
-- Addon Class
-- ============================================================================

local Addon = LibTSMClass.DefineClass("Addon", AddonPackage)

function Addon.__init(self, name)
	self.__super:__init(name)
end



-- ============================================================================
-- Initialization Code
-- ============================================================================

do
	LibTSMClass.ConstructWithTable(TSM, Addon, TSM_NAME)
end



-- ============================================================================
-- Module Functions (Debug Only)
-- ============================================================================

function TSM.AddonTestLogout()
	private.OnDisableHelper()
	TSM.DebugLogout()
	for _, path, _, _, _, moduleUnloadTime in TSM.ModuleInfoIterator() do
		if moduleUnloadTime > TIME_WARNING_THRESHOLD_MS then
			Log.Warn("Unloading %s took %0.2fms", path, moduleUnloadTime)
		end
	end
end
