---------------------------------------------------------------------------------
--
-- Prat - A framework for World of Warcraft chat mods
--
-- Copyright (C) 2006-2007  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
Revision: $Revision: 31419 $
Author(s): Sylvaanar (sylvanaar@mindspring.com) 
           Curney (asml8ed@gmail.com)
           Krtek (krtek4@gmail.com)
Inspired By: idChat2 by Industrial
Website: http://files.wowace.com/Prat/
Documentation: http://www.wowace.com/wiki/Prat
SVN: http://svn.wowace.com/wowace/trunk/Prat/
Description: A framework for chat frame modules.
Dependencies: Ace2
]]

-- Debuging Subsytems
local function DBG_SPLIT(...)   DBG:Debug("SPLIT", ...)   end
local function DBG_PATTERN(...) DBG:Debug("PATTERN", ...) end
local function DBG_OUTPUT(...)  DBG:Debug("OUTPUT", ...)  end
local function DBG_MODULE(...)  DBG:Debug("MODULE", ...)  end

local function DUMP_PATTERN(...) DBG:Dump("PATTERN", ...) end
local function DUMP_MODULE(...) DBG:Dump("MODULE", ...) end
local function DUMP_SPLIT(...) DBG:Dump("SPLIT", ...) end


local L = AceLibrary("AceLocale-2.2"):new("Prat")

L:RegisterTranslations("enUS", function() return {
    ["Prat"] = true,
    ["A framework for chat frame modules."] = true,
    ["Disabled Modules"] = true,
    ["Enabled Modules"] = true,
    ["toggle"] = true,
    ["Toggle"] = true,
    ["Toggle the module on and off."] = true,
    ["Debugging"] = true,
    ["debug"] = true,
    ["Show debug messages."] = true,
    ["Favorites"] = true,
    ["Module Views"] = true,
    ["Modules By Category"] = true,
    ["Display enabled modules by category. Modules can appear in more than one category."] = true,
    ["Do the actual chat replacement"] = true,
    ["Allows 2.0 modules to make chat replacements. This is required for all modules colored blue."] = true,
    ["Run taint checks"] = true,
    ["Run checks for taint on specific variables"] = true,
    ["Hint: Select 'Favorite' in a module to add it here"] = true,
    ["Favorite"] = true,
    ["Place this module on the favorite's list, and show it on the main menu."] = true,
    ["Updated Modules"] = true,
    ["Module Control"] = true,

    ["Channel"] = true,
    ["Chatframe"] = true,
    ["Buttons"] = true,
    ["Actions"] = true,
    ["Colors"] = true,
    ["Text"] = true,
    ["Names"] = true,
    ["Links"] = true,
    ["Behavior"] = true,
    ["Filtering"] = true,
    ["Developer"] = true,
    ["Formatting"] = true,
    ["Info"] = true,
    ["Sounds"] = true,
} end)

L:RegisterTranslations("zhCN", function() return {
    ["Prat"] = "Prat",
    ["A framework for chat frame modules."] = "聊天窗口增加插件。",
} end)

L:RegisterTranslations("koKR", function() return {
    ["Prat"] = "Prat",
    ["A framework for chat frame modules."] = "대화창 기능에 관한 프레임워크입니다.",
    ["Disabled Modules"] = "사용하지 않는 기능",
    ["Enabled Modules"] = "사용중인 기능",
--    ["toggle"] = true,
    ["Toggle"] = "사용",
    ["Toggle the module on and off."] = "기능 사용 여부를 결정합니다.",
    ["Debugging"] = "디버깅",
--    ["debug"] = true,
    ["Show debug messages."] = "디버그 메세지를 표시합니다.",
    ["Favorites"] = "즐겨찾기",
    ["Module Views"] = "모듈 표시",
    ["Modules By Category"] = "분류별 모듈",
    ["Display enabled modules by category. Modules can appear in more than one category."] = "사용중인 모듈을 분류별로 표시합니다. 각 모듈은 하나 이상의 분류에 나타날 수 있습니다.",
--    ["Do the actual chat replacement"] = "강력한 대화 기능을 사용하세요.",
--    ["Allows 2.0 modules to make chat replacements. This is required for all modules colored blue."] = true,
--    ["Run taint checks"] = "Taint 검사 실행",
--    ["Run checks for taint on specific variables"] = true,
    ["Hint: Select 'Favorite' in a module to add it here"] = "도움말: 각 모듈의 '즐겨찾기'를 선택하면 여기에 추가됩니다.",
    ["Favorite"] = "즐겨찾기",
    ["Place this module on the favorite's list, and show it on the main menu."] = "이 모듈을 즐겨찾기 목록에 두고 주 메뉴에서 사용하세요.",
    ["Updated Modules"] = "업데이트된 모듈",
    ["Module Control"] = "모듈 관리",

    ["Channel"] = "채널",
    ["Chatframe"] = "대화창",
    ["Buttons"] = "버튼",
    ["Actions"] = "행동",
    ["Colors"] = "색상",
    ["Text"] = "글자",
    ["Names"] = "이름",
    ["Links"] = "링크",
    ["Behavior"] = "동작",
    ["Filtering"] = "필터링",
    ["Developer"] = "개발자",
    ["Formatting"] = "형식화",
    ["Info"] = "정보",
} end)

L:RegisterTranslations("zhTW", function() return {
    ["Prat"] = "Prat",
    ["A framework for chat frame modules."] = "整合的聊天框架模組。",
} end)

L:RegisterTranslations("esES", function() return {
    ["Prat"] = "Prat",
    ["A framework for chat frame modules."] = "Un entorno para m\195\179dulos de chat.",
    ["Disabled Modules"] = "M\195\179dulos Desactivados",
    ["Enabled Modules"] = "M\195\179dulos Activados",
    ["toggle"] = "activar",
    ["Toggle"] = "Activar",
    ["Toggle the module on and off."] = "Activa y desactiva este m\195\179dulo",
    ["Debugging"] = "Debugging",
    ["debug"] = "debug",
    ["Show debug messages."] = "Mostrar mensajes de debug",
    ["Favorites"] = "Favoritos",
    ["Module Views"] = "Vistas de m\195\179dulo",
    ["Modules By Category"] = "M\195\179dulos por categor\195\173a",
    ["Display enabled modules by category. Modules can appear in more than one category."] = "Muestra los m\195\179dulos activados por categor\195\173as. Los módulos pueden aparecer en m\195\161s de una categor\195\173a",
    ["Do the actual chat replacement"] = "Realizar el reemplazo del chat",
    ["Allows 2.0 modules to make chat replacements. This is required for all modules colored blue."] = "Permite a m\195\179dulos 2.0 realizar reemplazos en el chat. Esto es requerido para todos lo m\195\179dulos de color azul.",
    ["Run taint checks"] = "Iniciar comprobaciones de contaminaci\195\179n",
    ["Run checks for taint on specific variables"] = "Inicia comprobaciones para la contaminaci\195\179n de variables espec\195\173ficas",
    ["Hint: Select 'Favorite' in a module to add it here"] = "Consejo: Elige 'Favorito' en un m\195\179dulo para a\195\177adirlo aqu\195\173",
    ["Favorite"] = "Favorito",
    ["Place this module on the favorite's list, and show it on the main menu."] = "Coloca este m\195\179dulo en la lista de favoritos, y lo muestra en el men\195\186 principal",
    ["Updated Modules"] = "M\195\179dulos Actualizados",


    ["Channel"] = "Canal",
    ["Chatframe"] = "Marco de Chat",
    ["Buttons"] = "Botones",
    ["Actions"] = "Acciones",
    ["Colors"] = "Colores",
    ["Text"] = "Texto",
    ["Names"] = "Nombres",
    ["Links"] = "Enlaces",
    ["Behavior"] = "Comportamiento",
    ["Filtering"] = "Filtrado",
    ["Developer"] = "Desarrollador",
    ["Formatting"] = "Formateado",
    ["Info"] = "Info",
} end)

