#include <string.h>
#include <dos.h>

#include "sfx.h"
#include "db.h"
#include "globals.h"
#include "sound.h"
#include "engine.h"
#include "error.h"
#include "fx_man.h"
#include "player.h"
#include "gameutil.h"
#include "trig.h"
#include "globals.h"
#include "misc.h"
#include "debug4g.h"

// sample rate in samples / sec
#define kSampleRate		11110

// sound of sound in XY units / sec
#define kSoundVel		M2X(600.0)

// sound of sound in XY units / frame interval
#define kSoundVelFrame	(kSoundVel * kFrameTicks / kTimerRate)

// Intra-aural delay in seconds
#define kIntraAuralTime	0.0006

// Intra-aural distance based on delay and speed of sound
#define kIntraAuralDist	(kIntraAuralTime / 2 * kSoundVel)

// 16:16 fixed point constant for convert XY distance to sample phase
#define kDistPhase		((kSampleRate << 16) / kSoundVel)

// scale value for all 3D sounds
#define kVolScale		256

// volume different between sound in front and behind pinna focus
#define kBackFilter 	0x4000

int gPinnaAngle = kAngle45;

static POINT2D earL, earR, earL0, earR0;
static VECTOR2D earVL, earVR;
static int lPhase, rPhase, lVol, rVol;
static int lVel, rVel;

struct SOUNDEFFECT
{
	char *name;
	int relVol;
	RESHANDLE hResource;
};

