--[[
Name: LibRock-1.0
Revision: $Rev: 63317 $
Developed by: ckknight (ckknight@gmail.com)
Website: http://www.wowace.com/
Description: Library to allow for library and addon creation and easy table recycling functions.
License: LGPL v2.1
]]

local MAJOR_VERSION = "LibRock-1.0"
local MINOR_VERSION = tonumber(("$Revision: 63317 $"):match("(%d+)")) - 60000

local _G = _G
local GetLocale = _G.GetLocale
local CATEGORIES
if GetLocale() == "deDE" then
	CATEGORIES = {
		["Action Bars"] = "Aktionsleisten",
		["Auction"] = "Auktion",
		["Audio"] = "Audio",
		["Battlegrounds/PvP"] = "Schlachtfeld/PvP",
		["Buffs"] = "Stärkungszauber",
		["Chat/Communication"] = "Chat/Kommunikation",
		["Druid"] = "Druide",
		["Hunter"] = "Jäger",
		["Mage"] = "Magier",
		["Paladin"] = "Paladin",
		["Priest"] = "Priester",
		["Rogue"] = "Schurke",
		["Shaman"] = "Schamane",
		["Warlock"] = "Hexenmeister",
		["Warrior"] = "Krieger",
		["Healer"] = "Heiler",
		["Tank"] = "Tank",
		["Caster"] = "Zauberer",
		["Combat"] = "Kampf",
		["Compilations"] = "Zusammenstellungen",
		["Data Export"] = "Datenexport",
		["Development Tools"] = "Entwicklungstools",
		["Guild"] = "Gilde",
		["Frame Modification"] = "Frameveränderungen",
		["Interface Enhancements"] = "Interfaceverbesserungen",
		["Inventory"] = "Inventar",
		["Library"] = "Bibliotheken",
		["Map"] = "Karte",
		["Mail"] = "Post",
		["Miscellaneous"] = "Diverses",
		["Quest"] = "Quest",
		["Raid"] = "Schlachtzug",
		["Tradeskill"] = "Beruf",
		["UnitFrame"] = "Einheiten-Fenster",
	}
elseif GetLocale() == "frFR" then
	CATEGORIES = {
		["Action Bars"] = "Barres d'action",
		["Auction"] = "Hôtel des ventes",
		["Audio"] = "Audio",
		["Battlegrounds/PvP"] = "Champs de bataille/JcJ",
		["Buffs"] = "Buffs",
		["Chat/Communication"] = "Chat/Communication",
		["Druid"] = "Druide",
		["Hunter"] = "Chasseur",
		["Mage"] = "Mage",
		["Paladin"] = "Paladin",
		["Priest"] = "Prêtre",
		["Rogue"] = "Voleur",
		["Shaman"] = "Chaman",
		["Warlock"] = "Démoniste",
		["Warrior"] = "Guerrier",
		["Healer"] = "Soigneur",
		["Tank"] = "Tank",
		["Caster"] = "Casteur",
		["Combat"] = "Combat",
		["Compilations"] = "Compilations",
		["Data Export"] = "Exportation de données",
		["Development Tools"] = "Outils de développement",
		["Guild"] = "Guilde",
		["Frame Modification"] = "Modification des fenêtres",
		["Interface Enhancements"] = "Améliorations de l'interface",
		["Inventory"] = "Inventaire",
		["Library"] = "Bibliothèques",
		["Map"] = "Carte",
		["Mail"] = "Courrier",
		["Miscellaneous"] = "Divers",
		["Quest"] = "Quêtes",
		["Raid"] = "Raid",
		["Tradeskill"] = "Métiers",
		["UnitFrame"] = "Fenêtres d'unité",
	}
elseif GetLocale() == "koKR" then
	CATEGORIES = {
		["Action Bars"] = "액션바",
		["Auction"] = "경매",
		["Audio"] = "음향",
		["Battlegrounds/PvP"] = "전장/PvP",
		["Buffs"] = "버프",
		["Chat/Communication"] = "대화/의사소통",
		["Druid"] = "드루이드",
		["Hunter"] = "사냥꾼",
		["Mage"] = "마법사",
		["Paladin"] = "성기사",
		["Priest"] = "사제",
		["Rogue"] = "도적",
		["Shaman"] = "주술사",
		["Warlock"] = "흑마법사",
		["Warrior"] = "전사",
		["Healer"] = "힐러",
		["Tank"] = "탱커",
		["Caster"] = "캐스터",
		["Combat"] = "전투",
		["Compilations"] = "복합",
		["Data Export"] = "자료 출력",
		["Development Tools"] = "개발 도구",
		["Guild"] = "길드",
		["Frame Modification"] = "구조 변경",
		["Interface Enhancements"] = "인터페이스 강화",
		["Inventory"] = "인벤토리",
		["Library"] = "라이브러리",
		["Map"] = "지도",
		["Mail"] = "우편",
		["Miscellaneous"] = "기타",
		["Quest"] = "퀘스트",
		["Raid"] = "공격대",
		["Tradeskill"] = "전문기술",
		["UnitFrame"] = "유닛 프레임",
	}
elseif GetLocale() == "zhTW" then
	CATEGORIES = {
		["Action Bars"] = "動作列",
		["Auction"] = "拍賣",
		["Audio"] = "音效",
		["Battlegrounds/PvP"] = "戰場/PvP",
		["Buffs"] = "增益",
		["Chat/Communication"] = "聊天/通訊",
		["Druid"] = "德魯伊",
		["Hunter"] = "獵人",
		["Mage"] = "法師",
		["Paladin"] = "聖騎士",
		["Priest"] = "牧師",
		["Rogue"] = "盜賊",
		["Shaman"] = "薩滿",
		["Warlock"] = "術士",
		["Warrior"] = "戰士",
		["Healer"] = "治療者",
		["Tank"] = "坦克",
		["Caster"] = "施法者",
		["Combat"] = "戰鬥",
		["Compilations"] = "整合",
		["Data Export"] = "資料匯出",
		["Development Tools"] = "開發工具",
		["Guild"] = "公會",
		["Frame Modification"] = "框架修改",
		["Interface Enhancements"] = "介面增強",
		["Inventory"] = "庫存",
		["Library"] = "程式庫",
		["Map"] = "地圖",
		["Mail"] = "郵件",
		["Miscellaneous"] = "雜項",
		["Quest"] = "任務",
		["Raid"] = "團隊",
		["Tradeskill"] = "交易技能",
		["UnitFrame"] = "頭像框架",
	}
elseif GetLocale() == "zhCN" then
	CATEGORIES = {
		["Action Bars"] = "动作条",
		["Auction"] = "拍卖",
		["Audio"] = "音频",
		["Battlegrounds/PvP"] = "战场/PvP",
		["Buffs"] = "增益魔法",
		["Chat/Communication"] = "聊天/交流",
		["Druid"] = "德鲁伊",
		["Hunter"] = "猎人",
		["Mage"] = "法师",
		["Paladin"] = "圣骑士",
		["Priest"] = "牧师",
		["Rogue"] = "潜行者",
		["Shaman"] = "萨满祭司",
		["Warlock"] = "术士",
		["Warrior"] = "战士",
		["Healer"] = "治疗",
		["Tank"] = "坦克",
		["Caster"] = "远程输出",
		["Combat"] = "战斗",
		["Compilations"] = "编译",
		["Data Export"] = "数据导出",
		["Development Tools"] = "开发工具",
		["Guild"] = "公会",
		["Frame Modification"] = "框架修改",
		["Interface Enhancements"] = "界面增强",
		["Inventory"] = "背包",
		["Library"] = "库",
		["Map"] = "地图",
		["Mail"] = "邮件",
		["Miscellaneous"] = "杂项",
		["Quest"] = "任务",
		["Raid"] = "团队",
		["Tradeskill"] = "商业技能",
		["UnitFrame"] = "头像框架",
	}
elseif GetLocale() == "esES" then
	CATEGORIES = {
		["Action Bars"] = "Barras de Acción",
		["Auction"] = "Subasta",
		["Audio"] = "Audio",
		["Battlegrounds/PvP"] = "Campos de Batalla/JcJ",
		["Buffs"] = "Buffs",
		["Chat/Communication"] = "Chat/Comunicación",
		["Druid"] = "Druida",
		["Hunter"] = "Cazador",
		["Mage"] = "Mago",
		["Paladin"] = "Paladín",
		["Priest"] = "Sacerdote",
		["Rogue"] = "Pícaro",
		["Shaman"] = "Chamán",
		["Warlock"] = "Brujo",
		["Warrior"] = "Guerrero",
		["Healer"] = "Sanador",
		["Tank"] = "Tanque",
		["Caster"] = "Conjurador",
		["Combat"] = "Combate",
		["Compilations"] = "Compilaciones",
		["Data Export"] = "Exportar Datos",
		["Development Tools"] = "Herramientas de Desarrollo",
		["Guild"] = "Hermandad",
		["Frame Modification"] = "Modificación de Marcos",
		["Interface Enhancements"] = "Mejoras de la Interfaz",
		["Inventory"] = "Inventario",
		["Library"] = "Biblioteca",
		["Map"] = "Mapa",
		["Mail"] = "Correo",
		["Miscellaneous"] = "Misceláneo",
		["Quest"] = "Misión",
		["Raid"] = "Banda",
		["Tradeskill"] = "Habilidad de Comercio",
		["UnitFrame"] = "Marco de Unidades",
	}
else -- enUS
	CATEGORIES = {
		["Action Bars"] = "Action Bars",
		["Auction"] = "Auction",
		["Audio"] = "Audio",
		["Battlegrounds/PvP"] = "Battlegrounds/PvP",
		["Buffs"] = "Buffs",
		["Chat/Communication"] = "Chat/Communication",
		["Druid"] = "Druid",
		["Hunter"] = "Hunter",
		["Mage"] = "Mage",
		["Paladin"] = "Paladin",
		["Priest"] = "Priest",
		["Rogue"] = "Rogue",
		["Shaman"] = "Shaman",
		["Warlock"] = "Warlock",
		["Warrior"] = "Warrior",
		["Healer"] = "Healer",
		["Tank"] = "Tank",
		["Caster"] = "Caster",
		["Combat"] = "Combat",
		["Compilations"] = "Compilations",
		["Data Export"] = "Data Export",
		["Development Tools"] = "Development Tools",
		["Guild"] = "Guild",
		["Frame Modification"] = "Frame Modification",
		["Interface Enhancements"] = "Interface Enhancements",
		["Inventory"] = "Inventory",
		["Library"] = "Library",
		["Map"] = "Map",
		["Mail"] = "Mail",
		["Miscellaneous"] = "Miscellaneous",
		["Quest"] = "Quest",
		["Raid"] = "Raid",
		["Tradeskill"] = "Tradeskill",
		["UnitFrame"] = "UnitFrame",
	}
end

local select = _G.select
local tostring = _G.tostring
local pairs = _G.pairs
local ipairs = _G.ipairs
local error = _G.error
local setmetatable = _G.setmetatable
local getmetatable = _G.getmetatable
local type = _G.type
local pcall = _G.pcall
local next = _G.next
local tonumber = _G.tonumber
local strmatch = _G.strmatch
local table_remove = _G.table.remove
local debugstack = _G.debugstack
local LoadAddOn = _G.LoadAddOn
local GetAddOnInfo = _G.GetAddOnInfo
local GetAddOnMetadata = _G.GetAddOnMetadata
local GetNumAddOns = _G.GetNumAddOns
local DisableAddOn = _G.DisableAddOn
local EnableAddOn = _G.EnableAddOn
local IsAddOnLoadOnDemand = _G.IsAddOnLoadOnDemand
local IsLoggedIn = _G.IsLoggedIn
local geterrorhandler = _G.geterrorhandler
local assert = _G.assert
local collectgarbage = _G.collectgarbage
local table_sort = _G.table.sort
local table_concat = _G.table.concat

-- #AUTODOC_NAMESPACE Rock


local LibStub = _G.LibStub

local Rock = LibStub:GetLibrary(MAJOR_VERSION, true) or _G.Rock
local oldRock
if not Rock then
	Rock = LibStub:NewLibrary(MAJOR_VERSION, MINOR_VERSION)
	if not Rock then
		return
	end
	Rock.name = MAJOR_VERSION
else
	Rock, oldRock = Rock:NewLibrary(MAJOR_VERSION, MINOR_VERSION)
	if not Rock then
		return
	end
end
_G.Rock = Rock

