local MAJOR_VERSION = "2.0"
local MINOR_VERSION = tonumber((string.gsub("$Revision: 11580 $", "^.-(%d+).-$", "%1")))

if FUBAR_REVISION and FUBAR_REVISION > MINOR_VERSION then
	MINOR_VERSION = FUBAR_REVISION
end
FUBAR_REVISION = nil

local Compost = AceLibrary("Compost-2.0")
local Dewdrop = AceLibrary("Dewdrop-2.0")
local Tablet = AceLibrary:HasInstance("Tablet-2.0") and AceLibrary("Tablet-2.0")
local Jostle = AceLibrary("Jostle-2.0")

local L = AceLibrary("AceLocale-2.0"):new("FuBar")

FuBar = AceLibrary("AceAddon-2.0"):new("AceDB-2.0", "AceConsole-2.0", "AceEvent-2.0", "AceHook-2.0")
local FuBar = FuBar
FuBar.title = "FuBar"
FuBar.version = MAJOR_VERSION .. "." .. MINOR_VERSION
FuBar.date = string.gsub("$Date: 2006-09-20 20:41:36 -0400 (Wed, 20 Sep 2006) $", "^.-(%d%d%d%d%-%d%d%-%d%d).-$", "%1")

FuBar:RegisterDB("FuBar2DB")

local charID = AceLibrary("AceDB-2.0").CHAR_ID
local realmID = AceLibrary("AceDB-2.0").REALM_ID
local classID = AceLibrary("AceDB-2.0").CLASS_ID

local _G = getfenv(0)
local plugins = {}

local FuBar_Panel = FuBar_Panel
_G.FuBar_Panel = nil

if not FUBAR_DEFAULTS then
	FUBAR_DEFAULTS = function()
		return {
			fontSize = 12,
			adjust = true,
			panels = {
				[1] = {
					attachPoint = "TOP",
					plugins = {
						left = {
							"LocationFu",
							"ExperienceFu",
							"PerformanceFu",
						},
						center = {},
						right = {
							"ClockFu",
							"VolumeFu",
						}
					}
				}, 
				[2] = {
					attachPoint = "BOTTOM"
				}
			}
		}
	end
end

local CATEGORIES = {
	["Action Bars"] = L["Action Bars"],
	["Auction"] = L["Auction"],
	["Audio"] = L["Audio"],
	["Battlegrounds/PvP"] = L["Battlegrounds/PvP"],
	["Buffs"] = L["Buffs"],
	["Chat/Communication"] = L["Chat/Communication"],
	["Druid"] = L["Druid"],
	["Hunter"] = L["Hunter"],
	["Mage"] = L["Mage"],
	["Paladin"] = L["Paladin"],
	["Priest"] = L["Priest"],
	["Rogue"] = L["Rogue"],
	["Shaman"] = L["Shaman"],
	["Warlock"] = L["Warlock"],
	["Warrior"] = L["Warrior"],
	["Healer"] = L["Healer"],
	["Tank"] = L["Tank"],
	["Caster"] = L["Caster"],
	["Combat"] = L["Combat"],
	["Compilations"] = L["Compilations"],
	["Data Export"] = L["Data Export"],
	["Development Tools"] = L["Development Tools"],
	["Guild"] = L["Guild"],
	["Frame Modification"] = L["Frame Modification"],
	["Interface Enhancements"] = L["Interface Enhancements"],
	["Inventory"] = L["Inventory"],
	["Library"] = L["Library"],
	["Map"] = L["Map"],
	["Mail"] = L["Mail"],
	["Miscellaneous"] = L["Miscellaneous"],
	["Quest"] = L["Quest"],
	["Raid"] = L["Raid"],
	["Tradeskill"] = L["Tradeskill"],
	["UnitFrame"] = L["UnitFrame"],
	["Others"] = L["Others"],
}

local basePluginFrame

local alreadyEnabled = false

local backgrounds = {["Interface\\AddOns\\FuBar\\background"] = L["Default"]}

function FuBar:RegisterSkin(name, bgFile)
	assert(type(name) == "string", "You must provide a name for your skin")
	assert(type(bgFile) == "string", "You must provide a file for your skin")
	if not backgrounds[bgFile] then
		backgrounds[bgFile] = name
	end
end

local function SetBackground(bgFile)
	if backgrounds[bgFile] then
		FuBar.db.profile.skin = bgFile
		FuBar_Panel:SetBackground(bgFile)
	end
end

local function GetBackground()
	return backgrounds[FuBar.db.profile.skin] and FuBar.db.profile.skin or "Interface\\AddOns\\FuBar\\background"
end

local function background_sort(alpha, bravo)
	return backgrounds[alpha] < backgrounds[bravo]
end

