local gas_cl_enabled = GetConVar("cl_tfa_fx_gasblur")
local oldshell_cl_enabled = GetConVar("cl_tfa_legacy_shells")

local ScrW, ScrH = ScrW, ScrH

local BindToKeyTBL = {
	["ctrl"] = KEY_LCONTROL,
	["rctrl"] = KEY_LCONTROL,
	["alt"] = KEY_LALT,
	["ralt"] = KEY_RALT,
	["space"] = KEY_SPACE,
	["caps"] = KEY_CAPSLOCK,
	["capslock"] = KEY_CAPSLOCK,
	["tab"] = KEY_TAB,
	["back"] = KEY_BACKSPACE,
	["backspace"] = KEY_BACKSPACE,
	[0] = KEY_0,
	[1] = KEY_1,
	[2] = KEY_2,
	[3] = KEY_3,
	[4] = KEY_4,
	[5] = KEY_5,
	[6] = KEY_6,
	[7] = KEY_7,
	[8] = KEY_8,
	[9] = KEY_9
}

local alphabet = "abcdefghijklmnopqrstuvwxyz"

for i = 1, string.len(alphabet) do
	local sstr = string.sub( alphabet, i, i )
	BindToKeyTBL[ sstr ] =  string.byte( sstr ) - 86
end


local SoundChars = {
	["*"] = "STREAM",--Streams from the disc and rapidly flushed; good on memory, useful for music or one-off sounds
	["#"] = "DRYMIX",--Skip DSP, affected by music volume rather than sound volume
	["@"] = "OMNI",--Play the sound audible everywhere, like a radio voiceover or surface.PlaySound
	[">"] = "DOPPLER",--Left channel for heading towards the listener, Right channel for heading away
	["<"] = "DIRECTIONAL",--Left channel = front facing, Right channel = read facing
	["^"] = "DISTVARIANT",--Left channel = close, Right channel = far
	["("] = "SPATIALSTEREO_LOOP",--Position a stereo sound in 3D space; broken
	[")"] = "SPATIALSTEREO",--Same as above but actually useful
	["}"] = "FASTPITCH",--Low quality pitch shift
	["$"] = "CRITICAL",--Keep it around in memory
	["!"] = "SENTENCE",--NPC dialogue
	["?"] = "USERVOX"--Fake VOIP data; not that useful
}
local DefaultSoundChar = ")"

local SoundChannels = {
	["shoot"] = CHAN_WEAPON,
	["shootwrap"] = CHAN_STATIC,
	["misc"] = CHAN_AUTO
}


--Scope

local cv_rt

function TFA.RTQuality()
	if not cv_rt then
		cv_rt = GetConVar("cl_tfa_3dscope_quality")
	end

	return math.Clamp(cv_rt:GetInt(), cv_rt:GetMin(), cv_rt:GetMax())
end

--Sensitivity

local ss, fov_og, resrat, fov_cv

fov_cv = GetConVar("fov_desired")
function TFA.CalculateSensitivtyScale( fov_target, fov_src, screenscale )
	if not LocalPlayer():IsValid() then return 1 end
	resrat = ScrW() / ScrH()
	fov_og = fov_src or TFADUSKFOV or fov_cv:GetFloat()
	ss = screenscale or 1
	return math.Clamp(math.atan( resrat * math.tan(math.rad( fov_target / 2  ) ) ) / math.atan( resrat * math.tan( math.rad( fov_og / 2) ) ) / ss, 0, 1)
end

--Ammo

local AmmoTypesByName = {}
local AmmoTypesAdded = {}

function TFA.AddAmmo(id, name)
	if not AmmoTypesAdded[id] then
		AmmoTypesAdded[id] = true

		game.AddAmmoType({
			name = id
		})
	end

	if name and language then
		language.Add(id .. "_ammo", name)
	end

	if name then
		AmmoTypesByName[name] = AmmoTypesByName[name] or id

		return AmmoTypesByName[name]
	end

	return id
end

--Particles

