/*
 * WAV streaming music support. Adapted from ioquake3 with changes.
 *
 * Copyright (C) 1999-2005 Id Software, Inc.
 * Copyright (C) 2005 Stuart Dalton <badcdev@gmail.com>
 * Copyright (C) 2010-2012 O.Sezer <sezero@users.sourceforge.net>
 *
 * 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.,
 * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 *
 */

#include "quakedef.hpp"

#if defined(USE_CODEC_WAVE)
#include "snd_codec.h"
#include "snd_codeci.h"
#include "snd_wave.h"

/*
=================
FGetLittleLong
=================
*/
static int FGetLittleLong (FILE *f)
{
	int		v;
	if (!fread(&v, sizeof(v), 1, f))
		return -1;
	return LittleLong(v);
}

/*
=================
FGetLittleShort
=================
*/
static short FGetLittleShort(FILE *f)
{
	short	v;
	if (!fread(&v, sizeof(v), 1, f))
		return -1;
	return LittleShort(v);
}

/*
=================
WAV_ReadChunkInfo
=================
*/
static int WAV_ReadChunkInfo(FILE *f, char *name)
{
	int len;

	name[4] = 0;

	if (!fread(name, 4, 1, f))
		return -1;

	len = FGetLittleLong(f);
	if (len < 0)
	{
		console::info("WAV: Negative chunk length\n");
		return -1;
	}

	return len;
}

/*
=================
WAV_FindRIFFChunk

Returns the length of the data in the chunk, or -1 if not found
=================
*/
static int WAV_FindRIFFChunk(FILE *f, const char *chunk)
{
	char	name[5];
	int		len;

	while ((len = WAV_ReadChunkInfo(f, name)) >= 0)
	{
		/* If this is the right chunk, return */
		if (!strncmp(name, chunk, 4))
			return len;
		len = ((len + 1) & ~1);	/* pad by 2 . */

		/* Not the right chunk - skip it */
		fseek(f, len, SEEK_CUR);
	}

	return -1;
}

/*
=================
WAV_ReadRIFFHeader
=================
*/
static bool WAV_ReadRIFFHeader(const char *name, FILE *file, snd_info_t *info)
{
	char dump[16];
	int wav_format;
	int fmtlen = 0;

	if (fread(dump, 1, 12, file) < 12 ||
	    strncmp(dump, "RIFF", 4) != 0 ||
	    strncmp(&dump[8], "WAVE", 4) != 0)
	{
		console::info("%s is missing RIFF/WAVE chunks\n", name);
		return false;
	}

	/* Scan for the format chunk */
	if ((fmtlen = WAV_FindRIFFChunk(file, "fmt ")) < 0)
	{
		console::info("%s is missing fmt chunk\n", name);
		return false;
	}

	/* Save the parameters */
	wav_format = FGetLittleShort(file);
	if (wav_format != WAV_FORMAT_PCM)
	{
		console::info("%s is not Microsoft PCM format\n", name);
		return false;
	}

	info->channels = FGetLittleShort(file);
	info->rate = FGetLittleLong(file);
	FGetLittleLong(file);
	FGetLittleShort(file);
	info->bits = FGetLittleShort(file);

	if (info->bits != 8 && info->bits != 16)
	{
		console::info("%s is not 8 or 16 bit\n", name);
		return false;
	}

	info->width = info->bits / 8;
	info->dataofs = 0;

	/* Skip the rest of the format chunk if required */
	if (fmtlen > 16)
	{
		fmtlen -= 16;
		fseek(file, fmtlen, SEEK_CUR);
	}

	/* Scan for the data chunk */
	if ((info->size = WAV_FindRIFFChunk(file, "data")) < 0)
	{
		console::info("%s is missing data chunk\n", name);
		return false;
	}

	if (info->channels != 1 && info->channels != 2)
	{
		console::info("Unsupported number of channels %d in %s\n",
						info->channels, name);
		return false;
	}
	info->samples = (info->size / info->width) / info->channels;
	if (info->samples == 0)
	{
		console::info("%s has zero samples\n", name);
		return false;
	}

	return true;
}

/*
=================
S_WAV_CodecOpenStream
=================
*/
static bool S_WAV_CodecOpenStream(snd_stream_t *stream)
{
	long start = stream->fh.start;

	/* Read the RIFF header */
	/* The file reads are sequential, therefore no need
	 * for the FS_*() functions: We will manipulate the
	 * file by ourselves from now on. */
	if (!WAV_ReadRIFFHeader(stream->name, stream->fh.file, &stream->info))
		return false;

	stream->fh.start = ftell(stream->fh.file); /* reset to data position */
	if (stream->fh.start - start + stream->info.size > stream->fh.length)
	{
		console::info("%s data size mismatch\n", stream->name);
		return false;
	}

	return true;
}

/*
=================
S_WAV_CodecReadStream
=================
*/
int S_WAV_CodecReadStream(snd_stream_t *stream, int bytes, void *buffer)
{
	int remaining = stream->info.size - stream->fh.pos;
	int i, samples;

	if (remaining <= 0)
		return 0;
	if (bytes > remaining)
		bytes = remaining;
	stream->fh.pos += bytes;
	if (!fread(buffer, bytes, 1, stream->fh.file))
		return -1;
	if (stream->info.width == 2)
	{
		samples = bytes / 2;
		for (i = 0; i < samples; i++)
			((short *)buffer)[i] = LittleShort( ((short *)buffer)[i] );
	}
	return bytes;
}

static void S_WAV_CodecCloseStream (snd_stream_t *stream)
{
	S_CodecUtilClose(&stream);
}

static int S_WAV_CodecRewindStream (snd_stream_t *stream)
{
	FS_rewind(&stream->fh);
	return 0;
}

static bool S_WAV_CodecInitialize (void)
{
	return true;
}

static void S_WAV_CodecShutdown (void)
{
}

snd_codec_t wav_codec =
{
	CODECTYPE_WAVE,
	true,	/* always available. */
	"wav",
	S_WAV_CodecInitialize,
	S_WAV_CodecShutdown,
	S_WAV_CodecOpenStream,
	S_WAV_CodecReadStream,
	S_WAV_CodecRewindStream,
	NULL, /* jump */
	S_WAV_CodecCloseStream,
	NULL
};

#endif	/* USE_CODEC_WAVE */

