/*
Copyright (C) 2002-2003 Victor Luchits

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.

*/
// snd_ogg.c

#include "client.h"
#include "snd_loc.h"

/* The function prototypes for the callbacks are basically the same as for
 * the stdio functions fread, fseek, fclose, ftell. 
 * The one difference is that the FILE * arguments have been replaced with
 * a void * - this is to be used as a pointer to whatever internal data these
 * functions might need. In the stdio case, it's just a FILE * cast to a void *
 * 
 * If you use other functions, check the docs for these functions and return
 * the right values. For seek_func(), you *MUST* return -1 if the stream is
 * unseekable
 */

#ifdef _MSC_VER
typedef __int64 ogg_int64_t;
#else
typedef long long ogg_int64_t;
#endif

typedef struct {
	size_t			(*read_func)  (void *ptr, size_t size, size_t nmemb, void *datasource);
	int				(*seek_func)  (void *datasource, ogg_int64_t offset, int whence);
	int				(*close_func) (void *datasource);
	long			(*tell_func)  (void *datasource);
} ov_callbacks;

typedef struct
{
	long			endbyte;
	int				endbit;

	unsigned char	*buffer;
	unsigned char	*ptr;
	long			storage;
} oggpack_buffer;

typedef struct
{
	unsigned char	*data;
	int				storage;
	int				fill;
	int				returned;

	int				unsynced;
	int				headerbytes;
	int				bodybytes;
} ogg_sync_state;

typedef struct vorbis_info
{
	int				version;
	int				channels;
	long			rate;

	long			bitrate_upper;
	long			bitrate_nominal;
	long			bitrate_lower;
	long			bitrate_window;

	void			*codec_setup;
} vorbis_info;

typedef struct
{
	unsigned char	*body_data;
	long			body_storage;
	long			body_fill;
	long			body_returned;

	int				*lacing_vals;
	ogg_int64_t		*granule_vals;

	long			lacing_storage;
	long			lacing_fill;
	long			lacing_packet;
	long			lacing_returned;
	
	unsigned char	header[282];
	int				header_fill;

	int				e_o_s;
	int				b_o_s;
	long			serialno;
	long			pageno;
	ogg_int64_t		packetno;
	ogg_int64_t		granulepos;
} ogg_stream_state;

typedef struct vorbis_dsp_state
{
	int				analysisp;
	vorbis_info		*vi;

	float			**pcm;
	float			**pcmret;
	int				pcm_storage;
	int				pcm_current;
	int				pcm_returned;

	int				preextrapolate;
	int				eofflag;

	long			lW;
	long			W;
	long			nW;
	long			centerW;

	ogg_int64_t		granulepos;
	ogg_int64_t		sequence;

	ogg_int64_t		glue_bits;
	ogg_int64_t		time_bits;
	ogg_int64_t		floor_bits;
	ogg_int64_t		res_bits;

	void			*backend_state;
} vorbis_dsp_state;

typedef struct vorbis_block
{
	float			**pcm;
	oggpack_buffer	opb;
	
	long			lW;
	long			W;
	long			nW;
	int				pcmend;
	int				mode;
	
	int				eofflag;
	ogg_int64_t		granulepos;
	ogg_int64_t		sequence;
	vorbis_dsp_state *vd;

	void            *localstore;
	long            localtop;
	long            localalloc;
	long            totaluse;
	struct alloc_chain *reap;

	long			glue_bits;
	long			time_bits;
	long			floor_bits;
	long			res_bits;

	void			*internal;
} vorbis_block;

typedef struct OggVorbis_File
{
	void            *datasource;
	int             seekable;
	ogg_int64_t     offset;
	ogg_int64_t     end;
	ogg_sync_state  oy; 

	int             links;
	ogg_int64_t     *offsets;
	ogg_int64_t     *dataoffsets;
	long            *serialnos;
	ogg_int64_t     *pcmlengths;

	vorbis_info     *vi;
	void			*vc;

	ogg_int64_t     pcm_offset;
	int             ready_state;
	long            current_serialno;
	int             current_link;

	double          bittrack;
	double          samptrack;

	ogg_stream_state os;
	vorbis_dsp_state vd;
	vorbis_block     vb;

	ov_callbacks	callbacks;
} OggVorbis_File;

//=============================================================================

int (*qov_clear)( OggVorbis_File *vf );
int (*qov_open_callbacks)( void *datasource, OggVorbis_File *vf, char *initial, long ibytes, ov_callbacks callbacks );
ogg_int64_t (*qov_pcm_total)( OggVorbis_File *vf, int i );
int (*qov_raw_seek)( OggVorbis_File *vf, ogg_int64_t pos );
ogg_int64_t (*qov_raw_tell)( OggVorbis_File *vf );
vorbis_info *(*qov_info)( OggVorbis_File *vf, int link );
long (*qov_read)( OggVorbis_File *vf, char *buffer, int length, int bigendianp, int word, int sgned, int *bitstream );

