-- $Id: CastCommLib.lua 174 2007-01-11 14:47:14Z pasta $

--[[ LICENSE
Copyright (c) 2006-2007, Kyle Smith
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 author 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.
--]]

--[[ DOCUMENTATION
Description:

CastCommLib is an embedded library for communicating spell casts and their
targets using the AddonMessage channel.  Addons can register with CastCommLib
to receive notification of these messages.

CastCommLib will use ChatThrottleLib if it is available.

API:

CastCommLib:RegisterCallback(key, func)
-- Adds a callback funciton to be invoked when a spell event is received
--
-- If key is a table, then func must be a string and the callback will be
-- invoked as:
--   key[func](key, sender, senderUnit, action, target, channel, spell, rank,
--             displayName, icon, startTime, endTime, isTradeSkill)
-- otherwise func must be a function and will be invoked as:
--   func(sender, senderUnit, action, target, channel, spell, rank,
--        displayName, icon, startTime, endTime, isTradeSkill)
--
-- string sender:
--   the name of the character that sent the event
-- string senderUnit:
--   the unitid of the character that sent the event
-- string action:
--   one of "START", "DELAYED", "INTERRUPTED", "FAILED", "SUCCEEDED",
--          "CHANNEL_START", "CHANNEL_UPDATE", or "CHANNEL_STOP".
--   depending on the event that caused the message to be sent
-- string target:
--   the name of the target for the spell
-- boolean channel:
--   if the spell is channeled
-- string spell:
--   the name of the spell
-- string rank:
--   the rank of the spell
-- string displayName:
--   the name of the spell (not sure how this differes from spell, but it's
--                          from UnitCastingInfo())
-- string icon:
--   the path to the texture for the spell
-- number startTime:
--   the time the spell started casting/channeling
-- number endTime:
--   the time the spell will finish casting/channeling
-- boolean isTradeSkill:
--   if the spell is a tradeskill
--
-- Usage:
--   CastCommLib:RegisterCallback(MyAddon, "CastCommLib")
-- or
--   CastCommLib:RegisterCallback("MyAddon", OnCastCommLibReceived)


CastCommLib:UnregisterCallback(key)
-- Removes the callback function specified by key
--
-- Usage:
--   CastCommLib:UnregisterCallback(MyAddon)
-- or
--   CastCommLib:UnregisterCallback("MyAddon")

CastCommLib:GetUnitId(name)
-- returns the cached unitid for the specified name

CastCommLib:IterateGroupMembers(name)
-- returns an iterator suitable for use with for
-- return values for each iteration is the name of the next person in the
-- specified person's group

CastCommLib:GetCastingInfo(name)
-- returns target, channel, spell, rank, displayName, icon, startTime, endTime,
--         isTradeSkill
--
-- See CastCommLib:RegisterCallback for details

CastCommLib:Enable()
-- Enables the library.  You probably shouldn't be calling this.

CastCommLib:Disable()
-- Disables the library.  You probably shouldn't be calling this.

CastCommLib:ToggleDebugging(on)
-- Enables, disables, or toggles the debugging state of CastCommLib
--
-- If on is nil, the debugging state will be toggled, otherwise it will be set
-- to the value of on.

--]]

local name = "CastCommLib"
local version = tonumber(("$Revision: 174 $"):match("(%d+)")) or 1

local oldLib = getglobal(name)

if oldLib and oldLib.version >= version then return end

local lib = {}
lib.name = name
lib.version = version


--[[ LOCALIZATION ]]----------------------------------------------------------

local L
local currentLocale = GetLocale()

local L_defaults = {
	-- error messages
	ERROR_CALLBACK_REGISTERED = "a callback has already been registered with that key",
	ERROR_CALLBACK_USAGE = string.format("%s version %d usage: RegisterCallback(table, string) or RegisterCallback(key, function)", name, version),
}

if currentLocale == "enUS" then
	L = {}

elseif currentLocale == "deDE" then
	L = {}

elseif currentLocale == "frFR" then
	L = {}

