#include "nw4r/snd/snd_SoundPlayer.h"

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

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

#include <climits> // INT_MAX

#include <decomp.h>
#include "common.h" // nullptr

#include "nw4r/snd/snd_BasicSound.h"
#include "nw4r/snd/snd_global.h" // AUX_BUS_NUM
#include "nw4r/snd/snd_PlayerHeap.h"
#include "nw4r/snd/snd_SoundThread.h"

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

#include <nw4r/NW4RAssert.hpp>

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

namespace nw4r { namespace snd {

SoundPlayer::SoundPlayer() :
	mPlayableCount		(1),
	mPlayableLimit		(INT_MAX),
	mVolume				(1.0f),
	mLpfFreq			(0.0f),
	mOutputLineFlag		(OUTPUT_LINE_MAIN),
	mMainOutVolume		(1.0f),
	mBiquadType			(0),
	mBiquadValue		(0.0f),
	mMainSend			(0.0f)
{
	for (int i = 0; i < 4; i++)
		mRemoteOutVolume[i] = 1.0f;

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

}

SoundPlayer::~SoundPlayer()
{
	StopAllSound(0);
}

void SoundPlayer::Update()
{
	detail::SoundThread::AutoLock lock;

	NW4R_RANGE_FOR_NO_AUTO_INC(itr, mSoundList)
	{
		decltype(itr) curItr = itr++;

		curItr->Update();
	}

	detail_SortPriorityList();
}

void SoundPlayer::StopAllSound(int fadeFrames)
{
	detail::SoundThread::AutoLock lock;

	NW4R_RANGE_FOR_NO_AUTO_INC(itr, mSoundList)
	{
		decltype(itr) curItr = itr++;

		curItr->Stop(fadeFrames);
	}
}

void SoundPlayer::PauseAllSound(bool flag, int fadeFrames)
{
	detail::SoundThread::AutoLock lock;

	NW4R_RANGE_FOR_NO_AUTO_INC(itr, mSoundList)
	{
		decltype(itr) curItr = itr++;

		curItr->Pause(flag, fadeFrames);
	}
}

void SoundPlayer::SetVolume(f32 volume)
{
	if (volume < 0.0f)
		volume = 0.0f;

	mVolume = volume;
}

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

f32 SoundPlayer::GetRemoteOutVolume(int remote) const
{
	return mRemoteOutVolume[remote];
}

void SoundPlayer::SetFxSend(AuxBus bus, f32 send)
{
	mFxSend[bus] = send;
}

#if 0
// SoundPlayer::SetVolume ([R89JEL]:/bin/RVL/Debug/mainD.MAP:14397)
/* also __FILE__ because this is the first assert in the file so it needs to
 * reference that first as well
 */
DECOMP_FORCE(__FILE__);
DECOMP_FORCE(NW4RAssert_String(volume >= 0.0f));

// SoundPlayer::SetFxSend ([R89JEL]:/bin/RVL/Debug/mainD.MAP:14405)
DECOMP_FORCE(NW4RAssertHeaderClampedLValue_String(bus));
#endif

void SoundPlayer::RemoveSoundList(detail::BasicSound *sound)
{
	detail::SoundThread::AutoLock lock;

	mSoundList.Erase(sound);
	sound->DetachSoundPlayer(this);
}

void SoundPlayer::InsertPriorityList(detail::BasicSound *sound)
{
	detail::SoundThread::AutoLock lock;

	decltype(mPriorityList.GetBeginIter()) itr = mPriorityList.GetBeginIter();
	for (; itr != mPriorityList.GetEndIter(); ++itr)
	{
		if (sound->CalcCurrentPlayerPriority()
		    < itr->CalcCurrentPlayerPriority())
		{
			break;
		}
	}

	mPriorityList.Insert(itr, sound);
}

void SoundPlayer::RemovePriorityList(detail::BasicSound *sound)
{
	detail::SoundThread::AutoLock lock;

	mPriorityList.Erase(sound);
}

void SoundPlayer::detail_SortPriorityList(detail::BasicSound *sound)
{
	RemovePriorityList(sound);
	InsertPriorityList(sound);
}

void SoundPlayer::detail_SortPriorityList()
{
	detail::SoundThread::AutoLock lock;

	if (mPriorityList.GetSize() < 2)
		return;

	static detail::BasicSound::SoundPlayerPriorityLinkList
		tmplist[detail::BasicSound::PRIORITY_MAX + 1];

	while (!mPriorityList.IsEmpty())
	{
		detail::BasicSound &front = mPriorityList.GetFront();
		mPriorityList.PopFront();
		tmplist[front.CalcCurrentPlayerPriority()].PushBack(&front);
	}

	for (int i = 0; i < (int)ARRAY_LENGTH(tmplist); i++)
	{
		while (!tmplist[i].IsEmpty())
		{
			detail::BasicSound &front = tmplist[i].GetFront();

			tmplist[i].PopFront();
			mPriorityList.PushBack(&front);
		}
	}
}

bool SoundPlayer::detail_AppendSound(detail::BasicSound *sound)
{
	NW4RAssertPointerNonnull_Line(402, sound);

	detail::SoundThread::AutoLock lock;

	int allocPriority = sound->CalcCurrentPlayerPriority();

	if (GetPlayableSoundCount() == 0)
		return false;

	while (GetPlayingSoundCount() >= GetPlayableSoundCount())
	{
		detail::BasicSound *dropSound = GetLowestPrioritySound();
		if (!dropSound)
			return false;

		if (allocPriority < dropSound->CalcCurrentPlayerPriority())
			return false;

		dropSound->Shutdown();
	}

	mSoundList.PushBack(sound);
	InsertPriorityList(sound);
	sound->AttachSoundPlayer(this);

	return true;
}

void SoundPlayer::detail_RemoveSound(detail::BasicSound *sound)
{
	RemovePriorityList(sound);
	RemoveSoundList(sound);
}

void SoundPlayer::SetPlayableSoundCount(int count)
{
	NW4RAssert_Line(453, count >= 0);

	detail::SoundThread::AutoLock lock;

	NW4RCheckMessage_Line(458, count <= mPlayableLimit,
	                      "playable sound count is over limit.");

	mPlayableCount = ut::Clamp(count, 0, mPlayableLimit);

	while (GetPlayingSoundCount() > GetPlayableSoundCount())
	{
		detail::BasicSound *dropSound = GetLowestPrioritySound();
		NW4RAssertPointerNonnull_Line(467, dropSound);

		dropSound->Shutdown();
	}
}

void SoundPlayer::detail_SetPlayableSoundLimit(int limit)
{
	NW4RAssert_Line(483, limit >= 0);

	mPlayableLimit = limit;
}

bool SoundPlayer::detail_CanPlaySound(int startPriority)
{
	detail::SoundThread::AutoLock lock;

	if (GetPlayableSoundCount() == 0)
		return false;

	if (GetPlayingSoundCount() >= GetPlayableSoundCount())
	{
		detail::BasicSound *dropSound = GetLowestPrioritySound();
		if (!dropSound)
			return false;

		if (startPriority < dropSound->CalcCurrentPlayerPriority())
			return false;
	}

	return true;
}

void SoundPlayer::detail_AppendPlayerHeap(detail::PlayerHeap *heap)
{
	NW4RAssertPointerNonnull_Line(524, heap);

	detail::SoundThread::AutoLock lock;

	heap->AttachSoundPlayer(this);
	mHeapList.PushBack(heap);
}

detail::PlayerHeap *SoundPlayer::detail_AllocPlayerHeap(detail::BasicSound *sound)
{
	NW4RAssertPointerNonnull_Line(557, sound);

	detail::SoundThread::AutoLock lock;

	if (mHeapList.IsEmpty())
		return nullptr;

	detail::PlayerHeap &playerHeap = mHeapList.GetFront();
	mHeapList.PopFront();

	playerHeap.AttachSound(sound);
	sound->AttachPlayerHeap(&playerHeap);
	playerHeap.Clear();

	return &playerHeap;
}

void SoundPlayer::detail_FreePlayerHeap(detail::BasicSound *sound)
{
	NW4RAssertPointerNonnull_Line(587, sound);

	detail::SoundThread::AutoLock lock;

	detail::PlayerHeap *heap = sound->GetPlayerHeap();
	if (!heap)
		return;

	heap->DetachSound(sound);
	sound->DetachPlayerHeap(heap);
	mHeapList.PushBack(heap);
}

}} // namespace nw4r::snd
