////////////////////////////////////////////////////////////////////////////////
//
// Copyright 2016 RWS Inc, All Rights Reserved
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of version 2 of the GNU General Public License as published by
// the Free Software Foundation
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
//
// SoundThing.cpp
// Project: Nostril (aka Postal)
// 
// History:
//		02/24/97 MJR	Stole infrastructure from Jon's AnimThing.
//
//		03/07/97	JMI	Now remembers last channel played with and will attempt
//							to abort the sample, if this object is killed.
//							This obejct now responds to the delete message.
//
//		03/07/97	JMI	Was only checking messages when the sound looped.
//
//		03/13/97	JMI	Load now takes a version number.
//
//		03/25/97	JMI	Changed EDIT_GUI_FILE to 8.3 compliant name.
//
//		04/10/97 BRH	Updated this to work with the new multi layer attribute
//							maps.
//
//		04/22/97	JMI	When I added the remembering of the last channel played,
//							I forgot to actually remember the last channel played.
//							Duh!  Fixed.
//
//		05/29/97	JMI	Removed ASSERT on m_pRealm->m_pAttribMap which no longer
//							exists.
//
//		06/17/97	JMI	Converted all occurrences of rand() to GetRand() and
//							srand() to SeedRand().
//
//		06/29/97	JMI	Converted EditRect(), EditRender(), and/or Render() to
//							use Map3Dto2D().
//
//		07/09/97	JMI	Now uses m_pRealm->Make2dResPath() to get the fullpath
//							for 2D image components.
//
//		07/17/97	JMI	Changed RSnd*'s to SampleMaster::SoundInstances.
//							Now uses new SampleMaster interface for volume and play
//							instance reference.
//
//		07/18/97	JMI	Got rid of bogus immitation PlaySample functions.
//							Now there is one PlaySample() function.  Also, you now
//							MUST specify a category and you don't have to specify a
//							SoundInstance ptr to specify a volume.
//							Also, added user edittable member m_lVolumeHalfLife.
//
//		07/25/97 MJR	Added the loading/saving of x,y,z.
//
//		07/28/97	JMI	Now varies volume as sound plays (based on distance to
//							ear).
//							Also, makes sure the next sound does not start until at
//							least after the sound sample should be done.
//
//		08/01/97	JMI	Added looping parameters.
//							Also, fixed bug where &m_b* were casted to long* (but
//							they are not 32 bits).
//
//		08/04/97	JMI	Made hiding and showing of id 499, in UseLoopingGuiCall,
//							_slightly_ more efficient.
//
//		08/04/97	JMI	Added m_sAmbient indicating whether or not this sound
//							is ambient (i.e., non-essential).
//							Also, implemented a special random number generator
//							strictly for sound things so they can be merry and random
//							and not de-synchronize.
//
//		08/05/97	JMI	Changed priority to use Z position rather than 2D 
//							projected Y position.
//
//		08/09/97	JMI	Added better UI for 'loop back from end' usage.
//							Now offers a checkbox option to loop infinitely.
//
//		08/11/97	JMI	Added RelayVolume() so CSoundRelays can update their
//							CSoundThing parents.
//
//		08/12/97	JMI	Now can browse for files with paths relative to the
//							samples SAK.
//
//		09/27/99	JMI	Eliminated boolean performance warnings.
//
//////////////////////////////////////////////////////////////////////////////
//
// This CThing-derived class will play sounds with various options.
//
//////////////////////////////////////////////////////////////////////////////
#define SOUNDTHING_CPP

#include "RSPiX.h"
#include "SoundThing.h"
#include "game.h"

// This class has its own GetRandom() to keep it from de-synching the game.
#ifdef GetRandom
	#undef GetRandom
#endif

#ifdef GetRand
	#undef GetRand
#endif

#ifdef MOBILE //Arm RAND_MAX is a full int, code expecting a short!!
#define RAND_MAX 0x7fff
#endif

////////////////////////////////////////////////////////////////////////////////
// Macros/types/etc.
////////////////////////////////////////////////////////////////////////////////

#define IMAGE_FILE			"soundthing.bmp"

#define GUI_FILE_NAME		"res/editor/Sound.gui"

#define END_OF_TIME			0x7fffffff	// Unfortunately, we won't be dead (or,
													// considering it's always just 24 days 
													// off, fortunately).  The program, will
													// break, however.

////////////////////////////////////////////////////////////////////////////////
// Class statics.
////////////////////////////////////////////////////////////////////////////////

int16_t	CSoundThing::ms_sFileCount			= 0;	// File count.         
int32_t	CSoundThing::ms_lGetRandomSeed	= 0;	// Seed for get random.

