#ifndef NW4R_SND_SEQ_PLAYER_H
#define NW4R_SND_SEQ_PLAYER_H

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

#include "common.h"

#include "nw4r/snd/snd_BasicPlayer.h"
#include "nw4r/snd/snd_DisposeCallbackManager.h" // DisposeCallback
#include "nw4r/snd/snd_SeqTrack.h"
#include "nw4r/snd/snd_SoundThread.h"
#include "nw4r/ut/ut_Lock.h"

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

// forward declarations
namespace nw4r { namespace snd { namespace detail { class Channel; }}}
namespace nw4r { namespace snd { namespace detail { class NoteOnCallback; }}}
namespace nw4r { namespace snd { namespace detail { struct NoteOnInfo; }}}
namespace nw4r { namespace snd { namespace detail { class SeqTrack; }}}
namespace nw4r { namespace snd { namespace detail { class SeqTrackAllocator; }}}

namespace nw4r { namespace snd
{
	// [R89JEL]:/bin/RVL/Debug/mainD.elf:.debug::0x2dbb2
	struct SeqUserprocCallbackParam
	{
		s16		volatile *localVariable;	// size 0x04, offset 0x00
		s16		volatile *globalVariable;	// size 0x04, offset 0x04
		s16		volatile *trackVariable;	// size 0x04, offset 0x08
		bool	cmpFlag;					// size 0x01, offset 0x0C
		/* 3 bytes padding */
	}; // size 0x10
}} // namespace nw4r::snd

/*******************************************************************************
 * classes
 */

