local SLE, T, E, L, V, P, G = unpack(select(2, ...))

--GLOBALS: unpack, NUM_BAG_SLOTS, IsAddOnLoaded, DEFAULT_CHAT_FRAME
local _G = _G
local select, format, tonumber, match, ipairs, pairs, gsub = select, format, tonumber, string.match, ipairs, pairs, gsub
local getmetatable, error, type, assert, random = getmetatable, error, type, assert, random
local tremove, tinsert, tconcat, date = tremove, tinsert, table.concat, date
local strjoin, strmatch, strsplit, strfind = strjoin, strmatch, strsplit, strfind
local EnumerateFrames = EnumerateFrames
local GetItemInfo = GetItemInfo

T.Values = {
	FontFlags = {
		NONE = L["NONE"],
		OUTLINE = 'Outline',
		THICKOUTLINE = 'Thick',
		MONOCHROME = '|cffaaaaaaMono|r',
		MONOCHROMEOUTLINE = '|cffaaaaaaMono|r Outline',
		MONOCHROMETHICKOUTLINE = '|cffaaaaaaMono|r Thick',
	},
	positionValues = {
		TOPLEFT = 'TOPLEFT',
		LEFT = 'LEFT',
		BOTTOMLEFT = 'BOTTOMLEFT',
		RIGHT = 'RIGHT',
		TOPRIGHT = 'TOPRIGHT',
		BOTTOMRIGHT = 'BOTTOMRIGHT',
		CENTER = 'CENTER',
		TOP = 'TOP',
		BOTTOM = 'BOTTOM',
	},
	-- FontSize = { min = 8, max = 64, step = 1 },
	Strata = { BACKGROUND = 'BACKGROUND', LOW = 'LOW', MEDIUM = 'MEDIUM', HIGH = 'HIGH', DIALOG = 'DIALOG', TOOLTIP = 'TOOLTIP' },
	AllPoints = { TOPLEFT = 'TOPLEFT', LEFT = 'LEFT', BOTTOMLEFT = 'BOTTOMLEFT', RIGHT = 'RIGHT', TOPRIGHT = 'TOPRIGHT', BOTTOMRIGHT = 'BOTTOMRIGHT', CENTER = 'CENTER', TOP = 'TOP', BOTTOM = 'BOTTOM' }
}

T.StringToUpper = function(str)
	return (gsub(str, '^%l', strupper))
end

T.GetSpell = function(id)
	local name = GetSpellInfo(id)
	return name
end

--Some of Simpy's herecy bullshit
T.rgsub = function(pattern, ...)
	local z = select("#", ...)
	local x = floor(z / 2)
	local s
	for i = 1, x do
		z = select(i, ...)
		if strmatch(pattern, z) then
			s = select(i + x, ...)
			pattern = gsub(pattern, z, s)
		end
	end
	return pattern
end

T.SafeHookScript = function(frame, handlername, newscript)
	local oldValue = frame:GetScript(handlername)
	frame:SetScript(handlername, newscript)
	return oldValue
end

--Search in a table like {"arg1", "arg2", "arg3"}
function SLE:SimpleTable(table, item)
	for i = 1, #table do
		if table[i] == item then
			return true
		end
	end

	return false
end
--Search in a table like {["stuff"] = {}, ["stuff2"] = {} }
function SLE:ValueTable(table, item)
	for i, _ in pairs(table) do
		if i == item then
			return true
		end
	end

	return false
end

function SLE:GetIconFromID(idtype, id)
	local path
	if idtype == 'item' then
		path = select(10, GetItemInfo(id))
	elseif idtype == 'spell' then
		path = select(3, GetSpellInfo(id))
	elseif idtype == 'achiev' then
		path = select(10, GetAchievementInfo(id))
	end
	return path or nil
end

--For searching stuff in bags
function SLE:BagSearch(itemId)
	for container = 0, NUM_BAG_SLOTS do
		for slot = 1, GetContainerNumSlots(container) do
			if itemId == GetContainerItemID(container, slot) then
				return container, slot
			end
		end
	end
end

--Additional tutorials if any
function SLE:AddTutorials()
end

