--[[
Name: Jostle-2.0
Revision: $Rev: 9780 $
Author(s): ckknight (ckknight@gmail.com)
Website: http://ckknight.wowinterface.com/
Documentation: http://wiki.wowace.com/index.php/Jostle-2.0
SVN: http://svn.wowace.com/root/trunk/JostleLib/Jostle-2.0
Description: A library to handle rearrangement of blizzard's frames when bars are added to the sides of the screen.
Dependencies: AceLibrary, AceOO-2.0, AceEvent-2.0
]]

local MAJOR_VERSION = "Jostle-2.0"
local MINOR_VERSION = "$Revision: 9780 $"

if not AceLibrary then error(MAJOR_VERSION .. " requires AceLibrary") end
if not AceLibrary:IsNewVersion(MAJOR_VERSION, MINOR_VERSION) then return end

local Jostle = {}
local events = {}

local blizzardFramesData = {}

function events:PLAYER_AURAS_CHANGED()
	events:ScheduleEvent(Jostle.Refresh, 0, Jostle)
end

function events:AceEvent_FullyInitialized()
	Jostle.fullyInitialized = true
	for k,v in pairs(blizzardFramesData) do
		blizzardFramesData[k] = nil
	end
	events:ScheduleEvent(Jostle.Refresh, 0, Jostle)
end

function Jostle:ToggleWorldMap()
	if self.hooks.orig.ToggleWorldMap then
		self.hooks.orig.ToggleWorldMap()
	end
	self:Refresh()
end

function Jostle:TicketStatusFrame_OnEvent()
	if self.hooks.orig.TicketStatusFrame_OnEvent then
		self.hooks.orig.TicketStatusFrame_OnEvent()
	end
	self:Refresh(TicketStatusFrame, TemporaryEnchantFrame)
end

function Jostle:FCF_UpdateDockPosition()
	if self.hooks.orig.FCF_UpdateDockPosition then
		self.hooks.orig.FCF_UpdateDockPosition()
	end
	self:Refresh(DEFAULT_CHAT_FRAME)
end

function Jostle:FCF_UpdateCombatLogPosition()
	if self.hooks.orig.FCF_UpdateCombatLogPosition then
		self.hooks.orig.FCF_UpdateCombatLogPosition()
	end
	self:Refresh(ChatFrame2)
end

function Jostle:UIParent_ManageFramePositions()
	if self.hooks.orig.UIParent_ManageFramePositions then
		self.hooks.orig.UIParent_ManageFramePositions()
	end
	self:Refresh(GroupLootFrame1, TutorialFrameParent, FramerateLabel, QuestTimerFrame, DurabilityFrame)
end

function Jostle:ToggleWorldMap()
	if self.hooks.orig.ToggleWorldMap then
		self.hooks.orig.ToggleWorldMap()
	end
	self:Refresh()
end

function Jostle:WorldMapFrameCloseButton_OnClick()
	if self.hooks.orig.WorldMapFrameCloseButton_OnClick then
		self.hooks.orig.WorldMapFrameCloseButton_OnClick()
	end
	self:Refresh()
end

function Jostle:UIParent_ManageRightSideFrames()
	for _,frame in self.blizzardFrames do
		frame = getglobal(frame)
		local frameData = blizzardFramesData[frame]
		if frameData and not (frameData.lastX and frameData.lastY and (frameData.lastX ~= frame:GetLeft() or frameData.lastY ~= frame:GetTop())) then
			frameData.lastX = nil
			frameData.lastY = nil
		end
	end
	if self.hooks.orig.UIParent_ManageRightSideFrames then
		self.hooks.orig.UIParent_ManageRightSideFrames()
	end
	self:Refresh()
end

function Jostle:GetScreenTop()
	local bottom = GetScreenHeight()
	for _,frame in ipairs(self.topFrames) do
		if frame.IsVisible and frame:IsVisible() and frame.GetBottom and frame:GetBottom() and frame:GetBottom() < bottom then
			bottom = frame:GetBottom()
		end
	end
	return bottom
end

function Jostle:GetScreenBottom()
	local top = 0
	for _,frame in ipairs(self.bottomFrames) do
		if frame.IsVisible and frame:IsVisible() and frame.GetTop and frame:GetTop() and frame:GetTop() > top then
			top = frame:GetTop()
		end
	end
	return top