namespace nw4r { namespace snd { namespace detail
{
	// [R89JEL]:/bin/RVL/Debug/mainD.elf:.debug::0x2dfad
	class SeqPlayer : public BasicPlayer,
	                  public DisposeCallback,
	                  public SoundThread::PlayerCallback
	{
	// enums
	public:
		// [R89JEL]:/bin/RVL/Debug/mainD.elf:.debug::0x2dee8
		enum OffsetType
		{
			OFFSET_TYPE_TICK,
			OFFSET_TYPE_MILLISEC,
		};

		// [R89JEL]:/bin/RVL/Debug/mainD.elf:.debug::0x2df3b
		enum SetupResult
		{
			SETUP_SUCCESS,

			SETUP_ERR_CANNOT_ALLOCATE_TRACK,

			SETUP_ERR_UNKNOWN
		};

	// nested types
	public:
		// [R89JEL]:/bin/RVL/Debug/mainD.elf:.debug::0x2cce4
		struct ParserPlayerParam
		{
			u8				volume;		// size 0x01, offset 0x00
			u8				priority;	// size 0x01, offset 0x01
			u8				timebase;	// size 0x01, offset 0x02
			/* 1 byte padding */
			u16				tempo;		// size 0x02, offset 0x04
			/* 2 bytes padding */
			NoteOnCallback	*callback;	// size 0x04, offset 0x08
		}; // size 0x0c

	// typedefs
	public:
		typedef void SeqUserprocCallback(u16 procId,
		                                 SeqUserprocCallbackParam *param,
		                                 void *callbackData);

	// methods
	public:
		// cdtors
		SeqPlayer();
		virtual ~SeqPlayer();

		// virtual function ordering
		// vtable BasicPlayer
		virtual bool Start();
		virtual void Stop();
		virtual void Pause(bool flag);
		virtual bool IsActive() const { return mActiveFlag; }
		virtual bool IsStarted() const { return mStartedFlag; }
		virtual bool IsPause() const { return mPauseFlag; }

		// vtable DisposeCallback
		virtual void InvalidateData(void const *start, void const *end);
		virtual void InvalidateWaveData(void const *, void const *) {}

		// vtable SeqPlayer
		/* WARNING: must come before SoundThread::PlayerCallback virtual
		 * functions
		 */
		virtual void ChannelCallback(Channel *channel ATTR_UNUSED) {}

		// vtable SoundThread::PlayerCallback
		virtual void OnUpdateFrameSoundThread() { Update(); }
		virtual void OnShutdownSoundThread() { Stop(); }

		// methods
		SetupResult Setup(SeqTrackAllocator *trackAllocator, u32 allocTracks,
		                  int voiceOutCount, NoteOnCallback *callback);
		void Update();
		void Skip(OffsetType offsetType, int offset);
		void Shutdown();

		bool IsReleasePriorityFix() const { return mReleasePriorityFixFlag; }
		f32 GetPanRange() const { return mPanRange; }
		int GetVoiceOutCount() const { return mVoiceOutCount; }
		ParserPlayerParam &GetParserPlayerParam() { return mParserParam; }
		SeqTrack *GetPlayerTrack(int trackNo);
		s16 volatile *GetVariablePtr(int varNo);

		s16 GetLocalVariable(int varNo) const;
		void SetLocalVariable(int varNo, s16 value);
		static void SetGlobalVariable(int varNo, s16 value);

		void SetTempoRatio(f32 tempo);
		void SetReleasePriorityFix(bool fix);
		void SetChannelPriority(int priority);
		void SetSeqUserprocCallback(SeqUserprocCallback *callback, void *arg);
		void SetPlayerTrack(int trackNo, SeqTrack *track);

		f32 CalcTickPerMsec() const
		{
			return CalcTickPerMinute() / (60 * 1000);
		}
		f32 CalcTickPerMinute() const
		{
			return mParserParam.timebase * mParserParam.tempo * mTempoRatio;
		}

		u32 GetTickCounter() const {
			return mTickCounter;
		}

		void SetSeqData(void const *seqBase, s32 seqOffset);

		void CallSeqUserprocCallback(u16 procId, SeqTrack *track);
		Channel *NoteOn(int bankNo, NoteOnInfo const &noteOnInfo);

		void SetTrackMute(u32 trackFlags, SeqMute mute);
		void SetTrackSilence(u32 trackFlags, bool silence, int fadeFrames);
		void SetTrackVolume(u32 trackFlags, f32 volume);

		static void InitSeqPlayer();

	private:
		void InitParam(int voiceOutCount, NoteOnCallback *callback);

		void UpdateChannelParam();

		void CloseTrack(int trackNo);
		void FinishPlayer();

		int ParseNextTick(bool doNoteOn); // meant to be bool?
		void UpdateTick(int msec);
		void SkipTick();

		template <typename T>
		void SetTrackParam(u32 trackFlags, void (SeqTrack::*pSetter)(T), T param) {
			ut::AutoInterruptLock lock;

			for (int i = 0; i < TRACK_NUM && trackFlags != 0;
				trackFlags >>= 1, i++) {

				if (trackFlags & 1) {
					SeqTrack* pTrack = GetPlayerTrack(i);

					if (pTrack != NULL) {
						(pTrack->*pSetter)(param);
					}
				}
			}
		}

		template <typename T, typename U>
		void SetTrackParam(u32 trackFlags, void (SeqTrack::*pSetter)(T, U), T param, U param2) {
			ut::AutoInterruptLock lock;

			for (int i = 0; i < TRACK_NUM && trackFlags != 0;
				trackFlags >>= 1, i++) {

				if (trackFlags & 1) {
					SeqTrack* pTrack = GetPlayerTrack(i);

					if (pTrack != NULL) {
						(pTrack->*pSetter)(param, param2);
					}
				}
			}
		}

	// static members
	public:
		static int const MAX_SKIP_TICK_PER_FRAME = 768;
		static int const DEFAULT_TEMPO = 120;
		static int const DEFAULT_TIMEBASE = 48;
		static int const VARIABLE_DEFAULT_VALUE = -1;
		static int const TRACK_NUM_PER_PLAYER = 16;
		static int const GLOBAL_VARIABLE_NUM = 16;
		static int const PLAYER_VARIABLE_NUM = 16;

		static const int TRACK_NUM = 16;

	private:
		static s16 mGlobalVariable[GLOBAL_VARIABLE_NUM];

	// members
	private:
		/* base BasicPlayer */												// size 0x0a4, offset 0x000
		/* base DisposeCallback */											// size 0x00c, offset 0x0a4
		/* base SoundThread::PlayerCallback */								// size 0x00c, offset 0x0b0
		bool				mActiveFlag;									// size 0x001, offset 0x0ec
		bool				mStartedFlag;									// size 0x001, offset 0x0ed
		bool				mPauseFlag;										// size 0x001, offset 0x0ee
		bool				mReleasePriorityFixFlag;						// size 0x001, offset 0x0ef
		f32					mPanRange;										// size 0x004, offset 0x0f0
		f32					mTempoRatio;									// size 0x004, offset 0x0f4
		f32					mTickFraction;									// size 0x004, offset 0x0f8
		u32					mSkipTickCounter;								// size 0x004, offset 0x0fc
		f32					mSkipTimeCounter;								// size 0x004, offset 0x100
		s32					mVoiceOutCount;									// size 0x004, offset 0x104
		ParserPlayerParam	mParserParam;									// size 0x00c, offset 0x108
		SeqTrackAllocator	*mSeqTrackAllocator;							// size 0x004, offset 0x114
		SeqUserprocCallback	*mSeqUserprocCallback;							// size 0x004, offset 0x118
		void				*mSeqUserprocCallbackArg;						// size 0x004, offset 0x11c
		SeqTrack			*mTracks[TRACK_NUM_PER_PLAYER];					// size 0x040, offset 0x120
		s16					volatile mLocalVariable[PLAYER_VARIABLE_NUM];	// size 0x020, offset 0x160
		u32					mTickCounter;									// size 0x004, offset 0x180
	}; // size 0x184
}}} // namespace nw4r::snd::detail

#endif // NW4R_SND_SEQ_PLAYER_H
