//	PortAudioDevice.cpp - A PortAudio subclass of AudioDevice.
//  ----------------------------------------------------------------------------
//	This file is part of 'NiallsAVLib', base code for any kind of audiovisual
//	apps.
//	Copyright (C) 2012  Niall Moody
//	
//	This program is free software: you can redistribute it and/or modify
//	it under the terms of the GNU General Public License as published by
//	the Free Software Foundation, either version 3 of the License, or
//	(at your option) any later version.
//
//	This program is distributed in the hope that it will be useful,
//	but WITHOUT ANY WARRANTY; without even the implied warranty of
//	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//	GNU General Public License for more details.
//
//	You should have received a copy of the GNU General Public License
//	along with this program.  If not, see <http://www.gnu.org/licenses/>.
//	----------------------------------------------------------------------------

#include "PortAudioDevice.h"
#include "DebugHeaders.h"


#include <stdint.h>
#include <cassert>

using namespace std;

//------------------------------------------------------------------------------
PortAudioDevice::PortAudioDevice():
stream(0),
callback(0),
numInputs(0),
numOutputs(2),
samplerate(44100.0),
isRunning(false)
{
#ifdef WIN32
	api = paDirectSound;
#elif defined(LINUX)
	api = paALSA;
#elif defined(OSX)
	api = paCoreAudio;
#endif
}

//------------------------------------------------------------------------------
PortAudioDevice::~PortAudioDevice()
{
	if(isRunning)
		stop();
}

//------------------------------------------------------------------------------
void PortAudioDevice::setCallback(AudioDeviceCallback *val)
{
	callback = val;
}

//------------------------------------------------------------------------------
void PortAudioDevice::setNumChannels(int inputs, int outputs)
{
	numInputs = inputs;
	numOutputs = outputs;
}

//------------------------------------------------------------------------------
void PortAudioDevice::setSamplerate(double rate)
{
	samplerate = rate;
}

//------------------------------------------------------------------------------
void PortAudioDevice::setAPI(const wstring& val)
{
	if(val == L"DirectSound")
		api = paDirectSound;
	else if(val == L"ASIO")
		api = paASIO;
	else if(val == L"MME")
		api = paMME;
	else if(val == L"WDM")
		api = paWDMKS;
	else if(val == L"WASAPI")
		api = paWASAPI;
	else if(val == L"ALSA")
		api = paALSA;
	else if(val == L"JACK")
		api = paJACK;
	else if(val == L"OSS")
		api = paOSS;
	else if(val == L"CoreAudio")
		api = paCoreAudio;
	else
	{
#ifdef WIN32
		api = paDirectSound;
#elif defined(LINUX)
		api = paALSA;
#elif defined(OSX)
		api = paCoreAudio;
#endif
	}
}

