#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>
#include <windows.h>

#include <zlib.h>
#include "../xsfc/tagget.h"
#include "../xsfc/drvimpl.h"

#include "vbam/gba/Globals.h"
#include "vbam/gba/Sound.h"
#include "vbam/common/SoundDriver.h"

static struct
{
	unsigned char *rom;
	unsigned romsize;
	unsigned entry;
} loaderwork = { 0, 0, 0 };

static void load_term(void)
{
	if (loaderwork.rom)
	{
		free(loaderwork.rom);
		loaderwork.rom = 0;
	}
	loaderwork.romsize = 0;
	loaderwork.entry = 0;
}

int mapgsf(u8 *d, int l, int &s)
{
	if ((unsigned)l > loaderwork.romsize)
	{
		l = loaderwork.romsize;
	}
	if (l)
	{
		CopyMemory(d, loaderwork.rom, l);
	}
	s = l;
	return l;
}

static DWORD getdwordle(const BYTE *pData)
{
	return pData[0] | (((DWORD)pData[1]) << 8) | (((DWORD)pData[2]) << 16) | (((DWORD)pData[3]) << 24);
}

static int load_map(int level, unsigned char *udata, unsigned usize)
{	
	unsigned char *iptr;
	unsigned isize;
	unsigned char *xptr;
	unsigned xentry = getdwordle(udata + 0);
	unsigned xsize = getdwordle(udata + 8);
	unsigned xofs = getdwordle(udata + 4) & 0x1ffffff;
	if (level == 1)
	{
		loaderwork.entry = xentry;
	}
	{
		iptr = loaderwork.rom;
		isize = loaderwork.romsize;
		loaderwork.rom = 0;
		loaderwork.romsize = 0;
	}
	if (!iptr)
	{
		unsigned rsize = xofs + xsize;
		{
			rsize -= 1;
			rsize |= rsize >> 1;
			rsize |= rsize >> 2;
			rsize |= rsize >> 4;
			rsize |= rsize >> 8;
			rsize |= rsize >> 16;
			rsize += 1;
		}
		iptr = (unsigned char *) malloc(rsize + 10);
		if (!iptr)
			return XSF_FALSE;
		memset(iptr, 0, rsize + 10);
		isize = rsize;
	}
	else if (isize < xofs + xsize)
	{
		unsigned rsize = xofs + xsize;
		{
			rsize -= 1;
			rsize |= rsize >> 1;
			rsize |= rsize >> 2;
			rsize |= rsize >> 4;
			rsize |= rsize >> 8;
			rsize |= rsize >> 16;
			rsize += 1;
		}
		xptr = (unsigned char *) realloc(iptr, xofs + rsize + 10);
		if (!xptr)
		{
			free(iptr);
			return XSF_FALSE;
		}
		iptr = xptr;
		isize = rsize;
	}
	memcpy(iptr + xofs, udata + 12, xsize);
	{
		loaderwork.rom = iptr;
		loaderwork.romsize = isize;
	}
	return XSF_TRUE;
}

static int load_mapz(int level, unsigned char *zdata, unsigned zsize, unsigned zcrc)
{
	int ret;
	int zerr;
	uLongf usize = 16;
	uLongf rsize = usize;
	unsigned char *udata;
	unsigned char *rdata;

	udata = (unsigned char *) malloc(usize);
	if (!udata)
		return XSF_FALSE;

	while (Z_OK != (zerr = uncompress(udata, &usize, zdata, zsize)))
	{
		if (Z_MEM_ERROR != zerr && Z_BUF_ERROR != zerr)
		{
			free(udata);
			return XSF_FALSE;
		}
		if (usize >= 12)
		{
			usize = getdwordle(udata + 8) + 12;
			if (usize < rsize)
			{
				rsize += rsize;
				usize = rsize;
			}
			else
				rsize = usize;
		}
		else
		{
			rsize += rsize;
			usize = rsize;
		}
		free(udata);
		udata = (unsigned char *) malloc(usize);
		if (!udata)
			return XSF_FALSE;
	}

	rdata = (unsigned char *) realloc(udata, usize);
	if (!rdata)
	{
		free(udata);
		return XSF_FALSE;
	}

	if (0)
	{
		unsigned ccrc = crc32(crc32(0L, Z_NULL, 0), rdata, usize);
		if (ccrc != zcrc)
			return XSF_FALSE;
	}

	ret = load_map(level, rdata, usize);
	free(rdata);
	return ret;
}

