if SERVER then
	AddCSLuaFile()
end

ENT.Type = "anim"
ENT.WDJ_IsAudioSource = true

function ENT:SetupDataTables()
	self:NetworkVar("Int", 0, "price")
	self:NetworkVar("Entity", 0, "owning_ent")
	self:NetworkVar("Int", 1, "Channel")
end

wdj.DJ_AudioSources = {}
hook.Add(SERVER and "OnEntityCreated" or "NetworkEntityCreated", "wdj.DJAudioSourcesTable", function(ent)
	local class = ent:GetClass()
	if class == "wdj_audiosoource" or class == "wdj_radio" then
		wdj.DJ_AudioSources[ent:EntIndex()] = ent
	end
end)

hook.Add("EntityRemoved", "wdj.DJAudioSourcesTable", function(ent)
	wdj.DJ_AudioSources[ent:EntIndex()] = nil
end)

if DarkRP then 
	function ENT:PhysgunPickup(ply)
		return ply == self:Getowning_ent() or ply:IsAdmin()
	end

	function ENT:CanTool(ply, trace, mode)
		return mode == "remover" and ply == self:Getowning_ent() or mode == "remover" and ply:IsAdmin()
	end
end

ENT.Vol3dFadeMax = 1536

function ENT:GetVolumeFadeDistance()
	return self.Vol3dFadeMax
end

function ENT:IsAudioSourceEnabled()
	return true
end

if CLIENT then
	local local_ply
	local function EntIndexSorter(a, b)
		if a:GetClass() == "wdj_mastercontroller" then return false end
		if b:GetClass() == "wdj_mastercontroller" then return true end
		return a:EntIndex() < b:EntIndex()
	end

	wdj.gspcache = wdj.gspcache or {}
	function ENT:GetChannelSpeaker()
		local channel = self:GetChannel()
		local gspcache = wdj.gspcache
		local cached = gspcache[channel]
		if cached and cached.time > CurTime() - 1 then
			return unpack(cached.data)
		end
		local es = {}
		for _, s in pairs(wdj.GetAudioSources(channel)) do
			es[#es + 1] = s
		end
		table.sort(es, EntIndexSorter)
		local first = table.remove(es, 1)
		cached = cached or {data = {}}
		gspcache[channel] = cached
		cached.time = CurTime()
		cached.data[1] = first
		cached.data[2] = es
		return first, es
	end

	function ENT:GetRadioMaster()
		return wdj.GetMaster(self:GetChannel())
	end

	function ENT:ShouldPlay()
		return self:IsAudioSourceEnabled() and IsValid(self:GetRadioMaster()) and self:IsCloseEnough() and self:GetChannelSpeaker() == self and GetConVar("wdj_volume"):GetFloat() > 0
	end

	function ENT:IsCloseEnough()
		local_ply = local_ply or LocalPlayer()
		local ep = local_ply:EyePos()
		local volFadeDist = self:GetVolumeFadeDistance()
		local volFadeDistSquared = volFadeDist * volFadeDist
		if !self:IsDormant() and ep:DistToSqr(self:GetPos()) < volFadeDistSquared then return true end
		local main, others = self:GetChannelSpeaker()
		for _, o in pairs(others) do
			if IsValid(o) and !o:IsDormant() and ep:DistToSqr(o:GetPos()) < volFadeDistSquared then return true end
		end
		return false
	end

	ENT.Use3D = true

	local cvar_vol = CreateConVar("wdj_volume", 1, FCVAR_ARCHIVE)
	function ENT:Think()
		local tb = self:GetTable()
		if !tb then return end
		local master = self:GetRadioMaster()
		if !IsValid(master) then return end
		local link = master:GetCurMedia()
		local curTime = CurTime()
		local elapsed = curTime - (master:GetCurMediaStarted() or curTime)

		if IsValid(tb.Clip) then
			if !self:ShouldPlay() or !link or tb.Clip:getUrl() != link then
				tb.Clip:stop()
				tb.Clip = nil
			else
				if math.abs(elapsed - tb.Clip:getTime()) >= 3 then
					tb.Clip:seek(elapsed)
				end

				local vol = cvar_vol:GetFloat()
				tb.Clip:setVolume(vol)

				self:DelegateToMainSource()
			end
		end

		if !IsValid(tb.Clip) and link != "" and link != "--loading" and self:ShouldPlay() then
			local service = wdj.medialib.load("media").guessService(link)
			local opts = {use3D = tb.Use3D}
			if opts then
				opts.ent3D = self
				opts.fadeMax3D = tb.Vol3dFadeMax
			end
			tb.Clip = service:load(link, opts)
			tb.Clip:play()
			tb.Clip:seek(elapsed)
			self:PostMediaCreated(tb.Clip)
		end

		self:SetNextClientThink(curTime + 1)
		return true
	end

	function ENT:DelegateToMainSource()
		local tb = self:GetTable()
		local dt = tb.dt
		if !tb or !dt then return end

		if !IsValid(tb.Clip) then return end

		local main, others = self:GetChannelSpeaker()
		if self == main then
			local_ply = local_ply or LocalPlayer()
			local ep = local_ply:EyePos()
			local volFadeDist = dt.VolumeFadeDistance
			if tb.Use3D then
				local speaker, dist, volFadeDist = main, ep:DistToSqr(main:GetPos()), main:GetVolumeFadeDistance()
				local minFadedDist = (dist / volFadeDist) ^ 2
				for _,o in pairs(others) do
					local odist, ovolFadeDist
					if IsValid(o) then
						odist, ovolFadeDist = ep:DistToSqr(o:GetPos()), o:GetVolumeFadeDistance()
					else
						odist, ovolFadeDist = math.huge, 1024
					end
					local fadedDist = (odist / ovolFadeDist) ^ 2
					if fadedDist < minFadedDist then
						speaker, minFadedDist, volFadeDist = o, fadedDist, ovolFadeDist
					end
				end
				tb.Clip:set3DEnt(speaker)
				tb.Clip:set3DFadeMax(volFadeDist)
			else
				local vol = 0
				vol = vol + math.max(0, (1 - ep:Distance(main:GetPos()) / main:GetVolumeFadeDistance())) ^ 2
				for _,o in pairs(others) do
					if IsValid(o) then
						vol = vol + math.max(0, (1 - ep:Distance(o:GetPos()) / o:GetVolumeFadeDistance())) ^ 2
					end
				end
				local clip = tb.Clip
				clip.internalVolume = math.min(vol, 1)
				clip:applyVolume()
			end
		end
	end

	function ENT:PostMediaCreated()
	end

	function ENT:OnRemove()
		if IsValid(self.Clip) then
			self.Clip:stop()
		end
	end
end

if SERVER then
	function ENT:OnTakeDamage(dmg)
		self:TakePhysicsDamage(dmg)
	end
end