/**
 * OpenAL cross platform audio library
 * Copyright (C) 1999-2000 by authors.
 * This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Library General Public
 *  License as published by the Free Software Foundation; either
 *  version 2 of the License, or (at your option) any later version.
 *
 * This library 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
 *  Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 *  License along with this library; if not, write to the
 *  Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 *  Boston, MA  02111-1307, USA.
 * Or go to http://www.gnu.org/copyleft/lgpl.html
 */

#include <stdlib.h>
#include "include\alMain.h"
#include "include\alError.h"
#include "include\alSource.h"

ALAPI ALvoid ALAPIENTRY alGenSources(ALsizei n,ALuint *sources)
{
	ALfloat DefaultDirection[]={0.0f, 0.0f, 0.0f};
	ALfloat DefaultPosition[]={0.0f, 0.0f, 0.0f};
	ALfloat DefaultVelocity[]={0.0f, 0.0f, 0.0f};
	ALCcontext *Context;
	ALsource *Source;
	ALsizei i=0;

	Context=alcGetCurrentContext();
	alcSuspendContext(Context);
	if (!Context->Source)
	{
		Context->Source=malloc(sizeof(ALsource));
		if (Context->Source)
		{
			memset(Context->Source,0,sizeof(ALsource));
			sources[i]=(ALuint)Context->Source;
			Context->Source->valid=AL_TRUE;
			alSourcei(sources[i],AL_CONE_INNER_ANGLE,360);
			alSourcei(sources[i],AL_CONE_OUTER_ANGLE,360);
			alSourcef(sources[i],AL_PITCH,1.0f);
			alSourcefv(sources[i],AL_POSITION,DefaultPosition);
			alSourcefv(sources[i],AL_DIRECTION,DefaultDirection);
			alSourcefv(sources[i],AL_VELOCITY,DefaultVelocity);
			alSourcef(sources[i],AL_REFERENCE_DISTANCE,1.0f);
			alSourcef(sources[i],AL_MAX_DISTANCE,1000000.0f);
			alSourcef(sources[i],AL_ROLLOFF_FACTOR,1.0f);
			alSourcei(sources[i],AL_LOOPING,AL_FALSE);
			alSourcef(sources[i],AL_GAIN,1.0f);
			alSourcef(sources[i],AL_MIN_GAIN,0.0f);
			alSourcef(sources[i],AL_MAX_GAIN,1.0f);
			alSourcef(sources[i],AL_CONE_OUTER_GAIN,1.0f);
			alSourcei(sources[i],AL_SOURCE_STATE,AL_INITIAL);
			alSourcei(sources[i],AL_BUFFER,0);
			Context->SourceCount++;
			i++;
		}
		alcUpdateContext(Context);
		Source=Context->Source;
	}
	else
	{
		Source=Context->Source;
		while (Source->next)
			Source=Source->next;
	}
	while ((Source)&&(i<n))
	{
		Source->next=malloc(sizeof(ALsource));
		if (Source->next)
		{
			memset(Source->next,0,sizeof(ALsource));
			sources[i]=(ALuint)Source->next;
			Source->next->previous=Source;
			Source->next->valid=AL_TRUE;
			alSourcei(sources[i],AL_CONE_INNER_ANGLE,360);
			alSourcei(sources[i],AL_CONE_OUTER_ANGLE,360);
			alSourcef(sources[i],AL_PITCH,1.0f);
			alSourcefv(sources[i],AL_POSITION,DefaultPosition);
			alSourcefv(sources[i],AL_DIRECTION,DefaultDirection);
			alSourcefv(sources[i],AL_VELOCITY,DefaultVelocity);
			alSourcef(sources[i],AL_REFERENCE_DISTANCE,1.0f);
			alSourcef(sources[i],AL_MAX_DISTANCE,1000000.0f);
			alSourcef(sources[i],AL_ROLLOFF_FACTOR,1.0f);
			alSourcei(sources[i],AL_LOOPING,AL_FALSE);
			alSourcef(sources[i],AL_GAIN,1.0f);
			alSourcef(sources[i],AL_MIN_GAIN,0.0f);
			alSourcef(sources[i],AL_MAX_GAIN,1.0f);
			alSourcef(sources[i],AL_CONE_OUTER_GAIN,1.0f);
			alSourcei(sources[i],AL_SOURCE_STATE,AL_INITIAL);
			alSourcei(sources[i],AL_BUFFER,0);
			Context->SourceCount++;
			i++;
		}
		alcUpdateContext(Context);
		Source=Source->next;
	}
	alcProcessContext(Context);
	if (i!=n) alSetError(AL_OUT_OF_MEMORY);
}