SOUNDEFFECT soundEffect[kMaxSoundEffect] =
{
	{ "arc1",		80 },	// kSfxArc1
	{ "arc2",		80 },	// kSfxArc2
	{ "arc3",		80 },	// kSfxArc3
	{ "dmhack2",	60 },	// kSfxAxeHack
	{ "bat",		80 },	// kSfxBatFlap
	{ "bould3",     80 },	// kSfxBoulderTrap
	{ "breathe1",   80 },	// kSfxBreathe1
	{ "breathe2",   80 },	// kSfxBreathe2
	{ "bubrise",    80 },	// kSfxBubbleRise
	{ "chains",     80 },	// kSfxChains
	{ "choke",		80 },	// kSfxChoke1
	{ "choke2",     80 },	// kSfxChoke2
	{ "gargoyle",	80 },	// kSfxClaw1
	{ "dmpldeth",	80 },	// kSfxDeath
	{ "dive",       80 },	// kSfxDive
	{ "dorcreak",   80 },	// kSfxDoorCreak
	{ "doorslid",   80 },	// kSfxDoorSlide
	{ "dripsurf",	20 },	// kSfxDrip1
	{ "dripwat4",   15 },	// kSfxDrip2
	{ "dripwat5",   15 },	// kSfxDrip3
	{ "emerge",		80 },	// kSfxEmerge
	{ "exploa",   	300 },	// kSfxExplode1
	{ "fall",       80 },	// kSfxFall
	{ "floorcr2",   80 },	// kSfxFloorCreak
	{ "footdrt1",   80 },	// kSfxFootDirt1
	{ "footdrt2",   80 },	// kSfxFootDirt2
	{ "footgrs1",	80 },	// kSfxFootGrass1
	{ "footgrs2",   80 },	// kSfxFootGrass2
	{ "footmtl1",   80 },	// kSfxFootMetal1
	{ "footmtl2",   80 },	// kSfxFootMetal2
	{ "footstn1",   80 },	// kSfxFootStone1
	{ "footstn2",   80 },	// kSfxFootStone2
	{ "footwod1",	80 },	// kSfxFootWood1
	{ "footwod2",   80 },	// kSfxFootWood2
	{ "footwtr1",   80 },	// kSfxFootWater1
	{ "footwtr2",   80 },	// kSfxFootWater2
	{ "pf_flesh",   15 },	// kSfxForkFlesh
	{ "pf_metal",   15 },	// kSfxForkMetal
	{ "pf_rock",    10 },	// kSfxForkRock
	{ "pf_uw_fl",   10 },	// kSfxForkFleshUW
	{ "pf_uw_mt",   10 },	// kSfxForkMetalUW
	{ "pf_uw_rk",   10 },	// kSfxForkRockUW
	{ "pf_uw_wd",   10 },	// kSfxForkWoodUW
	{ "pf_wood",    10 },	// kSfxForkWood
	{ "flesh2",     80 },	// kSfxFry
	{ "tnt_fuse",   80 },	// kSfxFuseLoop
	{ "gargoyle",   80 },	// kSfxGargFlap
	{ "gasp",       80 },	// kSfxGasp
	{ "gearstrt",   80 },	// kSfxGearStart
	{ "geargrin",   80 },	// kSfxGearMove
	{ "gearhalt",   80 },	// kSfxGearStop
	{ "glasshit",   120 },	// kSfxGlassHit
	{ "gust1",      80 },	// kSfxGust1
	{ "gust2",      80 },	// kSfxGust2
	{ "gust3",      80 },	// kSfxGust3
	{ "gust4",      80 },	// kSfxGust4
	{ "dmitemup",	80 },	// kSfxItemUp
	{ "jump",       40 },	// kSfxJump
	{ "land",       40 },	// kSfxLand
	{ "lnd_dirt",   80 },	// kSfxLandDirt
	{ "lnd_metl",   80 },	// kSfxLandMetal
	{ "lnd_stn",    80 },	// kSfxLandStone
	{ "lnd_wood",   80 },	// kSfxLandWood
	{ "lnd_wtr",    80 },	// kSfxLandWater
	{ "elevstrt",   80 },	// kSfxLiftStart
	{ "elevmov",    80 },	// kSfxLiftMove
	{ "elevstop",   80 },	// kSfxLiftStop
	{ "dmradio",	80 },	// kSfxMessage		// this should be an interface sound, not 3D
	{ "dmplpain",   60 },	// kSfxPlayerPain1
	{ "pottery",    80 },	// kSfxPotteryHit
	{ "dmgetpow",	80 },	// kSfxPowerUp
	{ "tnt_arm",    80 },	// kSfxProxArm
	{ "tnt_prox",   60 },	// kSfxProxDet
	{ "rain2",      80 },	// kSfxRain
	{ "tnt_det",    80 },	// kSfxRemoteDet
	{ "dmspawn",	80 },	// kSfxRespawn
	{ "sewage",     80 },	// kSfxSewage
	{ "dmshotck",	80 },	// kSfxShotCock1
	{ "dmshotgn",   180 },	// kSfxShotFire1
	{ "slabmove",   80 },	// kSfxSlabMove
	{ "spearair",   80 },	// kSfxSpearAir
	{ "spearfir",   80 },	// kSfxSpearFire
	{ "spear_uw",   80 },	// kSfxSpearFireUW
	{ "spearchm",   80 },	// kSfxSpearLoad
	{ "spraycan",   80 },	// kSfxSprayCan
	{ "sprayflm",   80 },	// kSfxSprayFlame
	{ "water2",     80 },	// kSfxStream
	{ "pl_swim1",   80 },	// kSfxSwim
	{ "pl_uw_sw",   80 },	// kSfxSwimUW
	{ "dooropen",   80 },	// kSfxSwingOpen
	{ "doorclos",   80 },	// kSfxSwingShut
	{ "switch",     40 },	// kSfxSwitch1
	{ "switch5",    40 },	// kSfxSwitch2
	{ "bdy_burn",	50 },	// kSfxThingBurn
	{ "bdy_fall",	80 },	// kSfxThingFall
	{ "bdy_splg",	50 },	// kSfxThingSplat
	{ "thunder",    80 },	// kSfxThunder
	{ "thunder2",   80 },	// kSfxThunder2
	{ "grandck3",   80 },	// kSfxTickTock
	{ "dmpistol",   200 },	// kSfxTomFire1
	{ "tnt_toss",   40 },	// kSfxToss
	{ "undrwatr",   80 },	// kSfxUnderwater
	{ "waterlap",   80 },	// kSfxWaterLap
	{ "dmwpnup",	80 },	// kSfxWeaponUp
	{ "zipclose",   80 },	// kSfxZipClose
	{ "ziplight",   80 },	// kSfxZipLight
	{ "zipopen",    80 },	// kSfxZipOpen
	{ "mgdie",		160 },	// kSfxMGDie
};

