---------------------------------------------------------------------------------
--
-- Prat - A framework for World of Warcraft chat mods
--
-- Copyright (C) 2006-2008  Prat Development Team
--
-- This program is free software; you can redistribute it and/or
-- modify it under the terms of the GNU General Public License
-- as published by the Free Software Foundation; either version 2
-- of the License, or (at your option) any later version.
--
-- This program is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-- GNU General Public License for more details.
--
-- You should have received a copy of the GNU General Public License
-- along with this program; if not, write to:
--
-- Free Software Foundation, Inc.,
-- 51 Franklin Street, Fifth Floor,
-- Boston, MA  02110-1301, USA.
--
--
-------------------------------------------------------------------------------
--[[
Name: Prat 3.0 (modules.lua)
Revision: $Revision: 82088 $
Author(s): Sylvaanar (sylvanaar@mindspring.com)
Inspired By: Prat 2.0, Prat, and idChat2 by Industrial
Website: http://files.wowace.com/Prat/
Documentation: http://www.wowace.com/wiki/Prat
Forum: http://www.wowace.com/forums/index.php?topic=6243.0
SVN: http://svn.wowace.com/wowace/trunk/Prat/
Description: The implementation of the module framework
]]

--[[ BEGIN STANDARD HEADER ]]--

-- Imports
local _G = _G
local LibStub = LibStub
local tonumber = tonumber
local tostring = tostring
local pairs, ipairs = pairs, ipairs
local type = type
local Prat = Prat
local pcall = pcall
local setmetatable = setmetatable
local tinsert = tinsert
-- Isolate the environment
setfenv(1, Prat)

--[[ END STANDARD HEADER ]]--

Addon.defaultModuleState = true

--[[

Module system flow:

1) Module code loaded by the client
2) Module first asks if it should  be INSTALLED
3) If no, the module code returns before creating the module
4) If yes, the module is given the name it should register with
5) The module code creates a new module calling NewModule
6) Once the module has been created, we recieve OnModuleCreated, we want to
   remember that this module is installed, so we save that info
7) The module will have its on initalize called after Prat's.  
8) If the module is disabled it will stop at this point
9) If the module is enabled, it will call its OnEnable

That give us the states: EXISTS, INSTALLED, INITIALIZED, ENABLED, 
                                                         DISABLED


]]

local function NOP() end

do
	Modules = {}
	function RequestModuleName(self, name)  --  <== EXISTS
		if type(name) ~= "string" then 
			name = tostring(self)
		end

		CreateModuleControlOption(name)

		-- Duh, this still requires separate loader due to the saved variable
		if Prat.db and Prat.db.profile.modules[name] == 1 then
			Modules[name] = "EXISTS"
		end

		if not Modules[name] then
			Modules[name] = "EXISTS"
			return name
		end
	end
end