elseif currentLocale == "zhCN" then
	L = {}

elseif currentLocale == "zhTW" then
	L = {}

elseif currentLocale == "koKR" then
	L = {}

elseif currentLocale == "esES" then
	L = {}

else
	L = {}
end

-- provide default translations
setmetatable(L, { __index = L_defaults })


--[[ HERE'S THE REAL CODE ]]--------------------------------------------------

function lib:Initialize (old)
	self:Debug("Initialize()")

	self.callbacks = old and old.callbacks or {}

	self:RegisterEvent("ADDON_LOADED")
end

function lib:Enable ()
	self:Debug("Enable()")

	-- spell
	self:RegisterEvent("UNIT_SPELLCAST_SENT")
	self:RegisterEvent("UNIT_SPELLCAST_START")
	self:RegisterEvent("UNIT_SPELLCAST_DELAYED")
	self:RegisterEvent("UNIT_SPELLCAST_INTERRUPTED")
	self:RegisterEvent("UNIT_SPELLCAST_SUCCEEDED")
	self:RegisterEvent("UNIT_SPELLCAST_FAILED")
	self:RegisterEvent("UNIT_SPELLCAST_CHANNEL_START")
	self:RegisterEvent("UNIT_SPELLCAST_CHANNEL_UPDATE")
	self:RegisterEvent("UNIT_SPELLCAST_CHANNEL_STOP")

	-- comm
	self:RegisterEvent("CHAT_MSG_ADDON")

	-- roster
	self:UpdateRoster()
	self:RegisterEvent("PARTY_MEMBERS_CHANGED", "UpdateRoster")
	self:RegisterEvent("RAID_ROSTER_UPDATE", "UpdateRoster")
end

function lib:Disable ()
	self:Debug("Disable()")
	self:UnregisterAllEvents()
end


--[[ CALLBACK FUNCTIONS ]]----------------------------------------------------

function lib:RegisterCallback (k, f)
	if self.callbacks[k] then
		error(L["ERROR_CALLBACK_REGISTERED"], 2)
	end

	if type(k) == "table" then
		if type(f) ~= "string" then
			error(L["ERROR_CALLBACK_USAGE"], 2)
		end
	elseif type(f) ~= "function" then
		error(L["ERROR_CALLBACK_USAGE"], 2)
	end

	self.callbacks[k] = f
end

function lib:UnregisterCallback (k)
	self.callbacks[k] = nil
end

--[[ SPELL FUNCTIONS ]]-------------------------------------------------------

--[[ Notes on event orders
The following are the order that these events have been known to arrive in:

Casting an instant spell successfully:
UNIT_SPELLCAST_SENT
UNIT_SPELLCAST_SUCCEEDED

Attempting to cast on an in-range target with obstructed line-of-sight:
UNIT_SPELLCAST_SENT
UNIT_SPELLCAST_FAILED

Attempting to cast a non-instant spell while moving:
UNIT_SPELLCAST_FAILED

Casting a non-instant spell and then moving:
UNIT_SPELLCAST_SENT
UNIT_SPELLCAST_START
UNIT_SPELLCAST_INTERRUPTED

Casting a non-instant spell successfully:
UNIT_SPELLCAST_SENT
UNIT_SPELLCAST_START
UNIT_SPELLCAST_SUCCEEDED
UNIT_SPELLCAST_SUCCEEDED

Casting a non-instant spell while being attacked:
UNIT_SPELLCAST_SENT
UNIT_SPELLCAST_START
UNIT_SPELLCAST_DELAYED
UNIT_SPELLCAST_SUCCEEDED
UNIT_SPELLCAST_SUCCEEDED

Casting a channeled spell and then moving:
UNIT_SPELLCAST_SENT
UNIT_SPELLCAST_CHANNEL_START
UNIT_SPELLCAST_SUCCEEDED
UNIT_SPELLCAST_CHANNEL_STOP

Casting a channeled spell successfully:
UNIT_SPELLCAST_SENT
UNIT_SPELLCAST_CHANNEL_START
UNIT_SPELLCAST_SUCCEEDED
UNIT_SPELLCAST_CHANNEL_STOP

Casting a channeled spell while being attacked:
UNIT_SPELLCAST_SENT
UNIT_SPELLCAST_CHANNEL_START
UNIT_SPELLCAST_SUCCEEDED
UNIT_SPELLCAST_CHANNEL_UPDATE
UNIT_SPELLCAST_CHANNEL_STOP

--]]

do
	local lastSpell
	local lastTarget
	local isChanneling
	local isInstant

	-- only fired for player
	function lib:UNIT_SPELLCAST_SENT (unit, spell, rank, target)
		if unit ~= "player" then return end

		lastSpell = spell
		lastTarget = target
		isChanneling = nil
		isInstant = true
	end

	-- only fired for player
	function lib:UNIT_SPELLCAST_START (unit)
		if unit ~= "player" then return end
		if not lastSpell then return end

		isInstant = false
		self:SendMessage("START", lastTarget)
	end

	function lib:UNIT_SPELLCAST_DELAYED (unit)
		if unit ~= "player" then return end
		if not lastSpell then return end

		self:SendMessage("DELAYED")
	end

	function lib:UNIT_SPELLCAST_INTERRUPTED (unit)
		if unit ~= "player" then return end
		if not lastSpell then return end

		self:SendMessage("INTERRUPTED")
		lastSpell = nil
	end

	-- only fired for player
	function lib:UNIT_SPELLCAST_SUCCEEDED (unit, spell, rank)
		if unit ~= "player" then return end
		if not lastSpell then return end
		if isChanneling then return end

		if not isInstant then
			self:SendMessage("SUCCEEDED")
		end
		lastSpell = nil
	end

	function lib:UNIT_SPELLCAST_FAILED (unit)
		if unit ~= "player" then return end
		if not lastSpell then return end

		self:SendMessage("FAILED")
		lastSpell = nil
	end

	function lib:UNIT_SPELLCAST_CHANNEL_START (unit)
		if unit ~= "player" then return end
		if not lastSpell then return end

		isChanneling = true
		isInstant = false
		self:SendMessage("CHANNEL_START", lastTarget)
	end

	function lib:UNIT_SPELLCAST_CHANNEL_UPDATE (unit)
		if unit ~= "player" then return end
		if not lastSpell then return end
		if not isChanneling then return end

		self:SendMessage("CHANNEL_UPDATE")
	end

	function lib:UNIT_SPELLCAST_CHANNEL_STOP (unit)
		if unit ~= "player" then return end
		if not lastSpell then return end
		if not isChanneling then return end

		self:SendMessage("CHANNEL_STOP")
		lastSpell = nil
		isChanneling = nil
	end
end

--[[ ROSTER FUNCTIONS ]]------------------------------------------------------

do
	local roster = {}
	local roster_subgroup = {}

	local party_units = {
		"player", "party1", "party2", "party3", "party4", "party5",
	}

	local raid_units = {
		"raid1", "raid2", "raid3", "raid4", "raid5",
		"raid6", "raid7", "raid8", "raid9", "raid10",
		"raid11", "raid12", "raid13", "raid14", "raid15",
		"raid16", "raid17", "raid18", "raid19", "raid20",
		"raid21", "raid22", "raid23", "raid24", "raid25",
		"raid26", "raid27", "raid28", "raid29", "raid30",
		"raid31", "raid32", "raid33", "raid34", "raid35",
		"raid36", "raid37", "raid38", "raid39", "raid40",
	}

	local unitIndex
	local function ExistingUnits (units)
		unitIndex = unitIndex + 1

		local unit = units[unitIndex]

		if unit and UnitExists(unit) then
			return unit
		else
			return nil
		end
	end
	
	local function UnitIterator (units)
		unitIndex = 0
		return ExistingUnits, units
	end

	local unitsToRemove = {}
	function lib:UpdateRoster ()
		self:Debug("UpdateRoster()")
		local units, inRaid

		if GetNumRaidMembers() > 0 then
			units = raid_units
			inRaid = true
		else
			units = party_units
			inRaid = false
		end

		for name, unit in pairs(roster) do
			unitsToRemove[name] = true
		end

		for unitid in UnitIterator(units) do
			local name = UnitName(unitid)
			local raidIndex = unitid:match("raid(%d+)")
			local subgroup = raidIndex and select(3, GetRaidRosterInfo(raidIndex)) or 1
			self:Debug("Saw", unitid, name, subgroup)
			roster[name] = unitid
			roster_subgroup[name] = subgroup
			unitsToRemove[name] = nil
		end

		for name in pairs(unitsToRemove) do
			self:Debug("Removed", name)
			roster[name] = nil
			roster_subgroup[name] = nil
			unitsToRemove[name] = nil
		end
	end

	function lib:GetUnitId (name)
		return roster[name]
	end

	local igm_index
	local function SubgroupIterator (group)
		igm_index = next(roster_subgroup, igm_index)

		while igm_index and roster_subgroup[igm_index] do
			if roster_subgroup[igm_index] == group then
				return igm_index, roster[igm_index]
			end

			igm_index = next(roster_subgroup, igm_index)
		end
		
		return nil
	end
	
	function lib:IterateGroupMembers (name)
		igm_index = nil

		return SubgroupIterator, roster_subgroup[name]
	end
end


--[[ COMM FUNCTIONS ]]--------------------------------------------------------
-- GetSpellInfo() is in here too

do
	function lib:CHAT_MSG_ADDON (prefix, message, distribution, sender)
		if prefix ~= self.name then return end

		self:ReceiveMessage(sender, message)
	end

	-- ChatThrottleLib support
	local SendAddonMessage = SendAddonMessage
	local CTL
	function lib:ADDON_LOADED (name)
		if ChatThrottleLib == CTL then return end

		CTL = ChatThrottleLib
		self:Debug("ChatThrottleLib", CTL.version, "detected")
		local function CTL_SendAddonMessage (prefix, message,
		                                     distribution)
			return CTL:SendAddonMessage("ALERT", prefix, message,
						    distribution)
		end

		SendAddonMessage = CTL_SendAddonMessage
	end

	-- action abbreviations for the addon message
	-- all abbreviations are 2 letters so we can use string.sub instead of
	-- string.match

	-- lookup action -> abbreviation
	local actionAbbr = {}
	-- lookup abbreviation -> action
	local abbrAction = {}

	-- keep the forward and backup lookups consistent
	-- also, sanity-check the input
	local function actionAbbr__newindex (t, k, v)
		error("don't modify this table!")
	end

	setmetatable(actionAbbr, { __newindex = actionAbbr__newindex })

	local function abbrAction__newindex (t, k, v)
		-- sanity-check our arguments
		if type(k) ~= "string" then
			error("key must be string")
		end
		if type(v) ~= "string" then
			error("value must be string")
		end
		if k:len() ~= 2 then
			error("key must be 2 characters long")
		end

		rawset(t, k, v)
		rawset(actionAbbr, v, k)
	end

	setmetatable(abbrAction, { __newindex = abbrAction__newindex })

	abbrAction["C "] = "START"
	abbrAction["D "] = "DELAYED"
	abbrAction["I "] = "INTERRUPTED"
	abbrAction["F "] = "FAILED"
	abbrAction["S "] = "SUCCEEDED"
	abbrAction["CC"] = "CHANNEL_START"
	abbrAction["CU"] = "CHANNEL_UPDATE"
	abbrAction["CS"] = "CHANNEL_STOP"

	-- these actions mean a new spell has been cast
	local actionSpellBegin = {
		["START"] = true,
		["CHANNEL_START"] = true,
	}

	-- these actions mean we need to update the information about the spell
	local actionSpellContinue = {
		["DELAYED"] = true,
		["CHANNEL_UPDATE"] = true,
	}

	-- these actions mean the spell is no longer being cast
	local actionSpellEnd = {
		["INTERRUPTED"] = true,
		["FAILED"] = true,
		["SUCCEEDED"] = true,
		["CHANNEL_STOP"] = true,
	}

	-- message functions
	function lib:SendMessage(action, target)
		local message

		if actionSpellBegin[action] then
			message = ("%-2s%s"):format(actionAbbr[action], target)
		else
			message = ("%-2s"):format(actionAbbr[action])
		end

		local distribution
		if select(2, IsInInstance()) == "pvp" then
			distribution = "BATTLEGROUND"
		elseif GetNumRaidMembers() > 0 then
			distribution = "RAID"
		elseif GetNumPartyMembers() > 0 then
			distribution = "PARTY"
		end

		self:Debug("SendAddonMessage", self.name, message, distribution)

		if distribution then
			SendAddonMessage(self.name, message, distribution)
		else
			self:ReceiveMessage(UnitName("player"), message)
		end
	end

	-- cache all the spell info because it isn't available after the spell
	-- is finished casting
	local cache_target = {}
	local cache_channel = {}
	local cache_spell = {}
	local cache_rank = {}
	local cache_displayName = {}
	local cache_icon = {}
	local cache_startTime = {}
	local cache_endTime = {}
	local cache_isTradeSkill = {}
	function lib:ReceiveMessage(sender, message)
		local action, target, channel, senderUnit
		local spell, rank, displayName, icon, startTime, endTime,
		      isTradeSkill

		action = message:sub(1, 2)
		target = message:sub(3, -1)

		action = abbrAction[action]

		senderUnit = self:GetUnitId(sender)

		if not senderUnit then
			self:Debug("Unable to determine unit for", sender)
			return
		end

		if action == "START" then
			cache_target[sender] = target
		elseif action == "CHANNEL_START" then
			cache_target[sender] = target
			cache_channel[sender] = true
			channel = true
		else
			target = cache_target[sender]
			channel = cache_channel[sender]
		end

		if not actionSpellEnd[action] then
			if channel then
				spell, rank, displayName, icon, startTime, endTime, isTradeSkill = UnitChannelInfo(senderUnit)
			else
				spell, rank, displayName, icon, startTime, endTime, isTradeSkill = UnitCastingInfo(senderUnit)
			end

			cache_spell[sender] = spell
			cache_rank[sender] = rank
			cache_displayName[sender] = displayName
			cache_icon[sender] = icon
			cache_startTime[sender] = startTime
			cache_endTime[sender] = endTime
			cache_isTradeSkill[sender] = isTradeSkill
		else
			spell = cache_spell[sender]
			rank = cache_rank[sender]
			displayName = cache_displayName[sender]
			icon = cache_icon[sender]
			startTime = cache_startTime[sender]
			endTime = cache_endTime[sender]
			isTradeSkill = cache_isTradeSkill[sender]
		end

		self:Debug("ReceiveMessage", sender, senderUnit, action, target,
			   channel, spell, rank, displayName, icon, startTime,
		           endTime, isTradeSkill)

		for k, f in pairs(self.callbacks) do
			if type(k) == "table" then
				k[f](k, sender, senderUnit, action, target,
				     channel, spell, rank, displayName, icon,
				     startTime, endTime, isTradeSkill)
			else
				f(sender, senderUnit, action, target, channel,
				  spell, rank, displayName, icon, startTime,
				  endTime, isTradeSkill)
			end
		end

		if actionSpellEnd[action] then
			cache_target[sender] = nil
			cache_channel[sender] = nil
			cache_spell[sender] = nil
			cache_rank[sender] = nil
			cache_displayName[sender] = nil
			cache_icon[sender] = nil
			cache_startTime[sender] = nil
			cache_endTime[sender] = nil
			cache_isTradeSkill[sender] = nil
		end
	end

	function lib:GetCastingInfo (name)
		return cache_target[name], cache_channel[name],
		       cache_spell[name], cache_rank[name],
		       cache_displayName[name], cache_icon[name],
		       cache_startTime[name], cache_endTime[name],
		       cache_isTradeskill[name]
	end
end


--[[ LIBRARY CORE ]]----------------------------------------------------------
-- everything below here is just basic library stuff
-- inspired by Dongle and Ace2

--[[ DEBUGGING ]]-------------------------------------------------------------

do
	function lib:InitializeDebugging (old)
		if old then
			self.debug = old.debug
			self.debugFrame = old.debugFrame
		else
			self.debug = false
			self.debugFrame = ChatFrame1
		end
	end

	local debugArgs = {}
	function lib:DebugPrint (...)
		for i = #debugArgs, 1, -1 do
			debugArgs[i] = nil
		end

		table.insert(debugArgs,
		             string.format("|cfffcc000||%s ||%d|||r",
		                           self.name, self.version))

		for i = 1, select("#", ...) do
			table.insert(debugArgs, tostring(select(i, ...)))
		end

		local message = table.concat(debugArgs, " ")

		self.debugFrame:AddMessage(message)
	end

	function lib:ToggleDebugging (on)
		if on == nil then
			self.debug = not self.debug
		else
			self.debug = on
		end

		self:DebugPrint("Debugging",
		                self.debug and "|cff00cf00enabled|r"
		                            or "|cffcf0000disabled|r")

		return self.debug
	end

	function lib:Debug (...)
		if not self.debug then return end
	
		return self:DebugPrint(...)
	end
end


--[[ EVENT HANDLING ]]--------------------------------------------------------

do
	-- event dispatch table
	local events = {}

	-- event dispatch function
	local function OnEvent (frame, event, ...)
		local handler = events[event]

		lib.event = event

		if handler and type(lib[handler]) == "function" then
			lib:Debug("|cffcccc00Event:|r", event, ...)
			return lib[handler](lib, ...)
		end
	end

	-- I'm sure we'll use this for a timer eventually
	local time_since_last_schedule = 0
	local time_until_next_schedule
	local next_scheduled_function
	local function OnUpdate (frame, elapsed)
		-- nothing to do
		if not time_until_next_schedule then return end

		time_since_last_schedule = time_since_last_schedule + elapsed

		-- nothing to do yet
		if time_since_last_schedule < time_until_next_schedule then
			return
		end

		next_scheduled_function()

		-- reset
		time_since_last_schedule = 0
		time_until_next_schedule = nil
		next_scheduled_function = nil
	end

	function lib:InitializeEvents (old)
		if old then
			old:UnregisterAllEvents()
			self.eventFrame = old.eventFrame
			self.eventFrame:SetScript("OnEvent", nil)
			self.eventFrame:SetScript("OnUpdate", nil)
		else
			local frameName = self.name .. "_EVENT_FRAME"
			self.eventFrame = CreateFrame("Frame", frameName)
		end

		self.eventFrame:SetScript("OnEvent", OnEvent)
		-- self.eventFrame:SetScript("OnUpdate", OnUpdate)
	end

	function lib:RegisterEvent (event, handler)
		events[event] = handler or event
		self.eventFrame:RegisterEvent(event)
	end

	function lib:UnregisterEvent(event)
		self.eventFrame:UnregisterEvent(event)
		events[event] = nil
	end

	function lib:UnregisterAllEvents ()
		for event in pairs(events) do
			self:UnregisterEvent(event)
		end
	end

	function lib:TriggerEvent (event, ...)
		return OnEvent(nil, event, ...)
	end
end


--[[ LIBRARY MANAGEMENT ]]----------------------------------------------------

function lib:Install ()
	local old = getglobal(self.name)

	if old then
		old:Disable()
	end

	-- debugging
	self:InitializeDebugging(old)

	-- event handling
	self:InitializeEvents(old)

	-- install the library in the global namespace
	setglobal(self.name, self)

	-- register important event handlers
	self:RegisterEvent("LIB_INSTALLED", "Initialize")
	self:RegisterEvent("PLAYER_LOGIN", "Enable")
	self:RegisterEvent("PLAYER_LOGOUT", "Disable")

	-- tell the library to do it's thing
	self:TriggerEvent("LIB_INSTALLED", old)
end

lib:Install()
