////////////////////////////////////////////////////////////////////////////////
// Audio.cpp
// Class for audio playback via SDL
// Copyright Bjoern Ganster
// Developed since 2021
////////////////////////////////////////////////////////////////////////////////

#include "Audio.h"
#include "Sound.h"

#include <iostream>

#define FILE_NUMBER FILE_NO_AUDIO_CPP

// Constructor
Audio::Audio()
: VirtualClass("Audio", Audio_ID),
  mAudioDevice(0)
{
}

// Destructor
Audio::~Audio()
{
	SET_SCOPE;
	if (mAudioDevice != 0) {
		SDL_CloseAudioDevice(mAudioDevice);
	}
	RETURN;
}

AudioProvider* gAudioProvider = nullptr;

void setAudioProvider(AudioProvider* newProvider)
{
	gAudioProvider = newProvider;
}

void audioCallback(void* userdata, Uint8* stream, int len)
{
	SET_SCOPE;
	if (gAudioProvider != nullptr) {
		gAudioProvider->provideAudio(userdata, stream, len);
	} else {
		SDL_memset(stream, 0, len);
	}
	RETURN;
}

// Setup audio device, must be called once
bool Audio::setup()
{
	// Specify what we want
	SET_SCOPE;
	SDL_AudioSpec want;
	SDL_memset(&want, 0, sizeof(want));
	want.freq = 44100; // default to CD quality
	want.format = AUDIO_S16;
	want.channels = 1; //2;
	want.samples = 4096;

	// If callbacks are active, the whole app runs multithreaded. In that case, the current method of obtaining
	// call stacktraces won't work.
	want.callback = audioCallback;
	want.userdata = this;

	// See what we get ...
	mAudioDevice = SDL_OpenAudioDevice(NULL, 0, &want, &mAudioSpec, SDL_AUDIO_ALLOW_ANY_CHANGE);
	if (mAudioDevice == 0) {
		SDL_Log("Failed to open audio: %s", SDL_GetError());
		RETURN false;
	} else {
		if (want.freq != mAudioSpec.freq) {
			COUT << "Wanted freq " << want.freq << ", got " << mAudioSpec.freq << std::endl;
		}
		if (want.format != mAudioSpec.format) {
			COUT << "Wanted format " << want.format << ", got " << mAudioSpec.format << std::endl;
		}
		if (want.channels != mAudioSpec.channels) {
			COUT << "Wanted channels " << (int)want.channels 
				<< ", got " << (int)mAudioSpec.channels << std::endl;
		}
		if (want.samples != mAudioSpec.samples) {
			COUT << "Wanted samples " << want.samples << ", got " << mAudioSpec.samples << std::endl;
		}
		COUT << "We have an audio device" << std::endl;
		RETURN true;
	}
}

// Get audio properties
unsigned char Audio::getChannels() const
{
	return mAudioSpec.channels;
}

SDL_AudioFormat Audio::getFormat() const
{
	return mAudioSpec.format;
}

int Audio::getFrequency() const
{
	return mAudioSpec.freq;
}

// Queue audio for immediate playback
void Audio::queue(const char* data, size_t dataSize)
{
	if (data != nullptr && dataSize > 0 && mAudioSpec.callback == nullptr) {
		SDL_QueueAudio(mAudioDevice, data, dataSize);
	}
}

// Queue whole Sound for immediate playback
void Audio::queue(const Sound& sound)
{
	if (mAudioSpec.callback != nullptr) {
		return;
	}

	SET_SCOPE;
	if (sound.mAudioFormat.format == mAudioSpec.format
	&&  sound.mAudioFormat.channels == mAudioSpec.channels 
	&&  sound.mAudioFormat.freq == mAudioSpec.freq) 
	{
		SET_OP;
		queue(sound.mData.getPtr<char>(), sound.mData.getSize());
		SDL_QueueAudio(mAudioDevice, sound.mData.getPtr<char>(), sound.mData.getSize());
	} else {
		SET_OP;
		Sound convertedSound;
		convertedSound.copyConvert(sound, mAudioSpec);
		queue(convertedSound.mData.getPtr<char>(), convertedSound.mData.getSize());
	}

	SDL_PauseAudioDevice(mAudioDevice, 0);
	RETURN;
}

// Unpause audio
// Apparently, sound playback has to be unpaused before anything happens (SDL)...
void Audio::unpause()
{
	SDL_PauseAudioDevice(mAudioDevice, 0);
}

// Pause audio
void Audio::pause()
{
	SDL_PauseAudioDevice(mAudioDevice, 1);
}

Audio* gAudio = nullptr;

void prepareAudio()
{
	gAudio = new Audio();
	gAudio->setup();
}

// Obtain a pointer to the global audio device
Audio* getAudio()
{
	return gAudio;
}

const SDL_AudioSpec& Audio::getAudioSpec() const
{
	return mAudioSpec;
}

// Return remaining queued audio duration in ms
DWORD Audio::getRemainingQueuedAudioMS() const
{
	SET_SCOPE;
	auto val = SDL_GetQueuedAudioSize(mAudioDevice);
	auto bps = getBytesPerSample(mAudioSpec.format);
	RETURN val / (bps * mAudioSpec.channels);
}

// Retrieve archived sound
Sound* Audio::getSound(const char* name)
{
	SET_SCOPE;
	for (auto iter = mArchivedSounds.begin(); iter != mArchivedSounds.end(); ++iter) {
		auto archivedSound = *iter;
		if (archivedSound.name == name) {
			RETURN archivedSound.sound;
		}
	}

	RETURN nullptr;
}

// Archive sound for later retrieval
void Audio::archiveSound(Sound* sound, const char* name)
{
	SET_SCOPE;
	ArchivedSound archivedSound;
	archivedSound.name = name;
	archivedSound.sound = sound;
	mArchivedSounds.emplace_back(archivedSound);
	RETURN;
}
