local E, L, V, P, G = unpack(ElvUI); --Import: Engine, Locales, PrivateDB, ProfileDB, GlobalDB

--Cache global variables
--Lua functions
local unpack, assert, tonumber, type, pairs = unpack, assert, tonumber, type, pairs
local getn, tinsert, tremove = table.getn, tinsert, tremove
local abs, ceil, floor, modf, mod = math.abs, math.ceil, math.floor, math.modf, math.mod
local find, format, byte, len, sub, gsub, upper, split, utf8sub = string.find, string.format, string.byte, string.len, string.sub, string.gsub, string.upper, string.split, string.utf8sub
--WoW API / Variables
local GetScreenWidth, GetScreenHeight = GetScreenWidth, GetScreenHeight
local CreateFrame = CreateFrame

--Return short value of a number
local shortValueDec, value
function E:ShortValue(v)
	shortValueDec = format("%%.%df", E.db.general.decimalLength or 1)
	value = abs(v)
	if E.db.general.numberPrefixStyle == "METRIC" then
		if value >= 1e12 then
			return format(shortValueDec.."T", v / 1e12)
		elseif value >= 1e9 then
			return format(shortValueDec.."G", v / 1e9)
		elseif value >= 1e6 then
			return format(shortValueDec.."M", v / 1e6)
		elseif value >= 1e3 then
			return format(shortValueDec.."k", v / 1e3)
		else
			return format("%.0f", v)
		end
	elseif E.db.general.numberPrefixStyle == "CHINESE" then
		if value >= 1e8 then
			return format(shortValueDec.."Y", v / 1e8)
		elseif value >= 1e4 then
			return format(shortValueDec.."W", v / 1e4)
		else
			return format("%.0f", v)
		end
	elseif E.db.general.numberPrefixStyle == "KOREAN" then
		if value >= 1e8 then
			return format(shortValueDec.."억", v / 1e8)
		elseif value >= 1e4 then
			return format(shortValueDec.."만", v / 1e4)
		elseif value >= 1e3 then
			return format(shortValueDec.."천", v / 1e3)
		else
			return format("%.0f", v)
		end
	elseif E.db.general.numberPrefixStyle == "GERMAN" then
		if value >= 1e12 then
			return format(shortValueDec.."Bio", v / 1e12)
		elseif value >= 1e9 then
			return format(shortValueDec.."Mrd", v / 1e9)
		elseif value >= 1e6 then
			return format(shortValueDec.."Mio", v / 1e6)
		elseif value >= 1e3 then
			return format(shortValueDec.."Tsd", v / 1e3)
		else
			return format("%.0f", v)
		end
	else
		if value >= 1e12 then
			return format(shortValueDec.."T", v / 1e12)
		elseif value >= 1e9 then
			return format(shortValueDec.."B", v / 1e9)
		elseif value >= 1e6 then
			return format(shortValueDec.."M", v / 1e6)
		elseif value >= 1e3 then
			return format(shortValueDec.."K", v / 1e3)
		else
			return format("%.0f", v)
		end
	end
end

function E:IsEvenNumber(num)
	return mod(num, 2) == 0
end

function E:ColorGradient(perc, r1, g1, b1, r2, g2, b2, r3, g3, b3)
	if perc >= 1 then
		return r3, g3, b3
	elseif perc <= 0 then
		return r1, g1, b1
	end

	local segment, relperc = modf(perc)
	if segment > 0 then
		r1, g1, b1, r2, g2, b2 = r2, g2, b2, r3, g3, b3
	end

	return r1 + (r2-r1)*relperc, g1 + (g2-g1)*relperc, b1 + (b2-b1)*relperc
end

function E:Round(num, idp)
	if idp and idp > 0 then
		local mult = 10 ^ idp
		return floor(num * mult + 0.5) / mult
	end
	return floor(num + 0.5)
end

function E:Truncate(v, decimals)
	return v - (mod(v, 0.1 ^ (decimals or 0)))
end

function E:RGBToHex(r, g, b)
	r = r <= 1 and r >= 0 and r or 1
	g = g <= 1 and g >= 0 and g or 1
	b = b <= 1 and b >= 0 and b or 1
	return format("|cff%02x%02x%02x", r*255, g*255, b*255)
end