end

function Jostle:RegisterTop(frame)
	for k,f in ipairs(self.bottomFrames) do
		if f == frame then
			table.remove(self.bottomFrames, k)
			break
		end
	end
	for _,f in ipairs(self.topFrames) do
		if f == frame then
			return
		end
	end
	table.insert(self.topFrames, frame)
	self:Refresh()
	return true
end

function Jostle:RegisterBottom(frame)
	for k,f in ipairs(self.topFrames) do
		if f == frame then
			table.remove(self.topFrames, k)
			break
		end
	end
	for _,f in ipairs(self.bottomFrames) do
		if f == frame then
			return
		end
	end
	table.insert(self.bottomFrames, frame)
	self:Refresh()
	return true
end

function Jostle:Unregister(frame)
	for k,f in ipairs(self.topFrames) do
		if f == frame then
			table.remove(self.topFrames, k)
			self:Refresh()
			return true
		end
	end
	for k,f in ipairs(self.bottomFrames) do
		if f == frame then
			table.remove(self.bottomFrames, k)
			self:Refresh()
			return true
		end
	end
end

function Jostle:IsTopAdjusting()
	return self.topAdjust
end

function Jostle:EnableTopAdjusting()
	if not self.topAdjust then
		self.topAdjust = not self.topAdjust
		self:Refresh()
	end
end

function Jostle:DisableTopAdjusting()
	if self.topAdjust then
		self.topAdjust = not self.topAdjust
		self:Refresh()
	end
end

function Jostle:IsBottomAdjusting()
	return self.bottomAdjust
end

function Jostle:EnableBottomAdjusting()
	if not self.bottomAdjust then
		self.bottomAdjust = not self.bottomAdjust
		self:Refresh()
	end
end

function Jostle:DisableBottomAdjusting()
	if self.bottomAdjust then
		self.bottomAdjust = not self.bottomAdjust
		self:Refresh()
	end
end

local function getsecond(_, value)
	return value
end