local optionsTable = {
	handler = FuBar,
	type = "group",
	args = {
		overflow = {
			type = "toggle",
			name = L["Overflow plugins"],
			desc = L["Let plugins overflow onto another panel"],
			get = "IsOverflowing",
			set = "ToggleOverflowing",
		},
		create = {
			type = "execute",
			name = L["Create new panel"],
			desc = L["Create new panel"],
			func = "new",
			handler = FuBar_Panel
		},
		adjust = {
			type = "toggle",
			name = L["Auto-adjust frames"],
			desc = L["Toggle auto-adjustment of blizzard's frames"],
			get = "IsAdjust",
			set = "ToggleAdjust",
		},
		autohideTop = {
			type = "toggle",
			name = L["Auto-hide top panels"],
			desc = L["Toggle auto-hiding of the top panels"],
			get = "IsAutoHidingTop",
			set = "ToggleAutoHidingTop",
		},
		autohideBottom = {
			type = "toggle",
			name = L["Auto-hide bottom panels"],
			desc = L["Toggle auto-hiding of the bottom panels"],
			get = "IsAutoHidingBottom",
			set = "ToggleAutoHidingBottom",
		},
		texture = {
			type = "text",
			name = L["Texture"],
			desc = L["Change the texture of the panels"],
			get = GetBackground,
			set = SetBackground,
			validate = backgrounds
		},
		spacing = {
			type = "group",
			name = L["Spacing"],
			desc = L["Spacing between plugins"],
			args = {
				left = {
					type = "range",
					name = L["Left-aligned spacing"],
					desc = L["Set spacing between left-aligned plugins"],
					max = 40,
					min = 0,
					step = 1,
					get = "GetLeftSpacing",
					set = "SetLeftSpacing",
				},
				center = {
					type = "range",
					name = L["Center-aligned spacing"],
					desc = L["Set spacing between center-aligned plugins"],
					max = 40,
					min = 0,
					step = 1,
					get = "GetCenterSpacing",
					set = "SetCenterSpacing",
				},
				right = {
					type = "range",
					name = L["Right-aligned spacing"],
					desc = L["Set spacing between right-aligned plugins"],
					max = 40,
					min = 0,
					step = 1,
					get = "GetRightSpacing",
					set = "SetRightSpacing",
				},
			}
		},
		fontsize = {
			type = "group",
			name = L["Font size"],
			desc = L["Font size"],
			args = {
				panel = {
					type = "range",
					name = L["Panel font size"],
					desc = L["Set font size for the plugins on the panel"],
					max = 24,
					min = 7,
					step = 1,
					get = "GetFontSize",
					set = "SetFontSize",
				},
				tooltip = {
					type = "range",
					name = L["Tooltip font size"],
					desc = L["Set font size for the tooltip"],
					max = 2,
					min = 0.5,
					isPercent = true,
					get = function()
						return Tablet:GetFontSizePercent(FuBar.db.profile.tooltip)
					end,
					set = function(value)
						return Tablet:SetFontSizePercent(FuBar.db.profile.tooltip, value)
					end,
					hidden = function()
						return not Tablet
					end
				}
			}
		},
		transparency = {
			type = "group",
			name = L["Transparency"],
			desc = L["Transparency"],
			args = {
				panel = {
					type = "range",
					name = L["Panel transparency"],
					desc = L["Set transparency of the panels"],
					max = 1,
					min = 0,
					step = 0.05,
					isPercent = true,
					get = "GetTransparency",
					set = "SetTransparency",
				},
				tooltip = {
					type = "range",
					name = L["Tooltip transparency"],
					desc = L["Set transparency of the tooltip"],
					max = 1,
					min = 0,
					step = 0.05,
					isPercent = true,
					get = function()
						return Tablet:GetTransparency(FuBar.db.profile.tooltip)
					end,
					set = function(value)
						return Tablet:SetTransparency(FuBar.db.profile.tooltip, value)
					end,
					hidden = function()
						return not Tablet
					end
				}
			}
		},
		thickness = {
			type = "range",
			name = L["Thickness"],
			desc = L["Set thickness between the panels"],
			max = 20,
			min = 0,
			step = 1,
			get = "GetThickness",
			set = "SetThickness",
		},
	}
}

FuBar:RegisterChatCommand(L:GetTable("ChatCommands"), optionsTable)

optionsTable.args.standby = nil

function FuBar:GetScaledCursorPosition()
	local x, y = GetCursorPosition()
	local scale = GetScreenHeight() / 768
	return x * scale, y * scale
end

function FuBar:Reset()
	self:UnregisterEvent("PLAYER_LOGOUT")
	FuBar2DB = {}
	ReloadUI()
end

function FuBar:GetPanel(panelId)
	return FuBar_Panel.instances[panelId]
end

function FuBar:GetNumPanels()
	return table.getn(FuBar_Panel.instances)
end

function FuBar:GetBottommostTopPanel()
	local bottom = GetScreenHeight()
	local best
	for i = 1, self:GetNumPanels() do
		local panel = self:GetPanel(i)
		if panel:GetAttachPoint() == "TOP" and panel.frame:GetBottom() < bottom then
			bottom = panel.frame:GetBottom()
			best = panel
		end
	end
	return best
end

function FuBar:GetTopmostBottomPanel()
	local top = 0
	local best
	for i = 1, self:GetNumPanels() do
		local panel = self:GetPanel(i)
		if panel:GetAttachPoint() == "BOTTOM" and panel.frame:GetTop() > top then
			top = panel.frame:GetTop()
			best = panel
		end
	end
	return best
end

local isChangingProfile, previousProfile

function FuBar:IsChangingProfile()
	return isChangingProfile
end

function FuBar:GetPluginProfiling()
	for _,p in ipairs(plugins) do
		if p:GetTitle() == self.db.profile.profilePlugin then
			return p, p:GetTitle()
		end
	end
end

function FuBar:SetPluginProfiling(plugin)
	self.db.profile.profilePlugin = plugin:GetTitle()
end

function FuBar:IsAdjust()
	return self.db.profile.adjust
end

function FuBar:ToggleAdjust()
	self.db.profile.adjust = not self.db.profile.adjust
	self:UpdateJostleAdjustments()
	return self.db.profile.adjust
end

function FuBar:IsOverflowing()
	return not self.db.profile.overflow
end

function FuBar:ToggleOverflowing()
	self.db.profile.overflow = not self.db.profile.overflow
	for i = 1, self:GetNumPanels() do
		self:GetPanel(i):CheckForOverlap()
	end
	return not self.db.profile.overflow
