Quantcast
--[[-------------------------------------------------------------------------
  Copyright (c) 2006, Dongle Development Team
  All rights reserved.

  Redistribution and use in source and binary forms, with or without
  modification, are permitted provided that the following conditions are
  met:

      * Redistributions of source code must retain the above copyright
        notice, this list of conditions and the following disclaimer.
      * Redistributions in binary form must reproduce the above
        copyright notice, this list of conditions and the following
        disclaimer in the documentation and/or other materials provided
        with the distribution.
      * Neither the name of the Dongle Development Team nor the names of
        its contributors may be used to endorse or promote products derived
        from this software without specific prior written permission.

  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
  DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
  THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
---------------------------------------------------------------------------]]
local major = "DongleStub-Beta0"
local minor = tonumber(string.match("$Revision: 221 $", "(%d+)") or 1)

local g = getfenv(0)

if not g.DongleStub or g.DongleStub:IsNewerVersion(major, minor) then
	local lib = setmetatable({}, {
		__call = function(t,k)
			if type(t.versions) == "table" and t.versions[k] then
				return t.versions[k].instance
			else
				error("Cannot find a library with name '"..tostring(k).."'", 2)
			end
		end
	})

	function lib:IsNewerVersion(major, minor)
		local versionData = self.versions and self.versions[major]

		if not versionData then return true end
		local oldmajor,oldminor = versionData.instance:GetVersion()

		return minor > oldminor
	end

	local function NilCopyTable(src, dest)
		for k,v in pairs(dest) do dest[k] = nil end
		for k,v in pairs(src) do dest[k] = v end
	end

	function lib:Register(newInstance, activate, deactivate)
		local major,minor = newInstance:GetVersion()
		if not self:IsNewerVersion(major, minor) then return false end
		if not self.versions then self.versions = {} end

		local versionData = self.versions[major]
		if not versionData then
			-- New major version
			versionData = {
				["instance"] = newInstance,
				["deactivate"] = deactivate,
			}

			self.versions[major] = versionData
			if type(activate) == "function" then
				activate(newInstance)
			end
			return newInstance
		end

		local oldDeactivate = versionData.deactivate
		local oldInstance = versionData.instance

		versionData.deactivate = deactivate

		local skipCopy
		if type(activate) == "function" then
			 skipCopy = activate(newInstance, oldInstance)
		end

		-- Deactivate the old libary if necessary
		if type(oldDeactivate) == "function" then
			oldDeactivate(oldInstance, newInstance)
		end

		-- Re-use the old table, and discard the new one
		if not skipCopy then
			NilCopyTable(newInstance, oldInstance)
		end
		return oldInstance
	end

	function lib:GetVersion() return major,minor end

	local function Activate(new, old)
		if old then
			new.versions = old.versions
		end
		g.DongleStub = new
	end

	-- Actually trigger libary activation here
	local stub = g.DongleStub or lib
	stub:Register(lib, Activate)
end

--[[-------------------------------------------------------------------------
  Begin Library Implementation
---------------------------------------------------------------------------]]

local major = "Dongle-Beta0"
local minor = tonumber(string.match("$Revision: 228 $", "(%d+)") or 1)

assert(DongleStub, string.format("Dongle requires DongleStub.", major))
assert(DongleStub and DongleStub:GetVersion() == "DongleStub-Beta0",
	string.format("Dongle requires DongleStub-Beta0.  You are using an older version.", major))

if not DongleStub:IsNewerVersion(major, minor) then return end

local Dongle = {}
local methods = {
	"RegisterEvent", "UnregisterEvent", "UnregisterAllEvents",
	"RegisterMessage", "UnregisterMessage", "UnregisterAllMessages", "TriggerMessage",
	"EnableDebug", "Print", "PrintF", "Debug", "DebugF",
	"InitializeDB",
	"InitializeSlashCommand",
	"NewModule", "HasModule", "IterateModules",
}

local registry = {}
local lookup = {}
local loadqueue = {}
local loadorder = {}
local events = {}
local databases = {}
local commands = {}
local messages = {}

local frame

local function assert(level,condition,message)
	if not condition then
		error(message,level)
	end
end

local function argcheck(value, num, ...)
	assert(1, type(num) == "number",
		"Bad argument #2 to 'argcheck' (number expected, got " .. type(level) .. ")")

	for i=1,select("#", ...) do
		if type(value) == select(i, ...) then return end
	end

	local types = strjoin(", ", ...)
	local name = string.match(debugstack(), "`argcheck'.-[`<](.-)['>]") or "Unknown"
	error(string.format("bad argument #%d to '%s' (%s expected, got %s)",
		num, name, types, type(value)), 3)