L:RegisterTranslations("deDE", function() return {
    ["Prat"] = "Prat",
    ["A framework for chat frame modules."] = "Ein Grundaddon f\195\188r Chatfenster Module",
    ["Disabled Modules"] = "Deaktivierte Module",
    ["Enabled Modules"] = "Aktivierte Module",
    ["toggle"] = "Aktivieren",
    ["Toggle"] = "Einschalten",
    ["Toggle the module on and off."] = "Schaltet das Modul an und aus.",
    ["Debugging"] = "Pr\195\188fen",
    ["debug"] = "Pr\195\188fe",
    ["Show debug messages."] = "Zeige Pr\195\188fungsnachrichten",
    ["Favorites"] = "Favoriten",
    ["Module Views"] = "Modul Anzeigearten",
    ["Modules By Category"] = "Module nach Kategorien",
    ["Display enabled modules by category. Modules can appear in more than one category."] = "Zeige aktivierte Module nach Kategorie an. Module k\195\182nnen in mehr als nur einer Kategorie erscheinen.",
    ["Do the actual chat replacement"] = "Aktiviere die gegenw\195\164rtigenen Chat\195\164nderrungen",
    ["Allows 2.0 modules to make chat replacements. This is required for all modules colored blue."] = "Erlaubt den 2.0 Modulen ihre Chat Änderungen zu aktivieren. Dies ist Vorraussetzung für alle Blau eingef\195\164rbten Module!",
    ["Run taint checks"] = "F\195\188hre Verf\195\164lschungspr\195\188fungen durch",
    ["Run checks for taint on specific variables"] = "F\195\188hrt Pr\195\188fungen durch ob bestimmte Variablen verf\195\164lscht sind.",
    ["Hint: Select 'Favorite' in a module to add it here"] = "Hinweis: W\195\164hle 'Favorit' in einen Modul um es hier hinzuzuf\195\188gen",
    ["Favorite"] = "Favorit",
    ["Place this module on the favorite's list, and show it on the main menu."] = "F\195\188ge dieses Modul zu der Favoriten Liste hinzu, und zeige es im Hauptmen\195\188 an.",
    ["Updated Modules"] = "Aktualisierte Module",

    ["Channel"] = "Kanal",
    ["Chatframe"] = "Chatfenster",
    ["Buttons"] = "Tasten",
    ["Actions"] = "Aktionen",
    ["Text"] = "Text",
    ["Names"] = "Namen",
    ["Links"] = "Links",
    ["Behavior"] = "Verhalten",
    ["Filtering"] = "Filterung",
    ["Developer"] = "Entwickler",
    ["Formatting"] = "Formatierung",
    ["Info"] = "Info",
} end)

L:RegisterTranslations("frFR", function() return {
    ["Prat"] = "Prat",
    ["A framework for chat frame modules."] = "Une librairie pour les modules de fen\195\170tres de chat.",
    ["Disabled Modules"] = "Modules d\195\169sactiv\195\169s",
    ["Enabled Modules"] = "Modules activ\195\169s",
    ["toggle"] = "activer/d\195\169sactiver",
    ["Toggle"] = "Activer/D\195\169sactiver",
    ["Toggle the module on and off."] = "Active/d\195\169sactive le module",
    ["Debugging"] = "D\195\169boguage",
    ["debug"] = "debug",
    ["Show debug messages."] = "Afficher les messages de d\195\169boguage.",
    ["Favorites"] = "Favoris",
    ["Module Views"] = "Liste des modules",
    ["Modules By Category"] = "Par cat\195\169gorie",
    ["Display enabled modules by category. Modules can appear in more than one category."] = "DEV: Concept de vue",
    ["Do the actual chat replacement"] = "Effectuer les remplacements dans les messages",
    ["Allows 2.0 modules to make chat replacements. This is required for all modules colored blue."] = "Autoriser les modules 2.0 à remplacer dans les messages. Ceci est requis pour les modules affich\195\169s en bleu.",
    ["Run taint checks"] = "V\195\169rifier les variables douteuses",
    ["Run checks for taint on specific variables"] = "Active des v\195\169rifications pour certaines variables douteuses.",
    ["Hint: Select 'Favorite' in a module to add it here"] = "Astuce: S\195\169lectionner 'favori' dans un module pour l'ajouter ici",
    ["Favorite"] = "Favori",
    ["Place this module on the favorite's list, and show it on the main menu."] = "Ajoute ce module à votre liste des favoris et l'affiche dans le menu principal.",

    ["Channel"] = "Canaux",
    ["Chatframe"] = "Fen\195\170tre de chat",
    ["Buttons"] = "Boutons",
    ["Actions"] = "Actions",
    ["Text"] = "Texte",
    ["Names"] = "Noms",
    ["Links"] = "Liens",
    ["Behavior"] = "Comportement",
    ["Filtering"] = "Filtrage",
    ["Developer"] = "Developpeurs",
    ["Formatting"] = "Formattage",
    ["Info"] = "Informations",
} end)

Prat = AceLibrary("AceAddon-2.0"):new("AceConsole-2.0", "AceDB-2.0", "AceDebug-2.0", "AceEvent-2.0", "AceHook-2.1", "AceModuleCore-2.0")


Prat.revision = tonumber(string.sub("$Revision: 31419 $", 12, -3))
Prat:RegisterDB("PratDB", "PratPerCharDB")
Prat:SetModuleMixins("AceEvent-2.0", "AceHook-2.1",  "AceDebug-2.0")




-- Part of a system for modules to reports to the core via a callback
-- ENABLED/DISABLED are reported by default - they let the core handle moving the menu
Prat.Notifications = {
    ENABLED = "ENABLED", DISABLED = "DISABLED", UPDATED = "UPDATED"
}
Prat.Categories = {
    CHANNEL = L["Channel"],
    FRAME = L["Chatframe"],
    BUTTON = L["Buttons"],
    ACTION = L["Actions"],
    TEXT = L["Text"],
    NAME = L["Names"],
    LINK = L["Links"],
    FILTER = L["Filtering"],
    DEV = L["Developer"],
    INFO = L["Info"],
    BEHAVIOR = L["Behavior"],
    FORMATTING = L["Formatting"],
    COLORS = L["Colors"],
    SOUNDS = L["Sounds"],
}

-- Status flags.
Prat.Statuses = {
    CONFIRMEDTAINT = "CONFIRMEDTAINT", -- Module taints/or causes the client not to function
    TAINT = "TAINT",                    -- Module taints(maybe) - no harmful effect observed
    BROKEN = "BROKEN",                 -- This module is not fully functional
    BADINIT = "BADINIT",               -- This module did not init properly
    BADENABLE = "BADENABLE",           -- This module did not enable properly
    PRATEVENTS = "PRATEVENTS",          -- This module uses Prat events to process chat text
    NEEDSCATEGORIES = "NEEDCATS",       -- This says we still need some categories on this module
}

Prat.Colors = {
    BROKEN = "ff8080",
    CONFIRMEDTAINT = "ff2020",
    TAINT =  "ffff20",
    BADINIT = "ffff80",
    BADENABLE = "ffff80",
    PRATEVENTS = "8080ff",
    VERSION2 = "8080ff",
    NEEDSCATEGORIES = "ffa0ff",
    HIDDEN = "808080",
    CATEGORY = "8080ff",
    UPDATED = "60ff60"                 -- This will show which modules were updated since you last ran
}


Prat.VERSION_STRING = "Prat "..CLR:Colorize(Prat.Colors.VERSION2,"2.0").." - Beta 3 ("..CLR:Colorize(Prat.Colors.VERSION2, Prat.revision)..")"

Prat.sounds = {
	["TellMessage"] = "TellMessage",
}
	   

local builtinSounds = {
    ["popup"] = "Interface\\AddOns\\Prat\\sounds\\chatmod1.mp3",
    ["Bell"] = "Interface\\AddOns\\Prat\\sounds\\chatmod3.mp3",
	["Heart"] = "Interface\\AddOns\\Prat\\Sounds\\Heart.wav",
	["Kachink"] = "Interface\\AddOns\\Prat\\Sounds\\Kachink.wav",
	["Link"] = "Interface\\AddOns\\Prat\\Sounds\\Link.wav",
	["Text1"] = "Interface\\AddOns\\Prat\\Sounds\\Text1.wav",
	["Text2"] = "Interface\\AddOns\\Prat\\Sounds\\Text2.wav", 
	["Info"] = "Interface\\AddOns\\Prat\\Sounds\\Info.mp3"
}


local media = AceLibrary("SharedMedia-1.0")
function Prat:RegisterSound(name, path)
    Prat.sounds[name] = path
    
    media:Register("sound", name, path)
end


Prat.Favorites = {}
Prat.Updated = {}

Prat.Modules = {}

Prat.DisabledOptions = {
    order = 322,
    type = 'group',
    name = L["Disabled Modules"],
    desc = L["Disabled Modules"],
    args = {},
    hidden = true,
}



