Quantcast
--[[
##############################################################################
_____/\\\\\\\\\\\____/\\\________/\\\__/\\\________/\\\__/\\\\\\\\\\\_       #
 ___/\\\/////////\\\_\/\\\_______\/\\\_\/\\\_______\/\\\_\/////\\\///__      #
  __\//\\\______\///__\//\\\______/\\\__\/\\\_______\/\\\_____\/\\\_____     #
   ___\////\\\__________\//\\\____/\\\___\/\\\_______\/\\\_____\/\\\_____    #
    ______\////\\\________\//\\\__/\\\____\/\\\_______\/\\\_____\/\\\_____   #
     _________\////\\\______\//\\\/\\\_____\/\\\_______\/\\\_____\/\\\_____  #
      __/\\\______\//\\\______\//\\\\\______\//\\\______/\\\______\/\\\_____ #
       _\///\\\\\\\\\\\/________\//\\\________\///\\\\\\\\\/____/\\\\\\\\\\\_#
        ___\///////////___________\///___________\/////////_____\///////////_#
##############################################################################
S U P E R - V I L L A I N - U I   By: Munglunch                              #
############################################################################## ]]--
--[[ GLOBALS ]]--
local _G = _G;
local unpack        = _G.unpack;
local select        = _G.select;
local pairs         = _G.pairs;
local type          = _G.type;
local rawset        = _G.rawset;
local rawget        = _G.rawget;
local tinsert       = _G.tinsert;
local tremove       = _G.tremove;
local tostring      = _G.tostring;
local error         = _G.error;
local getmetatable  = _G.getmetatable;
local setmetatable  = _G.setmetatable;
local string    = _G.string;
local math      = _G.math;
local table     = _G.table;
--[[ STRING METHODS ]]--
local upper = string.upper;
local format, find, match, gsub = string.format, string.find, string.match, string.gsub;
--[[ MATH METHODS ]]--
local floor = math.floor
--[[ TABLE METHODS ]]--
local tsort, tconcat = table.sort, table.concat;
--[[
##########################################################
ADDON DATA
##########################################################
]]--
local SVUI = {};
local SVUINameSpace, SVUICore = ...;
local version = GetAddOnMetadata(..., "Version");
local build = select(2, GetBuildInfo());
local SetAddonCore;
local callbacks = {};
local numCallbacks = 0;
--[[
##########################################################
CONSTANTS
##########################################################
]]--
BINDING_HEADER_SVUI = "SuperVillain UI";
SLASH_RELOADUI1="/rl"
SLASH_RELOADUI2="/reloadui"
SlashCmdList.RELOADUI=ReloadUI
--[[
##########################################################
MUNGLUNCH's FASTER ASSERT FUNCTION
##########################################################
]]--
function enforce(condition, ...)
   if not condition then
      if next({...}) then
         local fn = function (...) return(string.format(...)) end
         local s,r = pcall(fn, ...)
         if s then
            error("Error!: " .. r, 2)
         end
      end
      error("Error!", 2)
   end
end
local assert = enforce;
--[[
##########################################################
LOCAL FUNCTIONS
##########################################################
]]--
local function formatValueString(text)
    if "string" == type(text) then
        text = gsub(text,"\n","\\n")
        if match(gsub(text,"[^'\"]",""),'^"+$') then
            return "'"..text.."'";
        else
            return '"'..gsub(text,'"','\\"')..'"';
        end
    else
        return tostring(text);
    end
end
local function formatKeyString(text)
    if "string"==type(text) and match(text,"^[_%a][_%a%d]*$") then
        return text;
    else
        return "["..formatValueString(text).."]";
    end
end
local function RegisterCallback(self, m, h)
    assert(type(m) == "string" or type(m) == "function", "Bad argument #1 to :RegisterCallback (string or function expected)")
    if type(m) == "string" then
        assert(type(h) == "table", "Bad argument #2 to :RegisterCallback (table expected)")
        assert(type(h[m]) == "function", "Bad argument #1 to :RegisterCallback (m \"" .. m .. "\" not found)")
        m = h[m]
    end
    callbacks[m] = h or true
    numCallbacks = numCallbacks + 1
end
local function UnregisterCallback(self, m, h)
    assert(type(m) == "string" or type(m) == "function", "Bad argument #1 to :UnregisterCallback (string or function expected)")
    if type(m) == "string" then
        assert(type(h) == "table", "Bad argument #2 to :UnregisterCallback (table expected)")
        assert(type(h[m]) == "function", "Bad argument #1 to :UnregisterCallback (m \"" .. m .. "\" not found)")
        m = h[m]
    end
    callbacks[m] = nil
    numCallbacks = numCallbacks + 1
end
local function DispatchCallbacks()
    if (numCallbacks < 1) then return end
    for m, h in pairs(callbacks) do
        local ok, err = pcall(m, h ~= true and h or nil)
        if not ok then
            print("ERROR:", err)
        end
    end
end
--[[
##########################################################
BUILD CLASS COLOR GLOBAL
##########################################################
]]--
SVUI_CLASS_COLORS = {};
do
    local classes = {};
    local supercolors = {
        ["HUNTER"]        = { r = 0.454, g = 0.698, b = 0 },
        ["WARLOCK"]       = { r = 0.286, g = 0,     b = 0.788 },
        ["PRIEST"]        = { r = 0.976, g = 1,     b = 0.839 },
        ["PALADIN"]       = { r = 0.956, g = 0.207, b = 0.733 },
        ["MAGE"]          = { r = 0,     g = 0.796, b = 1 },
        ["ROGUE"]         = { r = 1,     g = 0.894, b = 0.117 },
        ["DRUID"]         = { r = 1,     g = 0.513, b = 0 },
        ["SHAMAN"]        = { r = 0,     g = 0.38,  b = 1 },
        ["WARRIOR"]       = { r = 0.698, g = 0.36,  b = 0.152 },
        ["DEATHKNIGHT"]   = { r = 0.847, g = 0.117, b = 0.074 },
        ["MONK"]          = { r = 0.015, g = 0.886, b = 0.38 },
    };
    for class in pairs(RAID_CLASS_COLORS) do
        tinsert(classes, class)
    end
    tsort(classes)
    setmetatable(SVUI_CLASS_COLORS,{
        __index = function(t, k)
            if k == "RegisterCallback" then return RegisterCallback end
            if k == "UnregisterCallback" then return UnregisterCallback end
            if k == "DispatchCallbacks" then return DispatchCallbacks end
        end
    });
    for i, class in ipairs(classes) do
        local color = supercolors[class]
        local r, g, b = color.r, color.g, color.b
        local hex = ("ff%02x%02x%02x"):format(r * 255, g * 255, b * 255)
        if not SVUI_CLASS_COLORS[class] or not SVUI_CLASS_COLORS[class].r or not SVUI_CLASS_COLORS[class].g or not SVUI_CLASS_COLORS[class].b then
            SVUI_CLASS_COLORS[class] = {
                r = r,
                g = g,
                b = b,
                colorStr = hex,
            }
        end
    end
    classes = nil
end
--[[
##########################################################
APPENDED GLOBAL FUNCTIONS
##########################################################
]]--
function math.parsefloat(value,decimal)
    if decimal and decimal > 0 then
        local calc1 = 10 ^ decimal;
        local calc2 = (value * calc1) + 0.5;
        return floor(calc2) / calc1
    end
    return floor(value + 0.5)
end

function table.dump(targetTable)
    local dumpTable = {};
    local dumpCheck = {};
    for key,value in ipairs(targetTable) do
        tinsert(dumpTable, formatValueString(value));
        dumpCheck[key] = true;
    end
    for key,value in pairs(targetTable) do
        if not dumpCheck[key] then
            tinsert(dumpTable, "\n    "..formatKeyString(key).." = "..formatValueString(value));
        end
    end
    local output = tconcat(dumpTable, ", ");
    return "{ "..output.." }";
end

function table.copy(targetTable,deepCopy,mergeTable)
    mergeTable = mergeTable or {};
    if targetTable==nil then return nil end
    if mergeTable[targetTable] then return mergeTable[targetTable] end
    local replacementTable = {}
    for key,value in pairs(targetTable)do
        if deepCopy and type(value) == "table" then
            replacementTable[key] = table.copy(value, deepCopy, mergeTable)
        else
            replacementTable[key] = value
        end
    end
    setmetatable(replacementTable, table.copy(getmetatable(targetTable), deepCopy, mergeTable))
    mergeTable[targetTable] = replacementTable;
    return replacementTable
end

function string.trim(this)
    return this:find('^%s*$') and '' or this:match('^%s*(.*%S)')
end

function string.color(this, color)
    return ("|cff%s%s|r"):format(color, this)
end

function string.link(this, prefix, text, color)
    text = tostring(text)
    local colorstring = tostring(this):color(color or "ffffff")
    return ("|H%s:%s|h%s|h"):format(prefix, text, colorstring)
end
--[[
##########################################################
DEFINE REGISTRY HELPERS
##########################################################
]]--
do
    local PackageQueue, ScriptQueue = {},{};

    local INFO_BY = "%s by %s";
    local INFO_VERSION = "%s%s - Version: %d";
    local INFO_NEW = "%s (Newest: %d)";
    local INFO_NAME = "Plugins";
    local INFO_HEADER = "SuperVillain UI (version %.3f): Plugins";

    if GetLocale() == "ruRU" then
        INFO_BY = "%s от %s";
        INFO_VERSION = "%s%s - Версия: %d";
        INFO_NEW = "%s (Последняя: %d)";
        INFO_NAME = "Плагины";
        INFO_HEADER = "SuperVillain UI (устарела %.3f): Плагины";
    end

    local rootstring = function(self) return self.___name end

    local changeDBVar = function(self, value, key, sub1, sub2, sub3)
        local config = self.__owner.db[self.___name]
        if((sub1 and sub2 and sub3) and (config[sub1] and config[sub1][sub2] and config[sub1][sub2][sub3])) then
            self.__owner.db[self.___name][sub1][sub2][sub3][key] = value
        elseif((sub1 and sub2) and (config[sub1] and config[sub1][sub2])) then
            self.__owner.db[self.___name][sub1][sub2][key] = value
        elseif(sub1 and config[sub1]) then
            self.__owner.db[self.___name][sub1][key] = value
        else
            self.__owner.db[self.___name][key] = value
        end
        self.db = self.__owner.db[self.___name]
        if(self.UpdateLocals) then
            self:UpdateLocals()
        end
    end

    local innerOnEvent = function(self, event, ...)
        local obj = self.__owner
        if self[event] and type(self[event]) == "function" then
            self[event](obj, event, ...)
        end
    end

    local registerEvent = function(self, eventname, eventfunc)
        if not self.___eventframe then
            self.___eventframe = CreateFrame("Frame", nil)
            self.___eventframe.__owner = self
            self.___eventframe:SetScript("OnEvent", innerOnEvent)
        end
        local fn = eventfunc
        if type(eventfunc) == "string" then
            fn = self[eventfunc]
        elseif(not fn and self[eventname]) then
            fn = self[eventname]
        end
        self.___eventframe[eventname] = fn
        self.___eventframe:RegisterEvent(eventname)
    end

    local unregisterEvent = function(self, event, ...)
        if(self.___eventframe) then
            self.___eventframe:UnregisterEvent(event)
        end
    end

    local addonEvent = function(self, event, addon)
        if addon == "SVUI_ConfigOMatic" then
            local list = self.__owner.Plugins
            for i, plugin in pairs(list) do
                if(plugin.callback) then
                    plugin.callback()
                end
            end
        end
    end

    local function SetNewComponent(obj, name, parent, plugin)
        local addonmeta = {}
        local oldmeta = getmetatable(obj)
        if oldmeta then
            for k, v in pairs(oldmeta) do addonmeta[k] = v end
        end
        addonmeta.__tostring = rootstring
        setmetatable( obj, addonmeta )
        obj.___name = name
        obj.__owner = parent
        obj.initialized = false
        obj.CombatLocked = false
        obj.ChangeDBVar = changeDBVar
        obj.RegisterEvent = registerEvent
        obj.UnregisterEvent = unregisterEvent
        if(not plugin) then
            return obj
        end
    end

    local Registry_SetCallback = function(self, fn)
        if(fn and type(fn) == "function") then
            self.Callbacks[#self.Callbacks+1] = fn
        end
    end

    local Registry_NewScript = function(self, fn)
        if(fn and type(fn) == "function") then
            ScriptQueue[#ScriptQueue+1] = fn
        end
    end

    local Registry_NewPackage = function(self, obj, name)
        if self.__owner[name] then return end
        PackageQueue[#PackageQueue+1] = name
        self.Packages[#self.Packages+1] = name

        self.__owner[name] = SetNewComponent(obj, name, self.__owner)

        if(self.__owner.AddonLaunched) then
            if(self.__owner[name].Load) then
                self.__owner[name]:Load()
            end
        end
    end

    local Registry_FetchPlugins = function(self)
        local list = "";
        for addon, plugin in pairs(self.Plugins) do
            if addon ~= self.__owner.___name then
                local author = GetAddOnMetadata(addon, "Author")
                local Pname = GetAddOnMetadata(addon, "Title") or addon
                local color = plugin.old and "|cffFF0000" or "|cff00FF00"
                list = ("%s%s"):format(list, Pname)
                if author then
                  list = self.INFO_BY:format(list, author)
                end
                list = self.INFO_VERSION:format(list, color, plugin.___ver)
                if plugin.old then
                  list = self.INFO_NEW:format(list, plugin.newversion)
                end
                list = ("%s|r\n"):format(list)
            end
        end
        return list
    end

    local Registry_NewPlugin = function(self, obj, name, callbackFunc)
        if(callbackFunc and type(callbackFunc) == "function") then
            local addon = obj.___name
            local ver = obj.___ver or GetAddOnMetadata(addon, "Version")
            self.Plugins[addon] = {
                version = ver,
                callback = callbackFunc
            }
            if addon ~= self.__owner.___name then
                local fetch = self:FetchPlugins() or ""
                self.__owner.Options.args.plugins.args.pluginOptions.args.pluginlist.args.active.name = fetch
            end
            callbackFunc()
        end

        SetNewComponent(obj, name, self.__owner, true)
    end

    local Registry_RunCallbacks = function(self)
        for i=1, #self.Callbacks do
            local fn = self.Callbacks[i]
            if(fn and type(fn) == "function") then
                fn()
            end
        end
    end

    local Registry_Update = function(self, name, dataOnly)
        local obj = self.__owner[name]
        if obj then
            if self.__owner.db[name] then
                obj.db = self.__owner.db[name]
            end
            if obj.ReLoad and not dataOnly then
                obj:ReLoad()
            end
        end
    end

    local Registry_UpdateAll = function(self)
        local list = self.Packages
        for _,name in pairs(list) do
            local obj = self.__owner[name]
            if self.__owner.db[name] then
                obj.db = self.__owner.db[name]
            end
            if obj and obj.ReLoad then
                obj:ReLoad()
            end
        end
    end

    local Registry_PreLoad = function(self)
        if not PackageQueue then return end
        for i=1,#PackageQueue do
            local name = PackageQueue[i]
            local obj = self.__owner[name]
            if(obj and obj.PriorityLoad and (not obj.initialized)) then
                if self.__owner.db[name] then
                    obj.db = self.__owner.db[name]
                end
                obj:PriorityLoad()
                obj.PriorityLoad = nil
                obj.initialized = true;
            end
        end
    end

    local Registry_Load = function(self)
        if not PackageQueue then return end
        for i=1,#PackageQueue do
            local name = PackageQueue[i]
            local obj = self.__owner[name]
            if obj and not obj.initialized then
                if self.__owner.db[name] then
                    obj.db = self.__owner.db[name]
                end
                if obj.Load then
                    obj:Load()
                    obj.Load = nil
                end
                obj.initialized = true;
            end
        end

        PackageQueue = nil

        if not ScriptQueue then return end
        for i=1, #ScriptQueue do
            local fn = ScriptQueue[i]
            if(fn and type(fn) == "function") then
                fn()
            end
        end

        ScriptQueue = nil
    end

    --[[ GLOBAL NAMESPACE ]]--

    function SetAddonCore(obj,n,v)
        obj = {
            ___name = n,
            ___ver = v,
            db = {},
            Global = {
                Accountant = {},
                profiles = {},
                profileKeys = {},
            },
            Configs = {},
            Media = {},
            DisplayAudit = {},
            DynamicOptions = {},
            Dispellable = {},
            Snap = {},
            Options = {
                type = "group",
                name = "|cff339fffConfig-O-Matic|r",
                args = {
                    plugins = {
                        order = -2,
                        type = "group",
                        name = "Plugins",
                        childGroups = "tab",
                        args = {
                            pluginheader = {
                                order = 1,
                                type = "header",
                                name = "SuperVillain Plugins",
                            },
                            pluginOptions = {
                                order = 2,
                                type = "group",
                                name = "",
                                args = {
                                    pluginlist = {
                                        order = 1,
                                        type = "group",
                                        name = "Summary",
                                        args = {
                                            active = {
                                                order = 1,
                                                type = "description",
                                                name = ""
                                            }
                                        }
                                    },
                                }
                            }
                        }
                    }
                }
            },
            Registry = {
                Packages = {},
                Plugins = {},
                Callbacks = {},
                INFO_BY = INFO_BY,
                INFO_VERSION = INFO_VERSION,
                INFO_NEW = INFO_NEW,
                INFO_NAME = INFO_NAME,
                INFO_HEADER = INFO_HEADER,
                SetCallback = Registry_SetCallback,
                NewScript = Registry_NewScript,
                NewPackage = Registry_NewPackage,
                FetchPlugins = Registry_FetchPlugins,
                NewPlugin = Registry_NewPlugin,
                RunCallbacks = Registry_RunCallbacks,
                Update = Registry_Update,
                UpdateAll = Registry_UpdateAll,
                PreLoadPackages = Registry_PreLoad,
                LoadPackages = Registry_Load,
            }
        }
        local mt = {}
        local old = getmetatable(obj)
        if old then
            for k, v in pairs(old) do mt[k] = v end
        end
        mt.__tostring = rootstring
        setmetatable(obj, mt)

        obj.Registry.__owner = obj

        return obj
    end
end

SVUI = SetAddonCore(SVUI, SVUINameSpace, version)

--[[ LOCALIZATION HELPERS ]]--
local failsafe = function() assert(false) end

local metaread = {
    __index = function(self, key)
        rawset(self, key, key)
        return key
    end
}

local activeLocale

local defaultwrite = setmetatable({}, {
    __newindex = function(self, key, value)
        if not rawget(activeLocale, key) then
            rawset(activeLocale, key, value == true and key or value)
        end
    end,
    __index = failsafe
})

local metawrite = setmetatable({}, {
    __newindex = function(self, key, value)
        rawset(activeLocale, key, value == true and key or value)
    end,
    __index = failsafe
})

SVUI.Localization = setmetatable({}, metaread)

--[[ MISC ]]--
SVUI.fubar = function() return end
SVUI.class = select(2,UnitClass("player"));
SVUI.ClassRole = "";
SVUI.ConfigurationMode = false;
--[[ MISC ]]--

--[[ UTILITY FRAMES ]]--
SVUI.UIParent = CreateFrame("Frame", "SVUIParent", UIParent);
SVUI.UIParent:SetFrameLevel(UIParent:GetFrameLevel());
SVUI.UIParent:SetPoint("CENTER", UIParent, "CENTER");
SVUI.UIParent:SetSize(UIParent:GetSize());
SVUI.Snap[1] = SVUI.UIParent;

SVUI.Cloaked = CreateFrame("Frame", nil, UIParent);
SVUI.Cloaked:Hide();
--[[ UTILITY FRAMES ]]--

function SVUI:SetLocaleStrings(locale, isDefault)
    local gameLocale = GetLocale()
    if gameLocale == "enGB" then gameLocale = "enUS" end

    activeLocale = self.Localization

    if isDefault then
        return defaultwrite
    elseif(locale == GAME_LOCALE or locale == gameLocale) then
        return metawrite
    end
end

function SVUI:Prototype(n, v)
    local obj = {
        ___name = n,
        ___ver = v
    }
    local mt = {}
    local old = getmetatable(obj)
    if old then
        for k, v in pairs(old) do mt[k] = v end
    end
    mt.__tostring = rootstring
    setmetatable(obj, mt)
    return obj
end

SVUICore[1] = SVUI
SVUICore[2] = SVUI.Localization
SVUICore[3] = SVUI.Global
SVUICore[4] = SVUI.Configs

_G[SVUINameSpace] = SVUICore;