#include "AudioPlayer.h"
#include "WavSource.h"

#include <iostream>

int streamCallback(const void* input,
                                void* output,
                                unsigned long framesPerBuffer,
                                const PaStreamCallbackTimeInfo *timeInfo,
                                PaStreamCallbackFlags flags,
                                void* data) {
	AudioPlayer* player = (AudioPlayer*)data;
	return player->streamCallback(input, output, framesPerBuffer, timeInfo, flags);
}

void streamFinishedCallback(void *data) {
	AudioPlayer* player = (AudioPlayer*)data;
	player->streamFinishedCallback();
}

#define PA_CHECK_ERROR(err) \
	if (err != PaErrorCode::paNoError) { \
	    throw std::runtime_error(Pa_GetErrorText(err)); \
	}

AudioPlayer::AudioPlayer(std::string path) {
	_sndSource = openSoundFile(path);

	PaError err = Pa_Initialize();
	PA_CHECK_ERROR(err);

	PaStreamParameters outParam;
	outParam.device = Pa_GetDefaultOutputDevice();
	if (outParam.device == paNoDevice) {
		throw std::runtime_error("Haven't found an audio device!");
	}

	outParam.channelCount = _sndSource->getChannelCount();
	outParam.sampleFormat = paFloat32;
	outParam.suggestedLatency = Pa_GetDeviceInfo(outParam.device)->defaultLowOutputLatency;
	outParam.hostApiSpecificStreamInfo = nullptr;

	err = Pa_OpenStream(&_stream, NULL, &outParam, _sndSource->getSampleRate(),
	                    /*paFramesPerBufferUnspecified*/1024*_sndSource->getChannelCount(), paClipOff,
	                    ::streamCallback, this);
	PA_CHECK_ERROR(err);

	err = Pa_SetStreamFinishedCallback(_stream, ::streamFinishedCallback);
	PA_CHECK_ERROR(err);
}

AudioPlayer::~AudioPlayer() {
	try {
		stop();

		PaError err = Pa_CloseStream(_stream);
		PA_CHECK_ERROR(err);

		err = Pa_Terminate();
		PA_CHECK_ERROR(err);
	} catch (std::exception& ex) {
		std::cout << "AudioPlayer destructor exception: " << ex.what() << std::endl;
	}

	if(_sndSource) {
		delete _sndSource;
	}
}

ISndSource *AudioPlayer::openSoundFile(std::string path) {
	std::string ext = path.substr(path.find_last_of(".") + 1);

	if(ext == "wav") {
		return new WavSource(path);
	} else {
		throw std::runtime_error("unsupported sound file format");
	}
}

int AudioPlayer::streamCallback(const void* input,
                                void* output,
                                unsigned long framesPerBuffer,
                                const PaStreamCallbackTimeInfo *timeInfo,
                                PaStreamCallbackFlags flags) {

	_sndSource->readData(framesPerBuffer, output);

	if(_streamListener) {
		_streamListener((float*)output, framesPerBuffer, _sndSource->getSampleDepth(), _sndSource->getChannelCount());
	}

	return paContinue;
}

void AudioPlayer::streamFinishedCallback() {
	if(_streamFinishedLitener) {
		_streamFinishedLitener();
	}
}

void AudioPlayer::play() {
	PaError err = Pa_StartStream(_stream);
	PA_CHECK_ERROR(err);
}

void AudioPlayer::stop() {
	PaError err = Pa_StopStream(_stream);
	PA_CHECK_ERROR(err);
}

void AudioPlayer::setStreamListener(StreamListenerType callback) {
	_streamListener = callback;
}

void AudioPlayer::setStreamFinishedListener(StreamFinishedType callback) {
	_streamFinishedLitener = callback;
}