local L = setmetatable({}, {__index=function(self,key) self[key] = key; return key end})
if GetLocale() == "zhCN" then
	L["Advanced options"] = "高级选项"
	L["Advanced options for developers and power users."] = "开发者与高级用户的高级选项"
	L["Unit tests"] = "框体测试"
	L["Enable unit tests to be run. This is for developers only.\n\nYou must ReloadUI for changes to take effect."] = "开启框体测试，仅供开发者使用。\n\n需要重载用户界面。"
	L["Contracts"] = "侦错协定"
	L["Enable contracts to be run. This is for developers and anyone wanting to file a bug. Do not file a bug unless contracts are enabled. This will slightly slow down your addons if enabled."] = "启用侦错协定，这是给插件作者用来通报错误所使用。"
	L["Reload UI"] = "重载UI"
	L["Reload the User Interface for some changes to take effect."] = "部分功能更改需要重载用户界面才会生效。"
	L["Reload"] = "重载"
	L["Give donation"] = "捐赠"
	L["Donate"] = "捐赠"
	L["Give a much-needed donation to the author of this addon."] = "给插件作者捐赠支持插件开发。"
	L["File issue"] = "通报错误"
	L["Report"] = "报告"
	L["File a bug or request a new feature or an improvement to this addon."] = "发送错误报告或请求新功能及要改进的部分。"
	L["Press Ctrl-C to copy, then Alt-Tab out of the game, open your favorite web browser, and paste the link into the address bar."] = "Ctrl-C复制网址，Alt-Tab切换到桌面，打开浏览器，在地址栏贴上网址。"
	L["Press Cmd-C to copy, then Cmd-Tab out of the game, open your favorite web browser, and paste the link into the address bar."] = "Cmd-C复制网址，Cmd-Tab切换到电脑桌面，打开浏览器，在地址栏贴上网址。"
	L["Enabled"] = "开启"
	L["Enable or disable this addon."] = "启用这个插件。"	

elseif GetLocale() == "zhTW" then
	L["Advanced options"] = "進階選項"
	L["Advanced options for developers and power users."] = "插件作者、進階用戶選項"
	L["Unit tests"] = "單元測試"
	L["Enable unit tests to be run. This is for developers only.\n\nYou must ReloadUI for changes to take effect."] = "啟用單元測試，這是給插件作者使用的功能。\n\n需要重載介面才能使用。"
	L["Contracts"] = "偵錯協定"
	L["Enable contracts to be run. This is for developers and anyone wanting to file a bug. Do not file a bug unless contracts are enabled. This will slightly slow down your addons if enabled."] = "啟用偵錯協定，這是給插件作者用來通報錯誤所使用。"
	L["Reload UI"] = "重載介面"
	L["Reload the User Interface for some changes to take effect."] = "重新載入使用者介面，部分功能才會生效。"
	L["Reload"] = "重載"
	L["Give donation"] = "捐贈"
	L["Donate"] = "捐贈"
	L["Give a much-needed donation to the author of this addon."] = "捐贈金錢給插件作者。"
	L["File issue"] = "通報錯誤"
	L["Report"] = "報告"
	L["File a bug or request a new feature or an improvement to this addon."] = "發出錯誤報告或請求新功能及要改進的部分。"
	L["Press Ctrl-C to copy, then Alt-Tab out of the game, open your favorite web browser, and paste the link into the address bar."] = "Ctrl-C複製網址，Alt-Tab切換到電腦桌面，打開瀏覽器，在網址列貼上網址。"
	L["Press Cmd-C to copy, then Cmd-Tab out of the game, open your favorite web browser, and paste the link into the address bar."] = "Cmd-C複製網址，Cmd-Tab切換到電腦桌面，打開瀏覽器，在網址列貼上網址。"
	L["Enabled"] = "啟用"
	L["Enable or disable this addon."] = "啟用這個插件。"
elseif GetLocale() == "koKR" then
	L["Advanced options"] = "상세 옵션"
	L["Advanced options for developers and power users."] = "개발자와 파워 사용자를 위한 상세 옵션입니다."
	L["Unit tests"] = "유닛 테스트"
	L["Enable unit tests to be run. This is for developers only.\n\nYou must ReloadUI for changes to take effect."] = "유닛 테스트를 사용합니다. 이것은 개발자만을 위한 옵션입니다.\n\n변경된 결과를 적용하기 위해 당신의 UI를 재실행 합니다."
	L["Contracts"] = "계약"
	L["Enable contracts to be run. This is for developers and anyone wanting to file a bug. Do not file a bug unless contracts are enabled. This will slightly slow down your addons if enabled."] = "계약을 사용합니다. 이것은 개발자와 버그 파일을 알릴 분이면 누구나 사용 가능합니다. 계약이 가능하지 않으면 버그 파일을 보내지 마십시오. 이것은 당신의 애드온 속도를 약간 떨어뜨립니다."
	L["Reload UI"] = "UI 재실행"
	L["Reload the User Interface for some changes to take effect."] = "변경된 결과를 적용하기 위해 사용자 인터페이스를 재실행합니다."
	L["Reload"] = "재실행"
	L["Give donation"] = "기부"
	L["Donate"] = "기부"
	L["Give a much-needed donation to the author of this addon."] = "이 애드온의 제작자에게 필요한 기부를 합니다."
	L["File issue"] = "파일 이슈"
	L["Report"] = "보고"
	L["File a bug or request a new feature or an improvement to this addon."] = "버그 파일을 알리거나 새로운 기능 또는 이 애드온에 대한 개선을 부탁합니다."
	L["Press Ctrl-C to copy, then Alt-Tab out of the game, open your favorite web browser, and paste the link into the address bar."] = "Ctrl-C로 복사합니다. Alt-Tab 눌려 게임으로 부터 나간후 웹 브라우저를 엽니다. 복사된 링크를 주소 창에 붙여넣기 합니다."
	L["Press Cmd-C to copy, then Cmd-Tab out of the game, open your favorite web browser, and paste the link into the address bar."] = "Cmd-C로 복사합니다. Cmd-Tab 눌려 게임으로 부터 나간후 웹 브라우저를 엽니다. 복사된 링크를 주소 창에 붙여넣기 합니다."
	L["Enabled"] = "사용"
	L["Enable or disable this addon."] = "이 애드온을 사용하거나 사용하지 않습니다."
elseif GetLocale() == "frFR" then
	L["Advanced options"] = "Options avancées"
	L["Advanced options for developers and power users."] = "Options avancées à l'attention des développeurs et des utilisateurs expérimentés."
	L["Reload UI"] = "Recharger IU"
	L["Reload the User Interface for some changes to take effect."] = "Recharge l'interface utilisateur afin que certains changements prennent effet."
	L["Reload"] = "Recharger"
	L["Give donation"] = "Faire un don"
	L["Donate"] = "Don"
	L["Give a much-needed donation to the author of this addon."] = "Permet de faire un don bien mérité à l'auteur de cet addon."
	L["File issue"] = "Problème"
	L["Report"] = "Signaler"
	L["File a bug or request a new feature or an improvement to this addon."] = "Permet de signaler un bogue ou de demander une amélioration à cet addon."
	L["Press Ctrl-C to copy, then Alt-Tab out of the game, open your favorite web browser, and paste the link into the address bar."] = "Ctrl-C pour copier, puis Alt-Tab pour sortir du jeu. Ouvrez votre navigateur internet et collez le lien dans la barre d'adresse."
	L["Press Cmd-C to copy, then Cmd-Tab out of the game, open your favorite web browser, and paste the link into the address bar."] = "Cmd-C pour copier, puis Alt-Tab pour sortir du jeu. Ouvrez votre navigateur internet et collez le lien dans la barre d'adresse."
	L["Enabled"] = "Activé"
	L["Enable or disable this addon."] = "Active ou désactive cet addon."
end

local isStandalone = debugstack():match("[Oo%.][Nn%.][Ss%.]\\([^\\]+)\\") == MAJOR_VERSION or nil
local unitTestDB, enableContracts

local weakKey = { __mode = 'k' }

-- frame to manage events from
Rock.frame = oldRock and oldRock.frame or _G.CreateFrame("Frame")
local frame = Rock.frame
-- dict of libraries in { ["major"] = object } form
Rock.libraries = oldRock and oldRock.libraries or { [MAJOR_VERSION] = Rock }
local libraries = Rock.libraries
-- set of libraries which have gone through the finalization process in { [object] = true } form
Rock.finalizedLibraries = setmetatable(oldRock and oldRock.finalizedLibraries or { }, weakKey)
local finalizedLibraries = Rock.finalizedLibraries
-- set of libraries which have been tried to be loaded.
Rock.scannedLibraries = oldRock and oldRock.scannedLibraries or {}
local scannedLibraries = Rock.scannedLibraries
-- exportedMethods[library] = { "method1", "method2" }
Rock.exportedMethods = setmetatable(oldRock and oldRock.exportedMethods or {}, weakKey)
local exportedMethods = Rock.exportedMethods
-- mixinToObject[mixin][object] = true
Rock.mixinToObject = setmetatable(oldRock and oldRock.mixinToObject or {}, weakKey)
local mixinToObject = Rock.mixinToObject
-- dict of addons in { ["name"] = object } form
Rock.addons = oldRock and oldRock.addons or {}
local addons = Rock.addons
-- set of libraries that should be finalized before ADDON_LOADED.
Rock.pendingLibraries = setmetatable(oldRock and oldRock.pendingLibraries or { }, weakKey)
local pendingLibraries = Rock.pendingLibraries
-- list of addons in order of created that need to be initialized by ADDON_LOADED.
Rock.pendingAddons = oldRock and oldRock.pendingAddons or {}
local pendingAddons = Rock.pendingAddons
-- dictionary of addons to their folder names
Rock.addonToFolder = oldRock and oldRock.addonToFolder or {}
local addonToFolder = Rock.addonToFolder
-- set of folders which have been loaded
Rock.foldersLoaded = oldRock and oldRock.foldersLoaded or {}
local foldersLoaded = Rock.foldersLoaded
-- list of addons in order of created that need to be enabled by PLAYER_LOGIN.
Rock.pendingAddonsEnable = oldRock and oldRock.pendingAddonsEnable or {}
local pendingAddonsEnable = Rock.pendingAddonsEnable
-- set of addons which have been enabled at least once.
Rock.addonsAlreadyEnabled = oldRock and oldRock.addonsAlreadyEnabled or {}
local addonsAlreadyEnabled = Rock.addonsAlreadyEnabled
-- set of addons which have no database and are set to be inactive.
Rock.inactiveAddons = oldRock and oldRock.inactiveAddons or {}
local inactiveAddons = Rock.inactiveAddons
-- set of addons which are currently enabled (not necessarily should be)
Rock.currentlyEnabledAddons = oldRock and oldRock.currentlyEnabledAddons or {}
local currentlyEnabledAddons = Rock.currentlyEnabledAddons
-- dictionary of namespace to list of functions which will be run.
Rock.unitTests = oldRock and oldRock.unitTests or {}
local unitTests = Rock.unitTests
-- metatable for addons
Rock.addon_mt = oldRock and oldRock.addon_mt or {}
local addon_mt = Rock.addon_mt
for k in pairs(addon_mt) do
	addon_mt[k] = nil
end
function addon_mt:__tostring()
	return tostring(self.name)
end

local function better_tostring(self)
	if type(self) == "table" and self.name then
		return tostring(self.name)
	end
	return tostring(self)
end

local function figureCurrentAddon(pos)
	local stack = debugstack(pos+1, 1, 0)
	local folder = stack:match("[Oo%.][Nn%.][Ss%.]\\([^\\]+)\\")
	if folder then
		return folder
	end

	local partFolder = stack:match("...([^\\]+)\\")
	if partFolder then
		local partFolder_len = #partFolder
		for i = 1, GetNumAddOns() do
			local name = GetAddOnInfo(i)
			if #name >= partFolder_len then
				local partName = name:sub(-partFolder_len)
				if partName == partFolder then
					return name
				end
			end
		end
	end
	return nil
end

--[[---------------------------------------------------------------------------
Returns:
	string - the localized name of the given category.
Arguments:
	string - the English name of the category.
Example:
	local uf = Rock:GetLocalizedCategory("UnitFrame")
-----------------------------------------------------------------------------]]
function Rock:GetLocalizedCategory(name)
	if type(name) ~= "string" then
		error(("Bad argument #2 to `GetLocalizedCategory'. Expected %q, got %q."):format("string", type(name)), 2)
	end
	local cat = CATEGORIES[name]
	if cat then
		return cat
	end
	local name_lower = name:lower()
	for k in pairs(CATEGORIES) do
		if k:lower() == name_lower then
			return k
		end
	end
	return _G.UNKNOWN or "Unknown"
end

local weak = {__mode = 'kv'}

Rock.recycleData = oldRock and oldRock.recycleData or {}
local recycleData = Rock.recycleData
if recycleData.pools then
	setmetatable(recycleData.pools, weak)
end
if recycleData.debugPools then
	setmetatable(recycleData.debugPools, weak)
end
if recycleData.newList then
	setmetatable(recycleData.newList, weak)
end
if recycleData.newDict then
	setmetatable(recycleData.newDict, weak)
end
if recycleData.newSet then
	setmetatable(recycleData.newSet, weak)
