local cvar_outside_enable = CreateConVar("wcr_outsidecar", 1, FCVAR_ARCHIVE)
local cvar_outside_dist = CreateConVar("wcr_outsidecar_distance", 500, FCVAR_ARCHIVE)
local cvar_volume = CreateConVar("wcr_stereovolume", 100, FCVAR_ARCHIVE)
local cvar_non3dvolmul = CreateConVar("wcr_non3dvolumemult", 0.5, FCVAR_ARCHIVE)
local cvar_htmlvolmul = CreateConVar("wcr_htmlvolumemult", 0.5, FCVAR_ARCHIVE)
local cvar_no3dswitchreload = CreateConVar("wcr_no3dswitchreload", 0, FCVAR_ARCHIVE)
local cvar_disable3d = CreateConVar("wcr_disable3d", 0, FCVAR_ARCHIVE)

local function GetDriver(car)
	if !IsValid(car) then return end
	local plys = player.GetAll()
	for i=1, #plys do
		local plys_i = plys[i]
		if plys_i:GetVehicle() == car then
			return plys_i
		end
	end
end

local function Media_Stop(media)
	if media.Stop then
		media:Stop()
	else
		media:stop()
	end
end

local function Media_SetVolume(media, vol)
	if media.SetVolume then
		media:SetVolume(vol)
	else
		media:setVolume(vol * cvar_htmlvolmul:GetFloat())
	end
end

local Entity = FindMetaTable("Entity")

local PCS = wcr._PCS or {}
wcr._PCS = PCS

function Entity:WCR_RadioStop()
	if !self.WCR_Player then return end
	Media_Stop(self.WCR_Player)
	self.WCR_Player = nil
	self.WCR_PlayerCreated = nil
end

