#if OS_WINDOWS
	#include "impl/windows/core.c"
#elif OS_LINUX
	#include "impl/linux/core.c"
#endif

Str8 FS_ReadEntireFile(M_Arena *arena, Str8 path) {
	Str8 result = { 0 };

	OS_Handle file = FS_FileOpen(path, FS_ACCESS_READ);
	if (file.v[0]) {
		result.count = FS_FileSize(file);
		result.data  = M_ArenaPush(arena, U8, .count = result.count);

		FS_FileRead(file, result.data, result.count, 0);
		FS_FileClose(file);
	}

	return result;
}

internal void SDL_SubmitAudio(void *user, SDL_AudioStream *stream, int needed, int total) {
	Audio_Context *audio = cast(Audio_Context *) user;

	(void) total;

	M_TempScope(0, 0) {
		U32 prev = 0;
		U32 it   = audio->head;

		U32 n_samples = needed    >> 1;
		U32 n_frames  = n_samples >> 1;

		F32 *left_f32  = M_ArenaPush(temp.arena, F32, .count = n_frames);
		F32 *right_f32 = M_ArenaPush(temp.arena, F32, .count = n_frames);

		while (it != 0) {
			Audio_Track *track = &audio->tracks[it];
			U32 next = track->next;

			F32 *l0 = left_f32;
			F32 *r0 = right_f32;

			Assert(track->playing);

			U32 remain = track->data->n_frames - track->n_played;
			U32 play   = Min(n_frames, remain);
			U32 off    = track->n_played << 1; // played n frames, thus double to n samples

			for (U32 f = 0; f < play; ++f) {
				*l0++ += cast(F32) (track->data->samples[off + (2 * f) + 0]) * track->volume * audio->volume;
				*r0++ += cast(F32) (track->data->samples[off + (2 * f) + 1]) * track->volume * audio->volume;
			}

			track->n_played += play;

			if (track->n_played == track->data->n_frames) {
				if (prev == 0) {
					// Head has finished playing
					//
					audio->head = track->next;
					track->next = audio->free;
					audio->free = it;
				}
				else {
					// We're in the middle somewhere so prev->next == it->next
					//
					audio->tracks[prev].next = track->next;
					track->next = audio->free;
					audio->free = it;
				}

				track->playing = false;
			}

			it = next;
		}

		F32 *l0 = left_f32;
		F32 *r0 = right_f32;

		S16 *interleaved = M_ArenaPush(temp.arena, S16, .count = n_samples);
		S16 *i0 = interleaved;

		for (U32 n = 0; n < n_frames; ++n) {
			*i0++ = cast(S16) (l0[n] + 0.5f);
			*i0++ = cast(S16) (r0[n] + 0.5f);
		}

		SDL_PutAudioStreamData(stream, interleaved, needed);
	}
}

void Audio_Init(M_Arena *arena, Audio_Context *audio, F32 volume) {
	SDL_AudioSpec spec = { 0 };
	spec.format   = SDL_AUDIO_S16LE;
	spec.channels = 2;
	spec.freq     = 44100;

	audio->stream = SDL_OpenAudioDeviceStream(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, &spec, SDL_SubmitAudio, audio);

	if (audio->stream) {
		M_TempScope(0, 0) {
			Str8 exec = FS_SystemPath(temp.arena, FS_SYSTEM_PATH_EXE);
			Str8 path = Sf(temp.arena, "%.*s/assets", Sv(exec));

			FS_List files = FS_PathList(temp.arena, path);

			U32 n_audio = 0;
			for (FS_Entry *it = files.first; it != 0; it = it->next) {
				if (Str8_EndsWith(it->basename, S("wav"))) {
					n_audio += 1;
				}
			}

			audio->n_sounds = 0;
			audio->sounds   = M_ArenaPush(arena, Audio_Data, .count = n_audio);

			for (FS_Entry *it = files.first; it != 0; it = it->next) {
				if (Str8_EndsWith(it->basename, S("wav"))) {
					Audio_Data *sound = &audio->sounds[audio->n_sounds];

					U8 *data;
					U32 count;
					SDL_AudioSpec wav;

					SDL_LoadWAV((const char *) it->path.data, &wav, &data, &count);

					Assert(wav.freq == 44100);

					sound->samples  = (S16 *) data;
					sound->n_frames = (count >> 2);

					audio->n_sounds += 1;
				}
			}

			audio->n_tracks = 16;

			for (U32 it = 1; it < audio->n_tracks; ++it) {
				audio->tracks[it].next = it + 1;
			}

			audio->tracks[audio->n_tracks - 1].next = 0;

			audio->volume = volume;
			audio->head   = 0;
			audio->free   = 1;

			printf("--- Loaded %d sounds ---\n", audio->n_sounds);
		}
	}
}

U32 Audio_Play(Audio_Context *audio, U32 index) {
	U32 result = 0;
	if (audio->free != 0 && SDL_LockAudioStream(audio->stream)) {
		result = audio->free;

		Audio_Track *track = &audio->tracks[result];
		audio->free = track->next;

		track->playing  = true;
		track->data     = &audio->sounds[index];
		track->n_played = 0;
		track->volume   = 1.0f;
		track->next     = audio->head;
		audio->head     = result;

		SDL_UnlockAudioStream(audio->stream);
	}

	return result;
}

void Audio_ChangeVolume(Audio_Context *audio, U32 handle, F32 volume) {
	if (SDL_LockAudioStream(audio->stream)) {
		audio->tracks[handle].volume = volume;
		SDL_UnlockAudioStream(audio->stream);
	}
}
