/* -*- coding: utf-8; tab-width: 8; indent-tabs-mode: t; -*- */

/*
 * Copyright (c) 2025, Awe Morris. All rights reserved.
 */

/*
 * HAL for OpenSL ES on Android
 */

/* Base */
#include "stratohal/platform.h"

/* OpenSLES */
#include <SLES/OpenSLES.h>
#include <SLES/OpenSLES_Android.h>

/* POSIX */
#include <pthread.h>
#include <sys/types.h>

/* C */
#include <stdlib.h>
#include <string.h>
#include <math.h>

/*
 * sound Buffer Parameters
 */
#define SAMPLING_RATE		(44100)
#define CHANNELS		(2)
#define FRAME_SIZE		(4)
#define BUF_FRAMES		(SAMPLING_RATE / 4)

/*
 * OpenSL ES Objects
 */
static SLObjectItf engine_object;
static SLEngineItf engine_engine;
static SLObjectItf output_mix_object;
static SLObjectItf bq_player_object[SOUND_TRACKS];
static SLPlayItf bq_player_play[SOUND_TRACKS];
static SLAndroidSimpleBufferQueueItf bq_player_buffer_queue[SOUND_TRACKS];

/* Buffers */
static uint32_t sample_buf[SOUND_TRACKS][BUF_FRAMES];

/* Mutex objects to synchronize accesses to sample_buf */
static pthread_mutex_t sound_mutex[SOUND_TRACKS];

/* Input Streams */
static struct wave *wave[SOUND_TRACKS];

/* Volume Values */
static float volume[SOUND_TRACKS];

/* Finish Flags */
static bool pre_finish[SOUND_TRACKS];	 /* Shows if we reached an end-of-stream. */
static bool post_finish[SOUND_TRACKS];	 /* Shows if we finished a playback of a final buffer. */

/*
 * Forward Declarations
 */
static void play_callback(SLAndroidSimpleBufferQueueItf bq, void *context);
static void enqueue(int stream);
static void scale_samples(uint32_t *buf, int frames, float vol);

/*
 * Initialize OpenSL ES.
 */
void init_opensl_es(void)
{
	for (int i = 0; i < SOUND_TRACKS; i++)
		pthread_mutex_init(&sound_mutex[i], NULL);

	slCreateEngine(&engine_object, 0, NULL, 0, NULL, NULL);
	(*engine_object)->Realize(engine_object, SL_BOOLEAN_FALSE);
	(*engine_object)->GetInterface(engine_object, SL_IID_ENGINE, &engine_engine);

	const SLInterfaceID outids[1] = {SL_IID_PLAYBACKRATE};
	const SLboolean outreq[1] = {SL_BOOLEAN_FALSE};
	(*engine_engine)->CreateOutputMix(engine_engine, &output_mix_object, 1, outids, outreq);
	(*output_mix_object)->Realize(output_mix_object, SL_BOOLEAN_FALSE);

	SLDataLocator_AndroidSimpleBufferQueue loc_bufq = {SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, 2};
	SLDataFormat_PCM format_pcm = {
		SL_DATAFORMAT_PCM,
		2,
		SL_SAMPLINGRATE_44_1,
		SL_PCMSAMPLEFORMAT_FIXED_16,
		SL_PCMSAMPLEFORMAT_FIXED_16,
		SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT,
		SL_BYTEORDER_LITTLEENDIAN
	};
	SLDataSource audio_src = {&loc_bufq, &format_pcm};
	SLDataLocator_OutputMix loc_outmix = {SL_DATALOCATOR_OUTPUTMIX, output_mix_object};
	SLDataSink audioSnk = {&loc_outmix, NULL};
	const SLInterfaceID bqids[1] = {SL_IID_BUFFERQUEUE};
	const SLboolean bqreq[1] = {SL_BOOLEAN_TRUE};
	for (int i = 0; i < SOUND_TRACKS; i++) {
		(*engine_engine)->CreateAudioPlayer(engine_engine, &bq_player_object[i], &audio_src, &audioSnk, 1, bqids, bqreq);
		(*bq_player_object[i])->Realize(bq_player_object[i], SL_BOOLEAN_FALSE);
		(*bq_player_object[i])->GetInterface(bq_player_object[i], SL_IID_PLAY, &bq_player_play[i]);
		(*bq_player_object[i])->GetInterface(bq_player_object[i], SL_IID_BUFFERQUEUE, &bq_player_buffer_queue[i]);
		(*bq_player_buffer_queue[i])->RegisterCallback(bq_player_buffer_queue[i], play_callback, (void *)(intptr_t)i);
		(*bq_player_play[i])->SetCallbackEventsMask(bq_player_play[i], SL_PLAYEVENT_HEADATEND);
		(*bq_player_play[i])->SetPlayState(bq_player_play[i], SL_PLAYSTATE_PLAYING);
	}
}

