#include <sound_tool.h>
#include <math.h>
#include "gamemem.h"
#include "vectors.h"
#include "entities.h"
#include "config.h"
#include "gamesound.h"
#include "gameinitexit.h"
#include "carphysics.h"
#include "roads.h"
#include "environment.h"
#include "random.h"
#include "gameframe.h"
#include "sky.h"

typedef struct{
	STSoundRef engine,skid,turbo,noise,hall,wind,horn;
	tFileRef skidSound;
//	int enginePlaying,skidPlaying,turboPlaying,noisePlaying,hallPlaying,windPlaying,hornPlaying;
} tSoundSource;

int gSoundLoopRunnig=false;
STSoundRef gSoundLoop;
int gThunder;

void SoundPause()
{
	ST_SetAppVolume(0);
}

void SoundInit()
{
	if(gConfig->soundEnable)
		ST_Open(16,ST_DEFAULT_SAMPLE_RATE);
	ST_SetAppVolume(gConfig->soundVolume*stMaxVolume);
}

void SoundReInit()
{
	ST_SetAppVolume(gConfig->soundVolume*stMaxVolume);
}


#define kReferenceDistance 7
#define kRollOfFactor 0.6
#define kDopplerFactor 1
#define kDopplerVelocity 331.45

//Callback to Loop Sounds
void SoundLoopCallback(long theMessage, STSoundCookie soundCookie,struct STSoundSpec *soundSpec)
{
	if(theMessage==stSoundFinished)
		ST_PlaySoundParam(soundSpec);
	else if(theMessage==stSoundBumped)
		soundSpec->refCon=(void*)gFrameCount;
}


//calls ST_AdjustSound to setup 3d placement and custom volume/pitch of sound
void SetupSound(STSoundCookie s,tVector3 pos,tVector3 velo,float vol,float pitch,float prio)
{
	if(!s)
		return;
	tVector3 dir=pos-gCameraEntity->pos;
	if(gConfig->carsOnSpeed)
		dir=dir+Vector(30*sin(gFrameCount*0.01),10*sin(gFrameCount*0.1),20*sin(gFrameCount*0.008));
	float distance=~dir;
	float panning=0;
	if(distance)
	{
		dir=dir*1/distance;
		float distanceFactor=kReferenceDistance/(kReferenceDistance+kRollOfFactor*(distance-kReferenceDistance));
		vol*=distanceFactor;
		float listenerSpeed=-dir*gCameraEntity->velo;
		if(gConfig->carsOnSpeed)
			listenerSpeed+=90*sin(gFrameCount*0.001);
		float sourceSpeed=dir*velo;
		pitch=pitch+pitch*kDopplerFactor*((kDopplerVelocity-listenerSpeed)/(kDopplerVelocity+sourceSpeed)-1);
		tMatrix3 m;
		MatrixTranspose(gCameraEntity->dir,m);
		dir=dir*m;
		panning=dir.x;
	}

	STSoundSpec spec;
	ST_GetSoundSpec(s,&spec);
	prio*=vol;
	if(prio>1)prio=1;
	spec.priority=prio*0xff;
	spec.leftVol=0.1*ST_MAX_VOLUME*vol*(1+panning);//*gConfig->soundVolume;
	spec.rightVol=0.1*ST_MAX_VOLUME*vol*(1-panning);//*gConfig->soundVolume;
	spec.pitchAdjustment=ST_NORMAL_PITCH/pitch;
	ST_AdjustSound(s,&spec);
}

void LoadSound(tFileRef f)
{
	void *data=FileGetDataPtr(f);
	void **soundData=IndirectInitialize(data,MemoryBlockSize(data));
	gFileTable[f].parsedData=MemoryAllocateBlock(sizeof(STSoundRef));
	ST_LoadIndirect(soundData,(STSoundRef*)gFileTable[f].parsedData);
	IndirectDeallocate(soundData);
	gFileTable[f].parsed=true;
}

void LoadAllSounds()
{
	for(int i=0;i<gFileTableSize;i++)
		if(char *extension=FileGetExtension(i))
//			if(!_stricmp(extension,kFileTypeWAV))
			if(!_stricmp(extension,".ima"))
				LoadSound(i);
}