struct SAMPLE3D
{
	int hVoice;
	BYTE *pRaw;
};

struct VISBY
{
	SAMPLE3D left, right;
	int id;
	int x, y, z;
};

VISBY Visby[kMaxXSprites];


struct BONKLE
{
	SAMPLE3D left, right;
	int x, y, z;
};

#define kMaxBonkles	64
BONKLE Bonkle[kMaxBonkles];


void sfxInit( void )
{
	for (int i = 0; i < kMaxSoundEffect; i++)
	{
		if ( (soundEffect[i].hResource = gSysRes.Lookup(soundEffect[i].name, ".RAW")) == NULL )
		{
			dprintf("Missing sound resource: %s.RAW\n", soundEffect[i].name);
		}
	}
}


void sfxTerm( void )
{
}


void sfxKillAllSounds( void )
{
}


/*******************************************************************************

Here is the equation for determining the frequency shift of a sound:

	            Ú        ¿
	            ³ v + vL ³
	    fL = fS ³ ÄÄÄÄÄÄ ³
	            ³ v + vS ³
	            À        Ù

Where:

	fL	= frequency as heard by the listener
	fS	= frequency of the source
	v	= velocity of sound
	vL	= velocity of listener
	vS	= velocity of source

Velocities are measured on a vector from the listener to the source.

Speed of sound at STP = 343 m/s = 1126 ft/s.

*******************************************************************************/

/*
#define kSoundVelocity	((double)kSampleRate / kTimerRate)
int Freq( int velocity )
{
	return kSampleRate * kSoundVelocity / (ClipLow(kSoundVelocity - velocity, 1));
}
*/


inline int FreqShift( int nPitch, int vL )
{
	return muldiv(nPitch, kSoundVelFrame + vL, kSoundVelFrame);
}


static int Vol3d(int nAngle, int vol)
{
	return vol - mulscale16(vol, kBackFilter / 2 - mulscale30(kBackFilter / 2, Cos(nAngle)));
}


void Calc3DValues( int x, int y, int z, int relVol )
{
	int dx, dy, dz, dist, nAngle;

	dx = x - gMe->sprite->x;
	dy = y - gMe->sprite->y;
	dz = z - gMe->sprite->z;
	nAngle = getangle(dx, dy);
	dist = ClipLow(Dist3d(dx, dy, dz), 32);
	int monoVol = muldiv(relVol, kVolScale, dist);

	// normal vector for listener -> source
	dx = Cos(nAngle);
	dy = Sin(nAngle);

	int lDist = qdist(x - earL.x, y - earL.y);
	lVol = Vol3d(nAngle - (gMe->sprite->ang - gPinnaAngle), monoVol);
	lPhase = mulscale16r(lDist, kDistPhase);
	lVel = dmulscale30r(dx, earVL.dx, dy, earVL.dy);

	int rDist = qdist(x - earR.x, y - earR.y);
	rVol = Vol3d(nAngle - (gMe->sprite->ang + gPinnaAngle), monoVol);
	rPhase = mulscale16r(rDist, kDistPhase);
	rVel = dmulscale30r(dx, earVR.dx, dy, earVR.dy);
}

/*
void UpdateSound( void )
{
	if (lVoice > FX_Ok)
	{
		FX_SetPan(lVoice, lVol, lVol, 0);
		FX_SetFrequency(lVoice, Freq(lVel));
	}

	if (rVoice > FX_Ok)
	{
		FX_SetPan(rVoice, rVol, 0, rVol);
		FX_SetFrequency(rVoice, Freq(rVel));
	}
}
*/