////////////////////////////////////////////////////////////////////////////////
// Load object (should call base class version!)
////////////////////////////////////////////////////////////////////////////////
int16_t CSoundThing::Load(								// Returns 0 if successfull, non-zero otherwise
	RFile* pFile,											// In:  File to load from
	bool bEditMode,										// In:  True for edit mode, false otherwise
	int16_t sFileCount,										// In:  File count (unique per file, never 0)
	uint32_t	ulFileVersion)									// In:  Version of file format to load.
	{
	int16_t sResult = CThing::Load(pFile, bEditMode, sFileCount, ulFileVersion);
	
	if (sResult == 0)
		{
		// If new file . . . 
		if (sFileCount != ms_sFileCount)
			{
			ms_sFileCount	= sFileCount;
			
			// Do one time stuff.

			// Reseed local random.
			ms_lGetRandomSeed	= 0;
			}

		switch (ulFileVersion)
			{
			default:
			case 40:
				pFile->Read(&m_sAmbient);
			case 39:
			case 38:
				pFile->Read(&m_sPurgeSampleWhenDone	);
				pFile->Read(&m_sUseLooping				);
				pFile->Read(&m_lStopLoopingTime		);
				pFile->Read(&m_lNumLoopBacks			);
				pFile->Read(&m_lLoopBackTo				);
				pFile->Read(&m_lLoopBackFrom			);
																
			case 37:
			case 36:
			case 35:
			case 34:
				pFile->Read(&m_dX);
				pFile->Read(&m_dY);
				pFile->Read(&m_dZ);
			case 33:
			case 32:
			case 31:
				pFile->Read(&m_lVolumeHalfLife);
			case 30:
			case 29:
			case 28:
			case 27:
			case 26:
			case 25:
			case 24:
			case 23:
			case 22:
			case 21:
			case 20:
			case 19:
			case 18:
			case 17:
			case 16:
			case 15:
			case 14:
			case 13:
			case 12:
			case 11:
			case 10:
			case 9:
			case 8:
			case 7:
			case 6:
			case 5:
			case 4:
			case 3:
			case 2:
			case 1:
				int32_t	lBool;
				pFile->Read(&lBool);
				m_bInitiallyEnabled	= lBool ? true : false;
				pFile->Read(&lBool);
				m_bInitiallyRepeats	= lBool ? true : false;
				pFile->Read(m_lMinTime, 2);
				pFile->Read(m_lRndTime, 2);
				pFile->Read(m_szResName);
				break;
			}

		// Make sure there were no file errors or format errors . . .
		if (!pFile->Error() && sResult == 0)
			{
			sResult = Init();
			}
		else
			{
			sResult = -1;
			TRACE("CSoundThing::Load(): Error reading from file!\n");
			}
		}
	
	return sResult;
	}

////////////////////////////////////////////////////////////////////////////////
// Startup object
////////////////////////////////////////////////////////////////////////////////
int16_t CSoundThing::Startup(void)								// Returns 0 if successfull, non-zero otherwise
	{
	// Set the collective volume to zero to start.
	m_lCollectiveVolume	= 0;

	return 0;
	}


////////////////////////////////////////////////////////////////////////////////
// Shutdown object
////////////////////////////////////////////////////////////////////////////////
int16_t CSoundThing::Shutdown(void)							// Returns 0 if successfull, non-zero otherwise
	{
	return 0;
	}


////////////////////////////////////////////////////////////////////////////////
// Suspend object
////////////////////////////////////////////////////////////////////////////////
void CSoundThing::Suspend(void)
	{
	m_sSuspend++;
	}


////////////////////////////////////////////////////////////////////////////////
// Resume object
////////////////////////////////////////////////////////////////////////////////
void CSoundThing::Resume(void)
	{
	m_sSuspend--;

	// If we're actually going to start updating again....
	if (m_sSuspend == 0)
		{
		// This is kind of cheesy, but it will work to some degree
		m_lLastStartTime = m_pRealm->m_time.GetGameTime();
		}
	}