ALAPI ALvoid ALAPIENTRY alDeleteSources(ALsizei n,ALuint *sources)
{
	ALCcontext *Context;
	ALsource *Source;
	ALsizei i;

	Context=alcGetCurrentContext();
	alcSuspendContext(Context);
	for (i=0;i<n;i++)
	{
		if (alIsSource(sources[i]))
		{
			Source=((ALsource *)sources[i]);
			if (Source->previous)
				Source->previous->next=Source->next;
			else
				Context->Source=Source->next;
			if (Source->next)
				Source->next->previous=Source->previous;
			memset(Source,0,sizeof(ALsource));
			Context->SourceCount--;
			free(Source);
			alcUpdateContext(Context);
		}
		else alSetError(AL_INVALID_NAME);
	}
	alcProcessContext(Context);
}

ALAPI ALboolean ALAPIENTRY alIsSource(ALuint source)
{
	ALboolean result=AL_FALSE;
	ALCcontext *Context;
	ALsource *Source;
	
	Context=alcGetCurrentContext();
	alcSuspendContext(Context);
	Source=((ALsource *)source);
	if (Source)
	{
		if ((Source->previous==NULL)||(Source->previous->next==Source))
		{
			if ((Source->next==NULL)||(Source->next->previous==Source))
				result=Source->valid;
		}
	}
	alcProcessContext(Context);
	return result;
}

ALAPI ALvoid ALAPIENTRY alSourcef(ALuint source,ALenum pname,ALfloat value)
{
	ALCcontext *Context;
	ALsource *Source;

	Context=alcGetCurrentContext();
	alcSuspendContext(Context);
	if (alIsSource(source))
	{
		Source=((ALsource *)source);
		switch(pname)
		{
			case AL_PITCH:
				if ((value>=0.0f)&&(value<=2.0f))
				{	
					Source->param[pname-AL_CONE_INNER_ANGLE].data.f=value;
					Source->param[pname-AL_CONE_INNER_ANGLE].valid=AL_TRUE;
					alcUpdateContext(Context);
				}
				else
					alSetError(AL_INVALID_VALUE);
				break;
			case AL_GAIN:
			case AL_MAX_DISTANCE:
			case AL_ROLLOFF_FACTOR:
			case AL_REFERENCE_DISTANCE:
				if (value>=0.0f)
				{
					Source->param[pname-AL_CONE_INNER_ANGLE].data.f=value;
					Source->param[pname-AL_CONE_INNER_ANGLE].valid=AL_TRUE;
					alcUpdateContext(Context);
				}
				else
					alSetError(AL_INVALID_VALUE);
				break;
			case AL_MIN_GAIN:
			case AL_MAX_GAIN:
			case AL_CONE_OUTER_GAIN:
				if ((value>=0.0f)&&(value<=1.0f))
				{	
					Source->param[pname-AL_CONE_INNER_ANGLE].data.f=value;
					Source->param[pname-AL_CONE_INNER_ANGLE].valid=AL_TRUE;
					alcUpdateContext(Context);
				}
				else
					alSetError(AL_INVALID_VALUE);
				break;
			default:
				alSetError(AL_INVALID_ENUM);
				break;
		}
	} 
	else alSetError(AL_INVALID_NAME);
	alcProcessContext(Context);
}

ALAPI ALvoid ALAPIENTRY alSourcefv(ALuint source,ALenum pname,ALfloat *values)
{
	ALCcontext *Context;
	ALsource *Source;

	Context=alcGetCurrentContext();
	alcSuspendContext(Context);
	if (alIsSource(source))
	{
		Source=((ALsource *)source);
		switch(pname) 
		{
			case AL_POSITION:
			case AL_VELOCITY:
			case AL_DIRECTION:
				Source->param[pname-AL_CONE_INNER_ANGLE].data.fv3[0]=values[0];
				Source->param[pname-AL_CONE_INNER_ANGLE].data.fv3[1]=values[1];
				Source->param[pname-AL_CONE_INNER_ANGLE].data.fv3[2]=values[2];
				Source->param[pname-AL_CONE_INNER_ANGLE].valid=AL_TRUE;
				alcUpdateContext(Context);
				break;
			default:
				alSetError(AL_INVALID_ENUM);
				break;
		}
	} 
	else alSetError(AL_INVALID_NAME);
	alcProcessContext(Context);
}