dllfunc_t oggvorbisfuncs[] =
{
	{ "ov_clear",			( void ** )&qov_clear },
	{ "ov_open_callbacks",	( void ** )&qov_open_callbacks },
	{ "ov_pcm_total",		( void ** )&qov_pcm_total },
	{ "ov_raw_seek",		( void ** )&qov_raw_seek },
	{ "ov_raw_tell",		( void ** )&qov_raw_tell },
	{ "ov_info",			( void ** )&qov_info },
	{ "ov_read",			( void ** )&qov_read },

	{ NULL, NULL }
};

void *vorbisLibrary;

/*
===================
SNDOGG_Init
===================
*/
void SNDOGG_Init( void )
{
	if( vorbisLibrary )
		SNDOGG_Shutdown ();

	if( s_vorbis->integer ) {
		vorbisLibrary = Sys_LoadLibrary( VORBISFILE_LIBNAME, oggvorbisfuncs );
		if( vorbisLibrary )
			Com_Printf( "Loaded %s\n", VORBISFILE_LIBNAME );
	}
}

/*
===================
SNDOGG_Shutdown
===================
*/
void SNDOGG_Shutdown( void )
{
	if( vorbisLibrary )
		Sys_UnloadLibrary( &vorbisLibrary );
}

//=============================================================================

static size_t ovcb_read( void *ptr, size_t size, size_t nb, void *datasource )
{
	bgTrack_t *track = ( bgTrack_t * )datasource;

	return FS_Read( ptr, size * nb, track->file ) / size;
}

static int ovcb_seek( void *datasource, ogg_int64_t offset, int whence )
{
	bgTrack_t *track = ( bgTrack_t * )datasource;

	switch( whence ) {
		case SEEK_SET:
			return FS_Seek( track->file, (int)offset, FS_SEEK_SET );
		case SEEK_CUR:
			return FS_Seek( track->file, (int)offset, FS_SEEK_CUR );
		case SEEK_END:
			return FS_Seek( track->file, (int)offset, FS_SEEK_END );
	}

	return -1;
}

static int ovcb_close( void *datasource ) {
	return 0;
}

static long ovcb_tell( void *datasource )
{
	bgTrack_t *track = ( bgTrack_t * )datasource;

	return FS_Tell( track->file );
}

int SNDOGG_FRead( bgTrack_t *track, void *ptr, size_t size );
int SNDOGG_FSeek( bgTrack_t *track, int pos );
void SNDOGG_FClose( bgTrack_t *track );

/*
===================
SNDOGG_OpenTrack
===================
*/
qboolean SNDOGG_OpenTrack( char *name, bgTrack_t *track )
{
	int		file;
	char	path[MAX_QPATH];
	vorbis_info	*vi;
	OggVorbis_File *vf;
	ov_callbacks callbacks = { ovcb_read, ovcb_seek, ovcb_close, ovcb_tell };

	if( !vorbisLibrary || !track )
		return qfalse;

	Q_strncpyz( path, name, sizeof(path) );
	COM_ReplaceExtension( path, ".ogg", sizeof(path) );

	if( FS_FOpenFile( path, &file, FS_READ ) == -1 )
		return qfalse;

	track->file = file;
	track->vorbisFile = vf = S_Malloc( sizeof( OggVorbis_File ) );

	if( qov_open_callbacks( track, vf, NULL, 0, callbacks ) < 0 ) {
		Com_Printf( "SNDOGG_OpenTrack: couldn't open %s for reading\n", path );
		goto fail;
	}

	vi = qov_info( vf, -1 );
	if( (vi->channels != 1) && (vi->channels != 2) ) {
		Com_Printf( "SNDOGG_OpenTrack: %s has an unsupported number of channels: %i\n", path, vi->channels );
		goto fail;
	}

	track->info.channels = vi->channels;
	track->info.rate = vi->rate;
	track->info.width = 2;
	track->info.loopstart = -1;
	track->info.dataofs = qov_raw_tell( vf );
	track->info.samples = qov_pcm_total( vf, -1 );

	track->read = SNDOGG_FRead;
	track->seek = SNDOGG_FSeek;
	track->close = SNDOGG_FClose;

	return qtrue;

fail:
	if( file )
		FS_FCloseFile( file );
	if( vf ) {
		qov_clear( vf );
		S_Free( vf );
	}

	track->file = 0;
	track->vorbisFile = NULL;

	return qfalse;
}

/*
===================
SNDOGG_FRead
===================
*/
int SNDOGG_FRead( bgTrack_t *track, void *ptr, size_t size ) {
	int bs;

	return qov_read( track->vorbisFile, ( char * )ptr, (int)size, 0, 2, 1, &bs );
}

/*
===================
SNDOGG_FSeek
===================
*/
int SNDOGG_FSeek( bgTrack_t *track, int pos ) {
	return qov_raw_seek( track->vorbisFile, (ogg_int64_t)pos );
}

/*
===================
SNDOGG_FClose
===================
*/
void SNDOGG_FClose( bgTrack_t *track )
{
	FS_FCloseFile( track->file );
	qov_clear( track->vorbisFile );
	S_Free( track->vorbisFile );
}