function E:HexToRGB(hex)
	local rhex, ghex, bhex = sub(hex, 1, 2), sub(hex, 3, 4), sub(hex, 5, 6)
	return tonumber(rhex, 16), tonumber(ghex, 16), tonumber(bhex, 16)
end

function E:FramesOverlap(frameA, frameB)
	if not frameA or not frameB then return end

	local sA, sB = frameA:GetEffectiveScale(), frameB:GetEffectiveScale()
	if not sA or not sB then return end

	local frameALeft = frameA:GetLeft()
	local frameARight = frameA:GetRight()
	local frameABottom = frameA:GetBottom()
	local frameATop = frameA:GetTop()

	local frameBLeft = frameB:GetLeft()
	local frameBRight = frameB:GetRight()
	local frameBBottom = frameB:GetBottom()
	local frameBTop = frameB:GetTop()

	if not frameALeft or not frameARight or not frameABottom or not frameATop then return end
	if not frameBLeft or not frameBRight or not frameBBottom or not frameBTop then return end

	return ((frameALeft*sA) < (frameBRight*sB))
		and ((frameBLeft*sB) < (frameARight*sA))
		and ((frameABottom*sA) < (frameBTop*sB))
		and ((frameBBottom*sB) < (frameATop*sA))
end

function E:GetScreenQuadrant(frame)
	local x, y = frame:GetCenter()
	local screenWidth = GetScreenWidth()
	local screenHeight = GetScreenHeight()
	local point

	if not frame:GetCenter() then
		return "UNKNOWN", frame:GetName()
	end

	if (x > (screenWidth / 3) and x < (screenWidth / 3)*2) and y > (screenHeight / 3)*2 then
		point = "TOP"
	elseif x < (screenWidth / 3) and y > (screenHeight / 3)*2 then
		point = "TOPLEFT"
	elseif x > (screenWidth / 3)*2 and y > (screenHeight / 3)*2 then
		point = "TOPRIGHT"
	elseif (x > (screenWidth / 3) and x < (screenWidth / 3)*2) and y < (screenHeight / 3) then
		point = "BOTTOM"
	elseif x < (screenWidth / 3) and y < (screenHeight / 3) then
		point = "BOTTOMLEFT"
	elseif x > (screenWidth / 3)*2 and y < (screenHeight / 3) then
		point = "BOTTOMRIGHT"
	elseif x < (screenWidth / 3) and (y > (screenHeight / 3) and y < (screenHeight / 3)*2) then
		point = "LEFT"
	elseif x > (screenWidth / 3)*2 and y < (screenHeight / 3)*2 and y > (screenHeight / 3) then
		point = "RIGHT"
	else
		point = "CENTER"
	end

	return point
end

function E:GetXYOffset(position, override)
	local default = E.Spacing
	local x, y = override or default, override or default

	if position == "TOP" then
		return 0, y
	elseif position == "TOPLEFT" then
		return x, y
	elseif position == "TOPRIGHT" then
		return -x, y
	elseif position == "BOTTOM" then
		return 0, -y
	elseif position == "BOTTOMLEFT" then
		return x, -y
	elseif position == "BOTTOMRIGHT" then
		return -x, -y
	elseif position == "LEFT" then
		return -x, 0
	elseif position == "RIGHT" then
		return x, 0
	elseif position == "CENTER" then
		return 0, 0
	end
end

local gftStyles, gftDec, gftUseStyle, gftDeficit = {
	-- keep percents in this table with `PERCENT` in the key, and `%.1f%%` in the value somewhere.
	-- we use these two things to follow our setting for decimal length. they need to be EXACT.
	["CURRENT"] = "%s",
	["CURRENT_MAX"] = "%s - %s",
	["CURRENT_PERCENT"] = "%s - %.1f%%",
	["CURRENT_MAX_PERCENT"] = "%s - %s | %.1f%%",
	["PERCENT"] = "%.1f%%",
	["DEFICIT"] = "-%s"
}

