--[[
Name: LibJostle-3.0
Revision: $Rev: 52 $
Author(s): ckknight (ckknight@gmail.com)
Website: http://ckknight.wowinterface.com/
Documentation: http://www.wowace.com/addons/libjostle-3-0/
SVN: svn://svn.wowace.com/wow/libjostle-3-0/mainline/trunk
Description: A library to handle rearrangement of blizzards frames when bars are added to the sides of the screen.
License: LGPL v2.1
--]]

local MAJOR_VERSION = "LibJostle-3.0"
local MINOR_VERSION = tonumber(("$Revision: 52 $"):match("(%d+)")) + 90000

if not LibStub then error(MAJOR_VERSION .. " requires LibStub") end

local oldLib = LibStub:GetLibrary(MAJOR_VERSION, true)
local Jostle = LibStub:NewLibrary(MAJOR_VERSION, MINOR_VERSION)
if not Jostle then
	return
end

local blizzardFrames = {
	'PlayerFrame',
	'TargetFrame',
	'MinimapCluster',
	'PartyMemberFrame1',
	'TicketStatusFrame',
	'WorldStateAlwaysUpFrame',
	'MainMenuBar',
	'MultiBarRight',
	'CT_PlayerFrame_Drag',
	'CT_TargetFrame_Drag',
	'Gypsy_PlayerFrameCapsule',
	'Gypsy_TargetFrameCapsule',
	'ConsolidatedBuffs',
	'BuffFrame',
	'DEFAULT_CHAT_FRAME',
	'ChatFrame2',
	'GroupLootFrame1',
	'TutorialFrameParent',
	'FramerateLabel',
	'DurabilityFrame',
	'CastingBarFrame',
}
local blizzardFramesData = {}

local _G = _G

Jostle.hooks = oldLib and oldLib.hooks or {}
Jostle.topFrames = oldLib and oldLib.topFrames or {}
Jostle.bottomFrames = oldLib and oldLib.bottomFrames or {}
Jostle.topAdjust = oldLib and oldLib.topAdjust
Jostle.bottomAdjust = oldLib and oldLib.bottomAdjust
if Jostle.topAdjust == nil then
	Jostle.topAdjust = true
end
if Jostle.bottomAdjust == nil then
	Jostle.bottomAdjust = true
end

if not Jostle.hooks.WorldMapFrame_Hide then
	Jostle.hooks.WorldMapFrame_Hide = true
	hooksecurefunc(WorldMapFrame, "Hide", function()
		if Jostle.WorldMapFrame_Hide then
			Jostle:WorldMapFrame_Hide()
		end
	end)
end

if not Jostle.hooks.TicketStatusFrame_OnEvent then
	Jostle.hooks.TicketStatusFrame_OnEvent = true
	hooksecurefunc("TicketStatusFrame_OnEvent", function()
		if Jostle.TicketStatusFrame_OnEvent then
			Jostle:TicketStatusFrame_OnEvent()
		end
	end)
end

if not Jostle.hooks.FCF_UpdateDockPosition then
	Jostle.hooks.FCF_UpdateDockPosition = true
	hooksecurefunc("FCF_UpdateDockPosition", function()
		if Jostle.FCF_UpdateDockPosition then
			Jostle:FCF_UpdateDockPosition()
		end
	end)
end
	
if FCF_UpdateCombatLogPosition and not Jostle.hooks.FCF_UpdateCombatLogPosition then
	Jostle.hooks.FCF_UpdateCombatLogPosition = true
	hooksecurefunc("FCF_UpdateCombatLogPosition", function()
		if Jostle.FCF_UpdateCombatLogPosition then
			Jostle:FCF_UpdateCombatLogPosition()
		end
	end)
end

if not Jostle.hooks.UIParent_ManageFramePositions then
	Jostle.hooks.UIParent_ManageFramePositions = true
	hooksecurefunc("UIParent_ManageFramePositions", function()
		if Jostle.UIParent_ManageFramePositions then
			Jostle:UIParent_ManageFramePositions()
		end
	end)
end