-- Define handler, this has the effect of making Fubar not display its normal options
Prat.Options = {
    name = "Prat",
    desc = L["A framework for chat frame modules."],
    type = "group",
    handler = Prat, 
    args = {


--[[
        optsep90 = {
            order = 90,
            type = 'header',
            hidden = function() return #Prat.Updated == 0 end
        },

        updatedmods = {
            order = 95,
            type = "group",
            name = L["Updated Modules"],
            desc = L["Updated Modules"],
            hidden = function()
                        if  #Prat.Updated == 0 then return true end

                         local a = Prat.Options.args.updatedmods.args
                         for i,v in ipairs(Prat.Updated) do
                            a["upt"..i] = v.moduleOptions
                         end
                    end,
            args = {},
         },
        optsep92 = {
            order = 97,
            type = 'header',
        },
]]
        optsep991 = {
            order = 98,
            type = 'header',
            name = L["Favorites"],
        },

        optsep309 = {
            order = 309,
            type = 'header',
        },
        optsep324 = {
            order = 317,
            type = 'header',
            name = L["Module Views"],
        },

        categorized = {
            order = 318,
            type = 'group',
            name = L["Modules By Category"],
            desc = L["Display enabled modules by category. Modules can appear in more than one category."],
            args = {}
        },

        enabledmods = {
            order = 319,
            type = 'group',
            name = L["Enabled Modules"],
            desc = L["Enabled Modules"],
            args = {}
        },

        togglemods = {
            order = 321,
            type = 'group',
            name = L["Module Control"],
            desc = L["Module Control"],
            args = {},          
        },
       
        optsep303 = {
            order = 344,
            type = 'header',
            hidden = function() return not Prat.db.profile.debug end,
        },
        debugOpts = {
            name = "Development Menu",
            desc = "Development Menu",
            order = 345,
            type = 'group',
            disabled = false,
            hidden = function() return not Prat.db.profile.debug end,
            args = {
                dumpSplit = {
                    name = "Dump the split table",
                    desc = "Dump the split table",
                    type = "toggle",
                    order = 304,
                    get = function() return Prat.db.profile.dumpSplit end,
                    set = function(v) Prat.db.profile.dumpSplit = v end,
                },
                dumpSplitPostEvent = {
                    type = "toggle",
                    order = 305,
                    name = "Dump the split table after the PreAddonMessage",
                    desc = "Dump the split table after the PreAddonMessage",
                    get = function() return Prat.db.profile.dumpSplitPostEvent end,
                    set = function(v) Prat.db.profile.dumpSplitPostEvent = v end,
                },
                dumpEventInfo = {
                    type = "toggle",
                    order = 306,
                    name = "Dump the event info",
                    desc = "Dump the event info",
                    get = function() return Prat.db.profile.dumpEventInfo end,
                    set = function(v) Prat.db.profile.dumpEventInfo = v end,
                },
                dumpOutputString = {
                    type = "toggle",
                    order = 307,
                    name = "Dump Output Info",
                    desc = "Dump the ouput string at each location it may change",
                    get = function() return Prat.db.profile.dumpOutputString end,
                    set = function(v) Prat.db.profile.dumpOutputString = v end,

                },
                forceDebugModules = {
                    type = "toggle",
                    order = 308,
                    name = "Force Debug On All Modules",
                    desc = "Force Debug On All Modules",
                    get = function() return Prat.db.profile.forceDebugModules end,
                    set = function(v) Prat.db.profile.forceDebugModules = v end,
                },
                forceDebugOffModules = {
                    type = "execute",
                    order = 308,
                    name = "Force Debug Off For All Modules",
                    desc = "Force Debug Off For All Modules",
                    func = function()
                            	for name,module in Prat:IterateModules() do
                                    module:SetDebugging(false)
                            	end
                           end,
                },
                debugSubsystem = {
                    type = "group",
                    order = 310,
                    name = "Toggle Subsystem Debugging",
                    desc = "Toggle Subsystem Debugging",
                    pass = true,
                    get  = function(x) return Prat.db.profile.sub_debug[x] end,
                    set  = function(x, y)  Prat.db.profile.sub_debug[x] = y  DBG:SetDebug(x, y) end,
                    args = {},

                },
            },
        },
    },
}

Prat.ConsoleOptions = {
    type = 'group',
    name = "Prat",
    handler = Prat, 
    desc = L["A framework for chat frame modules."],
    args = {
            enable = Prat.Options.args.togglemods,
    },
}

--
-- Register the /prat console command
--
Prat:RegisterChatCommand({"/prat"}, Prat.ConsoleOptions)
Prat:RegisterChatCommand({"/pratfu"}, Prat.Options)

function Prat:OnInitialize()
    Prat:RegisterDefaults("profile", {
        debug = false,
        dumpSplit = false,
        dumpEventInfo = false,
        doReplace = true,
        dumpSplitPostEvent = false,
        dumpOutputString = false,
        integratedDebugger = false,
        forceDebugModules = false,
        sub_debug = {["*"]=false},

        mod_versions = {["*"]=0}
    })

    local d = self.db.profile.sub_debug
    DBG:SetDebug("PATTERN", d["PATTERN"])
    DBG:SetDebug("SPLIT",d["SPLIT"])
    DBG:SetDebug("OUTPUT",d["OUTPUT"])
    DBG:SetDebug("MODULE",d["MODULE"])

    self.dumping = false

    self:SetDebugging(self.db.profile.debug)

    for k,v in pairs(self.db.profile.sub_debug) do
        DBG:SetDebug(k, v)
    end
   
   local o = Prat.Options.args
   for k,v in pairs(o) do
        if o[k].type == "group" 
            and type(o[k].disabled) == "nil" 
            and type(o[k].handler) == "nil" then 
            
            o[k].disabled = o[k].disabled or "~IsActive"
            o[k].handler = Prat
        end
   end
    
    -- Register the built in sounds so that all sounds
    -- are registered through the same method
    for k,v in pairs(builtinSounds) do
        self:RegisterSound(k, v)
    end
    
    self:RegisterEvent("Prat_ModuleEnabled")    
    self:RegisterEvent("Prat_ModuleDisabled")   

    self.Registered = {}
    self.debugdata = {}
    for i = 1, NUM_CHAT_WINDOWS do
        self.debugdata[i] = {}
    end
    -- This event says that Prat has initialized and will be starting
    -- up soon (Modules will not see this)
    self:TriggerEvent("Prat_Initialized")
    self:BuildCategoryOptionsGroups()
end

function Prat:OnEnable()
    self:ScheduleEvent("Prat_PostEnable", self.PostEnable, 0.1, self)
    DEFAULT_CHAT_FRAME:AddMessage("---------- "..Prat.VERSION_STRING .." ----------")
end

function Prat:OnDisable()
	-- Disable all modules
	local name, module = nil, nil
	for name, module in self:IterateModules() do
		self:ToggleModuleActive(module, false)
	end

	self:TriggerEvent("Prat_Disabled")
	self:UnregisterAllEvents()
end


function Prat:PostEnable()
 	-- Enable all modules
	local name, module = nil, nil
	for name, module in self:IterateModules() do
	    if not self:IsModuleActive(module) then 
    		self:ToggleModuleActive(module, true)
    	end
	end

    -- Inbound Hooking
    self:Hook("ChatFrame_MessageEventHandler", true)

    -- Outbound hooking
    self:SecureHook("ChatEdit_ParseText")

    -- Display Hooking
    for i = 1, NUM_CHAT_WINDOWS do
        self:Hook(getglobal("ChatFrame"..i), "AddMessage", true)
    end


    if not self.debugdata then  self.debugdata = {} end
    self.dumping = false
    -- This event fires after Prat's hooks are installed
    -- Prat's core wont operate until after this event
    self:TriggerEvent("Prat_Ready")
    
end

function Prat:BuildCategoryOptionsGroups()
    local base = Prat.Options.args.categorized.args

    for k,v in pairs(Prat.Categories) do
        base[k] = { name = v, desc = v, type = "group", args = {},
        hidden =
            function()
                local t = base[k].args
                local hide = true
                for k,v in pairs(t) do
                    hide = false
                end
                return hide
            end
         }
    end
end


Prat.SplitMessageOut = {
    MESSAGE = "",
    TYPE = "",
    TARGET = "",
    CHANNEL = "",
    LANGUAGE = "",
}

function Prat:ChatEdit_ParseText(editBox, send)
    local command = editBox:GetText()
    
	local cmd = command:match("^/[%a%d_]+")
    if cmd and IsSecureCmd(cmd) then
        DBG_PATTERN("The command '"..cmd.."' is secure.")
		return
	end
    
    
    local m = Prat.SplitMessageOut
    m.MESSAGE = command 

    m.CTYPE = editBox:GetAttribute("chatType")
    m.TARGET = editBox:GetAttribute("tellTarget")
    m.CHANNEL = editBox:GetAttribute("channelTarget")
    m.LANGUAGE = editBox.language
    m.SEND = send
