---------------------------------------------------------------------------------
--
-- Prat - A framework for World of Warcraft chat mods
--
-- Copyright (C) 2006-2018  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.
--
--
-------------------------------------------------------------------------------


--[[ 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, select(2, ...))

--[[ 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
--    Print("onEnable() "..self.name)
    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
--    Print("onDisable() "..self.name)
    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 function getDescription(self)
    return self.PL.module_desc
  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,
    GetDescription = getDescription,

    -- Standard fields
    section = "extras",
  }

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

    addon.PL = Prat:GetLocalizer({})

    return addon
  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
	