function TFA.ParticleTracer( name,startPos,endPos,doWhiz,ent,att)
	if type(ent) ~= "number" and IsValid(ent) and ent.EntIndex then
		ent = ent:EntIndex()
	end
	if ent then
		att = att or -1
		return util.ParticleTracerEx(name,startPos,endPos,doWhiz,ent,att)
	else
		return util.ParticleTracerEx(name,startPos,endPos,doWhiz,0,-1)
	end
end

--Binds

function TFA.BindToKey( bind, default )
	return BindToKeyTBL[ string.lower( bind ) ] or default or KEY_C
end

--Sounds

function TFA.PatchSound( path, kind )
	local pathv
	local c = string.sub(path,1,1)

	if SoundChars[c] then
		pathv = string.sub( path, 2, string.len(path) )
	else
		pathv = path
	end

	local kindstr = kind
	if not kindstr then
		kindstr = DefaultSoundChar
	end
	if string.len(kindstr) > 1 then
		local found = false
		for k,v in pairs( SoundChars ) do
			if v == kind then
				kindstr = k
				found = true
				break
			end
		end
		if not found then
			kindstr = DefaultSoundChar
		end
	end

	return kindstr .. pathv
end

function TFA.AddSound( name, channel, volume, level, pitch, wave, char )
	char = char or ""

	local SoundData = {
		name = name,
		channel = channel or CHAN_AUTO,
		volume = volume or 1,
		level = level or 75,
		pitch = pitch or 100
	}

	if char ~= "" then
		if type(wave) == "string" then
			wave = TFA.PatchSound(wave, char)
		elseif type(wave) == "table" then
			local patchWave = table.Copy(wave)

			for k, v in pairs(patchWave) do
				patchWave[k] = TFA.PatchSound(v, char)
			end

			wave = patchWave
		end
	end

	SoundData.sound = wave

	sound.Add(SoundData)
end

function TFA.AddFireSound( id, path, wrap, kindv )
	kindv = kindv or ")"

	TFA.AddSound(id, wrap and SoundChannels.shootwrap or SoundChannels.shoot, 1, 120, {97, 103}, path, kindv)
end

function TFA.AddWeaponSound( id, path, kindv )
	kindv = kindv or ")"

	TFA.AddSound(id, SoundChannels.misc, 1, 80, {97, 103}, path, kindv)
end

--Frametime

--CVar Mediators
function TFA.GetGasEnabled()
	local enabled = false

	if gas_cl_enabled then
		enabled = gas_cl_enabled:GetBool()
	end

	return enabled
end

function TFA.GetLegacyShellsEnabled()
	local enabled = false

	if oldshell_cl_enabled then
		enabled = oldshell_cl_enabled:GetBool()
	end

	return enabled
end

local ejectionsmoke_cl_enabled = GetConVar("cl_tfa_fx_ejectionsmoke")
local muzzlesmoke_cl_enabled = GetConVar("cl_tfa_fx_muzzlesmoke")

function TFA.GetMZSmokeEnabled()
	local enabled = false

	if muzzlesmoke_cl_enabled then
		enabled = muzzlesmoke_cl_enabled:GetBool()
	end

	return enabled
end

function TFA.GetEJSmokeEnabled()
	local enabled = false

	if ejectionsmoke_cl_enabled then
		enabled = ejectionsmoke_cl_enabled:GetBool()
	end

	return enabled
end

local muzzleflashsmoke_cl_enabled = GetConVar("cl_tfa_fx_muzzleflashsmoke")

function TFA.GetMZFSmokeEnabled()
	local enabled = false

	if muzzleflashsmoke_cl_enabled then
		enabled = muzzleflashsmoke_cl_enabled:GetBool()
	end

	return enabled
end

local ricofx_cl_enabled = GetConVar("cl_tfa_fx_impact_ricochet_enabled")

function TFA.GetRicochetEnabled()
	local enabled = false

	if ricofx_cl_enabled then
		enabled = ricofx_cl_enabled:GetBool()
	end

	return enabled
end

