#include "nw4r/snd/snd_BasicSound.h"

/* Original source:
 * kiwi515/ogws
 * src/nw4r/snd/snd_BasicSound.cpp
 */

/*******************************************************************************
 * headers
 */

#include <climits> // ULONG_MAX
#include <cstring>

#include "common.h"

#include "nw4r/snd/snd_BasicPlayer.h"
#include "nw4r/snd/snd_ExternalSoundPlayer.h"
#include "nw4r/snd/snd_global.h"
#include "nw4r/snd/snd_MoveValue.h"
#include "nw4r/snd/snd_SoundActor.h"
#include "nw4r/snd/snd_SoundHandle.h"
#include "nw4r/snd/snd_SoundPlayer.h"

#include "nw4r/ut/ut_algorithm.h" // ut::Clamp
#include "nw4r/ut/ut_RuntimeTypeInfo.h"

#include "nw4r/NW4RAssert.hpp"

/*******************************************************************************
 * types
 */

// forward declarations
namespace nw4r { namespace snd { namespace detail { class PlayerHeap; }}}

/*******************************************************************************
 * variables
 */

namespace nw4r { namespace snd { namespace detail
{
	ut::detail::RuntimeTypeInfo const BasicSound::typeInfo(nullptr);
}}} // namespace nw4r::snd::detail

/*******************************************************************************
 * functions
 */