//------------------------------------------------------------------------------
bool PortAudioDevice::start()
{
	bool retval = false;

	if(!isRunning)
	{
		if(Pa_Initialize() == paNoError)
		{
			const PaHostApiInfo *apiInfo;
			PaStreamParameters inputParams, outputParams;
			PaError err;
			const PaDeviceInfo *devInfo;
			PaHostApiIndex apiIndex = Pa_HostApiTypeIdToHostApiIndex(api);

			if(apiIndex == paHostApiNotFound)
				return false;
#ifdef DEBUG_STATEMENTS
			else
			{
				switch(api)
				{
					case paDirectSound:
						DebugStatement(L"PortAudioDevice: Using DirectSound API.");
						break;
					case paASIO:
						DebugStatement(L"PortAudioDevice: Using ASIO API.");
						break;
					case paMME:
						DebugStatement(L"PortAudioDevice: Using MME API.");
						break;
					case paWDMKS:
						DebugStatement(L"PortAudioDevice: Using WDM API.");
						break;
					case paWASAPI:
						DebugStatement(L"PortAudioDevice: Using WASAPI API.");
						break;
					case paALSA:
						DebugStatement(L"PortAudioDevice: Using ALSA API.");
						break;
					case paJACK:
						DebugStatement(L"PortAudioDevice: Using JACK API.");
						break;
					case paOSS:
						DebugStatement(L"PortAudioDevice: Using OSS API.");
						break;
					case paCoreAudio:
						DebugStatement(L"PortAudioDevice: Using CoreAudio API.");
						break;
				}
			}
#endif

			apiInfo = Pa_GetHostApiInfo(apiIndex);

			devInfo = Pa_GetDeviceInfo(apiInfo->defaultInputDevice);

			inputParams.device = apiInfo->defaultInputDevice;
			inputParams.channelCount = numInputs;
			inputParams.sampleFormat = paFloat32;
			if(devInfo)
				inputParams.suggestedLatency = devInfo->defaultLowInputLatency;
			else
				inputParams.suggestedLatency = 0.0;
			inputParams.suggestedLatency = 0.0;
			inputParams.hostApiSpecificStreamInfo = 0;

			devInfo = Pa_GetDeviceInfo(apiInfo->defaultOutputDevice);

			outputParams.device = apiInfo->defaultOutputDevice;
			outputParams.channelCount = numOutputs;
			outputParams.sampleFormat = paFloat32;
			if(devInfo)
				outputParams.suggestedLatency = devInfo->defaultLowOutputLatency;
			else
				outputParams.suggestedLatency = 0.0;
			outputParams.hostApiSpecificStreamInfo = 0;

			if(Pa_IsFormatSupported(NULL, &outputParams, samplerate) == paInvalidSampleRate)
			{
				DebugStatement(L"44100Hz samplerate not supported, using 48000Hz.");
				samplerate = 48000.0; //This is obviously a nasty hacky way of doing things.

				if(Pa_IsFormatSupported(NULL, &outputParams, samplerate) == paInvalidSampleRate)
					DebugStatement(L"48000Hz not supported either?");
			}

			err = Pa_OpenStream(&stream,
								(numInputs < 1) ? 0 : &inputParams,
								(numOutputs < 1) ? 0 : &outputParams,
								samplerate,
								0,
								paNoFlag,
								portAudioCallback,
								(void *)callback);
			if(err != paNoError)
			{
				string tempstr;
				wstring tempstr2;
				DebugStatement(L"PortAudioDevice Error: Could not open stream.");

				tempstr = Pa_GetErrorText(err);
				UTF8File::charToWstring(tempstr, tempstr2);
				DebugStatement(tempstr2);

				Pa_Terminate();
			}
			else if(Pa_StartStream(stream) == paNoError)
			{
				isRunning = true;
				retval = true;
			}
			else
			{
				DebugStatement(L"PortAudioDevice Error: Could not start stream.");
				Pa_CloseStream(stream);
				Pa_Terminate();
			}
		}
		else
			DebugStatement(L"PortAudioDevice Error: Could not initialise.");
	}

	return retval;
}

//------------------------------------------------------------------------------
void PortAudioDevice::stop()
{
	if(isRunning)
	{
		Pa_StopStream(stream);
		Pa_CloseStream(stream);
		Pa_Terminate();
	}
}

//------------------------------------------------------------------------------
int PortAudioDevice::portAudioCallback(const void *input,
									   void *output,
									   unsigned long frameCount,
									   const PaStreamCallbackTimeInfo *timeInfo,
									   PaStreamCallbackFlags statusFlags,
									   void *userData)
{
	unsigned long i;
	AudioDeviceCallback *callback = (AudioDeviceCallback *)userData;

	//Because it seems to be filled with the input(/nonsense), we need to blank
	//the output buffer.
	for(i=0;i<(frameCount*2);++i)
		((float *)output)[i] = 0.0f;

	if(callback)
		callback->getAudio((float *)input, (float *)output, frameCount);

	return paContinue;
}
