--[[
	love-microphone
	QueueableSource.lua

	Provides a QueueableSource object, pseudo-inheriting from Source.
	See http://love2d.org/wiki/Source for better documentation on
	most methods except queue and new.
]]

local ffi = require("ffi")
local al = require("openal")

local QueueableSource = {}
local typecheck = {
	Object = true,
	QueueableSource = true
}

--[[
	alFormat getALFormat(SoundData data)
		data: The SoundData to query.

	Returns the correct alFormat enum value for the given SoundData.
]]
local function getALFormat(data)
	local stereo
	if data.getChannelCount then
		stereo = data:getChannelCount() == 2
	else
		stereo = data:getChannels() == 2
	end
	local deep = data:getBitDepth() == 16

	if (stereo) then
		if (deep) then
			return al.AL_FORMAT_STEREO16
		else
			return al.AL_FORMAT_STEREO8
		end
	end

	if (deep) then
		return al.AL_FORMAT_MONO16
	else
		return al.AL_FORMAT_MONO8
	end
end

--[[
	QueueableSource QueueableSource:new(uint bufferCount=16)
		bufferCount: The number of buffers to use to hold queued sounds.

	Creates a new QueueableSource object.
]]
function QueueableSource:new(bufferCount)
	if (bufferCount) then
		if (type(bufferCount) ~= "number" or bufferCount % 1 ~= 0 or bufferCount < 0) then
			return nil, "Invalid argument #1: bufferCount must be a positive integer if given."
		end
	else
		bufferCount = 16
	end

	local new = {}

	for key, value in pairs(self) do
		if (key ~= "new") then
			new[key] = value
		end
	end

	local pBuffers = ffi.new("ALuint[?]", bufferCount)
	al.alGenBuffers(bufferCount, pBuffers)

	local freeBuffers = {}
	for i = 0, bufferCount - 1 do
		table.insert(freeBuffers, pBuffers[i])
	end

	local pSource = ffi.new("ALuint[1]")
	al.alGenSources(1, pSource)
	al.alSourcei(pSource[0], al.AL_LOOPING, 0)

	new._bufferCount = bufferCount
	new._pBuffers = pBuffers
	new._freeBuffers = freeBuffers
	new._source = pSource[0]
	new._pAvailable = ffi.new("ALint[1]")
	new._pBufferHolder = ffi.new("ALuint[16]")

	local wrapper = newproxy(true)
	getmetatable(wrapper).__index = new
	getmetatable(wrapper).__gc = function(self)
		al.alSourceStop(new._source)
		al.alSourcei(new._source, al.AL_BUFFER, 0)
		al.alDeleteSources(1, pSource)
		al.alDeleteBuffers(bufferCount, pBuffers)
	end

	return wrapper
end

--[[
	string QueueableSource:type()

	Returns the string name of the class, "QueueableSource".
]]
function QueueableSource:type()
	return "QueueableSource"
end

--[[
	bool QueueableSource:typeOf(string type)
		type: The type to check against.

	Returns whether the object matches the given type.
]]
function QueueableSource:typeOf(type)
	return typecheck[type]
end

--[[
	void QueueableSource:queue(SoundData data) (Success)
	(void, string) QueueableSource:queue(SoundData data) (Failure)
		data: The SoundData to queue for playback.

	Queues a new SoundData to play.

	Will fail and return nil and an error message if no buffers were available.
]]
function QueueableSource:queue(data)
	self:step()

	if (#self._freeBuffers == 0) then
		return nil, "No free buffers were available to playback the given audio."
	end

	local top = table.remove(self._freeBuffers, 1)

	al.alBufferData(top, getALFormat(data), data:getPointer(), data:getSize(), data:getSampleRate())
	al.alSourceQueueBuffers(self._source, 1, ffi.new("ALuint[1]", top))
end

--[[
	void QueueableSource:step()

	Opens up queues that have been used.
	Called automatically by queue.
]]
function QueueableSource:step()
	al.alGetSourcei(self._source, al.AL_BUFFERS_PROCESSED, self._pAvailable)

	if (self._pAvailable[0] > 0) then
		al.alSourceUnqueueBuffers(self._source, self._pAvailable[0], self._pBufferHolder)

		for i = 0, self._pAvailable[0] - 1 do
			table.insert(self._freeBuffers, self._pBufferHolder[i])
		end
	end
end

--[[
	void QueueableSource:clear()

	Stops playback and clears all queued data.
]]
function QueueableSource:clear()
	self:pause()

	for i = 0, self._bufferCount - 1 do
		al.alSourceUnqueueBuffers(self._source, self._bufferCount, self._pBuffers)
		table.insert(self._freeBuffers, self._pBuffers[i])
	end
end

--[[
	uint QueueableSource:getFreeBufferCount()

	Returns the number of free buffers for queueing sounds with this QueueableSource.
]]
function QueueableSource:getFreeBufferCount()
	return #self._freeBuffers
end

--[[
	void QueueableSource:play()

	Begins playing audio.
]]
function QueueableSource:play()
	if (not self:isPlaying()) then
		al.alSourcePlay(self._source)
	end
end

--[[
	bool QueueableSource:isPlaying()

	Returns whether the source is playing audio.
]]
function QueueableSource:isPlaying()
	local state = ffi.new("ALint[1]")

	al.alGetSourcei(self._source, al.AL_SOURCE_STATE, state)

	return (state[0] == al.AL_PLAYING)
end

--[[
	void QueueableSource:pause()

	Stops playing audio.
]]
function QueueableSource:pause()
	if (not self:isPaused()) then
		al.alSourcePause(self._source)
	end
end

--[[
	void QueueableSource:isPaused()

	Returns whether the source is paused.
]]
function QueueableSource:isPaused()
	local state = ffi.new("ALint[1]")

	al.alGetSourcei(self._source, al.AL_SOURCE_STATE, state)

	return (state[0] == al.AL_PAUSED)
end

return QueueableSource