local tmp = {}
function Jostle:Refresh(f1, f2, f3, f4, f5, f6, f7, f8)
	if not self.fullyInitialized then
		return
	end
	local frames
	if f1 then
		for k in pairs(tmp) do
			tmp[k] = nil
		end
		tmp.n = 0
		table.insert(tmp, f1)
		table.insert(tmp, f2)
		table.insert(tmp, f3)
		table.insert(tmp, f4)
		table.insert(tmp, f5)
		table.insert(tmp, f6)
		table.insert(tmp, f7)
		table.insert(tmp, f8)
		frames = tmp
	else
		frames = self.blizzardFrames
	end
	local screenHeight = GetScreenHeight()
	for _,frame in ipairs(frames) do
		if type(frame) == "string" then
			frame = getglobal(frame)
		end
		if frame and not blizzardFramesData[frame] and frame.GetTop and frame:GetCenter() and getsecond(frame:GetCenter()) then
			if getsecond(frame:GetCenter()) <= screenHeight / 2 or frame == MultiBarRight then
				blizzardFramesData[frame] = {y = frame:GetBottom(), top = false}
			else
				blizzardFramesData[frame] = {y = frame:GetTop() - screenHeight, top = true}
			end
		end
	end
	
	local topOffset = self:IsTopAdjusting() and self:GetScreenTop() or GetScreenHeight()
	local bottomOffset = self:IsBottomAdjusting() and self:GetScreenBottom() or 0
	
	for _,frame in ipairs(frames) do
		if type(frame) == "string" then
			frame = getglobal(frame)
		end
		if ((frame and frame.IsUserPlaced and not frame:IsUserPlaced()) or ((frame == DEFAULT_CHAT_FRAME or frame == ChatFrame2) and SIMPLE_CHAT == "1") or frame == FramerateLabel) and (frame ~= ChatFrame2 or SIMPLE_CHAT == "1") then
			local frameData = blizzardFramesData[frame]
			if (getsecond(frame:GetPoint(1)) ~= UIParent and getsecond(frame:GetPoint(1)) ~= WorldFrame) then
				-- do nothing
			elseif frame == PlayerFrame and (CT_PlayerFrame_Drag or Gypsy_PlayerFrameCapsule) then
				-- do nothing
			elseif frame == TargetFrame and (CT_TargetFrame_Drag or Gypsy_TargetFrameCapsule) then
				-- do nothing
			elseif frame == PartyMemberFrame1 and (CT_MovableParty1_Drag or Gypsy_PartyFrameCapsule) then
				-- do nothing
			elseif frame == MainMenuBar and Gypsy_HotBarCapsule then
				-- do nothing
			elseif frame == DurabilityFrame and DurabilityFrame:IsShown() and (DurabilityFrame:GetLeft() > GetScreenWidth() or DurabilityFrame:GetRight() < 0 or DurabilityFrame:GetBottom() > GetScreenHeight() or DurabilityFrame:GetTop() < 0) then
				DurabilityFrame:Hide()
			elseif frame == FramerateLabel and ((frameData.lastX and frameData.lastX ~= frame:GetLeft()) or WorldFrame:GetHeight() ~= UIParent:GetHeight())  then
				-- do nothing
			elseif frame == TemporaryEnchantFrame or frame == CastingBarFrame or frame == TutorialFrameParent or frame == FramerateLabel or frame == QuestTimerFrame or frame == DurabilityFrame or frame == QuestWatchFrame or not (frameData.lastX and frameData.lastY and (frameData.lastX ~= frame:GetLeft() or frameData.lastY ~= frame:GetTop())) then
				local anchor
				local anchorAlt
				local width, height = GetScreenWidth(), GetScreenHeight()
				local x
				if frame:GetRight() and frame:GetLeft() then
					local anchorFrame = UIParent
					if frame == MainMenuBar or frame == GroupLootFrame1 then
						x = 0
						anchor = ""
					elseif frame:GetRight() <= width / 2 then
						x = frame:GetLeft()
						anchor = "LEFT"
					else
						x = frame:GetRight() - width
						anchor = "RIGHT"
					end
					local y = blizzardFramesData[frame].y
					local offset = 0
					if blizzardFramesData[frame].top then
						anchor = "TOP" .. anchor
						offset = topOffset - screenHeight
					else
						anchor = "BOTTOM" .. anchor
						offset = bottomOffset
					end
					if frame == MinimapCluster and not MinimapBorderTop:IsShown() then
						offset = offset + MinimapBorderTop:GetHeight() * 3/5
					elseif frame == TemporaryEnchantFrame and TicketStatusFrame:IsVisible() then
						offset = offset - TicketStatusFrame:GetHeight()
					elseif frame == DEFAULT_CHAT_FRAME then
						y = MainMenuBar:GetHeight() + 32
						if PetActionBarFrame:IsVisible() or ShapeshiftBarFrame:IsVisible() then
							offset = offset + ShapeshiftBarFrame:GetHeight()
						end
						if MultiBarBottomLeft:IsVisible() then
							offset = offset + MultiBarBottomLeft:GetHeight() - 21
						end
					elseif frame == ChatFrame2 then
						y = MainMenuBar:GetHeight() + 32
						if MultiBarBottomRight:IsVisible() then
							offset = offset + MultiBarBottomRight:GetHeight() - 21
						end
					elseif frame == CastingBarFrame then
						y = MainMenuBar:GetHeight() + 17
						if PetActionBarFrame:IsVisible() or ShapeshiftBarFrame:IsVisible() then
							offset = offset + ShapeshiftBarFrame:GetHeight()
						end
						if MultiBarBottomLeft:IsVisible() or MultiBarBottomRight:IsVisible() then
							offset = offset + MultiBarBottomLeft:GetHeight()
						end
					elseif frame == GroupLootFrame1 or frame == TutorialFrameParent or frame == FramerateLabel then
						if MultiBarBottomLeft:IsVisible() or MultiBarBottomRight:IsVisible() then
							offset = offset + MultiBarBottomLeft:GetHeight()
						end
					elseif frame == QuestTimerFrame or frame == DurabilityFrame or frame == QuestWatchFrame then
						anchorFrame = MinimapCluster
						x = 0
						y = 0
						offset = 0
						if frame ~= QuestTimerFrame and QuestTimerFrame:IsVisible() then
							y = y - QuestTimerFrame:GetHeight()
						end
						if frame == QuestWatchFrame and DurabilityFrame:IsVisible() then
							y = y - DurabilityFrame:GetHeight()
						end
						if frame == DurabilityFrame then
							x = -20
						end
						anchor = "TOPRIGHT"
						anchorAlt = "BOTTOMRIGHT"
						if MultiBarRight:IsVisible() then
							x = x - MultiBarRight:GetWidth()
							if MultiBarLeft:IsVisible() then
								x = x - MultiBarLeft:GetWidth()
							end
						end
					end
					if frame == FramerateLabel then
						anchorFrame = WorldFrame
					end
					frame:ClearAllPoints()
					frame:SetPoint(anchor, anchorFrame, anchorAlt or anchor, x, y + offset)
					blizzardFramesData[frame].lastX = frame:GetLeft()
					blizzardFramesData[frame].lastY = frame:GetTop()
				end
			end
		end
	end
