#include "sfl.h"
#include "sflSoundWav.h"
#include "sflIoObject.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <malloc.h>
#include <assert.h>

#define RIFF_STRING 0x46464952
#define WAVE_STRING 0x45564157
#define FMT_STRING 0x20746D66
#define DATA_STRING 0x61746164

// wavファイルヘッダ
struct sflWavHeader_t {
	unsigned long riff;        // 'RIFF' の 4 文字
	size_t riffHeaderSize;     // ファイルサイズ - 8
	unsigned long type;        // 'WAVE' の 4 文字
	unsigned long formatId;    // 'fmt ' の 4 文字
	size_t formatChunkSize;    // fmt チャンクのバイト数
	short format;              //  フォーマットID PCMなら 1
	unsigned short channels;   // ステレオなら 2
	unsigned long samplerate;  // サンプリングレート
	unsigned long bytePerSec;  // バイト/秒
	unsigned short blockAlign; // ブロックサイズ ステレオ 16 bitなら 16bit * 2 = 4byte
	unsigned short bitsWidth;  // ビット/サンプル 16 bitなら 16
	unsigned long dataId;      // 'data' の 4 文字
	size_t dataChunkSize;      // PCMデータのbyte数
};
typedef struct sflWavHeader_t sflWavHeader;

struct sflSoundWav_t {
	sflSound super;
	sflIoObject *io;
	long totalSamples;
	long outputSamples;
	int dataStart;
};
typedef struct sflSoundWav_t sflSoundWav;

static int sflSoundWavClose(sflSound *self);
static size_t sflSoundWavRead(sflSound *self, void *outbuf, size_t size);
static int sflSoundWavSeek(sflSound *self, int offset, int whence);
static long sflSoundWavTell(sflSound *self);
static sflSound *sflSoundWavClone(sflSound *self);

sflSound *sflSoundWavOpen(const char *filename)
{
	static void *sflSoundWavFunc[] = {
		(void*)sflSoundWavRead,
		(void*)sflSoundWavSeek,
		(void*)sflSoundWavTell,
		(void*)sflSoundWavClose,
		(void*)sflSoundWavClone,
	};

	sflSoundWav *self = malloc(sizeof(sflSoundWav));
	if (!self) {
		DEBUG_PRINTF("malloc failed\n");
		goto error;
	}
	memset(self, 0, sizeof(sflSoundWav));

	self->super.vptr = sflSoundWavFunc;

	int res = 0;
	self->io = sflIoObjectFullBufferedOpen(filename, SFL_IO_READ_ONLY, 0777, &res);
	if (!self->io) {
		DEBUG_PRINTF("can't open %s, 0x%08x\n", filename, res);
		goto error;
	}

	assert(sizeof(sflWavHeader) == 44);

	sflWavHeader header;
	if (self->io->read(self->io, &header, sizeof(header)) != sizeof(header)) {
		DEBUG_PRINTF("file too short\n");
		goto error;
	}

	if (header.riff != RIFF_STRING || 
	    header.type != WAVE_STRING || 
	    header.formatId != FMT_STRING || 
		header.format != 1 || 
		header.channels != 2 ||
		header.samplerate != 44100 ||
		header.blockAlign != 4 ||
		header.bitsWidth != 16 ||
	    header.dataId != DATA_STRING) {
		DEBUG_PRINTF("Unrecognized wav format %s\n", filename);
		goto error;
	}

	self->totalSamples = header.dataChunkSize / header.blockAlign;
	self->outputSamples = 0;
	self->dataStart = sizeof(header);

	return (sflSound*)self;

error:
	if (self) {
		if (self->io)
			self->io->close(self->io);

		free(self);
	}
	return 0;
}

static size_t sflSoundWavRead(sflSound *self, void *outbuf, size_t size)
{
	sflSoundWav *sound = (sflSoundWav*)self;
	long res = sound->io->read(sound->io, outbuf, size * 4) / 4;
	sound->outputSamples += res;
	return res;
}

static int sflSoundWavSeek(sflSound *self, int offset, int whence)
{
	sflSoundWav *sound = (sflSoundWav*)self;
	
	long pos = sound->outputSamples;
	switch (whence) {
		case SEEK_SET:
			pos = offset;
			break;
		case SEEK_CUR:
			pos += offset;
			break;
		case SEEK_END:
			pos = sound->totalSamples + offset;
			break;
		default:
			DEBUG_PRINTF("invalid whence %d\n", whence);
			assert(0);
			return -1;
	}
	if (pos == sound->outputSamples)
		return 0;

	if (pos < 0 || sound->totalSamples < pos) {
		DEBUG_PRINTF("invalid position, offset = %d, whence = %d\n", offset, whence);
		return -1;
	}

	sound->outputSamples = pos;
	return sound->io->lseek32(sound->io, sound->dataStart + sound->outputSamples * 4, SEEK_SET);
}

static long sflSoundWavTell(sflSound *self)
{
	sflSoundWav *sound = (sflSoundWav*)self;
	return sound->outputSamples;
}

static int sflSoundWavClose(sflSound *self)
{
	sflSoundWav *sound = (sflSoundWav*)self;
	int res;
	res = sound->io->close(sound->io);
	if (res < 0)
		DEBUG_PRINTF("io->close failed 0x%08x\n", res);
	
	free(sound);
	return res;
}

static sflSound *sflSoundWavClone(sflSound *self)
{
	sflSoundWav *sound = (sflSoundWav*)self;
	sflSoundWav *clone = malloc(sizeof(sflSoundWav));
	if (!clone) {
		DEBUG_PRINTF("malloc failed\n");
		return 0;
	}

	*clone = *sound;
	clone->io = sound->io->clone(sound->io);
	if (!clone->io) {
		DEBUG_PRINTF("failed\n");
		free(clone);
		return 0;
	}
	
	return (sflSound*)clone;
}