end

function FuBar:UpdateJostleAdjustments()
	if not self.db.profile.adjust then
		Jostle:DisableTopAdjusting()
		Jostle:DisableBottomAdjusting()
	else
		if self:IsAutoHidingTop() then
			Jostle:DisableTopAdjusting()
		else
			Jostle:EnableTopAdjusting()
		end
		if self:IsAutoHidingBottom() then
			Jostle:DisableBottomAdjusting()
		else
			Jostle:EnableBottomAdjusting()
		end
	end
end

function FuBar:IsAutoHidingTop()
	return self.db.profile.autohideTop
end

local autohideTopTime, autohideBottomTime

function FuBar:ToggleAutoHidingTop()
	self.db.profile.autohideTop = not self.db.profile.autohideTop
	self:UpdateJostleAdjustments()
	if not self.db.profile.autohideTop then
		for i = 1, self:GetNumPanels() do
			if self:GetPanel(i):GetAttachPoint() == "TOP" then
				local frame = getglobal("FuBarFrame" .. i)
				frame:SetAlpha(1)
				frame:SetFrameStrata("HIGH")
			end
		end
	else
		self:ScheduleEvent("FuBar_AutoHideTop", self.OnUpdate_AutoHideTop, 1, self)
	end
	autohideTopTime = GetTime()
	return self.db.profile.autohideTop
end

function FuBar:IsAutoHidingBottom()
	return self.db.profile.autohideBottom
end

function FuBar:ToggleAutoHidingBottom()
	self.db.profile.autohideBottom = not self.db.profile.autohideBottom
	self:UpdateJostleAdjustments()
	if not self.db.profile.autohideBottom then
		for i = 1, self:GetNumPanels() do
			if self:GetPanel(i):GetAttachPoint() == "BOTTOM" then
				local frame = getglobal("FuBarFrame" .. i)
				frame:SetAlpha(1)
				frame:SetFrameStrata("HIGH")
			end
		end
	else
		self:ScheduleEvent("FuBar_AutoHideBottom", self.OnUpdate_AutoHideBottom, 1, self)
	end
	autohideBottomTime = GetTime()
	return self.db.profile.autohideBottom
end

function FuBar:GetFontSize()
	if not self.db.profile then
		return 12
	end
	return self.db.profile.fontSize or 12
end
	
function FuBar:SetFontSize(size)
	size = size or 12
	self.db.profile.fontSize = size
	for i,plugin in ipairs(plugins) do
		if type(plugin.SetFontSize) ~= "function" then
			table.remove(plugins, i)
			i = i - 1
		else
			plugin:SetFontSize(size)
		end
	end
	for i = 1, self:GetNumPanels() do
		self:GetPanel(i):UpdateTexture()
	end
	self:Update()
	Jostle:Refresh()
end

function FuBar:GetLeftSpacing()
	if not self.db.profile then
		return 20
	end
	return self.db.profile.leftSpacing or 20
end

function FuBar:SetLeftSpacing(size)
	self.db.profile.leftSpacing = size
	self:Update()
end

function FuBar:GetCenterSpacing()
	if not self.db.profile then
		return 20
	end
	return self.db.profile.centerSpacing or 20
end

function FuBar:SetCenterSpacing(size)
	self.db.profile.centerSpacing = size
	self:Update()
end
	
function FuBar:GetRightSpacing()
	if not self.db.profile then
		return 20
	end
	return self.db.profile.rightSpacing or 20
end

function FuBar:SetRightSpacing(size)
	self.db.profile.rightSpacing = size
	self:Update()
end

function FuBar:SetThickness(size)
	self.db.profile.thickness = size
	for i = 1, self:GetNumPanels() do
		self:GetPanel(i):UpdateTexture()
	end
	self:Update()
	Jostle:Refresh()
end

function FuBar:GetThickness(size)
	if not self.db.profile then
		return 5
	end
	return self.db.profile.thickness or 5
end

function FuBar:GetTransparency()
	return self.db.profile.transparency or 0.8
end

function FuBar:SetTransparency(value)
	self.db.profile.transparency = value
	for i = 1, self:GetNumPanels() do
		self:GetPanel(i):UpdateTexture()
	end
end

local function CheckLoadCondition(loadCondition)
	return RunScript("(function()"..loadCondition.." end)()")
end

local function _IsCorrectPlugin(plugin)
	if type(plugin) ~= "table" then
		return false
	elseif type(plugin.GetName) ~= "function" then
		return false
	elseif type(plugin.GetTitle) ~= "function" then
		return false
	elseif type(plugin.GetCategory) ~= "function" then
		return false
	elseif type(plugin.SetFontSize) ~= "function" then
		return false
	elseif type(plugin.GetFrame) ~= "function" then
		return false
	elseif type(plugin.Show) ~= "function" then
		return false
	elseif type(plugin.Hide) ~= "function" then
		return false
	elseif type(plugin.GetPanel) ~= "function" then
		return false
	elseif type(plugin:GetName()) ~= "string" then
		return false
	elseif type(plugin:GetTitle()) ~= "string" then
		return false
	elseif type(plugin:GetCategory()) ~= "string" then
		return false
	end
	local frame = plugin:GetFrame()
	if type(frame) ~= "table" then
		return false
	elseif type(frame[0]) ~= "userdata" then
		return false
	elseif type(frame.GetFrameType) ~= "function" then
		return false
	elseif type(frame:GetFrameType()) ~= "string" then
		return false
	end
	return true
end

local donothing = function() end