end

local function safecall(func,...)
	local success,err = pcall(func,...)
	if not success then
		geterrorhandler()(err)
	end
end

function Dongle:New(name, obj)
	argcheck(name, 2, "string")
	argcheck(obj, 3, "table", "nil")

	if not obj then
		obj = {}
	end

	if registry[name] then
		error("A Dongle with the name '"..name.."' is already registered.")
	end

	local reg = {["obj"] = obj, ["name"] = name}

	registry[name] = reg
	lookup[obj] = reg
	lookup[name] = reg

	for k,v in pairs(methods) do
		obj[v] = self[v]
	end

	-- Add this Dongle to the end of the queue
	table.insert(loadqueue, obj)
	return obj,name
end

function Dongle:NewModule(name, obj)
	local reg = lookup[self]
	assert(3, reg, "You must call 'NewModule' from a registered Dongle.")
	argcheck(name, 2, "string")
	argcheck(obj, 3, "table", "nil")

	obj,name = Dongle:New(name, obj)

	if not reg.modules then reg.modules = {} end
	reg.modules[obj] = obj
	reg.modules[name] = obj
	table.insert(reg.modules, name)
	table.sort(reg.modules)

	return obj,name
end

function Dongle:HasModule(module)
	local reg = lookup[self]
	assert(3, reg, "You must call 'HasModule' from a registered Dongle.")
	argcheck(module, 2, "string", "table")

	return reg.modules[module]
end

local function ModuleIterator(t, name)
	if not t then return end
	local module
	repeat
		name,module = next(t, name)
	until type(name) == "string" or not name

	if not name then return end
	return name, module
end

function Dongle:IterateModules()
	local reg = lookup[self]
	assert(3, reg, "You must call 'IterateModules' from a registered Dongle.")

	return ModuleIterator, reg.modules
end

local function ADDON_LOADED(event, ...)
	for i=1, #loadqueue do
		local obj = loadqueue[i]
		table.insert(loadorder, obj)

		if type(obj.Initialize) == "function" then
			safecall(obj.Initialize, obj)
		end

		if Dongle.initialized and type(obj.Enable) == "function" then
			safecall(obj.Enable, obj)
		end
		loadqueue[i] = nil
	end
end

local function PLAYER_LOGOUT(event)
	self:ClearDBDefaults()
	for k,v in pairs(registry) do
		local obj = v.obj
		if type(obj["Disable"]) == "function" then
			safecall(obj["Disable"], obj)
		end
	end
end

local function PLAYER_LOGIN()
	Dongle.initialized = true
	for i,obj in ipairs(loadorder) do
		if type(obj.Enable) == "function" then
			safecall(obj.Enable, obj)
		end
	end
end

local function OnEvent(frame, event, ...)
	local eventTbl = events[event]
	if eventTbl then
		for obj,func in pairs(eventTbl) do
			if type(func) == "string" then
				if type(obj[func]) == "function" then
					safecall(obj[func], obj, event, ...)
				end
			else
				safecall(func, event, ...)
			end
		end
	end
end

function Dongle:RegisterEvent(event, func)
	local reg = lookup[self]
	assert(3, reg, "You must call 'RegisterEvent' from a registered Dongle.")
	argcheck(event, 2, "string")
	argcheck(func, 3, "string", "function", "nil")

	-- Name the method the same as the event if necessary
	if not func then func = event end

	if not events[event] then
		events[event] = {}
		frame:RegisterEvent(event)
	end
	events[event][self] = func
end

function Dongle:UnregisterEvent(event)
	local reg = lookup[self]
	assert(3, reg, "You must call 'UnregisterEvent' from a registered Dongle.")
	argcheck(event, 2, "string")

	if events[event] then
		events[event][self] = nil
		if not next(events[event]) then
			events[event] = nil
			frame:UnregisterEvent(event)
		end
	end
end

function Dongle:UnregisterAllEvents()
	assert(3, lookup[self], "You must call 'UnregisterAllEvents' from a registered Dongle.")

	for event,tbl in pairs(events) do
		tbl[self] = nil
		if not next(tbl) then
			events[event] = nil
			frame:UnregisterEvent(event)
		end
	end
end

