/* FAudio - XAudio Reimplementation for FNA
 *
 * Copyright (c) 2011-2021 Ethan Lee, Luigi Auriemma, and the MonoGame Team
 *
 * This software is provided 'as-is', without any express or implied warranty.
 * In no event will the authors be held liable for any damages arising from
 * the use of this software.
 *
 * Permission is granted to anyone to use this software for any purpose,
 * including commercial applications, and to alter it and redistribute it
 * freely, subject to the following restrictions:
 *
 * 1. The origin of this software must not be misrepresented; you must not
 * claim that you wrote the original software. If you use this software in a
 * product, an acknowledgment in the product documentation would be
 * appreciated but is not required.
 *
 * 2. Altered source versions must be plainly marked as such, and must not be
 * misrepresented as being the original software.
 *
 * 3. This notice may not be removed or altered from any source distribution.
 *
 * Ethan "flibitijibibo" Lee <flibitijibibo@flibitijibibo.com>
 *
 */

/* FAudio_operationset.c originally written by Tyler Glaiel */

#include "FAudio_internal.h"

/* Core OperationSet Types */

typedef enum FAudio_OPERATIONSET_Type
{
	FAUDIOOP_ENABLEEFFECT,
	FAUDIOOP_DISABLEEFFECT,
	FAUDIOOP_SETEFFECTPARAMETERS,
	FAUDIOOP_SETFILTERPARAMETERS,
	FAUDIOOP_SETOUTPUTFILTERPARAMETERS,
	FAUDIOOP_SETVOLUME,
	FAUDIOOP_SETCHANNELVOLUMES,
	FAUDIOOP_SETOUTPUTMATRIX,
	FAUDIOOP_START,
	FAUDIOOP_STOP,
	FAUDIOOP_EXITLOOP,
	FAUDIOOP_SETFREQUENCYRATIO
} FAudio_OPERATIONSET_Type;

struct FAudio_OPERATIONSET_Operation
{
	FAudio_OPERATIONSET_Type Type;
	uint32_t OperationSet;
	FAudioVoice *Voice;

	union
	{
		struct
		{
			uint32_t EffectIndex;
		} EnableEffect;
		struct
		{
			uint32_t EffectIndex;
		} DisableEffect;
		struct
		{
			uint32_t EffectIndex;
			void *pParameters;
			uint32_t ParametersByteSize;
		} SetEffectParameters;
		struct
		{
			FAudioFilterParameters Parameters;
		} SetFilterParameters;
		struct
		{
			FAudioVoice *pDestinationVoice;
			FAudioFilterParameters Parameters;
		} SetOutputFilterParameters;
		struct
		{
			float Volume;
		} SetVolume;
		struct
		{
			uint32_t Channels;
			float *pVolumes;
		} SetChannelVolumes;
		struct
		{
			FAudioVoice *pDestinationVoice;
			uint32_t SourceChannels;
			uint32_t DestinationChannels;
			float *pLevelMatrix;
		} SetOutputMatrix;
		struct
		{
			uint32_t Flags;
		} Start;
		struct
		{
			uint32_t Flags;
		} Stop;
		/* No special data for ExitLoop
		struct
		{
		} ExitLoop;
		*/
		struct
		{
			float Ratio;
		} SetFrequencyRatio;
	} Data;

	FAudio_OPERATIONSET_Operation *next;
};

/* Used by both Commit and Clear routines */

static inline void DeleteOperation(
	FAudio_OPERATIONSET_Operation *op,
	FAudioFreeFunc pFree
) {
	if (op->Type == FAUDIOOP_SETEFFECTPARAMETERS)
	{
		pFree(op->Data.SetEffectParameters.pParameters);
	}
	else if (op->Type == FAUDIOOP_SETCHANNELVOLUMES)
	{
		pFree(op->Data.SetChannelVolumes.pVolumes);
	}
	else if (op->Type == FAUDIOOP_SETOUTPUTMATRIX)
	{
		pFree(op->Data.SetOutputMatrix.pLevelMatrix);
	}
	pFree(op);
}