local function IsCorrectPlugin(plugin)
	local add = DEFAULT_CHAT_FRAME.AddMessage
	DEFAULT_CHAT_FRAME.AddMessage = donothing
	local ret, msg = pcall(_IsCorrectPlugin, plugin)
	DEFAULT_CHAT_FRAME.AddMessage = add
	if ret then
		return msg
	end
end

local function _IsCorrectPanel(panel)
	if type(panel) ~= "table" then
		return false
	elseif type(panel.AddPlugin) ~= "function" then
		return false
	elseif type(panel.RemovePlugin) ~= "function" then
		return false
	elseif type(panel.GetNumPlugins) ~= "function" then
		return false
	elseif type(panel:GetNumPlugins()) ~= "number" then
		return false
	elseif type(panel.GetPlugin) ~= "function" then
		return false
	elseif type(panel.HasPlugin) ~= "function" then
		return false
	elseif type(panel.GetPluginSide) ~= "function" then
		return false
	end
	return true
end

local function IsCorrectPanel(panel)
	local add = DEFAULT_CHAT_FRAME.AddMessage
	DEFAULT_CHAT_FRAME.AddMessage = donothing
	local ret, msg = pcall(_IsCorrectPanel, panel)
	DEFAULT_CHAT_FRAME.AddMessage = add
	if ret then
		return msg
	end
end

local n
local mt = {
	__newindex = function(self, k, v)
		rawset(self, k, v)
		n[k] = v
	end,
}

local function IsAceAddon(addon)
	if type(addon) ~= "table" then
		return false
	elseif addon == AceAddon then
		return true
	elseif not getmetatable(addon) then
		return false
	else
		return IsAceAddon(getmetatable(addon).__index)
	end
end

local blank = {}
local function LoadAddOnWrapper(addon)
	local o = getfenv(0)
	n = {}
	setmetatable(o, mt)
	local success, ret = pcall(LoadAddOn, addon)
	setmetatable(o, blank)
	if not ret then
		FuBar:Print("Error loading LoadOnDemand plugin " .. addon)
		return
	elseif not success then
		FuBar:Print("Error loading LoadOnDemand plugin " .. addon .. ": " .. ret)
		return
	end
	
	local dependencies
	local plugin
	for k,v in pairs(n) do
		if IsCorrectPlugin(v) then
			if plugin then
				if not dependencies then
					dependencies = {}
				end
				table.insert(dependencies, plugin)
			end
			plugin = v
			if IsAceAddon(plugin) then
				if not plugin._initialized then
					if not AceData.profileBasePath then
						--plugin._initialized = true
						--plugin:Initialize()
						--ace1ToBeInitialized[plugin] = true
					else
						plugin._initialized = true
						ace:InitializeApp(plugin)
					end
				end
			else
				if not plugin._initialized then
					plugin._initialized = true
					if type(plugin.Initialize) == "function" then
						plugin:Initialize()
					end
				end
			end
		end
	end
	if not plugin then
		FuBar:Print("Error loading LoadOnDemand plugin " .. addon)
		return
	end
	return ret, plugin, dependencies
end

local toBeLoaded

local function CleanDB()
	local self = FuBar
	
	local t = Compost:Acquire()
	for _,panel in ipairs(self.db.profile.panels) do
		if panel and panel.plugins then
			for _,part in pairs(panel.plugins) do
				if part then
					for i,plugin in ipairs(part) do
						if t[plugin] then
							i = i - 1
							table.remove(part, i)
						else
							t[plugin] = true
						end
					end
				end
			end
		end
	end
	t = Compost:Reclaim(t)
end