end
if recycleData.del then
	setmetatable(recycleData.del, weak)
end

local tmp = {}
local function myUnpack(t, start)
	if not start then
		start = 1
	end
	local value = t[start]
	if value == nil then
		return
	end
	t[start] = nil
	return value, myUnpack(t, start+1)
end

--[[---------------------------------------------------------------------------
Notes:
	* Returns functions for the specified namespace based on what is provided.
	* function types:
	; "newList" : to create a list
	; "newDict" : to create a dictionary
	; "newSet" : to create a set
	; "del" : to delete a table
	; "unpackListAndDel" : deletes a table and returns what its contents were as a list, in order.
	; "unpackSetAndDel" : deletes a table and returns what its contents were as a set, in no particular order.
	; "unpackDictAndDel" : deletes a table and returns what its contents were as a dictionary, in no particular order.
	* If you provide "Debug" as the last argument, then the namespace can be debugged with ''':DebugRecycle'''
	* It is '''not recommended''' to use table recycling with tables that have more than 128 keys, as it is typically faster to let lua's garbage collector handle it.
Arguments:
	string - the namespace. ''Note: this doesn't necessarily have to be a string.''
Example:
	local newList, newDict, newSet, del, unpackListAndDel, unpackSetAndDel, unpackDictAndDel = Rock:GetRecyclingFunctions("MyNamespace", "newList", "newDict", "newSet", "del", "unpackListAndDel", "unpackSetAndDel", "unpackDictAndDel")

	local t = newList('alpha', 'bravo') -- same as t = {'alpha', 'bravo'}
	local u = newDict('alpha', 'bravo') -- same as t = {['alpha'] = 'bravo'}
	local v = newSet('alpha', 'bravo') -- same as t = {['alpha'] = true, ['bravo'] = true}
	t = del(t) -- you want to clear your reference as well as deleting.
	u = del(u)
	v = del(v)

	-- for debugging
	local newList = Rock:GetRecyclingFunctions("MyNamespace", "newList", "Debug")
	local t = newList()
	Rock:DebugRecycle("MyNamespace")
	t = del(t)

	-- unpacking functions
	unpackListAndDel(newList(...)) => ...
	unpackSetAndDel(newSet(...)) => ...
	unpackDictAndDel(newDict(...)) => ...
	newList(unpackListAndDel(t)) => t
	newSet(unpackSetAndDel(t)) => t
	newDict(unpackDictAndDel(t)) => t
	-- as you can see, they are inverses of each other.
-----------------------------------------------------------------------------]]
function Rock:GetRecyclingFunctions(namespace, ...)
	local pools = recycleData.pools
	if not pools then
		pools = setmetatable({}, weak)
		recycleData.pools = pools
	end
	if namespace == "newList" or namespace == "newSet" or namespace == "newDict" or namespace == "del" or namespace == "unpackListAndDel" or namespace == "unpackSetAndDel" or namespace == "unpackDictAndDel" then
		error(("Bad argument #2 to `GetRecyclingFunctions'. Cannot be %q"):format(namespace), 2)
	end
	local pool = pools[namespace]
	if not pool then
		pool = setmetatable({}, weak)
		pools[namespace] = pool
	end
	local n = select('#', ...)
	local debug = select(n, ...) == "Debug"
	if debug then
		n = n - 1
		local debugPools = recycleData.debugPools
		if not debugPools then
			debugPools = setmetatable({}, weak)
			recycleData.debugPools = debugPools
		end
		debug = debugPools[namespace]
		if not debug then
			debug = { num = 0 }
			debugPools[namespace] = debug
		end
	elseif recycleData.debugPools and recycleData.debugPools[namespace] then
		debug = recycleData.debugPools[namespace]
	end
	for i = 1, n do
		local func = select(i, ...)
		local recycleData_func = recycleData[func]
		if not recycleData_func then
			recycleData_func = setmetatable({}, weak)
			recycleData[func] = recycleData_func
		end
		if func == "newList" then
			local newList = recycleData_func[namespace]
			if not newList then
				function newList(...)
					local t = next(pool)
					local n = select('#', ...)
					if t then
						pool[t] = nil
						for i = 1, n do
							t[i] = select(i, ...)
						end
					else
						t = { ... }
					end

					if debug then
						debug[t] = debugstack(2)
						debug.num = debug.num + 1
					end

					return t, n
				end
				recycleData_func[namespace] = newList
			end
			tmp[i] = newList
		elseif func == "newDict" then
			local newDict = recycleData_func[namespace]
			if not newDict then
				function newDict(...)
					local t = next(pool)
					if t then
						pool[t] = nil
					else
						t = {}
					end

					for i = 1, select('#', ...), 2 do
						t[select(i, ...)] = select(i+1, ...)
					end

					if debug then
						debug[t] = debugstack(2)
						debug.num = debug.num + 1
					end

					return t
				end
				recycleData_func[namespace] = newDict
			end
			tmp[i] = newDict
		elseif func == "newSet" then
			local newSet = recycleData_func[namespace]
			if not newSet then
				function newSet(...)
					local t = next(pool)
					if t then
						pool[t] = nil
					else
						t = {}
					end

					for i = 1, select('#', ...) do
						t[select(i, ...)] = true
					end

					if debug then
						debug[t] = debugstack(2)
						debug.num = debug.num + 1
					end

					return t
				end
				recycleData_func[namespace] = newSet
			end
			tmp[i] = newSet
		elseif func == "del" then
			local del = recycleData_func[namespace]
			if not del then
				function del(t)
					if not t then
						error(("Bad argument #1 to `del'. Expected %q, got %q."):format("table", type(t)), 2)
					end
					if pool[t] then
						local _, ret = pcall(error, "Error, double-free syndrome.", 3)
						geterrorhandler()(ret)
					end
					setmetatable(t, nil)
					for k in pairs(t) do
						t[k] = nil
					end
					t[true] = true
					t[true] = nil
					pool[t] = true

					if debug then
						debug[t] = nil
						debug.num = debug.num - 1
					end
					return nil
				end
				recycleData_func[namespace] = del
			end
			tmp[i] = del
		elseif func == "unpackListAndDel" then
			local unpackListAndDel = recycleData_func[namespace]
			if not unpackListAndDel then
				local function f(t, start, finish)
					if start > finish then
						for k in pairs(t) do
							t[k] = nil
						end
						t[true] = true
						t[true] = nil
						pool[t] = true
						return
					end
					return t[start], f(t, start+1, finish)
				end
				function unpackListAndDel(t, start, finish)
					if not t then
						error(("Bad argument #1 to `unpackListAndDel'. Expected %q, got %q."):format("table", type(t)), 2)
					end
					if not start then
						start = 1
					end
					if not finish then
						finish = #t
					end
					setmetatable(t, nil)
					if debug then
						debug[t] = nil
						debug.num = debug.num - 1
					end
					return f(t, start, finish)
				end
			end
			tmp[i] = unpackListAndDel
		elseif func == "unpackSetAndDel" then
			local unpackSetAndDel = recycleData_func[namespace]
			if not unpackSetAndDel then
				local function f(t, current)
					current = next(t, current)
					if current == nil then
						for k in pairs(t) do
							t[k] = nil
						end
						t[true] = true
						t[true] = nil
						pool[t] = true
						return
					end
					return current, f(t, current)
				end
				function unpackSetAndDel(t)
					if not t then
						error(("Bad argument #1 to `unpackListAndDel'. Expected %q, got %q."):format("table", type(t)), 2)
					end
					setmetatable(t, nil)
					if debug then
						debug[t] = nil
						debug.num = debug.num - 1
					end
					return f(t, nil)
				end
			end
			tmp[i] = unpackSetAndDel
		elseif func == "unpackDictAndDel" then
			local unpackDictAndDel = recycleData_func[namespace]
			if not unpackDictAndDel then
				local function f(t, current)
					local value
					current, value = next(t, current)
					if current == nil then
						for k in pairs(t) do
							t[k] = nil
						end
						t[true] = true
						t[true] = nil
						pool[t] = true
						return
					end
					return current, value, f(t, current)
				end
				function unpackDictAndDel(t)
					if not t then
						error(("Bad argument #1 to `unpackListAndDel'. Expected %q, got %q."):format("table", type(t)), 2)
					end
					setmetatable(t, nil)
					if debug then
						debug[t] = nil
						debug.num = debug.num - 1
					end
					return f(t, nil)
				end
			end
			tmp[i] = unpackDictAndDel
		else
			error(("Bad argument #%d to `GetRecyclingFunctions': %q, %q, %q, %q, %q, %q, or %q expected, got %s"):format(i+2, "newList", "newDict", "newSet", "del", "unpackListAndDel", "unpackSetAndDel", "unpackDictAndDel", type(func) == "string" and ("%q"):format(func) or tostring(func)), 2)
		end
	end
	return myUnpack(tmp)
end

--[[---------------------------------------------------------------------------
Notes:
	* Prints information about the specified recycling namespace, including what tables are still in play and where they come from and how many there are.
	* This goes in tandem with ''':GetRecyclingFunctions'''
Arguments:
	string - the namespace. ''Note: this doesn't necessarily have to be a string.''
Example:
	local newList = Rock:GetRecyclingFunctions("MyNamespace", "newList", "Debug")
	local t = newList()
	Rock:DebugRecycle("MyNamespace")
	t = del(t)
-----------------------------------------------------------------------------]]
function Rock:DebugRecycle(namespace)
	local debug = recycleData.debugPools and recycleData.debugPools[namespace]
	if not debug then
		return
	end
	for k, v in pairs(debug) do
		if k ~= "num" then
			_G.DEFAULT_CHAT_FRAME:AddMessage(v)
			_G.DEFAULT_CHAT_FRAME:AddMessage("------")
		end
	end
	_G.DEFAULT_CHAT_FRAME:AddMessage(("%s: %d tables in action."):format(tostring(namespace), debug.num))
end

local newList, del, unpackListAndDel, unpackDictAndDel = Rock:GetRecyclingFunctions(MAJOR_VERSION, "newList", "del", "unpackListAndDel", "unpackDictAndDel")