--Local function for detecting TFA Base weapons.
function TFA.PlayerCarryingTFAWeapon(ply)
	if not ply then
		if CLIENT then
			if LocalPlayer():IsValid() then
				ply = LocalPlayer()
			else
				return false, nil, nil
			end
		elseif game.SinglePlayer() then
			ply = Entity(1)
		else
			return false, nil, nil
		end
	end

	if not (IsValid(ply) and ply:IsPlayer() and ply:Alive()) then return end
	local wep = ply:GetActiveWeapon()

	if IsValid(wep) then
		if (wep.IsTFAWeapon) then return true, ply, wep end

		return false, ply, wep
	end

	return false, ply, nil
end

local sv_cheats = GetConVar("sv_cheats")
local host_timescale = GetConVar("host_timescale")

function TFA.FrameTime()
	return engine.TickInterval() * game.GetTimeScale() * (sv_cheats:GetBool() and host_timescale:GetFloat() or 1)
end

local buffer = {}

function TFA.tbezier(t, values, amount)
	assert(isnumber(t), 't is not a number')
	assert(t >= 0 and t <= 1, '0 <= t <= 1!')
	assert(#values >= 2, 'at least two values must be provided')
	amount = amount or #values
	local a, b = values[1], values[2]

	-- linear
	if amount == 2 then
		return a + (b - a) * t
	-- square
	elseif amount == 3 then
		return (1 - t) * (1 - t) * a + 2 * t * (1 - t) * b + t * t * values[3]
	-- cube
	elseif amount == 4 then
		return (1 - t) * (1 - t) * (1 - t) * a + 3 * t * (1 - t) * (1 - t) * b + 3 * t * t * (1 - t) * values[3] + t * t * t * values[4]
	end

	for point = 1, amount do
		local point1 = values[point]
		local point2 = values[point + 1]
		if not point2 then break end
		buffer[point] = point1 + (point2 - point1) * t
	end

	return TFA.tbezier(t, buffer, amount - 1)
end

if DLib and math.tbezier and isfunction(math.tbezier) then
	TFA.tbezier = math.tbezier
end

function TFA.Quintic(t)
	return t * t * t * (t * (t * 6 - 15) + 10)
end

function TFA.Cosine(t)
	return (1 - math.cos(t * math.pi)) / 2
end

function TFA.Sinusine(t)
	return (1 - math.sin(t * math.pi)) / 2
end

function TFA.Cubic(t)
	return -2 * t * t * t + 3 * t * t
end

function TFA.UnfoldBaseClass(tableInput, tableOutput)
	if tableOutput == nil then tableOutput = tableInput end
	if not istable(tableInput) or not istable(tableOutput) then return tableOutput end

	if tableInput ~= tableOutput then
		for k, v in pairs(tableInput) do
			if tableOutput[k] == nil then
				tableOutput[k] = v
			end
		end
	end

	if istable(tableInput.BaseClass) then
		TFA.UnfoldBaseClass(tableInput.BaseClass, tableOutput)
	end

	return tableOutput
end

if CLIENT then
	local cvar_scale = GetConVar("cl_tfa_hud_scale")
	local sscache = {}

	function TFA.ScaleH(num)
		if not sscache[num] then
			sscache[num] = num * (ScrH() / 1080) * cvar_scale:GetFloat()
		end

		return sscache[num]
	end

	local function EmptyCache()
		sscache = {}
	end

	cvars.AddChangeCallback("cl_tfa_hud_scale", EmptyCache, "TFA_ClearSScaleCache")
	hook.Add("OnScreenSizeChanged", "_TFA_DropSScaleCache", EmptyCache)

	local color_black = Color(0, 0, 0, 255)
	function TFA.DrawTextShadowed(text, font, x, y, color, shadowlength, shadowcolor)
		shadowlength = shadowlength or 2
		shadowcolor = shadowcolor or ColorAlpha(color_black, color.a)

		surface.SetFont(font)

		surface.SetTextPos(x + shadowlength, y + shadowlength)
		surface.SetTextColor(shadowcolor.r, shadowcolor.g, shadowcolor.b, shadowcolor.a)
		surface.DrawText(text)

		surface.SetTextPos(x, y)
		surface.SetTextColor(color.r, color.g, color.b, color.a)
		surface.DrawText(text)
	end
end