if not Jostle.hooks.PlayerFrame_SequenceFinished then
	Jostle.hooks.PlayerFrame_SequenceFinished = true
	hooksecurefunc("PlayerFrame_SequenceFinished", function()
		if Jostle.PlayerFrame_SequenceFinished then
			Jostle:PlayerFrame_SequenceFinished()
		end
	end)
end

Jostle.frame = oldLib and oldLib.frame or CreateFrame("Frame")
local JostleFrame = Jostle.frame
local start = GetTime()
local nextTime = 0
local fullyInitted = false
JostleFrame:SetScript("OnUpdate", function(this, elapsed)
	local now = GetTime()
	if now - start >= 3 then
		fullyInitted = true
		for k,v in pairs(blizzardFramesData) do
			blizzardFramesData[k] = nil
		end
		this:SetScript("OnUpdate", function(this, elapsed)
			if GetTime() >= nextTime then
				Jostle:Refresh()
				this:Hide()
			end
		end)
	end
end)
function JostleFrame:Schedule(time)
	time = time or 0
	nextTime = GetTime() + time
	self:Show()
end
JostleFrame:UnregisterAllEvents()
JostleFrame:SetScript("OnEvent", function(this, event, ...)
	return Jostle[event](Jostle, ...)
end)
JostleFrame:RegisterEvent("PLAYER_AURAS_CHANGED")
JostleFrame:RegisterEvent("PLAYER_REGEN_DISABLED")
JostleFrame:RegisterEvent("PLAYER_REGEN_ENABLED")
JostleFrame:RegisterEvent("PLAYER_CONTROL_GAINED")

function Jostle:PLAYER_AURAS_CHANGED()
	JostleFrame:Schedule()
end

function Jostle:WorldMapFrame_Hide()
	JostleFrame:Schedule()
end

function Jostle:TicketStatusFrame_OnEvent()
	self:Refresh(TicketStatusFrame, ConsolidatedBuffs)
end

function Jostle:FCF_UpdateDockPosition()
	self:Refresh(DEFAULT_CHAT_FRAME)
end

function Jostle:FCF_UpdateCombatLogPosition()
	self:Refresh(ChatFrame2)
end

function Jostle:UIParent_ManageFramePositions()
	self:Refresh(GroupLootFrame1, TutorialFrameParent, FramerateLabel, DurabilityFrame)
end

function Jostle:PlayerFrame_SequenceFinished()
	self:Refresh(PlayerFrame)
end

function Jostle:GetScreenTop()
	local bottom = GetScreenHeight()
	for _,frame in ipairs(self.topFrames) do
		if frame.IsShown and frame:IsShown() 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.IsShown and frame:IsShown() 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)
	JostleFrame:Schedule()
	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)
	JostleFrame:Schedule()
	return true
end

function Jostle:Unregister(frame)
	for k,f in ipairs(self.topFrames) do
		if f == frame then
			table.remove(self.topFrames, k)
			JostleFrame:Schedule()
			return true
		end
	end
	for k,f in ipairs(self.bottomFrames) do
		if f == frame then
			table.remove(self.bottomFrames, k)
			JostleFrame:Schedule()
			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
		JostleFrame:Schedule()
	end
end

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

function Jostle:IsBottomAdjusting()
	return self.bottomAdjust
end

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

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

local tmp = {}
local queue = {}
local inCombat = false
function Jostle:ProcessQueue()
	if not inCombat and HasFullControl() then
		for k in pairs(queue) do
			self:Refresh(k)
			queue[k] = nil
		end
	end
end
function Jostle:PLAYER_CONTROL_GAINED()
	self:ProcessQueue()
end

function Jostle:PLAYER_REGEN_ENABLED()
	inCombat = false
	self:ProcessQueue()
end

function Jostle:PLAYER_REGEN_DISABLED()
	inCombat = true
end

local function isClose(alpha, bravo)
	return math.abs(alpha - bravo) < 0.1
end

