//-----------------------------------------------------------------------------
//
//  $Logfile:: /Source/wave.c                                                 $
// $Revision:: 2                                                              $
//   $Author:: Burgermac                                                      $
//     $Date:: 12/27/00 7:43 PM                                               $
//
// Copyright (C) 1998 by Ritual Entertainment, Inc.
// All rights reserved.
//
// This source is may not be distributed and/or modified without
// expressly written permission by Ritual Entertainment, Inc.
//
// DESCRIPTION:
// wave header info
//

#include "qcommon.h"
#include "wave.h"
#ifndef DEDICATED_ONLY
#include "snd_sound.h"
#endif

/*
===============================================================================

WAV loading

===============================================================================
*/

typedef struct WaveLoadState_t {
	byte *data_p;
	byte *iff_end;
	byte *last_chunk;
	byte *iff_data;
	int iff_chunk_len;
} WaveLoadState_t;

static cvar_t *sound_dir;

static short GetLittleShort(WaveLoadState_t *Input)
{
	byte *WorkPtr;
	int val;
	WorkPtr = Input->data_p;
	val = WorkPtr[0] + (WorkPtr[1]<<8);
	WorkPtr+=2;
	Input->data_p = WorkPtr;
	return (short)val;
}

static int GetLittleLong(WaveLoadState_t *Input)
{
	byte *WorkPtr;
	int val;
	WorkPtr = Input->data_p;
	val = WorkPtr[0] + (WorkPtr[1]<<8) + (WorkPtr[2]<<16) + (WorkPtr[3]<<24);
	WorkPtr+=4;
	Input->data_p = WorkPtr;
	return (int)val;
}

static void FindNextChunk(WaveLoadState_t *Input, const char *name)
{
	while (1) {
		byte *WorkPtr;
		int Len;
		
		WorkPtr = Input->last_chunk;
	
		if (WorkPtr >= Input->iff_end) {	// didn't find the chunk
			break;
		}
		Input->data_p = WorkPtr+4;
		Len = GetLittleLong(Input);
		if (Len < 0) {
			break;
		}
		Input->iff_chunk_len = Len;
		Input->data_p = WorkPtr;
		Input->last_chunk = WorkPtr + 8 + ( (Len + 1) & ~1 );
		if (!strncmp((char *)WorkPtr, name, 4)) {
			return;
		}
	}
	Input->data_p = NULL;		/* Oh forget it! */
}

static void FindChunk(WaveLoadState_t *Input, const char *name)
{
	Input->last_chunk = Input->iff_data;
	FindNextChunk (Input,name);
}


/*
============
GetWavinfo
============
*/
wavinfo_t GetWavinfo (const char *name, unsigned char *wav, int wavlength)
{
	wavinfo_t info;
	int i;
	int format;
	int samples;
	WaveLoadState_t WaveState;
	
	memset (&info, 0, sizeof(info));
	
	if (!wav)
		return info;
	
	WaveState.iff_data = wav;
	WaveState.iff_end = wav + wavlength;
	
	// find "RIFF" chunk
	FindChunk(&WaveState,"RIFF");
	if (!(WaveState.data_p && !strncmp((char *)WaveState.data_p+8, "WAVE", 4))) {
		Com_Printf("Missing RIFF/WAVE chunks\n");
		return info;
	}
	
	// get "fmt " chunk
	WaveState.iff_data = WaveState.data_p + 12;
	
	FindChunk(&WaveState,"fmt ");
	if (!WaveState.data_p) {
		Com_Printf("Missing fmt chunk\n");
		return info;
	}
	WaveState.data_p += 8;
	format = GetLittleShort(&WaveState);
	
	if (format != WAV_PCM_FORMAT && format != WAV_IMA_ADPCM_FORMAT) {
		Com_Printf( "Microsoft PCM or IMA ADPCM format only for %s\n", name );
		return info;
	}
	info.format = format;
	
	info.channels = GetLittleShort(&WaveState);
	info.rate = GetLittleLong(&WaveState);
	// skip over avg bytes per sec
	WaveState.data_p += 4;
	// grab block align
	info.blockalign = GetLittleShort(&WaveState);
	info.width = GetLittleShort(&WaveState) / 8;
	if (info.format == WAV_IMA_ADPCM_FORMAT) {
		// skip over extra size
		WaveState.data_p += 2;
		// grab samples per block
		info.samplesperblock = GetLittleShort(&WaveState);
	}
	
	// get cue chunk
	FindChunk(&WaveState,"cue ");
	if (WaveState.data_p) {
		WaveState.data_p += 32;
		info.loopstart = GetLittleLong(&WaveState);
	
		// if the next chunk is a LIST chunk, look for a cue length marker
		FindNextChunk (&WaveState,"LIST");
		if (WaveState.data_p) {
			if (!strncmp ((char *)WaveState.data_p + 28, "mark", 4)) {	// this is not a proper parse, but it works with cooledit...
				WaveState.data_p += 24;
				i = GetLittleLong (&WaveState);	// samples in loop
				info.samples = info.loopstart + i;
			}
		}
	} else {
		info.loopstart = -1;
	}
	// find data chunk
	FindChunk(&WaveState,"data");
	if (!WaveState.data_p) {
		Com_Printf("Missing data chunk\n");
		return info;
	}
	
	WaveState.data_p += 4;
	samples = GetLittleLong(&WaveState);
	
	if (info.samples) {
		if (samples < info.samples) {
			Com_Error (ERR_DROP, "Sound %s has a bad loop length", name);
		}
	} else {
		info.samples = samples;
	}
	info.dataofs = WaveState.data_p - wav;
	
	return info;
}
	

