/*
Copyright (C) 1996-1997 Id Software, Inc.

This program 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.

This program 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 this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

*/

#include "quakedef.h"

#ifdef _WIN32
#include "winquake.h"
#endif

#pragma comment(lib, "libs/fmod/fmodvc.lib")

typedef struct
{
	byte			*data;
	int				length;
	int				position;
	char			filename[MAX_QPATH];
} SND_File_t;

typedef struct
{
	int				volume;
	int				channel;
	qboolean		inuse;
	qboolean		locked;
	qboolean		looping;
	qboolean		paused;
	unsigned int	streamposition;
} SND_Channel_t;

/*////////////////////////////////////////////////////////////////////////////////////
//                         All public vars here for no screwups                     //
////////////////////////////////////////////////////////////////////////////////////*/
SND_File_t	SND_File;

int				SND_Samplerate;
int				SND_Initialised;
int				SND_HardwareChannels;
int				SND_HardwareChannels2D;
int				SND_HardwareChannels3D;
int				SND_SoftwareChannels;
cvar_t			SND_AudioDriver		= {"fmod_audiodriver", "2", true}; /* 0 = No Sound, 1 = WINMM, 2 = DSound, 3 = A3D */

char			OutputAPI[4][64];
int				SND_IsBlocked = 0;

FSOUND_STREAM	*SND_MusicStream;
SND_Channel_t	SND_MusicChannel;

/*////////////////////////////////////////////////////////////////////////////////////
//                  Custom Fmod file routines to allow PAK file access              //
////////////////////////////////////////////////////////////////////////////////////*/

unsigned int *F_CALLBACKAPI SND_FOpen (const char *name)
{
	int		/*i,*/ j;
	int		fi;

	if(!SND_File.length)
	{
		j = COM_OpenFile ((char *)name, &fi);

		if(j == -1)
		{
			Con_SafePrintf("Failed to open %s, file not found\n", name);
			return 0;
		}

		SND_File.data = malloc(j);
		SND_File.length = j;
		SND_File.position = 0;
		strcpy(SND_File.filename, name);

		Sys_FileRead(fi, SND_File.data, j);
		COM_CloseFile(fi);
		if (developer.value)
			Con_DPrintf("SND_Fopen: Sucessfully opened %s\n", name);
		return (unsigned int *)1;
	}

	Con_SafePrintf("SND_FOpen: Failed to open %s, insufficient handles\n", name);
	return 0;
}

void F_CALLBACKAPI SND_FClose (void *handle)
{
	SND_File.length = 0;
	SND_File.position = 0;
	strcpy(SND_File.filename, "\0");
	free(SND_File.data);
}

int F_CALLBACKAPI SND_FRead (void *buffer, int size, void *handle)
{
	int /*i, j,*/ read;
//	unsigned char buf;

	if((SND_File.position + size) > SND_File.length)
	{
		read = SND_File.length - SND_File.position;

		if(read <= 0)
		{
			return 0;
		}
	}
	else
	{
		read = size;
	}

	memcpy(buffer, SND_File.data + SND_File.position, read);

	SND_File.position += read;

	return read;
}

int F_CALLBACKAPI SND_FSeek (void *handle, int pos, signed char mode)
{
	switch(mode)
	{
	case SEEK_END:
		SND_File.position = SND_File.length + pos;
		break;
	case SEEK_CUR:
		SND_File.position += pos;
		break;
	case SEEK_SET:
		SND_File.position = 0 + pos;
		break;
	}

	return 1;
}

int F_CALLBACKAPI SND_FTell (void *handle)
{
	return SND_File.position;
}

/*////////////////////////////////////////////////////////////////////////////////////
//                           Streaming Audio Managment                              //
////////////////////////////////////////////////////////////////////////////////////*/

