/*
   ===========================================================================
   Copyright (C) 1999-2005 Id Software, Inc.
   Copyright (C) 2005 Stuart Dalton (badcdev@gmail.com)

   This file is part of Quake III Arena source code.

   Quake III Arena source code is free software; you can redistribute it
   and/or modify it under the terms of the GNU General Public License as
   published by the Free Software Foundation; either version 2 of the License,
   or (at your option) any later version.

   Quake III Arena source code is distributed in the hope that it will be
   useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with Foobar; if not, write to the Free Software
   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
   ===========================================================================
 */

#include "snd_local.h"

#ifdef __MACOSX__
  #define MAX_SRC 64
#else
  #define MAX_SRC 128
#endif
static src_t srclist[MAX_SRC];
static int src_count = 0;
static qboolean src_inited = qfalse;

typedef struct sentity_s
{
	src_t *src;
	int touched;    // Sound present this update?
} sentity_t;
static sentity_t entlist[MAX_EDICTS];

/*
 * Local helper functions
 */

static void source_setup( src_t *src, sfx_t *sfx, int priority, int entNum, int channel, float fvol, float attenuation )
{
	ALuint buffer;

	// Mark the SFX as used, and grab the raw AL buffer
	S_UseBuffer( sfx );
	buffer = S_GetALBuffer( sfx );

	src->lastUse = trap_Milliseconds();
	src->sfx = sfx;
	src->priority = priority;
	src->entNum = entNum;
	src->channel = channel;
	src->fvol = fvol;
	src->isActive = qtrue;
	src->isLocked = qfalse;
	src->isLooping = qfalse;
	src->isTracking = qfalse;

	qalSourcefv( src->source, AL_POSITION, vec3_origin );
	qalSourcefv( src->source, AL_VELOCITY, vec3_origin );
	qalSourcef( src->source, AL_GAIN, fvol * s_volume->value );
	qalSourcei( src->source, AL_SOURCE_RELATIVE, AL_FALSE );
	qalSourcei( src->source, AL_LOOPING, AL_FALSE );
	qalSourcei( src->source, AL_BUFFER, buffer );

	qalSourcef( src->source, AL_REFERENCE_DISTANCE, s_attenuation_refdistance->value );
	qalSourcef( src->source, AL_MAX_DISTANCE, s_attenuation_maxdistance->value );
	qalSourcef( src->source, AL_ROLLOFF_FACTOR, attenuation );
}

static void source_kill( src_t *src )
{
	if( src->isLocked )
		return;

	if( src->isActive )
		qalSourceStop( src->source );

	qalSourcei( src->source, AL_BUFFER, AL_NONE );

	src->sfx = 0;
	src->lastUse = 0;
	src->priority = 0;
	src->entNum = -1;
	src->channel = -1;
	src->fvol = 1;
	src->isActive = qfalse;
	src->isLocked = qfalse;
	src->isLooping = qfalse;
	src->isTracking = qfalse;
}

static void source_loop( int priority, sfx_t *sfx, int entNum, float fvol, float attenuation )
{
	src_t *src;
	qboolean need_to_play = qfalse;
	vec3_t origin, velocity;

	if( !sfx )
		return;

	// Do we need to start a new sound playing?
	if( !entlist[entNum].src )
	{
		src = S_AllocSource( priority, entNum, 0 );
		if( !src )
			return;
		need_to_play = qtrue;
	}
	else if( entlist[entNum].src->sfx != sfx )
	{
		// Need to restart. Just re-use this channel
		src = entlist[entNum].src;
		source_kill( src );
		need_to_play = qtrue;
	}
	else
	{
		src = entlist[entNum].src;
	}

	if( need_to_play )
	{
		source_setup( src, sfx, priority, entNum, -1, fvol, attenuation );
		qalSourcei( src->source, AL_LOOPING, AL_TRUE );
		src->isLooping = qtrue;

		entlist[entNum].src = src;
	}

	qalSourcef( src->source, AL_GAIN, fvol * s_volume->value );

	qalSourcef( src->source, AL_REFERENCE_DISTANCE, s_attenuation_refdistance->value );
	qalSourcef( src->source, AL_MAX_DISTANCE, s_attenuation_maxdistance->value );
	qalSourcef( src->source, AL_ROLLOFF_FACTOR, attenuation );

	if( attenuation )
	{
		qalSourcei( src->source, AL_SOURCE_RELATIVE, AL_FALSE );

		trap_GetEntitySpatilization( entNum, origin, velocity );
		qalSourcefv( src->source, AL_POSITION, origin );
		qalSourcefv( src->source, AL_VELOCITY, velocity );
	}
	else
	{
		qalSourcei( src->source, AL_SOURCE_RELATIVE, AL_TRUE );
		qalSourcefv( src->source, AL_POSITION, vec3_origin );
		qalSourcefv( src->source, AL_VELOCITY, vec3_origin );
	}

	entlist[entNum].touched = qtrue;

	if( need_to_play )
		qalSourcePlay( src->source );
}

