/*
* This code is released under the GNU General Public License.  See COPYING for 
* details.  Copyright 2003 John Spray: spray_john@users.sourceforge.net
*/

//SoundCore.c - The class that deals with audio.  Note that one should already
//have done an SDL_Init(SDL_AUDIO) before calling SoundCore::Init

#include <stdlib.h>
#include <stdio.h>
#include <math.h>
#include <AL/al.h>
#include <AL/alut.h>
#include <string.h>
#include <physfs.h>
#include <string>

#include "SoundCore.h"
#include "Game.h"
#include "Config.h"

extern Config GLOB_conf;

//SoundCore::SoundCore - Simple Initialisation
SoundCore::SoundCore()
{
	verbose=0;
	paused=0;
	reference_distance=50.0f;
	volume=GLOB_conf.volume;
	verbose=GLOB_conf.verbose;

	//alutInit(0,NULL);
	//fprintf(stderr,"SoundCore::SoundCore: OpenAL initialised\n");fflush(stderr);
	alListenerf(AL_GAIN,volume);


	alDopplerFactor(0.0f);
	ALenum error;
	if((error=alGetError())!=AL_NO_ERROR){
		fprintf(stderr,"SoundCore::SoundCore: Error on alDopplerFactor\n");
		PrintALError(error);
	}

}

SoundCore::~SoundCore()
{
	LListItem<SampleType> *sampleitem;
	LListItem<EffectType> *effectitem;
	ALenum error;

	alGetError();

	for(effectitem=effectlist.head;effectitem;effectitem=effectitem->next){
		alSourceStop(effectitem->data.alid);
		alDeleteSources(1,&effectitem->data.alid);
		if((error=alGetError())!=AL_NO_ERROR){
			fprintf(stderr,"SoundCore::~SoundCore: Error on alDeleteSources(1,%d)\n",effectitem->data.alid);
			PrintALError(error);
		}

	}

	for(sampleitem=samplelist.head;sampleitem;sampleitem=sampleitem->next){
		alDeleteBuffers(1,&sampleitem->data.alid);
		if((error=alGetError())!=AL_NO_ERROR){
			fprintf(stderr,"SoundCore::~SoundCore: Error on alDeleteBuffers(1,%d)\n",sampleitem->data.alid);
			PrintALError(error);
		}
	}

	//Looks like one isn't supposed to call alutExit unless 
	//one really does want to Exit
	/*alutExit();
	fprintf(stderr,"SoundCore::~SoundCore: OpenAL deinitialised\n");
	fflush(stderr);*/
	
}


void SoundCore::SetEar(Vector* newpos,Vector* newvel,Vector* newface,Vector* newup)
{

	s=newpos;
	alListener3f(AL_POSITION,s->x,s->y,s->z);

	v=newvel;
	alListener3f(AL_VELOCITY,v->x,v->y,v->z);

	orientation_face=newface;
	orientation_up=newup;
	float orientv[]={orientation_face->x,orientation_face->y,
			orientation_face->z,orientation_up->x,
			orientation_up->y,orientation_up->z};

	alListenerfv(AL_ORIENTATION,orientv);



}

//SoundCore::Update - if an effect has finished playing mark it
void SoundCore::Update()
{
	LListItem<EffectType> *effectitem,*temp;
	ALenum sourcestate;
	float orientv[]={orientation_face->x,orientation_face->y,orientation_face->z,orientation_up->x,orientation_up->y,orientation_up->z};

	alListener3f(AL_VELOCITY,v->x,v->y,v->z);
	alListener3f(AL_POSITION,s->x,s->y,s->z);
	alListenerfv(AL_ORIENTATION,orientv);

	for(effectitem=effectlist.head;effectitem;){
		alGetSourcei(effectitem->data.alid,AL_SOURCE_STATE,&sourcestate);
		if(sourcestate==AL_STOPPED){
			effectitem->data.isplaying=0;
			temp=effectitem->next;
			alDeleteSources(1,&effectitem->data.alid);

			effectlist.Del(effectitem);
			effectitem=temp;
			continue;
		}
		else{
			if(effectitem->data.still){
				alSource3f(effectitem->data.alid,AL_POSITION,
					effectitem->data.s_static.x,
					effectitem->data.s_static.y,
					effectitem->data.s_static.z);
				alSource3f(effectitem->data.alid,AL_VELOCITY,
					effectitem->data.v_static.x,
					effectitem->data.v_static.y,
					effectitem->data.v_static.z);
			}
			else{
				alSource3f(effectitem->data.alid,AL_POSITION,
					effectitem->data.s->x,
					effectitem->data.s->y,
					effectitem->data.s->z);
				alSource3f(effectitem->data.alid,AL_VELOCITY,
					effectitem->data.v->x,
					effectitem->data.v->y,
					effectitem->data.v->z);
			}
		}
		effectitem=effectitem->next;
	}
}