function FuBar:OnInitialize()
	toBeLoaded = {}
	
	if not next(FuBar.db.profile) then
		local def
		local profile = self:GetProfile()
		if string.find(profile, "^char/") or string.find(profile, "^class/") then
			local _,class = UnitClass("player")
			def = _G["FUBAR_DEFAULTS_" .. class] or FUBAR_DEFAULTS
		else
			def = FUBAR_DEFAULTS
		end
		if type(def) == "function" then
			def = def()
		end
		for k,v in pairs(def) do
			FuBar.db.profile[k] = v
		end
	end
	
	if not self.db.profile.loadOnDemand then
		self.db.profile.loadOnDemand = {}
	end
	
	if not self.db.profile.tooltip then
		self.db.profile.tooltip = {}
	end
	
	self:SetFontSize(self:GetFontSize())
	
	self.menu = function(level, value)
		local panelId = FuBar_Panel.selectedPanel or 1
		local panel = self:GetPanel(panelId)
		
		if not panel then
			Dewdrop:Close()
			return
		end
		
		if level == 1 then
			Dewdrop:AddLine(
				'text', self.name,
				'isTitle', true
			)
			
			local menuTitles = Compost:Acquire()
			for _,plugin in ipairs(plugins) do
				local alreadyExists = false
				local category = plugin:GetCategory()
				if not CATEGORIES[category] then
					category = L["Others"]
				end
				menuTitles[category] = true
			end
			for _,name in ipairs(toBeLoaded) do
				local category = self.db.profile.loadOnDemand[name].category
				if not CATEGORIES[category] then
					category = L["Others"]
				end
				menuTitles[category] = true
			end
			local titles = Compost:Acquire()
			for name in pairs(menuTitles) do
				table.insert(titles, name)
			end
			Compost:Reclaim(menuTitles)
			table.sort(titles)
			for _,category in ipairs(titles) do
				Dewdrop:AddLine(
					'text', CATEGORIES[category],
					'value', category,
					'hasArrow', true
				)
			end
			Compost:Reclaim(titles)
			
			Dewdrop:AddLine()
			
			Dewdrop:AddLine(
				'text', L["Attach"],
				'hasArrow', true,
				'value', "attach"
			)
			
			Dewdrop:AddLine(
				'text', L["Lock panel"],
				'arg1', panel,
				'func', "ToggleLocked",
				'checked', panel:IsLocked()
			)
			
			Dewdrop:AddLine(
				'text', L["Remove panel"],
				'arg1', panel,
				'func', "WarnDestroy",
				'disabled', panelId == 1 and not self:GetPanel(2),
				'closeWhenClicked', true
			)
			
			Dewdrop:AddLine()
			
			Dewdrop:FeedAceOptionsTable(optionsTable)
			
			Dewdrop:AddLine(
				'text', CLOSE,
				'arg1', Dewdrop,
				'func', "Close"
			)
		elseif Dewdrop:FeedAceOptionsTable(optionsTable) then
		elseif level == 2 then
			if value == "attach" then
				Dewdrop:AddLine(
					'text', L["Attach to top"],
					'arg1', panel,
					'func', panel:GetAttachPoint() ~= "TOP" and "SetAttachPoint",
					'arg2', "TOP",
					'checked', panel:GetAttachPoint() == "TOP",
					'closeWhenClicked', true,
					'isRadio', true
				)
				
				Dewdrop:AddLine(
					'text', L["Attach to bottom"],
					'arg1', panel,
					'func', panel:GetAttachPoint() ~= "BOTTOM" and "SetAttachPoint",
					'arg2', "BOTTOM",
					'checked', panel:GetAttachPoint() == "BOTTOM",
					'closeWhenClicked', true,
					'isRadio', true
				)
				
				Dewdrop:AddLine(
					'text', L["Detach panel"],
					'arg1', panel,
					'func', panel:GetAttachPoint() ~= "NONE" and "SetAttachPoint",
					'arg2', "NONE",
					'checked', panel:GetAttachPoint() == "NONE",
					'closeWhenClicked', true,
					'isRadio', true
				)
			else
				for _,plugin in ipairs(plugins) do
					local category = plugin:GetCategory()
					if not CATEGORIES[category] then
						category = L["Others"]
					end
					if category == value then
						Dewdrop:AddLine(
							'text', plugin:GetTitle(),
							'arg1', plugin,
							'func', plugin:GetPanel() and "Hide" or "Show",
							'arg2', FuBar_Panel.selectedPanel or 1,
							'checked', plugin:GetPanel()
						)
					end
				end
				for i,name in ipairs(toBeLoaded) do
					local i, name = i, name
					local lod = self.db.profile.loadOnDemand[name]
					
					local category = lod.category
					if not CATEGORIES[category] then
						category = L["Others"]
					end
					if category == value then
						Dewdrop:AddLine(
							'text', lod.title,
							'arg1', self,
							'func', "LoadPlugin",
							'arg2', name
						)
					end
				end
			end
		end
	end
	
	self:ScheduleEvent(self.LoadLoadOnDemandPlugins, 0, self)
	
	CleanDB()
end

function FuBar:LoadPlugin(name)
	if not alreadyEnabled then
		local dep1,dep2,dep3,dep4 = GetAddOnDependencies(name)
		if dep1 == "Ace" or dep2 == "Ace" or dep3 == "Ace" or dep4 == "Ace" then
			if not self.acePluginToLoad then
				self.acePluginToLoad = {}
			end
			self.acePluginToLoad[name] = true
			return
		end
	end
	local loaded, plugin, dependencies = LoadAddOnWrapper(name)
	if loaded then
		if type(plugin.SetLoadOnDemand) == "function" then
			plugin:SetLoadOnDemand(true)
		end
		self.db.profile.loadOnDemand[name] = {
			title = plugin:GetTitle(),
			category = plugin:GetCategory(),
			disabled = nil,
			condition = type(plugin.GetLoadCondition) == "function" and plugin:GetLoadCondition() or nil,
		}
		plugin:Show(FuBar_Panel.selectedPanel or false)
		if dependencies then
			for _,v in ipairs(dependencies) do
				if type(v.SetLoadOnDemand) == "function" then
					v:SetLoadOnDemand(true)
				end
				self.db.profile.loadOnDemand[name].disabled = nil
				for j,u in ipairs(toBeLoaded) do
					if u == v:GetTitle() then
						table.remove(toBeLoaded, j)
						break
					end
				end
				if v.hideWithoutStandby then
					v:EnableApp()
				else
					v:Show(panelId or false)
				end
			end
		end
		self:SetFontSize(self:GetFontSize())
		for i,n in ipairs(toBeLoaded) do
			if n == name then
				table.remove(toBeLoaded, i)
				break
			end
		end
		self.db.profile.loadOnDemand[name].disabled = nil
	end
end

function FuBar:OnEnable()
	if alreadyEnabled then
		ReloadUI()
		return
	end
	alreadyEnabled = true
	
	if AceLibrary:HasInstance("Tablet-2.0") then
		Tablet = AceLibrary("Tablet-2.0")
	end
	if not self.db.profile.panels then
		self.db.profile.panels = {}
	end
	
	if not self.db.profile.minimap then
		self.db.profile.minimap = {}
	end
	
	for i = table.getn(self.db.profile.panels), 1, -1 do
		if self.db.profile.panels[i] then
			FuBar_Panel:new()
		end
	end
	
	self:CheckResolution()
	
	if self:GetNumPanels() == 0 then
		FuBar_Panel:new()
	end
	
	if FuBar.db.profile.skin then
		SetBackground(FuBar.db.profile.skin)
	end
	
	self:RegisterEvent("AceLibrary_Register")