--    m.HISTORY = addHistory


    if send ~= 1 then
        return 
    end
    
    DUMP_SPLIT("Pre_Process", m)
    Prat:ProcessUserEnteredChat(m) 
    DUMP_SPLIT("Post_Process", m)
    
    editBox:SetAttribute("chatType", m.CTYPE)
    editBox:SetAttribute("tellTarget", m.TARGET)
    editBox:SetAttribute("channelTarget", m.CHANNEL)
    
    
    editBox:SetText(m.MESSAGE)
end


function Prat:ProcessUserEnteredChat(m)

    
    if (strlen(m.MESSAGE) <= 0) then
    	return
    end

--    m.CTYPE = editBox:GetAttribute("chatType")
--    m.TARGET = editBox:GetAttribute("tellTarget")
--    m.CHANNEL = editBox:GetAttribute("channelTarget")
--    m.LANGUAGE = editBox.language
--    m.HISTORY = addHistory
    
    self:TriggerEvent("Prat_PreOutboundChat", m)


    -- Remove all the pattern matches ahead of time
    m.MESSAGE = self:MatchPatterns(m.MESSAGE, "OUTBOUND")

    self:TriggerEvent("Prat_OutboundChat", m)

   -- Pattern Matches Put Back IN
    m.MESSAGE = self:ReplaceMatches(m.MESSAGE, "OUTBOUND")

    
end



local tmp_info = {}


function Prat:UpdateDisabledModulesMenu()
    for k,v in pairs(Prat.Options.args) do
        if self:HasModule(k) then
            if not self:IsModuleActive(k) then
                Prat.DisabledOptions.args[k] = v
                Prat.Options.args.eabledmods.args[k] = nil
            end
        end
    end
end



Prat.PatternRegistry = {}
Prat.PatternOwners = {}

-- Register a pattern with the pattern matching engine
-- You can supply a priority 1 - 100. Default is 50
-- 1 = highest, 100 = lowest.
-- pattern = { pattern, matchfunc, priority, type}
--
-- Priorities arent used currently, they are to help with
-- collisions later on if there are alot of patterns
--
function Prat:RegisterPattern(pattern, who)
    table.insert(self.PatternRegistry, pattern)

    DUMP_PATTERN("RegisterPattern", who, pattern)

    self.PatternRegistry[pattern] = who
end


function Prat:UnregisterAllPatterns(who)
    DBG_PATTERN("UnregisterAllPatterns", who)

    for k, owner in pairs(self.PatternOwners) do
        if owner == who then
            table.remove(self.PatternRegistry, k)
        end
    end
end

function Prat:UnregisterPattern(idx)
    table.remove(self.PatternRegistry, idx)
end

function Prat:RegisterMatch(text, ptype)
    if type(ptype) == "nil" then
        ptype = "FRAME"
    end

    local token = "@##"..Prat.tokennum.."##@"

    DBG_PATTERN("RegisterMatch", text, token)

    if type(Prat.MatchTable[ptype]) ~= "table" then 
        Prat.MatchTable[ptype] = {}
    end
    
    local mt = Prat.MatchTable[ptype]
    mt[token] = text
    Prat.tokennum = Prat.tokennum + 1
    return token
end

function Prat:MatchPatterns(text, ptype)
    if type(ptype) == "nil" then
        ptype = "FRAME"
    end
    
    Prat.tokennum = 1

    DBG_PATTERN("MatchPatterns -->", text, Prat.tokennum)

    -- Match and remove strings
    for _, v in ipairs(self.PatternRegistry) do
        if text and ptype == (v.type or "FRAME") then
            text = string.gsub (text, v.pattern, v.matchfunc)
        end
    end

    DBG_PATTERN("MatchPatterns <--", text, Prat.tokennum)

    return text
end

Prat.MatchTable = {}
function Prat:ReplaceMatches(text, ptype)
    if type(ptype) == "nil" then
        ptype = "FRAME"
    end
    
    -- Substitute them (or something else) back in

    if type(Prat.MatchTable[ptype]) ~= "table" then 
        Prat.MatchTable[ptype] = {}
    end
    local mt = Prat.MatchTable[ptype]

    DBG_PATTERN("ReplaceMatches -->", text)
    for k,v in pairs(mt) do
        text = string.gsub(text, k, v)
        mt[k] = nil
    end

    DBG_PATTERN("ReplaceMatches <--", text)

    return text
end



















Prat.last = {}

function Prat:ClearChatSections(message)
    for k,v in pairs(self.SplitMessageSrc) do
        message[k] = ""
    end
end


local t = {}
function Prat:BuildChatText(message)
    local s = message

    for i,v in ipairs(self.SplitMessageIdx) do
        if type(s[v]) == "string" then
            table.insert(t, s[v])
        end
    end

    local txt = table.concat(t, "")
    s["CONCAT"] = txt

    for k in pairs(t) do
        t[k] = nil
    end

    return txt
end




local eventMap = {
    CHAT_MSG_CHANNEL = true,
    CHAT_MSG_SAY = true,
    CHAT_MSG_GUILD = true,
    CHAT_MSG_WHISPER = true,
    CHAT_MSG_WHISPER_INFORM = true,
    CHAT_MSG_YELL = true,
    CHAT_MSG_PARTY = true,
    CHAT_MSG_OFFICER = true,
    CHAT_MSG_RAID = true,
    CHAT_MSG_RAID_LEADER = true,
    CHAT_MSG_RAID_WARNING = true,
    CHAT_MSG_BATTLEGROUND = true,
    CHAT_MSG_BATTLEGROUND_LEADER = true,
    CHAT_MSG_SYSTEM = true,
}


function Prat:ChatFrame_MessageEventHandler(event)
    local message, info
    local process = eventMap[event]

    local CMEResult
    
    if self:IsDebugging() then
        self.last = self.last or {}

        local dbg = self.debugdata[this:GetID()]
        dbg.EventInfo = dbg.EventInfo or {}

        self.last.event = event
        local de = dbg.EventInfo
        de.event = event
        for i=1,10 do
            self.last["arg"..i] = getglobal("arg"..i)
            de["arg"..i] = getglobal("arg"..i)
        end

        --self:Debug(event, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10)
        if self.db.profile.dumpEventInfo then
            self:ConsoleDump(this, nil, "Event Args", nil, Prat.last)
        end
    end

    -- This is here temporarily so we dont process the combatlog
--    if strsub(event, 1, 4) == "CHAT" then
        message, info = self:SplitChatMessage(this, event)
--    end

    if not info then
       return self.hooks["ChatFrame_MessageEventHandler"](event)
    else
        local m = Prat.SplitMessage

        -- Prat_FrameMessage is fired for every message going to the
        -- chatframe which is displayable (has a chat infotype)
        -- It may not be displayed, in which case no Pre/Post Addmessage
        -- events will fire
        -- Any addons which hook things will operate following this event
        -- but before Prat_PreAddMessage, OUTPUT will contain the chat line
        -- it may be modified by other addons.
        --
        -- Right now, prat will discard the chat line for chat types that
        -- it is handling
        --
        m.OUTPUT  = nil
        m.CAPTUREOUTPUT = true
        m.DONOTPROCESS = nil

        if self.db.profile.dumpOutputString then
            self:ConsoleDump(this, "Prat_FrameMessage", nil, m.CAPTUREOUTPUT, m.OUTPUT)
        end

        self:TriggerEvent("Prat_FrameMessage", message, this)

        -- A return value of true means that the message was processed
        -- normally this would result in the OnEvent returning
        -- for that chatframe
        CMEResult = self.hooks["ChatFrame_MessageEventHandler"](event)

        DBG_OUTPUT("CMEResult", CMEResult)
        if m.OUTPUT and not m.DONOTPROCESS then
            local r,g,b,id = self.INFO.r, self.INFO.g, self.INFO.b, self.INFO.id

            -- Remove all the pattern matches ahead of time
            m.MESSAGE = self:MatchPatterns(m.MESSAGE)

            self:DumpOutputString("Prat_PreAddMessage", m)
            self:TriggerEvent("Prat_PreAddMessage", message, this, self:BuildChatText(message), r,g,b,id )

            if self:IsDebugging() then

                DUMP_PATTERN(self.MatchTable)

                local d = self.debugdata[this:GetID()]
                d.PreAddMsgSplit = {}
                d.PreAddMsgSplitIDX = {}
                util:copyTable(Prat.SplitMessageIdx, d.PreAddMsgSplitIDX)
                util:copyTable(m, d.PreAddMsgSplit)
                local sd = d.PreAddMsgSplit
                -- nil stuff out to reduce spam
                for k,v in pairs(sd) do
                    if (v == "") then
                        sd[k] = nil
                    else
                        sd[k] = v
                    end
                end
                if self.db.profile.dumpSplitPostEvent then
                    self:ConsoleDump(this, "After Prat_PreAddMessage", d.PreAddMsgSplitIDX, d.PreAddMsgSplit)
                end
            end

            -- Pattern Matches Put Back IN
            m.MESSAGE = self:ReplaceMatches(m.MESSAGE)

            -- If process is set we have a split table
            -- If not then we have 1 string for the whole chatline
            if process then
                -- We are about to send the message

                m.OUTPUT  = self:BuildChatText(message) -- Combine all the chat sections
                m.CAPTUREOUTPUT = false

                self:DumpOutputString("AddMessage - build", m)

                this:AddMessage(m.OUTPUT, r,g,b,id);
            else
                if m.OUTPUT then
                    -- Now we have the chatstring that the client was planning to output
                    -- For now just do it. (Tack on POST too)
                    m.OUTPUT  = (m.PRE or "")..m.OUTPUT..(m.POST or "")

                    self:DumpOutputString("AddMessage - pass", m)


                    this:AddMessage(m.OUTPUT, r,g,b,id);
                end
            end

            -- We are about to send the message

            self:DumpOutputString("Prat_PostAddMessage", m)

            self:TriggerEvent("Prat_PostAddMessage",  m, this, m.OUTPUT, r,g,b,id)
        end

        m.CAPTUREOUTPUT = nil
        m.OUTPUT  = nil
        m.INFO = nil
    end
    
    return CMEResult