void SoundCore::SetVolume(float target_volume)
{
	// 0.001 coping with 0.0 apparently not being equal to -0.0
	if(target_volume>=-0.001f&&target_volume<=1.001f)
		volume=target_volume;
	else
		printf("SoundCore::SetVolume: Bad volume setting: %f\n",target_volume);

	alListenerf(AL_GAIN,volume);
	GLOB_conf.volume=volume;
}

float SoundCore::GetVolume()
{
	return volume;
}

//SoundCore::Play - choose an available effect, select wave and position data, and
//set it playing.
int SoundCore::Play(char* filename,int loopmode,Vector* sourcepos,Vector* sourcev)
{

	SampleType *target_sample=NULL;
	EffectType neweffect;
	LListItem<SampleType> *sampleitem;

	if(strlen(filename)>127||!filename){
		printf("SoundCore::Play: malformed filename\n");
		return -1;
	}

	for(sampleitem=samplelist.head;sampleitem;sampleitem=sampleitem->next){
		if(!strcmp(filename,sampleitem->data.filename)){
			target_sample=&sampleitem->data;
			break;
		}
	}

	if(target_sample==NULL){
		printf("SoundCore::Play: file named %s is not loaded!\n",filename);
		return -1;
	}

	if(verbose) printf("SoundCore::Play: playing %s\n",filename);
	

	neweffect.s=sourcepos;
	neweffect.v=sourcev;
	neweffect.still=0;
	alGenSources(1,&neweffect.alid);

	alSourcei(neweffect.alid,AL_BUFFER,target_sample->alid);

	if(loopmode==-1){
		alSourcei(neweffect.alid,AL_LOOPING,AL_TRUE);
	}
	else{
		alSourcei(neweffect.alid,AL_LOOPING,AL_FALSE);
	}
	alSourcePlay(neweffect.alid);

	alSourcef(neweffect.alid,AL_REFERENCE_DISTANCE,reference_distance);

	neweffect.isplaying=1;

	effectlist.Add(&neweffect);

	return neweffect.alid;
}

int SoundCore::Play(char* filename,int loopmode,Vector sourcepos,Vector sourcev)
{
	SampleType *target_sample=NULL;
	EffectType neweffect;
	LListItem<SampleType> *sampleitem;

	if(strlen(filename)>127||!filename){
		printf("SoundCore::Play: malformed filename\n");
		return -1;
	}

	for(sampleitem=samplelist.head;sampleitem;sampleitem=sampleitem->next){
		if(!strcmp(filename,sampleitem->data.filename)){
			target_sample=&sampleitem->data;
			break;
		}
	}

	if(target_sample==NULL){
		printf("SoundCore::Play: file named %s is not loaded!\n",filename);
		return -1;
	}

	if(verbose) printf("SoundCore::Play: playing %s\n",filename);
	
	neweffect.s_static=sourcepos;
	neweffect.v_static=sourcev;
	neweffect.v=NULL;
	neweffect.s=NULL;
	neweffect.still=1;
	alGenSources(1,&neweffect.alid);
	alSourcei(neweffect.alid,AL_BUFFER,target_sample->alid);
	if(loopmode==-1)
		alSourcei(neweffect.alid,AL_LOOPING,AL_TRUE);
	else
		alSourcei(neweffect.alid,AL_LOOPING,AL_FALSE);
	alSourcePlay(neweffect.alid);

	alSourcef(neweffect.alid,AL_REFERENCE_DISTANCE,reference_distance);

	neweffect.isplaying=1;
	effectlist.Add(&neweffect);
	return neweffect.alid;
}

int SoundCore::Play(char* filename)
{
	return Play(filename,0,s,v);
}