function Jostle:Refresh(...)
	if not fullyInitted then
		return
	end
	
	local screenHeight = GetScreenHeight()
	local topOffset = self:IsTopAdjusting() and self:GetScreenTop() or screenHeight
	local bottomOffset = self:IsBottomAdjusting() and self:GetScreenBottom() or 0
	if topOffset ~= screenHeight or bottomOffset ~= 0 then
		JostleFrame:Schedule(10)
	end
	
	local frames
	if select('#', ...) >= 1 then
		for k in pairs(tmp) do
			tmp[k] = nil
		end
		for i = 1, select('#', ...) do
			tmp[i] = select(i, ...)
		end
		frames = tmp
	else
		frames = blizzardFrames
	end

	if inCombat or not HasFullControl() and not UnitHasVehicleUI("player") then
		for _,frame in ipairs(frames) do
			if type(frame) == "string" then
				frame = _G[frame]
			end
			if frame then
				queue[frame] = true
			end
		end
		return
	end
	
	local screenHeight = GetScreenHeight()
	for _,frame in ipairs(frames) do
		if type(frame) == "string" then
			frame = _G[frame]
		end

		local framescale = frame and frame.GetScale and frame:GetScale() or 1

		if frame and not blizzardFramesData[frame] and frame.GetTop and frame:GetCenter() and select(2, frame:GetCenter()) then
			if select(2, frame:GetCenter()) <= screenHeight / 2 or frame == MultiBarRight then
				blizzardFramesData[frame] = {y = frame:GetBottom(), top = false}
			else
				blizzardFramesData[frame] = {y = frame:GetTop() - screenHeight / framescale, top = true}
			end
			if frame == MinimapCluster then
				blizzardFramesData[frame].lastX = GetScreenWidth() - MinimapCluster:GetWidth()
				blizzardFramesData[frame].lastY = GetScreenHeight()
				blizzardFramesData[frame].lastScale = 1
			end
		end
	end
	
	for _,frame in ipairs(frames) do
		if type(frame) == "string" then
			frame = _G[frame]
		end

		local framescale = frame and frame.GetScale and frame:GetScale() or 1

		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 (select(2, frame:GetPoint(1)) ~= UIParent and select(2, 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 == MinimapCluster and select(3, frame:GetPoint(1)) ~= "TOPRIGHT" 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 not isClose(frameData.lastX, frame:GetLeft())) or not isClose(WorldFrame:GetHeight() * WorldFrame:GetScale(), UIParent:GetHeight() * UIParent:GetScale()))  then
				-- do nothing
			elseif frame == PlayerFrame or frame == MainMenuBar or frame == ConsolidatedBuffs or frame == CastingBarFrame or frame == TutorialFrameParent or frame == FramerateLabel or frame == DurabilityFrame or frame == WatchFrame or not (frameData.lastScale and frame.GetScale and frameData.lastScale == frame:GetScale()) or not (frameData.lastX and frameData.lastY and (not isClose(frameData.lastX, frame:GetLeft()) or not isClose(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 or frame == FramerateLabel then
						x = 0
						anchor = ""
					elseif frame:GetRight() / framescale <= width / 2 then
						x = frame:GetLeft() / framescale
						anchor = "LEFT"
					else
						x = frame:GetRight() - width / framescale
						anchor = "RIGHT"
					end
					local y = blizzardFramesData[frame].y
					local offset = 0
					if blizzardFramesData[frame].top then
						anchor = "TOP" .. anchor
						offset = ( topOffset - height ) / framescale
					else
						anchor = "BOTTOM" .. anchor
						offset = bottomOffset / framescale
					end
					if frame == MinimapCluster and not MinimapBorderTop:IsShown() then
						offset = offset + MinimapBorderTop:GetHeight() * 3/5
					elseif frame == ConsolidatedBuffs and TicketStatusFrame:IsShown() then
						offset = offset - TicketStatusFrame:GetHeight() * TicketStatusFrame:GetScale()
					elseif frame == DEFAULT_CHAT_FRAME then
						y = MainMenuBar:GetHeight() * MainMenuBar:GetScale() + 32
						if PetActionBarFrame:IsShown() or StanceBarFrame:IsShown() then
							offset = offset + StanceBarFrame:GetHeight() * StanceBarFrame:GetScale()
						end
						if MultiBarBottomLeft:IsShown() then
							offset = offset + MultiBarBottomLeft:GetHeight() * MultiBarBottomLeft:GetScale() - 21
						end
					elseif frame == ChatFrame2 then
						y = MainMenuBar:GetHeight() * MainMenuBar:GetScale() + 32
						if MultiBarBottomRight:IsShown() then
							offset = offset + MultiBarBottomRight:GetHeight() * MultiBarBottomRight:GetScale() - 21
						end
					elseif frame == CastingBarFrame then
						y = MainMenuBar:GetHeight() * MainMenuBar:GetScale() + 17
						if PetActionBarFrame:IsShown() or StanceBarFrame:IsShown() then
							offset = offset + StanceBarFrame:GetHeight() * StanceBarFrame:GetScale()
						end
						if MultiBarBottomLeft:IsShown() or MultiBarBottomRight:IsShown() then
							offset = offset + MultiBarBottomLeft:GetHeight() * MultiBarBottomLeft:GetScale()
						end
					elseif frame == GroupLootFrame1 or frame == TutorialFrameParent or frame == FramerateLabel then
						if MultiBarBottomLeft:IsShown() or MultiBarBottomRight:IsShown() then
							offset = offset + MultiBarBottomLeft:GetHeight() * MultiBarBottomLeft:GetScale()
						end
					elseif frame == DurabilityFrame or frame == WatchFrame then
						anchorFrame = MinimapCluster
						x = 0
						y = 0
						offset = 0
						if frame == WatchFrame and DurabilityFrame:IsShown() then
							y = y - DurabilityFrame:GetHeight() * DurabilityFrame:GetScale()
						end
						if frame == DurabilityFrame then
							x = -20
						end
						anchor = "TOPRIGHT"
						anchorAlt = "BOTTOMRIGHT"
						if MultiBarRight:IsShown() then
							x = x - MultiBarRight:GetWidth() * MultiBarRight:GetScale()
							if MultiBarLeft:IsShown() then
								x = x - MultiBarLeft:GetWidth() * MultiBarLeft:GetScale()
							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()
					blizzardFramesData[frame].lastScale = framescale
				end
			end
		end
	end
end

local function compat()
	local Jostle20 = {}
	function Jostle20:RegisterTop(...)
		Jostle:RegisterTop(...)
	end
	function Jostle20:RegisterBottom(...)
		Jostle:RegisterBottom(...)
	end
	function Jostle20:GetScreenTop(...)
		Jostle:GetScreenTop(...)
	end
	function Jostle20:GetScreenBottom(...)
		Jostle:GetScreenBottom(...)
	end
	function Jostle20:Unregister(...)
		Jostle:Unregister(...)
	end
	function Jostle20:IsTopAdjusting(...)
		Jostle:IsTopAdjusting(...)
	end
	function Jostle20:EnableTopAdjusting(...)
		Jostle:EnableTopAdjusting(...)
	end
	function Jostle20:DisableTopAdjusting(...)
		Jostle:DisableTopAdjusting(...)
	end
	function Jostle20:IsBottomAdjusting(...)
		Jostle:IsBottomAdjusting(...)
	end
	function Jostle20:EnableBottomAdjusting(...)
		Jostle:EnableBottomAdjusting(...)
	end
	function Jostle20:DisableBottomAdjusting(...)
		Jostle:DisableBottomAdjusting(...)
	end
	function Jostle20:Refresh(...)
		Jostle:Refresh(...)
	end
	local function activate(self, oldLib)
		Jostle20 = self
		if oldLib and oldLib.topFrames and oldLib.bottomFrames then
			for i,v in ipairs(oldLib.topFrames) do
				Jostle:RegisterTop(v)
			end
			for i,v in ipairs(oldLib.bottomFrames) do
				Jostle:RegisterBottom(v)
			end
		end
	end
	local function external(self, instance, major)
		if major == "AceEvent-2.0" then
			instance:embed(self)

			self:UnregisterAllEvents()
			self:CancelAllScheduledEvents()
		end
	end
	AceLibrary:Register(Jostle20, "Jostle-2.0", MINOR_VERSION*1000, activate, external)
	Jostle20 = AceLibrary("Jostle-2.0")
end
if AceLibrary then
	compat()
elseif Rock then
	function Jostle:OnLibraryLoad(major, version)
		if major == "AceLibrary" then
			compat()
		end
	end
end
