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

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

#include "pcsxr/psxcommon.h"
#include "pcsxr/misc.h"
#include "pcsxr/r3000a.h"

static struct
{
	unsigned char *rom;
	unsigned romsize;
	unsigned first_load_map;
	unsigned t_addr;
	unsigned t_size;
	unsigned init_pc;
	unsigned init_gp;
	unsigned init_sp;
	unsigned refresh_rate;
} loaderwork = { 0, 0, 1, 0, 0, 0, 0, 0, 0};

static void load_term(void)
{
	if (loaderwork.rom)
	{
		free(loaderwork.rom);
		loaderwork.rom = 0;
	}
	loaderwork.romsize = 0;
	loaderwork.first_load_map = 1;
	loaderwork.t_addr = 0;
	loaderwork.t_size = 0;
	loaderwork.init_pc = 0;
	loaderwork.init_gp = 0;
	loaderwork.init_sp = 0;
	loaderwork.refresh_rate = 0;
}

static int load_init(void)
{
	load_term();
	loaderwork.romsize = 0x200000;
	loaderwork.rom = (unsigned char*)malloc(loaderwork.romsize);
	if (!loaderwork.rom) return XSF_FALSE;
	memset(loaderwork.rom, 0, loaderwork.romsize);
	loaderwork.first_load_map = 1;
	loaderwork.t_addr = 0;
	loaderwork.t_size = 0;
	loaderwork.init_pc = 0;
	loaderwork.init_gp = 0;
	loaderwork.init_sp = 0;
	loaderwork.refresh_rate = 0;
	return XSF_TRUE;
}

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

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


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


static int load_map(int level, unsigned char *udata, unsigned usize)
{
	//unsigned char *iptr;
	//unsigned isize;
	//unsigned char *xptr;
	EXE_HEADER exe_header;
	
	if (usize < 0x800)
		return XSF_FALSE;

	memset(&exe_header, 0, sizeof(EXE_HEADER));
	memcpy(&exe_header, udata, sizeof(EXE_HEADER));

	if (memcmp(exe_header.id, "PS-X EXE", 8))
		return XSF_FALSE;

	exe_header.t_addr &= 0x1fffff;	
	//if (exe_header.t_size < usize - 0x800)
		exe_header.t_size = usize - 0x800;	//Fix many invalid PSFs...
	
	if ((exe_header.t_addr < 0x10000) || (exe_header.t_size > 0x1f0000) || (exe_header.t_addr + exe_header.t_size > 0x200000))
		return XSF_FALSE;

	if (!loaderwork.rom || loaderwork.romsize != 0x200000)
		return XSF_FALSE;

	memcpy(loaderwork.rom + exe_header.t_addr, udata + 0x800, exe_header.t_size);
	
	if (loaderwork.first_load_map)
	{
		loaderwork.first_load_map = 0;
		loaderwork.t_addr = exe_header.t_addr;
		loaderwork.t_size = exe_header.t_size;
		loaderwork.init_pc = exe_header.pc0;
		loaderwork.init_gp = exe_header.gp0;
		loaderwork.init_sp = exe_header.s_addr;
	}

	if (level == 1 && loaderwork.refresh_rate == 0)
	{
		if (!_strnicmp((const char*)udata + 0x71, "Japan", 5)) loaderwork.refresh_rate = 60;
		else if (!_strnicmp((const char*)udata + 0x71, "North America", 13)) loaderwork.refresh_rate = 60;
		else if (!_strnicmp((const char*)udata + 0x71, "Europe", 6)) loaderwork.refresh_rate = 50;
	}

	if (exe_header.t_addr + exe_header.t_size > loaderwork.t_addr + loaderwork.t_size)
	{
		loaderwork.t_size = exe_header.t_addr + exe_header.t_size - loaderwork.t_addr;
	}

	if (loaderwork.t_addr > exe_header.t_addr)
	{	
		loaderwork.t_size += loaderwork.t_addr - exe_header.t_addr;
		loaderwork.t_addr = exe_header.t_addr;
	}
	return XSF_TRUE;
}