/*
 * Sound system wide functions (snd_loc.h)
 */

qboolean S_InitSources( void )
{
	int i;

	memset( srclist, 0, sizeof( srclist ) );
	src_count = 0;

	// Allocate as many sources as possible
	for( i = 0; i < MAX_SRC; i++ )
	{
		qalGenSources( 1, &srclist[i].source );
		if( qalGetError() != AL_NO_ERROR )
			break;
		src_count++;
	}
	if( !src_count )
		return qfalse;

	Com_Printf( "allocated %d sources\n", src_count );

	src_inited = qtrue;
	return qtrue;
}

void S_ShutdownSources( void )
{
	int i;

	if( !src_inited )
		return;

	// Destroy all the sources
	for( i = 0; i < src_count; i++ )
	{
		//if( srclist[i].isLocked )
		//	Com_DPrintf("Warning: Source %d is locked\n", i);

		qalSourceStop( srclist[i].source );
		qalDeleteSources( 1, &srclist[i].source );
	}

	memset( srclist, 0, sizeof( srclist ) );

	src_inited = qfalse;
}

void S_UpdateSources( void )
{
	int i, entNum;
	ALint state;

	for( i = 0; i < src_count; i++ )
	{
		if( srclist[i].isLocked )
			continue;
		if( !srclist[i].isActive )
			continue;

		if( s_volume->modified )
			qalSourcef( srclist[i].source, AL_GAIN, srclist[i].fvol * s_volume->value );

		// Check if it's done, and flag it
		qalGetSourcei( srclist[i].source, AL_SOURCE_STATE, &state );
		if( state == AL_STOPPED )
		{
			source_kill( &srclist[i] );
			continue;
		}

		entNum = srclist[i].entNum;

		if( srclist[i].isLooping )
		{
			// If a looping effect hasn't been touched this frame, kill it
			if( !entlist[entNum].touched )
			{
				source_kill( &srclist[i] );
				entlist[entNum].src = NULL;
			}
			else
			{
				entlist[entNum].touched = qfalse;
			}
			continue; // Looping effect spatialization is done in source_loop
		}


		if( srclist[i].isTracking )
		{
			vec3_t origin, velocity;

			qalSourcei( srclist[i].source, AL_SOURCE_RELATIVE, AL_FALSE );

			trap_GetEntitySpatilization( entNum, origin, velocity );
			qalSourcefv( srclist[i].source, AL_POSITION, origin );
			qalSourcefv( srclist[i].source, AL_VELOCITY, velocity );
		}
	}
}

src_t *S_AllocSource( int priority, int entNum, int channel )
{
	int i;
	int empty = -1;
	int weakest = -1;
	int weakest_time = trap_Milliseconds();
	int weakest_priority = priority;

	for( i = 0; i < src_count; i++ )
	{
		if( srclist[i].isLocked )
			continue;

		if( !srclist[i].isActive && ( empty == -1 ) )
			empty = i;

		if( srclist[i].priority < weakest_priority ||
		   ( srclist[i].priority == weakest_priority && srclist[i].lastUse < weakest_time ) )
		{
			weakest_priority = srclist[i].priority;
			weakest_time = srclist[i].lastUse;
			weakest = i;
		}

		// Is it an exact match, and not on channel 0?
		if( ( srclist[i].entNum == entNum ) && ( srclist[i].channel == channel ) && ( channel != 0 ) )
		{
			source_kill( &srclist[i] );
			return &srclist[i];
		}
	}

	if( empty != -1 )
	{
		return &srclist[empty];
	}

	if( weakest != -1 )
	{
		//Com_DPrintf( "S_AllocSource: Overwriting weakest source\n" );
		source_kill( &srclist[weakest] );
		return &srclist[weakest];
	}

	//Com_DPrintf( "S_AllocSource: Couldn't alloc source\n" );
	return NULL;
}

