permissions = {
	["player.ban.temporary"] = false,
	["player.ban.permanent"] = false,
	["player.ban.view"] = false,
	["player.ban.edit"] = false,
	["player.ban.remove"] = false,
	["player.kick"] = false,
	["player.spectate"] = false,
	["player.teleport.single"] = false,
	["player.slap"] = false,
	["player.freeze"] = false,
	["player.bucket.join"] = false,
	["player.bucket.force"] = false,
	["player.screenshot"] = false,
	["player.mute"] = false,
	["player.warn"] = false,
	["player.teleport.everyone"] = false,
	["player.reports.view"] = false,
	["player.reports.claim"] = false,
	["player.reports.process"] = false,
	
	["server.cleanup.cars"] = false,
	["server.cleanup.props"] = false,
	["server.cleanup.peds"] = false,
	["server.shortcut.add"] = false,
	["server.reminder.add"] = false,
	["server.announce"] = false,
	["server.convars"] = false,
	["server.resources.start"] = false,
	["server.resources.stop"] = false,
	["server.chat"] = false,
	
	["immune"] = false,
	["anon"] = false,
}


function PrintDebugMessage(msg,level)
	loglevel = (GetConvarInt("ea_logLevel", 1))
	if not level or not tonumber(level) then level = 3 end
	
	if level == 1 and loglevel >= level then -- ERROR Loglevel
		Citizen.Trace("^1"..GetCurrentResourceName().."^7: "..msg.."^7\n")

		if IsDuplicityVersion() then
			for i,k in pairs(GetOnlineAdmins()) do
				TriggerClientEvent("EasyAdmin:showNotification", i, string.gsub(msg, "%^%d", ""))
			end
		else
			TriggerEvent("EasyAdmin:showNotification", string.gsub(msg, "%^%d", ""))
		end
	elseif level == 2 and loglevel >= level then -- WARN Loglevel
		Citizen.Trace("^3"..GetCurrentResourceName().."^7: "..msg.."^7\n")
	elseif level == 3 and loglevel >= level then -- INFO Loglevel 
		Citizen.Trace("^0"..GetCurrentResourceName().."^7: "..msg.."^7\n")
	elseif level == 4 and loglevel >= level then -- DEV Loglevel
		Citizen.Trace("^7"..GetCurrentResourceName().."^7: "..msg.."^7\n")
	elseif level > 4 and loglevel >= level then -- anything above 4 shouldn't exist, but kept just in case
		Citizen.Trace("^5"..GetCurrentResourceName().."^7: "..msg.."^7\n")
	end
end

if IsDuplicityVersion() then
	if GetConvar("ea_enableDebugging", "false") ~= "false" or GetConvarInt("ea_logLevel", 1) ~= 1 then
		SetConvar("ea_enableDebugging", "false")
		if GetConvarInt("ea_logLevel", 1) == 1 then
			SetConvar("ea_logLevel", 3)
		end
		if GetConvarInt("ea_logLevel", 1) > 1 then
			PrintDebugMessage("Debug Messages Enabled, Verbosity is ^2"..GetConvarInt("ea_logLevel", 1).."^7.", 2)
		end
	else
		enableDebugging = false
	end
end

if not IsDuplicityVersion() then
	RegisterNUICallback("keyboardFinished", function(data, cb)
		keyboardResult = data.result
		keyboardState = data.state
		cb('ok')
	end)

	function ttsSpeechItem(item)
		local ttsText = ""
		if not item or GetResourceKvpInt('ea_tts') == 0 then return end
		if type(item.Text) == "table" then
			if item.Text._Text then
				ttsText = item.Text._Text
				if item.Label then
					ttsText = ttsText .. ", " .. item.Label.Text._Text
				end
			end
		elseif type(item.Text) == "function" then
			ttsText = item.Base.Text._Text
			if item.Checked == true then
				ttsText = ttsText .. ", Checked"
			elseif item.Checked == false then
				ttsText = ttsText .. ", Unchecked"
			end
			if item.ItemText then 
				ttsText = ttsText .. ", " .. item.ItemText._Text
			end
		end
		SendNUIMessage({action= "speak", text=ttsText})
	end
	
	function ttsSpeechText(text)
		if not text or GetResourceKvpInt('ea_tts') == 0 then return end
		SendNUIMessage({action= "speak", text=text})
	end	
end


function displayKeyboardInput(title,default,maxLength)
	if alreadyTyping then return nil end
	keyboardResult, keyboardState = nil
	local label = GetLabelText(title)

	SetNuiFocus(true, true)
	SendNUIMessage({action= "open", title=label, default=default, maxLength=maxLength, resource=GetCurrentResourceName()})

	alreadyTyping = true

	while not keyboardState do --While typing is not aborted and not finished, this loop waits
		Citizen.Wait(0)
	end

	alreadyTyping = false
	SetNuiFocus(false,false)
	if keyboardState == 0 then
		return keyboardResult
	else
		return nil
	end