end


--function Prat:AddMessage(frame, text, r, g, b, id)
Prat.INFO = {r = 1.0, g = 1.0, b = 1.0, id = 0 }

function Prat:AddMessage(frame, text, r, g, b, id)
    local s = Prat.SplitMessage
    if s.OUTPUT == nil and s.CAPTUREOUTPUT ~= nil and Prat.dumping == false then
        self.INFO.r, self.INFO.g, self.INFO.b, self.INFO.id = r, g, b, id
        s.OUTPUT = text
        Prat.SplitMessage["INFO"] = Prat.INFO
    else
        Prat.hooks[frame].AddMessage(frame, text, r, g, b, id)
    end
end

function Prat:DumpOutputString(label, m)
    if self.db.profile.dumpOutputString then
        local so = m.OUTPUT
        local sc = m.CAPTUREOUTPUT

        self:ConsoleDump(this, label,  nil, m.CAPTUREOUTPUT, m.OUTPUT)

        if so == nil and sc ~= nil then
        else
            m.OUTPUT = so
            m.CAPTUREOUTPUT = sc
        end
    end
end

-- This is the structure of the chat message once it is split
-- section delimiters are uppercase inside and lower case outside
-- ie.   cC CHANNEL Cc =  [ channame ]
Prat.SplitMessageSrc = {
    PRE = "",

    cC = "",
        CHANNELNUM = "",
        CC = "",
        CHANNEL = "",
        zZ = "",
            ZONE = "",
        Zz = "",
    Cc = "",

    TYPEPREFIX = "",

    fF = "",
        FLAG = "",
    Ff = "",

    pP = "",
        lL = "",  -- link start
        PLAYERLINK= "",
        LL = "", --  link text start
        PLAYER = "",

        sS = "",
            SERVER = "",
        Ss = "",
        Ll = "",  -- link text end
    Pp = "",

    TYPEPOSTFIX = "",

    mM = "",
        gG = "",
        LANGUAGE = "",
        Gg = "",
        MESSAGE = "",
    Mm = "",

    POST = "",
}

-- All the separators owned by an info-item
-- this is case insensitive, so "xx" will include "XX", "xX", "Xx", "xx"
-- This can be used to empty the separator nodes when the main info-item
-- is empty or gets emptied
Prat.Separators = {
    ZONE = "zz",
    SERVER = "ss",
    MESSAGE = "mm",
    LANGUAGE = "gg",
    PLAYERLINK = "ll",
    FLAG = "ff",
    CHANNEL = "cc",
    PLAYER = "pp",
}

-- Remove an info-item from the split
function Prat:ClearSplitItem(split, item)
    if item and split and split[item] then
        local seps = Prat.Separators[item]

        split[item] = ""
        if seps then
            for k,v in pairs(split) do
                if strlower(k) == seps then
                    v = ""
                end
            end
        end
    end
end


function Prat:RegisterMessageItem(itemname, aftervar)
	--[[ RegisterMessageItem:

		API to allow other modules to inject new items into the components
		making up a chat message. Primarily intended to help resolve
		conflicts between modules.

		 - itemname  = name of the variable to be injected

		 - aftervar  = the position in the chat message after which the item
		               will be displayed

		Leave aftervar blank to position the item at the beginning of the list.

		If you would like to change the item's position in the chat message,
		call :RegisterMessageItem() again with a different value for aftervar.

		Example:
		--------

		The mod Prat_ExampleMod counts the number of times people
		say the word "Example" and you would like to display the count
		for a player before their name in a chat message. Default chat
		message structure contains:

			... cC CHANNEL Cc .. pP PLAYER Pp ...

		This means that the module should use the following:

			Prat:RegisterMessageItem('NUMEXAMPLES', 'Cc')

		Which would then alter the structure of chat messages to be:

			.. CHANNEL Cc .. NUMEXAMPLES .. pP PLAYER ...

]]

	local pos = 1

	if Prat.SplitMessageSrc[itemname] then
		self:ResetSeparators(itemname)

		local oldpos = self:GetMessageItemIdx(itemname)

		if oldpos ~= 0 then
			table.remove(Prat.SplitMessageIdx, oldpos)
		end
	end

	pos = aftervar and self:GetMessageItemIdx(aftervar) + 1 or pos

	table.insert(Prat.SplitMessageIdx, pos, itemname)
	Prat.SplitMessageSrc[itemname] = ""
end


function Prat:UnregisterMessageItem(itemname)
	self:ResetSeparators(itemname)

	Prat.SplitMessageSrc[itemname]	= nil
	Prat.SplitMessage[itemname]	= nil

	table.remove(Prat.SplitMessageIdx, self:GetMessageItemIdx(itemname))

	-- Prat.Seperators[itemname]	= nil
end


function Prat:ResetSeparators(itemname)
	if Prat.Separators[itemname] then
		local sepstr = string.lower(Prat.Separators[itemname])

		for i, v in ipairs(Prat.SplitMessageIdx) do
			if v:lower() == sepstr then
				Prat.SplitMessage[v]	= nil
				Prat.SplitMessageSrc[v]	= nil
				table.remove(Prat.SplitMessageIdx, i)
			end
		end
	end
end

function Prat:GetMessageItemIdx(itemname)
	for i, v in ipairs(Prat.SplitMessageIdx) do
		if v == itemname then
			return i
		end
	end

	return 0
end




Prat.SplitMessageIdx = {
    "PRE",
    "cC",
        "CHANNELNUM",
        "CC",
        "CHANNEL",
-- Zone is not usually included
--      "zZ",
--          "ZONE",
--      "Zz",

    "Cc",
    "TYPEPREFIX",
    "fF",
        "FLAG",
    "Ff",
    "pP",
        "lL",
            "PLAYERLINK",
        "LL",
        "PLAYER",
        "sS",
            "SERVER",
        "Ss",
        "Ll",
    "Pp",
    "TYPEPOSTFIX",
    "mM",
        "gG",
        "LANGUAGE",
        "Gg",
        "MESSAGE",
    "Mm",
    "POST",
}

Prat.SplitMessage = {}




-- Support for segmenting of the chat message to prevent unwanted module interactions
-- The message segments will be available from Prat_PreAddMessage to Prat_PostAddMessage
--
-- Segments are PRE, #C CHANNEL C#, #P PLAYERNAME #S SERVERNAME S# P#, #M MESSAGE M#, POST
--
-- Basic patterns will be used to try and split the chat into those sections
--
-- PRE/POST are for modules to use
--
-- The #'s are also segments, they contain the text surrounding the given element, they
-- should only contain values if the segment eg CHANNEL is not "".
--
-- After Prat_PreAddMessage, the current segments are concatatnated and passed to AddMessage
--
-- Access to the final result string is available during the Prat_PostAddMessage event

-- body = format(TEXT(getglobal("CHAT_"..type.."_GET"))..arg1, pflag..arg2);

local function safestr(s) return s or "" end