end

local function activate(self, oldLib, oldDeactivate)
	Jostle = self
	if oldLib then
		self.hooks = oldLib.hooks
		self.topFrames = oldLib.topFrames
		self.bottomFrames = oldLib.bottomFrames
		self.blizzardFrames = oldLib.blizzardFrames
		self.topAdjust = oldLib.topAdjust
		self.bottomAdjust = oldLib.bottomAdjust
		for name, method in pairs(self.hooks) do
			if type(method) == "function" then
				self.hooks[name] = nil
			end
		end
		for k in pairs(self.blizzardFrames) do
			self.blizzardFrames[k] = nil
		end
	else
		self.hooks = {
			orig = {}
		}
		self.blizzardFrames = {}
		self.topFrames = {}
		self.bottomFrames = {}
		self.topAdjust = true
		self.bottomAdjust = true
	end
	
	if not self.hooks.orig.ToggleWorldMap then
		self.hooks.orig.ToggleWorldMap = ToggleWorldMap
		self.hooks.ToggleWorldMap = self.ToggleWorldMap
		self.ToggleWorldMap = nil
		function ToggleWorldMap()
			if self.hooks.ToggleWorldMap then
				return self.hooks.ToggleWorldMap(self)
			else
				return self.hooks.orig.ToggleWorldMap()
			end
		end
	end
	
	if not self.hooks.orig.WorldMapFrameCloseButton_OnClick then
		self.hooks.orig.WorldMapFrameCloseButton_OnClick = WorldMapFrameCloseButton:GetScript("OnClick")
		self.hooks.WorldMapFrameCloseButton_OnClick = self.WorldMapFrameCloseButton_OnClick
		self.WorldMapFrameCloseButton_OnClick = nil
		WorldMapFrameCloseButton:SetScript("OnClick", function()
			if self.hooks.WorldMapFrameCloseButton_OnClick then
				return self.hooks.WorldMapFrameCloseButton_OnClick(self)
			else
				return self.hooks.orig.WorldMapFrameCloseButton_OnClick()
			end
		end)
	end
	
	if not self.hooks.orig.TicketStatusFrame_OnEvent then
		self.hooks.orig.TicketStatusFrame_OnEvent = TicketStatusFrame_OnEvent
		self.hooks.TicketStatusFrame_OnEvent = self.TicketStatusFrame_OnEvent
		self.TicketStatusFrame_OnEvent = nil
		function TicketStatusFrame_OnEvent()
			if self.hooks.TicketStatusFrame_OnEvent then
				return self.hooks.TicketStatusFrame_OnEvent(self)
			else
				return self.hooks.orig.TicketStatusFrame_OnEvent()
			end
		end
	end
	
	if not self.hooks.orig.FCF_UpdateDockPosition then
		self.hooks.orig.FCF_UpdateDockPosition = FCF_UpdateDockPosition
		self.hooks.FCF_UpdateDockPosition = self.FCF_UpdateDockPosition
		self.FCF_UpdateDockPosition = nil
		function FCF_UpdateDockPosition()
			if self.hooks.FCF_UpdateDockPosition then
				return self.hooks.FCF_UpdateDockPosition(self)
			else
				return self.hooks.orig.FCF_UpdateDockPosition()
			end
		end
	end
	
	if not self.hooks.orig.FCF_UpdateCombatLogPosition then
		self.hooks.orig.FCF_UpdateCombatLogPosition = FCF_UpdateCombatLogPosition
		self.hooks.FCF_UpdateCombatLogPosition = self.FCF_UpdateCombatLogPosition
		self.FCF_UpdateCombatLogPosition = nil
		function FCF_UpdateCombatLogPosition()
			if self.hooks.FCF_UpdateCombatLogPosition then
				return self.hooks.FCF_UpdateCombatLogPosition(self)
			else
				return self.hooks.orig.FCF_UpdateCombatLogPosition()
			end
		end
	end
	
	if not self.hooks.orig.UIParent_ManageFramePositions then
		self.hooks.orig.UIParent_ManageFramePositions = UIParent_ManageFramePositions
		self.hooks.UIParent_ManageFramePositions = self.UIParent_ManageFramePositions
		self.UIParent_ManageFramePositions = nil
		function UIParent_ManageFramePositions()
			if self.hooks.UIParent_ManageFramePositions then
				return self.hooks.UIParent_ManageFramePositions(self)
			else
				return self.hooks.orig.UIParent_ManageFramePositions()
			end
		end
	end
	
	if not self.hooks.orig.UIParent_ManageRightSideFrames then
		self.hooks.orig.UIParent_ManageRightSideFrames = UIParent_ManageRightSideFrames
		self.hooks.UIParent_ManageRightSideFrames = self.UIParent_ManageRightSideFrames
		self.UIParent_ManageRightSideFrames = nil
		function UIParent_ManageRightSideFrames()
			if self.hooks.UIParent_ManageRightSideFrames then
				return self.hooks.UIParent_ManageRightSideFrames(self)
			else
				return self.hooks.orig.UIParent_ManageRightSideFrames()
			end
		end
	end
	
	if not self.hooks.orig.ToggleWorldMap then
		self.hooks.orig.ToggleWorldMap = ToggleWorldMap
		self.hooks.ToggleWorldMap = self.ToggleWorldMap
		self.ToggleWorldMap = nil
		function ToggleWorldMap()
			if self.hooks.ToggleWorldMap then
				return self.hooks.ToggleWorldMap(self)
			else
				return self.hooks.orig.ToggleWorldMap()
			end
		end
	end
	
	table.insert(self.blizzardFrames, 'PlayerFrame')
	table.insert(self.blizzardFrames, 'TargetFrame')
	table.insert(self.blizzardFrames, 'MinimapCluster')
	table.insert(self.blizzardFrames, 'PartyMemberFrame1')
	table.insert(self.blizzardFrames, 'TicketStatusFrame')
	table.insert(self.blizzardFrames, 'WorldStateAlwaysUpFrame')
	table.insert(self.blizzardFrames, 'MainMenuBar')
	table.insert(self.blizzardFrames, 'MultiBarRight')
	table.insert(self.blizzardFrames, 'CT_PlayerFrame_Drag')
	table.insert(self.blizzardFrames, 'CT_TargetFrame_Drag')
	table.insert(self.blizzardFrames, 'Gypsy_PlayerFrameCapsule')
	table.insert(self.blizzardFrames, 'Gypsy_TargetFrameCapsule')
	table.insert(self.blizzardFrames, 'TemporaryEnchantFrame')
	table.insert(self.blizzardFrames, 'DEFAULT_CHAT_FRAME')
	table.insert(self.blizzardFrames, 'ChatFrame2')
	table.insert(self.blizzardFrames, 'GroupLootFrame1')
	table.insert(self.blizzardFrames, 'TutorialFrameParent')
	table.insert(self.blizzardFrames, 'FramerateLabel')
	table.insert(self.blizzardFrames, 'QuestTimerFrame')
	table.insert(self.blizzardFrames, 'DurabilityFrame')
	table.insert(self.blizzardFrames, 'CastingBarFrame')
	
	if oldDeactivate then
		oldDeactivate(oldLib)
	end
end

local function external(self, major, instance)
	if major == "AceEvent-2.0" then
		instance:embed(events)
		
		events:UnregisterAllEvents()
		events:RegisterEvent("PLAYER_AURAS_CHANGED")
		if instance:IsFullyInitialized() then
			self.fullyInitialized = true
			events:ScheduleEvent(self.Refresh, 0, self)
		else
			events:RegisterEvent("AceEvent_FullyInitialized")
		end
	end
end

AceLibrary:Register(Jostle, MAJOR_VERSION, MINOR_VERSION, activate, nil, external)