void sfxStart3DSound( int nXSprite, int soundId, int nPitchOffset )
{
	if ( gFXDevice == -1 )
		return;

	if ( soundId < 0 || soundId >= kMaxSoundEffect )
		ThrowError("Invalid sound ID", ES_ERROR);

	SOUNDEFFECT *pSoundEffect = &soundEffect[soundId];

	dassert(nXSprite > 0 && nXSprite < kMaxXSprites);
	VISBY *pVisby = &Visby[nXSprite];

	// don't restart an actor sound if its already playing
	if ( (pVisby->left.hVoice > 0 | pVisby->right.hVoice > 0 ) && pVisby->id == soundId )
		return;

	pVisby->id = soundId;

	// if there is an active voice, kill the sound
	if ( pVisby->left.hVoice > 0 )
	{
		FX_StopSound(pVisby->left.hVoice);
		Resource::Free(pVisby->left.pRaw);
		pVisby->left.pRaw = NULL;
	}

	if ( pVisby->right.hVoice > 0 )
	{
		FX_StopSound(pVisby->right.hVoice);
		Resource::Free(pVisby->right.pRaw);
		pVisby->right.pRaw = NULL;
	}

	SPRITE *pSprite = &sprite[xsprite[nXSprite].reference];
	Calc3DValues(pSprite->x, pSprite->y, pSprite->z, pSoundEffect->relVol);

	RESHANDLE hResource = pSoundEffect->hResource;
	if ( hResource == NULL )
	{
		dprintf("sfxStart3DSound() called for missing sound #%d\n", soundId);
		return;
	}

	int sampleSize = gSysRes.Size(hResource);
	char *pRaw = (char *)gSysRes.Lock(hResource);

	int nPitch = kSampleRate;
	if ( nPitchOffset )
		nPitch += mulscale16(BiRandom(nPitchOffset), kSampleRate);

	pVisby->left.pRaw = (BYTE *)Resource::Alloc(sampleSize + lPhase);
	memset(pVisby->left.pRaw, 128, lPhase);
	memcpy(&pVisby->left.pRaw[lPhase], pRaw, sampleSize);

	pVisby->right.pRaw = (BYTE *)Resource::Alloc(sampleSize + rPhase);
	memset(pVisby->right.pRaw, 128, rPhase);
	memcpy(&pVisby->right.pRaw[rPhase], pRaw, sampleSize);

	gSysRes.Unlock(hResource);

	int nPriority = 1;	// ASS seems to choke if this is less than 1, e.g., 0
	if ( lVol > nPriority )
		nPriority = lVol;
	if ( rVol > nPriority )
		nPriority = rVol;

	_disable();	// make sure the samples start in the same interval

	pVisby->left.hVoice = FX_PlayRaw(
		pVisby->left.pRaw, sampleSize + lPhase, FreqShift(nPitch, lVel), 0,
		lVol, lVol, 0, nPriority, (ulong)(&pVisby->left.hVoice));

	pVisby->right.hVoice = FX_PlayRaw(
		pVisby->right.pRaw, sampleSize + rPhase, FreqShift(nPitch, rVel), 0,
		rVol, 0, rVol, nPriority, (ulong)(&pVisby->right.hVoice));

	_enable();	// allow them to start
}