function E:GetFormattedText(style, min, max)
	assert(gftStyles[style], "Invalid format style: "..style)
	assert(min, "You need to provide a current value. Usage: E:GetFormattedText(style, min, max)")
	assert(max, "You need to provide a maximum value. Usage: E:GetFormattedText(style, min, max)")

	if max == 0 then max = 1 end

	gftDec = (E.db.general.decimalLength or 1)
	if (gftDec ~= 1) and find(style, "PERCENT") then
		gftUseStyle = gsub(gftStyles[style], "%%%.1f%%%%", "%%."..gftDec.."f%%%%")
	else
		gftUseStyle = gftStyles[style]
	end

	if style == "DEFICIT" then
		gftDeficit = max - min
		return ((gftDeficit > 0) and format(gftUseStyle, E:ShortValue(gftDeficit))) or ""
	elseif style == "PERCENT" then
		return format(gftUseStyle, min / max * 100)
	elseif style == "CURRENT" or ((style == "CURRENT_MAX" or style == "CURRENT_MAX_PERCENT" or style == "CURRENT_PERCENT") and min == max) then
		return format(gftStyles.CURRENT, E:ShortValue(min))
	elseif style == "CURRENT_MAX" then
		return format(gftUseStyle,  E:ShortValue(min), E:ShortValue(max))
	elseif style == "CURRENT_PERCENT" then
		return format(gftUseStyle, E:ShortValue(min), min / max * 100)
	elseif style == "CURRENT_MAX_PERCENT" then
		return format(gftUseStyle, E:ShortValue(min), E:ShortValue(max), min / max * 100)
	end
end

function E:AbbreviateString(string, allUpper)
	local newString = ""
	local words = {split(" ", string)}
	for _, word in pairs(words) do
		word = utf8sub(word, 1, 1)
		if allUpper then
			word = upper(word)
		end
		newString = newString..word
	end

	return newString
end

function E:ShortenString(string, numChars, dots)
	local bytes = len(string)
	if bytes <= numChars then
		return string
	else
		local len, pos = 0, 1
		while pos <= bytes do
			len = len + 1
			local c = byte(string, pos)
			if c > 0 and c <= 127 then
				pos = pos + 1
			elseif c >= 192 and c <= 223 then
				pos = pos + 2
			elseif c >= 224 and c <= 239 then
				pos = pos + 3
			elseif c >= 240 and c <= 247 then
				pos = pos + 4
			end
			if len == numChars then break end
		end

		if len == numChars and pos <= bytes then
			return sub(string, 1, pos - 1)..(dots and "..." or "")
		else
			return string
		end
	end
end

local waitTable = {}
local waitFrame
function E:Delay(delay, func, ...)
	if (type(delay) ~= "number") or (type(func) ~= "function") then
		return false
	end
	if waitFrame == nil then
		waitFrame = CreateFrame("Frame", "WaitFrame", E.UIParent)
		waitFrame:SetScript("OnUpdate", function()
			local waitRecord, waitDelay, waitFunc, waitParams
			local i, count = 1, getn(waitTable)
			while i <= count do
				waitRecord = tremove(waitTable, i)
				waitDelay = tremove(waitRecord, 1)
				waitFunc = tremove(waitRecord, 1)
				waitParams = tremove(waitRecord, 1)
				if waitDelay > arg1 then
					tinsert(waitTable, i, {waitDelay - arg1, waitFunc, waitParams})
					i = i + 1
				else
					count = count - 1
					waitFunc(unpack(waitParams))
				end
			end
		end)
	end
	tinsert(waitTable, {delay, func, arg})
	return true
end

function E:StringTitle(str)
	return gsub(str, "(.)", upper, 1)
end

E.TimeThreshold = 3

E.TimeColors = {
	[0] = "|cffeeeeee",
	[1] = "|cffeeeeee",
	[2] = "|cffeeeeee",
	[3] = "|cffeeeeee",
	[4] = "|cfffe0000",
	[5] = "|cff909090", --mmss
	[6] = "|cff707070", --hhmm
}

E.TimeFormats = {
	[0] = {"%dd", "%dd"},
	[1] = {"%dh", "%dh"},
	[2] = {"%dm", "%dm"},
	[3] = {"%ds", "%d"},
	[4] = {"%.1fs", "%.1f"},
	[5] = {"%d:%02d", "%d:%02d"}, --mmss
	[6] = {"%d:%02d", "%d:%02d"}, --hhmm
}

local DAY, HOUR, MINUTE = 86400, 3600, 60
local DAYISH, HOURISH, MINUTEISH = HOUR * 23.5, MINUTE * 59.5, 59.5
local HALFDAYISH, HALFHOURISH, HALFMINUTEISH = DAY/2 + 0.5, HOUR/2 + 0.5, MINUTE/2 + 0.5

