if not string.find(ace.version, "^1%.3") and not string.find(ace.version, "^1%.4") then
	message("|cffffff7fFuBar|r|cffffffff: Cannot load FuBar since your version of Ace is out of date. Your version: |r|cffffff7f" .. tostring(ace.version) .. "|r|cffffffff, expected version: |r|cffffff7f1.3|r|cffffffff or higher|r")
	return
end

local MAJOR_VERSION, MINOR_VERSION = "1.2", tonumber(string.sub("$Revision: 2181 $", 12, -3))

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

local compost = CompostLib:GetInstance('compost-1')
local metro = Metrognome:GetInstance('1')
local dewdrop = DewdropLib:GetInstance('1.0')
local tablet = TabletLib:GetInstance('1.0')
Gratuity:GetInstance('1'):RegisterTooltip(FuBarScanningTooltip)
local jostle = JostleLib:GetInstance('1.0')

FuBar = AceAddon:new({
	name		  = FuBarLocals.NAME,
	description   = FuBarLocals.DESCRIPTION,
	version	   = MAJOR_VERSION .. "." .. MINOR_VERSION,
	releaseDate   = string.sub("$Date: 2006-05-28 23:47:34 -1000 (Sun, 28 May 2006) $", 8, 17),
	aceCompatible = 103,
	author		= "ckknight",
	email		 = "ckknight@gmail.com",
	website	   = "http://ckknight.wowinterface.com/",
	category	  = "others",
	db			= AceDatabase:new("FuBarDB"),
	defaults	  = DEFAULT_OPTIONS,
	cmd		   = AceChatCmd:new(FuBarLocals.COMMANDS, FuBarLocals.CMD_OPTIONS),
	loc = FuBarLocals,
})
local FuBar = FuBar

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 tmp = private
private = nil
local private = tmp
tmp = nil
local plugins = {}

local tmp = PanelClass
PanelClass = nil
local PanelClass = tmp
tmp = nil

local basePluginFrame

