#define __USE_SYSBASE
#include <stdio.h>
#include <stdlib.h>
#include <proto/exec.h>
#include <exec/memory.h>
#include <proto/sndfile.h>

#ifdef __PPC__
#include <powerup/gcclib/powerup_protos.h>
#undef CreatePool
#define CreatePool PPCCreatePool
#undef AllocPooled
#define AllocPooled PPCAllocPooled
#undef FreePooled
#define FreePooled PPCFreePooled
#undef DeletePool
#define DeletePool PPCDeletePool
#undef AddTail
#define AddTail PPCAddTail
#undef Remove
#define Remove PPCRemove
#endif

#ifndef __GNUC__
#include <dos.h>
#endif

struct Library        *SndFileBase = NULL;
static struct MinList *FileList    = NULL;
static APTR            MemPool     = NULL;

typedef struct {
	struct MinNode fn_Node;
	APTR fn_File;
} FileNode;


#ifndef __GNUC__
static int break_cleanup(void)
{
	/* Dummy break function to make atexit() work. :P */
	return 1;
}
#endif

static void exit_cleanup(void)
{
	if (FileList)
	{
		FileNode *node;

		/* We can safely use the for-loop because we don't need to free the nodes as we go, */
		/* just DeletePool() when we are finished... (MemPools rule! ;) ) */

		if (FileList->mlh_TailPred != (struct MinNode *)FileList) for (node = (FileNode *)(FileList->mlh_Head); node->fn_Node.mln_Succ; node = (FileNode *)(node->fn_Node.mln_Succ))
		{
			SF_Close(node->fn_File);
		}
	}

	if (SndFileBase)
	{
		CloseLibrary(SndFileBase);
		SndFileBase = NULL;
	}

	if (MemPool)
	{
		DeletePool(MemPool);
		MemPool = NULL;
	}
}

static void Fail(STRPTR reason)
{
	fprintf(stderr, "%s\n", reason);
	fflush(stderr);
	exit(20);
}

static void InitBase(void)
{
	if (!SndFileBase)
	{
		if (!(SndFileBase = OpenLibrary("sndfile.library",0))) Fail("Unable to open sndfile.library.");

#ifndef __GNUC__
		onbreak(break_cleanup);
#endif
		atexit(exit_cleanup);
	}

	if (!FileList)
	{
		if (!(MemPool = CreatePool(MEMF_ANY | MEMF_CLEAR, sizeof(struct MinList)*4, sizeof(struct MinList)*2))) Fail("Unable to allocate needed memory.");
		if (!(FileList = AllocPooled(MemPool, sizeof(struct MinList)))) Fail("Unable to allocate needed memory.");

		/* Initialize the list */
		FileList->mlh_Head = (struct MinNode *)&FileList->mlh_Tail;
		FileList->mlh_TailPred = (struct MinNode *)&FileList->mlh_Head;
	}
}


size_t sf_get_lib_version(char* buffer, size_t bufferlen)
{
	if (!SndFileBase) InitBase();
	return SF_GetLibVersion(buffer, bufferlen);
}

SNDFILE* sf_open_read(const char *path, SF_INFO *wfinfo)
{
	FileNode *node;

	if (!SndFileBase) InitBase();
	if (!(node = AllocPooled(MemPool, sizeof(FileNode)))) Fail("Unable to allocate needed memory.");
	if ((node->fn_File = SF_OpenRead(path, wfinfo))) AddTail((struct List *)FileList, (struct Node *)node);

	return node->fn_File;
}

SNDFILE* sf_open_write(const char *path, const SF_INFO *wfinfo)
{
	FileNode *node;

	if (!SndFileBase) InitBase();
	if (!(node = AllocPooled(MemPool, sizeof(FileNode)))) Fail("Unable to allocate needed memory.");
	if ((node->fn_File = SF_OpenWrite(path, wfinfo))) AddTail((struct List *)FileList, (struct Node *)node);

	return node->fn_File;
}

int	sf_perror(SNDFILE *sndfile)
{
	int ret;
	char str[256];

	ret = SF_ErrorStr(sndfile, str, sizeof(str));
	printf("%s\n", str);
	return ret;
}

int	sf_error_str(SNDFILE *sndfile, char* str, size_t len)
{
	return SF_ErrorStr(sndfile, str, len);
}