/* OperationSet Execution */

static inline void ExecuteOperation(FAudio_OPERATIONSET_Operation *op)
{
	switch (op->Type)
	{
	case FAUDIOOP_ENABLEEFFECT:
		FAudioVoice_EnableEffect(
			op->Voice,
			op->Data.EnableEffect.EffectIndex,
			FAUDIO_COMMIT_NOW
		);
	break;

	case FAUDIOOP_DISABLEEFFECT:
		FAudioVoice_DisableEffect(
			op->Voice,
			op->Data.DisableEffect.EffectIndex,
			FAUDIO_COMMIT_NOW
		);
	break;

	case FAUDIOOP_SETEFFECTPARAMETERS:
		FAudioVoice_SetEffectParameters(
			op->Voice,
			op->Data.SetEffectParameters.EffectIndex,
			op->Data.SetEffectParameters.pParameters,
			op->Data.SetEffectParameters.ParametersByteSize,
			FAUDIO_COMMIT_NOW
		);
	break;

	case FAUDIOOP_SETFILTERPARAMETERS:
		FAudioVoice_SetFilterParameters(
			op->Voice,
			&op->Data.SetFilterParameters.Parameters,
			FAUDIO_COMMIT_NOW
		);
	break;

	case FAUDIOOP_SETOUTPUTFILTERPARAMETERS:
		FAudioVoice_SetOutputFilterParameters(
			op->Voice,
			op->Data.SetOutputFilterParameters.pDestinationVoice,
			&op->Data.SetOutputFilterParameters.Parameters,
			FAUDIO_COMMIT_NOW
		);
	break;

	case FAUDIOOP_SETVOLUME:
		FAudioVoice_SetVolume(
			op->Voice,
			op->Data.SetVolume.Volume,
			FAUDIO_COMMIT_NOW
		);
	break;

	case FAUDIOOP_SETCHANNELVOLUMES:
		FAudioVoice_SetChannelVolumes(
			op->Voice,
			op->Data.SetChannelVolumes.Channels,
			op->Data.SetChannelVolumes.pVolumes,
			FAUDIO_COMMIT_NOW
		);
	break;

	case FAUDIOOP_SETOUTPUTMATRIX:
		FAudioVoice_SetOutputMatrix(
			op->Voice,
			op->Data.SetOutputMatrix.pDestinationVoice,
			op->Data.SetOutputMatrix.SourceChannels,
			op->Data.SetOutputMatrix.DestinationChannels,
			op->Data.SetOutputMatrix.pLevelMatrix,
			FAUDIO_COMMIT_NOW
		);
	break;

	case FAUDIOOP_START:
		FAudioSourceVoice_Start(
			op->Voice,
			op->Data.Start.Flags,
			FAUDIO_COMMIT_NOW
		);
	break;

	case FAUDIOOP_STOP:
		FAudioSourceVoice_Stop(
			op->Voice,
			op->Data.Stop.Flags,
			FAUDIO_COMMIT_NOW
		);
	break;

	case FAUDIOOP_EXITLOOP:
		FAudioSourceVoice_ExitLoop(
			op->Voice,
			FAUDIO_COMMIT_NOW
		);
	break;

	case FAUDIOOP_SETFREQUENCYRATIO:
		FAudioSourceVoice_SetFrequencyRatio(
			op->Voice,
			op->Data.SetFrequencyRatio.Ratio,
			FAUDIO_COMMIT_NOW
		);
	break;

	default:
		FAudio_assert(0 && "Unrecognized operation type!");
	break;
	}
}