void FMOD_StartMusic (char *name, qboolean loop, qboolean notify, qboolean resume)
{
//	int				fval;
	FILE			*f;
	int				filefound = -1;
	char			file[MAX_QPATH];

	if(!SND_Initialised)
		return;

	if(SND_MusicChannel.inuse)
	{
		FMOD_StopMusic();
	}

	if(strlen(name) == 0)
		return;

	if(resume == true)
		strcpy(file, name);
	else
		sprintf(file, "%s.ogg", name);
	filefound = COM_FindFile(file, NULL, &f, true);

	if(filefound == -1)
	{
		if(resume == true)
			strcpy(file, name);
		else
			sprintf(file, "%s.mp3", name);
		filefound = COM_FindFile(file, NULL, &f, true);
	}

	if(loop)
	{
		SND_MusicStream = FSOUND_Stream_Open(file, (FSOUND_LOOP_NORMAL | FSOUND_2D), 0, 0);
		SND_MusicChannel.looping = 1;
	}
	else
	{
		SND_MusicStream = FSOUND_Stream_Open(file, (FSOUND_2D), 0, 0);
		SND_MusicChannel.looping = 0;
	}

	if(!SND_MusicStream)
	{
		Con_Printf("Couldn't open stream %s\n", file);
		return;
	}
	else
	{
		if(notify == true)
			Con_Printf("Playing: %s...\n", file);
	}

	SND_MusicChannel.inuse = true;
	if(resume == true)
	{
		FSOUND_Stream_SetPosition(SND_MusicStream, SND_MusicChannel.streamposition);
		SND_MusicChannel.channel = FSOUND_Stream_Play(SND_MusicChannel.channel, SND_MusicStream);
	}
	else
		SND_MusicChannel.channel = FSOUND_Stream_Play(SND_MusicChannel.channel, SND_MusicStream);
}

void FMOD_StopMusic (void)
{
//	int i;

	if(!SND_Initialised || !SND_MusicStream)
		return;

	FSOUND_Stream_Stop(SND_MusicStream);
	FSOUND_Stream_Close(SND_MusicStream);

	SND_MusicStream = NULL;

	SND_MusicChannel.inuse = false;
}

void FMOD_PauseMusic (void)
{
	if(!SND_Initialised || !SND_MusicChannel.inuse)
		return;

	if(SND_MusicChannel.paused == false)
	{
		FSOUND_Stream_Stop(SND_MusicStream);
		SND_MusicChannel.paused = true;
	}
}

void FMOD_ResumeMusic (void)
{
	if(!SND_Initialised || !SND_MusicChannel.inuse)
		return;

	if(SND_MusicChannel.paused == true)
	{
		FSOUND_Stream_SetPosition(SND_MusicStream, SND_MusicChannel.streamposition);
		SND_MusicChannel.channel = FSOUND_Stream_Play(SND_MusicChannel.channel, SND_MusicStream);
		SND_MusicChannel.paused = false;
	}
}

void FMOD_UpdateMusic (void)
{
	if(!SND_Initialised || !SND_MusicChannel.inuse)
		return;

	SND_MusicChannel.volume = bgmvolume.value * 255;
	
	FSOUND_SetVolume(SND_MusicChannel.channel, SND_MusicChannel.volume);
	SND_MusicChannel.streamposition = FSOUND_Stream_GetPosition(SND_MusicStream);

	if((int)(SND_MusicChannel.volume) == 0 || cl.paused)
		FMOD_PauseMusic();
	else
		FMOD_ResumeMusic();
}

void FMOD_StartMusicConsole (void)
{
	char name[MAX_QPATH];
	char file[MAX_QPATH];

	if (Cmd_Argc() < 2)
	{
		Con_Printf ("fmod_playmusic <filename> : play an audio file\n");
		return;
	}

	sprintf(name, "%s\0", Cmd_Argv(1));
	COM_StripExtension(name, file);

	FMOD_StartMusic(file, false, true, false);

	return;
}


/*////////////////////////////////////////////////////////////////////////////////////
//                           Quake Sound Initialisation                             //
////////////////////////////////////////////////////////////////////////////////////*/

void FMOD_Restart (void)
{
	int /*i,*/ fval;
//	unsigned int m;
	char filename[MAX_QPATH];

	// Save Music Info
	if(SND_MusicStream)
	{
		strcpy(filename, SND_File.filename);
		FMOD_StopMusic();
	}

	FMOD_Shutdown();

	fval = FSOUND_SetMixer(FSOUND_MIXER_QUALITY_AUTODETECT);

	if(!fval)
	{
		Con_Printf("\nFMOD initialization failed, couldn't set mixer\n");
		SND_Initialised = 0;
		return;
	}

	// Setup Driver
	switch((int)SND_AudioDriver.value)
	{
	case 0:
		fval = FSOUND_SetOutput(FSOUND_OUTPUT_NOSOUND);
		break;
	case 1:
		fval = FSOUND_SetOutput(FSOUND_OUTPUT_WINMM);
		break;
	case 2:
		fval = FSOUND_SetOutput(FSOUND_OUTPUT_DSOUND);
		break;
	case 3:
		fval = FSOUND_SetOutput(FSOUND_OUTPUT_A3D);
		break;
	}

	if(!fval)
	{
		Con_Printf("\nFMOD initialization failed, couldn't set driver\n");
		SND_Initialised = 0;
		return;
	}

	FSOUND_GetNumHWChannels(&SND_HardwareChannels2D, &SND_HardwareChannels3D, &SND_HardwareChannels);
	SND_SoftwareChannels = MAX_CHANNELS - SND_HardwareChannels;
	SND_Samplerate = s_samplerate.value;

	fval = FSOUND_Init(SND_Samplerate, SND_SoftwareChannels, 0);
	if(!fval)
	{
		Con_Printf("\nFMOD initialization failed\n");
		SND_Initialised = 0;
		return;
	}

	FSOUND_SetSFXMasterVolume(255);

	SND_Initialised = 1;

	// Restart Music if Needed
	FMOD_StartMusic(filename, SND_MusicChannel.looping, false, true);

	if (developer.value)
		Con_Printf("\nFMOD initialization succeeded\n");

	return;
}