--S&L print
function SLE:Print(msg, msgtype)
	if msgtype == 'error' then
		(_G[E.db.general.messageRedirect] or DEFAULT_CHAT_FRAME):AddMessage(strjoin('', '|cffff0000S&L Error:|r ', msg))
	elseif msgtype == 'warning' then
		(_G[E.db.general.messageRedirect] or DEFAULT_CHAT_FRAME):AddMessage(strjoin('', '|cffd3cf00S&L Warning:|r ', msg))
	elseif msgtype == 'info' then
		(_G[E.db.general.messageRedirect] or DEFAULT_CHAT_FRAME):AddMessage(strjoin('', '|cff14adcdS&L Info:|r ', msg))
	else
		(_G[E.db.general.messageRedirect] or DEFAULT_CHAT_FRAME):AddMessage(
			strjoin('', E['media'].hexvaluecolor, 'S&L Message:|r ', msg)
		)
	end
end

--A function to ensure any files which set movers will be recognised as text by git.
function SLE:SetMoverPosition(mover, anchor, parent, point, x, y)
	if not _G[mover] then
		return
	end
	local frame = _G[mover]

	frame:ClearAllPoints()
	frame:SetPoint(anchor, parent, point, x, y)
	E:SaveMoverPosition(mover)
end

--To get stuff from item link. Got this from suspctz
function SLE:GetItemSplit(itemLink)
	local itemString = strmatch(itemLink, 'item:([%-?%d:]+)')
	local itemSplit = {}

	-- Split data into a table
	for _, v in ipairs({strsplit(':', itemString)}) do
		if v == '' then
			itemSplit[#itemSplit + 1] = 0
		else
			itemSplit[#itemSplit + 1] = tonumber(v)
		end
	end

	return itemSplit
end

--Reseting shit
function SLE:Reset(group)
	if not group then
		print('U wot m8?')
	end
	if group == 'unitframes' or group == 'all' then
		E.db.sle.roleicons = 'ElvUI'
		E.db.sle.powtext = false
	end
	if group == 'backgrounds' or group == 'all' then
		E:CopyTable(E.db.sle.backgrounds, P.sle.backgrounds)
		E:ResetMovers(L["SLE_BG_1_Mover"])
		E:ResetMovers(L["SLE_BG_2_Mover"])
		E:ResetMovers(L["SLE_BG_3_Mover"])
		E:ResetMovers(L["SLE_BG_4_Mover"])
	end
	if group == 'datatexts' or group == 'all' then
		E:CopyTable(E.db.sle.datatexts, P.sle.datatexts)
		E:CopyTable(E.db.sle.dt, P.sle.dt)
	end
	if group == 'marks' or group == 'all' then
		E:CopyTable(E.db.sle.raidmarkers, P.sle.raidmarkers)
		E:ResetMovers(L["Raid Marker Bar"])
	end
	if group == 'all' then
		E:CopyTable(E.db.sle, P.sle)
		E:ResetMovers("PvP")
		E:ResetMovers(L["S&L UI Buttons"])
		E:ResetMovers(L["Error Frame"])
		E:ResetMovers(L["Pet Battle Status"])
		E:ResetMovers(L["Pet Battle AB"])
		E:ResetMovers(L["Garrison Tools Bar"])
		E:ResetMovers(L["Ghost Frame"])
		E:ResetMovers(L["Raid Utility"])
	end
	E:UpdateAll()
end

function SLE:GetMapInfo(id, arg)
	if not arg then
		return
	end
	local MapInfo
	if T.MapInfoTable[id] then
		MapInfo = T.MapInfoTable[id]
	else
		MapInfo = C_Map.GetMapInfo(id)
		T.MapInfoTable[id] = MapInfo
	end
	if not MapInfo then
		return UNKNOWN
	end
	if arg == 'all' then
		return MapInfo['name'], MapInfo['mapID'], MapInfo['parentMapID'], MapInfo['mapType']
	end
	return MapInfo[arg]
end

local txframe = CreateFrame('Frame')
local tx = txframe:CreateTexture()

function SLE:TextureExists(path)
	if not path or path == '' then
		return SLE:Print('Path not valid or defined.', 'error')
	end
	tx:SetTexture('?')
	tx:SetTexture(path)

	return (tx:GetTexture() ~= '?')
end

--Trying to determine the region player is in, not entirely reliable cause based on atypet not an actual region id
function SLE:GetRegion()
	local lib = LibStub('LibRealmInfo')
	if not GetPlayerInfoByGUID(E.myguid) then
		return
	end
	local rid, _, _, _, _, _, region = lib:GetRealmInfoByGUID(E.myguid)
	SLE.region = region
	if not SLE.region then
		if not IsTestBuild() then
			SLE.region =
				format(
				"An error happened. Your region is unknown. Realm: %s. RID: %s. Please report your realm name and the region you are playing in to |cff1784d1Shadow & Light|r authors.",
				E.myrealm,
				rid or 'nil'
			)
			SLE:Print(SLE.region, 'error')
		end
		SLE.region = 'PTR'
	end
end

--Registering and loading modules
SLE['RegisteredModules'] = {}
function SLE:RegisterModule(name)
	if self.initialized then
		self:GetModule(name):Initialize()
	else
		self['RegisteredModules'][#self['RegisteredModules'] + 1] = name
	end
end

local GetCVarBool = GetCVarBool
local pcall = pcall
local ScriptErrorsFrame_OnError = ScriptErrorsFrame_OnError
function SLE:InitializeModules()
	for _, module in pairs(SLE['RegisteredModules']) do
		module = self:GetModule(module)
		if module.Initialize then
			local _, catch = pcall(module.Initialize, module)

			if catch and GetCVarBool('scriptErrors') == true then
				if E.wowbuild < 24330 then --7.2
					ScriptErrorsFrame_OnError(catch, false)
				end
			end
		end
	end
end

--[[
Updating alongside with ElvUI. SLE:UpdateAll() is hooked to E:UpdateAll()
Modules are supposed to provide a function(s) to call when profile change happens (or global update is called).
Provided functions should be named Module:ForUpdateAll() or otherwise stored in SLE.UpdateFunctions table (when there is no need of reassigning settings table.
Each modules insert their functions in respective files.
]]
local collectgarbage = collectgarbage
SLE.UpdateFunctions = {}
function SLE:UpdateAll()
	if not SLE.initialized then
		return
	end

	for _, name in pairs(SLE['RegisteredModules']) do
		local module = SLE:GetModule(name)
		if module.ForUpdateAll then
			module:ForUpdateAll()
		else
			if SLE.UpdateFunctions[name] then
				SLE.UpdateFunctions[name]()
			end
		end
	end

	if not SLE._Compatibility['oRA3'] then
		SLE.BlizzRaid:CreateAndUpdateIcons()
	end

	SLE:SetCompareItems()

	collectgarbage('collect')
end

--Movable buttons in config stuff. Some Simpy's billshit applied
local function MovableButton_Match(s, v)
	local m1, m2, m3, m4 = "^" .. v .. "$", "^" .. v .. ",", "," .. v .. "$", "," .. v .. ","
	return (match(s, m1) and m1) or (match(s, m2) and m2) or (match(s, m3) and m3) or (match(s, m4) and v .. ",")
end
function SLE:MovableButtonSettings(db, key, value, remove, movehere)
	local str = db[key]
	if not db or not str or not value then
		return
	end
	local found = MovableButton_Match(str, E:EscapeString(value))
	if found and movehere then
		local tbl, sv, sm = {strsplit(",", str)}
		for i in ipairs(tbl) do
			if tbl[i] == value then
				sv = i
			elseif tbl[i] == movehere then
				sm = i
			end
			if sv and sm then
				break
			end
		end
		tremove(tbl, sm)
		tinsert(tbl, sv, movehere)

		db[key] = tconcat(tbl, ",")
	elseif found and remove then
		db[key] = gsub(str, found, "")
	elseif not found and not remove then
		db[key] = (str == "" and value) or (str .. "," .. value)
	end
end

function SLE:CreateMovableButtons(Order, Name, CanRemove, db, key)
	local moveItemFrom, moveItemTo
	local config = {
		order = Order,
		dragdrop = true,
		type = 'multiselect',
		name = Name,
		dragOnLeave = function()
		end, --keep this here
		dragOnEnter = function(info) moveItemTo = info.obj.value end,
		dragOnMouseDown = function(info)
			moveItemFrom, moveItemTo = info.obj.value, nil
		end,
		dragOnMouseUp = function(info)
			SLE:MovableButtonSettings(db, key, moveItemTo, nil, moveItemFrom) --add it in the new spot
			moveItemFrom, moveItemTo = nil, nil
		end,
		stateSwitchGetText = function(info, TEXT)
			local text = GetItemInfo(tonumber(TEXT))
			info.userdata.text = text
			return text
		end,
		stateSwitchOnClick = function(info)
			SLE:MovableButtonSettings(db, key, moveItemFrom)
		end,
		values = function()
			local str = db[key]
			if str == "" then
				return nil
			end
			return {strsplit(",", str)}
		end,
		get = function(info, value)
			local str = db[key]
			if str == "" then
				return nil
			end
			local tbl = {strsplit(",", str)}
			return tbl[value]
		end,
		set = function(info, value)
		end
	}
	if CanRemove then --This allows to remove shit
		config.dragOnClick = function(info)
			SLE:MovableButtonSettings(db, key, moveItemFrom, true)
		end
	end
	return config
end

--New API
local function LevelUpBG(frame, topcolor, bottomcolor)
	if not frame then
		return
	end
	if not frame.bg then
		frame.bg = frame:CreateTexture(nil, 'BACKGROUND')
	end
	frame.bg:SetTexture([[Interface\LevelUp\LevelUpTex]])
	frame.bg:ClearAllPoints()
	frame.bg:SetPoint('CENTER')
	frame.bg:Point('TOPLEFT', frame)
	frame.bg:Point('BOTTOMRIGHT', frame)
	frame.bg:SetTexCoord(0.00195313, 0.63867188, 0.03710938, 0.23828125)
	frame.bg:SetVertexColor(1, 1, 1, 0.7)

	if not frame.lineTop then
		frame.lineTop = frame:CreateTexture(nil, 'BACKGROUND')
	end
	frame.lineTop:SetDrawLayer('BACKGROUND', 2)
	frame.lineTop:SetTexture([[Interface\LevelUp\LevelUpTex]])
	frame.lineTop:ClearAllPoints()
	frame.lineTop:SetPoint('TOP', frame.bg, 0, 4)
	frame.lineTop:SetTexCoord(0.00195313, 0.81835938, 0.01953125, 0.03320313)
	frame.lineTop:Size(frame:GetWidth(), 7)

	if not frame.lineBottom then
		frame.lineBottom = frame:CreateTexture(nil, 'BACKGROUND')
	end
	frame.lineBottom:SetDrawLayer('BACKGROUND', 2)
	frame.lineBottom:SetTexture([[Interface\LevelUp\LevelUpTex]])
	frame.lineBottom:ClearAllPoints()
	frame.lineBottom:SetPoint('BOTTOM', frame.bg, 0, -2)
	frame.lineBottom:SetTexCoord(0.00195313, 0.81835938, 0.01953125, 0.03320313)
	frame.lineBottom:Size(frame:GetWidth(), 7)

	local ColorCode = {
		['red'] = {1, 0, 0},
		['green'] = {0, 1, 0},
		['blue'] = {0.15, 0.3, 1}
	}
	if topcolor then
		if type(topcolor) == 'table' then
			frame.lineTop:SetVertexColor(unpack(topcolor), 1)
		elseif type(topcolor) == 'string' then
			if ColorCode[topcolor] then
				local r, g, b = unpack(ColorCode[topcolor])
				frame.lineTop:SetVertexColor(r, g, b, 1)
			else
				error(format("Invalid color setting in |cff00FFFFLevelUpBG|r(frame, |cffFF0000topcolor|r, bottomcolor). |cffFFFF00\"%s\"|r is not a supported color.", topcolor))
				return
			end

		else
			error('Invalid color setting in |cff00FFFFLevelUpBG|r(frame, |cffFF0000topcolor|r, bottomcolor).')
			return
		end
	end
	if bottomcolor then
		if type(bottomcolor) == 'table' then
			frame.lineBottom:SetVertexColor(unpack(bottomcolor), 1)
		elseif type(bottomcolor) == 'string' then
			if ColorCode[bottomcolor] then
				local r, g, b = unpack(ColorCode[bottomcolor])
				frame.lineBottom:SetVertexColor(r, g, b, 1)
			else
				error(format("Invalid color setting in |cff00FFFFLevelUpBG|r(frame, [topcolor, |cffFF0000bottomcolor|r). |cffFFFF00\"%s\"|r is not a supported color.", topcolor))
				return
			end
		else
			error("Invalid color setting in |cff00FFFFLevelUpBG|r(frame, [topcolor, |cffFF0000bottomcolor|r).")
			return
		end
	end
end

--Add API
local function addapi(object)
	local mt = getmetatable(object).__index
	if not object.LevelUpBG then
		mt.LevelUpBG = LevelUpBG
	end
end

local handled = {['Frame'] = true}
local object = CreateFrame('Frame')
addapi(object)
addapi(object:CreateTexture())
addapi(object:CreateFontString())

object = EnumerateFrames()
while object do
	if not object:IsForbidden() and not handled[object:GetObjectType()] then
		addapi(object)
		handled[object:GetObjectType()] = true
	end

	object = EnumerateFrames(object)
end

--AF stuff
function SLE:IsFoolsDay()
	if strfind(date(), "04/01/") and not E.global.aprilFoolsSLE and not IsAddOnLoaded("ElvUI_SLE_Dev") then
		return true
	else
		return false
	end
end