static int load_mapz(int level, unsigned char *zdata, unsigned zsize, unsigned zcrc)
{
	int ret;
	int zerr;
	uLongf usize = zsize;
	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;
		}
		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) != 0x01465350)
		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;
}

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 (loaderwork.refresh_rate == 0) 
		loaderwork.refresh_rate = XSFTag::GetInt("_refresh", pfile, bytes, 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_init();
	return load_psf_and_libs(1, pfile, bytes);
}

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;

}

static HMODULE hSPUPlugin = 0;
static unsigned EnableInterpreterCpu = 0;

void xsf_set_extend_param_void(unsigned dwId, const void* lpPtr)
{
	if (!lpPtr)
		return;
	
	switch (dwId)
	{
	case 1:
	{
		hSPUPlugin = *((HMODULE*)lpPtr);
	}
	case 2:
	{
		EnableInterpreterCpu = *((unsigned*)lpPtr);
	}
	default:
		break;
	}
	
}

#if 0
struct drvimpl_buffer
{
	u8 * ptr;
	u32 len;
	u32 fil;
	u32 cur;
};
#endif
struct drvimpl_buffer sound_buffer =
{
	0, 0, 0, 0
};

static void buffer_term(void)
{
	if (sound_buffer.ptr) free(sound_buffer.ptr);
	sound_buffer.ptr = 0;
	sound_buffer.fil = 0;
	sound_buffer.cur = 0;
	sound_buffer.len = 0;
}

static int buffer_init(void)
{
	buffer_term();
	sound_buffer.len = 2 * 2 * 44100 / 5;
	sound_buffer.ptr = (u8 *)malloc(sound_buffer.len);
	if (!sound_buffer.ptr) return XSF_FALSE;
	sound_buffer.fil = 0;
	sound_buffer.cur = 0;
	return XSF_TRUE;
}

extern "C" void SPU_FlushBuffer(u16 * finalWave, unsigned length)
{
	if (length > sound_buffer.len - sound_buffer.fil)
	{
		length = sound_buffer.len - sound_buffer.fil;
	}
	if (length > 0)
	{
		CopyMemory(sound_buffer.ptr + sound_buffer.fil, finalWave, length);
		sound_buffer.fil += length;
	}
}

