	--Module support functions and global utility stubs
local DGV = DugisGuideViewer
local L = DugisLocals
local LuaUtils = LuaUtils

DGV.Modules = {}
local _

--By default DugiGuidesIsLoading is set to true because the game is loading from the begining
LuaUtils.DugiGuidesIsLoading = true

local function BaseKeyPredicate(key)
	return key=="Load" or key=="loaded" or 
		key=="Initialize" or key=="initialized" or 
		key=="ShouldLoad" or key=="essential" or
		key=="Unload" or key=="OnModulesLoaded"
end

local unloadedModuleChild
local unloadedModuleMeta = {
	__index = function (table, key)
		if BaseKeyPredicate(key) then return nil end
		return unloadedModuleChild
	end,
	__call = DGV.NoOp
}
unloadedModuleChild = setmetatable({}, unloadedModuleMeta)
--setmetatable(DGV.Modules, unloadedModuleMeta)

--[[local function EnsureLoadInitTable(table)
	local loadInit = rawget(table, "___LoadInitTable")
	if not loadInit then
		loadInit = {}
		rawset(table, "___LoadInitTable", loadInit)
	end
	return loadInit
end

local loadedModuleMeta = {
	__index = function (table, key)
		if BaseKeyPredicate(key) then return nil end
		return EnsureLoadInitTable(table)[key]
	end,
	__newindex = function(table, key, value)
		if BaseKeyPredicate(key) then
			rawset(table, key, value)
			return
		end
		EnsureLoadInitTable(table)[key] = value
	end
}]]

local function SetStubs(module)
	--if module then								this is going to have to be an enhancement for the future.  There are just too many ill-behaved modules that don't clean themselves up r/n
	--	setmetatable(module, unloadedModuleMeta)
	--end
	--Guides utility stubs
	local all = not module
	if all or module.name=="Guides" then
		DGV.UpdateMainFrame = DGV.NoOp
		DGV.UpdateQueryQuests = DGV.NoOp
		DGV.Zone_OnEvent = DGV.NoOp
		DGV.CHAT_MSG_SYSTEM = DGV.NoOp
		DGV.UI_INFO_MESSAGE = DGV.NoOp
		DGV.CHAT_MSG_LOOT = DGV.NoOp
		DGV.UpdateAchieveFrame = DGV.NoOp
		DGV.Guide_CRITERIA_UPDATE = DGV.NoOp
		DGV.PLAYER_LEVEL_UP = DGV.NoOp
		DGV.RegisterGuide = DGV.NoOp
		DGV.UpdateTravelToLocation = DGV.NoOp
		DGV.GetUnfinishedGuideIndexByQID = DGV.NoOp
		DGV.ReturnTag = DGV.NoOp
		DGV.IterateGuideIndicesWithQID = DGV.NoOp
		DGV.isValidGuide = DGV.NoOp
		DGV.ClearScreen = DGV.NoOp
		DGV.CompleteQuest = DGV.NoOp
		--DGV.RegisterQuestChains = DGV.NoOp
		DGV.CheckForFloorChange = DGV.NoOp
		DGV.Guide_CRITERIA_UPDATE = DGV.NoOp
		DGV.UpdateAllSIDs = DGV.NoOp
		DGV.PLAYER_ENTERING_WORLD = DGV.NoOp
		DGV.SCENARIO_CRITERIA_UPDATE = DGV.NoOp			
		function DGV:ShowLargeWindow()
			if not DugisGuideViewer.SettingsTree then
				DGV:CreateSettingsTree(DugisMainBorder)
			end
			DugisGuideViewer.SettingsTree.frame:SetPoint("TOPLEFT", DugisMainBorder, "TOPLEFT", "20", "-20")
			DugisGuideViewer.SettingsTree.frame:SetPoint("BOTTOMRIGHT", DugisMainBorder, "BOTTOMRIGHT", "-20", "20")
			DugisMainBorder:SetHeight(377)
			DugisMainBorder:Show()
			DugisMainBorder.bg:Hide()
			DugisMain:Hide()
			DugisReloadButton:Hide()
			--DugisSuggestButton:Hide()
			DugisResetButton:Hide()
			DugisPercentButton:Hide()
			DGV:SetAllBorders()
		end
	end
	--Professions utility stubs
	if all or module.name=="Professions" then
		DGV.UpdateProfessions =  DGV.NoOp
	end
	--ModelViewer utility stubs
	if all or module.name=="ModelViewer" then
		DGV.HasModel = DGV.NoOp
		DGV.IsModelDataOn = DGV.NoOp
		DGV.ShowModel = DGV.NoOp
		DGV.WipeModels = DGV.NoOp
	end
	--ReqLevel utility stubs
	if all or module.name=="ReqLevel" then
		DGV.ReqLevel = {}
	end
	if all or module.name=="NPC" then
		DugisNPCs = {}
	end
	--Record utility stubs
	if all or module.name=="Record" then
		SlashCmdList["DGR"] = DGV.NoOp
		DGV.ToggleRecordLimit = DGV.NoOp
		DGV.ShowRecord = DGV.NoOp
		DGV.OnAutoComplete = DGV.NoOp
		DGV.OnQuestDetail = DGV.NoOp
		DGV.OnQuestComplete = DGV.NoOp
		DGV.UpdateRecord = DGV.NoOp
		DGV.UpdateStickyFrame = DGV.NoOp
		DGV.ClearStickyFrame = DGV.NoOp
	end
	--Target utility stubs
	if all or module.name=="Target" then
		DGV.SetTarget = DGV.NoOp
		DGV.WipeTargetNPCs = DGV.NoOp
		DGV.FinalizeTarget = DGV.NoOp
		DGV.SetNPCTarget = DGV.NoOp
	end
	--MiniBlobs utility stubs
	if all or module.name=="MiniBlobs" then
		DGV.IsPlayerAtBlizzardDestination = DGV.NoOp
		DGV.OnMapChangeUpdateArrow = DGV.NoOp
	end
	--QuestPOI utility stubs
	if all or module.name=="QuestPOI" then
		DGV.InitializeQuestPOI = DGV.NoOp
	end
	--MapOverlays utility stubs
	if all or module.name=="MapOverlays" then
		DGV.InitializeMapOverlays = DGV.NoOp
	end
	--UnlistedQuest utility stubs
	if all or module.name=="UnlistedQuest" then
		DGV.IsQuestInGuide = DGV.NoOp
	end
