-- VgerCore  by Vger-Azjol-Nerub
-- www.vgermods.com
-- © 2006-2019 Green Eclipse.  This mod is released under the Creative Commons Attribution-NonCommercial-NoDerivs 3.0 license.
-- 
-- Version 1.0.11 -- IsClassic
local VgerCoreThisVersion = 1.11
-- 
-- VgerCore contains functionality that is shared by Vger's mods.
-- It can be used as a standalone add-on, or embedded within other mods.
--
------------------------------------------------------------

local InitializeOrUpgrade = (not VgerCore) or (not VgerCore.Version) or (VgerCore.Version < VgerCoreThisVersion)

-- If the currently loaded version of VgerCore isn't as good as this one, load the new one.
if InitializeOrUpgrade then

VgerCore = {}
VgerCore.Version = VgerCoreThisVersion

-- Is this WoW Classic?
VgerCore.IsClassic = (WOW_PROJECT_ID == WOW_PROJECT_CLASSIC)

-- Common colors
VgerCore.Color = {}

VgerCore.Color.Reset = "|r"

VgerCore.Color.Blue = "|cff8ec3e6"
VgerCore.Color.BlueR = 142 / 255
VgerCore.Color.BlueG = 195 / 255
VgerCore.Color.BlueB = 230 / 255

VgerCore.Color.DarkBlue = "|cff6a92ac"
VgerCore.Color.DarkBlueR = 106 / 255
VgerCore.Color.DarkBlueG = 146 / 255
VgerCore.Color.DarkBlueB = 172 / 255

VgerCore.Color.Green = "|cffb4fe2c"
VgerCore.Color.GreenR = 180 / 255
VgerCore.Color.GreenG = 255 / 255
VgerCore.Color.GreenB = 44 / 255

VgerCore.Color.Orange = "|cfffecf38"
VgerCore.Color.OrangeR = 255 / 255
VgerCore.Color.OrangeG = 207 / 255
VgerCore.Color.OrangeB = 56 / 255

VgerCore.Color.Lemon = "|cfffffdd0"
VgerCore.Color.LemonR = 255 / 255
VgerCore.Color.LemonG = 253 / 255
VgerCore.Color.LemonB = 208 / 255

VgerCore.Color.Salmon = "|cfffe8460"
VgerCore.Color.SalmonR = 255 / 255
VgerCore.Color.SalmonG = 132 / 255
VgerCore.Color.SalmonB = 96 / 255

VgerCore.Color.Beige = "|cffe0dec8"
VgerCore.Color.BeigeR = 224 / 255
VgerCore.Color.BeigeG = 222 / 255
VgerCore.Color.BeigeB = 200 / 255

VgerCore.Color.White = "|cffffffff"
VgerCore.Color.WhiteR = 255 / 255
VgerCore.Color.WhiteG = 255 / 255
VgerCore.Color.WhiteB = 255 / 255

VgerCore.Color.Grey = "|cff909090"
VgerCore.Color.GreyR = 144 / 255
VgerCore.Color.GreyG = 144 / 255
VgerCore.Color.GreyB = 144 / 255

VgerCore.Color.Silver = "|cffc0c0c0"
VgerCore.Color.SilverR = 192 / 255
VgerCore.Color.SilverG = 192 / 255
VgerCore.Color.SilverB = 192 / 255

VgerCore.Color.Black= "|cff000000"
VgerCore.Color.BlackR = 0 / 255
VgerCore.Color.BlackG = 0 / 255
VgerCore.Color.BlackB = 0 / 255

VgerCore.MoneyColor = {}
VgerCore.MoneyColor.Gold = "|cffecda90"
VgerCore.MoneyColor.Silver = "|cffd7d5d8"
VgerCore.MoneyColor.Copper = "|cffe2ad8e"


-- Displays a standard VgerCore message.
function VgerCore.Message(Text)
	if DEFAULT_CHAT_FRAME then
		DEFAULT_CHAT_FRAME:AddMessage(VgerCore.Color.Orange .. tostring(Text))
	else
		message(VgerCore.Color.Orange .. tostring(Text))
	end
end

-- Displays a bunch of messages from one string, separated by newlines.
-- Notes:
-- 	* Colors specified at the beginning of Text will not propagate to subsequent lines of Text.
-- 	Use the optional Color parameter instead.
-- 	* Empty lines will be skipped.  Add a space to the line if you want it to be printed.
function VgerCore.MultilineMessage(Text, Color)
	local Line
	local ColorString = Color
	if not ColorString then ColorString = "" end
	for Line in string.gmatch(Text, "[^\r\n]+") do
		VgerCore.Message(ColorString .. Line)
	end
end

