#include "sound/audiostream.h"

AudioStream::AudioStream()
{
	alGenBuffers(4, buffers);
	alGenSources(1, &source);
	data = (short *)std::malloc(UTIL::AUDIO::CHUNK_SIZE * sizeof(short) * 2); // Make space for stereo even if we are playing mono
	stopThread = false;
	eof = true;
	paused = true;
	streamThread = std::thread(&AudioStream::stream, this);
	//streamThread.detach();
}

std::string AudioStream::CurStreamName()
{
	std::lock_guard<std::mutex> lock(mutex);
	return songNames.front();
}

bool AudioStream::PlayStream()
{
	if (CurStreamName().empty()) {
		LOG(WARN, "Song queue empty!", NULL);
		return false;
	}
	LOG(INFO, "Current song is: {}", CurStreamName());
	return (paused = true);
}

void AudioStream::AddToQueue(std::string songName)
{
	std::lock_guard<std::mutex> lock(mutex);
	LOG(INFO, "Adding song {} to queue", songName);
	songNames.push(songName);
}

bool AudioStream::openFile(std::string fileName)
{
	int error = 0;
	file = stb_vorbis_open_filename(CurStreamName().c_str(), &error, NULL);
	if (file == nullptr || error != STBVorbisError::VORBIS__no_error) {
		LOG(ERROR, "Failed to open file: '{}' error code: {}", CurStreamName(), error);
		return false;
	}
	info = stb_vorbis_get_info(file);
	if (info.sample_rate != UTIL::AUDIO::SAMPLE_RATE) {
		LOG(ERROR, "Failed to open file: '{}', make sure to convert sample rate to 44100!", CurStreamName());
		stb_vorbis_close(file);
		return false;
	}
	stb_vorbis_seek_start(file);
	std::free(data);
	data = (short *)std::malloc(UTIL::AUDIO::CHUNK_SIZE * sizeof(short) * info.channels);
	return true;
}

int AudioStream::loadChunk(ALuint buffer)
{
	int sample = 0;
	if (file == NULL) {
		return sample;
	}
	if (info.channels == 0) {
		return sample;
	}
	sample = stb_vorbis_get_samples_short_interleaved(file, info.channels, data, UTIL::AUDIO::CHUNK_SIZE);
	if (sample == 0)
		return sample;
	format = info.channels == 1 ? AL_FORMAT_MONO16 : AL_FORMAT_STEREO16;
	//LOG(INFO, "Buffer id {}, Format {}, data size {}, sample rate {},\nfilename {}", 
	//	 buffer, format == AL_FORMAT_MONO16 ? "MONO" : "STEREO", sample * info.channels * sizeof(short), info.sample_rate, songNames.front());
	alBufferData(buffer, format, data, sample * info.channels * sizeof(short), info.sample_rate);

	return sample;
}

int AudioStream::loadInitalChunk()
{
	int samples = 0;
	if (!openFile(CurStreamName())) {
		return samples;
	}
	for (int i = 0; i < 4; i++) {
		samples = loadChunk(buffers[i]);
		if (samples == 0) {
			LOG(ERROR, "Music file {} too small!", CurStreamName());
			break;
		}
	}
	return samples;
}

int AudioStream::loadNextChunk()
{
	int samples = loadChunk(buffers[last_buffer]);
	return samples;
}

void AudioStream::popQueue()
{
	std::lock_guard<std::mutex> lock(mutex);
	if (songNames.empty())
		return;
	songNames.pop();
}

void AudioStream::cycleQueue()
{
	std::lock_guard<std::mutex> lock(mutex);
	LOG(INFO, "Cycling queue", NULL);
	if (songNames.empty())
		return;
	std::string buffer = songNames.front();
	songNames.pop();
	songNames.push(buffer);
	LOG(INFO, "New front: {}", songNames.front());
}

void AudioStream::stream()
{
	int processed = 0;
	int queued = 0;
	bool inital = true;

	std::this_thread::sleep_for(std::chrono::seconds(1));

	//LOG(INFO, "Call from in audio thread!", NULL);
	//LOG(INFO, "Top of queue is: {}", CurStreamName());
	paused = false;

	while (!stopThread) {
		if (songNames.empty() || paused) {
			LOG(INFO, "Paused or queue empty", NULL);
			continue;
		}
		alGetSourcei(source, AL_SOURCE_STATE, &state);
		if (inital) {
			int samples = loadInitalChunk();
			if (samples == 0) {
				LOG(ERROR, "Music file '{}' either failed to load or not big enough!", CurStreamName());
				popQueue();
				continue;
			}
			alSourceQueueBuffers(source, 4, buffers);
			alSourcePlay(source);
			alGetSourcei(source, AL_SOURCE_STATE, &state);
			inital = false;
			eof = false;
		}
		// If we are at the end of file, the and stream queue has a song in it
		// Load in the inital buffers
		if (state != AL_PLAYING) {
			alSourcePlay(source);
		}
		while (state == AL_PLAYING && !stopThread) {
			if (paused) {
				alSourcePause(source);
			}
			alGetSourcei(source, AL_SOURCE_STATE, &state);
			alGetSourcei(source, AL_BUFFERS_PROCESSED, &processed);
			alGetSourcei(source, AL_BUFFERS_QUEUED, &queued);
			
			while (processed != 0 && !stopThread) {
				ALuint unqueued;
				alSourceUnqueueBuffers(source, 1, &unqueued);
				for (int i = 0; i < 4; ++i)
					if (unqueued == buffers[i])
						last_buffer = i;
				int samples = loadNextChunk();
				if (samples == 0) {
					LOG(INFO, "Hit the end of the song!", NULL);
					cycleQueue();
					stb_vorbis_close(file);
					while (!openFile(CurStreamName())) {
						LOG(ERROR, "Failed to open next in stream! {}", CurStreamName());
						popQueue();
					}
				} else {
				}
				//LOG(INFO, "Buffer index: {}", last_buffer);
				alSourceQueueBuffers(source, 1, &buffers[last_buffer]);
				//last_buffer = (last_buffer + 1) % 4;
				processed--;
			}
		}
	}
}

AudioStream::~AudioStream()
{
	stopThread = true;
	if (streamThread.joinable())
		streamThread.join();
	stb_vorbis_close(file);
	std::free(data);
	alSourcei(source, AL_BUFFER, 0);
	alDeleteSources(1, &source);
	alDeleteBuffers(4, buffers);
}
