local webaudio = include("pac3/libraries/webaudio.lua")
pac.webaudio2 = webaudio
local BUILDER, PART = pac.PartTemplate("base_movable")

PART.FriendlyName = "web sound"
PART.ClassName = "sound2"

PART.Icon = 'icon16/music.png'
PART.Group = 'effects'

PART.ImplementsDoubleClickSpecified = true

BUILDER:StartStorableVars()
	BUILDER:SetPropertyGroup("generic")
		BUILDER:GetSet("Path", "", {editor_panel = "sound"})
		BUILDER:GetSet("AllPaths", "", {hide_in_editor = true})
		BUILDER:GetSet("Volume", 1, {editor_sensitivity = 0.25})
		BUILDER:GetSet("Pitch", 1, {editor_sensitivity = 0.125})
		BUILDER:GetSet("Radius", 1500)
		BUILDER:GetSet("Doppler", false)
		BUILDER:GetSet("MinPitch", 0, {editor_sensitivity = 0.125})
		BUILDER:GetSet("MaxPitch", 0, {editor_sensitivity = 0.125})

	BUILDER:SetPropertyGroup("playback")
		BUILDER:GetSet("PlayCount", 1,
			{editor_onchange =
			function(self, num)
				self.sens = 0.25
				num = tonumber(num)
				return math.Round(math.max(num, 0))
			end, editor_friendly = "PlayCount (0=loop)"}
		)
		BUILDER:GetSet("Sequential",false,{description = "if there are multiple sounds (separated by ; ), plays these sounds in sequential order instead of randomly"})
		BUILDER:GetSet("SequentialStep", 1,
			{editor_onchange =
			function(self, num)
				self.sens = 0.25
				num = tonumber(num)
				return math.Round(num)
			end})
		BUILDER:GetSet("StopOnHide", false)
		BUILDER:GetSet("PauseOnHide", false)
		BUILDER:GetSet("Overlapping", false)
		BUILDER:GetSet("PlayOnFootstep", false)

	BUILDER:SetPropertyGroup("filter")
		BUILDER:GetSet("FilterType", 0, {enums = {
			none = "0",
			lowpass = "1",
			highpass = "2",
		}})
		BUILDER:GetSet("FilterFraction", 1, {editor_sensitivity = 0.125, editor_clamp = {0, 1}})

	BUILDER:SetPropertyGroup("echo")
		BUILDER:GetSet("Echo", false)
		BUILDER:GetSet("EchoDelay", 0.5, {editor_sensitivity = 0.125})
		BUILDER:GetSet("EchoFeedback", 0.75, {editor_sensitivity = 0.125})

	BUILDER:SetPropertyGroup("lfo")
		BUILDER:GetSet("PitchLFOAmount", 0, {editor_sensitivity = 0.125, editor_friendly = "pitch amount"})
		BUILDER:GetSet("PitchLFOTime", 0, {editor_sensitivity = 0.125, editor_friendly = "pitch time"})

		BUILDER:GetSet("VolumeLFOAmount", 0, {editor_sensitivity = 0.125, editor_friendly = "volume amount"})
		BUILDER:GetSet("VolumeLFOTime", 0, {editor_sensitivity = 0.125, editor_friendly = "volume time"})

BUILDER:EndStorableVars()

function PART:Initialize()
	webaudio.Initialize()
	self.streams = {}
end

function PART:GetNiceName()
	local path = self:GetPath() .. ";"
	local tbl = {}
	for i, path in ipairs(path:Split(";")) do
		if path ~= "" then
			if path:StartWith("http") then
				path = path:gsub("%%(..)", function(char)
					local num = tonumber("0x" .. char)
					if num then
						return string.char(num)
					end
				end)
			end
			tbl[i] = pac.PrettifyName(("/".. path):match(".+/(.-)%.") or path:match("(.-)%.")) or "sound"
		end
	end
	return table.concat(tbl, ";")
end

local stream_vars = {}