void S_LockSource( src_t *src )
{
	src->isLocked = qtrue;
}

void S_UnlockSource( src_t *src )
{
	src->isLocked = qfalse;
}

ALuint S_GetALSource( const src_t *src )
{
	return src->source;
}

/*
 * Global functions (sound.h)
 */

// Play a local (non-spatialized) sound effect
void S_StartLocalSound( const char *name )
{
	sfx_t *sfx;
	src_t *src;

	src = S_AllocSource( SRCPRI_LOCAL, -1, CHAN_AUTO );
	if( !src )
		return;

	sfx = S_RegisterSound( name );
	if( !sfx )
		return;

	source_setup( src, sfx, SRCPRI_LOCAL, -1, CHAN_AUTO, 1.0, ATTN_NONE );
	qalSourcei( src->source, AL_SOURCE_RELATIVE, AL_TRUE );

	qalSourcePlay( src->source );
}

// Play a one-shot sound effect
// If entity and origin are both set, then entity will hear sound locally
// and others from the fixed origin.
static void S_StartSound( sfx_t *sfx, const vec3_t origin, int entNum, int channel, float fvol, float attenuation )
{
	vec3_t origin_new, velocity;
	src_t *src;

	if( !sfx )
		return;

	src = S_AllocSource( SRCPRI_ONESHOT, entNum, channel );
	if( !src )
		return;

	source_setup( src, sfx, SRCPRI_ONESHOT, entNum, channel, fvol, attenuation );

	if( !attenuation )
	{
		qalSourcei( src->source, AL_SOURCE_RELATIVE, AL_TRUE );
		VectorSet( origin_new, 0, 0, 0 );
		VectorSet( velocity, 0, 0, 0 );
	}
	else if( !origin )
	{
		src->isTracking = qtrue;
		qalSourcei( src->source, AL_SOURCE_RELATIVE, AL_FALSE );
		trap_GetEntitySpatilization( entNum, origin_new, velocity );
	}
	else
	{
		qalSourcei( src->source, AL_SOURCE_RELATIVE, AL_FALSE );
		VectorCopy( origin, origin_new );
		VectorSet( velocity, 0, 0, 0 );
	}
	qalSourcefv( src->source, AL_POSITION, origin_new );
	qalSourcefv( src->source, AL_VELOCITY, velocity );

	qalSourcePlay( src->source );
}

/*
   ====================
   S_StartFixedSound

   Starts a sound that has a fixed position
   ====================
 */
void S_StartFixedSound( sfx_t *sfx, const vec3_t origin, int channel, float fvol, float attenuation )
{
	S_StartSound( sfx, origin, 0, channel, fvol, attenuation );
}

/*
   ====================
   S_StartRelativeSound

   Starts a sound with position changing according to the entitys position
   ====================
 */
void S_StartRelativeSound( sfx_t *sfx, int entnum, int channel, float fvol, float attenuation )
{
	S_StartSound( sfx, NULL, entnum, channel, fvol, attenuation );
}

/*
   ====================
   S_StartGlobalSound

   Starts a sound which always plays with full volume
   ====================
 */
void S_StartGlobalSound( sfx_t *sfx, int channel, float fvol )
{
	S_StartSound( sfx, NULL, 0, channel, fvol, ATTN_NONE );
}

/*void S_AL_AddAmbientSound( int entNum, sfx_t *sfx, float fvol, float attenuation )
   {
    if( !sound_started )
   	return;

    source_loop( SRCPRI_AMBIENT, sfx, entNum, fvol, attenuation );
   }*/

void S_AddLoopSound( sfx_t *sfx, int entnum, float fvol, float attenuation )
{
	source_loop( SRCPRI_LOOP, sfx, entnum, fvol, attenuation );
}

void S_StopAllSources()
{
	int i;

	for( i = 0; i < src_count; i++ )
		source_kill( &srclist[i] );
}
