/*********************************************************************************************/
/*                                                                                           */
/* Sound																	   */
/*                                                                                           */
/*********************************************************************************************/

/*---------------------------------------------------------------------------
 *	Import Headers
\*--------------------------------------------------------------------------*/

#ifdef PSX_VERSION
#include "../spec_psx/typedefs.h"
#include "../spec_psx/specific.h"
#else
#include "../specific/standard.h"
#include "../specific/global.h"
#include "../specific/debug.h"
#endif

#include "sound.h"
#include "room.h"
#include "camera.h"
#include "text.h"
#include "control.h"

extern char exit_message[];
/*---------------------------------------------------------------------------
 *	Constants
\*--------------------------------------------------------------------------*/

#define MAX_SLOTS 32	// MUST be >= max physical channels

//#define SOUND_RANGE				10	// SOUND RANGE IN TERMS OF WORLD BLOCKS (range is spherical)
//#define SOUND_RADIUS			(SOUND_RANGE<<10)
//#define SOUND_RADIUS_SQRD		(SOUND_RADIUS*SOUND_RADIUS)
//#define SOUND_RANGE_MULT_CONSTANT	((int)(32768/SOUND_RADIUS))

#define SOUND_MAXVOL_RANGE		1
#define SOUND_MAXVOL_RADIUS		(SOUND_MAXVOL_RANGE<<10)
#define SOUND_MAXVOL_RADIUS_SQRD	(SOUND_MAXVOL_RADIUS*SOUND_MAXVOL_RADIUS)
#define MAX_VOLUME_CHANGE 		0x1000	// Max volume reduction from default if V flag set
#define MAX_PITCH_CHANGE 		6000

#define NO_PAN			(1<<12)
#define PITCH_WIBBLE	(1<<13)
#define VOLUME_WIBBLE	(1<<14)

enum sound_flags {
	NORMAL_SOUND,
	WAIT_SOUND,
	RESTART_SOUND,
	LOOPED_SOUND
};

/*---------------------------------------------------------------------------
 *	Type Definitions
\*--------------------------------------------------------------------------*/

typedef struct	{
	int nVolume;
	int nPan;
	int nSampleInfo;
	int nPitch;
} SoundSlot;

/*---------------------------------------------------------------------------
 *	Globals
\*--------------------------------------------------------------------------*/

int num_samples;				// Number of Samples
int sound_active;				// Do we want sound or not??
int num_sample_infos;			// Number of Sample Infos
SAMPLE_INFO* sample_infos;		// Sample Infos
sint16 sample_lut[MAX_SAMPLES];	// SampleNum to SampleInfo LUT

/*---------------------------------------------------------------------------
 *	Locals
\*--------------------------------------------------------------------------*/
#
static SoundSlot LaSlot[MAX_SLOTS];

/*---------------------------------------------------------------------------
 *  Macros
\*--------------------------------------------------------------------------*/

#define ArraySize(a) (sizeof(a)/sizeof((a)[0]))


#ifdef PSX_VERSION
#ifdef LOG
#define Log(format,args...) DebugOut(format "\n" , ## args)
#else
#define Log(args...)
#endif
#endif



/*---------------------------------------------------------------------------
 *  Public Functions
\*--------------------------------------------------------------------------*/

uint8	tracksRemoved[]={-1};

int	GetRealTrack(int track)
{
	int	i,real,tr=0;

//	printf("Track remapped from %d to ",track);
	for (i=2,real=2;i<track;i++)
	{
		if (tr>=0 && i==tracksRemoved[tr])
		{
			tr++;
			continue;
		}
		real++;
	}
//	printf("%d\n",real);
	return(real);
}