function Dongle:RegisterMessage(msg, func)
	local reg = lookup[self]
	assert(3, reg, "You must call 'RegisterMessage' from a registered Dongle.")
	argcheck(msg, 2, "string")
	argcheck(func, 3, "string", "function", "nil")

	-- Name the method the same as the event if necessary
	if not func then func = msg end

	if not messages[msg] then
		messages[msg] = {}
	end
	messages[msg][self] = func
end

function Dongle:UnregisterMessage(msg)
	local reg = lookup[self]
	assert(3, reg, "You must call 'UnregisterMessage' from a registered Dongle.")
	argcheck(msg, 2, "string")

	if messages[msg] then
		messages[msg][self] = nil
		if not next(messages[msg]) then
			messages[msg] = nil
		end
	end
end

function Dongle:UnregisterAllMessages()
	assert(3, lookup[self], "You must call 'UnregisterAllMessages' from a registered Dongle.")

	for msg,tbl in pairs(messages) do
		tbl[self] = nil
		if not next(tbl) then
			messages[msg] = nil
		end
	end
end

function Dongle:TriggerMessage(msg, ...)
	argcheck(msg, 2, "string")
	local msgTbl = messages[msg]
	if not msgTbl then return end

	for obj,func in pairs(msgTbl) do
		if type(func) == "string" then
			if type(obj[func]) == "function" then
				safecall(obj[func], obj, msg, ...)
			end
		else
			safecall(func, msg, ...)
		end
	end
end

function Dongle:EnableDebug(level)
	local reg = lookup[self]
	assert(3, reg, "You must call 'EnableDebug' from a registered Dongle.")
	argcheck(level, 2, "number", "nil")

	reg.debugLevel = level
end

do
	local function argsToStrings(a1, ...)
		if select("#", ...) > 0 then
			return tostring(a1), argsToStrings(...)
		else
			return tostring(a1)
		end
	end

	local function printHelp(obj, method, msg, ...)
		local reg = lookup[obj]
		assert(4, reg, "You must call '"..method.."' from a registered Dongle.")

		local name = reg.name
		msg = "|cFF33FF99"..name.."|r: "..tostring(msg)
		if select("#", ...) > 0 then
			msg = string.join(", ", msg, argsToStrings(...))
		end

		ChatFrame1:AddMessage(msg)
	end

	local function printFHelp(obj, method, msg, ...)
		local reg = lookup[obj]
		assert(4, reg, "You must call '"..method.."' from a registered Dongle.")

		local name = reg.name
		local success,txt = pcall(string.format, "|cFF33FF99%s|r: "..msg, name, ...)
		if success then
			ChatFrame1:AddMessage(txt)
		else
			error(string.gsub(txt, "'%?'", string.format("'%s'", method)), 3)
		end
	end

	function Dongle:Print(...)
		return printHelp(self, "Print", ...)
	end

	function Dongle:PrintF(...)
		return printFHelp(self, "PrintF", ...)
	end

	function Dongle:Debug(level, ...)
		local reg = lookup[self]
		assert(3, reg, "You must call 'Debug' from a registered Dongle.")
		argcheck(level, 2, "number")

		if reg.debugLevel and level <= reg.debugLevel then
			printHelp(self, "Debug", ...)
		end
	end

	function Dongle:DebugF(level, ...)
		local reg = lookup[self]
		assert(3, reg, "You must call 'DebugF' from a registered Dongle.")
		argcheck(level, 2, "number")

		if reg.debugLevel and level <= reg.debugLevel then
			printFHelp(self, "DebugF", ...)
		end
	end
end

local dbMethods = {
	"RegisterDefaults", "SetProfile", "GetProfiles", "DeleteProfile", "CopyProfile",
	"ResetProfile", "ResetDB",
}