int	sf_error_number(int errnum, char* str, size_t len)
{
	return SF_ErrorNumber(errnum, str, len);
}

size_t sf_get_header_info(SNDFILE *sndfile, char* buffer, size_t bufferlen, size_t offset)
{
	return SF_GetHeaderInfo(sndfile, buffer, bufferlen, offset);
}

int	sf_format_check(const SF_INFO *info)
{
	return SF_FormatCheck(info);
}

double sf_signal_max(SNDFILE *sndfile)
{
	return *SF_SignalMax(sndfile);
}

off_t sf_seek(SNDFILE *sndfile, off_t frames, int whence)
{
	return SF_Seek(sndfile, frames, whence);
}

size_t sf_read_raw(SNDFILE *sndfile, void *ptr, size_t bytes)
{
	return SF_ReadRaw(sndfile, ptr, bytes);
}

size_t sf_write_raw(SNDFILE *sndfile, void *ptr, size_t bytes)
{
	return SF_WriteRaw(sndfile, ptr, bytes);
}
/*
size_t sf_readf_char(SNDFILE *sndfile, char *ptr, size_t frames)
{
	return SF_ReadFChar(sndfile, ptr, frames);
}

size_t sf_writef_char(SNDFILE *sndfile, char *ptr, size_t frames)
{
	return SF_WriteFChar(sndfile, ptr, frames);
}
*/
size_t sf_readf_short(SNDFILE *sndfile, short *ptr, size_t frames)
{
	return SF_ReadFShort(sndfile, ptr, frames);
}

size_t sf_writef_short(SNDFILE *sndfile, short *ptr, size_t frames)
{
	return SF_WriteFShort(sndfile, ptr, frames);
}

size_t sf_readf_int(SNDFILE *sndfile, int *ptr, size_t frames)
{
	return SF_ReadFInt(sndfile, ptr, frames);
}

size_t sf_writef_int(SNDFILE *sndfile, int *ptr, size_t frames)
{
	return SF_WriteFInt(sndfile, ptr, frames);
}

size_t sf_readf_double(SNDFILE *sndfile, double *ptr, size_t frames, int normalize)
{
	return SF_ReadFDouble(sndfile, ptr, frames, normalize);
}

size_t sf_writef_double(SNDFILE *sndfile, double *ptr, size_t frames, int normalize)
{
	return SF_WriteFDouble(sndfile, ptr, frames, normalize);
}
/*
size_t sf_read_char(SNDFILE *sndfile, char *ptr, size_t items)
{
	return SF_ReadChar(sndfile, ptr, items);
}

size_t sf_write_char(SNDFILE *sndfile, char *ptr, size_t items)
{
	return SF_WriteChar(sndfile, ptr, items);
}
*/
size_t sf_read_short(SNDFILE *sndfile, short *ptr, size_t items)
{
	return SF_ReadShort(sndfile, ptr, items);
}

size_t sf_write_short(SNDFILE *sndfile, short *ptr, size_t items)
{
	return SF_WriteShort(sndfile, ptr, items);
}

size_t sf_read_int(SNDFILE *sndfile, int *ptr, size_t items)
{
	return SF_ReadInt(sndfile, ptr, items);
}

size_t sf_write_int(SNDFILE *sndfile, int *ptr, size_t items)
{
	return SF_WriteInt(sndfile, ptr, items);
}

size_t sf_read_double(SNDFILE *sndfile, double *ptr, size_t items, int normalize)
{
	return SF_ReadDouble(sndfile, ptr, items, normalize);
}

size_t sf_write_double(SNDFILE *sndfile, double *ptr, size_t items, int normalize)
{
	return SF_WriteDouble(sndfile, ptr, items, normalize);
}

int	sf_close(SNDFILE *sndfile)
{
	if (FileList->mlh_TailPred != (struct MinNode *)FileList)
	{
		FileNode *node;

		node = (FileNode *)(FileList->mlh_Head);
		while (node->fn_Node.mln_Succ && node->fn_File != sndfile) node = (FileNode *)(node->fn_Node.mln_Succ);

		if (node->fn_File == sndfile)
		{
			Remove((struct Node *)node);
			FreePooled(MemPool, node, sizeof(FileNode));
		}
	}

	return SF_Close(sndfile);
}