////////////////////////////////////////////////////////////////////////////////
// Update object
////////////////////////////////////////////////////////////////////////////////
void CSoundThing::Update(void)
	{
	if (!m_sSuspend)
		{
		// Get current time
		int32_t lCurTime = m_pRealm->m_time.GetGameTime();

		// If enabled and (non-ambient or ambients allowed) . . .
		if (m_bEnabled == true && (m_sAmbient == FALSE || g_GameSettings.m_sPlayAmbientSounds) )
			{
			// If current time hits next starting time (or if we're in the init state)
			if ((lCurTime >= m_lNextStartTime) || (m_sWhichTime < 0))
				{
				int32_t	lSampleDuration	= 0;
				int32_t	lLoopStartTime;
				int32_t	lLoopEndTime	= m_lLoopBackFrom;
				if (m_sUseLooping)
					{
					lLoopStartTime	= m_lLoopBackTo;
					}
				else
					{
					// Indicate no looping.
					lLoopStartTime	= -1;
					}

				// If first time (not init) OR nth time with repeat enabled . . .
				// Play the sound (unless it's the init state)
				if (m_sWhichTime == 0 || (m_sWhichTime > 0 && m_bRepeats == true) )
					{
					// If using loop parameters . . .
					if (m_sUseLooping)
						{						
						// Stop looping existing sound.
						StopLoopingSample(m_siChannel);
						}

					bool	bPurge;
					if (m_sPurgeSampleWhenDone)
						{
						bPurge	= true;
						}
					else
						{
						bPurge	= false;
						}



					PlaySample(																	// Returns nothing.
																									// Does not fail.
						m_id,																		// In:  Identifier of sample you want played.
						SampleMaster::Ambient,												// In:  Sound Volume Category for user adjustment
						DistanceToVolume(m_dX, m_dY, m_dZ, m_lVolumeHalfLife),	// In:  Initial Sound Volume (0 - 255) 
						&m_siChannel,															// Out: Handle for adjusting sound volume
						&lSampleDuration,														// Out: Sample duration in ms, if not NULL.
						lLoopStartTime,														// In:  Where to loop back to in milliseconds.                
																									//	-1 indicates no looping (unless m_sLoop is                 
																									// explicitly set).                                           
						lLoopEndTime,															// In:  Where to loop back from in milliseconds.              
																									// If less than 1, the end + lLoopEndTime is used.       
						bPurge);																	// In:  Call ReleaseAndPurge rather than Release after playing

					///////////////////////////////////////////////////////////////////////////////////////////////////
					// NOTE: We have to use the exact logic RSnd does to determine the loop points so we can correctly
					// determine the amount of time until we stop looping.
					///////////////////////////////////////////////////////////////////////////////////////////////////

					// If using the end . . .
					if (lLoopEndTime < 1)
						{
						// Use the duration plus the specified negative time.
						lLoopEndTime		+= lSampleDuration;
						}

					// Fix these values in case we're in release mode.
					if (lLoopStartTime > lSampleDuration)
						{
						lLoopStartTime = lSampleDuration;
						}

					if (lLoopEndTime > lSampleDuration)
						{
						lLoopEndTime = lSampleDuration;
						}

					if (lLoopStartTime < 0 && m_sUseLooping)
						{							
						lLoopStartTime = 0;
						}

					if (lLoopStartTime > lLoopEndTime)
						{
						lLoopStartTime = lLoopEndTime;
						}

					// If using loop parameters and loop backs are not infinite . . .
					if (m_sUseLooping && m_lNumLoopBacks >= 0)
						{							
						// Calculate time until we stop looping . . .
						m_lStopLoopingTime	= lCurTime + (m_lNumLoopBacks + 1) * (lLoopEndTime - lLoopStartTime) + lLoopStartTime;
						}
					else
						{
						// Set time way into the future so we don't bother for a while.
						m_lStopLoopingTime	= END_OF_TIME;
						}
					}

				// Save current start time (use lCurTime in case we're in the init state)
				m_lLastStartTime = lCurTime;
				
				// Inc which time to use (don't go past 1)
				if (m_sWhichTime < 1)
					m_sWhichTime++;


				// Pick random time between 0 and specified random time
//				int32_t	lRnd = (int32_t)(((float)GetRand() / RAND_MAX) * (float)m_lRndTime[m_sWhichTime]);
				int32_t	lRnd = (int32_t)(((float)GetRand() / float(0x7fff)) * (float)m_lRndTime[m_sWhichTime]);
				// Make sure this at least the length of the sample.
				int32_t	lWaitDuration	= MAX(int32_t(m_lMinTime[m_sWhichTime] + lRnd), lSampleDuration);
				// Calculate next starting time
				m_lNextStartTime = m_lLastStartTime + lWaitDuration;
				}
			
			// Relay our volume (we act as just another satellite).
			RelayVolume(DistanceToVolume(m_dX, m_dY, m_dZ, m_lVolumeHalfLife) ); 
			}

		// If time to stop looping . . .
		if (lCurTime >= m_lStopLoopingTime)
			{
			if (m_sUseLooping)
				{
				// Stop looping existing sound.
				StopLoopingSample(m_siChannel);
				}

			// Set time way into the future so we don't bother for a while.
			m_lStopLoopingTime	= END_OF_TIME;
			}

		// Process messages.
		ProcessMessages();

		switch (m_state)
			{
			case State_Happy:
				break;
			case State_Delete:
				// Banzai!
				delete this;
				return ;
			}
		}
	}