void FAudio_OPERATIONSET_CommitAll(FAudio *audio)
{
	FAudio_OPERATIONSET_Operation *op, *next, **committed_end;

	FAudio_PlatformLockMutex(audio->operationLock);
	LOG_MUTEX_LOCK(audio, audio->operationLock)

	if (audio->queuedOperations == NULL)
	{
		FAudio_PlatformUnlockMutex(audio->operationLock);
		LOG_MUTEX_UNLOCK(audio, audio->operationLock)
		return;
	}

	committed_end = &audio->committedOperations;
	while (*committed_end)
	{
		committed_end = &((*committed_end)->next);
	}

	op = audio->queuedOperations;
	do
	{
		next = op->next;

		*committed_end = op;
		op->next = NULL;
		committed_end = &op->next;

		op = next;
	} while (op != NULL);
	audio->queuedOperations = NULL;

	FAudio_PlatformUnlockMutex(audio->operationLock);
	LOG_MUTEX_UNLOCK(audio, audio->operationLock)
}

void FAudio_OPERATIONSET_Commit(FAudio *audio, uint32_t OperationSet)
{
	FAudio_OPERATIONSET_Operation *op, *next, *prev, **committed_end;

	FAudio_PlatformLockMutex(audio->operationLock);
	LOG_MUTEX_LOCK(audio, audio->operationLock)

	if (audio->queuedOperations == NULL)
	{
		FAudio_PlatformUnlockMutex(audio->operationLock);
		LOG_MUTEX_UNLOCK(audio, audio->operationLock)
		return;
	}

	committed_end = &audio->committedOperations;
	while (*committed_end)
	{
		committed_end = &((*committed_end)->next);
	}

	op = audio->queuedOperations;
	prev = NULL;
	do
	{
		next = op->next;
		if (op->OperationSet == OperationSet)
		{
			if (prev == NULL) /* Start of linked list */
			{
				audio->queuedOperations = next;
			}
			else
			{
				prev->next = next;
			}

			*committed_end = op;
			op->next = NULL;
			committed_end = &op->next;
		}
		else
		{
			prev = op;
		}
		op = next;
	} while (op != NULL);

	FAudio_PlatformUnlockMutex(audio->operationLock);
	LOG_MUTEX_UNLOCK(audio, audio->operationLock)
}

void FAudio_OPERATIONSET_Execute(FAudio *audio)
{
	FAudio_OPERATIONSET_Operation *op, *next;

	FAudio_PlatformLockMutex(audio->operationLock);
	LOG_MUTEX_LOCK(audio, audio->operationLock)

	op = audio->committedOperations;
	while (op != NULL)
	{
		next = op->next;
		ExecuteOperation(op);
		DeleteOperation(op, audio->pFree);
		op = next;
	}
	audio->committedOperations = NULL;

	FAudio_PlatformUnlockMutex(audio->operationLock);
	LOG_MUTEX_UNLOCK(audio, audio->operationLock)
}

/* OperationSet Compilation */

static inline FAudio_OPERATIONSET_Operation* QueueOperation(
	FAudioVoice *voice,
	FAudio_OPERATIONSET_Type type,
	uint32_t operationSet
) {
	FAudio_OPERATIONSET_Operation *latest;
	FAudio_OPERATIONSET_Operation *newop = voice->audio->pMalloc(
		sizeof(FAudio_OPERATIONSET_Operation)
	);

	newop->Type = type;
	newop->Voice = voice;
	newop->OperationSet = operationSet;
	newop->next = NULL;

	if (voice->audio->queuedOperations == NULL)
	{
		voice->audio->queuedOperations = newop;
	}
	else
	{
		latest = voice->audio->queuedOperations;
		while (latest->next != NULL)
		{
			latest = latest->next;
		}
		latest->next = newop;
	}

	return newop;
}

void FAudio_OPERATIONSET_QueueEnableEffect(
	FAudioVoice *voice,
	uint32_t EffectIndex,
	uint32_t OperationSet
) {
	FAudio_OPERATIONSET_Operation *op;

	FAudio_PlatformLockMutex(voice->audio->operationLock);
	LOG_MUTEX_LOCK(voice->audio, voice->audio->operationLock)

	op = QueueOperation(
		voice,
		FAUDIOOP_ENABLEEFFECT,
		OperationSet
	);

	op->Data.EnableEffect.EffectIndex = EffectIndex;

	FAudio_PlatformUnlockMutex(voice->audio->operationLock);
	LOG_MUTEX_UNLOCK(voice->audio, voice->audio->operationLock)
}