function Prat:SplitChatMessage(frame, event)
    self:ClearChatSections(self.SplitMessage)

    if ( strsub(event, 1, 8) == "CHAT_MSG" ) then
        local type = strsub(event, 10);
        local info = ChatTypeInfo[type];

        local s = self.SplitMessage
        
        s.MESSAGE = safestr(arg1)

        local chatget = getglobal("CHAT_"..type.."_GET")
        if chatget then
            s.TYPEPREFIX = gsub(TEXT(chatget), "(.*)%%s(.*)", "%1") or ""
            s.TYPEPOSTFIX = gsub(TEXT(chatget), "(.*)%%s(.*)", "%2") or ""
        end


        arg2 = safestr(arg2)
        if strlen(arg2) > 0 then
 
        	if ( strsub(type, 1, 7) == "MONSTER" or type == "RAID_BOSS_EMOTE" ) then
        		-- no link
        	else
               local plr, svr = strsplit("-", arg2)

                s.pP = "["
                s.lL = "|Hplayer:"
                s.PLAYERLINK = arg2
                s.LL = "|h"
                s.PLAYER = plr
    
                if svr and strlen(svr) > 0 then
                    s.sS = "-"
                    s.SERVER = svr
                end
    
                s.Ll = "|h"
                s.Pp = "]"
            end
        end

        arg6 = safestr(arg6)
        if strlen(arg6) > 0 then
            s.fF = ""
            s.FLAG = TEXT(getglobal("CHAT_FLAG_"..arg6))
            s.Ff = ""
        end

        arg3 = safestr(arg3)
        if ( (strlen(arg3) > 0) and (arg3 ~= "Universal") and (arg3 ~= this.defaultLanguage) ) then
            s.gG = "["
            s.LANGUAGE = arg3
            s.Gg = "] "
        else
            s.LANGUAGE_NOSHOW = arg3
        end

        arg9 = safestr(arg9)
        if strlen(arg9) > 0 then
            local bracket = gsub(s.TYPEPREFIX, "%[(.*)%](.*)", "%1")
            if strlen(bracket) > 0 then
                s.cC = "["
                s.Cc = "]"
                s.CHANNEL = bracket
                s.TYPEPREFIX = gsub(s.TYPEPREFIX, "%[(.*)%](.*)", "%2")
            end


            if strlen(safestr(arg8)) > 0 and arg8 > 0 then
                s.CHANNELNUM = tostring(arg8)
                s.CC = ". "
            end

            if arg7 > 0 then
                s.cC = "["
                s.Cc = "] "
                s.CHANNEL = gsub(arg9, "(.*)(%s%-%s)(.*)", "%1")
                s.zZ = gsub(arg9, "(.*)(%s%-%s)(.*)", "%2")
                s.ZONE = gsub(arg9, "(.*)(%s%-%s)(.*)", "%3")
                s.Zz = ""
            else
                if strlen(arg9) > 0 then
                    s.CHANNEL = arg9
                    s.cC = "["
                    s.Cc = "] "
                end
            end
        end

        if type == "SYSTEM" then
            local pl, p, rest = string.match(s.MESSAGE, "|Hplayer:(.-)|h%[(.-)%]|h(.+)")
            if pl and p then
                local plr, svr = strsplit("-", pl)
                s.pP = "["
                s.lL = "|Hplayer:"
                s.PLAYERLINK = pl
                s.LL = "|h"
                s.PLAYER = plr
                s.Ll = "|h"
                s.Pp = "]"
                s.MESSAGE = rest

                if svr and strlen(svr) > 0 then
                    s.sS = "-"
                    s.SERVER = svr
                end

            end
        end

        if self:IsDebugging() then
            local c = ""


            --          c = s.PRE..s.cC..s.CHANNELNUM..s.CC..s.CHANNEL..s.Cc..s.TYPEPREFIX..s.fF..s.FLAG..s.Ff
            --          c = c..s.pP..s.lL..s.PLAYERLINK..s.LL..s.PLAYER..s.sS..s.SERVER..s.Ss..s.Ll..s.Pp
            --          c = c..s.TYPEPOSTFIX..s.mM..s.MESSAGE..s.Mm..s.POST

            --          s.CONCAT = c

            -- This is how we are going to build the actual string
            local t = {}
            for i,v in ipairs(self.SplitMessageIdx) do
                table.insert(t, s[v])
            end
            s["CONCAT"] = table.concat(t, "")

            local sd = self.debugdata[frame:GetID()]

            sd.Split = {}
            sd.SplitIDX = {}
            util:copyTable(self.SplitMessage, sd.Split)
            util:copyTable(self.SplitMessageIdx, sd.SplitIDX)
            -- nil stuff out to reduce spam
            for k,v in pairs(sd.Split) do
                if (v == "") then
                    sd.Split[k] = nil
                else
                    sd.Split[k] = v
                end
            end
            if self.db.profile.dumpSplit then

                self:ConsoleDump(this, "Unprocessed Split", sd.SplitIDX, sd.Split)
            end
            --self:Debug(s.CONCAT)
        end


        return self.SplitMessage, info
    end
end



--
-- Process the message data that is passed like it is from the chat frame.
-- Handle it a bit different since it isnt
--
--
Prat.ExternalFramePlaceHolder = ChatFrame7

Prat.SampleExtMsg = { 
    pP = "[",
    lL = "|Hplayer:",
    PLAYERLINK = "Bar",
    LL = "|h",
    PLAYER = "Bar",
    Ll = "|h",
    Pp = "]",
    MESSAGE = "Foo",
    LANGUAGE_NOSHOW = "Common",
}
-- [Bar]: Foo


-- /print Prat:TestRender()

function Prat:TestRender()
    event = CHAT_MSG_GUILD 
    return Prat:RenderAsChatString(Prat.SampleExtMsg)
end

function Prat:RenderAsChatString(message)
    -- Perform any fixups here
    local type = strsub(event, 10);

    if type == "CHANNEL" then 
        message.cC = "["
        message.Cc = "] "
        if message.CHANNELNUM ~= nil then 
            message.CHANNELNUM = tostring(message.CHANNELNUM)
            message.CC = ". "
        end
    end    
    
    if message.TYPEPREFIX == nil or message.TYPEPOSTFIX == nil then
       local chatget = getglobal("CHAT_"..type.."_GET")
 
        if chatget then
            message.TYPEPREFIX = gsub(TEXT(chatget), "(.*)%%s(.*)", "%1") or ""
            message.TYPEPOSTFIX = gsub(TEXT(chatget), "(.*)%%s(.*)", "%2") or ""
        end
    end

    -- Need the color info if possible
    --Prat.INFO = {r = 1.0, g = 1.0, b = 1.0, id = 0 }
   -- message["INFO"] = Prat.INFO (pass a reference to a color table)

    -- Ensure the standard stuff isnt nil
    for k,v in pairs(self.SplitMessageSrc) do
        message[k] = message[k] or ""
    end

    self:TriggerEvent("Prat_FrameMessage", message, self.ExternalFramePlaceHolder)
    self:TriggerEvent("Prat_PreAddMessage", message, self.ExternalFramePlaceHolder, self:BuildChatText(message), r,g,b,id )
    
    message.OUTPUT  = self:BuildChatText(message)
    
    self:TriggerEvent("Prat_PostAddMessage",  message, self.ExternalFramePlaceHolder, message.OUTPUT, r,g,b,id)
    
    return message.OUTPUT
end



-- Do the /dump slash command
function Prat:ConsoleDump(frame, name, order, a1, ...)
    local s
    if select('#', ...) == 0 then
        s = util:literal_tostring(a1, true, order and util:merge_keys(order, a1))
    else
        s = (", "):join(util:literal_tostring_args_ordered(order, a1, ...))
    end

    Prat.dumping = true
    util:print(s, self, r, g, b, frame or self.printFrame, delay)
    Prat.dumping = false
end

function Prat:ConsoleDebugDump(what, ...)
    if Prat:IsDebugging() then
        Prat.dumping = true
        Prat:ConsoleDump(nil, nil, nil, what, ...)
        Prat.dumping = false
    end
end


function Prat:OnDebugEnable()
    self.db.profile.debug = true
    self:Debug("Debug - ON")

    self:TriggerEvent("Prat_DebugModeChanged")
end

function Prat:OnDebugDisable()
    self:Debug("Debug - OFF")
    self.db.profile.debug = false

    Prat:ScheduleEvent(self.UpdateDebug, 0, self)
end

function Prat:UpdateDebug()
    self:TriggerEvent("Prat_DebugModeChanged")
end
function Prat:DumpSplit(b)
    self.db.profile.dumpSplit = b
end




function CLR:Category(text) return self:Colorize(Prat.Colors.CATEGORY, text) end