--[[---------------------------------------------------------------------------
Notes:
	* Adds a unit test for the specified namespace
	* The function provided is called, and it should be where tests are performed, if a problem occurs, an error should fire. If no problems occur, it should return silently.
	* You can have as many tests per namespace as you want.
Arguments:
	string - the namespace.
	function - the function to call.
Example:
	Rock:AddUnitTest("LibMonkey-1.0", function()
		local LibMonkey = Rock("LibMonkey-1.0")
		assert(LibMonkey:Fling() == "Poo")
	end)
-----------------------------------------------------------------------------]]
function Rock:AddUnitTest(namespace, func)
	if not isStandalone then
		return
	end
	if type(namespace) ~= "string" then
		error(("Bad argument #2 to `AddUnitTest'. Expected %q, got %q."):format("string", type(namespace)), 2)
	end
	if namespace:find("^Lib[A-Z]") then
		local addon = figureCurrentAddon(2)
		if addon ~= namespace then
			return
		end
	end
	if type(func) ~= "function" then
		error(("Bad argument #3 to `AddUnitTest'. Expected %q, got %q."):format("function", type(func)), 2)
	end
	local addon = figureCurrentAddon(2)
	if libraries[namespace] and addon ~= namespace then
		-- only work on standalone libraries.
		return
	end
	local unitTests_namespace = unitTests[namespace]
	if not unitTests_namespace then
		unitTests_namespace = newList()
		unitTests[namespace] = unitTests_namespace
	end
	if not unitTests_namespace.addon then
		unitTests_namespace.addon = addon
	end
	if unitTestDB and not unitTestDB[namespace] then
		return
	end
	unitTests_namespace[#unitTests_namespace+1] = func
end

local LibRockEvent
local LibRockModuleCore
local OpenDonationFrame, OpenIssueFrame
function Rock:OnLibraryLoad(major, library)
	if major == "LibRockEvent-1.0" then
		LibRockEvent = library
		LibRockEvent:Embed(Rock)
	elseif major == "LibRockModuleCore-1.0" then
		LibRockModuleCore = library
	elseif major == "LibRockConfig-1.0" then
		if isStandalone then
			library.rockOptions.args.advanced = {
				type = 'group',
				groupType = 'inline',
				name = L["Advanced options"],
				desc = L["Advanced options for developers and power users."],
				order = -1,
				args = {
					unitTests = {
						type = 'multichoice',
						name = L["Unit tests"],
						desc = L["Enable unit tests to be run. This is for developers only.\n\nYou must ReloadUI for changes to take effect."],
						get = function(key)
							return unitTestDB[key]
						end,
						set = function(key, value)
							unitTestDB[key] = value or nil
						end,
						choices = function()
							local t = newList()
							for k in pairs(unitTests) do
								t[k] = k
							end
							return "@dict", unpackDictAndDel(t)
						end
					},
					contracts = {
						type = 'boolean',
						name = L["Contracts"],
						desc = L["Enable contracts to be run. This is for developers and anyone wanting to file a bug. Do not file a bug unless contracts are enabled. This will slightly slow down your addons if enabled."],
						get = function()
							return enableContracts
						end,
						set = function(value)
							_G.LibRock_1_0DB.contracts = value or nil
							enableContracts = value
						end,
					}
				}
			}
		end
		library.rockOptions.args.reloadui = {
			type = 'execute',
			name = L["Reload UI"],
			desc = L["Reload the User Interface for some changes to take effect."],
			buttonText = L["Reload"],
			func = function()
				_G.ReloadUI()
			end,
			order = -2,
		}
		Rock.donate = "Paypal:ckknight AT gmail DOT com"
		library.rockOptions.args.donate = {
			type = 'execute',
			name = L["Give donation"],
			buttonText = L["Donate"],
			desc = L["Give a much-needed donation to the author of this addon."],
			func = OpenDonationFrame,
			passValue = Rock,
			order = -3,
		}
		Rock.issueTracker = "Wowace:10027"
		library.rockOptions.args.issue = {
			type = 'execute',
			name = L["File issue"],
			buttonText = L["Report"],
			desc = L["File a bug or request a new feature or an improvement to this addon."],
			func = OpenIssueFrame,
			passValue = Rock,
			order = -4,
		}
	end
end

addon_mt.__index = {}
local addon_mt___index = addon_mt.__index
--[[---------------------------------------------------------------------------
#FORCE_DOC
Notes:
	* This is exported to all addons.
	* This information is retrieved from LibRockModuleCore-1.0 if it is a module, otherwise from LibRockDB-1.0 if it uses that as a mixin, otherwise it keeps a variable locally.
Returns:
	boolean - whether the addon is in an active state or not.
Example:
	local active = MyAddon:IsActive()
-----------------------------------------------------------------------------]]
function addon_mt___index:IsActive()
	if LibRockModuleCore then
		local core = LibRockModuleCore:HasModule(self)
		if core then
			return core:IsModuleActive(self)
		end
	end

	local self_db = self.db
	if self_db then
		local disabled
		local self_db_raw = self_db.raw
		if self_db_raw then
			local self_db_raw_disabled = self_db_raw.disabled
			if self_db_raw_disabled then
				local profile = type(self.GetProfile) == "function" and select(2, self:GetProfile()) or false
				disabled = self_db_raw_disabled[profile]
			end
		else
			return false
		end
		return not disabled
	end

	return not inactiveAddons[self]
end
--[[---------------------------------------------------------------------------
#FORCE_DOC
Notes:
	* This is exported to all addons.
	* If it enables the addon, it will call :OnEnable(first) on the addon and :OnEmbedEnable(addon, first) on all its mixins.
	* If it disables the addon, it will call :OnDisable(first) on the addon and :OnEmbedDisable(addon, first) on all its mixins.
	* This information is stored by LibRockModuleCore-1.0 if it is a module, otherwise from LibRockDB-1.0 if it uses that as a mixin, otherwise it keeps a variable locally.
Arguments:
	[optional] boolean - whether the addon should be in an active state or not. Default: not :IsActive()
Returns:
	boolean - whether the addon is in an active state or not.
Example:
	MyAddon:ToggleActive() -- switch
	MyAddon:ToggleActive(true) -- force on
	MyAddon:ToggleActive(false) -- force off
-----------------------------------------------------------------------------]]
function addon_mt___index:ToggleActive(state)
	if state and state ~= true then
		error(("Bad argument #2 to `ToggleActive'. Expected %q or %q, got %q."):format("boolean", "nil", type(state)), 2)
	end
	if LibRockModuleCore then
		local core = LibRockModuleCore:HasModule(self)
		if core then
			return core:ToggleModuleActive(self, state)
		end
	end

	local self_db = self.db
	if self_db then
		local self_db_raw = self_db.raw
		if not self_db_raw then
			error("Error saving to database with `ToggleActive'. db.raw not available.", 2)
		end
		local self_db_raw_disabled = self_db_raw.disabled
		if not self_db_raw_disabled then
			self_db_raw_disabled = newList()
			self_db_raw.disabled = self_db_raw_disabled
		end
		local profile = type(self.GetProfile) == "function" and select(2, self:GetProfile()) or false
		if state == nil then
			state = not not self_db_raw_disabled[profile]
		elseif (not self_db_raw_disabled[profile]) == state then
			return
		end
		self_db_raw_disabled[profile] = not state or nil
		if next(self_db_raw_disabled) == nil then
			self_db_raw.disabled = del(self_db_raw_disabled)
		end
	else
		if state == nil then
			state = not not inactiveAddons[self]
		elseif (not inactiveAddons[self]) == state then
			return
		end
		inactiveAddons[self] = not state or nil
	end

	Rock:RecheckEnabledStates()

	return state
end

local function noop() end

do
	local preconditions = setmetatable({}, weakKey)
	local postconditions = setmetatable({}, weakKey)
	local postconditionsOld = setmetatable({}, weakKey)

	local currentMethod = nil

	local function hook(object, method)
		local object_method = object[method]
		object[method] = function(...)
			local pre = preconditions[object_method]
			local post = postconditions[object_method]
			if pre then
				local old_currentMethod = currentMethod
				currentMethod = method
				pre(...)
				currentMethod = old_currentMethod
			end
			if not post then
				return object_method(...)
			end
			local oldFunc = postconditionsOld[object_method]
			local old
			if oldFunc then
				old = newList()
				oldFunc(old, ...)
			end

			local old_currentMethod = currentMethod
			currentMethod = nil
			local ret, n = newList(object_method(...))

			currentMethod = method
			if old then
				post(old, ret, ...)
				old = del(old)
			else
				post(ret, ...)
			end
			currentMethod = old_currentMethod
			return unpackListAndDel(ret, 1, n)
		end
	end

	local function precondition(object, method, func)
		if type(object) ~= "table" then
			error(("Bad argument #1 to `precondition'. Expected %q, got %q."):format("table", type(object)), 2)
		end
		if type(object[method]) ~= "function" then
			error(("Method %q not found on object %s. Expected %q, got %q."):format(tostring(method), tostring(object), "function", type(object[method])), 2)
		end
		if type(func) ~= "function" then
			error(("Bad argument #3 to `precondition'. Expected %q, got %q."):format("function", type(func)), 2)
		end

		local object_method = object[method]
		if preconditions[object_method] then
			error("Cannot call `preconditon' on the same method twice.", 2)
		end
		preconditions[object_method] = func

		if not postconditions[object_method] then
			hook(object, method)
		end
	end

	local function postcondition(object, method, func, fillOld)
		if type(object) ~= "table" then
			error(("Bad argument #1 to `postcondition'. Expected %q, got %q."):format("table", type(object)), 2)
		end
		if type(object[method]) ~= "function" then
			error(("Method %q not found on object %s. Expected %q, got %q."):format(tostring(method), tostring(object), "function", type(object[method])), 2)
		end
		if type(func) ~= "function" then
			error(("Bad argument #3 to `postcondition'. Expected %q, got %q."):format("function", type(func)), 2)
		end
		if fillOld and type(fillOld) ~= "function" then
			error(("Bad argument #4 to `postcondition'. Expected %q or %q, got %q."):format("function", "nil", type(func)), 2)
		end

		local object_method = object[method]
		if postconditions[object_method] then
			error("Cannot call `postcondition' on the same method twice.", 2)
		end
		postconditions[object_method] = func
		postconditionsOld[object_method] = fillOld

		if not preconditions[object_method] then
			hook(object, method)
		end
	end

	local function argCheck(value, position, ...)
		if not currentMethod then
			error("Cannot call `argCheck' outside of a pre/post-condition.", 2)
		end
		if type(position) ~= "number" then
			error(("Bad argument #2 to `argCheck'. Expected %q, got %q"):format("number", type(position)), 2)
		end
		local type_value = type(value)
		for i = 1, select('#', ...) do
			local v = select(i, ...)
			if type(v) ~= "string" then
				error(("Bad argument #%d to `argCheck'. Expected %q, got %q"):format(i+1, "string", type(v)), 2)
			end
			if v == type_value then
				return
			end
		end
		local t = newList(...)
		t[#t] = nil
		for i,v in ipairs(t) do
			t[i] = ("%q"):format(v)
		end
		local s
		if #t == 0 then
			s = ("%q"):format((...))
		elseif #t == 1 then
			s = ("%q or %q"):format(...)
		else
			s = table_concat(t, ", ") .. ", or " .. ("%q"):format(select(#t+1, ...))
		end
		t = del(t)

		error(("Bad argument #%d to `%s'. Expected %s, got %q."):format(position, tostring(currentMethod), s, type_value), 4)
	end

	--[[---------------------------------------------------------------------------
	Notes:
		* Returns functions for the specified namespace based on what is provided.
		* function types:
		; "precondition" : to set the pre-condition for a method.
		; "postcondition" : to set the post-condition for a method.
		; "argCheck" : to check the type of an argument, to be executed within a pre-condition.
		* preconditon is in the form of <tt>precondition(object, "methodName", func(self, ...))</tt>
		* postcondition is in the form of either <tt>postcondition(object, "methodName", func(returnValues, self, ...))</tt> or <tt>postcondition(object, "methodName", func(oldValues, returnValues, self, ...), populateOld(oldValues, self, ...))</tt>
		** returnValues is the list of return values, empty if no return values were sent.
		** if the populateOld function is provided, then the empty oldValues table is provided and expected to be filled, and then given to the func.
		* argCheck is in the form of <tt>argCheck(value, n, "type1" [, "type2", ...])</tt>
		** value is the value provided to the function you're checking.
		** n is the argument position. ''Note: 1 is the position of `self'. 2 would be the first "real" position.''
		** the tuple of types can be any string, but specifically "nil", "boolean", "string", "number", "function", "userdata", "table", etc.
	Arguments:
		string - the namespace. ''Note: this doesn't necessarily have to be a string.''
	Example:
		local precondition, postcondition, argCheck = Rock:GetRecyclingFunctions("Stack", "precondition", "postcondition", "argCheck")

		local stack = {}
		stack.IsEmpty = function(self)
			return self[1] == nil
		end
		stack.GetLength = function(self)
			return #self
		end
		stack.Push = function(self, value)
			self[#self+1] = value
		end
		precondition(stack, "Push", function(self, value)
			argCheck(value, 2, "string") -- only accept strings, no other values
		end)
		postcondition(stack, "Push", function(old, ret, self, value)
			assert(self:GetLength() == old.length+1)
			assert(not self:IsEmpty())
		end, function(old, self)
			old.length = self:GetLength()
		end)
		stack.Pop = function(self)
			local value = self[#self]
			self[#self] = nil
			return value
		end
		precondition(stack, "Pop", function(self)
			assert(self:GetLength() >= 1)
		end)
		postcondition(stack, "Pop", function(old, ret, self)
			assert(self:GetLength() == old.length-1)
		end, function(old, self)
			old.length = self:GetLength()
		end)
		stack.Peek = function(self)
			return self[#self]
		end
		precondition(stack, "Peek", function(self)
			assert(self:GetLength() >= 1)
		end)
		postcondition(stack, "Peek", function(old, ret, self)
			assert(self:GetLength() == old.length)
		end, function(old, self)
			old.length = self:GetLength()
		end)

		local t = setmetatable({}, {__index=stack})
		t:Push("Alpha")
		t:Push("Bravo")
		t:Push(5) -- error, only strings
		assert(t:Pop() == "Bravo")
		assert(t:Pop() == "Alpha")
		t:Pop() -- error, out of values
	-----------------------------------------------------------------------------]]
	function Rock:GetContractFunctions(namespace, ...)
		if namespace == "precondition" or namespace == "postcondition" or namespace == "argCheck" then
			error(("Bad argument #2 to `GetContractFunctions'. Cannot be %q."):format(namespace), 2)
		end
		local t = newList()
		if enableContracts then
			for i = 1, select('#', ...) do
				local v = select(i, ...)
				if v == "precondition" then
					t[i] = precondition
				elseif v == "postcondition" then
					t[i] = postcondition
				elseif v == "argCheck" then
					t[i] = argCheck
				else
					error(("Bad argument #%d to `GetContractFunctions'. Expected %q, %q, or %q, got %q."):format(i+2, "precondition", "postcondition", "argCheck", tostring(v)))
				end
			end
		else
			for i = 1, select('#', ...) do
				t[i] = noop
			end
		end
		return unpackListAndDel(t)
	end
end

--[[---------------------------------------------------------------------------
Notes:
	* convert a revision string to a number
Arguments:
	string - revision string
Returns:
	string or number - the string given or the number retrieved from it.
-----------------------------------------------------------------------------]]
local function coerceRevisionToNumber(version)
	if type(version) == "string" then
		return tonumber(version:match("(%-?%d+)")) or version
	else
		return version
	end
end

--[[---------------------------------------------------------------------------
Notes:
	* try to enable the standalone library specified
Arguments:
	string - name of the library.
Returns:
	boolean - whether the library is properly enabled and loadable.
-----------------------------------------------------------------------------]]
local function TryToEnable(addon)
	local islod = IsAddOnLoadOnDemand(addon)
	if islod then
		local _, _, _, enabled = GetAddOnInfo(addon)
		EnableAddOn(addon)
		local _, _, _, _, loadable = GetAddOnInfo(addon)
		if not loadable and not enabled then
			DisableAddOn(addon)
		end

		return loadable
	end
end

--[[---------------------------------------------------------------------------
Notes:
	* try to load the standalone library specified
Arguments:
	string - name of the library.
Returns:
	boolean - whether the library is loaded.
-----------------------------------------------------------------------------]]
local function TryToLoadStandalone(major)
	major = major:lower()
	if scannedLibraries[major] then
		return
	end
	scannedLibraries[major] = true
	local name, _, _, enabled, loadable, state = GetAddOnInfo(major)
	if state == "MISSING" or not IsAddOnLoadOnDemand(major) then
		-- backwards compatibility for X-AceLibrary
		local field = "X-AceLibrary-" .. major
		local loaded
		for i = 1, GetNumAddOns() do
			if GetAddOnMetadata(i, field) then
				name, _, _, enabled, loadable = GetAddOnInfo(i)

				loadable = (enabled and loadable) or TryToEnable(name)
				if loadable then
					loaded = true
					LoadAddOn(name)
				end
			end
		end

		return loaded
	elseif (enabled and loadable) or TryToEnable(major) then
		LoadAddOn(major)
		return true
	else
		return false
	end
end

--[[---------------------------------------------------------------------------
Notes:
	* Return the LibStub library, casing is unimportant.
Arguments:
	string - name of the library.
Returns:
	table or nil - library
	number - minor version
-----------------------------------------------------------------------------]]
local function GetLibStubLibrary(major)
	local lib, minor = LibStub:GetLibrary(major, true)
	if lib then
		return lib, minor
	end
	major = major:lower()
	for m, lib in LibStub:IterateLibraries() do
		if m:lower() == major then
			return LibStub:GetLibrary(m)
		end
	end
	return nil, nil
end

local finishLibraryRegistration
--[[---------------------------------------------------------------------------
Notes:
	* create a new library if the version provided is not out of date.
Arguments:
	string - name of the library.
	number - version of the library.
Returns:
	library, oldLibrary
	* table or nil - the library with which to manipulate
	* table or nil - the old version of the library to upgrade from
Example:
	local LibMonkey, oldLib = Rock:NewLibrary("LibMonkey-1.0", 50)
	if not LibMonkey then
		-- opt out now, out of date
		return
	end
-----------------------------------------------------------------------------]]
function Rock:NewLibrary(major, version)
	if type(major) ~= "string" then
		error(("Bad argument #2 to `NewLibrary'. Expected %q, got %q."):format("string", type(major)), 2)
	end
	if not major:match("^Lib[A-Z][A-Za-z%d%-]*%-%d+%.%d+$") then
		error(("Bad argument #2 to `NewLibrary'. Must match %q, got %q."):format("^Lib[A-Z][A-Za-z%d%-]*%-%d+%.%d+$", major), 2)
	end
	TryToLoadStandalone(major)
	version = coerceRevisionToNumber(version)
	if type(version) ~= "number" then
		error(("Bad argument #3 to `NewLibrary'. Expected %q, got %q."):format("number", type(version)), 2)
	end
	local library, oldMinor = LibStub:GetLibrary(major, true)
	if oldMinor and oldMinor >= version then
		-- in case LibStub is acting funny
		return nil, nil
	end
	local library, oldMinor = LibStub:NewLibrary(major, version)
	if not library then
		return nil, nil
	end
	local unitTests_major = unitTests[major]
	if unitTests_major then
		for k,v in pairs(unitTests_major) do
			unitTests_major[k] = nil
		end
	end
	for k, v in pairs(recycleData) do
		v[major] = nil
	end
	local mixinToObject_library = mixinToObject[library]

	local oldLib
	if oldMinor then
		-- previous version exists
		local mixins = newList()
		for mixin, objectSet in pairs(mixinToObject) do
			if objectSet[library] then
				mixins[mixin] = true
			end
		end
		for mixin in pairs(mixins) do
			mixin:Unembed(library)
		end
		mixins = del(mixins)
		oldLib = newList()
		for k, v in pairs(library) do
			oldLib[k] = v
			library[k] = nil
		end
		setmetatable(oldLib, getmetatable(library))
		setmetatable(library, nil)
	end
	finishLibraryRegistration(major, version, library, figureCurrentAddon(2))

	return library, oldLib
end
function finishLibraryRegistration(major, version, library, folder)
	library.name = major

	libraries[major] = library
	pendingLibraries[library] = folder
	local exportedMethods_library = exportedMethods[library]
	if exportedMethods_library then
		local mixinToObject_library = mixinToObject[library]
		if mixinToObject_library then
			for object in pairs(mixinToObject_library) do
				for _,v in ipairs(exportedMethods_library) do
					object[v] = nil
				end
			end
		end
		exportedMethods[library] = del(exportedMethods_library)
	end
	if library ~= Rock then
		Rock:Embed(library)
	end

	frame:Show()
end
if not oldRock then
	finishLibraryRegistration(MAJOR_VERSION, MINOR_VERSION, Rock, figureCurrentAddon(1))
end

-- #NODOC
local function __removeLibrary(libName)
	libraries[libName] = nil
	if LibStub.libs then
		LibStub.libs[libName] = nil
	end
	if LibStub.minors then
		LibStub.minors[libName] = nil
	end
	local lastCount
	repeat
		lastCount = collectgarbage('count')
		collectgarbage('collect')
	until lastCount == collectgarbage('count')
end
local function run(_,a)
	if a < 1/30 then
		collectgarbage('step')
	end
end

--[[---------------------------------------------------------------------------
Notes:
	* properly finalizes the library, essentially stating that it has loaded properly.
	* This will call :OnLibraryLoad("major", library) on every other library
	* This will also call :OnLibraryLoad("major", library) on the library provided, using every other library as the arguments.
	* An error will occur if this is not done before ADDON_LOADED.
Arguments:
	string - name of the library.
Example:
	local LibMonkey, oldLib = Rock:NewLibrary("LibMonkey-1.0", 50)
	if not LibMonkey then
		-- opt out now, out of date
		return
	end
	Rock:FinalizeLibrary("LibMonkey-1.0")
-----------------------------------------------------------------------------]]
function Rock:FinalizeLibrary(major)
	if type(major) ~= "string" then
		error(("Bad argument #2 to `FinalizeLibrary'. Expected %q, got %q."):format("string", type(major)), 2)
	end
	local library = libraries[major]
	if not library then
		error(("Bad argument #2 to `FinalizeLibrary'. %q is not a library."):format("string", major), 2)
	end
	pendingLibraries[library] = nil
	local library_OnLibraryLoad = library.OnLibraryLoad
	if library_OnLibraryLoad then
		for maj, lib in LibStub:IterateLibraries() do -- for all libraries
			if maj ~= major then
				local success, ret = pcall(library_OnLibraryLoad, library, maj, lib)
				if not success then
					geterrorhandler()(ret)
					break
				end
			end
		end
	end
	if finalizedLibraries[library] then
		return
	end
	finalizedLibraries[library] = true
	for maj, lib in pairs(libraries) do -- just Rock libraries
		if maj ~= major then
			local lib_OnLibraryLoad = lib.OnLibraryLoad
			if lib_OnLibraryLoad then
				local success, ret = pcall(lib_OnLibraryLoad, lib, major, library)
				if not success then
					geterrorhandler()(ret)
				end
			end
		end
	end
	if LibRockEvent then
		self:DispatchEvent("LibraryLoad", major, library)
	end
end

local function manualFinalize(major, library)
	if libraries[major] then -- non-Rock libraries only
		return
	end
	if finalizedLibraries[library] then -- don't do it twice
		return
	end
	finalizedLibraries[library] = true
	for maj, lib in pairs(libraries) do -- just Rock libraries
		if maj ~= major then
			local lib_OnLibraryLoad = lib.OnLibraryLoad
			if lib_OnLibraryLoad then
				local success, ret = pcall(lib_OnLibraryLoad, lib, major, library)
				if not success then
					geterrorhandler()(ret)
				end
			end
		end
	end
	if LibRockEvent then
		Rock:DispatchEvent("LibraryLoad", major, library)
	end
end

--[[---------------------------------------------------------------------------
Arguments:
	string - name of the library.
	[optional] boolean - whether to not load a library if it is not found. Default: false
	[optional] boolean - whether to not error if a library is not found. Default: false
Returns:
	library
	* table or nil - the library requested
Example:
	local LibMonkey = Rock:GetLibrary("LibMonkey-1.0")
	-- or
	local LibMonkey = Rock("LibMonkey-1.0")
-----------------------------------------------------------------------------]]
function Rock:GetLibrary(major, dontLoad, dontError)
	if type(major) ~= "string" then
		error(("Bad argument #2 to `GetLibrary'. Expected %q, got %q."):format("string", type(major)), 2)
	end
	if dontLoad and dontLoad ~= true then
		error(("Bad argument #3 to `GetLibrary'. Expected %q or %q, got %q."):format("boolean", "nil", type(dontLoad)), 2)
	end
	if dontError and dontError ~= true then
		error(("Bad argument #4 to `GetLibrary'. Expected %q or %q, got %q."):format("boolean", "nil", type(dontError)), 2)
	end
	if not dontLoad then
		TryToLoadStandalone(major)
	end

	local library = GetLibStubLibrary(major)
	if not library then
		if dontError then
			return nil
		end
		error(("Library %q not found."):format(major), 2)
	end

	return library
end

setmetatable(Rock, { __call = Rock.GetLibrary })

--[[---------------------------------------------------------------------------
Arguments:
	string - name of the library.
Returns:
	boolean - whether the library exists and is a proper mixin which can be embedded.
Example:
	local isMixin = Rock:IsLibraryMixin("LibMonkey-1.0")
-----------------------------------------------------------------------------]]
function Rock:IsLibraryMixin(name)
	local library = self:GetLibrary(name, false, true)
	if not library then
		return false
	end
	return not not exportedMethods[library]
end

--[[---------------------------------------------------------------------------
Arguments:
	string - name of the library.
	[optional] boolean - whether to not load a library if it is not found. Default: false
Returns:
	library
	* table or nil - the library requested
Example:
	local hasLibMonkey = Rock:HasLibrary("LibMonkey-1.0")
-----------------------------------------------------------------------------]]
function Rock:HasLibrary(major, dontLoad)
	if type(major) ~= "string" then
		error(("Bad argument #2 to `HasLibrary'. Expected %q, got %q."):format("string", type(major)), 2)
	end
	if dontLoad and dontLoad ~= true then
		error(("Bad argument #3 to `HasLibrary'. Expected %q or %q, got %q."):format("boolean", "nil", type(dontLoad)), 2)
	end
	if not dontLoad then
		TryToLoadStandalone(major)
	end
	return not not GetLibStubLibrary(major)
end

--[[---------------------------------------------------------------------------
Notes:
	* This is exported to all libraries
Returns:
	major, minor
	* string - name of the library
	* number - version of the library
Example:
	local major, minor = Rock:GetLibraryVersion() -- will be "LibRock-1.0", 12345
	local major, minor = LibMonkey:GetLibraryVersion() -- will be "LibMonkey-1.0", 50
-----------------------------------------------------------------------------]]
function Rock:GetLibraryVersion()
	if type(self) ~= "table" then
		return nil, nil
	end
	local major
	local name = self.name
	if name and GetLibStubLibrary(name) == self then
		major = name
	else
		for m, instance in LibStub:IterateLibraries() do
			if instance == self then
				major = m
				break
			end
		end
		if not major then
			return nil, nil
		end
	end
	local _, minor = GetLibStubLibrary(major)
	return major, minor
end

--[[---------------------------------------------------------------------------
Returns:
	an iterator to traverse all registered libraries.
Example:
	for major, library in Rock:IterateLibraries() do
		-- do something with major and library
	end
-----------------------------------------------------------------------------]]
function Rock:IterateLibraries()
	return LibStub:IterateLibraries()
end

--[[---------------------------------------------------------------------------
Notes:
	* This is exported to all libraries
	* Allows you to set precisely what methods for the library to export.
	* This automatically turns a library into a mixin.
Arguments:
	tuple - the list of method names to export.
Example:
	local LibMonkey = Rock:NewLibrary("LibMonkey-1.0", 50)
	LibMonkey.FlingPoo = function(self)
		return "Splat!"
	end
	LibMonkey:SetExportedMethods("FlingPoo")
	-- later
	local Darwin = Rock:NewAddon("Darwin", "LibMonkey-1.0")
	assert(Darwin:FlingPoo() == "Splat!")
-----------------------------------------------------------------------------]]
function Rock:SetExportedMethods(...)
	if exportedMethods[self] then
		error("Cannot call `SetExportedMethods' more than once.", 2)
	end
	local t = newList(...)
	if #t == 0 then
		error("Must supply at least 1 method to `SetExportedMethods'.", 2)
	end
	for i,v in ipairs(t) do
		if type(self[v]) ~= "function" then
			error(("Bad argument #%d to `SetExportedMethods'. Method %q does not exist."):format(i+1, tostring(v)), 2)
		end
	end
	exportedMethods[self] = t

	local mixinToObject_library = mixinToObject[self]
	if mixinToObject_library then
		for object in pairs(mixinToObject_library) do
			for _,method in ipairs(t) do
				object[method] = self[method]
			end
		end
	end
end

--[[---------------------------------------------------------------------------
Notes:
	* This is exported to all libraries
	* Embeds all the methods previously set to export onto a table.
	* This will call :OnEmbed(object) on the library if it is available.
Arguments:
	table - the table with which to export methods onto.
Returns:
	The table provided, after embedding.
Example:
	local LibMonkey = Rock:NewLibrary("LibMonkey-1.0", 50)
	LibMonkey.FlingPoo = function(self)
		return "Splat!"
	end
	LibMonkey:SetExportedMethods("FlingPoo")
	-- later
	local Darwin = {}
	Rock("LibMonkey-1.0"):Embed(Darwin)
	assert(Darwin:FlingPoo() == "Splat!")
-----------------------------------------------------------------------------]]
function Rock:Embed(object)
	if not exportedMethods[self]  then
		error(("Cannot call `Embed' for library %q if `SetExportedMethods' has not been called."):format(tostring(self.name)), 2)
	end
	if type(object) ~= "table" then
		error(("Bad argument #2 to `Embed'. Expected %q, got %q."):format("table", type(object)), 2)
	end

	for i,v in ipairs(exportedMethods[self]) do
		if type(self[v]) ~= "function" then
			error(("Problem embedding method %q from library %q. Expected %q, got %q."):format(tostring(v), better_tostring(self), "function", type(self[v])))
		end
		object[v] = self[v]
	end

	if not mixinToObject[self] then
		-- weak because objects come and go
		mixinToObject[self] = setmetatable(newList(), weakKey)
	end
	if mixinToObject[self][object] then
		error(("Cannot embed library %q into the same object %q more than once."):format(better_tostring(self), better_tostring(object)), 2)
	end
	mixinToObject[self][object] = true
	if type(rawget(object, 'mixins')) == "table" then
		object.mixins[self] = true
	end

	local self_OnEmbed = self.OnEmbed
	if self_OnEmbed then
		local success, ret = pcall(self_OnEmbed, self, object)
		if not success then
			geterrorhandler()(ret)
		end
	end

	return object
end

--[[---------------------------------------------------------------------------
Notes:
	* This is exported to all libraries
	* Unembeds all the methods previously set to export onto a table.
	* This will error if the library is not embedded on the object
	* This will call :OnUnembed(object) on the library if it is available.
Arguments:
	table - the table with which to export methods onto.
Returns:
	The table provided, after embedding.
Example:
	local LibMonkey = Rock:NewLibrary("LibMonkey-1.0", 50)
	LibMonkey.FlingPoo = function(self)
		return "Splat!"
	end
	LibMonkey:SetExportedMethods("FlingPoo")
	-- later
	local Darwin = {}
	Rock("LibMonkey-1.0"):Embed(Darwin)
	assert(Darwin:FlingPoo() == "Splat!")
	Rock("LibMonkey-1.0"):Unembed(Darwin)
	assert(Darwin.FlingPoo == nil)
-----------------------------------------------------------------------------]]
function Rock:Unembed(object)
	if not exportedMethods[self]  then
		error(("Cannot call `Unembed' for library %q if `SetExportedMethods' has not been called."):format(better_tostring(self)), 2)
	end

	if not mixinToObject[self] or not mixinToObject[self][object] then
		error(("Cannot unembed library %q from object %q, since it is not embedded originally."):format(better_tostring(self), better_tostring(object)), 2)
	end
	local mixinToObject_self = mixinToObject[self]
	mixinToObject_self[object] = nil
	if not next(mixinToObject_self) then
		mixinToObject[self] = del(mixinToObject_self)
	end

	local mixin_OnUnembed = self.OnUnembed
	if mixin_OnUnembed then
		local success, ret = pcall(mixin_OnUnembed, self, object)
		if not success then
			geterrorhandler()(ret)
		end
	end

	for i,v in ipairs(exportedMethods[self]) do
		object[v] = nil
	end
end

local function embedAce2Mixin(mixin, object)
	if not mixinToObject[mixin] then
		mixinToObject[mixin] = setmetatable(newList(), weakKey)
	end
	mixinToObject[mixin][object] = true
	mixin:embed(object)
end

local function embedLibStubMixin(mixin, object)
	if not mixinToObject[mixin] then
		mixinToObject[mixin] = setmetatable(newList(), weakKey)
	end
	mixinToObject[mixin][object] = true
	mixin:Embed(object)
end

--[[---------------------------------------------------------------------------
Notes:
	* create a new addon with the specified name.
Arguments:
	string - name of the addon.
	tuple - list of mixins with which to embed into this addon.
Returns:
	addon
	* table - the addon with which to manipulate
Example:
	local MyAddon = Rock:NewAddon("MyAddon", "Mixin-1.0", "OtherMixin-2.0")
-----------------------------------------------------------------------------]]
function Rock:NewAddon(name, ...)
	if type(name) ~= "string" then
		error(("Bad argument #2 to `NewAddon'. Expected %q, got %q"):format("string", type(name)), 2)
	end
	if name:match("^Lib[A-Z]") then
		error(("Bad argument #2 to `NewAddon'. Cannot start with %q, got %q."):format("Lib", name), 2)
	end
	if self == Rock and name:match("_") then
		error(("Bad argument #2 to `NewAddon'. Cannot contain underscores, got %q."):format(name), 2)
	end

	if addons[name] then
		error(("Bad argument #2 to `NewAddon'. Addon %q already created."):format(name), 2)
	end
	local addon = setmetatable(newList(), addon_mt)
	addon.name = name

	local mixinSet = newList()

	for i = 1, select('#', ...) do
		local libName = select(i, ...)
		if mixinSet[libName] then
			error(("Bad argument #%d to `NewAddon'. %q already stated."):format(i+2, tostring(libName)), 2)
		end
		mixinSet[libName] = true
		TryToLoadStandalone(libName)
		local library = Rock:GetLibrary(libName, false, true)
		if not library then
			error(("Bad argument #%d to `NewAddon'. Library %q is not found."):format(i+2, tostring(libName)), 2)
		end
		
		local style = 'rock'

		if not exportedMethods[library] then
			local good = false
			if AceLibrary then
				local AceOO = AceLibrary:HasInstance("AceOO-2.0", false) and AceLibrary("AceOO-2.0")
				if AceOO.inherits(library, AceOO.Mixin) then
					good = true
					style = 'ace2'
				end
			end
			if not good and type(rawget(library, 'Embed')) == "function" then
				good = true
				style = 'libstub'
			end
			if not good then
				error(("Bad argument #%d to `NewAddon'. Library %q is not a mixin."):format(i+2, tostring(libName)), 2)
			end
		end

		if library == Rock then
			error(("Bad argument #%d to `NewAddon'. Cannot use %q as a mixin."):format(i+2, tostring(libName)), 2)
		end

		if style == 'rock' then
			library:Embed(addon)
		elseif style == 'ace2' then
			embedAce2Mixin(library, addon)
		elseif style == 'libstub' then
			embedLibStubMixin(library, addon)
		end
	end

	mixinSet = del(mixinSet)

	addons[name] = addon
	pendingAddons[#pendingAddons+1] = addon
	pendingAddonsEnable[#pendingAddonsEnable+1] = addon
	addonToFolder[addon] = figureCurrentAddon(self == Rock and 2 or 4)

	frame:Show()

	return addon
end

--[[---------------------------------------------------------------------------
Arguments:
	string - name of the addon.
Returns:
	addon
	* table or nil - the addon requested
Example:
	local MyAddon = Rock:GetAddon("MyAddon")
-----------------------------------------------------------------------------]]
function Rock:GetAddon(name)
	if type(name) ~= "string" then
		return nil
	end
	local addon = addons[name]
	if addon then
		return addon
	end
	name = name:lower()
	for k, v in pairs(addons) do
		if k:lower() == name then
			return v
		end
	end
	return nil
end

--[[---------------------------------------------------------------------------
Arguments:
	string or table - name of the addon or the addon itself.
Returns:
	boolean - whether the addon requested exists.
Example:
	local hasMyAddon = Rock:HasAddon("MyAddon")
	-- or
	local hasMyAddon = Rock:HasAddon(MyAddon)
-----------------------------------------------------------------------------]]
function Rock:HasAddon(name)
	if type(name) == "string" then
		local addon = addons[name]
		if addon then
			return true
		end
		name = name:lower()
		for k, v in pairs(addons) do
			if k:lower() == name then
				return true
			end
		end
	elseif type(name) == "table" then
		for k,v in pairs(addons) do
			if v == name then
				return true
			end
		end
	end
	return false
end

--[[---------------------------------------------------------------------------
Returns:
	an iterator to traverse all addons created with Rock.
Example:
	for name, addon in Rock:IterateAddons() do
		-- do something with name and addon
	end
-----------------------------------------------------------------------------]]
function Rock:IterateAddons()
	return pairs(addons)
end

--[[---------------------------------------------------------------------------
Arguments:
	string - major version of the mixin library
Returns:
	an iterator to traverse all objects that the given mixin has embedded into
Example:
	local LibMonkey = Rock:NewLibrary("LibMonkey-1.0")
	local Darwin = Rock:NewAddon("Darwin", "LibMonkey-1.0")
	for object in LibMonkey:IterateMixinObjects("LibMonkey-1.0") do
		assert(object == Darwin)
	end
-----------------------------------------------------------------------------]]
function Rock:IterateMixinObjects(mixinName)
	local mixin
	if type(mixinName) == "table" then
		mixin = mixinName
	else
		if type(mixinName) ~= "string" then
			error(("Bad argument #2 to `IterateMixinObjects'. Expected %q or %q, got %q."):format("table", "string", type(mixinName)), 2)
		end
		mixin = libraries[mixinName]
	end
	local mixinToObject_mixin = mixinToObject[mixin]
	if not mixinToObject_mixin then
		return noop
	end
	return pairs(mixinToObject_mixin)
end

local function iter(object, mixin)
	mixin = next(mixinToObject, mixin)
	if not mixin then
		return nil
	elseif mixinToObject[mixin][object] then
		return mixin
	end
	return iter(object, mixin) -- try next mixin
end
--[[---------------------------------------------------------------------------
Returns:
	an iterator to traverse all mixins that an object has embedded
Example:
	local LibMonkey = Rock:NewLibrary("LibMonkey-1.0")
	local Darwin = Rock:NewAddon("Darwin", "LibMonkey-1.0")
	for mixin in Rock:IterateObjectMixins(Darwin) do
		assert(mixin == LibMonkey)
	end
-----------------------------------------------------------------------------]]
function Rock:IterateObjectMixins(object)
	if type(object) ~= "table" then
		error(("Bad argument #2 to `IterateObjectMixins'. Expected %q, got %q."):format("table", type(object)), 2)
	end
	return iter, object, nil
end

--[[---------------------------------------------------------------------------
Arguments:
	table - the object to check
	string - the mixin to check
Returns:
	boolean - whether the object has the given mixin embedded into it.
Example:
	local LibMonkey = Rock:NewLibrary("LibMonkey-1.0")
	local Darwin = Rock:NewAddon("Darwin", "LibMonkey-1.0")
	assert(Rock:DoesObjectUseMixin(Darwin, "LibMonkey-1.0"))
-----------------------------------------------------------------------------]]
function Rock:DoesObjectUseMixin(object, mixinName)
	if type(object) ~= "table" then
		error(("Bad argument #2 to `IterateObjectMixins'. Expected %q, got %q."):format("table", type(object)), 2)
	end
	local mixin
	if type(mixinName) == "table" then
		mixin = mixinName
	else
		if type(mixinName) ~= "string" then
			error(("Bad argument #3 to `IterateMiDoesObjectUseMixininObjects'. Expected %q or %q, got %q."):format("table", "string", type(mixinName)), 2)
		end
		mixin = libraries[mixinName]
	end
	if not mixin then
		return false
	end

	local mixinToObject_mixin = mixinToObject[mixin]
	if not mixinToObject_mixin then
		return false
	end
	return not not mixinToObject_mixin[object]
end

Rock.UID_NUM = oldRock and oldRock.UID_NUM or 0
--[[---------------------------------------------------------------------------
Notes:
	* This UID is not unique across sessions. If you save a UID in a saved variable, the same UID can be generated in another session.
Returns:
	number - a unique number.
Example:
	local UID = Rock:GetUID()
-----------------------------------------------------------------------------]]
function Rock:GetUID()
	local num = Rock.UID_NUM + 1
	Rock.UID_NUM = num
	return num
end

local function unobfuscateEmail(email)
	return email:gsub(" AT ", "@"):gsub(" DOT ", ".")
end
local function fix(char)
	return ("%%%02x"):format(char:byte())
end
local function urlencode(text)
	return text:gsub("[^0-9A-Za-z]", fix)
end

local url
local function makeURLFrame()
	makeURLFrame = nil
	local function bumpFrameLevels(frame, amount)
		frame:SetFrameLevel(frame:GetFrameLevel()+amount)
		local children = newList(frame:GetChildren())
		for _,v in ipairs(children) do
			bumpFrameLevels(v, amount)
		end
		children = del(children)
	end
	-- some code borrowed from Prat here
	StaticPopupDialogs["ROCK_SHOW_URL"] = {
		text = not IsMacClient() and L["Press Ctrl-C to copy, then Alt-Tab out of the game, open your favorite web browser, and paste the link into the address bar."] or L["Press Cmd-C to copy, then Cmd-Tab out of the game, open your favorite web browser, and paste the link into the address bar."],
		button2 = ACCEPT,
		hasEditBox = 1,
		hasWideEditBox = 1,
		showAlert = 1, -- HACK : it's the only way I found to make de StaticPopup have sufficient width to show WideEditBox :(

		OnShow = function()
			local editBox = _G[this:GetName() .. "WideEditBox"]
			editBox:SetText(url)
			editBox:SetFocus()
			editBox:HighlightText(0)
			editBox:SetScript("OnTextChanged", function() StaticPopup_EditBoxOnTextChanged() end)

			local button = _G[this:GetName() .. "Button2"]
			button:ClearAllPoints()
			button:SetWidth(200)
			button:SetPoint("CENTER", editBox, "CENTER", 0, -30)

			_G[this:GetName() .. "AlertIcon"]:Hide()  -- HACK : we hide the false AlertIcon
			this:SetFrameStrata("FULLSCREEN_DIALOG")
			bumpFrameLevels(this, 30)
		end,
		OnHide = function()
			local editBox = _G[this:GetName() .. "WideEditBox"]
			editBox:SetScript("OnTextChanged", nil)
			this:SetFrameStrata("DIALOG")
			bumpFrameLevels(this, -30)
		end,
		OnAccept = function() end,
		OnCancel = function() end,
		EditBoxOnEscapePressed = function() this:GetParent():Hide() end,
		EditBoxOnTextChanged = function()
			this:SetText(url)
			this:SetFocus()
			this:HighlightText(0)
		end,
		timeout = 0,
		whileDead = 1,
		hideOnEscape = 1
	}
end

function OpenDonationFrame(self)
	if makeURLFrame then
		makeURLFrame()
	end

	local donate = self.donate
	if type(donate) ~= "string" then
		donate = "Wowace"
	end
	local style, data = (":"):split(donate, 2)
	style = style:lower()
	if style ~= "website" and style ~= "paypal" then
		style = "wowace"
	end
	if style == "wowace" then
		url = "http://www.wowace.com/wiki/Donations"
	elseif style == "website" then
		url = data
	else -- PayPal
		local text = "https://www.paypal.com/cgi-bin/webscr?cmd=_xclick&business=" .. urlencode(unobfuscateEmail(data))
		local name
		if type(self.title) == "string" then
			name = self.title
		elseif type(self.name) == "string" then
			name = self.name
		end
		if name == MAJOR_VERSION then
			name = "Rock"
		end
		if name then
			name = name:gsub("|c%x%x%x%x%x%x%x%x", ""):gsub("|r", "")
			text = text .. "&item_name=" .. urlencode(name)
		end
		url = text
	end

	StaticPopup_Show("ROCK_SHOW_URL")
end
function OpenIssueFrame(self)
	if makeURLFrame then
		makeURLFrame()
	end

	local issueTracker = self.issueTracker
	if type(issueTracker) ~= "string" then
		return
	end
	local style, data = (":"):split(issueTracker, 2)
	style = style:lower()
	if style ~= "website" and style ~= "wowace" then
		return
	end
	if style == "wowace" then
		url = "http://jira.wowace.com/secure/CreateIssue.jspa?pid=" .. data
	elseif style == "website" then
		url = data
	end

	StaticPopup_Show("ROCK_SHOW_URL")
end
local function donate_hidden(addon)
	return type(addon.donate) ~= "string"
end

local function issue_hidden(addon)
	return type(addon.issueTracker) ~= "string"
end

-- #NODOC
function Rock:GetRockConfigOptions(addon)
	return 'active', {
		type = 'boolean',
		name = L["Enabled"],
		desc = L["Enable or disable this addon."],
		get = 'IsActive',
		set = 'ToggleActive',
		handler = addon,
		order = -1,
	}, 'donate', {
		type = 'execute',
		name = L["Give donation"],
		buttonText = L["Donate"],
		desc = L["Give a much-needed donation to the author of this addon."],
		func = OpenDonationFrame,
		hidden = donate_hidden,
		passValue = addon,
		order = -2,
	}, 'issue', {
		type = 'execute',
		name = L["File issue"],
		buttonText = L["Report"],
		desc = L["File a bug or request a new feature or an improvement to this addon."],
		func = OpenIssueFrame,
		hidden = issue_hidden,
		passValue = addon,
		order = -3,
	}
end

local function initAddon(addon, name)
	name = addonToFolder[addon] or name or ""
	-- TOC checks
	if addon.title == nil then
		addon.title = GetAddOnMetadata(name, "Title")
	end
	if type(addon.title) == "string" then
		addon.title = addon.title:gsub("|c%x%x%x%x%x%x%x%x", ""):gsub("|r", ""):gsub("%-Rock%-$", ""):trim()
	end
	if addon.notes == nil then
		addon.notes = GetAddOnMetadata(name, "Notes")
	end
	if type(addon.notes) == "string" then
		addon.notes = addon.notes:trim()
	end
	if addon.version == nil then
		addon.version = GetAddOnMetadata(name, "Version")
	end
	if type(addon.version) == "string" then
		addon.version = addon.version:trim()
	end
	if addon.author == nil then
		addon.author = GetAddOnMetadata(name, "Author")
	end
	if type(addon.author) == "string" then
		addon.author = addon.author:trim()
	end
	if addon.credits == nil then
		addon.credits = GetAddOnMetadata(name, "X-Credits")
	end
	if type(addon.credits) == "string" then
		addon.credits = addon.credits:trim()
	end
	if addon.donate == nil then
		addon.donate = GetAddOnMetadata(name, "X-Donate")
	end
	if type(addon.donate) == "string" then
		addon.donate = addon.donate:trim()
	end
	if addon.issueTracker == nil then
		addon.issueTracker = GetAddOnMetadata(name, "X-IssueTracker")
	end
	if type(addon.issueTracker) == "string" then
		addon.issueTracker = addon.issueTracker:trim()
	end
	if addon.category == nil then
		addon.category = GetAddOnMetadata(name, "X-Category")
	end
	if type(addon.category) == "string" then
		addon.category = addon.category:trim()
	end
	if addon.email == nil then
		addon.email = GetAddOnMetadata(name, "X-eMail") or GetAddOnMetadata(name, "X-Email")
	end
	if type(addon.email) == "string" then
		addon.email = addon.email:trim()
	end
	if addon.license == nil then
		addon.license = GetAddOnMetadata(name, "X-License")
	end
	if type(addon.license) == "string" then
		addon.license = addon.license:trim()
	end
	if addon.website == nil then
		addon.website = GetAddOnMetadata(name, "X-Website")
	end
	if type(addon.website) == "string" then
		addon.website = addon.website:trim()
	end

	for mixin in Rock:IterateObjectMixins(addon) do
		local mixin_OnEmbedInitialize = mixin.OnEmbedInitialize
		if mixin_OnEmbedInitialize then
			local success, ret = pcall(mixin_OnEmbedInitialize, mixin, addon)
			if not success then
				geterrorhandler()(ret)
			end
		end
	end

	local addon_OnInitialize = addon.OnInitialize
	if addon_OnInitialize then
		local success, ret = pcall(addon_OnInitialize, addon)
		if not success then
			geterrorhandler()(ret)
		end
	end

	if LibRockEvent then
		Rock:DispatchEvent("AddonInitialized", addon)
	end
end


local function manualEnable(addon)
	for i,v in ipairs(pendingAddons) do
		if v == addon then
			return
		end
	end
	if currentlyEnabledAddons[addon] then
		return false
	end
	currentlyEnabledAddons[addon] = true

	local first = not addonsAlreadyEnabled[addon]
	addonsAlreadyEnabled[addon] = true

	for mixin in Rock:IterateObjectMixins(addon) do
		local mixin_OnEmbedEnable = mixin.OnEmbedEnable
		if mixin_OnEmbedEnable then
			local success, ret = pcall(mixin_OnEmbedEnable, mixin, addon, first)
			if not success then
				geterrorhandler()(ret)
			end
		end
	end
	local addon_OnEnable = addon.OnEnable
	if addon_OnEnable then
		local success, ret = pcall(addon_OnEnable, addon, first)
		if not success then
			geterrorhandler()(ret)
		end
	end

	if LibRockEvent then
		Rock:DispatchEvent("AddonEnabled", addon, first)
	end

	return true, first
end

local function manualDisable(addon)
	if not currentlyEnabledAddons[addon] then
		return false
	end
	currentlyEnabledAddons[addon] = nil

	for mixin in Rock:IterateObjectMixins(addon) do
		local mixin_OnEmbedDisable = mixin.OnEmbedDisable
		if mixin_OnEmbedDisable then
			local success, ret = pcall(mixin_OnEmbedDisable, mixin, addon)
			if not success then
				geterrorhandler()(ret)
			end
		end
	end
	local addon_OnDisable = addon.OnDisable
	if addon_OnDisable then
		local success, ret = pcall(addon_OnDisable, addon)
		if not success then
			geterrorhandler()(ret)
		end
	end

	if LibRockEvent then
		Rock:DispatchEvent("AddonDisabled", addon)
	end
	return true
end

local function enableAddon(addon)
	for i,v in ipairs(pendingAddons) do
		if v == addon then
			return
		end
	end
	if addon_mt___index.IsActive(addon) then
		manualEnable(addon)
	end
end

-- #NODOC
-- This is used by internal Rock libraries after updating the active state.
function Rock:RecheckEnabledStates()
	local changed = false
	for _,addon in pairs(addons) do
		local good = true
		for _,a in ipairs(pendingAddonsEnable) do
			if addon == a then
				good = false
				break
			end
		end
		if good then
			if addon_mt___index.IsActive(addon) then
				if manualEnable(addon) then
					changed = true
				end
			else
				if manualDisable(addon) then
					changed = true
				end
			end
		end
	end
	if changed then
		return self:RecheckEnabledStates()
	end
end

frame:UnregisterAllEvents()
frame:RegisterEvent("ADDON_LOADED")
frame:RegisterEvent("PLAYER_LOGIN")
local function runMainAddonLoadedChunk(name)
	local tmp = newList()
	tmp, pendingAddons = pendingAddons, tmp
	for i, addon in ipairs(tmp) do
		local folder = addonToFolder[addon]
		if name and folder and not foldersLoaded[folder] then
			for j = i, #tmp do
				pendingAddons[#pendingAddons+1] = tmp[j]
				tmp[j] = nil
			end
			break
		end
		initAddon(addon, name)
	end

	if IsLoggedIn() then
		for i, addon in ipairs(tmp) do
			for j, v in ipairs(pendingAddonsEnable) do
				if v == addon then
					table_remove(pendingAddonsEnable, i)
					break
				end
			end
			enableAddon(addon)
		end
		for i, addon in ipairs(pendingAddonsEnable) do
			local good = true
			for j, v in ipairs(pendingAddons) do
				if v == addon then
					good = false
					break
				end
			end
			if not good then
				break
			end
			pendingAddonsEnable[i] = nil
			enableAddon(addon)
		end
	end
	tmp = del(tmp)
	for library, addonName in pairs(pendingLibraries) do
		if not name or foldersLoaded[addonName] then
			local success, ret = pcall(error, ("Library %q not finalized before ADDON_LOADED."):format(better_tostring(library)), 3)
			geterrorhandler()(ret)
			Rock:FinalizeLibrary((library:GetLibraryVersion()))
		end
	end

	if isStandalone then
		local LibRock_1_0DB = _G.LibRock_1_0DB
		if type(LibRock_1_0DB) ~= "table" then
			LibRock_1_0DB = {}
			_G.LibRock_1_0DB = LibRock_1_0DB
		end
		if type(LibRock_1_0DB.unitTests) ~= "table" then
			LibRock_1_0DB.unitTests = {}
		end
		enableContracts = LibRock_1_0DB.contracts or false
		unitTestDB = LibRock_1_0DB.unitTests
		for namespace, data in pairs(unitTests) do
			if not unitTestDB[namespace] then
				if data then
					del(data)
					unitTests[namespace] = false
				end
			elseif data and (not name or data.addon == name) then
				local stats = newList()
				for i,v in ipairs(data) do
					data[i] = nil

					local libs = newList()
					for k,v in pairs(libraries) do
						libs[k] = v
					end

					local success, ret = pcall(v)
					if not success then
						geterrorhandler()(ret)
						stats[i] = ret
					else
						stats[i] = false
					end

					for k in pairs(libraries) do
						if not libs[k] then
							__removeLibrary(k)
						end
					end
					libs = del(libs)

					local lastCount
					repeat
						lastCount = collectgarbage('count')
						collectgarbage('collect')
					until lastCount == collectgarbage('count')
				end
				del(data)
				unitTests[namespace] = false
				if #stats >= 1 then
					local pass, fail = 0, 0
					for i,v in ipairs(stats) do
						if v then
							fail = fail + 1
						else
							pass = pass + 1
						end
					end

					local color
					if fail == 0 then
						_G.DEFAULT_CHAT_FRAME:AddMessage(("|cff00ff00%s: %d unit test(s) passed."):format(namespace, pass))
					elseif pass > 0 then
						_G.DEFAULT_CHAT_FRAME:AddMessage(("|cffff0000%s: %d unit test(s) passed, %d unit test(s) failed."):format(namespace, pass, fail))
					else
						_G.DEFAULT_CHAT_FRAME:AddMessage(("|cffff0000%s: %d unit test(s) failed."):format(namespace, fail))
					end
					for i,v in ipairs(stats) do
						if v then
							_G.DEFAULT_CHAT_FRAME:AddMessage(("|cffff0000%s|r"):format(tostring(v)))
						end
					end
					if fail > 0 then
						_G.DEFAULT_CHAT_FRAME:AddMessage("|cffff0000----------|r")
					end
				end
				stats = del(stats)
			end
		end
	end
	if isStandalone and name == MAJOR_VERSION then
		Rock("LibRockEvent-1.0", false, true) -- load if possible
		Rock("LibRockConsole-1.0", false, true) -- load if possible - I like the default chat commands
		Rock("LibRockComm-1.0", false, true) -- load if possible - has version checking and the like
		Rock("LibRockConfig-1.0", false, true) -- load if possible - LibRock-1.0 registers with it.
	end

	for major, library in LibStub:IterateLibraries() do
		manualFinalize(major, library)
	end

	if IsLoggedIn() then
		collectgarbage('collect')
	end
end

frame:Show()
frame:SetScript("OnUpdate", function(this, elapsed)
	-- capture all un-initialized addons.
	runMainAddonLoadedChunk()
	collectgarbage('collect')
	this:SetScript("OnUpdate", run)
end)
frame:SetScript("OnEvent", function(this, event, ...)
	if event == "ADDON_LOADED" then
		-- this creates a new table and flushes the old in case someone LoDs an addon inside ADDON_LOADED.
		local name = ...
		foldersLoaded[name] = true
		runMainAddonLoadedChunk(name)
		frame:Show()
	elseif event == "PLAYER_LOGIN" then
		for i, addon in ipairs(pendingAddonsEnable) do
			local good = true
			for _, a in ipairs(pendingAddons) do
				if a == addon then
					good = false
					break
				end
			end
			if good then
				pendingAddonsEnable[i] = nil
				enableAddon(addon)
			end
		end
		collectgarbage('collect')
	end
end)

Rock:SetExportedMethods("SetExportedMethods", "Embed", "Unembed", "GetLibraryVersion")

Rock:FinalizeLibrary(MAJOR_VERSION)

for major, library in LibStub:IterateLibraries() do
	manualFinalize(major, library)
end

Rock:AddUnitTest(MAJOR_VERSION, function()
	-- test recycling
	local newList, newDict, newSet, del = Rock:GetRecyclingFunctions(MAJOR_VERSION .. "_UnitTest", "newList", "newDict", "newSet", "del", "Debug")
	local t = newList("Alpha", "Bravo", "Charlie")
	assert(t[1] == "Alpha")
	assert(t[2] == "Bravo")
	assert(t[3] == "Charlie")
	t = del(t)
	t = newList("Alpha", "Bravo", "Charlie")
	-- check recycled table
	assert(t[1] == "Alpha")
	assert(t[2] == "Bravo")
	assert(t[3] == "Charlie")
	t = del(t)
	t = newDict("Alpha", "Bravo", "Charlie", "Delta")
	assert(t.Alpha == "Bravo")
	assert(t.Charlie == "Delta")
	t = del(t)
	t = newSet("Alpha", "Bravo", "Charlie")
	assert(t.Alpha)
	assert(t.Bravo)
	assert(t.Charlie)
	t = del(t)

	local debug = recycleData.debugPools[MAJOR_VERSION .. "_UnitTest"]
	assert(debug.num == 0)
	t = newList()
	assert(debug.num == 1)
	t[1] = newList()
	assert(debug.num == 2)
	t[2] = newList()
	assert(debug.num == 3)
	t[1] = del(t[1])
	assert(debug.num == 2)
	t[2] = del(t[2])
	assert(debug.num == 1)
	t = del(t)
	assert(debug.num == 0)
end)

Rock:AddUnitTest(MAJOR_VERSION, function()
	-- test :GetUID()
	local t = {}
	for i = 1, 10000 do
		local uid = Rock:GetUID()
		if t[i] then
			error(("UID match for iteration %d, UID %s"):format(i, uid))
		end
		t[i] = true
	end
end)

Rock:AddUnitTest(MAJOR_VERSION, function()
	-- test basic creation and deletion
	assert(not LibStub:GetLibrary("LibRockFakeLib-1.0", true))
	assert(not Rock:HasLibrary("LibRockFakeLib-1.0"))
	local lib = Rock:NewLibrary("LibRockFakeLib-1.0", 1)
	Rock:FinalizeLibrary("LibRockFakeLib-1.0")
	lib = nil
	assert(LibStub:GetLibrary("LibRockFakeLib-1.0", true))
	assert(Rock:HasLibrary("LibRockFakeLib-1.0"))
	local good = false
	for _, lib in pairs(libraries) do
		if lib.name == "LibRockFakeLib-1.0" then
			good = true
			break
		end
	end
	assert(good)
	__removeLibrary("LibRockFakeLib-1.0")
	for _, lib in pairs(libraries) do
		assert(lib.name ~= "LibRockFakeLib-1.0")
	end
	assert(not LibStub:GetLibrary("LibRockFakeLib-1.0", true))
	assert(not Rock:HasLibrary("LibRockFakeLib-1.0"))
end)

Rock:AddUnitTest(MAJOR_VERSION, function()
	-- test library creation and the like
	assert(not Rock:HasLibrary("LibRockFakeLib-1.0"))
	for name in Rock:IterateLibraries() do
		assert(name ~= "LibRockFakeLib-1.0")
	end

	local myLib, oldLib = Rock:NewLibrary("LibRockFakeLib-1.0", 1)
	assert(myLib)
	assert(myLib.name == "LibRockFakeLib-1.0")
	assert(not oldLib)

	assert(myLib:GetLibraryVersion() == "LibRockFakeLib-1.0")
	assert(select(2, myLib:GetLibraryVersion()) == 1)

	local good = false
	for name in Rock:IterateLibraries() do
		if name == "LibRockFakeLib-1.0" then
			good = true
			break
		end
	end
	assert(good)
	assert(Rock:HasLibrary("LibRockFakeLib-1.0"))
	assert(Rock:GetLibrary("LibRockFakeLib-1.0") == myLib)
	assert(Rock("LibRockFakeLib-1.0") == myLib)

	assert(not Rock:IsLibraryMixin("LibRockFakeLib-1.0"))
	function myLib:DoSomething()
		return "Something"
	end
	myLib:SetExportedMethods("DoSomething")
	assert(Rock:IsLibraryMixin("LibRockFakeLib-1.0"))
	local t = {}
	assert(not Rock:DoesObjectUseMixin(t, "LibRockFakeLib-1.0"))
	assert(not t.DoSomething)
	for mixin in Rock:IterateObjectMixins(t) do
		assert(false)
	end
	for object in Rock:IterateMixinObjects("LibRockFakeLib-1.0") do
		assert(false)
	end
	myLib:Embed(t)
	assert(t:DoSomething() == "Something")
	assert(Rock:DoesObjectUseMixin(t, "LibRockFakeLib-1.0"))
	for mixin in Rock:IterateObjectMixins(t) do
		assert(mixin == myLib)
	end
	for object in Rock:IterateMixinObjects("LibRockFakeLib-1.0") do
		assert(object == t)
	end

	Rock:FinalizeLibrary("LibRockFakeLib-1.0")

	local myNewLib, oldLib = Rock:NewLibrary("LibRockFakeLib-1.0", 2)
	assert(myNewLib == myLib)
	assert(oldLib)
	assert(Rock:GetLibrary("LibRockFakeLib-1.0") == myLib)

	function myLib:DoSomething()
		return "Something else"
	end
	function myLib:TrySomething()
		return "Blah"
	end
	myLib:SetExportedMethods("DoSomething", "TrySomething")
	assert(Rock:IsLibraryMixin("LibRockFakeLib-1.0"))
	assert(t:DoSomething() == "Something else")
	assert(t:TrySomething() == "Blah")
	assert(Rock:DoesObjectUseMixin(t, "LibRockFakeLib-1.0"))
	for mixin in Rock:IterateObjectMixins(t) do
		assert(mixin == myLib)
	end
	for object in Rock:IterateMixinObjects("LibRockFakeLib-1.0") do
		assert(object == t)
	end

	Rock:FinalizeLibrary("LibRockFakeLib-1.0")

	local myNewLib, oldLib = Rock:NewLibrary("LibRockFakeLib-1.0", 3)
	assert(myNewLib == myLib)
	assert(oldLib)
	assert(Rock:GetLibrary("LibRockFakeLib-1.0") == myLib)

	function myLib:DoSomething()
		return "Something"
	end
	myLib:SetExportedMethods("DoSomething")
	assert(Rock:IsLibraryMixin("LibRockFakeLib-1.0"))
	assert(t:DoSomething() == "Something")
	assert(t.TrySomething == nil)
	assert(Rock:DoesObjectUseMixin(t, "LibRockFakeLib-1.0"))
	for mixin in Rock:IterateObjectMixins(t) do
		assert(mixin == myLib)
	end
	for object in Rock:IterateMixinObjects("LibRockFakeLib-1.0") do
		assert(object == t)
	end

	Rock:FinalizeLibrary("LibRockFakeLib-1.0")

	assert(not Rock:NewLibrary("LibRockFakeLib-1.0", 2)) -- out of date
	assert(not Rock:NewLibrary("LibRockFakeLib-1.0", 3)) -- same revision
end)

Rock:AddUnitTest(MAJOR_VERSION, function()
	assert(not Rock:HasAddon("RockFakeAddon"))
	for name in Rock:IterateAddons() do
		assert(name ~= "RockFakeAddon")
	end

	local myAddon = Rock:NewAddon("RockFakeAddon")

	assert(myAddon)
	assert(myAddon.name == "RockFakeAddon")

	local good = false
	for name in Rock:IterateAddons() do
		if name == "RockFakeAddon" then
			good = true
			break
		end
	end
	assert(good)
	assert(Rock:HasAddon("RockFakeAddon"))
	assert(Rock:GetAddon("RockFakeAddon") == myAddon)
end)

Rock:AddUnitTest(MAJOR_VERSION, function()
	-- test :OnLibraryLoad
	local lib = Rock:NewLibrary("LibRockFakeLib-1.0", 1)
	local triggered = false
	function lib:OnLibraryLoad(major, instance)
		if major == "LibRockFakeLib-2.0" then
			triggered = true
		end
	end
	Rock:FinalizeLibrary("LibRockFakeLib-1.0")

	local lib = Rock:NewLibrary("LibRockFakeLib-2.0", 1)
	assert(not triggered)
	Rock:FinalizeLibrary("LibRockFakeLib-2.0")
	assert(triggered)
	triggered = false
	local lib = Rock:NewLibrary("LibRockFakeLib-2.0", 2)
	assert(not triggered)
	Rock:FinalizeLibrary("LibRockFakeLib-2.0")
	assert(not triggered)
end)