//start playing a sound. if loop is set, use SoundLoopCallback() to loop sound.
STSoundCookie PlaySound(tFileRef f,int loop,int silentStart)
{
	if(gConfig->soundEnable&&!gBackgroundReplay)
	{
		if(!gFileTable[f].parsed)
			LoadSound(f);
		STSoundSpec spec;
		
		spec.sndCookie=NULL;
		spec.theSound=*((STSoundRef*)gFileTable[f].parsedData);
		spec.pitchAdjustment=ST_NORMAL_PITCH;
		spec.STCallBack=loop?SoundLoopCallback:NULL;
		if(silentStart)
		{
			spec.priority=0x00;
			spec.leftVol=0;//*gConfig->soundVolume;
			spec.rightVol=0;//*gConfig->soundVolume;
		}
		else
		{
			spec.priority=0x7f;
			spec.leftVol=ST_MAX_VOLUME;//*gConfig->soundVolume;
			spec.rightVol=ST_MAX_VOLUME;//*gConfig->soundVolume;
		}
		spec.soundSampleSize=16;
		spec.refCon=(void*)-1L;

		return ST_PlaySoundParam(&spec);
	}
	else
		return NULL;
}

#define kMinSkidGainSlideVelo 0.9
#define kMaxSkidGainSlideVelo 4.0
#define kMinSkidPitchSlideVelo 1.2
#define kMaxSkidPitchSlideVelo 15.0
#define kMaxSoundDistance 120
#define kEchoFadeTime 0.3
#include "text.h"
void CarSoundEntity(int i)
{
	if(gBackgroundReplay)
		return;
		
	tGameEntity *carEntity=gCarEntities[i];
	tCarPhysics *phys=(tCarPhysics*)carEntity->physics;
	tCarDefinition *car=&(phys->car);
	
	//initialise Sound info, if it doesn't exist.
	if(!carEntity->soundSource)
		carEntity->soundSource=MemoryAllocateZeroedBlock(sizeof(tSoundSource));
	
	//if car is far enough away from camera, silence all sound
	if(sqr(carEntity->pos-gCameraEntity->pos)>sqr(kMaxSoundDistance))
	{
		if(((tSoundSource*)carEntity->soundSource)->skid)
		{
			ST_HaltSound(((tSoundSource*)carEntity->soundSource)->skid);
			((tSoundSource*)carEntity->soundSource)->skid=NULL;
		}
		if(((tSoundSource*)carEntity->soundSource)->engine)
		{
			ST_HaltSound(((tSoundSource*)carEntity->soundSource)->engine);
			((tSoundSource*)carEntity->soundSource)->engine=NULL;
		}
		if(((tSoundSource*)carEntity->soundSource)->hall)
		{
			ST_HaltSound(((tSoundSource*)carEntity->soundSource)->hall);
			((tSoundSource*)carEntity->soundSource)->hall=NULL;
		}
		if(((tSoundSource*)carEntity->soundSource)->wind)
		{
			ST_HaltSound(((tSoundSource*)carEntity->soundSource)->wind);
			((tSoundSource*)carEntity->soundSource)->wind=NULL;
		}
		if(((tSoundSource*)carEntity->soundSource)->turbo)
		{
			ST_HaltSound(((tSoundSource*)carEntity->soundSource)->turbo);
			((tSoundSource*)carEntity->soundSource)->turbo=NULL;
		}
		if(((tSoundSource*)carEntity->soundSource)->horn)
		{
			ST_HaltSound(((tSoundSource*)carEntity->soundSource)->horn);
			((tSoundSource*)carEntity->soundSource)->horn=NULL;
		}
		return;
	}
	
	//officially stop bumped sounds
	STSoundSpec sp;
	if(((tSoundSource*)carEntity->soundSource)->skid)
	{
		ST_GetSoundSpec(((tSoundSource*)carEntity->soundSource)->skid,&sp);
		if(sp.refCon!=(void*)-1)
			if(sp.refCon<(void*)(gFrameCount-5*(int)kFPS))
				((tSoundSource*)carEntity->soundSource)->skid=NULL;
	}
	if(((tSoundSource*)carEntity->soundSource)->engine)
	{
		ST_GetSoundSpec(((tSoundSource*)carEntity->soundSource)->engine,&sp);
		if(sp.refCon!=(void*)-1)
			if(sp.refCon<(void*)(gFrameCount-5*(int)kFPS))
				((tSoundSource*)carEntity->soundSource)->engine=NULL;
	}
	if(((tSoundSource*)carEntity->soundSource)->hall)
	{
		ST_GetSoundSpec(((tSoundSource*)carEntity->soundSource)->hall,&sp);
		if(sp.refCon!=(void*)-1)
			if(sp.refCon<(void*)(gFrameCount-5*(int)kFPS))
				((tSoundSource*)carEntity->soundSource)->hall=NULL;
	}
	if(((tSoundSource*)carEntity->soundSource)->wind)
	{
		ST_GetSoundSpec(((tSoundSource*)carEntity->soundSource)->wind,&sp);
		if(sp.refCon!=(void*)-1)
			if(sp.refCon<(void*)(gFrameCount-5*(int)kFPS))
				((tSoundSource*)carEntity->soundSource)->wind=NULL;
	}
	if(((tSoundSource*)carEntity->soundSource)->turbo)
	{
		ST_GetSoundSpec(((tSoundSource*)carEntity->soundSource)->turbo,&sp);
		if(sp.refCon!=(void*)-1)
			if(sp.refCon<(void*)(gFrameCount-5*(int)kFPS))
				((tSoundSource*)carEntity->soundSource)->turbo=NULL;
	}
	if(((tSoundSource*)carEntity->soundSource)->horn)
	{
		ST_GetSoundSpec(((tSoundSource*)carEntity->soundSource)->horn,&sp);
		if(sp.refCon!=(void*)-1)
			if(sp.refCon<(void*)(gFrameCount-5*(int)kFPS))
				((tSoundSource*)carEntity->soundSource)->horn=NULL;
	}

	//engine sounds:
	
	//is the car in a tunnel?
	int echo=gSurfaceTypes->types[phys->wheels[0].surfaceType].soundEcho;
	
	//fade echo status
	if(phys->echo<echo)
	{
		phys->echo+=kFrameTime*(1/kEchoFadeTime);
		if(phys->echo>echo)
			phys->echo=echo;
	}	
	if(phys->echo>echo)
	{
		phys->echo-=kFrameTime*(1/kEchoFadeTime);
		if(phys->echo<echo)
			phys->echo=echo;
	}

	//if engine sound isn't running and echo<1, start engine sound
	if(!((tSoundSource*)carEntity->soundSource)->engine&&phys->echo<1.0)
		((tSoundSource*)carEntity->soundSource)->engine=PlaySound(car->engineSample,true,true);

	//if halled engine sound isn't running and echo<1, start engine sound
	if(!((tSoundSource*)carEntity->soundSource)->hall&&phys->echo>0.0)
		((tSoundSource*)carEntity->soundSource)->hall=PlaySound(car->hallSample,true,true);

	if(((tSoundSource*)carEntity->soundSource)->engine&&phys->echo>=1.0)
	{
		ST_HaltSound(((tSoundSource*)carEntity->soundSource)->engine);
		((tSoundSource*)carEntity->soundSource)->engine=NULL;
	}
	
	if(((tSoundSource*)carEntity->soundSource)->hall&&phys->echo<=0.0)
	{
		ST_HaltSound(((tSoundSource*)carEntity->soundSource)->hall);
		((tSoundSource*)carEntity->soundSource)->hall=false;
	}

	//Setup sound pitch/volume
	float rpm=(phys->rpm-car->jerkRPM)/(car->maxRPM-car->jerkRPM);
	float rpmGain=(car->zeroRPMSoundGain+rpm*(car->fullRPMSoundGain-car->zeroRPMSoundGain));
	float throttleGain=(car->zeroThrottleSoundGain+phys->throttle*(car->fullThrottleSoundGain-car->zeroThrottleSoundGain));

	float rpmPitch=(car->zeroRPMSoundPitch+rpm*(car->fullRPMSoundPitch-car->zeroRPMSoundPitch));
	float throttlePitch=(car->zeroThrottleSoundPitch+phys->throttle*(car->fullThrottleSoundPitch-car->zeroThrottleSoundPitch));

	if(((tSoundSource*)carEntity->soundSource)->engine)
		SetupSound(((tSoundSource*)carEntity->soundSource)->engine,carEntity->pos,carEntity->velo,1.5*rpmGain*throttleGain*(1.0-phys->echo),rpmPitch*throttlePitch,1);
	if(((tSoundSource*)carEntity->soundSource)->hall)
		SetupSound(((tSoundSource*)carEntity->soundSource)->hall,carEntity->pos,carEntity->velo,1.5*rpmGain*throttleGain*(phys->echo),rpmPitch*throttlePitch,1);

	//skid
	float slideVelo=0;
	for(int i=0;i<car->numWheels;i++)
		slideVelo+=gSurfaceTypes->types[phys->wheels[i].surfaceType].soundEnable?phys->wheels[i].slipVelo:0;
	slideVelo/=car->numWheels;
	float skidGain=(slideVelo-kMinSkidGainSlideVelo)/(kMaxSkidGainSlideVelo-kMinSkidGainSlideVelo);
	
	if(!gSurfaceTypes->types[phys->wheels[0].surfaceType].soundEnable)
		skidGain=0;
	if(((tSoundSource*)carEntity->soundSource)->skid&&((tSoundSource*)carEntity->soundSource)->skidSound!=gSurfaceTypes->types[phys->wheels[0].surfaceType].skidSound)
		skidGain=0;
	if(skidGain<=0){
		if(((tSoundSource*)carEntity->soundSource)->skid)
		{
			ST_HaltSound(((tSoundSource*)carEntity->soundSource)->skid);
			((tSoundSource*)carEntity->soundSource)->skid=NULL;
		}
	}
	else
		if(!((tSoundSource*)carEntity->soundSource)->skid)
		{
			((tSoundSource*)carEntity->soundSource)->skid=PlaySound(gSurfaceTypes->types[phys->wheels[0].surfaceType].skidSound,true,true);
			((tSoundSource*)carEntity->soundSource)->skidSound=gSurfaceTypes->types[phys->wheels[0].surfaceType].skidSound;
		}
				
	if(((tSoundSource*)carEntity->soundSource)->skid)
	{
		if(skidGain<0)skidGain=0;
		if(skidGain>1)skidGain=1;
		
		float skidPitch=(slideVelo-kMinSkidPitchSlideVelo)/(kMaxSkidPitchSlideVelo-kMinSkidPitchSlideVelo);
		if(skidPitch<0)skidPitch=0;
		if(skidPitch>1)skidPitch=1;

		SetupSound(((tSoundSource*)carEntity->soundSource)->skid,carEntity->pos,carEntity->velo,skidGain*2,(0.85+0.4*skidPitch),1);
	}
	
	//wind sounds (player Car only)
	if(carEntity!=gViewedEntity)
	{
		if(((tSoundSource*)carEntity->soundSource)->wind)
		{
			ST_HaltSound(((tSoundSource*)carEntity->soundSource)->wind);
			((tSoundSource*)carEntity->soundSource)->wind=NULL;
		}
	}
	else
	{
		if(!((tSoundSource*)carEntity->soundSource)->wind)
			((tSoundSource*)carEntity->soundSource)->wind=PlaySound(FileGetReference("wind.wav"),true,true);
		float velo=~carEntity->velo;
		float windGain=velo/60.0;
		float windPitch=velo/100;
		if(windGain>1)windGain=1;
		windGain*=1-CalcDraftFactor(carEntity);
		if(windPitch>1)windPitch=1;
		SetupSound(((tSoundSource*)carEntity->soundSource)->wind,carEntity->pos,carEntity->velo,windGain*2.5,1.0+0.6*windPitch,1);
	}

	//turbo sounds
	if(car->turboGain)
	{
		if(!((tSoundSource*)carEntity->soundSource)->turbo)
			((tSoundSource*)carEntity->soundSource)->turbo=PlaySound(car->turboSample,true,true);

		float turboAccel=(phys->rpm/car->maxRPM)*(0.3*phys->throttle+0.7)-0.1;
		phys->turboRPM+=turboAccel*kFrameTime*3;
		if(phys->turboRPM<0)phys->turboRPM=0;
		float maxTurbo=(phys->rpm/car->maxRPM)*(0.5*phys->throttle+0.5);
		if(phys->turboRPM>maxTurbo)phys->turboRPM-=(turboAccel>0?turboAccel*kFrameTime*3:0)+0.5*kFrameTime;
		
		float turboGain=phys->turboRPM;
		float turboPitch=phys->turboRPM;
		if(turboGain>1)turboGain=1;
		if(turboPitch>1)turboPitch=1;
		SetupSound(((tSoundSource*)carEntity->soundSource)->turbo,carEntity->pos,carEntity->velo,turboGain*car->turboGain*1.5,0.3+0.3*turboPitch,1);
	}
	
	//horn
	if(!(phys->lightFlags&kLightFlagHorn)){
		if(((tSoundSource*)carEntity->soundSource)->horn)
		{
			ST_HaltSound(((tSoundSource*)carEntity->soundSource)->horn);
			((tSoundSource*)carEntity->soundSource)->horn=NULL;
		}
	}
	else
		if(!((tSoundSource*)carEntity->soundSource)->horn)
		{
			tFileRef hornRef=car->hornSample;
			if(!hornRef)
				hornRef=FileGetReference("horn.wav");
			((tSoundSource*)carEntity->soundSource)->horn=PlaySound(hornRef,true,true);
		}

	if(((tSoundSource*)carEntity->soundSource)->horn)
		SetupSound(((tSoundSource*)carEntity->soundSource)->horn,carEntity->pos,carEntity->velo,2,car->hornPitch+1,10);
}