--	self:RegisterEvent("DISPLAY_SIZE_CHANGED", "OnDisplaySizeChanged")
	self:RegisterEvent("CVAR_UPDATE", "OnCVarUpdate")
	
	self:Hook("RestartGx")
	
	local function func()
		if not self:SetupPlugins() then
			self:ScheduleEvent(func, 0)
			return
		end
		self:Update()
		
		self:ScheduleEvent("FuBar_AutoHideTop", self.OnUpdate_AutoHideTop, 1, self)
		self:ScheduleEvent("FuBar_AutoHideBottom", self.OnUpdate_AutoHideBottom, 1, self)
	end
	self:RegisterEvent("AceEvent_FullyInitialized", func, true)
	
	self:UpdateJostleAdjustments()
	
	if self.db.profile.skin and backgrounds[self.db.profile.skin] then
		FuBar_Panel:SetBackground(self.db.profile.skin)
	end
	
	self:RegisterEvent("PLAYER_ENTERING_WORLD", "PLAYER_ENTERING_WORLD", true)
end

function FuBar:LoadLoadOnDemandPlugins()
	for i = 1, GetNumAddOns() do
		local name, title, notes, enabled, loadable, reason, security = GetAddOnInfo(i)
		if IsAddOnLoadOnDemand(i) and enabled and loadable and not IsAddOnLoaded(i) then
			local deps = Compost:Acquire(GetAddOnDependencies(name))
			local good = false
			for _,dep in ipairs(deps) do
				if dep == "FuBar" then
					good = true
					break
				end
			end
			if good then
				if not self.db.profile.loadOnDemand[name] then
					self:LoadPlugin(name)
				elseif not self.db.profile.loadOnDemand[name].disabled and (not self.db.profile.loadOnDemand[name].condition or CheckLoadCondition(self.db.profile.loadOnDemand[name].condition)) then
					self:LoadPlugin(name)
				else
					table.insert(toBeLoaded, name)
				end
			end
			Compost:Reclaim(deps)
		end
	end
end

function FuBar:AceLibrary_Register(major, instance)
	if major == "Tablet-2.0" then
		Tablet = instance
	end
end

function FuBar:PLAYER_ENTERING_WORLD()
	if self.acePluginToLoad then
		for name in pairs(self.acePluginToLoad) do
			self:LoadPlugin(name)
		end
		Compost:Reclaim(self.acePluginToLoad)
		self.acePluginToLoad = nil
	end
end

function FuBar:RestartGx()
	self.hooks.RestartGx.orig()
	self:ScheduleEvent(self.Update, 0, self)
end

function FuBar:OnCVarUpdate()
	if arg1 == "USE_UISCALE" then
		self:ScheduleEvent(self.CheckResolution, 0, self)
	end
end

local lastScreenWidth = GetScreenWidth()
function FuBar:CheckResolution()
	local screenWidth = GetScreenWidth()
	if lastScreenWidth ~= screenWidth then
		for i = 1, self:GetNumPanels() do
			self:GetPanel(i):Update()
		end
		lastScreenWidth = screenWidth
		Jostle:Refresh()
	end
end

local inTopPanel, inBottomPanel

function FuBar:OnUpdate_AutoHideTop()
	if not inTopPanel and self:IsAutoHidingTop() then
		for i = 1, self:GetNumPanels() do
			if self:GetPanel(i):GetAttachPoint() == "TOP" then
				local frame = getglobal("FuBarFrame" .. i)
				frame:SetAlpha(0)
				frame:SetFrameStrata("BACKGROUND")
			end
		end
	end
end

function FuBar:OnUpdate_AutoHideBottom()
	if not inBottomPanel and self:IsAutoHidingBottom() then
		for i = 1, self:GetNumPanels() do
			if self:GetPanel(i):GetAttachPoint() == "BOTTOM" then
				local frame = getglobal("FuBarFrame" .. i)
				frame:SetAlpha(0)
				frame:SetFrameStrata("BACKGROUND")
			end
		end
	end
end

local doneSetupPlugins, pluginsToSetup

function FuBar:OnDisable()
	for i = 1, self:GetNumPanels() do
		self:GetPanel(i).frame:Hide()
	end
end

function FuBar:Report()
	local report = Compost:Acquire(
		Compost:AcquireHash(
			'text', self.loc.ARGUMENT_ADJUST,
			'val', self:IsAdjust() and 1 or 0,
			'map', self.loc.MAP_ONOFF
		)
	)
	self.cmd:report(report)
	Compost:Reclaim(report, 1)
end

function FuBar:GetNumPlugins()
	return table.getn(plugins)
end

function FuBar:GetPlugin(i)
	return plugins[i]
end

function FuBar:RegisterPlugin(plugin)
	if not IsCorrectPlugin(plugin) then
		return
	end
	table.insert(plugins, plugin)
	
	local frame = plugin:GetFrame()
	local downTime
	if frame:HasScript("OnClick") then
		local OnClick = frame:GetScript("OnClick")
		frame:SetScript("OnClick", function()
			if OnClick and (not downTime or GetTime() < downTime + 0.5) and (not this.stopClick or GetTime() > this.stopClick) then
				OnClick()
			end
			downTime = nil
		end)
	end
	local OnMouseDown = frame:GetScript("OnMouseDown")
	frame:SetScript("OnMouseDown", function()
		if arg1 == "LeftButton" and not IsShiftKeyDown() and not IsControlKeyDown() and not IsAltKeyDown() then
			downTime = GetTime()
			FuBar:Plugin_StartDrag(plugin)
		end
		if OnMouseDown then
			OnMouseDown()
		end
	end)
	local OnMouseUp = frame:GetScript("OnMouseUp")
	frame:SetScript("OnMouseUp", function()
		if arg1 == "LeftButton" then
			if not FuBar:Plugin_StopDrag(plugin) then
				if OnMouseUp then
					OnMouseUp()
				end
			else
				this.stopClick = GetTime() + 0.05
			end
		elseif OnMouseUp then
			OnMouseUp()
		end
	end)
	local OnEnter = frame:GetScript("OnEnter")
	frame:SetScript("OnEnter", function()
		FuBar:Panel_OnEnter(plugin)
		if OnEnter then
			OnEnter()
		end
	end)
	local OnLeave = frame:GetScript("OnLeave")
	frame:SetScript("OnLeave", function()
		FuBar:Panel_OnLeave(this.plugin)
		if OnLeave then
			OnLeave()
		end
	end)