function Prat:RegisterModule(name, module)
    local modkey = module.name
    local ps = Prat.Statuses

  --  self:Debug("RegisterModule() - module="..modkey..", name="..name..", command="..(module.consoleCmd or name))

    -- Did they request the name? Check who its assigned to
    -- this doesnt do any mapping right now ( it just checks that the functino was called for the module name)
    local name_owner = self.Modules[modkey]


    if name_owner ~= module.name then
        DBG_MODULE("RegisterModule() NAME ERROR - name: "..name.."   owner: "..(name_owner or "nil"))
        module.status = "BROKEN"
    else
        if self.Registered[modkey] then
            -- Oh No, Registering again is a no-no!!
            DBG_MODULE("RegisterModule() DUPLICATE REGISTRATION ERROR - name: "..name)
            modules.status = "BROKEN"
        else
            self.Registered[modkey] = true
        end
    end

    --
    -- Set up DB
    --
    local revision = type(module.revision) == "number" and module.revision or -1
    if self.revision < revision then
        self.revision = revision
    end
    -- TODO: store this by what?
    if self.db.profile.mod_versions[modkey] < revision then
--        table.insert(self.Updated, module)
    end

    if module.db then
        DBG_MODULE("About to do something bad for - "..module.name)
        module:RegisterDefaults("profile", module.defaultDB or {})
    else
        module.db = self:AcquireDBNamespace(modkey)
        self:RegisterDefaults(modkey, "profile", module.defaultDB or {})
    end

    --
    -- DB is ready so, now lets get the options table
    --
    local opts = module:GetModuleOptions() --or module.moduleOptions
    local auto = module:GetModuleAutoOptions() --or module.toggleOptions

    -- Get the module's localized strings
    local mL = AceLibrary("AceLocale-2.2"):new(modkey)

    -- Assure the the module options root is correct
    opts = opts or {}

    -- Now make sure the module has a reference to it
    module.moduleOptions = module.moduleOptions or opts

    -- This is plain stupid they should not put up errors dammit
    --local name = mL:HasTranslation("module_name") and L["module_name"]
    --local desc = mL:HasTranslation("module_desc") and L["module_desc"]
    opts.name = opts.name or name or module.moduleName or module.name
--    if module.consoleName then
--        opts.guiName = opts.guiName or module.guiName or opts.name
--        opts.name = nil
--        opts.cmdName = module.consoleName
---    end
    opts.desc = opts.desc or module.moduleDesc or module.name
    opts.type = opts.type or "group"
    opts.args = opts.args or {}
    
    opts.disabled = opts.disabled or 
            function() 
                local lm = module
                return not Prat:IsModuleActive(lm) 
            end


    -- If all the module's options are just toggles, then build
    -- the options table automatically
    if auto then
        self:BuildAutoOptions(name, module, mL, opts, auto)
    end

    -- Add options that go into every module
    self:InjectModuleCommonOptions(opts, module)


        -- Now add it to Prat's Disabled options table
        local D = Prat.DisabledOptions.args
        if not module.moduleHidden then
            local T = Prat.Options.args.togglemods.args
            
            D[modkey] = opts
            T[modkey] = {}
            local stub = T[modkey]
            local toggle = opts.args[L["toggle"]]
        
            stub.type = toggle.type
            stub.desc = toggle.desc
            stub.name = opts.name   
            stub.get = toggle.get
            stub.set = toggle.set
             
        
            -- Also add it to the console options table
            local conopts = Prat.ConsoleOptions.args
            local conKey = module.consoleName or modkey 
            conopts[conKey] = opts    
        end
    
        local o = D[modkey]
        -- Make sure we have a copy of the modules name
        if o then
            o.name_org = o.name_org or o.name
        end

end





function Prat:ModuleNotify(notification, module)
    local n = Prat.Notifications

--    local  LOG= function(...) DBG:Debug("MODULE", "ModuleNotify", ...) end

    DBG_MODULE("ModuleNotify", notification, module, module.status)

    local o = module.moduleOptions

    if notification == n.ENABLED then
        if not module.enableCheck then
            if module.status == nil then module.status = "BADENABLE" end
        end
        module.enableCalled = true
        self:TriggerEvent("Prat_ModuleEnabled", module)
    elseif notification == n.DISABLED then
        module.enableCalled = false
        self:TriggerEvent("Prat_ModuleDisabled", module)
    elseif notification == n.UPDATED then
        self:UpdateMetaMenus(module, module.name, module.moduleOptions, true)
    end
end

function Prat:InjectModuleCommonOptions(opts, module)
    local m = module

    if  opts.hidden == nil  and  m.moduleHidden then
        opts.hidden = function() return not(Prat:IsDebugging()) and  m.moduleHidden end
    end

    if m then
        opts.handler = m
    end

    opts.args[L["toggle"]] = {
        type = "toggle",
        name = L["Toggle"],
        desc = L["Toggle the module on and off."],
        order = 405,
        get = function() return m.db.profile.on  end,
        set = function(v) m.db.profile.on = v m.core:ToggleModuleActive(m, v) end,
        hidden = m.moduleHidden,
    }

    opts.args["favorite"] = {
        type = "toggle",
        name = L["Favorite"],
        desc = L["Place this module on the favorite's list, and show it on the main menu."],
        order = 405,
        get = function() return m.db.profile.favorite  end,
        set = function(v) m.db.profile.favorite = v m.core:ModuleNotify(Prat.Notifications.UPDATED, m) end,
        disabled = function() return not Prat:IsModuleActive(module) end,
        hidden = m.moduleHidden,       
    }


    opts.args[L["debug"]] = {
        type = "toggle",
        name = L["Debugging"],
        desc = L["Show debug messages."],
        order = 401,
        get = function() return m:IsDebugging() end,
        set = function(v) m:SetDebugging(v) end,
        hidden = function() return  not Prat:IsDebugging() end,
        disabled = function() return not Prat:IsModuleActive(module) end
    }

    local n = 0
    for k,v in pairs(opts.args) do
        n = n + 1
    end

    if n > 3 and not m.moduleHidden then
        opts.args.headerCOspacer = {
        type = "header",
        order = 400,
        }
    end
end


-- Build an options table from the module defaults
-- empty tables are assumed to be per-chatframe options
-- and are filled with toggles for each chatframe, and
-- the individual/all option is added automatically

--
--
-- This got complicated. Its meant to be a simple way to have
-- your menus created, by defining values in the toggleOptions table
-- Each entry in the table is examined by name and type to match
-- a template menu pattarn and add it to the options menu
--
--  _sep = order
--  _options = { opt1 = "opt1" ... }
--  _pass = { sub_toggleOptions }
--  _handler , will make per-frame options that call a handler
--            func eg foo_handler  calls foo(idx, val)
--
--  name = type(number) -> toggle w/ order
--  name = type(boolean) -> toggle
--
--  name = {} empty table per-frame toggles
--
--  most of them set name and desc to foo_name, foo_desc and use that
--  to get the localized string. some also use foo_perframename foo_perframedesc
--
--  concept and nameing borrowed copied from bigwigs ora2
--
function Prat:BuildAutoOptions(name, module, moduleLocals, opts, togopts)
    local mL = moduleLocals
    local m = module
    local n = 0
    for k,v in pairs(togopts) do
        n = n + 1
        local val = v

        local optname, opttype = strsplit("_", k)

        if opttype then
            if opttype == "sep" then
                local ord = n
                if type(val) == "number" then
                    ord = val
                end

            	opts.args[optname..ord]  = {
    				order = ord,
    				type = 'header',
    			}
            elseif opttype == "options" then
                opts.args[optname] = {
                    name = mL[optname.."_name"],
                    desc = mL[optname.."_desc"],
                    type = "text",
                    get = function() return m.db.profile[optname] end,
                    set = function(v) m.db.profile[optname] = v end,
                    validate = {},
                }

                for k2,v2 in pairs(val) do
                    opts.args[optname].validate[k2]=v2
                end
            elseif opttype == "pass" then -- Use a passing group, options are tables with thier remaining args
                 opts.args[optname] = {
                    type = "group",
                    order = n,
                    name = mL[optname.."_name"],
                    desc = mL[optname.."_desc"],
                    args = {},
                }
                local sub_opts = opts.args[optname]
                m:InjectModulePassOptions(sub_opts) -- Use passing group (ModuleGetOption/ModuleSetOption)

                self:BuildAutoOptions(name, module, moduleLocals, sub_opts, val)
            elseif opttype == "handler" then -- This could also just be a passing group but w/e
                opts.args[optname] = {
                    type = "group",
                    order = n,
                    name = mL[optname.."_name"],
                    desc = mL[optname.."_desc"],
                    args = {},
                }


                local sub_opts = opts.args[optname].args
              for i=1,NUM_CHAT_WINDOWS do
                sub_opts["chat"..i] = {
                    name = string.format(mL[optname.."_perframename"], i),
                    desc = string.format(mL[optname.."_perframedesc"], i),
                    type = "toggle",
                    get = function() return m.db.profile[optname][i] end,
                    set = function(x) m.db.profile[optname][i] = x; m[optname](m, i, x) end
                }
              end

            end
        elseif type(val) ~= "table" then
            local ord = n
            if type(val) == "number" then
                ord = val
            end
            opts.args[k] = {
                type = "toggle",
                order = ord,
                name = mL[k.."_name"],
                desc = mL[k.."_desc"],
                get = function() return m.db.profile[k] end,
                set = function(x) m.db.profile[k] = x end,
            }
        elseif #val ~= 0 or val.type then -- Fill in missing fields
            if val.type then
                opts.args[k] = val
                val.order = val.order or n
                val.name = mL[k.."_name"]
                val.desc = mL[k.."_desc"]
                if not opts.passing then
                    val.get = val.get or function() return m.db.profile[k] end
                    val.set = val.set or function(x) m.db.profile[k] = x end
                    val.exec = (val.type == "exec") and (val.exec or k)
                end
           else
                opts.args[k] = {
                    type = "group",
                    order = n,
                    name = mL[k.."_name"],
                    desc = mL[k.."_desc"],
                    args = {},
                }

                self:BuildAutoToggleOptions(name, module, moduleLocals, opts.args[v].args, val)
            end
        else
            opts.args[k] = {
                type = "group",
                order = n,
                name = mL[k.."_name"],
                desc = mL[k.."_desc"],
                args = {},
            }
            local sub_opts = opts.args[k].args
            for i=1,NUM_CHAT_WINDOWS do
               sub_opts["chat"..i] = {
                    name = string.format(mL[k.."_perframename"], i),
                    desc = string.format(mL[k.."_perframedesc"], i),
                    type = "toggle",
                    get = function() return m.db.profile[k][i] end,
                    set = function(x) m.db.profile[k][i] = x;  end
                }
            end
        end
    end