local function initdb(parent, name, defaults, defaultProfile, olddb)
	local sv = getglobal(name)

	if not sv then
		sv = {}
		setglobal(name, sv)

		-- Lets do the initial setup

		sv.char = {}
		sv.faction = {}
		sv.realm = {}
		sv.class = {}
		sv.global = {}
		sv.profiles = {}
	end

	-- Initialize the specific databases
	local char = string.format("%s of %s", UnitName("player"), GetRealmName())
	local realm = string.format("%s", GetRealmName())
	local class = UnitClass("player")
	local race = select(2, UnitRace("player"))
	local faction = UnitFactionGroup("player")

	-- Initialize the containers
	if not sv.char then sv.char = {} end
	if not sv.realm then sv.realm = {} end
	if not sv.class then sv.class = {} end
	if not sv.faction then sv.faction = {} end
	if not sv.global then sv.global = {} end
	if not sv.profiles then sv.profiles = {} end
	if not sv.profileKeys then sv.profileKeys = {} end

	-- Initialize this characters profiles
	if not sv.char[char] then sv.char[char] = {} end
	if not sv.realm[realm] then sv.realm[realm] = {} end
	if not sv.class[class] then sv.class[class] = {} end
	if not sv.faction[faction] then sv.faction[faction] = {} end

	-- Try to get the profile selected from the char db
	local profileKey = sv.profileKeys[char] or defaultProfile or char
	sv.profileKeys[char] = profileKey

	local profileCreated
    if not sv.profiles[profileKey] then sv.profiles[profileKey] = {} profileCreated = true end

	if olddb then
		for k,v in pairs(olddb) do olddb[k] = nil end
	end

	local db = olddb or {}
	db.char = sv.char[char]
	db.realm = sv.realm[realm]
	db.class = sv.class[class]
	db.faction = sv.faction[faction]
	db.profile = sv.profiles[profileKey]
	db.global = sv.global
	db.profiles = sv.profiles

	-- Copy methods locally
	for idx,method in pairs(dbMethods) do
		db[method] = Dongle[method]
	end

	-- Set some properties in the object we're returning
	db.sv = sv
	db.sv_name = name
	db.profileKey = profileKey
	db.parent = parent
	db.charKey = char
	db.realmKey = realm
	db.classKey = class
	db.factionKey = faction

	databases[db] = true

	if defaults then
		db:RegisterDefaults(defaults)
	end

	return db,profileCreated
end

function Dongle:InitializeDB(name, defaults, defaultProfile)
	local reg = lookup[self]
	assert(3, reg, "You must call 'InitializeDB' from a registered Dongle.")
	argcheck(name, 2, "string")
	argcheck(defaults, 3, "table", "nil")
	argcheck(defaultProfile, 4, "string", "nil")

	local db,profileCreated = initdb(self, name, defaults, defaultProfile)

	if profileCreated then
		Dongle:TriggerMessage("DONGLE_PROFILE_CREATED", db, self, db.sv_name, db.profileKey)
	end
	return db
end

local function copyDefaults(dest, src, force)
	for k,v in pairs(src) do
		if type(v) == "table" then
			if not dest[k] then dest[k] = {} end
			copyDefaults(dest[k], v, force)
		else
			if (dest[k] == nil) or force then
				dest[k] = v
			end
		end
	end
end

-- This function operates on a Dongle DB object
function Dongle.RegisterDefaults(db, defaults)
	assert(3, databases[db], "You must call 'RegisterDefaults' from a Dongle database object.")
	argcheck(defaults, 2, "table")

	if defaults.char then copyDefaults(db.char, defaults.char) end
	if defaults.realm then copyDefaults(db.realm, defaults.realm) end
	if defaults.class then copyDefaults(db.class, defaults.class) end
	if defaults.faction then copyDefaults(db.faction, defaults.faction) end
	if defaults.global then copyDefaults(db.global, defaults.global) end
	if defaults.profile then copyDefaults(db.profile, defaults.profile) end

	db.defaults = defaults
end

local function removeDefaults(db, defaults)
	if not db then return end
	for k,v in pairs(defaults) do
		if type(v) == "table" and db[k] then
			removeDefaults(db[k], v)
			if not next(db[k]) then
				db[k] = nil
			end
		else
			if db[k] == defaults[k] then
				db[k] = nil
			end
		end
	end
end

function Dongle:ClearDBDefaults()
	for db in pairs(databases) do
		local defaults = db.defaults
		local sv = db.sv

		if db and defaults then
			if defaults.char then removeDefaults(db.char, defaults.char) end
			if defaults.realm then removeDefaults(db.realm, defaults.realm) end
			if defaults.class then removeDefaults(db.class, defaults.class) end
			if defaults.faction then removeDefaults(db.faction, defaults.faction) end
			if defaults.global then removeDefaults(db.global, defaults.global) end
			if defaults.profile then
				for k,v in pairs(sv.profiles) do
					removeDefaults(sv.profiles[k], defaults.profile)
				end
			end

			-- Remove any blank "profiles"
			if not next(db.char) then sv.char[db.charKey] = nil end
			if not next(db.realm) then sv.realm[db.realmKey] = nil end
			if not next(db.class) then sv.class[db.classKey] = nil end
			if not next(db.faction) then sv.faction[db.factionKey] = nil end
			if not next(db.global) then sv.global = nil end
		end
	end