int SoundEffect(int nSFX, PHD_3DPOS *pPos, int flags)
{
	SAMPLE_INFO 	*pSI;
	uint32		distance;
	uint16		pan;
	sint32 		x,y,z;
	int			pitch;
	int			nVolume;
	int			nSampleInfo,nSample,nSfxSamples,nSlot,nMode,nHandle;
	int			nMinVolume,nMinSlot,nAttenuation;
	int SOUND_RADIUS;
	int SOUND_RADIUS_SQRD;

// If camera pos is underwater and sound is not (or vice versa) don't play it
	if ((!sound_active)
	|| (!(flags & SFX_ALWAYS)
	&& ((flags & UNDERWATER) != (room[camera.pos.room_number].flags & UNDERWATER))))
		return 0;

// Get the internal index from lookup
	nSampleInfo = sample_lut[nSFX];

	if (nSampleInfo == -1)
	{
		Log("Non present sample:%d", nSFX);
		sample_lut[nSFX] = -2;
		return 0;
	}

	if (nSampleInfo == -2)
		return 0;

	pSI = &sample_infos[nSampleInfo];

	if ((pSI->randomness)
	&& ((GetRandomControl() & 255) > pSI->randomness))
		return 0;

	pan=0;

/* -------- determine distance from camera */

	SOUND_RADIUS = pSI->radius << 10;
	SOUND_RADIUS_SQRD = (SOUND_RADIUS * SOUND_RADIUS);

	if (pPos)
	{
		x = (pPos->x_pos-camera.mike_pos.x);
		y = (pPos->y_pos-camera.mike_pos.y);
		z = (pPos->z_pos-camera.mike_pos.z);

		if ((x < -SOUND_RADIUS)
		||  (x >  SOUND_RADIUS)
		||  (y < -SOUND_RADIUS)
		||  (y >  SOUND_RADIUS)
		||  (z < -SOUND_RADIUS)
		||  (z >  SOUND_RADIUS))
			return 0;

		distance = (x*x)+(y*y)+(z*z);

		if (distance > SOUND_RADIUS_SQRD)
			return 0;

		else if (distance < SOUND_MAXVOL_RADIUS_SQRD)
			distance=0;

		else
			distance = phd_sqrt(distance) - SOUND_MAXVOL_RADIUS;

		if (!(pSI->flags & NO_PAN))
			pan = phd_atan(z, x) - camera.actual_angle;
	}
	else
		distance=0;	// assume sound is at camera

// get nVolume of fx
	nVolume = pSI->volume << 7;

	/* nVolume wibble flag is set (V) */
	if ((pSI->flags & VOLUME_WIBBLE))
		nVolume -= ((GetRandomControl()*MAX_VOLUME_CHANGE)>>15);

	nAttenuation = (distance*distance) / (SOUND_RADIUS_SQRD/0x10000);
//	nAttenuation=0x10000*distance/SOUND_RADIUS;
	nVolume=((65536-nAttenuation)*nVolume)>>16;

	if (nVolume<=0)
		return 0;
	else if (nVolume>0x7fff)
		nVolume=0x7fff;

	if (flags&PITCH_SHIFT)
		pitch=(flags>>8)&0xffffff;
	else
		pitch=0x10000;		/* 65536 is default pitch, but may be wibbled if P flag set */

	pitch += (pSI->pitch << 9);

	if ((pSI->flags&PITCH_WIBBLE))
		pitch+=((GetRandomControl()*MAX_PITCH_CHANGE)>>14)-MAX_PITCH_CHANGE;

	if (pSI->number<0)
		return 0;

	// random sound effect range
	nSfxSamples=(pSI->flags>>2)&15;
	if (nSfxSamples==1)
		nSample=pSI->number;
	else
		nSample=pSI->number+(int)((GetRandomControl()*nSfxSamples)>>15);

	nMode = pSI->flags&3;

	switch (nMode)
	{
		case WAIT_SOUND:
			for ( nSlot=0 ; nSlot<ArraySize(LaSlot) ; nSlot++ )
			{
				if (LaSlot[nSlot].nSampleInfo == nSampleInfo)
			 	{
					if (S_SoundSampleIsPlaying(nSlot))
						return 0;
					LaSlot[nSlot].nSampleInfo = -1;
			 	}
			}
			break;

		case RESTART_SOUND:
			for ( nSlot=0 ; nSlot<ArraySize(LaSlot) ; nSlot++ )
			{
				if (LaSlot[nSlot].nSampleInfo == nSampleInfo)
			 	{
					S_SoundStopSample(nSlot);
					LaSlot[nSlot].nSampleInfo = -1;
					break;
			 	}
			}
			break;

		case LOOPED_SOUND:
			for ( nSlot=0 ; nSlot<ArraySize(LaSlot) ; nSlot++ )
			{
				if (LaSlot[nSlot].nSampleInfo == nSampleInfo)
				{
					if (nVolume > LaSlot[nSlot].nVolume)
					{
						LaSlot[nSlot].nVolume = nVolume;
						LaSlot[nSlot].nPan = pan;
						LaSlot[nSlot].nPitch = pitch;
						//LaSlot[nSlot].nDistance=(uint16)distance; LaSlot[nSlot].nSFX=(uint16)nSFX;
						return 1;
					}
					return 0;
				}
			}
			break;

		case NORMAL_SOUND:
			break;
	}

	if (nMode == LOOPED_SOUND)
		nHandle = S_SoundPlaySampleLooped(nSample, (uint16)nVolume, pitch, pan);
	else
		nHandle = S_SoundPlaySample(nSample, (uint16)nVolume, pitch, pan);

	if (nHandle>=0)
	{
		LaSlot[nHandle].nVolume = nVolume;
		LaSlot[nHandle].nPan = pan;
		LaSlot[nHandle].nPitch = pitch;
		LaSlot[nHandle].nSampleInfo = nSampleInfo;
		//LaSlot[nHandle].nDistance=(uint16)distance; LaSlot[nHandle].nSFX=(uint16)nSFX;
		return 1;
	}

	if (nHandle != ERR_SOUND_NO_FREE_SLOTS)
	{
		if (nSample>=0)
			Log("Can't play SFX %d",nSFX);
		pSI->number=-1;
		return 0;
	}

	nMinVolume=0x8000;
	nMinSlot=-1;
	for (nSlot=1;nSlot<ArraySize(LaSlot);nSlot++)
	{
		if ((LaSlot[nSlot].nSampleInfo>=0) && (LaSlot[nSlot].nVolume<nMinVolume))
	 	{
			nMinVolume=LaSlot[nSlot].nVolume;
			nMinSlot=nSlot;
	 	}
	}

	if (nMinSlot<0)
	{
		Log("Mad Failure #3 in SoundEffect");
		return 0;
	}

	if (nVolume<nMinVolume)
		return 0;	// new sound is quieter than all current sounds

//	printf("Stealing channel %d\n",nMinSlot);
//	Log("Stealing channel %d",nMinSlot);
	S_SoundStopSample(nMinSlot);
	LaSlot[nMinSlot].nSampleInfo=-1;

	if (nMode==LOOPED_SOUND)
		nHandle = S_SoundPlaySampleLooped(nSample, (uint16)nVolume, pitch, pan);
	else
		nHandle = S_SoundPlaySample(nSample, (uint16)nVolume, pitch, pan);

	if (nHandle >= 0)
	{
		LaSlot[nHandle].nVolume = nVolume;
		LaSlot[nHandle].nPan = pan;
		LaSlot[nHandle].nPitch = pitch;
		LaSlot[nHandle].nSampleInfo = nSampleInfo;
		//LaSlot[nHandle].nDistance=(uint16)distance; LaSlot[nHandle].nSFX=(uint16)nSFX;
		return 1;
	}

//	printf("Mad Failure #2 in SoundEffect\n");
	Log("Mad Failure #2 in SoundEffect");
	return 0;
}

