----------------------------------------------------------------
-- 文件名: LibAddon.lua
-- 日期: 2013-12-02
-- 描述: 基本的插件框架
-- 作者: 盒子哥
-- 特别感谢ace
----------------------------------------------------------------

local MAJOR, MINOR = "LibAddon", 1
local LibAddon, oldminor = LibStub:NewLibrary(MAJOR, MINOR)

LibAddon.eventhandler = LibAddon.eventhandler or EventDispatcher:getSingleton():CreateEventNotice("LibAddon") -- Our very own frame
LibAddon.addons = {} -- addons in general
LibAddon.statuses = {} -- statuses of addon.
LibAddon.addonlist = {} -- all addons
LibAddon.enablequeue = {} -- addons that are initialized and waiting to be enabled
LibAddon.embeds = setmetatable({}, {__index = function(tbl, key) tbl[key] = {} return tbl[key] end }) -- contains a list of libraries embedded in an addon

local Enable, Disable, EnableModule, DisableModule, Embed, NewModule, GetModule, GetName, IsEnabled, EnableDebug, DisableDebug,
	SetDefaultModuleState, SetDefaultModuleLibraries, SetEnabledState, SetDefaultModulePrototype, SetDebugState

local function errorhandler(err)
	return geterrorhandler()(err)
end

local function CreateDispatcher(argCount)
	local code = [[
		local xpcall, eh = ...
		local method, ARGS
		local function call() return method(ARGS) end
	
		local function dispatch(func, ...)
			 method = func
			 if not method then return end
			 ARGS = ...
			 return xpcall(call, eh)
		end
	
		return dispatch
	]]
	
	local ARGS = {}
	for i = 1, argCount do ARGS[i] = "arg"..i end
	code = code:gsub("ARGS", table.concat(ARGS, ", "))
	return assert(loadstring(code, "safecall Dispatcher["..argCount.."]"))(xpcall, errorhandler)
end

local Dispatchers = setmetatable({}, {__index=function(self, argCount)
	local dispatcher = CreateDispatcher(argCount)
	rawset(self, argCount, dispatcher)
	return dispatcher
end})

Dispatchers[0] = function(func)
	return xpcall(func, errorhandler)
end

local function safecall(func, ...)
	if type(func) == "function" then
		return Dispatchers[select('#', ...)](func, ...)
	end
end

local function safe_onload(self)
	safecall(self.OnInitialize, self);

	local embeds = self.embeds[addon]
	for i = 1, #embeds do
		local lib = LibStub:GetLibrary(embeds[i], true)
		if lib then safecall(lib.OnEmbedInitialize, lib, addon) end
	end	
end