void sfxCreate3DSound( int x, int y, int z, int soundId, int nPitchOffset )
{
	if ( gFXDevice == -1 )
		return;

	if ( soundId < 0 || soundId >= kMaxSoundEffect )
		ThrowError("Invalid sound ID", ES_ERROR);

	SOUNDEFFECT *pSoundEffect = &soundEffect[soundId];

	Calc3DValues(x, y, z, pSoundEffect->relVol);

	RESHANDLE hResource = pSoundEffect->hResource;
	if ( hResource == NULL )
	{
		dprintf("sfxCreate3DSound() called for missing sound #%d\n", soundId);
		return;
	}


	int sampleSize = gSysRes.Size(hResource);
	char *pRaw = (char *)gSysRes.Lock(hResource);

	int nPitch = kSampleRate;
	if ( nPitchOffset )
		nPitch += mulscale16(BiRandom(nPitchOffset), kSampleRate);

	for (int i = 0; i < kMaxBonkles; i++)
		if ( Bonkle[i].left.pRaw == NULL && Bonkle[i].right.pRaw == NULL )
			break;
	dassert(i < kMaxBonkles);
	BONKLE *pBonkle = &Bonkle[i];

	pBonkle->left.pRaw = (BYTE *)Resource::Alloc(sampleSize + lPhase);
	memset(pBonkle->left.pRaw, 128, lPhase);
	memcpy(&pBonkle->left.pRaw[lPhase], pRaw, sampleSize);

	pBonkle->right.pRaw = (BYTE *)Resource::Alloc(sampleSize + rPhase);
	memset(pBonkle->right.pRaw, 128, rPhase);
	memcpy(&pBonkle->right.pRaw[rPhase], pRaw, sampleSize);

	gSysRes.Unlock(hResource);

	int nPriority = 1;	// ASS seems to choke if this is less than 1, e.g., 0
	if ( lVol > nPriority )
		nPriority = lVol;
	if ( rVol > nPriority )
		nPriority = rVol;

	_disable();	// make sure the samples start in the same interval

	pBonkle->left.hVoice = FX_PlayRaw(
		pBonkle->left.pRaw, sampleSize + lPhase, FreqShift(nPitch, lVel), 0,
		lVol, lVol, 0, nPriority, (ulong)(&pBonkle->left.hVoice));

	pBonkle->right.hVoice = FX_PlayRaw(
		pBonkle->right.pRaw, sampleSize + rPhase, FreqShift(nPitch, rVel), 0,
		rVol, 0, rVol, nPriority, (ulong)(&pBonkle->right.hVoice));

	_enable();	// allow them to start
}


void sfxKill3DSound( int nXSprite )
{
	if ( gFXDevice == -1 )
		return;

	dassert(nXSprite > 0 && nXSprite < kMaxXSprites);
	VISBY *pVisby = &Visby[nXSprite];

	// if there is an active voice, kill the sound
	if ( pVisby->left.hVoice > 0 )
		FX_StopSound(pVisby->left.hVoice);
	if ( pVisby->right.hVoice > 0 )
		FX_StopSound(pVisby->right.hVoice);
}

/*
void sfxKillAll3DSounds( void )
{
}
*/

// ear distance (in samples)
#define kEarLX (int)(-kIntraAuralTime / 2 * kSampleRate)
#define kEarRX (int)(+kIntraAuralTime / 2 * kSampleRate)

void sfxUpdate3DSounds( void )
{
	// update listener ear positions
	int earDX, earDY;

	earDX = mulscale30(Cos(gMe->sprite->ang + kAngle90), kIntraAuralDist);
	earDY = mulscale30(Sin(gMe->sprite->ang + kAngle90), kIntraAuralDist);

	earL0 = earL;
	earL.x = gMe->sprite->x - earDX;
	earL.y = gMe->sprite->y - earDY;
	earR0 = earR;
	earR.x = gMe->sprite->x + earDX;
	earR.y = gMe->sprite->y + earDY;

	// update ear velocities
	earVL.dx = earL.x - earL0.x;
	earVL.dy = earL.y - earL0.y;

	earVR.dx = earR.x - earR0.x;
	earVR.dy = earR.y - earR0.y;

	int i;

	for (i = 0; i < kMaxXSprites; i++)
	{
		if ( Visby[i].left.pRaw != NULL && Visby[i].left.hVoice <= 0 )
		{
			Resource::Free(Visby[i].left.pRaw);
			Visby[i].left.pRaw = NULL;
		}
		if ( Visby[i].right.pRaw != NULL && Visby[i].right.hVoice <= 0 )
		{
			Resource::Free(Visby[i].right.pRaw);
			Visby[i].right.pRaw = NULL;
		}
	}

	for (i = 0; i < kMaxBonkles; i++)
	{
		if (Bonkle[i].left.hVoice <= 0 && Bonkle[i].left.pRaw != NULL )
		{
			Resource::Free(Bonkle[i].left.pRaw);
			Bonkle[i].left.pRaw = NULL;
		}
		if (Bonkle[i].right.hVoice <= 0 && Bonkle[i].right.pRaw != NULL )
		{
			Resource::Free(Bonkle[i].right.pRaw);
			Bonkle[i].right.pRaw = NULL;
		}
	}
}