void StopSoundEffect(int nSFX)
{
	int nSlot;
	int nStartSampleInfo,nEndSampleInfo;

	if (!sound_active)
		return;
	nStartSampleInfo=sample_lut[nSFX];	// Get the internal index from lookup
	nEndSampleInfo=nStartSampleInfo+((sample_infos[nStartSampleInfo].flags>>2)&15);
	for (nSlot=0;nSlot<ArraySize(LaSlot);nSlot++)
	{
		if (LaSlot[nSlot].nSampleInfo>=nStartSampleInfo && LaSlot[nSlot].nSampleInfo<nEndSampleInfo)
	 	{
			S_SoundStopSample(nSlot);
			LaSlot[nSlot].nSampleInfo=-1;
	 	}
	}
}

void SOUND_EndScene(void)
{
	int nSlot;

	if (!sound_active)
		return;

	for (nSlot=0;nSlot<ArraySize(LaSlot);nSlot++)
	{
		if (LaSlot[nSlot].nSampleInfo>=0)
    		{
			if ((sample_infos[LaSlot[nSlot].nSampleInfo].flags&3)==LOOPED_SOUND)
    			{
				if (LaSlot[nSlot].nVolume)
    				{
					S_SoundSetPanAndVolume(nSlot,(sint16)LaSlot[nSlot].nPan,(uint16)LaSlot[nSlot].nVolume);
					S_SoundSetPitch(nSlot,LaSlot[nSlot].nPitch);
					LaSlot[nSlot].nVolume=0;
    				}
				else
    				{
					S_SoundStopSample(nSlot);
					LaSlot[nSlot].nSampleInfo=-1;
    				}
    			}
			else if (!S_SoundSampleIsPlaying(nSlot))
				LaSlot[nSlot].nSampleInfo=-1;
    		}
	}

//	{
//		long cnt;
//		for ( nSlot=cnt=0 ; nSlot<ArraySize(LaSlot) ; nSlot++ )
//		{
//			if (!S_SoundSampleIsPlaying(nSlot))
//				cnt++;
//		}
//		sprintf(exit_message, "Free channels : %d", cnt);
//		PrintDbug(1, 8, exit_message);
//	}
}