-- Displays a large VgerCore message.
function VgerCore.BigMessage(Text)
	if UIErrorsFrame then
		UIErrorsFrame:AddMessage(tostring(Text), VgerCore.Color.GreenR, VgerCore.Color.GreenG, VgerCore.Color.GreenB, 1.0, 4.0)
	end
	if DEFAULT_CHAT_FRAME then
		DEFAULT_CHAT_FRAME:AddMessage(VgerCore.Color.Green .. tostring(Text))
	end
end

-- Displays a VgerCore error message if the condition is false.
function VgerCore.Assert(Condition, Message)
	-- Possibility: call the assert() function to get a callstack and integrate with mods such as Swatter.
	if not Condition then VgerCore.Fail(Message) end
end

-- Displays a VgerCore error message.
function VgerCore.Fail(Message)
	VgerCore.Message(VgerCore.Color.Salmon .. "ERROR:  " .. VgerCore.Color.White .. tostring(Message))
end

-- Hooks an insecure function.  Similar to the base WoW API's hooksecurefunc.  The hook function will be run
-- after the original function to be hooked, unless Pre is true, in which case the hook will be run first.
-- Valid usage:
-- VgerCore.HookInsecureFunction(Object, FunctionName, Hook, Pre)
-- VgerCore.HookInsecureFunction(FunctionName, Hook, Pre)
function VgerCore.HookInsecureFunction(arg1, arg2, arg3, arg4)
	local TypeOfObject = type(arg1)
	local OldFunction
	if TypeOfObject == "table" then -- Object, FunctionName, Hook, Pre
		OldFunction = arg1[arg2]
		if OldFunction then
			arg1[arg2] = VgerCore.CreateHookFunction(OldFunction, arg3, arg4)
		else
			VgerCore.Fail("VgerCore.HookInsecureFunction: could not find member function '" .. arg2 .. "'.")
		end
	elseif TypeOfObject == "string" then -- FunctionName, Hook, Pre
		OldFunction = getglobal(arg1)
		if OldFunction then
			local Environment = getfenv()
			Environment[arg1] = VgerCore.CreateHookFunction(OldFunction, arg2, arg3)
		else
			VgerCore.Fail("VgerCore.HookInsecureFunction: could not find function '" .. arg1 .. "'.")
		end
	else
		VgerCore.Fail("VgerCore.HookInsecureFunction argument 1 must be table or string, not " .. TypeOfObject .. ".")
	end
end

-- Hooks an insecure script handler.  Works just like HookInsecureFunction(Object, FunctionName, Hook, Pre), except that
-- instead of a function name, a script name is passed.
function VgerCore.HookInsecureScript(Object, ScriptName, Hook, Pre)
	local OldFunction = Object:GetScript(ScriptName)
	if OldFunction then
		Object:SetScript(ScriptName, VgerCore.CreateHookFunction(OldFunction, Hook, Pre))
	else
		Object:SetScript(ScriptName, Hook)
	end
end

-- Internal function used by HookInsecureFunction.
function VgerCore.CreateHookFunction(OldFunction, Hook, Pre)
	if Pre then
		return function(...)
			Hook(...)
			return OldFunction(...)
		end
	else
		return function(...)
			local ReturnValue = OldFunction(...)
			Hook(...)
			return ReturnValue
		end
	end
end

-- Executes a chat command just as if it were typed in the chat window.
-- Returns true if successful, or false if not (primarily if the command is a secure function, such as /cast).
function VgerCore.ExecuteChatCommand(Command)
	local EditBox = DEFAULT_CHAT_FRAME.editBox
	if not EditBox then return false end
	
	-- First, make sure that this command is okay.
	local _, _, SlashCommand = strfind(Command, "^(/%w+) ")
	if SlashCommand then
		if IsSecureCmd(SlashCommand) then
			VgerCore.Fail(SlashCommand .. " is a secure command and cannot be run automatically.")
			return false
		end
	end

	-- Now, execute the chat command.
	local PreviousText = EditBox:GetText()
	EditBox:SetText(Command)
	ChatEdit_SendText(EditBox)
	EditBox:SetText(PreviousText)
	return true
end

-- Runs a macro.
-- Returns true if successful, or false if not.
function VgerCore.RunMacro(MacroName)
	-- First, get the text of the macro.
	local _, _, Script, _ = GetMacroInfo(MacroName)
	if not Script then return false end

	-- Then, execute each line individually.  Ignore comments marked with # or -.
	local Line
	for Line in string.gmatch(Script, "[^\n]+") do
		local FirstChar = strsub(Line, 1, 1)
		if FirstChar ~= "#" and FirstChar ~= "-" then
			VgerCore.ExecuteChatCommand(Line)
		end
	end
	return true
end