ALAPI ALvoid ALAPIENTRY alSource3f(ALuint source,ALenum pname,ALfloat v1,ALfloat v2,ALfloat v3)
{
	ALCcontext *Context;
	ALsource *Source;

	Context=alcGetCurrentContext();
	alcSuspendContext(Context);
	if (alIsSource(source))
	{
		Source=((ALsource *)source);
		switch(pname) 
		{
			case AL_POSITION:
			case AL_VELOCITY:
			case AL_DIRECTION:
				Source->param[pname-AL_CONE_INNER_ANGLE].data.fv3[0]=v1;
				Source->param[pname-AL_CONE_INNER_ANGLE].data.fv3[1]=v2;
				Source->param[pname-AL_CONE_INNER_ANGLE].data.fv3[2]=v3;
				Source->param[pname-AL_CONE_INNER_ANGLE].valid=AL_TRUE;
				alcUpdateContext(Context);
				break;
			default:
				alSetError(AL_INVALID_ENUM);
				break;
		}
	} 
	else alSetError(AL_INVALID_NAME);
	alcProcessContext(Context);
}

ALAPI ALvoid ALAPIENTRY alSourcei(ALuint source,ALenum pname,ALint value)
{
	ALCcontext *Context;
	ALsource *Source;

	Context=alcGetCurrentContext();
	alcSuspendContext(Context);
	if (alIsSource(source))
	{
		Source=((ALsource *)source);
		switch(pname) 
		{
			case AL_SOURCE_RELATIVE:
				if ((value==AL_FALSE)||(value==AL_TRUE))
				{
					Source->relative=value;
					alcUpdateContext(Context);
				}
				else
					alSetError(AL_INVALID_VALUE);
				break;
			case AL_CONE_INNER_ANGLE:
			case AL_CONE_OUTER_ANGLE:
				if ((value>=0)&&(value<=360))
				{
					Source->param[pname-AL_CONE_INNER_ANGLE].data.i=value;
					Source->param[pname-AL_CONE_INNER_ANGLE].data.f=(float)value;
					Source->param[pname-AL_CONE_INNER_ANGLE].valid=AL_TRUE;
					alcUpdateContext(Context);
				}
				else
					alSetError(AL_INVALID_VALUE);
				break;
			case AL_LOOPING:
				if ((value==AL_FALSE)||(value==AL_TRUE))
				{
					Source->param[pname-AL_CONE_INNER_ANGLE].data.i=value;
					Source->param[pname-AL_CONE_INNER_ANGLE].valid=AL_TRUE;
					alcUpdateContext(Context);
				}
				else
					alSetError(AL_INVALID_VALUE);
				break;
			case AL_BUFFER:
				Source->param[pname-AL_CONE_INNER_ANGLE].data.i=value;
				Source->param[pname-AL_CONE_INNER_ANGLE].valid=AL_TRUE;
				alcUpdateContext(Context);
				break;
			case AL_SOURCE_STATE:
				Source->state=value;
				alcUpdateContext(Context);
				break;
			default:
				alSetError(AL_INVALID_ENUM);
				break;
	
		}
	} 
	else alSetError(AL_INVALID_NAME);
	alcProcessContext(Context);
}