do
	local module_defaults = {}
	function SetModuleDefaults(self, module, defaults)
		module_defaults[type(module) == "table" and module.name or module] = defaults
	end

	local module_init = {}
	function SetModuleInit(self, module, init)
		module_init[type(module) == "table" and module.name or module or "null"] = init
	end
	local function GetModuleInit(module)
		return module_init[type(module) == "table" and module.name or module or "null"]
	end
	
	local sectionlist = {
		--display
		["Prat_ChannelColorMemory"] = "display",
		["Prat_ChannelSticky"] = "display",
		["Prat_ChatFrames"] = "display",
		["Prat_Fading"] = "display",
		["Prat_History"] = "display",
		["Prat_Frames"] = "display",
		["Prat_Editbox"] = "display",
		["Prat_Paragraph"] = "display",
		["Prat_Scroll"] = "display",
		["Prat_Clear"] = "display",
		["Prat_Font"] = "display",
		["Prat_ChatTabs"] = "display",
		["Prat_Buttons"] = "display",
		["Prat_Original Buttons"] = "display",

		--formatting
		["Prat_ChannelNames"] = "formatting",
		["Prat_PlayerNames"] = "formatting",
		["Prat_ServerNames"] = "formatting",
		["Prat_Substitutions"] = "formatting",
		["Prat_Timestamps"] = "formatting",
		["Prat_UrlCopy"] = "formatting",
		--extras
		["Prat_AddonMsgs"] = "extras",
		["Prat_EventNames"] = "extras",
		["Prat_PopupMessage"] = "extras",
		["Prat_Sounds"] = "extras",
		}
	setmetatable(sectionlist, {__index = function(t,k,v)
			return "extras"
			end})

	local function onInit(self)  --  ==> INSTALLED -> INITIALIZED
		local defaults, opts, init
		defaults, module_defaults[self.name] = module_defaults[self.name] or {}
		self.db = Prat.db:RegisterNamespace(self.name, defaults)

		init = GetModuleInit(self)
		if init then 
			init(self) 
			SetModuleInit(self, self, nil)
		end
		opts = GetModuleOptions(self.name)
		if opts then 
			opts.handler = self
			opts.disabled = "IsDisabled"
			Options.args[sectionlist[self.name]].args[self.name], opts = opts
			SetModuleOptions(self, self.name, nil)
		end

		local v = Prat.db.profile.modules[self.moduleName]
	    if v == 4 or v == 5 then 
	        self.db.profile.on = (v == 5) and true or false
			Prat.db.profile.modules[self.moduleName] = v-2
		else
			Prat.db.profile.modules[self.moduleName] = self.db.profile.on and 3 or 2
	    end
		self:SetEnabledState(self.db.profile.on)

		Modules[self.name] = "INITALIZED"
	end


	local function onEnable(self)   -- ==> INITIALIZED/DISABLED -> ENABLED
		local pats = GetModulePatterns(self)
		if pats then 
			for _,v in ipairs(pats) do
				RegisterPattern(v, self.name)
			end
		end
		
		self:OnModuleEnable()
		Modules[self.name] = "ENABLED"
	end
	local function onDisable(self)  -- ==>INITIALIZED/ENABLED -> DISABLED
		UnregisterAllPatterns(self.name)
		self:OnModuleDisable()
		Modules[self.name] = "DISABLED"
	end


	local function setValue(self, info, b)
		self.db.profile[info[#info]] = b
		self:OnValueChanged(info, b)
	end
	local function getValue(self, info)
		return self.db.profile[info[#info]]
	end

	local function getSubValue(self, info, val)
		return self.db.profile[info[#info]][val]
	end
	local function setSubValue(self, info, val, b)
		self.db.profile[info[#info]][val] = b
		self:OnSubValueChanged(info, val, b)
	end

	local defclr = { r=1, b=1, g=1, a=1 }
	local function getColorValue(self, info)
		local c = self.db.profile[info[#info]] or defclr
		return c.r, c.g, c.b, c.a
	end	
	local function setColorValue(self, info, r,g,b,a)
		local c = self.db.profile[info[#info]] or defclr
		c.r, c.g, c.b, c.a = r,g,b,a
		self:OnColorValueChanged(info, r,g,b,a)
	end

	local function isDisabled(self)
		return not self:IsEnabled()
	end

	local prototype = {
		OnEnable = onEnable,
		OnDisable = onDisable,
		OnInitialize = onInit,
		OnModuleEnable = NOP,
		OnModuleDisable = NOP,
		OnModuleInit = NOP,
		OnValueChanged = NOP,
		OnSubValueChanged = NOP,
		OnColorValueChanged = NOP,
		GetValue = getValue,
		SetValue = setValue,
		GetSubValue = getSubValue,
		SetSubValue = setSubValue,
		GetColorValue = getColorValue,
		SetColorValue = setColorValue,
		IsDisabled = isDisabled,

		-- Standard fields
		L = {},
		section = "extras",
	}

	function NewModule(self, name, ...)  -- <== INSTALLED (Ace3 does the <== INITIALIZED)
		return Addon:NewModule(name, prototype, ...)
	end

--	local locs, section
--	function NewModuleEx(self, name, locs, section, ...)  -- <== INSTALLED (Ace3 does the <== INITIALIZED)
--		return Addon:NewModule(name, prototype, ...)
--	end

	function Addon:OnModuleCreated(module) -- EXISTS -> INSTALLED
--[===[@debug@
		_G[module.moduleName:lower()] = module  
--@end-debug@]===]
		Modules[module.name], Modules[module.moduleName] = "INSTALLED"
	end
end


--[[ 

For module options, i want to use the single closure style executed from the main chunk of the module,
such as:

SetModuleOptionTable(name, function() return { ... } )

This way the options can be GC'd by from the modules, before the decision is made as
to whether we will actually load the module. they will go into a table here, and either
free'd, given back to the module for it to execute, or possible executed on this end. 

In any case there will only be 1 copy of the closure, and if executed it will create its data
and then it can be freed leaving no code behind. Prat 2.0 used alot of memory solely because of 
its options tables, this tries to avoid that.

]]

do
	local module_options = {}
	function SetModuleOptions(self, module, options)
		module_options[type(module) == "table" and module.name or module or "null"] = options
	end
	
	function GetModuleOptions(module)
		return module_options[type(module) == "table" and module.name or module or "null"]
	end
end

do
	local module_patterns = {}
	function SetModulePatterns(self, module, patterns)
		module_patterns[type(module) == "table" and module.name or module or "null"] = patterns
	end
	
	function GetModulePatterns(module)
		return module_patterns[type(module) == "table" and module.name or module or "null"]
	end
end

do 
	local modules_toload = {}
	local extensions_toload = {}
	function AddModuleToLoad(self, module_closure)
		tinsert(modules_toload, module_closure)
	end
	
	function AddModuleExtension(self, extension_closure)
		tinsert(extensions_toload, extension_closure)
	end

	local function loadNow(self, mod)
		local success, ret = pcall(mod)
		if not success then
			 _G.geterrorhandler()(ret)
		end
	end

	function LoadModules()
		for i=1,#modules_toload,1 do
			local success, ret = pcall(modules_toload[i])
			if not success then
				 _G.geterrorhandler()(ret)
			end
			modules_toload[i] = nil
		end	
		modules_toload = nil

		for i=1,#extensions_toload,1 do
			local success, ret = pcall(extensions_toload[i])
			if not success then
				 _G.geterrorhandler()(ret)
			end
			extensions_toload[i] = nil
		end	
		extensions_toload = nil

		LoadModules = nil
		AddModuleToLoad = loadNow
		AddModuleExtension = loadNow
	end
end
	