static int load_psf_one(int level, unsigned char *pfile, unsigned bytes)
{
	unsigned char *ptr = pfile;
	unsigned code_size;
	unsigned resv_size;
	unsigned code_crc;
	if (bytes < 16 || getdwordle(ptr) != 0x22465350)
		return XSF_FALSE;

	resv_size = getdwordle(ptr + 4);
	code_size = getdwordle(ptr + 8);
	code_crc = getdwordle(ptr + 12);

	if (resv_size)
	{
	}

	if (code_size)
	{
		ptr = pfile + 16 + resv_size;
		if (16 + resv_size + code_size > bytes)
			return XSF_FALSE;
		if (!load_mapz(level, ptr, code_size, code_crc))
			return XSF_FALSE;
	}

	return XSF_TRUE;
}

typedef struct
{
	const char *tag;
	size_t taglen;
	int level;
	int found;
} loadlibwork_t;

static int load_psf_and_libs(int level, void *pfile, unsigned bytes);

static XSFTag::enum_callback_returnvalue load_psfcb(void *pWork, const char *pNameTop, const char *pNameEnd, const char *pValueTop, const char *pValueEnd)
{
	loadlibwork_t *pwork = (loadlibwork_t *)pWork;
	XSFTag::enum_callback_returnvalue ret = XSFTag::enum_continue;
	if (pNameEnd - pNameTop == pwork->taglen && !_strnicmp(pNameTop, pwork->tag , pwork->taglen))
	{
		ptrdiff_t l = pValueEnd - pValueTop;
		char *lib = (char*) malloc(l + 1);
		if (!lib)
		{
			ret = XSFTag::enum_break;
		}
		else
		{
			void *libbuf;
			unsigned libsize;
			memcpy(lib, pValueTop, l);
			lib[l] = '\0';
			if (!xsf_get_lib(lib, &libbuf, &libsize))
			{
				ret = XSFTag::enum_break;
			}
			else
			{
				if (!load_psf_and_libs(pwork->level + 1, libbuf, libsize))
					ret = XSFTag::enum_break;
				else
					pwork->found++;
				free(libbuf);
			}
			free(lib);
		}
	}
	return ret;
}

static int load_psf_and_libs(int level, void *pfile, unsigned bytes)
{
	int haslib = 0;
	loadlibwork_t work;

	work.level = level;
	work.tag = "_lib";
	work.taglen = strlen(work.tag);
	work.found = 0;

	if (level <= 10 && XSFTag::Enum(load_psfcb, &work, pfile, bytes) < 0)
		return XSF_FALSE;

	haslib = work.found;

	if (!load_psf_one(level, (unsigned char *)pfile, bytes))
		return XSF_FALSE;

/*	if (haslib) */
	{
		int n = 2;
		do
		{
			char tbuf[16];
#ifdef HAVE_SPRINTF_S
			sprintf_s(tbuf, sizeof(tbuf), "_lib%d", n++);
#else
			sprintf(tbuf, "_lib%d", n++);
#endif
			work.tag = tbuf;
			work.taglen = strlen(work.tag);
			work.found = 0;
			if (XSFTag::Enum(load_psfcb, &work, pfile, bytes) < 0)
				return XSF_FALSE;
		}
		while (work.found);
	}
	return XSF_TRUE;
}

static int load_psf(void *pfile, unsigned bytes)
{
	load_term();

	return load_psf_and_libs(1, pfile, bytes);
}

static u16 getwordle(const unsigned char *pData)
{
	return pData[0] | (((u16)pData[1]) << 8);
}

static struct
{
	u8 * ptr;
	u32 len;
	u32 fil;
	u32 cur;
}
buffer =
{
	0,0,0,0
};

class GSFSoundDriver : public SoundDriver
{
	void freebuffer()
	{
		if (buffer.ptr)
		{
			delete [] buffer.ptr;
			buffer.ptr = 0;
		}
		buffer.len = 0;
		buffer.fil = 0;
		buffer.cur = 0;
	}
public:
	GSFSoundDriver()
	{
	}
	~GSFSoundDriver()
	{
		freebuffer();
	}

	bool init(long sampleRate)
	{
		freebuffer();
		u32 len = (sampleRate / 10) << 2;
		buffer.ptr = new(xsfc::nothrow) u8[len];
		if (!buffer.ptr)
		{
			return false;
		}
		buffer.len = len;
		return true;
	}

	void pause()
	{
	}

	void reset()
	{
	}

	void resume()
	{
	}

	void write(u16 * finalWave, int length)
	{
		if (u32(length) > buffer.len - buffer.fil)
		{
			length = buffer.len - buffer.fil;
		}
		if (length > 0)
		{
			CopyMemory(buffer.ptr + buffer.fil, finalWave, length);
			buffer.fil += length;
		}
	}