void FAudio_OPERATIONSET_QueueDisableEffect(
	FAudioVoice *voice,
	uint32_t EffectIndex,
	uint32_t OperationSet
) {
	FAudio_OPERATIONSET_Operation *op;

	FAudio_PlatformLockMutex(voice->audio->operationLock);
	LOG_MUTEX_LOCK(voice->audio, voice->audio->operationLock)

	op = QueueOperation(
		voice,
		FAUDIOOP_DISABLEEFFECT,
		OperationSet
	);

	op->Data.DisableEffect.EffectIndex = EffectIndex;

	FAudio_PlatformUnlockMutex(voice->audio->operationLock);
	LOG_MUTEX_UNLOCK(voice->audio, voice->audio->operationLock)
}

void FAudio_OPERATIONSET_QueueSetEffectParameters(
	FAudioVoice *voice,
	uint32_t EffectIndex,
	const void *pParameters,
	uint32_t ParametersByteSize,
	uint32_t OperationSet
) {
	FAudio_OPERATIONSET_Operation *op;

	FAudio_PlatformLockMutex(voice->audio->operationLock);
	LOG_MUTEX_LOCK(voice->audio, voice->audio->operationLock)

	op = QueueOperation(
		voice,
		FAUDIOOP_SETEFFECTPARAMETERS,
		OperationSet
	);

	op->Data.SetEffectParameters.EffectIndex = EffectIndex;
	op->Data.SetEffectParameters.pParameters = voice->audio->pMalloc(
		ParametersByteSize
	);
	FAudio_memcpy(
		op->Data.SetEffectParameters.pParameters,
		pParameters,
		ParametersByteSize
	);
	op->Data.SetEffectParameters.ParametersByteSize = ParametersByteSize;

	FAudio_PlatformUnlockMutex(voice->audio->operationLock);
	LOG_MUTEX_UNLOCK(voice->audio, voice->audio->operationLock)
}

void FAudio_OPERATIONSET_QueueSetFilterParameters(
	FAudioVoice *voice,
	const FAudioFilterParameters *pParameters,
	uint32_t OperationSet
) {
	FAudio_OPERATIONSET_Operation *op;

	FAudio_PlatformLockMutex(voice->audio->operationLock);
	LOG_MUTEX_LOCK(voice->audio, voice->audio->operationLock)

	op = QueueOperation(
		voice,
		FAUDIOOP_SETFILTERPARAMETERS,
		OperationSet
	);

	FAudio_memcpy(
		&op->Data.SetFilterParameters.Parameters,
		pParameters,
		sizeof(FAudioFilterParameters)
	);

	FAudio_PlatformUnlockMutex(voice->audio->operationLock);
	LOG_MUTEX_UNLOCK(voice->audio, voice->audio->operationLock)
}

void FAudio_OPERATIONSET_QueueSetOutputFilterParameters(
	FAudioVoice *voice,
	FAudioVoice *pDestinationVoice,
	const FAudioFilterParameters *pParameters,
	uint32_t OperationSet
) {
	FAudio_OPERATIONSET_Operation *op;

	FAudio_PlatformLockMutex(voice->audio->operationLock);
	LOG_MUTEX_LOCK(voice->audio, voice->audio->operationLock)

	op = QueueOperation(
		voice,
		FAUDIOOP_SETOUTPUTFILTERPARAMETERS,
		OperationSet
	);

	op->Data.SetOutputFilterParameters.pDestinationVoice = pDestinationVoice;
	FAudio_memcpy(
		&op->Data.SetOutputFilterParameters.Parameters,
		pParameters,
		sizeof(FAudioFilterParameters)
	);

	FAudio_PlatformUnlockMutex(voice->audio->operationLock);
	LOG_MUTEX_UNLOCK(voice->audio, voice->audio->operationLock)
}