end

function Dongle.SetProfile(db, name)
	assert(3, databases[db], "You must call 'SetProfile' from a Dongle database object.")
	argcheck(name, 2, "string")

	local sv = db.sv
	local old = sv.profiles[db.profileKey]
	local new = sv.profiles[name]
	local profileCreated

	if not new then
		sv.profiles[name] = {}
		new = sv.profiles[name]
		profileCreated = true
	end

	if db.defaults and db.defaults.profile then
		-- Remove the defaults from the old profile
		removeDefaults(old, db.defaults.profile)

		-- Inject the defaults into the new profile
		copyDefaults(new, db.defaults.profile)
	end

	db.profile = new

	-- Save this new profile name
	sv.profileKeys[db.charKey] = name
    db.profileKey = name

	if profileCreated then
		Dongle:TriggerMessage("DONGLE_PROFILE_CREATED", db, db.parent, db.sv_name, db.profileKey)
	end

	Dongle:TriggerMessage("DONGLE_PROFILE_CHANGED", db, db.parent, db.sv_name, db.profileKey)
end

function Dongle.GetProfiles(db, t)
	assert(3, databases[db], "You must call 'GetProfiles' from a Dongle database object.")
	argcheck(t, 2, "table", "nil")

	t = t or {}
	local i = 1
	for profileKey in pairs(db.profiles) do
		t[i] = profileKey
		i = i + 1
	end
	return t, i - 1
end

function Dongle.DeleteProfile(db, name)
	assert(3, databases[db], "You must call 'DeleteProfile' from a Dongle database object.")
	argcheck(name, 2, "string")

	if db.profileKey == name then
		error("You cannot delete your active profile.  Change profiles, then attempt to delete.", 2)
	end

	db.sv.profiles[name] = nil
	Dongle:TriggerMessage("DONGLE_PROFILE_DELETED", db, db.parent, db.sv_name, name)
end

function Dongle.CopyProfile(db, name)
	assert(3, databases[db], "You must call 'CopyProfile' from a Dongle database object.")
	argcheck(name, 2, "string")

	assert(3, db.profileKey ~= name, "Source/Destination profile cannot be the same profile")
	assert(3, type(db.sv.profiles[name]) == "table", "Profile \""..name.."\" doesn't exist.")

	local profile = db.profile
	local source = db.sv.profiles[name]

	copyDefaults(profile, source, true)
	Dongle:TriggerMessage("DONGLE_PROFILE_COPIED", db, db.parent, db.sv_name, name, db.profileKey)
end

function Dongle.ResetProfile(db)
	assert(3, databases[db], "You must call 'ResetProfile' from a Dongle database object.")

	local profile = db.profile

	for k,v in pairs(profile) do
		profile[k] = nil
	end
	if db.defaults and db.defaults.profile then
		copyDefaults(profile, db.defaults.profile)
	end
	Dongle:TriggerMessage("DONGLE_PROFILE_RESET", db, db.parent, db.sv_name, db.profileKey)
end


function Dongle.ResetDB(db, defaultProfile)
	assert(3, databases[db], "You must call 'ResetDB' from a Dongle database object.")
    argcheck(defaultProfile, 2, "nil", "string")

	local sv = db.sv
	for k,v in pairs(sv) do
		sv[k] = nil
	end

	local parent = db.parent

	initdb(parent, db.sv_name, db.defaults, defaultProfile, db)
	Dongle:TriggerMessage("DONGLE_DATABASE_RESET", db, parent, db.sv_name, db.profileKey)
	Dongle:TriggerMessage("DONGLE_PROFILE_CREATED", db, db.parent, db.sv_name, db.profileKey)
	Dongle:TriggerMessage("DONGLE_PROFILE_CHANGED", db, db.parent, db.sv_name, db.profileKey)
	return db
end

local slashCmdMethods = {
	"RegisterSlashHandler",
	"PrintUsage",
}

local function OnSlashCommand(cmd, cmd_line)
	if cmd.patterns then
		for pattern, tbl in pairs(cmd.patterns) do
			if string.match(cmd_line, pattern) then
				if type(tbl.handler) == "string" then
					cmd.parent[tbl.handler](cmd.parent, string.match(cmd_line, pattern))
				else
					tbl.handler(string.match(cmd_line, pattern))
				end
				return
			end
		end
	end
	cmd:PrintUsage()
