SOUNDS = {}

sound = {}
local sound = sound

sound.resolution = 1/128
sound.baseLoopPriority = 10
sound.depthOffset = -900
sound.overallOrientedGain = 30
sound.rolloff = 1
sound.loaded = false
sound.loopFadeDuration = 0.2
sound.strippedSoundPaths = {}

function sound.getStrippedPath(path)
	if not sound.strippedSoundPaths[path] then
		sound.strippedSoundPaths[path] = string.stripPath(string.stripExtension(path))
		if sound.strippedSoundPaths[path] == "no file" then
			sound.strippedSoundPaths[path] = false
		end
	end
	return sound.strippedSoundPaths[path]
end

function sound.getSoundVariation(type, forced)
	local def = SOUNDS[type]
	if def then
		if def.extension then
			if def.variations then
				local var = 1
				if forced and def.variations > 1 then
					var = math.normalize(forced, 1, def.variations)
				else
					var = math.safeRandom(def.variations)
				end
				if def.padding then
					var = string.format("%."..def.padding.."i", var)
				end
				if def.suffix then
					var = def.suffix..var
				end
				if def.filename then
					if def.folder then
						return def.folder.."/"..def.filename..var..def.extension
					else
						return def.filename..var..def.extension
					end
				else
					if def.folder then
						return def.folder.."/"..type..var..def.extension
					else
						return type..var..def.extension
					end
				end
			elseif def.filename then
				if def.folder then
					return def.folder.."/"..def.filename..def.extension
				else
					return def.filename..def.extension
				end
			else
				if def.folder then
					return def.folder.."/"..type..def.extension
				else
					return type..def.extension
				end
			end
		end
	else
		return type
	end
end

function sound.getFarCone(file)
	return SOUNDS[file].farCone or 85
end

function sound.getNearCone(file)
	return SOUNDS[file].nearCone or 30
end

function sound.getSoundOffset(file)
	if SOUNDS[file].randomOffset then
		return math.safeRandom()*SOUNDS[file].randomOffset
	end
	return 0
end

function sound.getSoundVolume(file)
	return (SOUNDS[file].volume or 1)
end

function sound.getSoundModulation(type)
	if SOUNDS[type].modulation then
		return SOUNDS[type].modulation
	elseif SOUNDS[type].maxModulation then
		return math.safeRandom()*(SOUNDS[type].maxModulation - SOUNDS[type].minModulation) + SOUNDS[type].minModulation
	else
		return 1
	end
end

function sound.resetPitch()
	audio.setGlobalPitch(1)
end

function sound.init()
	audio.setOpenAlDistanceModel(6)
	audio.setGlobalRolloffFactor(sound.rolloff)
	audio.setSoundListenerOrientation(0, 0, 1, 0, -1, 0)
	audio.setSoundListenerPosition(0, 0, sound.depthOffset * sound.resolution*10, 0,0,0)

	local cont = daisy.getFolderContents("daisyMoon/sfx/", "*.*")
	for index, snd in pairs(cont) do
		local numname = string.stripExtension(snd)
		local name = string.stripTrailingNumbers(numname)
		local index = string.stripTrailingUnderscoredNumbers(numname)

		local def = SOUNDS[index] or {}
		def.extension = ".ogg"
		def.filename = name
		local num = string.stripUnderscoredPrefix(numname)
		if num then
			def.variations = math.max(def.variations or 0,num)
		end
		SOUNDS[index] = def
	end
end

function sound.preload()
	if not sound.loaded then		
		local path = daisy.getResourcesFolderPath("daisyMoon/sfx")
		local contents = daisy.getFolderContents(path, "*.wav")
		for index, file in pairs(contents) do
			audio.loadSound("sfx/"..file)
		end

		contents = daisy.getFolderContents(path, "*.ogg")
		for index, file in pairs(contents) do
			audio.loadSound("sfx/"..file)
		end
		
		sound.loaded = true
	end
end

function sound.playSound(file, gain, pan, modulation)
	if (_config.muteWhenOutOfFocus and not daisy.isWindowInFocus()) or _config.soundVolume <= 0 then
		return
	end
	local filename = nil
	local gain = gain or 1
	local modulation = modulation or 1
	if SOUNDS[file] then
		filename = sound.getSoundVariation(file)
		modulation = modulation*sound.getSoundModulation(file)
		gain = gain*sound.getSoundVolume(file)
	else
		filename = file
	end
	
	if DEBUG.showSoundNames then
		debug.print(1, "sound", "sound:"..filename.." vol:"..gain.." mod:"..modulation)
	end
	audio.setSoundPriority(gain*_config.soundVolume)
	audio.playSound("sfx/"..filename, gain*_config.soundVolume, pan, modulation)
end

SoundState = inherited("SoundState", State)

function SoundState:new()
	local s = instance(self)
	s.trackedSounds = {}
	s.masterVolume = 1
	s.loops = {}
	return s
end

function SoundState:update(time)
	self:updateSounds(time)
end

function SoundState:updateSounds(time)
	if self.masterVolumeForced then
		self.masterVolumeForced = false
	else
		self.masterVolume = 1
	end
	self:fadeLoopSounds(time)
end