void SoundCore::SetPaused(int target_paused)
{
	LListItem<EffectType> *effectitem;
	ALenum error;

	alGetError();

	if(target_paused==0){
		paused=0;
		//Play all Effects
		for(effectitem=effectlist.head;effectitem;effectitem=effectitem->next){
			alSourcePlay(effectitem->data.alid);
			if((error=alGetError())!=AL_NO_ERROR){
				fprintf(stderr,"SoundCore::LoadSample: Error on alutUnloadWAV\n");
				PrintALError(error);
			}
		}
	}
	else{
		paused=1;
		//Pause all Effects
		for(effectitem=effectlist.head;effectitem;effectitem=effectitem->next){
			alSourcePause(effectitem->data.alid);
		}
	}
}

void SoundCore::HandleMixError()
{
	fprintf(stderr,"HandleMixError got called in the OpenAL soundcore implementation!\n");
	return;
}

int SoundCore::StopEffect(ALint target_id)
{
	LListItem<EffectType> *effectitem;
	ALenum error,sourcestate;
	if(verbose || target_id==-1)
		printf("SoundCore::StopEffect: target_id=%d\n",target_id);
  
	alGetError();

	for(effectitem=effectlist.head;effectitem;effectitem=effectitem->next){
		if((ALint)effectitem->data.alid==target_id){
			alGetSourcei(effectitem->data.alid,AL_SOURCE_STATE,&sourcestate);
			alSourceStop(effectitem->data.alid);
			alDeleteSources(1,&effectitem->data.alid);
			if( (error=alGetError()) != AL_NO_ERROR ){
				fprintf(stderr,"SoundCore::StopEffect: Error on alDeleteSources\n");
				PrintALError(error);
			}
			effectlist.Del(effectitem);
			return 0;
		}
	}

	printf("SoundCore::StopEffect: Invalid effect reference %d\n",target_id);
	return -1;
}

int SoundCore::LoadSample(char* filename)
{
	PHYSFS_file* filehandle;
	void* filedata;
	long filesize=0;
	SampleType newsample;

	filehandle=PHYSFS_openRead(filename);

	if(!filehandle) {printf("SoundCore::LoadSample: PHYSFS_openRead failed on %s with error %s!\n",filename,PHYSFS_getLastError()); return 1;}

	filesize=PHYSFS_fileLength(filehandle);

	filedata=malloc(filesize);
	if(!filedata) {printf("SoundCore::LoadSample: malloc failed!  Out of memory?\n"); return 1;}
	if(PHYSFS_read(filehandle,filedata,1,filesize)!=filesize){
		printf("SoundCore::LoadSample: PHYSFS_read and PHYSFS_fileLength disagree!  Aborting.\n");
		free(filedata);
		PHYSFS_close(filehandle);
		return 1;
	}

	PHYSFS_close(filehandle);

	ALenum format;
	ALvoid *data;
	ALsizei size,freq;
	ALboolean loop=AL_FALSE;
	ALenum error;

	alGetError();

	alGenBuffers(1,&newsample.alid);
	if((error=alGetError())!=AL_NO_ERROR){
		fprintf(stderr,"SoundCore::LoadSample: Error on alGenBuffers\n");
		PrintALError(error);
	}
	alutLoadWAVMemory((ALbyte*)filedata,&format,&data,&size,&freq,&loop);
	if((error=alGetError())!=AL_NO_ERROR){
		fprintf(stderr,"SoundCore::LoadSample: Error on alutLoadWAVMemory\n");
		PrintALError(error);
	}
	alBufferData(newsample.alid,format,data,size,freq);
	if((error=alGetError())!=AL_NO_ERROR){
		fprintf(stderr,"SoundCore::LoadSample: Error on alBufferData\n");
		PrintALError(error);
	}
	alutUnloadWAV(format,data,size,freq);
	if((error=alGetError())!=AL_NO_ERROR){
		fprintf(stderr,"SoundCore::LoadSample: Error on alutUnloadWAV\n");
		PrintALError(error);
	}

	strcpy(newsample.filename,filename);
	samplelist.Add(&newsample);


	free(filedata);

	return 0;
}

void SoundCore::PrintALError(ALenum code)
{
	string description;

	if(code==AL_INVALID_NAME)
		description="Invalid Name";
	else if(code==AL_INVALID_VALUE)
		description="Invalid Value";
	else if(code==AL_ILLEGAL_ENUM)
		description="Illegal Enum";
	else if(code==AL_ILLEGAL_COMMAND)
		description="Illegal COmmand";
	else if(code==AL_OUT_OF_MEMORY)
		description="Out of Memory";
	else if(code==AL_NO_ERROR)
		description="No Error";
	else
		description="Unknown error code";

	fprintf(stderr,"OpenAL error: %d: %s\n",code,description.c_str());
	fflush(stderr);
}