ALAPI ALvoid ALAPIENTRY alGetSourcef(ALuint source,ALenum pname,ALfloat *value)
{
	ALCcontext *Context;
	ALsource *Source;

	Context=alcGetCurrentContext();
	alcSuspendContext(Context);
	if (alIsSource(source))
	{
		Source=((ALsource *)source);
		switch(pname) 
		{
			case AL_PITCH:
			case AL_GAIN:
			case AL_MIN_GAIN:
			case AL_MAX_GAIN:
			case AL_MAX_DISTANCE:
			case AL_ROLLOFF_FACTOR:
			case AL_CONE_OUTER_GAIN:
			case AL_CONE_INNER_ANGLE:
			case AL_CONE_OUTER_ANGLE:
			case AL_REFERENCE_DISTANCE:
				if (Source->param[pname-AL_CONE_INNER_ANGLE].valid)
					*value=Source->param[pname-AL_CONE_INNER_ANGLE].data.f;
				break;
			default:
				alSetError(AL_INVALID_ENUM);
				break;
		}
	} 
	else alSetError(AL_INVALID_NAME);
	alcProcessContext(Context);
}

ALAPI ALvoid ALAPIENTRY alGetSourcefv(ALuint source,ALenum pname,ALfloat *values)
{
	ALCcontext *Context;
	ALsource *Source;

	Context=alcGetCurrentContext();
	alcSuspendContext(Context);
	if (alIsSource(source))
	{
		Source=((ALsource *)source);
		switch(pname) 
		{
			case AL_POSITION:
			case AL_VELOCITY:
			case AL_DIRECTION:
				if (Source->param[pname-AL_CONE_INNER_ANGLE].valid)
				{
					values[0]=Source->param[pname-AL_CONE_INNER_ANGLE].data.fv3[0];
					values[1]=Source->param[pname-AL_CONE_INNER_ANGLE].data.fv3[1];
					values[2]=Source->param[pname-AL_CONE_INNER_ANGLE].data.fv3[2];
				}
				break;
			default:
				alSetError(AL_INVALID_ENUM);
				break;
		}
	} 
	else alSetError(AL_INVALID_NAME);
	alcProcessContext(Context);
}

ALAPI ALvoid ALAPIENTRY alGetSourcei(ALuint source,ALenum pname,ALint *value)
{
	ALCcontext *Context;
	ALsource *Source;

	Context=alcGetCurrentContext();
	alcSuspendContext(Context);
	if (alIsSource(source))
	{
		Source=((ALsource *)source);
		switch(pname) 
		{
			case AL_SOURCE_RELATIVE:
				*value=Source->relative;
				break;
			case AL_CONE_INNER_ANGLE:
			case AL_CONE_OUTER_ANGLE:
			case AL_LOOPING:
			case AL_BUFFER:
				if (Source->param[pname-AL_CONE_INNER_ANGLE].valid)
					*value=Source->param[pname-AL_CONE_INNER_ANGLE].data.i;
				break;
			case AL_SOURCE_STATE:
				*value=Source->state;
				break;
			case AL_BUFFERS_QUEUED:
				*value=1;
				break;
			case AL_BUFFERS_PROCESSED:
				*value=1;
				break;
			default:
				alSetError(AL_INVALID_ENUM);
				break;
		}
	} 
	else alSetError(AL_INVALID_NAME);
	alcProcessContext(Context);
}

ALAPI ALvoid ALAPIENTRY alSourcePlay(ALuint source)
{
	ALCcontext *Context;
	ALsource *Source;

	Context=alcGetCurrentContext();
	alcSuspendContext(Context);
	if (alIsSource(source))
	{
		Source=((ALsource *)source);
		if (Source->state!=AL_PAUSED)
		{
			Source->state=AL_PLAYING;
			Source->inuse=AL_TRUE;
			Source->play=AL_TRUE;
			Source->position=0;
			Source->position_fraction=0;
		}
		else
		{
			Source->state=AL_PLAYING;
			Source->inuse=AL_TRUE;
			Source->play=AL_TRUE;
		}
		alcUpdateContext(Context);
	} 
	else alSetError(AL_INVALID_OPERATION);
	alcProcessContext(Context);
}

ALAPI ALvoid ALAPIENTRY alSourcePlayv(ALsizei n,ALuint *sources)
{
	ALCcontext *Context;
	ALsource *Source;
	ALsizei i;

	Context=alcGetCurrentContext();
	alcSuspendContext(Context);
	for (i=0;i<n;i++)
	{
		if (alIsSource(sources[i]))
		{
			Source=((ALsource *)sources[i]);
			if (Source->state!=AL_PAUSED)
			{
				Source->state=AL_PLAYING;
				Source->inuse=AL_TRUE;
				Source->play=AL_TRUE;
				Source->position=0;
				Source->position_fraction=0;
			}
			else
			{
				Source->state=AL_PLAYING;
				Source->inuse=AL_TRUE;
				Source->play=AL_TRUE;
			}
			alcUpdateContext(Context);
		} 
		else alSetError(AL_INVALID_OPERATION);
	}
	alcProcessContext(Context);
}