end
SetStubs()

function DGV:RegisterModule(name, ...)
	local dependencies = DGV.GetCreateTable()
	for i=1,select('#', ...) do
		local dependencyName = select(i, ...)
		local dependency = DGV.Modules[dependencyName]
		if not dependency then
			dependencies:Pool()
			return 
		end
		dependencies:Insert(dependency)
	end
	local module = {}
	module.name = name
	table.insert(DGV.Modules, module)
	DGV.Modules[name] = module
	SetStubs(module)
	setmetatable(module, unloadedModuleMeta)
	return module, dependencies:Pool()
end

StaticPopupDialogs["DUGIS_RELOAD_PROMPT"] = {
	text = L["Enabling these Dugi Guides features will require the UI to reload.  Do this now?"],
	button1 = L["Yes"],
	button2 = L["No"],
	OnAccept = function()
		DGV.chardb.GuideOn = true
        --In the copper mode RemoveAllWaypoints is not available (clocking it: #128)
        if DugisGuideViewer.RemoveAllWaypoints then
            DugisGuideViewer:RemoveAllWaypoints() --helps with reducing errors with Taxi
        end
		ReloadUI()
	end,
	OnCancel = function(self, data)
		if data then
			data()
		end
	end,
	timeout = 0,
	whileDead = true,
	hideOnEscape = true,
}

function DGV:ClearModule(module)
	local name = module.name
	local slFunc = module.ShouldLoad
	wipe(module)
	module.name = name
	module.ShouldLoad = slFunc
	SetStubs(module)
	--rawset(module, "___LoadInitTable", nil)
end

function DGV:ShowReloadUi()
	local dialog = StaticPopup_Show ("DUGIS_RELOAD_PROMPT")
	dialog.data = StaticPopupDialogs.DUGIS_RELOAD_PROMPT.data
	StaticPopupDialogs.DUGIS_RELOAD_PROMPT.button2 = L["No"]
	StaticPopupDialogs.DUGIS_RELOAD_PROMPT.data = DGV.NoOp
end

function DGV:SetEssentialsOnCancelReload()
	StaticPopupDialogs.DUGIS_RELOAD_PROMPT.button2 = L["Enable Essentials"]
	StaticPopupDialogs.DUGIS_RELOAD_PROMPT.data = function()
		DGV:TurnOnEssentials(true)
	end
end

local function ShouldLoad(module)
	if not module then return false end
	if module.ShouldLoad then return module:ShouldLoad() end
	return (DugisGuideViewer.chardb.EssentialsMode<1 or module.essential) and DugisGuideViewer:GuideOn()
end

local function InitModule(module)
	if not module.initialized and not module.loaded then
		if not module.Initialize then DGV:ShowReloadUi();return false end --module deleted
		setmetatable(module, nil)
		--setmetatable(module, loadedModuleMeta)
		module:Initialize()
		module.initialized = true
	end
	return true
end

local function LocalLoadModule(module, threading)
	if module and not module.loaded and module.Load and ShouldLoad(module) then
		module:Load(threading)
		module.loaded = true
	end
end

function DGV.DugiGuidesOnLoadingStart()
    DugisGuideViewer:UpdateIconStatus()
end

local function DugiGuidesOnLoadingEnd()
    LuaUtils.DugiGuidesIsLoading = false
    
    --if not (DugisGuideViewer.kalielsTrackerloaded or DugisGuideViewer.sylingTrackerloaded) then
	--	ObjectiveTrackerFrame:Show()
	--end  --Dont remember why this is here, it makes the objectivetracker show up even if nothing is tracked
	
    DugisGuideViewer:UpdateIconStatus()
end

local function UpdateGameLoadingProgress(threading, currentNormalizedProgress, oneGuide)
    if threading then
	
        if GamePreloader then
            if currentNormalizedProgress >= 0.95 then
                GamePreloader:Hide()
                GamePreloader.animationGroup:Stop()
            else
                if not GamePreloader:IsShown() then
                    GamePreloader:Show()
                    GamePreloader.animationGroup:Play()
                end
                GamePreloader.TexWrapper.Text:SetText((oneGuide and "Loading Dugi Guide " or "Loading Dugi Guides ") ..(LuaUtils:Round(currentNormalizedProgress, 2) * 100).."%..")
            end
		end
		if currentNormalizedProgress >= 0.98  then
			MainFramePreloader:HidePreloader()
		else
            MainFramePreloader:ShowPreloader()
        end
    end
end

LuaUtils.UpdateGameLoadingProgress = UpdateGameLoadingProgress

function LuaUtils.OnPlayerInCombat()
    if LuaUtils.DugiGuidesIsLoading then
        GamePreloader:Hide()
    end
end

local function LoadModules(threading)
    DGV.DugiGuidesOnLoadingStart()
    
    if threading then
        DGV.CreateStandardPreloader("GamePreloader", UIParent)  
        GamePreloader:Show()
        GamePreloader.animationGroup:Play()
        GamePreloader.TexWrapper.Background:SetVertexColor(0,0.0,0, 0.0);
        GamePreloader:SetPoint("TOPLEFT", UIParent, GetScreenWidth() * 0.5 -100,  -GetScreenHeight() * 0.1 + 50)
        GamePreloader:SetWidth(225)
        GamePreloader.TexWrapper.Text:SetWidth(220)
    end

    local progress = 0
    UpdateGameLoadingProgress(threading, progress)
    
	for _, module in ipairs(DGV.Modules) do
		if ShouldLoad(module) then
			if not InitModule(module) then return false end
		elseif module and not module.loaded and not module.essential
			and (DGV:UserSetting(DGV_UNLOADMODULES) or not module.initialized) then
			DGV:ClearModule(module) --clear modules not loaded
		end
	end
	for _, module in ipairs(DGV.Modules) do
	
		if threading then
			LuaUtils:WaitForCombatEnd(true)
			LuaUtils:RestIfNeeded(true)
		end
		
	    LocalLoadModule(module, threading)
            
        UpdateGameLoadingProgress(threading, progress)
        progress = progress + (0.95/#DGV.Modules)   
	end
	for _, module in ipairs(DGV.Modules) do
		if module.OnModulesLoaded and module.loaded then
			if threading then
				LuaUtils:RestIfNeeded(true)
			end
		
            module:OnModulesLoaded(threading)
		end
		--TODO: move this logic to guide modules
		if module.name and strmatch(module.name, "DugisGuide_")  then
			module.Initialize = nil
			module.Load = nil
			--[[if not module.initialized then
				ClearModule(module)
			end]]
		end
        
        progress = progress + (0.05/#DGV.Modules)
        UpdateGameLoadingProgress(threading, progress)
	end

    DugiGuidesOnLoadingEnd()

	return true
end

local function UnloadModules()
    local i
	for i=#DGV.Modules,1,-1 do
		local module = DGV.Modules[i]
		if module.initialized and not ShouldLoad(module) then
			module:Unload()
			module.loaded = false
			SetStubs(module)
		end
	end
end

function DGV:ReloadModules(threading)
	local isGuideMode, isEssentialMode, isOffMode = DugisGuideViewer.GetPluginMode()
	if isGuideMode and not isEssentialMode and not isOffMode then 
		UnloadModules()
	end 
	local result = LoadModules(threading)
	LuaUtils:collectgarbage(threading)
	StaticPopupDialogs.DUGIS_RELOAD_PROMPT.button2 = L["No"]
	StaticPopupDialogs.DUGIS_RELOAD_PROMPT.data = DGV.NoOp
	return result
end

function DGV:IsModuleLoaded(name)
	return DGV.Modules[name] and DGV.Modules[name].loaded
end

--[[function DGV:UnloadModule(name)
	local module = DGV.Modules[name]
	if module and module.loaded then
		module:Unload()
		module.loaded = false
	end
end

function DGV:LoadModule(name)
	local module = DGV.Modules[name]
	if module then
		if not InitModule(module) then return false end
		LocalLoadModule(module)
		module:OnModulesLoaded()
	end
	return true
end]]

function DGV:IsModuleRegistered(name)
	return rawget(DGV.Modules, name)
end