end





local favhint = {
        order = 99,
        type = 'header',
        name = CLR:Colorize(VERSION2, L["Hint: Select 'Favorite' in a module to add it here"]),
    }


function Prat:Prat_ModuleEnabled(mod)
    local k = mod.name
    local v = Prat.DisabledOptions.args[k]

    
--    self:Debug("Prat_ModuleEnabled - "..mod.name)

    Prat.Options.args.enabledmods.args[k] = v or Prat.Options.args.enabledmods.args[k]
    Prat.DisabledOptions.args[k] = nil

    self:UpdateMetaMenus(mod, k, v, true)
end

function Prat:UpdateMetaMenus(mod, key, opts, show)
    local favs = Prat.Options.args

    if mod.db.profile.favorite then
        Prat.Favorites[key] = show and opts or nil
        favs[key] = show and opts or nil
    else
        Prat.Favorites[key] = nil
        if favs[key] then favs[key] = nil end
    end

    favs.favhint = favhint
    for _, v in pairs(Prat.Favorites) do
       favs.favhint = nil
    end

    if mod.Categories then
        local base = Prat.Options.args.categorized.args
        local has = false
        for k,v in pairs(base) do
            v.args[key] = nil
            if show then
                for _,cat in ipairs(mod.Categories) do
                    if v.name == cat then
                        v.args[key] = show and opts or nil
                    end
                end
            end
        end
    end
end

function Prat:Prat_ModuleDisabled(mod)
    local k = mod.name

    local O = Prat.Options.args

    local C = O.categorized.args
--    local D = O.disabledmods.args
    local D = Prat.DisabledOptions.args
    local E = O.enabledmods.args

    local v = E[k] --Prat.Options.args[k]

--    self:Debug("Prat_ModuleDisabled - "..mod.name)

    if mod.Categories then
        local base = C
        local modkey, opts = k,v
        for k,v in pairs(base) do
            for _,cat in ipairs(mod.Categories) do
                if v.name == cat then
                    v.args[modkey] = nil
                end
            end
        end
    end

    if mod.db.profile.favorite then
        O[k] = nil
    end

    
    D[k] = v or D[k]
    
    E[k] = nil

    self:UpdateMetaMenus(mod, k, v, false)
end





-- This function will return a module name which
-- Prat likes. All modules need to get their names
-- by calling this function. Its done so we can
-- can control naming of all modules
function Prat:RequestModuleName(name)
    local prat_name = name -- TODO: Add any special prefixes here
    Prat.Modules[name] = prat_name -- Create a lookup table for the common name
    return prat_name
end

--
--
-- Module Prototype
--
--
Prat.modulePrototype.core = Prat
Prat.modulePrototype.revision = 1
Prat.modulePrototype.moduleOptions = nil        -- Options table for the module
Prat.modulePrototype.consoleCmd = nil           -- Set to override the console command (default = module name)
Prat.modulePrototype.toggleOptions = nil        -- Set to a table of strings to create auto-toggle options
Prat.modulePrototype.modHidden = nil            -- If true this module is hidden unless debug mode is on
Prat.modulePrototype.status = nil
Prat.modulePrototype.Categories = nil
Prat.modulePrototype.modulePatterns = nil
--
-- Modules can override these
--
function Prat.modulePrototype:GetModuleOptions()
    return self.moduleOptions
end
function Prat.modulePrototype:GetModuleAutoOptions()
    return self.toggleOptions
end
function Prat.modulePrototype:GetModulePatterns()
    return self.modulePatterns
end

function Prat.modulePrototype:OnModuleInit()
end
function Prat.modulePrototype:OnModuleEnable()
end
function Prat.modulePrototype:OnModuleDisable()
end
function Prat.modulePrototype:OnModuleDebugEnable()
end
function Prat.modulePrototype:OnModuleDebugDisable()
end

function Prat.modulePrototype:InjectModulePassOptions(opts)
    opts.pass = true
    opts.get = function(...) return self:ModuleGetOption(...) end
    opts.set = function(...) return self:ModuleSetOption(...) end
    opts.exec = function(...) return self:ModuleCallMethod(...) end
end




function Prat.modulePrototype:ModuleSetOption(name, value)
    self:Debug("ModuleSetOption", name, value)
    if self.db then
        self.db.profile[name] = value
    end
end
function Prat.modulePrototype:ModuleGetOption(name)
    local val = self.db and self.db.profile[name]
    self:Debug("ModuleGetOption", name, val)
    return val
end
function Prat.modulePrototype:ModuleCallMethod(name, ...)
    self:Debug("ModuleCallMethod", name, ...)
end



--
-- Modules Must Not Override these
--
Prat.modulePrototype.initCheck = false
Prat.modulePrototype.enableCheck = false

function Prat.modulePrototype:OnInitialize()
    self.initCheck = true


    if self.core.db.profile.forceDebugModules then
        self:SetDebugging(true)
    end

    self:Debug("OnInitialize()")

    -- Register the module, this will call GetModule*Options
    self.core:RegisterModule(self.moduleName or self.name, self)

    if not self.core.db.profile.forceDebugModules then
       self:SetDebugging(self.db.profile.debug)
    end

    self:OnModuleInit()

    local on = self.moduleHidden or self.db.profile.on
    self.db.profile.on = on

    if self.modHidden then
        --self.status = "HIDDEN"
    end

    --- This restores the module to its internal stored value for on/off
    self.core:ToggleModuleActive(self.name, on)
end

function Prat.modulePrototype:OnEnable()
    self.enableCheck = true
    self:Debug("OnEnable()")

    if not self.core:IsActive() then
        DBG_MODULE("Prat not active")
        self.core:ToggleModuleActive(self, false)
        return
    end

    if not self.db.profile.on then 
        DBG_MODULE("Module ["..self.name.."] should not be active")
        self.core:ToggleModuleActive(self, false)
        return
    end
    
    self:OnModuleEnable()

    local  p = self:GetModulePatterns()
    
    if type(p) == "table" then 
        for _,v in pairs(p) do
            self.core:RegisterPattern(v, self.name)
        end
    end
    
    self.core:ModuleNotify(Prat.Notifications.ENABLED, self)
end
function Prat.modulePrototype:OnDisable()
    self:Debug("OnDisable()")
    
    if not self.enableCalled then 
        DBG_MODULE("Module never enabled. Not calling OnModuleDisable")
        return
    end
    
    self.core:UnregisterAllPatterns(self.name)

    self:OnModuleDisable()  

    self.core:ModuleNotify(Prat.Notifications.DISABLED, self)
    self.enableCheck = false
end
function Prat.modulePrototype:OnDebugEnable()
    if self.db then
        self.db.profile.debug = true
    end
    self:Debug("Debug - ON")
    self:OnModuleDebugEnable()
end
function Prat.modulePrototype:OnDebugDisable()
    self:OnModuleDebugDisable()
    if self.db then
        self.db.profile.debug = false
    end
    self:Debug("Debug - OFF")
end
