--=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=--
--|	GypsyMod Library - Gypsy_Utility.lua
--|	Utility functions
--|	Respect to Dhargo, creator of Flexbar, for the meta table usage
--=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=--

--=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=--
--|	Summary & Explanation
--=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=--

-- Isolated file version
local version = 1.05;

--=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=--
--|	Version History
--|	1.00 - Initial library with function hooking methods
--|	1.01 - Moved Gypsy_Debug:Print to this library.
--|	1.02 - Added GetOppositePoint method.
--|	1.03 - Added series of point management & placement functions.
--| 1.04 - Fixed unhook to properly remove function record
--|	1.05 - Added cursor positioning
--=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=--

-- Load only if the library has not been loaded previously, or if previously loaded versions are older
if (not Gypsy_Utility or not Gypsy_Utility.version or Gypsy_Utility.version < version) then
	Gypsy_Utility = {};
	Gypsy_Utility.version = version;
	Gypsy_Utility.hooked = {};

	--=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=--
	--|	Gypsy_Utility:New
	--|	Used to make this library available for use locally from script files. 
	--|		Usage: local utility = Gypsy_Utility:New();
	--=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=--
	
	function Gypsy_Utility:New ()			
		local object = {};
		setmetatable(object, self);
		self.__index = self;
		return object;
	end
	
	--=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=--
	--|	Gypsy_Utility:Print
	--|	A basic print function, will print a message to any given chat frame, or the
	--|	errors frame. Accepts RGB values, and an alpha value for the errors frame.
	--=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=--
	
	function Gypsy_Utility:Print (msg, out, r, g, b, a)
		if (not msg) then	msg = "nil";			end
		if (not out) then	out = 1;				end
		if (not r) then		r = 1.0;				end
		if (not g) then		g = 1.0;				end
		if (not b) then		b = 1.0;				end
		if (not a) then		a = 1.0;				end
	
		if (out == "e" or out == "err" or out == "error") then
			UIErrorsFrame:AddMessage(msg, r, g, b, a, UIERRORS_HOLD_TIME);
		else
			local frame = getglobal("ChatFrame"..out);
			if (frame) then
				frame:AddMessage(msg, r, g, b);
			else
				DEFAULT_CHAT_FRAME:AddMessage(msg, r, g, b);
			end
		end
	end

	--=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=--
	--|	Gypsy_Utility:Hook
	--|	Hooks a new function into an existing one, either before or after. Input for mode
	--|	should be "before" or "after". Also saves original function for later un-hooking.
	--=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=--
	
	function Gypsy_Utility:Hook (mode, original, new)
		if (not mode) then
			mode = "after";
		end
		
		if (self.hooked[original]) then
			return;
		end

		self.hooked[original] = getglobal(original);

		if (mode == "before") then
			setglobal(original, function (...)
				new(unpack(arg));
				self.hooked[original](unpack(arg));
			end);
		elseif (mode == "after") then
			setglobal(original, function (...)
				self.hooked[original](unpack(arg));
				new(unpack(arg));
			end);
		elseif (mode == "replace") then
			setglobal(original, function (...)
				new(unpack(arg));
			end);
		end
	end
	
	--=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=--
	--|	Gypsy_Utility:UnHook
	--|	Unhooks a previously hooked function.
	--=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=--
	
	function Gypsy_Utility:UnHook (original)
		if (self.hooked[original]) then
			setglobal(original, self.hooked[original]);
			self.hooked[original] = nil;
		end
	end
	
	--=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=--
	--|	Gypsy_Utility:GetQuadrant
	--|	Calculates what quarter of the screen a frame is in and returns it in a format
	--|	similar to an anchor point.
	--=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=--
	
	function Gypsy_Utility:GetQuadrant (frame)
		if (this and not frame) then
			frame = this;
		elseif (not frame) then
			return nil;
		end
		
		local quadrant;
		
		local frameX, frameY = frame:GetCenter();
	
		local screenX, screenY = UIParent:GetCenter();
		
		if (frameX > screenX) then
			quadrant = "RIGHT";
		else
			quadrant = "LEFT";
		end
		
		if (frameY > screenY) then
			quadrant = "TOP"..quadrant;
		else
			quadrant = "BOTTOM"..quadrant;
		end
		
		return quadrant;
	end
	
	--=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=--
	--|	Gypsy_Utility:GetHalf
	--|	Returns what half of the screen a frame is on, vertical or horizontally.
	--=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=--
	
	function Gypsy_Utility:GetHalf (frame, orientation)
		if (this and not frame) then
			frame = this;
		elseif (not frame) then
			return nil;
		end
		
		local frameX, frameY = frame:GetCenter();
		
		local screenX, screenY = UIParent:GetCenter();
		
		if (orientation == "HORI") then
			if (frameX > screenX) then
				return "RIGHT";
			else
				return "LEFT";
			end
		else
			if (frameY > screenY) then
				return "TOP";
			else
				return "BOTTOM";
			end
		end
	end

	--=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=--
	--|	Gypsy_Utility:GetOppositePoint
	--|	Returns the opposite of the supplied point. BOTTOMLEFT returns TOPRIGHT, RIGHT
	--|	returns LEFT, BOTTOM returns TOP, CENTER returns CENTER, etc.
	--=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=--
	
	function Gypsy_Utility:GetOppositePoint (point)
		if (not point or point == "CENTER") then
			point = "CENTER";
		else
			if (strfind(point, "TOP")) then
				point = gsub(point, "TOP", "BOTTOM");
			else
				point = gsub(point, "BOTTOM", "TOP");
			end

			if (strfind(point, "LEFT")) then
				point = gsub(point, "LEFT", "RIGHT");
			else
				point = gsub(point, "RIGHT", "LEFT");
			end
		end
		
		return point;
	end

	--=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=--
	--|	Gypsy_Utility:GetAnchors
	--|	Returns auto-positioned anchoring information based off of a frame's location on
	--|	screen.
	--=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=--
	
	function Gypsy_Utility:GetAnchors (frame, directional, orientation)
		if (this and not frame) then
			frame = this;
		elseif (not frame) then
			return nil;
		end
		
		local point;
			
		if (directional) then
			point = self:GetHalf(frame, orientation);
		else
			point = self:GetQuadrant(frame);			
		end
		
		return point, frame:GetName(), self:GetOppositePoint(point);
	end
	
	--=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=--
	--|	Gypsy_Utility:PositionTooltip
	--|	Positions any tooltip in relation to where an object is on the screen.
	--=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=--
	
	function Gypsy_Utility:PositionTooltip (frame)
		if (this and not frame) then
			frame = this;
		elseif (not frame) then
			return nil;
		end
		
		GameTooltip:SetOwner(frame);
		
		GameTooltip:ClearAllPoints();
		GameTooltip:SetPoint(self:GetAnchors(frame));
	end
	
	--=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=--
	--|	Gypsy_Utility:GetCursorOffsets
	--|	Returns offsets for positioning an object relative to the cursor's location such
	--|	that the object always remains on-screen and optimally placed.
	--=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=--
	
	function Gypsy_Utility:GetCursorOffsets (frame)
		if (this and not frame) then
			frame = this;
		elseif (not frame) then
			return nil;
		end
		
		local screenX, screenY = UIParent:GetCenter();
		local offsetX, offsetY = GetCursorPosition();
		offsetX = offsetX / UIParent:GetScale();
		offsetY = offsetY / UIParent:GetScale();

		if (screenX < offsetX) then
			offsetX = offsetX - frame:GetWidth();
		end
		
		if (screenY < offsetY) then
			offsetY = offsetY - frame:GetHeight();
		end
		
		return offsetX, offsetY;
	end
	
	--=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=--
	--|	Gypsy_Utility:PositionDropDown
	--|	Positions any drop-down menu in relation to where an object is on the screen.
	--=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=--
	
	function Gypsy_Utility:PositionDropDown (frame)
		if (this and not frame) then
			frame = this;
		elseif (not frame) then
			return nil;
		end
		
		if (frame == "cursor") then
			DropDownList1:ClearAllPoints();
			DropDownList1:SetPoint("BOTTOMLEFT", "UIParent", "BOTTOMLEFT", self:GetCursorOffsets(DropDownList1));
		else
			DropDownList1:ClearAllPoints();
			DropDownList1:SetPoint(self:GetAnchors(frame));
		end
		
		if (DropDownList2:IsVisible()) then
			DropDownList2:ClearAllPoints();
			DropDownList2:SetPoint(self:GetAnchors(DropDownList1, 1, "HORI"));
			
			if (DropDownList3:IsVisible()) then
				DropDownList3:ClearAllPoints();
				DropDownList3:SetPoint(self:GetAnchors(DropDownList2, 1, "HORI"));
			end
		end
	end
end