function E:GetTimeInfo(s, threshhold, hhmm, mmss)
	if s < MINUTE then
		if s >= threshhold then
			return floor(s), 3, 0.51
		else
			return s, 4, 0.051
		end
	elseif s < HOUR then
		if mmss and s < mmss then
			return s/MINUTE, 5, 0.51, mod(s, MINUTE)
		else
			local minutes = floor((s/MINUTE)+.5)
			if hhmm and s < (hhmm * MINUTE) then
				return s/HOUR, 6, minutes > 1 and (s - (minutes*MINUTE - HALFMINUTEISH)) or (s - MINUTEISH), mod(minutes, MINUTE)
			else
				return ceil(s / MINUTE), 2, minutes > 1 and (s - (minutes*MINUTE - HALFMINUTEISH)) or (s - MINUTEISH)
			end
		end
	elseif s < DAY then
		if mmss and s < mmss then
			return s/MINUTE, 5, 0.51, mod(s, MINUTE)
		elseif hhmm and s < (hhmm * MINUTE) then
			local minutes = floor((s/MINUTE)+.5)
			return s/HOUR, 6, minutes > 1 and (s - (minutes*MINUTE - HALFMINUTEISH)) or (s - MINUTEISH), mod(minutes, MINUTE)
		else
			local hours = floor((s/HOUR)+.5)
			return ceil(s / HOUR), 1, hours > 1 and (s - (hours*HOUR - HALFHOURISH)) or (s - HOURISH)
		end
	else
		local days = floor((s/DAY)+.5)
		return ceil(s / DAY), 0, days > 1 and (s - (days*DAY - HALFDAYISH)) or (s - DAYISH)
	end
end

local COLOR_COPPER = "|cffeda55f"
local COLOR_SILVER = "|cffc7c7cf"
local COLOR_GOLD = "|cffffd700"

function E:FormatMoney(amount, style)
	local coppername = L.copperabbrev
	local silvername = L.silverabbrev
	local goldname = L.goldabbrev

	local val = abs(amount)
	local gold = floor(val / 10000)
	local silver = floor(mod(val / 100, 100))
	local copper = floor(mod(val, 100))

	if not style or style == "SMART" then
		local str = ""
		if gold > 0 then
			str = format("%d%s%s", gold, goldname, (silver > 0 or copper > 0) and " " or "")
		end
		if silver > 0 then
			str = format("%s%d%s%s", str, silver, silvername, copper > 0 and " " or "")
		end
		if copper > 0 or val == 0 then
			str = format("%s%d%s", str, copper, coppername)
		end
		return str
	end

	if style == "FULL" then
		if gold > 0 then
			return format("%d%s %d%s %d%s", gold, goldname, silver, silvername, copper, coppername)
		elseif silver > 0 then
			return format("%d%s %d%s", silver, silvername, copper, coppername)
		else
			return format("%d%s", copper, coppername)
		end
	elseif style == "SHORT" then
		if gold > 0 then
			return format("%.1f%s", amount / 10000, goldname)
		elseif silver > 0 then
			return format("%.1f%s", amount / 100, silvername)
		else
			return format("%d%s", amount, coppername)
		end
	elseif style == "SHORTINT" then
		if gold > 0 then
			return format("%d%s", gold, goldname)
		elseif silver > 0 then
			return format("%d%s", silver, silvername)
		else
			return format("%d%s", copper, coppername)
		end
	elseif style == "CONDENSED" then
		if gold > 0 then
			return format("%s%d|r.%s%02d|r.%s%02d|r", COLOR_GOLD, gold, COLOR_SILVER, silver, COLOR_COPPER, copper)
		elseif silver > 0 then
			return format("%s%d|r.%s%02d|r", COLOR_SILVER, silver, COLOR_COPPER, copper)
		else
			return format("%s%d|r", COLOR_COPPER, copper)
		end
	elseif style == "BLIZZARD" then
		if gold > 0 then
			return format("%s%s %d%s %d%s", gold, goldname, silver, silvername, copper, coppername)
		elseif silver > 0 then
			return format("%d%s %d%s", silver, silvername, copper, coppername)
		else
			return format("%d%s", copper, coppername)
		end
	end

	return self:FormatMoney(amount, "SMART")
end