function LibAddon:NewAddon(name, ...)
	local object = {}	
	object.name = name
	local addonmeta = {}
	addonmeta.__tostring = function() return name	end
	setmetatable( object, addonmeta )
	self.addons[name] = object
	object.modules = {}	
	object.defaultModuleLibraries = {}
	Embed( object )	
	self:EmbedLibraries(object, select(1, ...))
	table.insert(self.addonlist, object)
	object.event_handler = EventDispatcher:getSingleton():CreateEventNotice(name .. #self.addonlist);
	object.event_handler:RegEventNotice("OnLoad", function()
		safe_onload(object)
	end)
	return object
end

function LibAddon:GetAddon(name)
	return self.addons[name]
end

function LibAddon:EmbedLibraries(addon, ...)
	for i=1, select("#", ... ) do
		local libname = select(i, ...)
		self:EmbedLibrary(addon, libname, 4)
	end
end

function LibAddon:EmbedLibrary(addon, libname, offset)
	local lib = LibStub:GetLibrary(libname, true)
	if (lib and type(lib.Embed) == "function") then
		lib:Embed(addon)
		table.insert(self.embeds[addon], libname)
		return true
	end

	return false
end

function LibAddon:EnableAddon(addon)
	if type(addon) == "string" then addon = LibAddon:GetAddon(addon) end
	if (self.statuses[addon.name] or not addon.enabledState) then return false end
	
	self.statuses[addon.name] = true
	safecall(self.OnEnable, self)

	if self.statuses[addon.name] then
		local embeds = self.embeds[addon]
		for i = 1, #embeds do
			local lib = LibStub:GetLibrary(embeds[i], true)
			if lib then safecall(lib.OnEmbedEnable, lib, addon) end
		end
		
		for name, module in pairs(addon.modules) do
			self:EnableAddon(module)
		end		
	end

	return self.statuses[addon.name]
end

function LibAddon:DisableAddon(addon)
	if not self.statuses[addon.name] then return false end
	
	self.statuses[addon.name] = false
	safecall( addon.OnDisable, addon )

	if not self.statuses[addon.name] then
		local embeds = self.embeds[addon]
		for i = 1, #embeds do
			local lib = LibStub:GetLibrary(embeds[i], true)
			if lib then safecall(lib.OnEmbedDisable, lib, addon) end
		end
		
		for name, module in pairs(addon.modules) do
			self:DisableAddon(module)
		end		
	end

	return not self.statuses[addon.name]
end

function LibAddon:IterateAddons() 
	return pairs(self.addons) 
end

function LibAddon:IterateAddonStatus()
	return pairs(self.statuses)
end

function LibAddon:IterateEmbedsOnAddon(addon)
	return pairs(self.embeds[addon]) 
end

function LibAddon:IterateModulesOfAddon(addon) 
	return pairs(addon.modules)
end

function LibAddon:NotifyAddonLoaded(addon, loaded_name)
	safecall(addon.OnAddonLoaded, addon, loaded_name)
	
	local embeds = self.embeds[addon]
	for i = 1, #embeds do
		local lib = LibStub:GetLibrary(embeds[i], true)
		if lib then safecall(lib.OnEmbedAddonLoaded, lib, addon, loaded_name) end
	end
end

function LibAddon:NotifyAddonInit(addon)
	safecall(addon.OnInitialize, addon)
	
	local embeds = self.embeds[addon]
	for i = 1, #embeds do
		local lib = LibStub:GetLibrary(embeds[i], true)
		if lib then safecall(lib.OnEmbedInitialize, lib, addon) end
	end
end

--------------------
-- 模块
function GetModule(self, name)	
	return self.modules[name]
end

local function IsModuleTrue(self) return true end

function NewModule(self, name, prototype, ...)
	-- TOTO: 增加提示信息
	if self.modules[name] then return end

	local module = LibAddon:NewAddon(("%s-%s"):format(self.name or tostring(self), name))
	module.IsModule = IsModuleTrue
	module:SetEnabledState(self.defaultModuleState)
	module.__DEBUG = self.__DEBUG	-- 继承父插件的debug状态
	module.moduleName = name

	if type(prototype) == "string" then
		LibAddon:EmbedLibraries(module, prototype, ...)
	else
		LibAddon:EmbedLibraries(module, ...)
	end

	LibAddon:EmbedLibraries(module, unpack(self.defaultModuleLibraries))

	if not prototype or type(prototype) == "string" then
		prototype = self.defaultModulePrototype or nil
	end
	
	if type(prototype) == "table" then
		local mt = getmetatable(module)
		mt.__index = prototype
		setmetatable(module, mt)  -- More of a Base class type feel.
	end	
	
	self.modules[name] = module
	
	return module
end

function GetName(self)
	return self.moduleName or self.name
end

function Enable(self)
	self:SetEnabledState(true)
	return LibAddon:EnableAddon(self)
end

function Disable(self)
	self:SetEnabledState(false)
	return LibAddon:DisableAddon(self)
end

function EnableDebug(self)
	self:SetDebugState(true)
	for name, module in pairs(self.modules) do
		module:EnableDebug()
	end
end

function DisableDebug(self)
	self:SetDebugState(false)
	for name, module in pairs(self.modules) do
		module:DisableDebug()
	end
end

function EnableModule(self, name)
	local module = self:GetModule( name )
	return module:Enable()
end

function DisableModule(self, name)
	local module = self:GetModule( name )
	return module:Disable()
end

function SetDefaultModuleLibraries(self, ...)
	if next(self.modules) then
		error("Usage: SetDefaultModuleLibraries(...): cannot change the module defaults after a module has been registered.", 2)
	end
	self.defaultModuleLibraries = {...}
end

function SetDefaultModuleState(self, state)
	if next(self.modules) then
		error("Usage: SetDefaultModuleState(state): cannot change the module defaults after a module has been registered.", 2)
	end
	self.defaultModuleState = state
end

function SetDefaultDebugState(self, state)
	if next(self.modules) then
		error("Usage: SetDefaultModuleState(state): cannot change the module defaults after a module has been registered.", 2)
	end
	self.defaultDebugState = state
end

function SetDefaultModulePrototype(self, prototype)
	if next(self.modules) then
		error("Usage: SetDefaultModulePrototype(prototype): cannot change the module defaults after a module has been registered.", 2)
	end
	if type(prototype) ~= "table" then
		error(("Usage: SetDefaultModulePrototype(prototype): 'prototype' - table expected got '%s'."):format(type(prototype)), 2)
	end
	self.defaultModulePrototype = prototype
end

function SetEnabledState(self, state)
	self.enabledState = state
end

function SetDebugState(self, state)	
	self.__DEBUG = state	
end

local function _print(self, level, ...)
	local msg = ("<%s>"):format(self.name);	

	for i=1, select("#", ...) do
		local tmp = select(i, ...);
		if (type(tmp) == "string" or type(tmp) == "number") then
			msg = msg .. " " .. tmp;
		end		
	end

	CEGUI.Logger:getSingleton():logEvent(msg, level)
end

local function print(self, ...)
	_print(self, CEGUI.Standard, ...)
end

local function error(self, ...)
	_print(self, CEGUI.Errors, ...)
end

local function debug(self, ...)
	if (self.__DEBUG) then
		_print(self, CEGUI.Warnings, ...)
	end
end

local function IterateModules(self) return pairs(self.modules) end
local function IterateEmbeds(self) return pairs(LibAddon.embeds[self]) end

local function IsEnabled(self) return self.enabledState end
local mixins = {
	NewModule = NewModule,
	GetModule = GetModule,
	Enable = Enable,
	Disable = Disable,
	EnableModule = EnableModule,
	DisableModule = DisableModule,
	IsEnabled = IsEnabled,
	SetDefaultModuleLibraries = SetDefaultModuleLibraries,
	SetDefaultModuleState = SetDefaultModuleState,
	SetDefaultModulePrototype = SetDefaultModulePrototype,
	SetEnabledState = SetEnabledState,
	SetDebugState = SetDebugState,
	EnableDebug = EnableDebug, 
	DisableDebug = DisableDebug,
	IterateModules = IterateModules,
	IterateEmbeds = IterateEmbeds,
	GetName = GetName,
	print = print,
	error = error,
	debug = debug,
}

local function IsModule(self) return false end
local pmixins = {
	defaultModuleState = true,
	enabledState = true,
	IsModule = IsModule,
	__DEBUG = true,
}

function Embed(target, skipPMixins)
	for k, v in pairs(mixins) do
		target[k] = v
	end
	if not skipPMixins then
		for k, v in pairs(pmixins) do
			target[k] = target[k] or v
		end
	end
end


function _OnEvent(event, ...)
	if (event == EventNotice.EVNN_AddOnsLoad) then
		local name = ...
		local addon = LibAddon.addons[name]
		if (addon ~= nil) then
			LibAddon:NotifyAddonLoaded(addon, name)
		end
	elseif (event == EventNotice.EVNN_AddOnsInit) then
		for i = 1, #LibAddon.addonlist do
			local addon = LibAddon.addonlist[i]
			LibAddon:NotifyAddonInit(addon)
		end
	end
end

addon_event_handler = EventDispatcher:getSingleton():CreateEventNotice("LibAddon")
addon_event_handler:RegEventNotice("OnEvent", _OnEvent)
addon_event_handler:RegEventNotice("OnLoad", function() return true end)
addon_event_handler:RegEventNotice("OnUnload", function() return true end)