void SoundFrame()
{
	if(gConfig->soundEnable)
	{
		//for each car process car's sound
		for(int i=0;i<gGameInfo->numPlayers;i++)
			CarSoundEntity(i);
		
		//environmental sounds (rain, thunder)
		if(gEnvironment->soundLoopEnable)
		{
			if(!gSoundLoopRunnig)
			{
				gSoundLoopRunnig=true;
				gSoundLoop=PlaySound(gEnvironment->soundLoop,true,false);		
			}
			SetupSound(gSoundLoop,gCameraEntity->pos,gCameraEntity->velo,0.5+0.2*sin(gFrameCount*kFrameTime*0.5),1,10);
		}
		if(gEnvironment->soundRandomEnable)
		{
			if(gLightning)
				gThunder=gFrameCount;
			if(gFrameCount==gThunder+4*kFPS)
				SetupSound(PlaySound(FileGetIndexedReference(gEnvironment->soundRandom,RandomInt(0,3)),false,false),gCameraEntity->pos,gCameraEntity->velo,RandomFl(0.5,1.5),1,1);
		}
	}
}

//silence all sounds
void SoundSilence()
{
	if(gConfig->soundEnable&&!gBackgroundReplay)
		ST_HaltSound(nil);
	gSoundLoopRunnig=false;
}

void SoundFreeEntitySources(tGameEntity *entity)
{
}

//plays a sound from a car's location
void CarPlayCrashNoise(tGameEntity *carEntity,tFileRef sound,float volume)
{
	if(gConfig->soundEnable&&!gBackgroundReplay&&carEntity->soundSource)
	{
		int isSoundPlaying=true;
		if(!((tSoundSource*)carEntity->soundSource)->noise)
			isSoundPlaying=false;
		else if(!ST_IsSoundPlaying(((tSoundSource*)carEntity->soundSource)->noise))
			isSoundPlaying=false;
		if(!isSoundPlaying)
		{
			((tSoundSource*)carEntity->soundSource)->noise=PlaySound(sound,false,false);
			SetupSound(((tSoundSource*)carEntity->soundSource)->noise,carEntity->pos,carEntity->velo,volume,1,2);
		}
	}
}

//simple sound playback for user interface.
void PlayInterfaceSound(tFileRef sound)
{
	if(gConfig->interfaceSounds)
		PlaySound(sound,false,false);
}