local BIND = function(propertyName, setterMethodName, check)
	table.insert(stream_vars, propertyName)
	setterMethodName = setterMethodName or "Set" .. propertyName
	PART["Set" .. propertyName] = function(self, value)
		if check then
			value = check(value)
		end

		for url, stream in pairs(self.streams) do
			if stream:IsValid() then
				stream[setterMethodName](stream, value)
			else
				self.streams[url] = nil
			end
		end

		self[propertyName] = value
	end
end

BIND("Pitch",     "SetPlaybackRate")
BIND("PlayCount", "SetMaxLoopCount" )
BIND("Volume",    nil, function(n) return math.Clamp(n, 0, 4) end)
BIND("Radius",    "SetSourceRadius" )

BIND("FilterType")
BIND("FilterFraction")

BIND("Echo")
BIND("EchoDelay")
BIND("EchoFeedback", nil, function(n) return math.Clamp(n, 0, 0.99) end)

BIND("PitchLFOAmount")
BIND("PitchLFOTime")

BIND("VolumeLFOAmount")
BIND("VolumeLFOTime")

BIND("Doppler")

function PART:Silence(b)
	if b then
		if self.last_stream and self.last_stream.SetVolume then self.last_stream:SetVolume(0) end
	else
		if self.last_stream and self.last_stream.SetVolume then self.last_stream:SetVolume(self.Volume * pac.volume) end
	end
end

function PART:OnThink()
	local owner = self:GetRootPart():GetOwner()
	local pos = self:GetWorldPosition()
	if pos:DistToSqr(pac.EyePos) > pac.sounds_draw_dist_sqr then
		self.out_of_range = true
		self:Silence(true)
	else
		if self.out_of_range then self:Silence(false) end
		self.out_of_range = false
	end

	for url, stream in pairs(self.streams) do
		if not stream:IsValid() then self.streams[url] = nil goto CONTINUE end

		if self.PlayCount == 0 and not self.stopsound then
			stream:Resume()
		end

		if stream.owner_set ~= owner and owner:IsValid() then
			stream:SetSourceEntity(owner, true)
			stream.owner_set = owner
		end
		::CONTINUE::
	end

	if self.last_playonfootstep ~= self.PlayOnFootstep then
		local ent = self:GetOwner()
		if ent:IsValid() and ent:IsPlayer() then
			ent.pac_footstep_override = ent.pac_footstep_override or {}

			if self.PlayOnFootstep then
				ent.pac_footstep_override[self.UniqueID] = self
			else
				ent.pac_footstep_override[self.UniqueID] = nil
			end

			if table.Count(ent.pac_footstep_override) == 0 then
				ent.pac_footstep_override = nil
			end

			self.last_playonfootstep = self.PlayOnFootstep
		end
	end
end

function PART:SetPath(path)
	if #path > 1024 then
		self:AttachEditorPopup("This part has more sounds than the 1024-letter limit! Please do not touch the path field now!")
		self:SetInfo("This part has more sounds than the 1024-letter limit! Please do not touch the path field now!")
		if self.Name == "" then
			self:SetName("big sound list")
			pace.RefreshTree()
		end
	end

	self.seq_index = 1
	self.Path = path

	local paths = {}

	for _, path in ipairs(path:Split(";")) do
		local min, max = path:match(".+%[(.-),(.-)%]")

		min = tonumber(min)
		max = tonumber(max)

		if min and max then
			for i = min, max do
				table.insert(paths, (path:gsub("%[.-%]", i)))
				self.AllPaths = self.AllPaths .. ";" .. path
			end
		else
			table.insert(paths, path)
			self.AllPaths = self.AllPaths .. ";" .. path
		end

	end

	for _, stream in pairs(self.streams) do
		if stream:IsValid() then
			stream:Remove()
		end
	end

	self.streams = {}

	local function load(path)
		local stream = webaudio.CreateStream(path)
		self.streams[path] = stream

		stream:Set3D(true)
		stream.OnLoad = function()
			for _, key in ipairs(stream_vars) do
				self["Set" .. key](self, self["Get" .. key](self))
			end
		end
		stream.OnError =  function(_, err, info)
			info = info or "unknown error"
			if self:IsValid() and pac.LocalPlayer == self:GetPlayerOwner() and pace and pace.IsActive() then
				if pace and pace.current_part == self and not IsValid(pace.BusyWithProperties) then
					pace.MessagePrompt(err .. "\n" .. info, "OGG error for" .. path, "OK")
				else
					pac.Message("OGG error: ", err, " reason: ", err .. "\n" .. info, "OGG error for" .. path)
					self:SetError("OGG error: " .. err .. "\n" .. info .. "\nfor:" .. path)
				end
			end
		end

		stream.UpdateSourcePosition = function()
			if self:IsValid() then
				stream.SourcePosition = self:GetDrawPosition()
			end
		end

		if
			pace and
			pace.Editor:IsValid() and
			pace.current_part:IsValid() and
			pace.current_part.ClassName == "ogg2" and
			self:GetPlayerOwner() == pac.LocalPlayer
		then
			stream:Play()
		end
	end

	for _, path in ipairs(paths) do
		local info = sound.GetProperties(path)
		if info then
			path = info.sound
		end

		if not string.StartsWith(path, "http") or not pac.resource.Download(path, function(path) load("data/" .. path) end)

			then load("sound/" .. path)
		end
	end
	self.paths = paths