end

function FuBar:Update()
	for panelId = 1, self:GetNumPanels() do
		self:GetPanel(panelId):Update()
	end
end

function FuBar:SetupPlugins()
	doneSetupPlugins = true
	if pluginsToSetup then
		for i = 1, self:GetNumPanels() do
			local panel = self:GetPanel(i)
			for h = 1, 3 do
				local side
				if h == 1 then
					side = "LEFT"
				elseif h == 2 then
					side = "CENTER"
				else
					side = "RIGHT"
				end
				local order = panel:GetSavedOrder(side)
				for _,name in ipairs(order) do
					for plugin in pairs(pluginsToSetup) do
						if not plugin:IsDisabled() and plugin:GetTitle() == name then
							if not panel:AddPlugin(plugin, nil, side) then
								doneSetupPlugins = false
								return
							end
							pluginsToSetup[plugin] = nil
							break
						end
					end
				end
			end
		end
		
		local order = self.db.profile.detached
		if order then
			for name in pairs(order) do
				for plugin in pairs(pluginsToSetup) do
					if not plugin:IsDisabled() and plugin:GetTitle() == name then
						pluginsToSetup[plugin] = nil
						plugin:Show(0)
						break
					end
				end
			end
		else
			self.db.profile.detached = {}
		end
		
		for plugin in pairs(pluginsToSetup) do
			if type(plugin.GetDefaultPosition) == "function" and plugin:GetDefaultPosition() == "MINIMAP" then
				plugin:Show(0)
			else
				self.lastPanelId = (self.lastPanelId or 0) + 1
				if self.lastPanelId > self:GetNumPanels() then
					self.lastPanelId = 1
				end
				self:GetPanel(self.lastPanelId):AddPlugin(plugin, nil, type(plugin.GetDefaultPosition) == "function" and plugin:GetDefaultPosition() or "LEFT", true)
			end
		end
		
		pluginsToSetup = nil
	end
	return true
end

function FuBar:ShowPlugin(plugin, panelId)
	if doneSetupPlugins then
		if not panelId then
			doneSetupPlugins = false
			pluginsToSetup = Compost:Acquire()
			pluginsToSetup[plugin] = true
			local function func()
				if not self:SetupPlugins() then
					self:ScheduleEvent(func, 0)
					return
				end
				self:Update()
			end
			func()
		else
			self:GetPanel(panelId):AddPlugin(plugin, nil, type(plugin.GetDefaultPosition) == "function" and plugin:GetDefaultPosition() or "LEFT", true)
		end
	else
		if not pluginsToSetup then
			pluginsToSetup = {}
		end
		pluginsToSetup[plugin] = true
	end
end

local previousPosition_panel, previousPosition_index, previousPosition_side, previousPosition_x, previousPosition_y

function FuBar:Plugin_StartDrag(plugin)
	local panel = plugin:GetPanel()
	assert(panel, plugin:GetTitle() .. ": You must be attached to a panel.")
	self:Panel_OnLeave()
	local index, side = panel:IndexOfPlugin(plugin)
	
	if index then
		if not panel:IsLocked() then
			if index < panel:GetNumPlugins(side) then
				local frame = panel:GetPlugin(index + 1, side).frame
				local x, y = frame:GetLeft(), frame:GetBottom()
				frame:ClearAllPoints()
				frame:SetPoint("BOTTOMLEFT", UIParent, "BOTTOMLEFT", x, y)
			end
			
			previousPosition_panel = panel
			previousPosition_index = index
			previousPosition_side = side
			previousPosition_x, previousPosition_y = self:GetScaledCursorPosition()
			
			FuBar_Panel:PreventUpdate()
			panel:RemovePlugin(index, side)
			plugin:GetFrame():StartMoving()
		end
	end
end

function FuBar:Plugin_StopDrag(plugin)
	plugin:GetFrame():StopMovingOrSizing()
	if previousPosition_panel and not previousPosition_panel:IsLocked() then
		local insertAt
		local panel = previousPosition_panel
		local side = previousPosition_side
		local x, y = self:GetScaledCursorPosition()
		local px, py = previousPosition_x, previousPosition_y

		for panelId = 1, self:GetNumPanels() do
			local p = self:GetPanel(panelId)
			local frame = p.frame
			local x1, x2, y1, y2 = frame:GetLeft(), frame:GetRight(), frame:GetBottom(), frame:GetTop()
			if x1 <= x and x <= x2 and y1 <= y and y <= y2 then
				panel = p
				break
			end
		end
		
		if panel:GetNumPlugins(side) == 0 then
			insertAt = 1
		else
			for i = 1, panel:GetNumPlugins(side) + 1 do
				local left, right
				local leftPlugin
				local rightPlugin
				
				leftPlugin = panel:GetPlugin(i - 1, side)
				rightPlugin = panel:GetPlugin(i, side)
				
				if side == "RIGHT" then
					leftPlugin, rightPlugin = rightPlugin, leftPlugin
				end
				left = leftPlugin and leftPlugin:GetFrame():GetCenter() or 0
				right = rightPlugin and rightPlugin:GetFrame():GetCenter() or GetScreenWidth()
				if left <= x and x <= right then
					insertAt = i
					break
				end
			end
		end
		if not insertAt then
			insertAt = panel:GetNumPlugins(side) + 1
		end
		
		panel:AddPlugin(plugin, insertAt, side)
		FuBar_Panel:StopPreventUpdate()
		previousPosition_panel:Update()
		panel:Update()
		previousPosition_panel = nil
		previousPosition_index = nil
		previousPosition_side = nil
		previousPosition_x = nil
		previousPosition_y = nil
		if math.abs(px - x) <= 5 and math.abs(py - y) <= 5 then
			return false
		else
			return true
		end
	end