void sl_pause_sound(void)
{
	for (int i = 0; i < SOUND_TRACKS; i++) {
		if (bq_player_play[i] != NULL)
			(*bq_player_play[i])->SetPlayState(bq_player_play[i], SL_PLAYSTATE_STOPPED);
	}
}

void sl_resume_sound(void)
{
	for (int i = 0; i < SOUND_TRACKS; i++) {
		if (bq_player_play[i] != NULL)
			(*bq_player_play[i])->SetPlayState(bq_player_play[i], SL_PLAYSTATE_PLAYING);
	}
}

static void play_callback(SLAndroidSimpleBufferQueueItf bq, void *context)
{
	int stream = (intptr_t)context;

	pthread_mutex_lock(&sound_mutex[stream]);
	enqueue(stream);
	pthread_mutex_unlock(&sound_mutex[stream]);
}

static void enqueue(int stream)
{
	if (wave[stream] == NULL)
		return;

	/* Set a post-finish flag if an end-of-stream was detected in a previous filling. */
	if (pre_finish[stream]) {
		wave[stream] = NULL;
		post_finish[stream] = true;
		return;
	}

	/* Get PCM samples. */
	int read_samples = get_wave_samples(wave[stream], sample_buf[stream], BUF_FRAMES);

	/* Fill the remaining samples by zeros for a case where we have reached an end-of-stream. */
	if (read_samples < BUF_FRAMES)
		memset(sample_buf[stream] + read_samples, 0, (size_t)(BUF_FRAMES - read_samples) * FRAME_SIZE);

	/* Scale samples by a volume value. */
	scale_samples(sample_buf[stream], BUF_FRAMES, volume[stream]);

	/* Write the buffer. */
	(*bq_player_buffer_queue[stream])->Enqueue(bq_player_buffer_queue[stream], sample_buf[stream], read_samples * FRAME_SIZE);

	/* Set a pre-finish flag if we reached an end-of-stream. */
	if (is_wave_eos(wave[stream]))
		pre_finish[stream] = true;
}

static void scale_samples(uint32_t *buf, int frames, float vol)
{
	float scale;
	uint32_t frame;
	int32_t il, ir;	/* intermediate L/R */
	int16_t sl, sr;	/* source L/R*/
	int16_t dl, dr;	/* destination L/R */
	int i;

	__sync_synchronize();

	/* Convert a scale factor to an exponential value. */
	scale = (powf(10.0f, vol) - 1.0f) / (10.0f - 1.0f);

	/* Scale samples. */
	for (i = 0; i < frames; i++) {
		frame = buf[i];

		sl = (int16_t)(uint16_t)frame;
		sr = (int16_t)(uint16_t)(frame >> 16);

		il = (int)(sl * scale);
		ir = (int)(sr * scale);

		il = il > 32767 ? 32767 : il;
		il = il < -32768 ? -32768 : il;
		ir = ir > 32767 ? 32767 : ir;
		ir = ir < -32768 ? -32768 : ir;

		dl = (int16_t)il;
		dr = (int16_t)ir;

		buf[i] = ((uint32_t)(uint16_t)dl) | (((uint32_t)(uint16_t)dr) << 16);
	}
}

/*
 * HAL
 */

bool play_sound(int stream, struct wave *w)
{
	if (bq_player_play[stream] == NULL)
		return true;

	pthread_mutex_lock(&sound_mutex[stream]);
	{
		wave[stream] = w;
		pre_finish[stream] = false;
		post_finish[stream] = false;
		enqueue(stream);
	}
	pthread_mutex_unlock(&sound_mutex[stream]);

	return true;
}

bool stop_sound(int stream)
{
	if (bq_player_play[stream] == NULL)
		return true;

	pthread_mutex_lock(&sound_mutex[stream]);
	{
		wave[stream] = NULL;
		pre_finish[stream] = false;
		post_finish[stream] = false;
	}
	pthread_mutex_unlock(&sound_mutex[stream]);

	return true;
}

bool set_sound_volume(int stream, float vol)
{
	volume[stream] = vol;

	__sync_synchronize();

	return true;
}

bool is_sound_finished(int stream)
{
	__sync_synchronize();

	if (!post_finish[stream])
	    return false;

	return true;
}