end

PART.last_stream = NULL

function PART:UpdateSoundsFromAll()
	self:SetPath(self.AllPaths)
end

function PART:PlaySound(_, additiveVolumeFraction)
	--PrintTable(self.streams)
	additiveVolumeFraction = additiveVolumeFraction or 0
	local pos = self:GetWorldPosition()
	if pos:DistToSqr(pac.EyePos) > pac.sounds_draw_dist_sqr then return end

	local stream = table.Random(self.streams) or NULL
	if not stream:IsValid() then return end

	if self.Sequential then

		self.seq_index = self.seq_index or 1

		local basepath = self.paths[self.seq_index] or self.paths[1]
		local snd = "sound/".. basepath

		local cached_path = "data/pac3_cache/downloads/" .. pac.Hash(basepath) .. ".dat"

		if string.find(basepath, "^http") then
			snd = cached_path
		end

		if self.streams[snd]:IsValid() then
			stream = self.streams[snd]
			--print(snd,self.seq_index)
		end
		self.seq_index = self.seq_index + self.SequentialStep
		if self.seq_index > #self.paths then
			self.seq_index = self.seq_index - #self.paths
		elseif self.seq_index < 1 then
			self.seq_index = self.seq_index + #self.paths
		end
	end

	stream:SetAdditiveVolumeModifier(additiveVolumeFraction)

	if self.last_stream:IsValid() and not self.Overlapping and not self.PauseOnHide  then
		self.last_stream:Stop()
	end

	if self.MinPitch ~= self.MaxPitch then
		stream:SetAdditivePitchModifier(math.Rand(self.MinPitch, self.MaxPitch))
	else
		stream:SetAdditivePitchModifier(0)
	end

	if self.PauseOnHide then
		stream:Resume()
	else
		stream:Play()
	end

	self.last_stream = stream
end

function PART:StopSound(force_stop)
	for key, stream in pairs(self.streams) do
		if stream:IsValid() then
			if self.PauseOnHide then
				stream:Pause()
			elseif self.StopOnHide then
				stream:Stop()
			end
			if force_stop then stream:Stop() self.stopsound = true end
		end
	end
end

function PART:OnShow(from_rendering)
	if not from_rendering then
		self:PlaySound()
	end
	self.stopsound = false
end

function PART:OnDoubleClickSpecified()
	if self.playing then
		self:StopSound(true)
		self.playing = false
	else
		self:PlaySound()
		self.playing = true
	end
end

function PART:OnHide()
	self:StopSound()
end

function PART:OnRemove()
	for key, stream in pairs(self.streams) do
		if not stream:IsValid() then self.streams[key] = nil goto CONTINUE end

		stream:Remove()
		::CONTINUE::
	end
end

BUILDER:Register()