#include "nw4r/snd/snd_SeqTrack.h"

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

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

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

#include "nw4r/snd/snd_Channel.h"
#include "nw4r/snd/snd_global.h"
#include "nw4r/snd/snd_Lfo.h"
#include "nw4r/snd/snd_MoveValue.h"
#include "nw4r/snd/snd_NoteOnCallback.h"
#include "nw4r/snd/snd_SeqPlayer.h"
#include "nw4r/snd/snd_SoundThread.h"

#include "nw4r/ut/ut_algorithm.h"

#include "nw4r/NW4RAssert.hpp"

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

namespace nw4r { namespace snd { namespace detail {

void SeqTrack::SetPlayerTrackNo(int playerTrackNo)
{
	// specifically not the source variant
	NW4RAssertHeaderClampedLRValue_Line(38, playerTrackNo, 0,
	                                    SeqPlayer::TRACK_NUM_PER_PLAYER);

	mPlayerTrackNo = playerTrackNo;
}

SeqTrack::SeqTrack() :
	mOpenFlag		(false),
	mSeqPlayer		(nullptr),
	mChannelList	(nullptr)
{
	InitParam();
}

SeqTrack::~SeqTrack()
{
	Close();
}

void SeqTrack::InitParam()
{
	mExtVolume		= 1.0f;
	mExtPitch		= 1.0f;
	mExtPan			= 0.0f;
	mExtSurroundPan	= 0.0f;
	mPanRange		= 1.0f;

	mParserTrackParam.baseAddr			= nullptr;
	mParserTrackParam.currentAddr		= nullptr;
	mParserTrackParam.cmpFlag			= true;
	mParserTrackParam.noteWaitFlag		= true;
	mParserTrackParam.tieFlag			= false;
	mParserTrackParam.monophonicFlag	= false;
	mParserTrackParam.callStackDepth	= 0;
	mParserTrackParam.wait				= 0;
	mParserTrackParam.muteFlag			= false;
	mParserTrackParam.silenceFlag		= false;
	mParserTrackParam.noteFinishWait	= false;
	mParserTrackParam.portaFlag			= false;
	mParserTrackParam.damperFlag		= false;
	mParserTrackParam.bankNo			= 0;
	mParserTrackParam.prgNo				= 0;
	mParserTrackParam.lfoParam.Init();
	mParserTrackParam.lfoTarget			= 0;
	mParserTrackParam.sweepPitch		= 0.0f;
	mParserTrackParam.volume.InitValue(127);
	mParserTrackParam.pan.InitValue(0);
	mParserTrackParam.surroundPan.InitValue(0);
	mParserTrackParam.volume2			= 127;
	mParserTrackParam.velocityRange		= 127;
	mParserTrackParam.pitchBend.InitValue(0);
	mParserTrackParam.bendRange			= DEFAULT_BENDRANGE;
	mParserTrackParam.initPan			= 0;
	mParserTrackParam.transpose			= 0;
	mParserTrackParam.priority			= DEFAULT_PRIORITY;
	mParserTrackParam.portaKey			= DEFAULT_PORTA_KEY;
	mParserTrackParam.portaTime			= 0;
	mParserTrackParam.attack			= INVALID_ENVELOPE;
	mParserTrackParam.decay				= INVALID_ENVELOPE;
	mParserTrackParam.sustain			= INVALID_ENVELOPE;
	mParserTrackParam.release			= INVALID_ENVELOPE;
	mParserTrackParam.envHold			= INVALID_ENVELOPE;
	mParserTrackParam.mainSend			= 127;

	for (int i = 0; i < AUX_BUS_NUM; i++)
		mParserTrackParam.fxSend[i] = 0;

	mParserTrackParam.lpfFreq			= 0.0f;
	mParserTrackParam.biquadType		= 0;
	mParserTrackParam.biquadValue		= 0.0f;

	for (int varNo = 0; varNo < TRACK_VARIABLE_NUM; varNo++)
		mTrackVariable[varNo] = SeqPlayer::VARIABLE_DEFAULT_VALUE;
}

void SeqTrack::SetSeqData(void const *seqBase, s32 seqOffset)
{
	mParserTrackParam.baseAddr = static_cast<byte_t const *>(seqBase);
	mParserTrackParam.currentAddr = mParserTrackParam.baseAddr + seqOffset;
}

void SeqTrack::Open()
{
	mParserTrackParam.noteFinishWait	= false;
	mParserTrackParam.callStackDepth	= 0;
	mParserTrackParam.wait				= 0;

	mOpenFlag = true;
}

void SeqTrack::Close()
{
	SoundThread::AutoLock lock;

	ReleaseAllChannel(MUTE_RELEASE_VALUE);
	FreeAllChannel();

	mOpenFlag = false;
}

void SeqTrack::UpdateChannelLength()
{
	SoundThread::AutoLock lock;

	if (!mOpenFlag)
		return;

	for (Channel *channel = mChannelList; channel;
	     channel = channel->GetNextTrackChannel())
	{
		if (channel->GetLength() > 0)
			channel->SetLength(channel->GetLength() - 1);

		UpdateChannelRelease(channel);

		if (!channel->IsAutoUpdateSweep())
			channel->UpdateSweep(1);
	}
}

void SeqTrack::UpdateChannelRelease(Channel *channel)
{
	if (channel->GetLength() == 0 && !channel->IsRelease()
	    && !mParserTrackParam.damperFlag)
	{
		channel->NoteOff();
	}
}

int SeqTrack::ParseNextTick(bool doNoteOn)
{
	SoundThread::AutoLock lock;

	if (!mOpenFlag)
		return 0;

	mParserTrackParam.volume.Update();
	mParserTrackParam.pan.Update();
	mParserTrackParam.surroundPan.Update();
	mParserTrackParam.pitchBend.Update();

	if (mParserTrackParam.noteFinishWait)
	{
		if (mChannelList)
			return 1;

		mParserTrackParam.noteFinishWait = false;
	}

	if (mParserTrackParam.wait > 0 && --mParserTrackParam.wait > 0)
		return 1;

	if (mParserTrackParam.currentAddr)
	{
		while (mParserTrackParam.wait == 0 && !mParserTrackParam.noteFinishWait)
		{
			ParseResult result = Parse(doNoteOn);
			if (result == PARSE_RESULT_FINISH)
				return -1;
		}
	}

	return 1;
}

void SeqTrack::StopAllChannel()
{
	SoundThread::AutoLock lock;

	for (Channel *channel = mChannelList; channel;
	     channel = channel->GetNextTrackChannel())
	{
		Channel::FreeChannel(channel);
		channel->Stop();
	}

	mChannelList = nullptr;
}

void SeqTrack::ReleaseAllChannel(int release)
{
	SoundThread::AutoLock lock;

	UpdateChannelParam();

	for (Channel *channel = mChannelList; channel;
	     channel = channel->GetNextTrackChannel())
	{
		if (channel->IsActive())
		{
			if (release >= 0)
			{
				NW4RAssertHeaderClampedLRValue_Line(329, release, 0,
				                                    MAX_ENVELOPE_VALUE);

				channel->SetRelease(static_cast<u8>(release));
			}

			channel->Release();
		}
	}
}

void SeqTrack::PauseAllChannel(bool flag)
{
	SoundThread::AutoLock lock;

	for (Channel *channel = mChannelList; channel;
	     channel = channel->GetNextTrackChannel())
	{
		if (channel->IsActive() && flag != channel->IsPause())
			channel->Pause(flag);
	}
}

void SeqTrack::AddChannel(Channel *channel)
{
	SoundThread::AutoLock lock;

	channel->SetNextTrackChannel(mChannelList);
	mChannelList = channel;
}

void SeqTrack::UpdateChannelParam()
{
	SoundThread::AutoLock lock;

	if (!mOpenFlag)
		return;

	if (!mChannelList)
		return;

	f32 volume = 1.0f;
	f32 parserVolume = mParserTrackParam.volume.GetValue() / 127.0f;
	f32 parserVolume2 = mParserTrackParam.volume2 / 127.0f;
	f32 parserMainVolume = mSeqPlayer->GetParserPlayerParam().volume / 127.0f;

	volume *= parserVolume * parserVolume;
	volume *= parserVolume2 * parserVolume2;
	volume *= parserMainVolume * parserMainVolume;
	volume *= mExtVolume;
	volume *= mSeqPlayer->GetVolume();

	f32 pitch =
		mParserTrackParam.pitchBend.GetValue() / 128.0f * mParserTrackParam.bendRange;

	f32 pitchRatio = 1.0f;
	pitchRatio *= mSeqPlayer->GetPitch();
	pitchRatio *= mExtPitch;

	f32 pan = 0.0f;
	pan += ut::Clamp(mParserTrackParam.pan.GetValue() / 63.0f, -1.0f, 1.0f);
	pan *= mPanRange;
	pan *= mSeqPlayer->GetPanRange();
	pan += mExtPan;
	pan += mSeqPlayer->GetPan();

	f32 surroundPan = 0.0f;
	surroundPan +=
		ut::Clamp(mParserTrackParam.surroundPan.GetValue() / 63.0f, 0.0f, 2.0f);
	surroundPan += mExtSurroundPan;
	surroundPan += mSeqPlayer->GetSurroundPan();

	f32 lpfFreq = 0.0f;
	lpfFreq += mParserTrackParam.lpfFreq;
	lpfFreq += mSeqPlayer->GetLpfFreq();

	int biquadType = mParserTrackParam.biquadType;
	f32 biquadValue = mParserTrackParam.biquadValue;

	if (mSeqPlayer->GetBiquadType() != 0)
	{
		biquadType = mSeqPlayer->GetBiquadType();
		biquadValue = mSeqPlayer->GetBiquadValue();
	}

	int remoteFilter = 0;
	remoteFilter += mSeqPlayer->GetRemoteFilter();

	f32 mainSend = 0.0f;
	mainSend += mParserTrackParam.mainSend / 127.0f - 1.0f;
	mainSend += mSeqPlayer->GetMainSend();

	f32 fxSend[AUX_BUS_NUM];
	for (int i = 0; i < AUX_BUS_NUM; i++)
	{
		AuxBus bus = static_cast<AuxBus>(i);
		fxSend[i] = 0.0f;
		fxSend[i] += mParserTrackParam.fxSend[i] / 127.0f;
		fxSend[i] += mSeqPlayer->GetFxSend(bus);
	}

	for (Channel *channel = mChannelList; channel;
	     channel = channel->GetNextTrackChannel())
	{
		channel->SetUserVolume(volume);
		channel->SetUserPitch(pitch);
		channel->SetUserPitchRatio(pitchRatio);
		channel->SetUserPan(pan);
		channel->SetUserSurroundPan(surroundPan);
		channel->SetUserLpfFreq(lpfFreq);
		channel->SetBiquadFilter(biquadType, biquadValue);
		channel->SetRemoteFilter(remoteFilter);
		channel->SetOutputLine(mSeqPlayer->GetOutputLine());
		channel->SetMainOutVolume(mSeqPlayer->GetMainOutVolume());
		channel->SetMainSend(mainSend);
		
		for (int i = 0; i < AUX_BUS_NUM; i++)
		{
			AuxBus bus = static_cast<AuxBus>(i);
			channel->SetFxSend(bus, fxSend[i]);
		}

		for (int i = 0; i < 4; i++) {
			channel->SetRemoteOutVolume(i, mSeqPlayer->GetRemoteOutVolume(i));
		}

		for (int i = 0; i < mSeqPlayer->GetVoiceOutCount(); i++)
			channel->SetVoiceOutParam(i, mSeqPlayer->GetVoiceOutParam(i));

		channel->SetLfoParam(mParserTrackParam.lfoParam);
		channel->SetLfoTarget(
			static_cast<Channel::LfoTarget>(mParserTrackParam.lfoTarget));
	}
}

void SeqTrack::FreeAllChannel()
{
	SoundThread::AutoLock lock;

	for (Channel *channel = mChannelList; channel;
	     channel = channel->GetNextTrackChannel())
	{
		Channel::FreeChannel(channel);
	}

	mChannelList = nullptr;
}

void SeqTrack::ChannelCallbackFunc(Channel *dropChannel,
                                   Channel::ChannelCallbackStatus status,
                                   u32 userData)
{
	SoundThread::AutoLock lock;

	SeqTrack *track = reinterpret_cast<SeqTrack *>(userData);

	NW4RAssertPointerNonnull_Line(586, dropChannel);
	NW4RAssertPointerNonnull_Line(587, track);

	switch (status)
	{
	case Channel::CALLBACK_STATUS_STOPPED:
	case Channel::CALLBACK_STATUS_FINISH:
		Channel::FreeChannel(dropChannel);
		break;
	}

	if (track->mSeqPlayer)
		track->mSeqPlayer->ChannelCallback(dropChannel);

	if (track->mChannelList == dropChannel)
	{
		track->mChannelList = dropChannel->GetNextTrackChannel();
		return;
	}

	Channel *channel = track->mChannelList;
	NW4RAssertPointerNonnull_Line(608, channel);

	for (; channel->GetNextTrackChannel();
	     channel = channel->GetNextTrackChannel())
	{
		if (channel->GetNextTrackChannel() == dropChannel)
		{
			channel->SetNextTrackChannel(dropChannel->GetNextTrackChannel());
			return;
		}
	}

	NW4RAssert_Line(617, false);
}

void SeqTrack::SetMute(SeqMute mute)
{
	SoundThread::AutoLock lock;

	switch (mute)
	{
	case MUTE_OFF:
		mParserTrackParam.muteFlag = false;
		break;

	case MUTE_STOP:
		StopAllChannel();
		mParserTrackParam.muteFlag = true;
		break;

	case MUTE_RELEASE:
		ReleaseAllChannel(-1);
		FreeAllChannel();
		mParserTrackParam.muteFlag = true;
		break;

	case MUTE_NO_STOP:
		mParserTrackParam.muteFlag = true;
		break;
	}
}

void SeqTrack::SetSilence(bool silenceFlag, int fadeTimes)
{
	SoundThread::AutoLock lock;

	mParserTrackParam.silenceFlag = silenceFlag;

	for (Channel *channel = mChannelList; channel;
	     channel = channel->GetNextTrackChannel())
	{
		channel->SetSilence(silenceFlag, (fadeTimes + 2) / 3);
	}
}

void SeqTrack::SetVolume(f32 volume)
{
	mExtVolume = volume;
}

void SeqTrack::SetPitch(f32 pitch)
{
	NW4RAssert_Line(668, pitch >= 0.0f);

	mExtPitch = pitch;
}

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

void SeqTrack::SetSurroundPan(f32 surroundPan)
{
	mExtSurroundPan = surroundPan;
}

void SeqTrack::SetPanRange(f32 panRange)
{
	mPanRange = panRange;
}

void SeqTrack::SetLpfFreq(f32 lpfFreq)
{
	mParserTrackParam.lpfFreq = lpfFreq;
}

void SeqTrack::SetBiquadFilter(int type, f32 value)
{
	mParserTrackParam.biquadType	= type;
	mParserTrackParam.biquadValue	= value;
}

void SeqTrack::SetModDepth(f32 modDepth)
{
	mParserTrackParam.lfoParam.depth = modDepth;
}

void SeqTrack::SetModSpeed(f32 modSpeed)
{
	mParserTrackParam.lfoParam.speed = modSpeed;
}

#if 0
// SeqTrack::GetTrackVariable? maybe both?
DECOMP_FORCE(NW4RAssertHeaderClampedLValue_String(varNo));
#endif

void SeqTrack::SetTrackVariable(int variable, s16 value) {
	mTrackVariable[variable] = value;
}

s16 volatile *SeqTrack::GetVariablePtr(int varNo)
{
	NW4RAssertHeaderClampedLRValue_Line(754, varNo, 0, TRACK_VARIABLE_NUM);

	if (varNo < TRACK_VARIABLE_NUM)
		return &mTrackVariable[varNo];

	return nullptr;
}

Channel *SeqTrack::NoteOn(int key, int velocity, s32 length, bool tieFlag)
{
	SoundThread::AutoLock lock;

	NW4RAssertHeaderClampedLRValue_Line(787, key, 0, 127);
	NW4RAssertHeaderClampedLRValue_Line(788, velocity, 0, 127);

	SeqPlayer::ParserPlayerParam const &playerParam =
		mSeqPlayer->GetParserPlayerParam();

	Channel *channel = nullptr;
	velocity = velocity * mParserTrackParam.velocityRange / 127;

	if (tieFlag)
	{
		channel = GetLastChannel();
		if (channel)
		{
			channel->SetKey(static_cast<u8>(key));

			f32 initVolume = velocity / 127.0f;
			channel->SetInitVolume(initVolume * initVolume);
		}
	}

	if (GetParserTrackParam().monophonicFlag)
	{
		channel = GetLastChannel();
		if (channel)
		{
			if (channel->IsRelease())
			{
				channel->Stop();
				channel = nullptr;
			}
			else
			{
				channel->SetKey(static_cast<u8>(key));

				f32 initVolume = velocity / 127.0f;
				channel->SetInitVolume(initVolume * initVolume);
				channel->SetLength(length);
			}
		}
	}

	if (!channel)
	{
		NoteOnInfo info =
		{
			mParserTrackParam.prgNo,
			key,
			velocity,
			tieFlag ? -1 : length,
			mParserTrackParam.initPan,
			playerParam.priority + GetParserTrackParam().priority,
			mSeqPlayer->GetVoiceOutCount(),
			&ChannelCallbackFunc,
			reinterpret_cast<u32>(this)
		};

		channel = mSeqPlayer->NoteOn(mParserTrackParam.bankNo, info);
		if (!channel)
			return nullptr;

		if (channel->GetAlternateAssignId() > 0)
		{
			for (Channel *itr = mChannelList; itr;
			     itr = itr->GetNextTrackChannel())
			{
				if (itr->GetAlternateAssignId()
				    == channel->GetAlternateAssignId())
				{
					itr->Release();
				}
			}
		}

		AddChannel(channel);
	}

	if (mParserTrackParam.attack <= MAX_ENVELOPE_VALUE)
		channel->SetAttack(mParserTrackParam.attack);

	if (mParserTrackParam.decay <= MAX_ENVELOPE_VALUE)
		channel->SetDecay(mParserTrackParam.decay);

	if (mParserTrackParam.sustain <= MAX_ENVELOPE_VALUE)
		channel->SetSustain(mParserTrackParam.sustain);

	if (mParserTrackParam.release <= MAX_ENVELOPE_VALUE)
		channel->SetRelease(mParserTrackParam.release);

	if (mParserTrackParam.envHold <= MAX_ENVELOPE_VALUE)
		channel->SetHold(mParserTrackParam.envHold);

	f32 sweepPitch = mParserTrackParam.sweepPitch;
	if (mParserTrackParam.portaFlag)
		sweepPitch += mParserTrackParam.portaKey - key;

	if (!mParserTrackParam.portaTime)
	{
		NW4RCheckMessage_Line(905, length != 0, "portatime zero is invalid.");

		channel->SetSweepParam(sweepPitch, length, false);
	}
	else
	{
		int length = mParserTrackParam.portaTime;
		length *= length;
		length *= sweepPitch >= 0.0f ? sweepPitch : -sweepPitch;
		length >>= 5;
		length *= 5;

		channel->SetSweepParam(sweepPitch, length, true);
	}

	mParserTrackParam.portaKey = key;

	channel->SetSilence(mParserTrackParam.silenceFlag != false, 0);
	channel->SetReleasePriorityFix(mSeqPlayer->IsReleasePriorityFix());
	channel->SetPanMode(mSeqPlayer->GetPanMode());
	channel->SetPanCurve(mSeqPlayer->GetPanCurve());

	return channel;
}

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