--[[ -- default V Input
	DisplayOnscreenKeyboard(1, title, "", default, "", "", "", maxLength)

	while UpdateOnscreenKeyboard() ~= 1 and UpdateOnscreenKeyboard() ~= 2 do --While typing is not aborted and not finished, this loop waits
		Citizen.Wait(0)
	end
		
	if UpdateOnscreenKeyboard() ~= 2 then
		local result = GetOnscreenKeyboardResult()
		return result
	else
		return nil
	end
]]
end

function copyToClipboard(text)
	SendNUIMessage({action= "clip", text=text})
	TriggerEvent("EasyAdmin:showNotification", GetLocalisedText("copiedtoclipboard"))
end

function DoesPlayerHavePermission(player, object)
	if IsDuplicityVersion() then
		local haspermission = false
		if (player == 0 or player == "") then
			return true
		end-- Console. It's assumed this will be an admin with access.
		
		if not string.find(object, "easyadmin.") then -- compatability with outdated plugins
			object = "easyadmin."..object
		end
		
		if IsPlayerAceAllowed(player,object) then -- check if the player has access to this permission
			haspermission = true
			PrintDebugMessage(getName(player, true).." has Permissions for "..object..".", 4)
		else
			haspermission = false
			PrintDebugMessage(getName(player, true).." does not have Permissions for "..object..".", 4)
		end
		return haspermission
	else
		return (permissions[object] or false)
	end
end
exports('DoesPlayerHavePermission', DoesPlayerHavePermission)

function DoesPlayerHavePermissionForCategory(player, object)
	for perm in pairs(permissions) do
		if string.startswith(perm, object) then
			if DoesPlayerHavePermission(player, perm) then
				return true
			end
		end
	end
	return false
end
exports('DoesPlayerHavePermissionForCategory', DoesPlayerHavePermissionForCategory)


function GetVersion()
	local resourceName = GetCurrentResourceName()
	local version = GetResourceMetadata(resourceName, 'version', 0)
	local is_master = GetResourceMetadata(resourceName, 'is_master', 0) == "yes" or false
	return version, is_master
end
exports('GetVersion', GetVersion)


function loadLanguageStrings()
	local strfile = LoadResourceFile(GetCurrentResourceName(), "language/"..GetConvar("ea_LanguageName", "en")..".json")
	if strfile then
		strings = json.decode(strfile)[1]
	else
		strings = {language=GetConvar("ea_LanguageName", "en")}
	end
end

function GetLocalisedText(string)
	if not strings then return "Strings not Loaded yet!" end
	if not string then return "No String!" end
	if strings[string] then
		return strings[string]
	else
		return "String "..string.." not found in "..strings.language
	end
end
exports('GetLocalisedText', GetLocalisedText)

function formatDateString(string)
	local dateFormat = GetConvar("ea_dateFormat", '%d/%m/%Y 	%H:%M:%S')
	return os.date(dateFormat, string)
end
exports('formatDateString', formatDateString)

function formatShortcuts(thisstring)
	if not thisstring then return thisstring end
	local cleanString = string.gsub(string.lower(thisstring), " ", "")
	for shortcut,value in pairs(MessageShortcuts) do
		if string.lower(shortcut) == cleanString then
			thisstring = value
		end
	end
	return thisstring
end
exports('formatShortcuts', formatShortcuts)

function formatRightString(thisstring, customWidth)
	if not thisstring then return thisstring end -- in case string is nil, just yeet it back.
	local width = (customWidth or maxRightTextWidth)
	if string.len(thisstring) > width then
		thisstring = string.sub(thisstring, 1, width)..".."
	end

	return thisstring
end


-- some util funcs so i dont have to mess with NativeUI Source Code.
function getMenuItemTitle(item)
	if (item.Base and type(item.Base.Text) == "table" and item.Base.Text._Text) then
		return item.Base.Text._Text
	elseif (item.Text and type(item.Text) == "table" and item.Text._Text) then
		return item.Text._Text
	end
end

function setMenuItemTitle(item,text)
	if (item.Base and type(item.Base.Text) == "table" and item.Base.Text._Text) then
		item.Base.Text._Text = text
	elseif (item.Text and type(item.Text) == "table" and item.Text._Text) then
		item.Text._Text = text
	end
end

function getCachedPlayer(playerId)
	if CachedPlayers[playerId] then
		return CachedPlayers[playerId]
	else
		return false
	end
end
exports('getCachedPlayer', getCachedPlayer)

function math.round(num, numDecimalPlaces)
	if numDecimalPlaces and numDecimalPlaces>0 then
		local mult = 10^numDecimalPlaces
		return math.floor(num * mult + 0.5) / mult
	end
	return math.floor(num + 0.5)