////////////////////////////////////////////////////////////////////////////////
// Render object
////////////////////////////////////////////////////////////////////////////////
void CSoundThing::Render(void)
	{
	// Render our sound.  What a cheezy way of trying to justify putting this 
	//	here.

	// Adjust volume of last play instance.  Clip just in case.	
	SetInstanceVolume(m_siChannel, MIN((int32_t)255L, m_lCollectiveVolume) );

	// Reset volume.
	m_lCollectiveVolume	= 0;
	}

////////////////////////////////////////////////////////////////////////////////
// Helper function/macro for changing a GUIs text value.
////////////////////////////////////////////////////////////////////////////////
inline void SetGuiItemVal(	// Returns nothing.
	RGuiItem*	pguiRoot,	// In:  GUI Root.
	int32_t			lId,			// In:  ID of item whose text we'll change.
	int32_t			lVal)			// In:  New value.
	{
	RGuiItem*	pgui	= pguiRoot->GetItemFromId(lId);
	if (pgui)
		{
		pgui->SetText("%ld", lVal);
		pgui->Compose();
		}
	}

////////////////////////////////////////////////////////////////////////////////
// Callback from multibtn checkbox.
////////////////////////////////////////////////////////////////////////////////
static void CheckEnableGuiCall(	// Returns nothing.
	RGuiItem*	pgui_pmb)			// In:  GUI pointer to the multi button that was 
											// pressed.
	{
	ASSERT(pgui_pmb->m_type == RGuiItem::MultiBtn);

	RMultiBtn*	pmb	= (RMultiBtn*)pgui_pmb;

	// Show based on value stored in GUI.
	int16_t	sVisible	= pmb->m_ulUserData;
	// If unchecked . . .
	if (pmb->m_sState == 1)
		{
		// Opposite show/hide state.
		sVisible	= !sVisible;
		}

	RGuiItem*	pguiLoopSettingsContainer	= pmb->GetParent()->GetItemFromId(pmb->m_ulUserInstance);
	if (pguiLoopSettingsContainer)
		{
		pguiLoopSettingsContainer->SetVisible(sVisible);
		}
	}

////////////////////////////////////////////////////////////////////////////////
// Callback from GUI to browse and fill another GUI with result, if a relative
// path could be made.
////////////////////////////////////////////////////////////////////////////////
static void BrowseCall(		// Returns nothing.
	RGuiItem*	pgui)			// In:  GUI pointer that was pressed.
	{		
	}

////////////////////////////////////////////////////////////////////////////////
// Init object
////////////////////////////////////////////////////////////////////////////////
int16_t CSoundThing::Init(void)							// Returns 0 if successfull, non-zero otherwise
	{
	int16_t sResult = 0;

	Kill();

	m_lLastStartTime = 0;
	m_lNextStartTime = 0;
	m_sWhichTime = -1;
	m_bEnabled = m_bInitiallyEnabled;
	m_bRepeats = m_bInitiallyRepeats;

	// Tell samplemaster to cache (preload) this sample	
	CacheSample(m_id);

	return sResult;
	}


////////////////////////////////////////////////////////////////////////////////
// Kill object
////////////////////////////////////////////////////////////////////////////////
int16_t CSoundThing::Kill(void)							// Returns 0 if successfull, non-zero otherwise
	{
	if (m_pImage != 0)
		rspReleaseResource(&g_resmgrGame, &m_pImage);

	m_pRealm->m_scene.RemoveSprite(&m_sprite);

	// If we have a play instance identifier . . .
	if (m_siChannel != 0)
		{
		// Abort it.
		AbortSample(m_siChannel);
		// Done with ID.
		m_siChannel	= 0;
		}

	return 0;
	}


////////////////////////////////////////////////////////////////////////////////
// Process our message queue.
////////////////////////////////////////////////////////////////////////////////
void CSoundThing::ProcessMessages(void)
	{
	// Check queue of messages.
	GameMessage	msg;
	while (m_MessageQueue.DeQ(&msg) == true)
		{
		switch(msg.msg_Generic.eType)
			{
			case typeObjectDelete:
				m_state	= State_Delete;
				break;
			}
		
		}
	}

////////////////////////////////////////////////////////////////////////////////
// Don't call this from outside of CSoundThing.  It should affect only
// CSoundThing stuff.
////////////////////////////////////////////////////////////////////////////////
int32_t CSoundThing::GetRandom(void)
	{
	return (((ms_lGetRandomSeed = ms_lGetRandomSeed * 214013L + 2531011L) >> 16) & 0x7fff);
	}

////////////////////////////////////////////////////////////////////////////////
// Relay the volume to add to this CSoundThing's collective volume.
////////////////////////////////////////////////////////////////////////////////
void CSoundThing::RelayVolume(	// Returns nothing.
	int32_t lVolume)						// In:  Volume to relay.
	{
	m_lCollectiveVolume	+= lVolume;
	}

////////////////////////////////////////////////////////////////////////////////
// EOF
////////////////////////////////////////////////////////////////////////////////