void FAudio_OPERATIONSET_QueueSetVolume(
	FAudioVoice *voice,
	float Volume,
	uint32_t OperationSet
) {
	FAudio_OPERATIONSET_Operation *op;

	FAudio_PlatformLockMutex(voice->audio->operationLock);
	LOG_MUTEX_LOCK(voice->audio, voice->audio->operationLock)

	op = QueueOperation(
		voice,
		FAUDIOOP_SETVOLUME,
		OperationSet
	);

	op->Data.SetVolume.Volume = Volume;

	FAudio_PlatformUnlockMutex(voice->audio->operationLock);
	LOG_MUTEX_UNLOCK(voice->audio, voice->audio->operationLock)
}

void FAudio_OPERATIONSET_QueueSetChannelVolumes(
	FAudioVoice *voice,
	uint32_t Channels,
	const float *pVolumes,
	uint32_t OperationSet
) {
	FAudio_OPERATIONSET_Operation *op;

	FAudio_PlatformLockMutex(voice->audio->operationLock);
	LOG_MUTEX_LOCK(voice->audio, voice->audio->operationLock)

	op = QueueOperation(
		voice,
		FAUDIOOP_SETCHANNELVOLUMES,
		OperationSet
	);

	op->Data.SetChannelVolumes.Channels = Channels;
	op->Data.SetChannelVolumes.pVolumes = voice->audio->pMalloc(
		sizeof(float) * Channels
	);
	FAudio_memcpy(
		op->Data.SetChannelVolumes.pVolumes,
		pVolumes,
		sizeof(float) * Channels
	);

	FAudio_PlatformUnlockMutex(voice->audio->operationLock);
	LOG_MUTEX_UNLOCK(voice->audio, voice->audio->operationLock)
}

void FAudio_OPERATIONSET_QueueSetOutputMatrix(
	FAudioVoice *voice,
	FAudioVoice *pDestinationVoice,
	uint32_t SourceChannels,
	uint32_t DestinationChannels,
	const float *pLevelMatrix,
	uint32_t OperationSet
) {
	FAudio_OPERATIONSET_Operation *op;

	FAudio_PlatformLockMutex(voice->audio->operationLock);
	LOG_MUTEX_LOCK(voice->audio, voice->audio->operationLock)

	op = QueueOperation(
		voice,
		FAUDIOOP_SETOUTPUTMATRIX,
		OperationSet
	);

	op->Data.SetOutputMatrix.pDestinationVoice = pDestinationVoice;
	op->Data.SetOutputMatrix.SourceChannels = SourceChannels;
	op->Data.SetOutputMatrix.DestinationChannels = DestinationChannels;
	op->Data.SetOutputMatrix.pLevelMatrix = voice->audio->pMalloc(
		sizeof(float) * SourceChannels * DestinationChannels
	);
	FAudio_memcpy(
		op->Data.SetOutputMatrix.pLevelMatrix,
		pLevelMatrix,
		sizeof(float) * SourceChannels * DestinationChannels
	);

	FAudio_PlatformUnlockMutex(voice->audio->operationLock);
	LOG_MUTEX_UNLOCK(voice->audio, voice->audio->operationLock)
}

void FAudio_OPERATIONSET_QueueStart(
	FAudioSourceVoice *voice,
	uint32_t Flags,
	uint32_t OperationSet
) {
	FAudio_OPERATIONSET_Operation *op;

	FAudio_PlatformLockMutex(voice->audio->operationLock);
	LOG_MUTEX_LOCK(voice->audio, voice->audio->operationLock)

	op = QueueOperation(
		voice,
		FAUDIOOP_START,
		OperationSet
	);

	op->Data.Start.Flags = Flags;

	FAudio_PlatformUnlockMutex(voice->audio->operationLock);
	LOG_MUTEX_UNLOCK(voice->audio, voice->audio->operationLock)
}

void FAudio_OPERATIONSET_QueueStop(
	FAudioSourceVoice *voice,
	uint32_t Flags,
	uint32_t OperationSet
) {
	FAudio_OPERATIONSET_Operation *op;

	FAudio_PlatformLockMutex(voice->audio->operationLock);
	LOG_MUTEX_LOCK(voice->audio, voice->audio->operationLock)

	op = QueueOperation(
		voice,
		FAUDIOOP_STOP,
		OperationSet
	);

	op->Data.Stop.Flags = Flags;

	FAudio_PlatformUnlockMutex(voice->audio->operationLock);
	LOG_MUTEX_UNLOCK(voice->audio, voice->audio->operationLock)
}