end

function string.split(inputstr, sep)
	if sep == nil then
		sep = "%s"
	end
	local t={} ; i=1
	for str in string.gmatch(inputstr, "([^"..sep.."]+)") do
		t[i] = str
		i = i + 1
	end
	return t
end

function string.reverse(s)
	local r = ""
	for p,c in utf8.codes(s) do
		r = utf8.char(c)..r
	end
	return r
end

function string.startswith(string,start)
	return string:sub(1,string.len(start))==start
 end


--- http://www.lua.org/pil/11.5.html
function Set (list)
	local set = {}
	for _, l in ipairs(list) do set[l] = true end
	return set
end

-- Convert a lua table into a lua syntactically correct string
function table_to_string(tbl)
	return json.encode(tbl)
end

function mergeTables(t1, t2)
	local t = t1
	for i,v in pairs(t2) do
		table.insert(t, v)
	end
	return t
end

-- terrible function to look for URLs in a string
function matchURL(text_with_URLs)
	
	local domains = [[.ac.ad.ae.aero.af.ag.ai.al.am.an.ao.aq.ar.arpa.as.asia.at.au.aw.ax.az.ba.bb.bd.be.bf.bg.bh.bi.biz.bj.bm.bn.bo.br.bs.bt.bv.bw.by.bz.ca.cat.cc.cd.cf.cg.ch.ci.ck.cl.cm.cn.co.com.coop.cr.cs.cu.cv.cx.cy.cz.dd.de.dj.dk.dm.do.dz.ec.edu.ee.eg.eh.er.es.et.eu.fi.firm.fj.fk.fm.fo.fr.fx.ga.gb.gd.ge.gf.gh.gi.gl.gm.gn.gov.gp.gq.gr.gs.gt.gu.gw.gy.hk.hm.hn.hr.ht.hu.id.ie.il.im.in.info.int.io.iq.ir.is.it.je.jm.jo.jobs.jp.ke.kg.kh.ki.km.kn.kp.kr.kw.ky.kz.la.lb.lc.li.lk.lr.ls.lt.lu.lv.ly.ma.mc.md.me.mg.mh.mil.mk.ml.mm.mn.mo.mobi.mp.mq.mr.ms.mt.mu.museum.mv.mw.mx.my.mz.na.name.nato.nc.ne.net.nf.ng.ni.nl.no.nom.np.nr.nt.nu.nz.om.org.pa.pe.pf.pg.ph.pk.pl.pm.pn.post.pr.pro.ps.pt.pw.py.qa.re.ro.ru.rw.sa.sb.sc.sd.se.sg.sh.si.sj.sk.sl.sm.sn.so.sr.ss.st.store.su.sv.sy.sz.tc.td.tel.tf.tg.th.tj.tk.tl.tm.tn.to.tp.tr.travel.tt.tv.tw.tz.ua.ug.uk.um.us.uy.va.vc.ve.vg.vi.vn.vu.web.wf.ws.xxx.ye.yt.yu.za.zm.zr.zw]]
		
	local tlds = {}
	for tld in domains:gmatch'%w+' do
		tlds[tld] = true
	end
	local function max4(a,b,c,d) return math.max(a+0, b+0, c+0, d+0) end
	local protocols = {[''] = 0, ['http://'] = 0, ['https://'] = 0, ['ftp://'] = 0}
	local finished = {}
	
	for pos_start, url, prot, subd, tld, colon, port, slash, path in
	text_with_URLs:gmatch'()(([%w_.~!*:@&+$/?%%#-]-)(%w[-.%w]*%.)(%w+)(:?)(%d*)(/?)([%w_.~!*:@&+$/?%%#=-]*))'
	do
		if protocols[prot:lower()] == (1 - #slash) * #path and not subd:find'%W%W'
		and (colon == '' or port ~= '' and port + 0 < 65536)
		and (tlds[tld:lower()] or tld:find'^%d+$' and subd:find'^%d+%.%d+%.%d+%.$'
		and max4(tld, subd:match'^(%d+)%.(%d+)%.(%d+)%.$') < 256)
		then
			finished[pos_start] = true
			return url
		end
	end
	
	for pos_start, url, prot, dom, colon, port, slash, path in
	text_with_URLs:gmatch'()((%f[%w]%a+://)(%w[-.%w]*)(:?)(%d*)(/?)([%w_.~!*:@&+$/?%%#=-]*))'
	do
		if not finished[pos_start] and not (dom..'.'):find'%W%W'
		and protocols[prot:lower()] == (1 - #slash) * #path
		and (colon == '' or port ~= '' and port + 0 < 65536)
		then
			return url
		end
	end
end
exports('matchURL', matchURL)