	void setThrottle(unsigned short throttle)
	{
	}
};

SoundDriver * systemSoundInit()
{
	return new GSFSoundDriver();
}

static double watof(const wchar_t *p){
	int i = 1;
	int s = 1;
	double r = 0;
	double b = 1;
	while (p && *p)
	{
		switch (*p)
		{
#if ((L'9' - L'0') == 9) && ((L'8' - L'0') == 8) && ((L'7' - L'0') == 7) && ((L'6' - L'0') == 6) && ((L'5' - L'0') == 5) && ((L'4' - L'0') == 4) && ((L'3' - L'0') == 3) && ((L'2' - L'0') == 2) && ((L'1' - L'0') == 1)
		case L'0':	case L'1':	case L'2':	case L'3':	case L'4':
		case L'5':	case L'6':	case L'7':	case L'8':	case L'9':
			r = r * 10.0 + (*p - L'0'); if (!i) b *= 0.1; break;
#else
		case L'0': r = r * 10 + 0; if (!i) b *= 0.1; break;
		case L'1': r = r * 10 + 1; if (!i) b *= 0.1; break;
		case L'2': r = r * 10 + 2; if (!i) b *= 0.1; break;
		case L'3': r = r * 10 + 3; if (!i) b *= 0.1; break;
		case L'4': r = r * 10 + 4; if (!i) b *= 0.1; break;
		case L'5': r = r * 10 + 5; if (!i) b *= 0.1; break;
		case L'6': r = r * 10 + 6; if (!i) b *= 0.1; break;
		case L'7': r = r * 10 + 7; if (!i) b *= 0.1; break;
		case L'8': r = r * 10 + 8; if (!i) b *= 0.1; break;
		case L'9': r = r * 10 + 9; if (!i) b *= 0.1; break;
#endif
		case L'.': i = 0; break;
		case L'-': s = -s; break;
		case L' ': break;
		}
		p++;
	}
	return s * r * b;
}

void xsf_set_extend_param(unsigned dwId, const wchar_t *lpPtr)
{
	if (!lpPtr)
		return;

	if (dwId == 1)
	{
		soundFiltering = float(watof(lpPtr));
	}
	if (dwId == 2)
	{
		soundDeclicking = watof(lpPtr) != 0;
	}
}

static uint32_t dwSampleRate = 44100;
void xsf_set_extend_param_void(unsigned dwId, const void* lpPtr)
{
	if (!lpPtr)
		return;
	
	switch (dwId)
	{
	case 1:
	{
		dwSampleRate = *(uint32_t*)lpPtr;
		if (dwSampleRate < 8000)
			dwSampleRate = 8000;
		else if (dwSampleRate > 192000)
			dwSampleRate = 192000;
		soundSetSampleRate(dwSampleRate);
		break;
	}
	case 2:
		soundInterpolation = *(uint32_t*)lpPtr ? true : false;
		break;
	default:
		break;
	}
	
}

int xsf_start(void *pfile, unsigned bytes)
{
	if (!load_psf(pfile, bytes))
		return XSF_FALSE;

	cpuIsMultiBoot = ((loaderwork.entry >> 24) == 2);

	int size = CPULoadRom(0);

	soundInit();
	soundReset();

	CPUInit((char *)NULL, false);
	CPUReset();

	return XSF_TRUE;
}

extern "C" unsigned long dwChannelMute;
static unsigned long dwChannelMuteOld = 0;

int xsf_gen(void *pbuffer, unsigned samples)
{
	int ret = 0;
	u32 bytes = samples << 2;
	u8 *des = (u8 *)pbuffer;
	while (bytes > 0)
	{
		u32 remain = buffer.fil - buffer.cur;
		while (remain == 0)
		{
			buffer.cur = 0;
			buffer.fil = 0;

			if (dwChannelMuteOld != dwChannelMute)
			{
				soundSetEnable((((dwChannelMute & 0x30) << 4) | (dwChannelMute & 0xf)) ^ 0x30f);
				dwChannelMuteOld = dwChannelMute;
			}
			CPULoop(250000);

			remain = buffer.fil - buffer.cur;
		}
		u32 len = remain;
		if (len > bytes)
		{
			len = bytes;
		}
		CopyMemory(des, buffer.ptr + buffer.cur, len);
		bytes -= len;
		des += len;
		buffer.cur += len;
		ret += len;
	}
	return ret;
}

void xsf_term(void)
{
	CPUCleanUp();
	soundShutdown();
	load_term();
}