local backgrounds = {["Interface\\AddOns\\FuBar\\background"] = "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.data.skin = bgFile
		PanelClass:SetBackground(bgFile)
	end
end

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

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

function FuBar:Reset()
	self:UnregisterEvent("PLAYER_LOGOUT")
	FuBarDB = {}
	for _,plugin in ipairs(plugins) do
		if type(plugin.db) == "table" and type(plugin.db._table) == "table" then
			plugin.db._table = nil
		elseif type(plugin.db) == "string" then
			setglobal(plugin.db, nil)
		end
	end
	ReloadUI()
end

function FuBar:GetPanel(panelId)
	return PanelClass._panels[panelId]
end

function FuBar:GetNumPanels()
	return table.getn(PanelClass._panels)
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.data.profilePlugin then
			return p, p:GetTitle()
		end
	end
end

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

function FuBar:IsAdjust()
	return self.data.adjust
end

function FuBar:ToggleAdjust(loud)
	self.data.adjust = not self.data.adjust
	if loud then
		self.cmd:status(self.loc.ARGUMENT_ADJUST, self.data.adjust and 1 or 0, self.loc.MAP_ONOFF)
	end
	self:UpdateJostleAdjustments()
	return self.data.adjust
end

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

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

function FuBar:UpdateJostleAdjustments()
	if not self.data.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.data.autohideTop
end

local autohideTopTime, autohideBottomTime

function FuBar:ToggleAutoHidingTop(loud)
	self.data.autohideTop = not self.data.autohideTop
	if loud then
		self.cmd:status(self.loc.ARGUMENT_AUTOHIDETOP, self.data.autohideTop and 1 or 0, self.loc.MAP_ONOFF)
	end
	self:UpdateJostleAdjustments()
	if not self.data.autohideTop then
		for i = 1, self:GetNumPanels() do
			if self:GetPanel(i):GetAttachPoint() == "TOP" then
				getglobal("FuBarFrame" .. i):SetAlpha(1)
			end
		end
	else
		metro:Start(self.name .. "AutoHideTop")
	end
	autohideTopTime = GetTime()
	return self.data.autohideTop
end

function FuBar:IsAutoHidingBottom()
	return self.data.autohideBottom
end

function FuBar:ToggleAutoHidingBottom(loud)
	self.data.autohideBottom = not self.data.autohideBottom
	if loud then
		self.cmd:status(self.loc.ARGUMENT_AUTOHIDEBOTTOM, self.data.autohideBottom and 1 or 0, self.loc.MAP_ONOFF)
	end
	self:UpdateJostleAdjustments()
	if not self.data.autohideBottom then
		for i = 1, self:GetNumPanels() do
			if self:GetPanel(i):GetAttachPoint() == "BOTTOM" then
				getglobal("FuBarFrame" .. i):SetAlpha(1)
			end
		end
	else
		metro:Start(self.name .. "AutoHideBottom")
	end
	autohideBottomTime = GetTime()
	return self.data.autohideBottom
end

function FuBar:GetFontSize()
	if not self.data then
		return 12
	end
	return self.data.fontSize or 12
end
	
function FuBar:SetFontSize(size)
	size = size or 12
	self.data.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.data then
		return 20
	end
	return self.data.leftSpacing or 20
end

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

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

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

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

function FuBar:SetThickness(size)
	self.data.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.data then
		return 5
	end
	return self.data.thickness or 5
end

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

function FuBar:SetTransparency(value)
	self.data.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 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 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.cmd:msg("Error loading LoadOnDemand plugin " .. addon)
		return
	elseif not success then
		FuBar.cmd:msg("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
					plugin._initialized = true
					ace:InitializeApp(plugin)
				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.cmd:msg("Error loading LoadOnDemand plugin " .. addon)
		return
	end
	return ret, plugin, dependencies
end

local toBeLoaded

function FuBar:Initialize()
	if not FuBarDB then
		FuBarDB = {}
	end
	
	if not FuBarDB.pluginDB then
		FuBarDB.pluginDB = {}
	end
	
	toBeLoaded = {}
	
	if not FuBarDB.profiles then
		FuBarDB.profiles = {}
	end
	
	if not FuBarDB.profiles[self.profilePath[2]] then
		FuBarDB.profiles[self.profilePath[2]] = {
		}
		
		local _,class = UnitClass("player")
		local def
		if self.profilePath[2] == "default" then
			def = FUBAR_DEFAULTS
		else
			def = getglobal("FUBAR_DEFAULTS_" .. class) or FUBAR_DEFAULTS
		end
		if def then
			local function inherit(t, defaults)
				for k,v in pairs(defaults) do
					if type(v) == "table" then
						if t[k] == nil then
							t[k] = {}
						end
						inherit(t[k], v)
					elseif t[k] == nil then
						t[k] = v
					end
				end
			end
			
			if type(def) == "function" then
				def = def()
			end
			if type(def) == "table" then
				inherit(FuBarDB.profiles[self.profilePath[2]], def)
			end
		end
		
		if FuBarDB.profiles[self.profilePath[2]].adjust == nil then
			FuBarDB.profiles[self.profilePath[2]].adjust = true
		end
	end
	
	BossPanel = nil
	BossPanelTooltip = nil
	BossPanelPlugin = nil
	BossPanelUtils = nil
	BossPanelLocals = nil
	DisableAddOn("BossPanel")
	
	self.data = FuBarDB.profiles[self.profilePath[2]]
	
	self.data.loadOnDemand = nil
	
	if self.data.noOverflow == false then
		self.data.overflow = true
	end
	self.data.noOverflow = nil
	
	if not FuBarDB.global then
		FuBarDB.global = {}
	end
	
	self.fullData = FuBarDB.global
		
	if not self.fullData.loadOnDemand then
		self.fullData.loadOnDemand = {}
	end
	
	if not self.data.tooltip then
		self.data.tooltip = {}
	end
	
	self:SetFontSize(self:GetFontSize())
	
	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.fullData.loadOnDemand[name] then
					self:LoadPlugin(name)
				elseif not self.fullData.loadOnDemand[name].disabled and (not self.fullData.loadOnDemand[name].condition or CheckLoadCondition(self.fullData.loadOnDemand[name].condition)) then
					self:LoadPlugin(name)
				else
					table.insert(toBeLoaded, name)
				end
			end
			compost:Reclaim(deps)
		end
	end
	
	self.menu = function(level, value)
		local panelId = PanelClass._selectedPanel or 1
		local panel = self:GetPanel(panelId)
		
		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 self.loc.MENU_CATEGORIES[category] then
					category = "others"
				end
				menuTitles[category] = true
			end
			for _,name in ipairs(toBeLoaded) do
				local category = self.fullData.loadOnDemand[name].category
				if not self.loc.MENU_CATEGORIES[category] then
					category = "others"
				end
				menuTitles[category] = true
			end
			local titles = compost:Acquire()
			for name in menuTitles do
				table.insert(titles, name)
			end
			compost:Reclaim(menuTitles)
			table.sort(titles)
			for _,category in ipairs(titles) do
				dewdrop:AddLine(
					'text', self.loc.MENU_CATEGORIES[category],
					'value', category,
					'hasArrow', true
				)
			end
			compost:Reclaim(titles)
			
			dewdrop:AddLine()
			
			dewdrop:AddLine(
				'text', self.loc.MENU_ATTACH,
				'hasArrow', true,
				'value', "attach"
			)
			
			dewdrop:AddLine(
				'text', self.loc.MENU_LOCK_PANEL,
				'arg1', panel,
				'func', "ToggleLocked",
				'checked', panel:IsLocked()
			)
			
			dewdrop:AddLine(
				'text', self.loc.MENU_REMOVE_PANEL,
				'arg1', panel,
				'func', "WarnDestroy",
				'disabled', panelId == 1 and not self:GetPanel(2),
				'closeWhenClicked', true
			)
			
			dewdrop:AddLine()
			
			dewdrop:AddLine(
				'text', self.loc.MENU_OVERFLOW_PLUGINS,
				'arg1', self,
				'func', "ToggleOverflowing",
				'checked', self:IsOverflowing()
			)
			
			dewdrop:AddLine(
				'text', self.loc.MENU_SHOW_NEW_PANEL,
				'arg1', PanelClass,
				'func', "Create"
			)
			
			dewdrop:AddLine(
				'text', self.loc.MENU_ADJUST_FRAMES,
				'arg1', self,
				'func', "ToggleAdjust",
				'checked', self:IsAdjust()
			)
			
			if panel:GetAttachPoint() ~= "NONE" then
				local hasTop = panel:GetAttachPoint() == "TOP"
				local hasBottom = not hasTop
				if not hasTop then
					for i = 1, self:GetNumPanels() do
						if not hasTop and self:GetPanel(i):GetAttachPoint() == "TOP" then
							hasTop = true
						end
					end
				else
					for i = 1, self:GetNumPanels() do
						if not hasBottom and self:GetPanel(i):GetAttachPoint() == "BOTTOM" then
							hasBottom = true
						end
					end
				end
				if hasTop ~= hasBottom then
					dewdrop:AddLine(
						'text', self.loc.MENU_AUTO_HIDE,
						'arg1', self,
						'func', hasTop and "ToggleAutoHidingTop" or "ToggleAutoHidingBottom",
						'checked', hasTop and self:IsAutoHidingTop() or self:IsAutoHidingBottom()
					)
				elseif panel:GetAttachPoint() == "TOP" then
					dewdrop:AddLine(
						'text', self.loc.MENU_AUTO_HIDE_TOP,
						'arg1', self,
						'func', "ToggleAutoHidingTop",
						'checked', self:IsAutoHidingTop()
					)
				else
					dewdrop:AddLine(
						'text', self.loc.MENU_AUTO_HIDE_BOTTOM,
						'arg1', self,
						'func', "ToggleAutoHidingBottom",
						'checked', self:IsAutoHidingBottom()
					)
				end
			end
			
			dewdrop:AddLine(
				'text', self.loc.MENU_TEXTURE,
				'hasArrow', true,
				'value', "texture"
			)
			
			dewdrop:AddLine(
				'text', self.loc.MENU_SPACING,
				'hasArrow', true,
				'value', "spacing"
			)
			
			dewdrop:AddLine(
				'text', self.loc.MENU_FONT_SIZE,
				'hasArrow', true,
				'value', "fontsize"
			)
			
			dewdrop:AddLine(
				'text', self.loc.MENU_TRANSPARENCY,
				'hasArrow', true,
				'hasSlider', true,
				'sliderFunc', function(value)
					self:SetTransparency(value)
				end,
				'sliderValue', self:GetTransparency()
			)
			
			dewdrop:AddLine(
				'text', self.loc.MENU_THICKNESS,
				'hasArrow', true,
				'hasSlider', true,
				'sliderFunc', function(value)
					self:SetThickness(value * 20)
					return format("%.0f", value * 20)
				end,
				'sliderValue', self:GetThickness() / 20,
				'sliderBottom', 0,
				'sliderTop', 20
			)
			
			local r, g, b = tablet:GetColor(FuBar.data.tooltip)
			local a = tablet:GetTransparency(FuBar.data.tooltip)
			dewdrop:AddLine(
				'text', self.loc.MENU_TOOLTIP_COLOR,
				'hasColorSwatch', true,
				'hasOpacity', true,
				'r', r,
				'g', g,
				'b', b,
				'opacity', a,
				'swatchFunc', function(r, g, b)
					tablet:SetColor(FuBar.data.tooltip, r, g, b)
				end,
				'opacityFunc', function(a)
					tablet:SetTransparency(FuBar.data.tooltip, a)
				end,
				'cancelFunc', function(r, g, b, a)
					tablet:SetColor(FuBar.data.tooltip, r, g, b)
					tablet:SetTransparency(FuBar.data.tooltip, a)
				end
			)
			
			dewdrop:AddLine(
				'text', self.loc.MENU_PROFILE,
				'hasArrow', true,
				'value', "profile"
			)
			
			dewdrop:AddLine()
			
			dewdrop:AddLine(
				'text', self.loc.MENU_ABOUT,
				'arg1', self.cmd,
				'func', "DisplayAddonInfo"
			)
			
			dewdrop:AddLine(
				'text', CLOSE,
				'arg1', dewdrop,
				'func', "Close"
			)
		elseif level == 2 then
			if value == "profile" then
				dewdrop:AddLine(
					'text', self.loc.MENU_DEFAULT_PROFILE,
					'arg1', AceCommands,
					'func', self.profilePath[2] ~= "default" and "UseProfileDefault",
					'checked', self.profilePath[2] == "default",
					'isRadio', true
				)
				
				dewdrop:AddLine(
					'text', ace.char.class,
					'arg1', AceCommands,
					'func', self.profilePath[2] ~= ace.char.class and "UseProfileClass",
					'arg2', ACE_CMD_PROFILE_ALL,
					'checked', self.profilePath[2] == ace.char.class,
					'isRadio', true
				)
				
				dewdrop:AddLine(
					'text', ace.char.id,
					'arg1', AceCommands,
					'func', self.profilePath[2] ~= ace.char.id and "UseProfileChar",
					'arg2', ACE_CMD_PROFILE_ALL,
					'checked', self.profilePath[2] == ace.char.id,
					'isRadio', true
				)
			elseif value == "spacing" then
				dewdrop:AddLine(
					'text', self.loc.MENU_LEFT_ALIGNED,
					'hasArrow', true,
					'hasSlider', true,
					'sliderFunc', function(value)
						self:SetLeftSpacing(value * 40)
						return format("%.0f", value * 40)
					end,
					'sliderTop', 40,
					'sliderBottom', 0,
					'sliderValue', self:GetLeftSpacing() / 40
				)
				dewdrop:AddLine(
					'text', self.loc.MENU_CENTER_ALIGNED,
					'hasArrow', true,
					'hasSlider', true,
					'sliderFunc', function(value)
						self:SetCenterSpacing(value * 40)
						return format("%.0f", value * 40)
					end,
					'sliderTop', 40,
					'sliderBottom', 0,
					'sliderValue', self:GetCenterSpacing() / 40
				)
				dewdrop:AddLine(
					'text', self.loc.MENU_RIGHT_ALIGNED,
					'hasArrow', true,
					'hasSlider', true,
					'sliderFunc', function(value)
						self:SetRightSpacing(value * 40)
						return format("%.0f", value * 40)
					end,
					'sliderTop', 40,
					'sliderBottom', 0,
					'sliderValue', self:GetRightSpacing() / 40
				)
			elseif value == "texture" then
				local t = compost:Acquire()
				for bgFile in pairs(backgrounds) do
					table.insert(t, bgFile)
				end
				table.sort(t, background_sort)
				for _,bgFile in ipairs(t) do
					dewdrop:AddLine(
						'text', backgrounds[bgFile],
						'func', not PanelClass:IsBackground(bgFile) and SetBackground,
						'arg1', bgFile,
						'checked', PanelClass:IsBackground(bgFile),
						'isRadio', true
					)
				end
				compost:Reclaim(t)
			elseif value == "fontsize" then
				dewdrop:AddLine(
					'text', self.loc.MENU_PANEL,
					'hasArrow', true,
					'hasSlider', true,
					'sliderFunc', function(value)
						value = floor(value * 17 + 7.5)
						self:SetFontSize(value)
						return format("%d", value)
					end,
					'sliderTop', "24",
					'sliderBottom', "7",
					'sliderValue', (self:GetFontSize() - 7) / 17
				)
				local value = tablet:GetFontSizePercent(FuBar.data.tooltip)
				if value < 1 then
					value = value - 0.5
				else
					value = value / 2
				end
				dewdrop:AddLine(
					'text', self.loc.MENU_TOOLTIP,
					'hasArrow', true,
					'hasSlider', true,
					'sliderFunc', function(value)
						if value < 0.5 then
							value = value + 0.5
						else
							value = value * 2
						end
						tablet:SetFontSizePercent(FuBar.data.tooltip, value)
						return format("%.0f%%", value * 100)
					end,
					'sliderTop', "200%",
					'sliderBottom', "50%",
					'sliderValue', value
				)
			elseif value == "attach" then
				dewdrop:AddLine(
					'text', self.loc.MENU_ATTACH_TO_TOP,
					'arg1', panel,
					'func', panel:GetAttachPoint() ~= "TOP" and "SetAttachPoint",
					'arg2', "TOP",
					'checked', panel:GetAttachPoint() == "TOP",
					'closeWhenClicked', true,
					'isRadio', true
				)
				
				dewdrop:AddLine(
					'text', self.loc.MENU_ATTACH_TO_BOTTOM,
					'arg1', panel,
					'func', panel:GetAttachPoint() ~= "BOTTOM" and "SetAttachPoint",
					'arg2', "BOTTOM",
					'checked', panel:GetAttachPoint() == "BOTTOM",
					'closeWhenClicked', true,
					'isRadio', true
				)
				
				dewdrop:AddLine(
					'text', self.loc.MENU_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 self.loc.MENU_CATEGORIES[category] then
						category = "others"
					end
					if category == value then
						dewdrop:AddLine(
							'text', plugin:GetTitle(),
							'arg1', plugin,
							'func', plugin:GetPanel() and "Hide" or "Show",
							'arg2', not plugin:GetPanel() and PanelClass._selectedPanel or 1,
							'checked', plugin:GetPanel()
						)
					end
				end
				for i,name in ipairs(toBeLoaded) do
					local i, name = i, name
					local lod = self.fullData.loadOnDemand[name]
					
					local category = lod.category
					if not self.loc.MENU_CATEGORIES[category] then
						category = "others"
					end
					if category == value then
						dewdrop:AddLine(
							'text', lod.title,
							'arg1', self,
							'func', "LoadPlugin",
							'arg2', name
						)
					end
				end
			end
		end
	end
	
	local old_ToggleDropDownMenu = ToggleDropDownMenu
	ToggleDropDownMenu = function(alpha, bravo, charlie, delta, echo, foxtrot)
		old_ToggleDropDownMenu(alpha, bravo, charlie, delta, echo, foxtrot)
		if alpha == 1 and delta and type(delta) == "table" and delta.plugin then
			DropDownList1.attachedTo = delta.plugin
		elseif alpha == 1 then
			DropDownList1.attachedTo = nil
		end
	end

	local old_DropDownList1_Hide = DropDownList1.Hide
	DropDownList1.Hide = function(self)
		old_DropDownList1_Hide(self)
		DropDownList1.attachedTo = nil
	end
	
	local tmp = AceCommands.UseProfileDefault
	AceCommands.UseProfileDefault = function(a)
		isChangingProfile = true
		previousProfile = self.profilePath[2]
		PanelClass._stopUpdates = true
		tmp(a)
		PanelClass._stopUpdates = false
		isChangingProfile = false
		self:OnAceProfileLoaded()
	end
	
	local tmp = AceCommands.UseProfileClass
	AceCommands.UseProfileClass = function(a, addon)
		isChangingProfile = true
		previousProfile = self.profilePath[2]
		PanelClass._stopUpdates = true
		tmp(a, addon)
		PanelClass._stopUpdates = false
		isChangingProfile = false
		self:OnAceProfileLoaded()
	end
	
	local tmp = AceCommands.UseProfileChar
	AceCommands.UseProfileChar = function(a, opt)
		isChangingProfile = true
		previousProfile = self.profilePath[2]
		PanelClass._stopUpdates = true
		tmp(a, opt)
		PanelClass._stopUpdates = false
		isChangingProfile = false
		self:OnAceProfileLoaded()
	end
end

function FuBar:LoadPlugin(name)
	local loaded, plugin, dependencies = LoadAddOnWrapper(name)
	if loaded then
		if type(plugin.SetLoadOnDemand) == "function" then
			plugin:SetLoadOnDemand(true)
		end
		self.fullData.loadOnDemand[name] = {
			title = plugin:GetTitle(),
			category = plugin:GetCategory(),
			disabled = nil,
			condition = type(plugin.GetLoadCondition) == "function" and plugin:GetLoadCondition() or nil,
		}
		plugin:Show(PanelClass._selectedPanel or false)
		if dependencies then
			for _,v in ipairs(dependencies) do
				if type(v.SetLoadOnDemand) == "function" then
					v:SetLoadOnDemand(true)
				end
				self.fullData.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.fullData.loadOnDemand[name].disabled = nil
	end
end

local alreadyEnabled = false

function FuBar:Enable()
	if alreadyEnabled then
		ReloadUI()
		return
	end
	alreadyEnabled = true
	if not self.data.panels then
		self.data.panels = {}
	end
	
	if not self.data.minimap then
		self.data.minimap = {}
	end
	
	for i = table.getn(self.data.panels), 1, -1 do
		if self.data.panels[i] then
			PanelClass:Create()
		end
	end
	
	self:CheckResolution()
	
	if self:GetNumPanels() == 0 then
		PanelClass:Create()
	end
	
	self:RegisterEvent("DISPLAY_SIZE_CHANGED", "OnDisplaySizeChanged")
	self:RegisterEvent("CVAR_UPDATE", "OnCVarUpdate")
	
	self:Hook("RestartGx")
	
	local function _OnUpdate(self)
		if not self:SetupPlugins() then
			self._OnUpdate = _OnUpdate
			metro:Start(self.name)
			return
		end
		self:Update()
	end
	self._OnUpdate = _OnUpdate
	
	metro:Register(self.name, self.OnUpdate, 0, self)
	metro:Start(self.name)
	metro:Register(self.name .. "RestartGx", self.OnUpdate_RestartGx, 0, self)
	metro:Register(self.name .. "UiScale", self.OnUpdate_UiScale, 0, self)
	metro:Register(self.name .. "AutoHideTop", self.OnUpdate_AutoHideTop, 1, self)
	metro:Start(self.name .. "AutoHideTop")
	metro:Register(self.name .. "AutoHideBottom", self.OnUpdate_AutoHideBottom, 1, self)
	metro:Start(self.name .. "AutoHideBottom")
	
	self:UpdateJostleAdjustments()
	
	if self.data.skin and backgrounds[self.data.skin] then
		PanelClass:SetBackground(self.data.skin)
	end
end
	
function FuBar:RestartGx()
	self:CallHook("RestartGx")
	metro:Start(self.name .. "RestartGx")
end

function FuBar:OnCVarUpdate()
	if arg1 == "USE_UISCALE" then
		metro:Start(self.name .. "UiScale")
	end
end

local stopResolutionCheck = false
function FuBar:CheckResolution()
	if stopResolutionCheck then
		return
	end
	local screenWidth = GetScreenWidth()
	if not self.data.screenWidth then
		self.data.screenWidth = screenWidth
	end
	
	if self.data.screenWidth ~= screenWidth then
		local ratio = screenWidth / self.data.screenWidth
		for i = 1, self:GetNumPanels() do
			local panel = self:GetPanel(i)
			panel.data.width = panel.data.width * ratio
			panel.data.x = panel.data.x * ratio
			panel:Update()
		end
		self.data.screenWidth = screenWidth
		self.data.lastUiScale = uiScale
		jostle:Refresh()
	end
end

function FuBar:OnUpdate()
	if self._OnUpdate then
		metro:Stop(self.name)
		local f = self._OnUpdate
		self._OnUpdate = nil
		f(self)
	end
end

function FuBar:OnUpdate_RestartGx()
	metro:Stop(self.name .. "RestartGx")
	self:Update()
end

local inTopPanel, inBottomPanel

function FuBar:OnUpdate_AutoHideTop()
	metro:Stop(self.name .. "AutoHideTop")
	if not inTopPanel and self:IsAutoHidingTop() then
		for i = 1, self:GetNumPanels() do
			if self:GetPanel(i):GetAttachPoint() == "TOP" then
				getglobal("FuBarFrame" .. i):SetAlpha(0)
			end
		end
	end
end

function FuBar:OnUpdate_AutoHideBottom()
	metro:Stop(self.name .. "AutoHideBottom")
	if not inBottomPanel and self:IsAutoHidingBottom() then
		for i = 1, self:GetNumPanels() do
			if self:GetPanel(i):GetAttachPoint() == "BOTTOM" then
				getglobal("FuBarFrame" .. i):SetAlpha(0)
			end
		end
	end
end

function FuBar:OnUpdate_UiScale()
	metro:Stop(self.name .. "UiScale")
	self:CheckResolution()
end

local doneSetupPlugins, pluginsToSetup

function FuBar:OnAceProfileLoaded()
	isChangingProfile = false
	if previousProfile == self.profilePath[2] then
		return
	end
	if not FuBarDB.profiles[self.profilePath[2]] then
		FuBarDB.profiles[self.profilePath[2]] = {}
		
		local _,class = UnitClass("player")
		local def
		if self.profilePath[2] == "default" then
			def = FUBAR_DEFAULTS
		else
			def = getglobal("FUBAR_DEFAULTS_" .. class) or FUBAR_DEFAULTS
		end
		if def then
			local function inherit(t, defaults)
				for k,v in pairs(defaults) do
					if type(v) == "table" then
						if t[k] == nil then
							t[k] = {}
						end
						inherit(t[k], v)
					elseif t[k] == nil then
						t[k] = v
					end
				end
			end
			
			if type(def) == "function" then
				def = def()
			end
			if type(def) == "table" then
				inherit(FuBarDB.profiles[self.profilePath[2]], def)
			end
		end
		
		if FuBarDB.profiles[self.profilePath[2]].adjust == nil then
			FuBarDB.profiles[self.profilePath[2]].adjust = true
		end
	end
	self.data = FuBarDB.profiles[self.profilePath[2]]
	
	if not self.data.tooltip then
		self.data.tooltip = {}
	end
	
	if not self.data.panels then
		self.data.panels = {}
	end
	
	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 = 1, self:GetNumPanels() do
		local panel = self:GetPanel(i)
		panel.data = nil
		panel.plugins = nil
		panel.frame:Hide()
	end
	PanelClass._panels = {}
	
	for i = table.getn(self.data.panels), 1, -1 do
		if self.data.panels[i] then
			PanelClass:Create()
		end
	end
	
	if self:GetNumPanels() == 0 then
		PanelClass:Create()
	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.fullData.loadOnDemand[name] then
					self:LoadPlugin(name)
				elseif not self.fullData.loadOnDemand[name].disabled and (not self.fullData.loadOnDemand[name].condition or CheckLoadCondition(self.fullData.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
	
	self:SetupPlugins()
	
	self:SetFontSize(self:GetFontSize())
	
	local function _OnUpdate(self)
		if not self:SetupPlugins() then
			self._OnUpdate = _OnUpdate
			metro:Start(self.name)
			return
		end
		self:Update()
	end
	self._OnUpdate = _OnUpdate
	metro:Start(self.name)
end

function FuBar:Disable()
	for _,plugin in ipairs(plugins) do
		plugin:DisableAddon()
	end
	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:CreateBasicPluginFrame(name, plugin)
	if plugin then
		plugin.cmd:msg("FuBar:CreateBasicPluginFrame(\"name\", plugin) is deprecated, use FuBarPlugin:GetInstance(\"version\"):CreateBasicPluginFrame(\"name\")")
		return getmetatable(plugin).__index.CreateBasicPluginFrame(plugin, name)
	else
		return FuBarPlugin:GetInstance("1.2"):CreateBasicPluginFrame(name)
	end
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.data.detached
		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
		
		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")
			end
		end
		
		pluginsToSetup = nil
	end
	return true
end

function FuBar:ShowPlugin(plugin, panelId)
	if doneSetupPlugins then
		if not panelId then
			if type(plugin.GetDefaultPosition) == "function" and plugin:GetDefaultPosition() == "MINIMAP" then
				plugin:Show(0)
				return
			end
			self.lastPanelId = (self.lastPanelId or 0) + 1
			if self.lastPanelId > self:GetNumPanels() then
				self.lastPanelId = 1
			end
			panelId = self.lastPanelId
		end
		self:GetPanel(panelId):AddPlugin(plugin, nil, type(plugin.GetDefaultPosition) == "function" and plugin:GetDefaultPosition() or "LEFT")
	else
		if not pluginsToSetup then
			pluginsToSetup = {}
		end
		pluginsToSetup[plugin] = true
	end
end

function FuBar:OnDisplaySizeChanged()
	stopResolutionCheck = true
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()
			
			PanelClass._stopUpdates = true
			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)
		PanelClass._stopUpdates = false
		previousPosition_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:Plugin_OnClick(plugin)
	if not plugin:IsDisabled() and type(plugin.OnClick) == "function" then
		plugin:OnClick()
	end
end

function FuBar:Plugin_OnDoubleClick(plugin)
	if not plugin:IsDisabled() and type(plugin.OnDoubleClick) == "function" then
		plugin:OnDoubleClick()
	end
end

function FuBar:Plugin_OnMouseDown(plugin, button)
	if not plugin:IsDisabled() and type(plugin.OnMouseDown) == "function" then
		plugin:OnMouseDown(button)
	end
end

function FuBar:Plugin_OnMouseUp(plugin, button)
	if not plugin:IsDisabled() and type(plugin.OnMouseUp) == "function" then
		plugin:OnMouseUp(button)
	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
		metro:Stop(self.name .. "AutoHideTop")
	elseif point == "BOTTOM" then
		inBottomPanel = true
		metro:Stop(self.name .. "AutoHideBottom")
	end
	for i = 1, self:GetNumPanels() do
		local panel = self:GetPanel(i)
		if panel:GetAttachPoint() == point then
			panel.frame:SetAlpha(1)
		end
	end
end

function FuBar:Panel_OnLeave(plugin)
	local point = plugin
	if type(plugin) == "table" then
		point = plugin:GetPanel():GetAttachPoint()
	end
	if point == "TOP" then
		inTopPanel = false
		metro:Start(self.name .. "AutoHideTop")
	elseif point == "BOTTOM" then
		inBottomPanel = false
		metro:Start(self.name .. "AutoHideBottom")
	end
end

FuBar:RegisterForLoad()