void FAudio_OPERATIONSET_QueueExitLoop(
	FAudioSourceVoice *voice,
	uint32_t OperationSet
) {
	FAudio_PlatformLockMutex(voice->audio->operationLock);
	LOG_MUTEX_LOCK(voice->audio, voice->audio->operationLock)

	QueueOperation(
		voice,
		FAUDIOOP_EXITLOOP,
		OperationSet
	);

	/* No special data for ExitLoop */

	FAudio_PlatformUnlockMutex(voice->audio->operationLock);
	LOG_MUTEX_UNLOCK(voice->audio, voice->audio->operationLock)
}

void FAudio_OPERATIONSET_QueueSetFrequencyRatio(
	FAudioSourceVoice *voice,
	float Ratio,
	uint32_t OperationSet
) {
	FAudio_OPERATIONSET_Operation *op;

	FAudio_PlatformLockMutex(voice->audio->operationLock);
	LOG_MUTEX_LOCK(voice->audio, voice->audio->operationLock)

	op = QueueOperation(
		voice,
		FAUDIOOP_SETFREQUENCYRATIO,
		OperationSet
	);

	op->Data.SetFrequencyRatio.Ratio = Ratio;

	FAudio_PlatformUnlockMutex(voice->audio->operationLock);
	LOG_MUTEX_UNLOCK(voice->audio, voice->audio->operationLock)
}

/* Called when releasing the engine */

void FAudio_OPERATIONSET_ClearAll(FAudio *audio)
{
	FAudio_OPERATIONSET_Operation *current, *next;

	FAudio_PlatformLockMutex(audio->operationLock);
	LOG_MUTEX_LOCK(audio, audio->operationLock)

	current = audio->queuedOperations;
	while (current != NULL)
	{
		next = current->next;
		DeleteOperation(current, audio->pFree);
		current = next;
	}
	audio->queuedOperations = NULL;

	FAudio_PlatformUnlockMutex(audio->operationLock);
	LOG_MUTEX_UNLOCK(audio, audio->operationLock)
}

/* Called when releasing a voice */

static inline void RemoveFromList(
	FAudioVoice *voice,
	FAudio_OPERATIONSET_Operation **list
) {
	FAudio_OPERATIONSET_Operation *current, *next, *prev;

	current = *list;
	prev = NULL;
	while (current != NULL)
	{
		const uint8_t baseVoice = (voice == current->Voice);
		const uint8_t dstVoice = (
			current->Type == FAUDIOOP_SETOUTPUTFILTERPARAMETERS &&
			voice == current->Data.SetOutputFilterParameters.pDestinationVoice
		) || (
			current->Type == FAUDIOOP_SETOUTPUTMATRIX &&
			voice == current->Data.SetOutputMatrix.pDestinationVoice
		);

		next = current->next;
		if (baseVoice || dstVoice)
		{
			if (prev == NULL) /* Start of linked list */
			{
				*list = next;
			}
			else
			{
				prev->next = next;
			}

			DeleteOperation(current, voice->audio->pFree);
		}
		else
		{
			prev = current;
		}
		current = next;
	}
}

void FAudio_OPERATIONSET_ClearAllForVoice(FAudioVoice *voice)
{
	FAudio_PlatformLockMutex(voice->audio->operationLock);
	LOG_MUTEX_LOCK(voice->audio, voice->audio->operationLock)

	RemoveFromList(voice, &voice->audio->queuedOperations);
	RemoveFromList(voice, &voice->audio->committedOperations);

	FAudio_PlatformUnlockMutex(voice->audio->operationLock);
	LOG_MUTEX_UNLOCK(voice->audio, voice->audio->operationLock)
}

/* vim: set noexpandtab shiftwidth=8 tabstop=8: */