function SoundState:playRelativeLoop(loop, relX, relY, gain, modulation, loopTable, variation)
	if DEBUG.disableSounds then
		return
	end

	if loop  then
		-- default values
		local gain = gain or 1
		local modulation = modulation or 1
		local filename = nil

		if not SOUNDS[loop] then
			filename = loop
			loop = sound.getStrippedPath(loop)
			if not loop then
				debug.print(1, "sounds", "trying to play loop sound that is not a file")
				return
			end
		end
		
		local loopTable = loopTable or self.loops
		local activeLoop = loopTable[loop]

		if activeLoop and activeLoop.id then
			if DEBUG.showDebug and DEBUG.showGraphs then
				debug.addToGraph("loop", 1)
			end
			activeLoop.active = true
			activeLoop.gain = self.masterVolume*_config.soundVolume*sound.overallOrientedGain*gain
			activeLoop.modulation = modulation
			activeLoop.relativeX = relX
			activeLoop.relativeY = relY			
		else
			local activeLoop = loopTable[loop] or {}
			local offset = 0
			if SOUNDS[loop] then
				filename = sound.getSoundVariation(loop, variation) or filename
				activeLoop.baseModulation = sound.getSoundModulation(loop)
				activeLoop.baseVolume = sound.getSoundVolume(loop)
				offset = sound.getSoundOffset(loop)
				--audio.setOrientedSoundCone(sound.getNearCone(loop), sound.getFarCone(loop))
			else
				activeLoop.baseModulation = 1
				activeLoop.baseVolume = 1
				--audio.setOrientedSoundCone(30, 85)
			end

			activeLoop.activity = 1
			activeLoop.relativeX = relX
			activeLoop.relativeY = relY

			activeLoop.gain = gain*self.masterVolume*_config.soundVolume
			activeLoop.modulation = modulation

			--audio.setSoundPriority(activeLoop.gain + sound.baseLoopPriority)
			activeLoop.id = audio.startTrackedSound("sfx/"..filename, activeLoop.baseVolume*activeLoop.gain*sound.overallOrientedGain, activeLoop.baseModulation*modulation, relX*sound.resolution, relY*sound.resolution, 0, 0,0,0, offset)
			if activeLoop.id < 0 then
				--debug.print(1, "loop", "loop sound: loops/"..tostring(filename).. " could not be started".." vol:"..activeLoop.baseVolume*activeLoop.gain*sound.overallOrientedGain.." mod:"..activeLoop.baseModulation*modulation)
				--printtable(string.split(debug.traceback(), "C:"))
			else
				self.trackedSounds[activeLoop.id] = activeLoop
				loopTable[loop] = activeLoop
				if DEBUG.showSoundNames then
					debug.print(1, "sound", "looping sound:"..loop.." -> "..filename.." vol:"..string.format("%.2f", activeLoop.baseVolume*gain))
				end
				return true
			end
		end
	end
end

function SoundState:playRelativeSound(file, relX, relY, gain, modulation, relZ, variation)
	if DEBUG.disableSounds then
		return
	end
	if file then
		-- default values
		local filename = nil
		local distance = distance or 2000
		local gain = (gain or 1) *_config.soundVolume * self.masterVolume
		local modulation = modulation or 1
		local relZ = relZ or 0
		local def = SOUNDS[file]
		
		if def then
			filename = sound.getSoundVariation(file, variation)
			modulation = modulation*sound.getSoundModulation(file)
			gain = gain*sound.getSoundVolume(file)
			--audio.setOrientedSoundCone(def.nearCone or 30, def.farCone or 85)
		else
			filename = file
			--audio.setOrientedSoundCone(30, 85)
		end
					
		if DEBUG.showDebug and DEBUG.showGraphs then
			debug.addToGraph("sound", 1)
		end
		if gain > 0 then
			--audio.setSoundPriority(gain)
			--print("sfx/"..filename, gain*sound.overallOrientedGain, modulation, 32, relY*sound.resolution, relZ*sound.resolution)
			audio.playOrientedSound("sfx/"..filename, gain*sound.overallOrientedGain, modulation, relX*sound.resolution, relY*sound.resolution, relZ*sound.resolution)

			if  DEBUG.showSoundNames then
				debug.print(1, "sound", "oriented sound:"..file.. " -> ".. filename.." vol:"..gain.." mod:"..modulation.. " relX"..relX.." relY"..relY)
			end
		end

		return true 
	end
end

function SoundState:keepLoopsAliveAndSilent()
	for index, loop in pairs(self.trackedSounds) do
		audio.updateTrackedSound(loop.id, 0)
	end
end

function SoundState:fadeLoopSounds(time)		
	for index, activeLoop in pairs(self.trackedSounds) do
		if activeLoop.active then
			activeLoop.active = false
			if activeLoop.activity < 1 then
				activeLoop.activity = math.min(1,activeLoop.activity + time/sound.loopFadeDuration)
			end
		else
			if activeLoop.activity > 0 then
				activeLoop.activity = activeLoop.activity - time/sound.loopFadeDuration
			end
		end
		if activeLoop.activity > 0 then
			audio.updateTrackedSound(activeLoop.id, activeLoop.activity*activeLoop.baseVolume*activeLoop.gain, activeLoop.baseModulation*activeLoop.modulation, activeLoop.relativeX*sound.resolution, activeLoop.relativeY*sound.resolution, nil, 0,0,0)
		else
			audio.stopTrackedSound(activeLoop.id)
			activeLoop.id = nil
			self.trackedSounds[index] = nil
		end
	end
end

function SoundState:stopLoopingSounds()
	for index, activeLoop in pairs(self.trackedSounds) do
		audio.stopTrackedSound(activeLoop.id)
		self.trackedSounds[index] = nil
	end
end

function SoundState:forceMasterVolume(vol)
	self.masterVolume = vol
	self.masterVolumeForced = true
end

function SoundState:renderDebug()
	if DEBUG.showDebug and DEBUG.showSoundNames then
		local mody = 0
		for index, owner in pairs(self.trackedSounds) do
			for indy, soundVal in pairs(owner.trackedLoopSounds) do
				video.renderTextSprites(indy, 10, canvas.h - mody - 20, 0)
				mody = mody + 15
			end
		end
	end
end