namespace nw4r { namespace snd { namespace detail {

BasicSound::BasicSound(int priority, int ambientPriority) :
	mPlayerHeap			(nullptr),
	mGeneralHandle		(nullptr),
	mTempGeneralHandle	(nullptr),
	mSoundPlayer		(nullptr),
	mSoundActor			(nullptr),
	mExtSoundPlayer		(nullptr),
	mId					(INVALID_ID)
	// mPauseNestCounter	(0)
{
	// specifically not the source variant
	NW4RAssertHeaderClampedLRValue_Line(53, priority, PRIORITY_MIN,
	                                    PRIORITY_MAX);

	mAmbientInfo.paramUpdateCallback	= nullptr;
	mAmbientInfo.argUpdateCallback		= nullptr;
	mAmbientInfo.argAllocaterCallback	= nullptr;
	mAmbientInfo.arg					= nullptr;
	mAmbientInfo.argSize				= 0;

	mVoiceOutCount						= 1;
	mPriority							= priority;
	mAmbientParam.priority				= ambientPriority;
}

void BasicSound::InitParam()
{
	mPauseState			= PAUSE_STATE_NORMAL;
	mUnPauseFlag		= false;
	mStartFlag			= false;
	mStartedFlag		= false;
	mAutoStopFlag		= false;
	mFadeOutFlag		= false;
	mAutoStopCounter	= 0;
	mUpdateCounter		= 0;

	mFadeVolume.InitValue(0.0f);
	mPauseFadeVolume.InitValue(1.0f);
	mFadeVolume.SetTarget(1.0f, 1);

	mInitVolume			= 1.0f;
	mExtPitch			= 1.0f;
	mExtPan				= 0.0f;
	mExtSurroundPan		= 0.0f;

	mExtMoveVolume.InitValue(1.0f);

	// clang-format off
	mLpfFreq			= 0.0f;
	mBiquadFilterType	= 0;
	mBiquadFilterValue	= 0.0f;
	mOutputLineFlag		= mSoundPlayer ? mSoundPlayer->GetDefaultOutputLine() : OUTPUT_LINE_MAIN;
	mMainOutVolume		= 1.0f;
	mMainSend			= 0.0f;
	// clang-format on

	for (int i = 0; i < AUX_BUS_NUM; i++)
		mFxSend[i] = 0.0f;

	for (int i = 0; i < 4; i++)
		mRemoteOutVolume[i] = 1.0f;

	mAmbientParam.volume			= 1.0f;
	mAmbientParam.pitch				= 1.0f;
	mAmbientParam.pan				= 0.0f;
	mAmbientParam.surroundPan		= 0.0f;
	mAmbientParam.fxSend			= 0.0f;
	mAmbientParam.lpf				= 0.0f;
	mAmbientParam.biquadFilterValue	= 0.0f;
	mAmbientParam.biquadFilterType	= 0;
	mAmbientParam.priority			= 0;

	// mPauseNestCounter = 0;
}

void BasicSound::StartPrepared()
{
	if (!mStartedFlag)
		mStartFlag = true;
}

void BasicSound::Stop(int fadeFrames)
{
	BasicPlayer &basicPlayer = GetBasicPlayer();

	if (fadeFrames == 0 || !basicPlayer.IsActive() || !basicPlayer.IsStarted()
	    || basicPlayer.IsPause())
	{
		Shutdown();
		return;
	}

	int frames = fadeFrames * mFadeVolume.GetValue();
	mFadeVolume.SetTarget(0.0f, frames);

	SetPlayerPriority(0);

	mAutoStopFlag = false;
	mPauseState = PAUSE_STATE_NORMAL;
	mUnPauseFlag = false;
	mFadeOutFlag = true;
}

void BasicSound::Pause(bool flag, int fadeFrames)
{
	int frames;

	if (flag)
	{
		// mPauseNestCounter++;

		switch (mPauseState)
		{
		case PAUSE_STATE_NORMAL:
		case PAUSE_STATE_PAUSING:
		case PAUSE_STATE_UNPAUSING:
			frames = fadeFrames * mPauseFadeVolume.GetValue();
			if (frames <= 0)
				frames = 1;

			mPauseFadeVolume.SetTarget(0.0f, frames);
			mPauseState = PAUSE_STATE_PAUSING;
			mUnPauseFlag = false;

			return;

		case PAUSE_STATE_PAUSED:
			return;

		default:
			NW4RPanicMessage_Line(207, "Unexpected pause state %d",
			                      mPauseState);
			return;
		}
	}
	else
	{
//		if (mPauseNestCounter && --mPauseNestCounter)
//			return;

		switch (mPauseState)
		{
		case PAUSE_STATE_NORMAL:
			return;

		case PAUSE_STATE_PAUSING:
		case PAUSE_STATE_PAUSED:
		case PAUSE_STATE_UNPAUSING:
			frames = fadeFrames * (1.0f - mPauseFadeVolume.GetValue());
			if (frames <= 0)
				frames = 1;

			mPauseFadeVolume.SetTarget(1.0f, frames);
			mPauseState = PAUSE_STATE_UNPAUSING;
			mUnPauseFlag = true;

			return;

		default:
			NW4RPanicMessage_Line(234, "Unexpected pause state %d",
			                      mPauseState);
			return;
		}
	}
}

void BasicSound::SetAutoStopCounter(int count)
{
	mAutoStopCounter = count;
	mAutoStopFlag = count > 0;
}

void BasicSound::FadeIn(int fadeFrames)
{
	if (!mFadeOutFlag)
	{
		int duration = fadeFrames * (1.0f - mFadeVolume.GetValue());
		mFadeVolume.SetTarget(1.0f, duration);
	}
}

bool BasicSound::IsPause() const
{
	return mPauseState == PAUSE_STATE_PAUSING
	    || mPauseState == PAUSE_STATE_PAUSED;
}

void BasicSound::Update()
{
	BasicPlayer &basicPlayer = GetBasicPlayer();

	if (mAutoStopFlag && basicPlayer.IsActive())
	{
		if (mAutoStopCounter == 0)
		{
			if (mPauseState == PAUSE_STATE_NORMAL
			    || mPauseState == PAUSE_STATE_UNPAUSING)
			{
				Stop(0);
				return;
			}
		}
		else
		{
			mAutoStopCounter--;
		}
	}

	bool playerStartFlag = false;
	if (!mStartedFlag)
	{
		if (!mStartFlag)
			return;

		if (!IsPrepared())
			return;

		playerStartFlag = true;
	}

	if (basicPlayer.IsStarted() && mUpdateCounter < ULONG_MAX)
		mUpdateCounter++;

	if (!basicPlayer.IsActive())
	{
		Shutdown();
		return;
	}

	switch (mPauseState)
	{
	case PAUSE_STATE_PAUSING:
		mPauseFadeVolume.Update();
		break;

	case PAUSE_STATE_UNPAUSING:
		mPauseFadeVolume.Update();
		UpdateMoveValue();
		break;

	case PAUSE_STATE_NORMAL:
		UpdateMoveValue();
		break;
	}

	if (mAmbientInfo.argUpdateCallback)
		mAmbientInfo.argUpdateCallback->at_0x0c(mAmbientInfo.arg, this);

	if (mAmbientInfo.paramUpdateCallback)
	{
		SoundAmbientParam ambientParam;

		if (mUpdateCounter != 0)
		{
			ambientParam.volume				= mAmbientParam.volume;
			ambientParam.pitch				= mAmbientParam.pitch;
			ambientParam.pan				= mAmbientParam.pan;
			ambientParam.surroundPan		= mAmbientParam.surroundPan;
			ambientParam.fxSend				= mAmbientParam.fxSend;
			ambientParam.lpf				= mAmbientParam.lpf;
			ambientParam.biquadFilterValue	= mAmbientParam.biquadFilterValue;
			ambientParam.biquadFilterType	= mAmbientParam.biquadFilterType;
			ambientParam.priority			= mAmbientParam.priority;
			ambientParam.field_0x24			= mAmbientParam.field_0x24;
		}
		else
		{
			ambientParam.field_0x24 = 0;
		}

		for (int i = 0; i < mVoiceOutCount; i++)
			ambientParam.voiceOutParam[i] = basicPlayer.GetVoiceOutParam(i);

		mAmbientInfo.paramUpdateCallback->detail_UpdateAmbientParam(
			mAmbientInfo.arg, mId, mVoiceOutCount, &ambientParam);

		mAmbientParam.volume			= ambientParam.volume;
		mAmbientParam.pitch				= ambientParam.pitch;
		mAmbientParam.pan				= ambientParam.pan;
		mAmbientParam.surroundPan		= ambientParam.surroundPan;
		mAmbientParam.fxSend			= ambientParam.fxSend;
		mAmbientParam.lpf				= ambientParam.lpf;
		mAmbientParam.biquadFilterValue	= ambientParam.biquadFilterValue;
		mAmbientParam.biquadFilterType	= ambientParam.biquadFilterType;
		mAmbientParam.priority			= ambientParam.priority;
		mAmbientParam.field_0x24		= ambientParam.field_0x24;

		for (int i = 0; i < mVoiceOutCount; i++)
			basicPlayer.SetVoiceOutParam(i, ambientParam.voiceOutParam[i]);
	}

	if (mSoundActor)
		mActorParam = mSoundActor->detail_GetActorParam();

	UpdateParam();

	if (mFadeOutFlag && mFadeVolume.IsFinished())
	{
		mFadeOutFlag = false;

		Shutdown();
		return;
	}

	if (playerStartFlag)
	{
		if (basicPlayer.Start())
		{
			mStartedFlag = true;
			mStartFlag = false;
		}
		else
		{
			Shutdown();
			return;
		}
	}

	if (mPauseState == PAUSE_STATE_PAUSING)
	{
		if (mPauseFadeVolume.IsFinished())
		{
			basicPlayer.Pause(true);
			mPauseState = PAUSE_STATE_PAUSED;
		}
	}
	else if (mPauseState == PAUSE_STATE_UNPAUSING)
	{
		if (mPauseFadeVolume.IsFinished())
			mPauseState = PAUSE_STATE_NORMAL;
	}

	if (mUnPauseFlag)
	{
		basicPlayer.Pause(false);
		mUnPauseFlag = false;
	}
}

void BasicSound::UpdateMoveValue()
{
	mFadeVolume.Update();
	mExtMoveVolume.Update();
}

void BasicSound::UpdateParam()
{
	f32 volume = 1.0f;
	volume *= mInitVolume;
	volume *= GetSoundPlayer()->GetVolume();
	volume *= mExtMoveVolume.GetValue();
	volume *= mFadeVolume.GetValue();
	volume *= mPauseFadeVolume.GetValue();
	volume *= mAmbientParam.volume;
	volume *= mActorParam.volume;

	f32 pan = 0.0f;
	pan += mExtPan;
	pan += mAmbientParam.pan;
	pan += mActorParam.pan;

	f32 surroundPan = 0.0f;
	surroundPan += mExtSurroundPan;
	surroundPan += mAmbientParam.surroundPan;

	f32 pitch = 1.0f;
	pitch *= mExtPitch;
	pitch *= mAmbientParam.pitch;
	pitch *= mActorParam.pitch;

	f32 lpfFreq = mLpfFreq;
	lpfFreq += mAmbientParam.lpf;
	lpfFreq += GetSoundPlayer()->GetLpfFreq();

	int biquadFilterType = mBiquadFilterType;
	f32 biquadFilterValue = mBiquadFilterValue;

	if (biquadFilterType == 0)
	{
		biquadFilterType = GetSoundPlayer()->GetBiquadFilterType();
		biquadFilterValue = GetSoundPlayer()->GetBiquadFilterValue();

		if (biquadFilterType == 0)
		{
			biquadFilterType = mAmbientParam.biquadFilterType;
			biquadFilterValue = mAmbientParam.biquadFilterValue;
		}
	}

	int outputLineFlag = mOutputLineFlag;

	f32 mainOutVolume = 1.0f;
	mainOutVolume *= mMainOutVolume;
	mainOutVolume *= GetSoundPlayer()->GetMainOutVolume();

	f32 remoteOutVolume[4];
	for (int i = 0; i < 4; i++) {
		remoteOutVolume[i] = 1.0f;
		remoteOutVolume[i] *= GetSoundPlayer()->GetRemoteOutVolume(i);
		remoteOutVolume[i] *= mRemoteOutVolume[i];
	}

	f32 mainSend = 0.0f;
	mainSend += mMainSend;
	mainSend += GetSoundPlayer()->GetMainSend();

	f32 fxSend[AUX_BUS_NUM];
	for (int i = 0; i < AUX_BUS_NUM; i++)
	{
		fxSend[i] = 0.0f;
		fxSend[i] += mFxSend[i];
		fxSend[i] += GetSoundPlayer()->GetFxSend(i);
	}

	fxSend[AUX_A] += mAmbientParam.fxSend;

	BasicPlayer &basicPlayer = GetBasicPlayer();
	basicPlayer.SetVolume(volume);
	basicPlayer.SetPan(pan);
	basicPlayer.SetSurroundPan(surroundPan);
	basicPlayer.SetPitch(pitch);
	basicPlayer.SetLpfFreq(lpfFreq);
	basicPlayer.SetBiquadFilter(biquadFilterType, biquadFilterValue);
	basicPlayer.SetOutputLine(outputLineFlag);
	basicPlayer.SetMainOutVolume(mainOutVolume);

	for (int i = 0; i < 4; i++) {
		basicPlayer.SetRemoteOutVolume(i, remoteOutVolume[i]);
	}

	basicPlayer.SetMainSend(mainSend);

	for (int i = 0; i < AUX_BUS_NUM; i++)
		basicPlayer.SetFxSend(static_cast<AuxBus>(i), fxSend[i]);
}

void BasicSound::Shutdown()
{
	BasicPlayer &basicPlayer = GetBasicPlayer();

	if (basicPlayer.IsActive())
	{
		if (mFadeOutFlag)
			basicPlayer.SetVolume(0.0f);

		basicPlayer.Stop();
	}

	SetId(INVALID_ID);

	if (IsAttachedGeneralHandle())
		DetachGeneralHandle();

	if (IsAttachedTempGeneralHandle())
		DetachTempGeneralHandle();

	if (IsAttachedTempSpecialHandle())
		DetachTempSpecialHandle();

	if (mPlayerHeap)
		mSoundPlayer->detail_FreePlayerHeap(this);

	if (mSoundPlayer)
		mSoundPlayer->detail_RemoveSound(this);

	if (mExtSoundPlayer)
		mExtSoundPlayer->RemoveSound(this);

	if (mAmbientInfo.argAllocaterCallback)
	{
		mAmbientInfo.argAllocaterCallback->detail_FreeAmbientArg(mAmbientInfo.arg, this);

		mAmbientInfo.arg = nullptr;
	}

	mStartedFlag = false;
	mFadeOutFlag = false;
}

void BasicSound::AttachPlayerHeap(PlayerHeap *heap)
{
	NW4RAssertPointerNonnull_Line(615, heap);
	NW4RAssert_Line(616, mPlayerHeap == NULL);

	mPlayerHeap = heap;
}

void BasicSound::DetachPlayerHeap(PlayerHeap *heap)
{
	NW4RAssertPointerNonnull_Line(632, heap);
	NW4RAssert_Line(633, heap == mPlayerHeap);

	mPlayerHeap = nullptr;
}

void BasicSound::AttachSoundPlayer(SoundPlayer *player)
{
	NW4RAssertPointerNonnull_Line(650, player);
	NW4RAssert_Line(651, mSoundPlayer == NULL);

	mSoundPlayer = player;
}

void BasicSound::DetachSoundPlayer(SoundPlayer *player)
{
	NW4RAssertPointerNonnull_Line(667, player);
	NW4RAssert_Line(668, player == mSoundPlayer);

	mSoundPlayer = nullptr;
}

void BasicSound::AttachSoundActor(SoundActor *actor)
{
	NW4RAssertPointerNonnull_Line(685, actor);
	NW4RAssert_Line(686, mSoundActor == NULL);

	mSoundActor = actor;
}

void BasicSound::DetachSoundActor(SoundActor *actor)
{
	NW4RAssertPointerNonnull_Line(702, actor);
	NW4RAssert_Line(703, actor == mSoundActor);

	mSoundActor = nullptr;
}

void BasicSound::AttachExternalSoundPlayer(ExternalSoundPlayer *extPlayer)
{
	NW4RAssertPointerNonnull_Line(720, extPlayer);
	NW4RAssert_Line(721, mExtSoundPlayer == NULL);

	mExtSoundPlayer = extPlayer;
}

void BasicSound::DetachExternalSoundPlayer(ExternalSoundPlayer *extPlayer)
{
	NW4RAssertPointerNonnull_Line(737, extPlayer);
	NW4RAssert_Line(738, extPlayer == mExtSoundPlayer);

	mExtSoundPlayer = nullptr;
}

int BasicSound::GetRemainingFadeFrames() const
{
	return mPauseFadeVolume.GetRemainingTime();
}

int BasicSound::GetVoiceOutCount() const
{
	return mVoiceOutCount;
}

void BasicSound::SetPlayerPriority(int priority)
{
	// specifically not the source variant
	NW4RAssertHeaderClampedLRValue_Line(797, priority, PRIORITY_MIN,
	                                    PRIORITY_MAX);

	mPriority = priority;

	if (mSoundPlayer)
		mSoundPlayer->detail_SortPriorityList(this);

	OnUpdatePlayerPriority();
}

void BasicSound::SetInitialVolume(f32 volume)
{
	NW4RAssert_Line(818, volume >= 0.0f);
	if (volume < 0.0f) {
		volume = 0.0f;
	}

	mInitVolume = mInitVolume = volume;
}

void BasicSound::SetVolume(f32 volume, int frames)
{
	NW4RAssert_Line(833, volume >= 0.0f);
	if (volume < 0.0f) {
		volume = 0.0f;
	}

	mExtMoveVolume.SetTarget(volume, frames);
}

void BasicSound::SetPitch(f32 pitch)
{
	NW4RAssert_Line(848, pitch >= 0.0f);

	mExtPitch = pitch;
}

void BasicSound::SetPan(f32 pan)
{
	mExtPan = pan;
}

void BasicSound::SetSurroundPan(f32 pan)
{
	mExtSurroundPan = pan;
}

void BasicSound::SetLpfFreq(f32 freq)
{
	mLpfFreq = freq;
}

void BasicSound::SetOutputLineFlag(int flag)
{
	mOutputLineFlag = flag;
}

void BasicSound::SetFxSend(AuxBus bus, f32 send)
{
	// specifically not the source variant
	NW4RAssertHeaderClampedLValue_Line(979, bus, AUX_A, AUX_BUS_NUM);

	mFxSend[bus] = send;
}

void BasicSound::SetRemoteFilter(int filter)
{
	GetBasicPlayer().SetRemoteFilter(filter);
}

void BasicSound::SetPanMode(PanMode panMode)
{
	GetBasicPlayer().SetPanMode(panMode);
}

void BasicSound::SetPanCurve(PanCurve panCurve)
{
	GetBasicPlayer().SetPanCurve(panCurve);
}

void BasicSound::SetAmbientInfo(AmbientInfo const &ambientArgInfo)
{
	NW4RAssertPointerNonnull_Line(1090, ambientArgInfo.argAllocaterCallback);

	void *ambientArg =
		ambientArgInfo.argAllocaterCallback->detail_AllocAmbientArg(ambientArgInfo.argSize);
	if (!ambientArg)
	{
		NW4RCheckMessage_Line(1093, ambientArg, "Failed to alloc AmbientArg.");
		return;
	}

	std::memcpy(ambientArg, ambientArgInfo.arg, ambientArgInfo.argSize);
	mAmbientInfo = ambientArgInfo;
	mAmbientInfo.arg = ambientArg;

	if (ambientArgInfo.paramUpdateCallback)
	{
		int voiceOutCount =
			mAmbientInfo.paramUpdateCallback->detail_GetRequiredVoiceOutCount(mAmbientInfo.arg, mId);

		if (voiceOutCount > 4)
			voiceOutCount = 4;

		mVoiceOutCount = voiceOutCount;
	}
}

int BasicSound::GetAmbientPriority(AmbientInfo const &ambientInfo, u32 soundId)
{
	if (!ambientInfo.paramUpdateCallback)
		return PRIORITY_MIN;

	int priority =
		ambientInfo.paramUpdateCallback->detail_GetAmbientPriority(ambientInfo.arg, soundId);

	return priority;
}

bool BasicSound::IsAttachedGeneralHandle()
{
	return mGeneralHandle != nullptr;
}

bool BasicSound::IsAttachedTempGeneralHandle()
{
	return mTempGeneralHandle != nullptr;
}

void BasicSound::DetachGeneralHandle()
{
	mGeneralHandle->DetachSound();
}

void BasicSound::DetachTempGeneralHandle()
{
	mTempGeneralHandle->DetachSound();
}

void BasicSound::SetId(u32 id)
{
	mId = id;

	GetBasicPlayer().SetId(id);
}

}}} // namespace nw4r::snd::detail