end

function FuBar:Panel_OnEnter(plugin)
	local point = plugin
	if type(plugin) == "table" then
		point = plugin:GetPanel():GetAttachPoint()
	end
	if point == "TOP" then
		inTopPanel = true
		self:CancelScheduledEvent("FuBar_AutoHideTop")
	elseif point == "BOTTOM" then
		inBottomPanel = true
		self:CancelScheduledEvent("FuBar_AutoHideBottom")
	end
	for i = 1, self:GetNumPanels() do
		local panel = self:GetPanel(i)
		if panel:GetAttachPoint() == point then
			panel.frame:SetAlpha(1)
			panel.frame:SetFrameStrata("HIGH")
		end
	end
end

function FuBar:Panel_OnLeave(plugin)
	local point = plugin
	if type(plugin) == "table" then
		if not plugin:GetPanel() then
			return
		end
		point = plugin:GetPanel():GetAttachPoint()
	end
	if point == "TOP" then
		inTopPanel = false
		self:ScheduleEvent("FuBar_AutoHideTop", self.OnUpdate_AutoHideTop, 1, self)
	elseif point == "BOTTOM" then
		inBottomPanel = false
		self:ScheduleEvent("FuBar_AutoHideBottom", self.OnUpdate_AutoHideBottom, 1, self)
	end
end

function FuBar:OnProfileEnable(oldName, _, copyFrom)
	isChangingProfile = true
	if not next(FuBar.db.profile) then
		local def
		local profile = self:GetProfile()
		if string.find(profile, "^char/") or string.find(profile, "^class/") then
			local _,class = UnitClass("player")
			def = _G["FUBAR_DEFAULTS_" .. class] or FUBAR_DEFAULTS
		else
			def = FUBAR_DEFAULTS
		end
		if type(def) == "function" then
			def = def()
		end
		for k,v in pairs(def) do
			FuBar.db.profile[k] = v
		end
	end
	
	if not self.db.profile.loadOnDemand then
		self.db.profile.loadOnDemand = {}
	end
	
	if not self.db.profile.tooltip then
		self.db.profile.tooltip = {}
	end
	
	if not self.db.profile.panels then
		self.db.profile.panels = {}
	end
	
	CleanDB()
	
	doneSetupPlugins = false
	pluginsToSetup = {}
	for i,plugin in ipairs(plugins) do
		pluginsToSetup[plugin] = true
		local frame = plugin:GetFrame()
		frame:Hide()
		frame:ClearAllPoints()
		plugin:SetPanel(nil)
	end
	
	for i = self:GetNumPanels(), 1, -1 do
		self:GetPanel(i):del(true)
	end
	
	for i = table.getn(self.db.profile.panels), 1, -1 do
		if self.db.profile.panels[i] then
			FuBar_Panel:new()
		end
	end
	
	if self:GetNumPanels() == 0 then
		FuBar_Panel:new()
	end
	
	self:CheckResolution()
	
	for i = 1, GetNumAddOns() do
		local name, title, notes, enabled, loadable, reason, security = GetAddOnInfo(i)
		if IsAddOnLoadOnDemand(i) and enabled and loadable and not IsAddOnLoaded(i) then
			local good = false
			for _,dep in ipairs({GetAddOnDependencies(name)}) do
				if dep == "FuBar" then
					good = true
				end
			end
			if good then
				if not self.db.profile.loadOnDemand[name] then
					self:LoadPlugin(name)
				elseif not self.db.profile.loadOnDemand[name].disabled and (not self.db.profile.loadOnDemand[name].condition or CheckLoadCondition(self.db.profile.loadOnDemand[name].condition)) then
					self:LoadPlugin(name)
				else
					local exists = false
					for _,n in ipairs(toBeLoaded) do
						if n == name then
							exists = true
							break
						end
					end
					if not exists then
						table.insert(toBeLoaded, name)
					end
				end
			end
		end
	end
	
	for i,plugin in ipairs(plugins) do
		if plugin.IsDisabled and plugin:IsDisabled() and plugin:GetPanel() then
			plugin:GetPanel():RemovePlugin(plugin)
		end
	end
	
	local name = self:GetProfile()
	for _,plugin in ipairs(plugins) do
		if type(plugin.SetProfile) == "function" and not plugin.independentProfile then
			plugin:SetProfile(name, copyFrom)
		end
	end
	
	self:SetFontSize(self:GetFontSize())
	
	local function func()
		if not self:SetupPlugins() then
			self:ScheduleEvent(func, 0)
			return
		end
		self:Update()
	end
	self:ScheduleEvent(func, 0.01)
	
	isChangingProfile = false
end