void SOUND_Stop()
{
	int nSlot;

	if (!sound_active)
		return;

	S_SoundStopAllSamples();

	for ( nSlot=0 ; nSlot<ArraySize(LaSlot) ; ++nSlot )
		LaSlot[nSlot].nSampleInfo = -1;
}

void SOUND_Init()
{
	int nSlot;

	S_SoundSetMasterVolume(32);		// hmm...

	for ( nSlot=0 ; nSlot<ArraySize(LaSlot) ; ++nSlot )
		LaSlot[nSlot].nSampleInfo = -1;

	sound_active=1;	// hmm...
}

#if 0

#define RING_SIZE 16

void SOUND_DrawOverlay(void)
{
	int nSlot;
	static int anV[MAX_SLOTS][RING_SIZE];
	static int nRingPos=0;
	int i,nY1,nY2;

	if (input&IN_E)
	{
		char buf[128];
		static char hexme[]="0123456789abcdef";
		char* p;
		uint8 bVal;

		p=buf;
		for (nSlot=0;nSlot<ArraySize(LaSlot);++nSlot)
		{
			if (LaSlot[nSlot].nSampleInfo<0)
				*p++='-';
			else
			{
				bVal=(uint8)LaSlot[nSlot].nSFX;
				*p++=hexme[bVal>>4];
				*p++=hexme[bVal&0x0f];
				*p++=' ';
			}
		}
		p[-1]=0;
		Log(buf);
	}

	for (nSlot=0;nSlot<ArraySize(LaSlot);++nSlot)
	{
		if ((LaSlot[nSlot].nSampleInfo>=0) && (LaSlot[nSlot].nDistance))
	 	{
			int nDistance,nAngle,nX,nY,nMul;

			nDistance=LaSlot[nSlot].nDistance;
			nAngle=LaSlot[nSlot].nPan;
			nMul=nDistance*100/SOUND_RADIUS;
			nX=(nMul*phd_sin(nAngle))>>W2V_SHIFT;
			nY=-(nMul*phd_cos(nAngle))>>W2V_SHIFT;
			S_DrawScreenLine(phd_winxmax-120,120,0,nX,nY,inv_colours[C_WHITE],0,0);
			nX+=phd_winxmax-120;
			nY+=120;
			S_DrawScreenLine(nX-1,nY-1,0,2,2,inv_colours[C_WHITE],0,0);
			S_DrawScreenLine(nX+1,nY-1,0,-2,2,inv_colours[C_WHITE],0,0);
	 	}
	}

#ifdef PC_VERSION
	for (nSlot=0;nSlot<MAX_SLOTS;++nSlot)
	{
		if (LaSlot[nSlot].nSampleInfo>=0)
		{
			int nVolume,nAttenuation;
			double dVolume;

			nVolume=sample_infos[LaSlot[nSlot].nSampleInfo].volume;
#ifdef PC_VERSION
#ifdef GAMEDEBUG
			if (rawkey(0x13))
				nAttenuation=LaSlot[nSlot].nDistance*LaSlot[nSlot].nDistance/(SOUND_RADIUS_SQRD/0x10000);
			else
#endif
#endif
				nAttenuation=0x10000*LaSlot[nSlot].nDistance/SOUND_RADIUS;
			nVolume=((65536-nAttenuation)*nVolume)>>16;
			if (nVolume<=0)
				nVolume=0;
			if (nVolume>0x8000)
				nVolume=0x8000;
			dVolume=5000.0*((((double)nVolume)/32768.0)-1.0);
			anV[nSlot][nRingPos]=(int)((dVolume+10000.0)*(200.0/10000.0));
		}
		else
			anV[nSlot][nRingPos]=0;
		nY1=anV[nSlot][(nRingPos+1)&(RING_SIZE-1)];
		for (i=1;i<RING_SIZE;++i)
		{
			nY2=anV[nSlot][(nRingPos+i+1)&(RING_SIZE-1)];
			if ((nY1|nY2))
				S_DrawScreenLine(phd_winxmax-300+(i<<2),250-nY1,phd_znear,4,nY1-nY2,nSlot+0xe0,0,0);
			nY1=nY2;
		}
	}
	nRingPos+=1;
	nRingPos&=(RING_SIZE-1);
#endif
}
#endif