end

function Dongle:InitializeSlashCommand(desc, name, ...)
	local reg = lookup[self]
	assert(3, reg, "You must call 'InitializeSlashCommand' from a registered Dongle.")
	argcheck(desc, 2, "string")
	argcheck(name, 3, "string")
	argcheck(select(1, ...), 4, "string")
	for i = 2,select("#", ...) do
		argcheck(select(i, ...), i+2, "string")
	end

	local cmd = {}
	cmd.desc = desc
	cmd.name = name
	cmd.parent = self
	cmd.slashes = { ... }
	for idx,method in pairs(slashCmdMethods) do
		cmd[method] = Dongle[method]
	end

	local genv = getfenv(0)

	for i = 1,select("#", ...) do
		genv["SLASH_"..name..tostring(i)] = "/"..select(i, ...)
	end

	genv.SlashCmdList[name] = function(...) OnSlashCommand(cmd, ...) end

	commands[cmd] = true

	return cmd
end

function Dongle.RegisterSlashHandler(cmd, desc, pattern, handler)
	assert(3, commands[cmd], "You must call 'RegisterSlashHandler' from a Dongle slash command object.")

	argcheck(desc, 2, "string")
	argcheck(pattern, 3, "string")
	argcheck(handler, 4, "function", "string")

	if not cmd.patterns then
		cmd.patterns = {}
	end
	cmd.patterns[pattern] = {
		["desc"] = desc,
		["handler"] = handler,
	}
end

function Dongle.PrintUsage(cmd)
	assert(3, commands[cmd], "You must call 'PrintUsage' from a Dongle slash command object.")

	local usage = cmd.desc.."\n".."/"..table.concat(cmd.slashes, ", /")..":\n"
	if cmd.patterns then
		local descs = {}
		for pattern,tbl in pairs(cmd.patterns) do
			table.insert(descs, tbl.desc)
		end
		table.sort(descs)
		for _,desc in pairs(descs) do
			usage = usage.." - "..desc.."\n"
		end
	end
	cmd.parent:Print(usage)
end

--[[-------------------------------------------------------------------------
  Begin DongleStub required functions and registration
---------------------------------------------------------------------------]]

function Dongle:GetVersion() return major,minor end

local function Activate(self, old)
	if old then
		self.registry = old.registry or registry
		self.lookup = old.lookup or lookup
		self.loadqueue = old.loadqueue or loadqueue
		self.loadorder = old.loadorder or loadorder
		self.events = old.events or events
		self.databases = old.databases or databases
		self.commands = old.commands or commands
		self.messages = old.messages or messages

		registry = self.registry
		lookup = self.lookup
		loadqueue = self.loadqueue
		loadorder = self.loadorder
		events = self.events
		databases = self.databases
		commands = self.commands
		messages = self.messages

		frame = old.frame

		local reg = self.registry[major]
		reg.obj = self
		lookup[self] = reg
		lookup[major] = reg
	else
		self.registry = registry
		self.lookup = lookup
		self.loadqueue = loadqueue
		self.loadorder = loadorder
		self.events = events
		self.databases = databases
		self.commands = commands
		self.messages = messages

		local reg = {obj = self, name = "Dongle"}
		registry[major] = reg
		lookup[self] = reg
		lookup[major] = reg
	end

	if not frame then
		frame = CreateFrame("Frame")
	end

	self.frame = frame
	frame:SetScript("OnEvent", OnEvent)

	-- Register for events using Dongle itself
	self:RegisterEvent("ADDON_LOADED", ADDON_LOADED)
	self:RegisterEvent("PLAYER_LOGIN", PLAYER_LOGIN)
	self:RegisterEvent("PLAYER_LOGOUT", PLAYER_LOGOUT)

	-- Convert all the modules handles
	for name,obj in pairs(registry) do
		for k,v in ipairs(methods) do
			obj[k] = self[v]
		end
	end

	-- Convert all database methods
	for db in pairs(databases) do
		for idx,method in ipairs(dbMethods) do
			db[method] = self[method]
		end
	end

	-- Convert all slash command methods
	for cmd in pairs(commands) do
		for idx,method in ipairs(slashCmdMethods) do
			cmd[method] = self[method]
		end
	end
end

local function Deactivate(self, new)
	self:UnregisterAllEvents()
	lookup[self] = nil
end

DongleStub:Register(Dongle, Activate, Deactivate)