function Entity:WCR_RadioThink(isLocalCar, isThirdperson)
	local car = self
	if !IsValid(car) or car:IsDormant() then
		PCS[car] = nil
		car:WCR_RadioStop()
		return
	end
	local isPlaying = IsValid(car.WCR_Player)
	local shouldPlay = car:WCR_HasRadioCapability() and cvar_volume:GetFloat() > 0
	local station = car:WCR_GetChannel()
	local stat = wcr.AllStations and wcr.AllStations[station]
	local isWDJ = stat and stat.WDJ_Channel != nil
	local url = stat and stat.Link
	if (!url or url == "") and (!stat or !stat.WDJ_Channel) then
		shouldPlay = false
		PCS[car] = nil
	end
	if !isLocalCar and shouldPlay then
		local carpos = car:GetPos()
		local req_dist = cvar_outside_dist:GetFloat()
		if !isPlaying then
			req_dist = req_dist - 100
		end
		local play_outside = !isWDJ and cvar_outside_enable:GetBool() and carpos:DistToSqr(LocalPlayer():EyePos()) < req_dist * req_dist
		if !play_outside then
			shouldPlay = false
		end
	end
	if !shouldPlay then
		if isPlaying then
			car:WCR_RadioStop()
		end
		return
	end
	local hasUrlChanged = car.WCR_Url != url
	if isWDJ then
		local master = wdj.GetMaster(stat.WDJ_Channel)
		hasUrlChanged = IsValid(master) and (!IsValid(car.WCR_Player) or type(car.WCR_Player) != "table" or car.WCR_Player:getUrl() != master:GetCurMedia())
	end
	local should3D = !isLocalCar or (isThirdperson and !cvar_disable3d:GetBool())
	local has3DChanged = !isWDJ and IsValid(car.WCR_Player) and !cvar_no3dswitchreload:GetBool() and car.WCR_Player.Is3D and car.WCR_Player:Is3D() != should3D
	if isPlaying and !shouldPlay then
		car:WCR_RadioStop()
	elseif shouldPlay and (hasUrlChanged or has3DChanged or !isPlaying) and !car.WCR_StartingStereo then
		local only3DChanged = has3DChanged and !hasUrlChanged and isPlaying
		if IsValid(car.WCR_Player) and !only3DChanged then
			car:WCR_RadioStop()
		end
		if isWDJ then
			local master = wdj.GetMaster(stat.WDJ_Channel)
			if IsValid(master) then
				local link = master:GetCurMedia()
				if link != "" and link != "--loading" then
					local elapsed = CurTime() - (master:GetCurMediaStarted() or CurTime())
					local service = wdj.medialib.load("media").guessService(link)
					local mediaclip = service:load(link)
					mediaclip:seek(elapsed)
					mediaclip:play()
					car.WCR_Player = mediaclip
					car.WCR_PlayerCreated = CurTime()
					car.WCR_Url = nil
				end
			end
		else
			if hasUrlChanged then
				car.WCR_PlayAttempts = 0
			end
			if car.WCR_PlayAttempts >= 2 then return end
			car.WCR_StartingStereo = true
			car.WCR_Url = url
			local opts = should3D and "3d" or ""
			sound.PlayURL(url, opts, function(chan, err, errstr)
				if !IsValid(car) and IsValid(chan) then
					chan:Stop()
					car.WCR_StartingStereo = false
					return
				end
				if !IsValid(car) then return end
				if car:WCR_GetChannel() == 0 then
					car:WCR_RadioStop()
					car.WCR_StartingStereo = false
					return
				end
				if IsValid(car.WCR_Player) and only3DChanged then
					car:WCR_RadioStop()
				end
				if !IsValid(chan) then
					car.WCR_PlayAttempts = (car.WCR_PlayAttempts or 0) + 1
					car.WCR_StartingStereo = false
					return
				end
				car.WCR_StartingStereo = false
				car.WCR_Player = chan
				car.WCR_PlayerCreated = CurTime()
				car.WCR_PlayAttempts = 0
			end)
		end
	end
	if IsValid(car.WCR_Player) then
		local volumemul = 1
		if !isWDJ then
			if car.WCR_Player:Is3D() then
				if isLocalCar then
					car.WCR_Player:SetPos(LocalPlayer():EyePos())
					car.WCR_Player:Set3DFadeDistance(200, 1000000000)
				else
					car.WCR_Player:SetPos(car:GetPos())
					car.WCR_Player:Set3DFadeDistance(150, cvar_outside_dist:GetFloat())
				end
			else
				volumemul = volumemul * cvar_non3dvolmul:GetFloat()
			end
		end
		if !isLocalCar and car.WCR_PlayerCreated then
			local fadein = math.min((CurTime() - car.WCR_PlayerCreated) * 2, 1)
			volumemul = volumemul * fadein
		end
		local vol = cvar_volume:GetFloat() or 50
		vol = vol / 100
		vol = vol * volumemul
		vol = math.min(vol, 1)
		Media_SetVolume(car.WCR_Player, vol)
	end
end

hook.Add("EntityNetworkedVarChanged", "WCR_RadioChangeListener", function(ent, name)
	if name == "wcr_chan" then
		PCS[ent] = true
	end
end)

hook.Add("NetworkEntityCreated", "WCR_PCSAdd", function(ent)
	if ent:WCR_IsCarEntity() then
		PCS[ent] = true
	end
end)

hook.Add("NotifyShouldTransmit", "WCR_PCSUpdater", function(ent, shouldTransmit)
	if IsValid(ent) and ent:WCR_IsCarEntity() and shouldTransmit then
		PCS[ent] = true
	end
end)

local local_ply, localVeh, isThirdperson, isLocalCar
hook.Add("Think", "WCR_ActiveRadioUpdater", function()
	local_ply = local_ply or LocalPlayer()
	localVeh = local_ply:GetVehicle()
	localVeh = IsValid(localVeh) and localVeh:WCR_GetCarEntity()
	isThirdperson = IsValid(localVeh) and (!localVeh.GetThirdPersonMode or localVeh:GetThirdPersonMode())
	for car in pairs(PCS) do
		isLocalCar = localVeh == car
		car:WCR_RadioThink(isLocalCar, isThirdperson)
	end
end)

hook.Add("EntityRemoved", "WCR_RadioCleanup", function(ent)
	if IsValid(ent.WCR_Player) then
		Media_Stop(ent.WCR_Player)
	end
end)