ALAPI ALvoid ALAPIENTRY alSourcePause(ALuint source)
{
	ALCcontext *Context;
	ALsource *Source;

	Context=alcGetCurrentContext();
	alcSuspendContext(Context);
	if (alIsSource(source))
	{
		Source=((ALsource *)source);
		if (Source->state==AL_PLAYING)
		{
			Source->state=AL_PAUSED;
			Source->inuse=AL_FALSE;
		}
		alcUpdateContext(Context);
	} 
	else alSetError(AL_INVALID_OPERATION);
	alcProcessContext(Context);
}

ALAPI ALvoid ALAPIENTRY alSourcePausev(ALsizei n,ALuint *sources)
{
	ALCcontext *Context;
	ALsource *Source;
	ALsizei i;

	Context=alcGetCurrentContext();
	alcSuspendContext(Context);
	for (i=0;i<n;i++)
	{
		if (alIsSource(sources[i]))
		{
			Source=((ALsource *)sources[i]);
			if (Source->state==AL_PLAYING)
			{
				Source->state=AL_PAUSED;
				Source->inuse=AL_FALSE;
			}
			alcUpdateContext(Context);
		} 
		else alSetError(AL_INVALID_OPERATION);
	} 
	alcProcessContext(Context);
}

ALAPI ALvoid ALAPIENTRY alSourceStop(ALuint source)
{
	ALCcontext *Context;
	ALsource *Source;

	Context=alcGetCurrentContext();
	alcSuspendContext(Context);
	if (alIsSource(source))
	{
		Source=((ALsource *)source);
		if (Source->state!=AL_INITIAL)
		{
			Source->state=AL_STOPPED;
			Source->inuse=AL_FALSE;
		}
		alcUpdateContext(Context);
	} 
	else alSetError(AL_INVALID_OPERATION);
	alcProcessContext(Context);
}

ALAPI ALvoid ALAPIENTRY alSourceStopv(ALsizei n,ALuint *sources)
{
	ALCcontext *Context;
	ALsource *Source;
	ALsizei i;

	Context=alcGetCurrentContext();
	alcSuspendContext(Context);
	for (i=0;i<n;i++)
	{
		if (alIsSource(sources[i]))
		{
			Source=((ALsource *)sources[i]);
			if (Source->state!=AL_INITIAL)
			{
				Source->state=AL_STOPPED;
				Source->inuse=AL_FALSE;
			}
			alcUpdateContext(Context);
		} 
		else alSetError(AL_INVALID_OPERATION);
	}
	alcProcessContext(Context);
}

ALAPI ALvoid ALAPIENTRY alSourceRewind(ALuint source)
{
	ALCcontext *Context;
	ALsource *Source;

	Context=alcGetCurrentContext();
	alcSuspendContext(Context);
	if (alIsSource(source))
	{
		Source=((ALsource *)source);
		if (Source->state!=AL_INITIAL)
		{
			Source->state=AL_INITIAL;
			Source->inuse=AL_FALSE;
			Source->position=0;
			Source->position_fraction=0;
		}
		alcUpdateContext(Context);
	} 
	else alSetError(AL_INVALID_OPERATION);
	alcProcessContext(Context);
}

ALAPI ALvoid ALAPIENTRY alSourceRewindv(ALsizei n,ALuint *sources)
{
	ALCcontext *Context;
	ALsource *Source;
	ALsizei i;

	Context=alcGetCurrentContext();
	alcSuspendContext(Context);
	for (i=0;i<n;i++)
	{
		if (alIsSource(sources[i]))
		{
			Source=((ALsource *)sources[i]);
			if (Source->state!=AL_INITIAL)
			{
				Source->state=AL_INITIAL;
				Source->inuse=AL_FALSE;
				Source->position=0;
				Source->position_fraction=0;
			}
			alcUpdateContext(Context);
		} 
		else alSetError(AL_INVALID_OPERATION);
	}
	alcProcessContext(Context);
}