-- Returns true if the user is in a Battleground, or false if not.
function VgerCore.IsInBattleground()
	local Battleground
	for Battleground = 1, GetMaxBattlefieldID() do
		if GetBattlefieldStatus(Battleground) == "active" then return true end
	end
	return false
end

-- Comparer function for use in table.sort that sorts strings alphabetically, ignoring case.
function VgerCore.CaseInsensitiveComparer(a, b)
	return strlower(a) < strlower(b)
end

-- Returns a six-digit hex string for three RGB values 0-1.
function VgerCore.RGBToHex(r, g, b)
	r = r <= 1 and r >= 0 and r or 0
	g = g <= 1 and g >= 0 and g or 0
	b = b <= 1 and b >= 0 and b or 0
	return format("%02x%02x%02x", r * 255, g * 255, b * 255)
end

-- Returns RGB values 0-1 for a six-digit hex string, or nil if unsuccessful.
function VgerCore.HexToRGB(hex)
	if not hex or strlen(hex) ~= 6 then return end
	local r, g, b = strsub(hex, 1, 2), strsub(hex, 3, 4), strsub(hex, 5, 6)
	r, g, b = r or 0, g or 0, b or 0
	return tonumber(r, 16) / 255, tonumber(g, 16) / 255, tonumber(b, 16) / 255
end

-- Same as strfind, but finds the last occurrence of a substring.  The substring to find must be
-- no more than two characters.
function VgerCore.StringFindReverse(str, find)
	local FindLen = strlen(find)
	VgerCore.Assert(FindLen == 1 or FindLen == 2, "The substring to find must be one or two characters in length.")
	local FindByte, FindByte2 = strbyte(find, 1, 2)
	local StringLength = strlen(str)
	local i
	for i = StringLength, 1, -1 do
		if strbyte(str, i) == FindByte and (FindByte2 == nil or strbyte(str, i + 1) == FindByte2) then return i end
	end
	return nil
end

-- Returns a string representation of a number to a maximum of one decimal place.  If the number passed is nil, nil is returned.
function VgerCore.FormatShortDecimal(Number)
	if Number == nil then
		return nil
	elseif Number >= 1000 then
		return BreakUpLargeNumbers(floor(Number + .5))
	elseif abs(Number - floor(Number)) < .0001 then
		return tostring(Number)
	else
		return format("%.1f", Number)
	end
end

-- Returns a string representation of a number to a maximum of one decimal place.  If the number is long, it is shortened using
-- an abbreviation like "12.3k" so that it fits in a small space.   If the number passed is nil, nil is returned.
function VgerCore.FormatCompactDecimal(Number)
	if Number == nil then
		return nil
	elseif Number >= 1000000000 then
		return format("%.1fb", Number / 1000000000)
	elseif Number >= 1000000 then
		return format("%.1fm", Number / 1000000)
	elseif Number >= 10000 then
		return format("%.1fk", Number / 1000)
	elseif Number >= 1000 then
		return BreakUpLargeNumbers(floor(Number + .5))
	elseif abs(Number - floor(Number)) < .0001 then
		return tostring(Number)
	else
		return format("%.1f", Number)
	end
end

-- Returns a string representation of a number with no decimal places.  If the number passed is nil, nil is returned.
function VgerCore.FormatInteger(Number)
	if Number == nil then
		return nil
	else
		return BreakUpLargeNumbers(floor(Number + .5))
	end
end

function VgerCore.ThrottleDelayCore(Duration, Immediate, Func, arg1)
	if arg1 ~= nil then
		VgerCore.Fail("Throttle and Delay don't support functions with arguments!")
	end
	local ThrottleUntil, WasThrottled
	local TimerCallback = function()
		Func()
		ThrottleUntil = nil
		WasThrottled = nil
	end
	local Throttled = function()
		if (not Immediate) or (ThrottleUntil ~= nil and GetTime() < ThrottleUntil) then
			-- We were called too soon.
			-- Or, if Immediate is true, then delay the first call too.
			if not WasThrottled then
				if ThrottleUntil == nil then ThrottleUntil = GetTime() + Duration end
				C_Timer.After(Duration, TimerCallback)
				WasThrottled = true
			end
		else
			-- It's been long enough, or this is the first call of the sequence.
			ThrottleUntil = GetTime() + Duration
			Func()
		end
	end
	return Throttled
end

-- Returns a version of Function that is called no more often than Duration (in sec).
function VgerCore.Throttle(Duration, Func, arg1)
	return VgerCore.ThrottleDelayCore(Duration, true, Func, arg1)
end

-- Returns a version of Function that delays its own execution by Duration (in sec), and also
-- prevents execution more than once in the same duration.
function VgerCore.Delay(Duration, Func, arg1)
	return VgerCore.ThrottleDelayCore(Duration, false, Func, arg1)
end

------------------------------------------------------------
end -- if InitializeOrUpgrade
