#include "game.h"

/*** defines ***/
#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)

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

/*** types ***/
enum sound_flags {NORMAL_SOUND, WAIT_SOUND, RESTART_SOUND, LOOPED_SOUND};

typedef struct
	{
	int nVolume;
	int nPan;
	int nSampleInfo;
	int nPitch;
//	uint16 nDistance;
//	uint16 nSFX;
	}
	SoundSlot;

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

/*** locals ***/
static SoundSlot LaSlot[MAX_SLOTS];

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

#ifdef PSX_VERSION
#define Log(args...)
#endif

/*** code ***/
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;

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

	nSampleInfo=sample_lut[nSFX];	// Get the internal index from lookup
	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 && (GetRandomDraw()>(int)pSI->randomness))	// Do chance check
		return 0;

	pan=0;
	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) ||	// Do box test
			(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;
		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;

	/* nVolume wibble flag is set (V) */
	if ((pSI->flags&VOLUME_WIBBLE))
		nVolume-=((GetRandomDraw()*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;
	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 */

	if ((pSI->flags&PITCH_WIBBLE))
		pitch+=((GetRandomDraw()*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)((GetRandomDraw()*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
//	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;
		}

	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;
			}
	}

void SOUND_Stop(void)
	{
	int nSlot;

	if (!sound_active)
		return;

	S_SoundStopAllSamples();
	for (nSlot=0;nSlot<ArraySize(LaSlot);++nSlot)
		LaSlot[nSlot].nSampleInfo=-1;
//	for (nSlot=0;nSlot<ArraySize(LaSlot);++nSlot)
//		if (LaSlot[nSlot].nSampleInfo>=0)
//			{
//			S_SoundStopSample(nSlot);
//			LaSlot[nSlot].nSampleInfo=-1;
//			}
	}

void SOUND_Init(void)
	{
	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