void FMOD_Init (void)
{
	int		fval;
	float	fver;

	Cmd_AddCommand("fmod_restart", FMOD_Restart);
	Cmd_AddCommand("fmod_playmusic", FMOD_StartMusicConsole);
	Cmd_AddCommand("fmod_stopmusic", FMOD_StopMusic);
	Cmd_AddCommand("fmod_pausemusic", FMOD_PauseMusic);
	Cmd_AddCommand("fmod_resumemusic", FMOD_ResumeMusic);

	Cvar_RegisterVariable(&SND_AudioDriver);

	if (COM_CheckParm("-nosound"))
	{
		SND_Initialised = 0;
		return;
	}

	SND_Samplerate = s_samplerate.value;

	fver = FSOUND_GetVersion();
	if(fver < FMOD_VERSION)
	{
		Con_Printf("\nFMOD DLL version incorrect, found v%1.2f, requires v%1.2f or newer\n", fver, FMOD_VERSION);
		SND_Initialised = 0;
		return;
	}

	fval = FSOUND_SetMixer(FSOUND_MIXER_QUALITY_AUTODETECT);
	if(!fval)
	{
		Con_Printf("\nFMOD initialization failed, couldn't set mixer\n");
		SND_Initialised = 0;
		return;
	}

	// You may wonder why I initalize twice... so I can get the number of hardware channels and
	// then only allocate the minimum number of software channels required for MAX_CHANNELS
	fval = FSOUND_Init(SND_Samplerate, MAX_CHANNELS, 0);
	if(!fval)
	{
		Con_Printf("\nFMOD initialization failed\n");
		SND_Initialised = 0;
		return;
	}

	FSOUND_GetNumHWChannels(&SND_HardwareChannels2D, &SND_HardwareChannels3D, &SND_HardwareChannels);
	SND_SoftwareChannels = MAX_CHANNELS - SND_HardwareChannels;

	FSOUND_Close();

	fval = FSOUND_Init(SND_Samplerate, SND_SoftwareChannels, 0);
	if(!fval)
	{
		Con_Printf("\nFMOD initialization failed\n");
		SND_Initialised = 0;
		return;
	}

	FSOUND_SetSFXMasterVolume(255);

	FSOUND_File_SetCallbacks(SND_FOpen, SND_FClose, SND_FRead, SND_FSeek, SND_FTell);

	fval = FSOUND_GetOutput();

	strcpy(OutputAPI[0], "Null Audio Driver");
	strcpy(OutputAPI[1], "Windows Wave Audio");
	strcpy(OutputAPI[2], "Microsoft DirectSound");
	strcpy(OutputAPI[3], "Aureal A3D");

	if (developer.value)
	{
		Con_Printf("\n%i software channels\n", SND_SoftwareChannels);
		Con_Printf("%i hardware channels\n", SND_HardwareChannels);
		Con_Printf("using %s\n", OutputAPI[fval]);
		Con_Printf("Sound sampling rate: %i\n", SND_Samplerate);
		Con_Printf("FMOD version: v%1.2f\n", FMOD_VERSION);
	}

	SND_Initialised = 1;

	// Lock Music channel
	SND_MusicChannel.volume = 0;
	SND_MusicChannel.channel = 0;
	SND_MusicChannel.inuse = false;
	SND_MusicChannel.locked = true;
	SND_MusicChannel.looping = false;
	SND_MusicChannel.paused = false;
	SND_MusicChannel.streamposition = 0;

	return;
}

void FMOD_Shutdown (void)
{
	FSOUND_Close();

	SND_Initialised = 0;

	return;
}