/*
==============
SoundTimeLength
==============
*/
#define MAX_SOUNDTIMES 64
#define MAX_SOUNDTIME_LENGTH ( 64 - sizeof( float ) )
typedef struct soundtime_t {
	char name[MAX_SOUNDTIME_LENGTH];
	float length;
} soundtime_t;

static soundtime_t soundtimes[MAX_SOUNDTIMES];
static int numsoundtimes = 0;
static int soundtimesptr = 0;

static void AddSoundTime( const char *name, float length )
{
	int i;
	soundtime_t *WorkPtr;

	if (strlen(name) > MAX_SOUNDTIME_LENGTH ) {
		Com_DPrintf(DEVELOPER_MSG_SOUND, "AddSoundTime: sound name too long %s\n", name);
		return;
	}
	
	i = soundtimesptr;
	WorkPtr = &soundtimes[i];
	WorkPtr->length = length;
	strcpy( WorkPtr->name, name );
	if ( ++i >= MAX_SOUNDTIMES ) {
		i = 0;
	}
	soundtimesptr = i;
	if (numsoundtimes < MAX_SOUNDTIMES ) {
		++numsoundtimes;
	}
}

static float FindSoundTime( const char *name )
{
	int i;
	soundtime_t *WorkPtr;
	
	i = numsoundtimes;
	if (i) {
		WorkPtr = soundtimes;
		do {
			if ( !Q_stricmp( name, WorkPtr->name ) ) {
				return WorkPtr->length;
			}
			++WorkPtr;
		} while (--i);
	}
	return -1;
}

float SoundTimeLength ( const char *name )
{
	byte		data[64];
	char		namebuffer[1024];
	wavinfo_t	info;
	float		time;
	int			len;
	FILE *f;

	time = FindSoundTime( name );
	if ( time >= 0 )
	{
		return time;
	}

	// see if it is cached on the client
#ifndef DEDICATED_ONLY
	if ( S_SoundLength( name, &time ) )
	{
		return time;
	}
#endif

	if ( !sound_dir )
	{
		sound_dir = Cvar_Get( "s_sounddir", "sound", 0 );
	}
	Com_sprintf( namebuffer, sizeof(namebuffer), "%s/%s", sound_dir->string, name );

	// look for it in the filesystem or pack files
	FS_FOpenFile(namebuffer, &f);
	if (!f)
	{
		Com_DPrintf(DEVELOPER_MSG_SOUND, "AddSoundTime: sound was not found %s\n", name);
		return 1;
	}

	//
	// only load in the header
	//
//	len = FS_Read(data, 64, f);
	len = FS_FRead(data, 64, 1, f);
	if (len > 64)
		len = 64;

	FS_FCloseFile(f);

	info = GetWavinfo( name, data, len );

	if (info.format == WAV_PCM_FORMAT)
	{
		info.samples /= info.width;
		info.samples /= info.channels;
	}
	else if (info.format == WAV_IMA_ADPCM_FORMAT)
	{
		int numblocks;
		// determine number of blocks
		numblocks = info.samples/info.blockalign;
		// subtrack block headers from real_length
		info.samples -= numblocks*info.channels*sizeof(unsigned);
		info.samples *= 2/info.channels;
	}
	time = ( ( float )info.samples / ( float )info.rate );
	AddSoundTime( name, time );
	return time;
}