int Load_PSX_EXE()
{
	if ((loaderwork.t_addr < 0x10000) || (loaderwork.t_size > 0x1f0000) || (loaderwork.t_addr + loaderwork.t_size > 0x200000))
		return XSF_FALSE;

	void * ret = memcpy(PSXM(SWAP32(loaderwork.t_addr|0x80000000)), SWAP32(loaderwork.rom + loaderwork.t_addr),SWAP32(loaderwork.t_size));
	if (!ret)
		return XSF_FALSE;

	psxRegs.pc = SWAP32(loaderwork.init_pc);
	psxRegs.GPR.n.sp = SWAP32(loaderwork.init_sp);
	if (psxRegs.GPR.n.sp == 0)
		psxRegs.GPR.n.sp = 0x801fff00;
	psxRegs.GPR.n.gp = loaderwork.init_gp;

	//Chocobo's Dungeon 2 patch.
	if (PSXMu32(0xbc090) == SWAP32(0x0802f040)) {
		PSXMu32(0xbc090) = SWAP32(0);
		PSXMu32(0xbc094) = SWAP32(0x0802f040);
		PSXMu32(0xbc098) = SWAP32(0);
	}
	//Fix Chocobo's Dungeon 2's Village psf.
	if (PSXMu32(0x106A68) == SWAP32(0x1F016630) && PSXMu32(0x106A6C) == SWAP32(0x0FEEF103))
	{
		PSXMu32(0x106A6C) = SWAP32(0);
	}

	//Crash Bandicoot 2 and 3 patch
	if (PSXMu32(0x118b8) == SWAP32(0x1060fffd)) {
		PSXMu32(0x118b8) = SWAP32(0x1040fffd);
	}

	//Crash Team Racing patch
	if (PSXMu32(0x2571c) == SWAP32(0x0C01D5AA)) {
		PSXMu32(0x2571c) = SWAP32(0x0C00B4E5);
	}
	if (PSXMu32(0x29BA8) == SWAP32(0x24850008) && PSXMu32(0x29BAC) == SWAP32(0xAF840878)) {
		PSXMu32(0x29BA8) = SWAP32(0x00000000);
		PSXMu32(0x29BAC) = SWAP32(0x24850008);
		PSXMu32(0x29BB0) = SWAP32(0xAF840878);
		PSXMu32(0x29BB8) = SWAP32(0xAF8508B0);
	}

	return XSF_TRUE;
}

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

	memset(&Config, 0, sizeof(PcsxConfig));
	if (EnableInterpreterCpu)
		Config.Cpu = CPU_INTERPRETER;
	else
		Config.Cpu = CPU_DYNAREC;

	if (loaderwork.refresh_rate == 50)
		Config.PsxType = PSX_TYPE_PAL;
	else
		Config.PsxType = PSX_TYPE_NTSC;

	//HLE BIOS以外のときはpsxResetの前に呼ばなければならない
	//なぜならpsxReset内でCPUを動かすから
	if (0 > InitSPUplugin(hSPUPlugin))
	{
		//DeinitSPUPluginを呼ぶべき??
		//buffer_term();
		return XSF_FALSE;
	}
	if (!buffer_init())
		return XSF_FALSE;

	psxInit();
	psxReset();	//HLE BIOS以外ではここでcpuが動く
	psxRegs.intCycle[PSXINT_SPUASYNC].cycle = PSXCLK / 44100;
	psxRegs.intCycle[PSXINT_SPUASYNC].sCycle = psxRegs.cycle; //HLE BIOS以外のときのため
	sound_buffer.fil = 0;

	int ret = Load_PSX_EXE();
	load_term();
	if (!ret)
		return XSF_FALSE;

	//HLE BIOSのときはここでInitSPUplugin(hSPUPlugin)を呼んでも良い
	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 = sound_buffer.fil - sound_buffer.cur;
		while (remain == 0)
		{
			sound_buffer.cur = 0;
			sound_buffer.fil = 0;

			psxCpu->Execute();

			remain = sound_buffer.fil - sound_buffer.cur;
		}
		u32 len = remain;
		if (len > bytes)
		{
			len = bytes;
		}
		CopyMemory(des, sound_buffer.ptr + sound_buffer.cur, len);
		bytes -= len;
		des += len;
		sound_buffer.cur += len;
		ret += len;
	}
	return ret;
}
//int xsf_gen(void *pbuffer, unsigned samples){return 0;}//リンカエラー回避
int xsf_gen(void *pbuffer, unsigned samples, double scale)
{//64bit float 化(ついでに音量制御)
    const double k = scale / 0x8000;
	double *dst = (double*)pbuffer;
	while (samples > 0)
	{
		u32 remain = sound_buffer.fil - sound_buffer.cur;
		u32 loop_count = 0;
		while (remain == 0)
		{
            sound_buffer.cur = 0;
            sound_buffer.fil = 0;

			psxCpu->Execute();

			remain = sound_buffer.fil - sound_buffer.cur;
		}
		u32 len = remain / 2;
		if (len > samples * 2)
		{
			len = samples * 2;
		}
        short *src = (short*)(sound_buffer.ptr + sound_buffer.cur);
        for(u32 i = 0; i < len; i++){
            dst[i] = src[i] * k;
        }
        samples -= len / 2;
        dst += len;
        sound_buffer.cur += len*2;
	}
	return 0;
}

void xsf_term(void)
{
	buffer_term();
	psxShutdown();
	DeinitSPUplugin();
	load_term();
}

