#include "neslib.h"
// NES hardware-dependent functions by Shiru (shiru@mail.ru)
// with improvements by VEG
// Feel free to do anything you want with this code, consider it Public Domain

const char palBrightTable[] = {
	0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,
	0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,
	0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,
	0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,
	0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0a,0x0b,0x0c,0x0f,0x0f,0x0f,
	0x10,0x11,0x12,0x13,0x14,0x15,0x16,0x17,0x18,0x19,0x1a,0x1b,0x1c,0x00,0x00,0x00,
	0x20,0x21,0x22,0x23,0x24,0x25,0x26,0x27,0x28,0x29,0x2a,0x2b,0x2c,0x10,0x10,0x10,
	0x30,0x31,0x32,0x33,0x34,0x35,0x36,0x37,0x38,0x39,0x3a,0x3b,0x3c,0x20,0x20,0x20,
	0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,
	0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,
	0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,
	0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30
};

volatile char OAM_BUF[256];
volatile char PAL_BUF[32];

#pragma align(OAM_BUF, 256)

char NTSC_MODE;
volatile char FRAME_CNT1;
volatile char FRAME_CNT2;
volatile char VRAM_UPDATE;
char *	volatile NAME_UPD_ADR;
volatile char NAME_UPD_ENABLE;
volatile char PAL_UPDATE;
const char *volatile 	PAL_BG_PTR;
const char *volatile 	PAL_SPR_PTR;
volatile char SCROLL_X;
volatile char SCROLL_Y;
char SCROLL_X1;
char SCROLL_Y1;
char PAD_STATE[2];
char PAD_STATEP[2];
char PAD_STATET[2];
volatile char PPU_CTRL_VAR;
char PPU_CTRL_VAR1;
volatile char PPU_MASK_VAR;
char RAND_SEED[2];

int main(void)
{
	ppu.mask = 0;
	nesio.dmc_freq = 0;
	ppu.control = 0;

	char c = ppu.status;
	do {} while (!(ppu.status & 0x80));
	do {} while (!(ppu.status & 0x80));

	nesio.input[1] = 0x40;

	ppu.addr = 0x3f;
	ppu.addr = 0x00;
	for(char i=0; i<32; i++)
		ppu.data = 0x0f;

	ppu.addr = 0x20;
	ppu.addr = 0x00;

	for(unsigned i=0; i<0x1000; i++)
		ppu.data = 0x00;

	char i = 0;
	do {
		((char *)0x200)[i] = 0;
		((char *)0x300)[i] = 0;
		((char *)0x400)[i] = 0;
		((char *)0x500)[i] = 0;
		((char *)0x600)[i] = 0;
		((char *)0x700)[i] = 0;
		i++;
	} while (i);

	pal_bright(4);
	pal_clear();
	oam_clear();

	PPU_CTRL_VAR = 0x80;
	PPU_MASK_VAR = 0x06;

	ppu.control = 0x80;

	RAND_SEED[0] = 0xfd;
	RAND_SEED[1] = 0xfd;

	ppu.scroll = 0x00;
	ppu.scroll = 0x00;
	ppu.oamaddr = 0x00;

	//	detect PAL or NTSC based on Blarg code.

	ppu_wait_nmi();
	
	__asm {
		ldx #52				
		ldy #24
	system_check_wait:
		dex
		bne system_check_wait
		dey
		bne system_check_wait

		lda $2002
		and #$80
		sta NTSC_MODE
	}

	nes_game();

	return 0;
}

__hwinterrupt void nmi(void)
{
	if (PPU_MASK_VAR & 0x18)
	{
		nesio.oamdma = (unsigned)(&OAM_BUF[0]) >> 8;

		if (PAL_UPDATE)
		{
			PAL_UPDATE = 0;
			ppu.addr = 0x3f;
			ppu.addr = 0x00;

			char c = PAL_BG_PTR[PAL_BUF[0]];

			ppu.data = c;
			ppu.data = PAL_BG_PTR[PAL_BUF[1]];
			ppu.data = PAL_BG_PTR[PAL_BUF[2]];
			ppu.data = PAL_BG_PTR[PAL_BUF[3]];

			#pragma unroll(full)
			for(char j=0; j<3; j++)
			{
				ppu.data = c;
				ppu.data = PAL_BG_PTR[PAL_BUF[5 + 4 * j + 0]];
				ppu.data = PAL_BG_PTR[PAL_BUF[5 + 4 * j + 1]];
				ppu.data = PAL_BG_PTR[PAL_BUF[5 + 4 * j + 2]];
			}

			#pragma unroll(full)
			for(char j=0; j<4; j++)
			{
				ppu.data = c;
				ppu.data = PAL_SPR_PTR[PAL_BUF[17 + 4 * j + 0]];
				ppu.data = PAL_SPR_PTR[PAL_BUF[17 + 4 * j + 1]];
				ppu.data = PAL_SPR_PTR[PAL_BUF[17 + 4 * j + 2]];
			}
		}

		if (VRAM_UPDATE)
		{
			VRAM_UPDATE = 0;

			if (NAME_UPD_ENABLE)
				flush_vram_update(NAME_UPD_ADR);
		}

		ppu.addr = 0x00;
		ppu.addr = 0x00;

		ppu.scroll = SCROLL_X;
		ppu.scroll = SCROLL_Y;

		ppu.control = PPU_CTRL_VAR;
	}

	ppu.mask = PPU_MASK_VAR;

	FRAME_CNT1++;
	FRAME_CNT2++;
	if (FRAME_CNT2 == 6)
		FRAME_CNT2 = 0;

	//	jsr FamiToneUpdate
}

void pal_all(const char *data)
{
	for(char i=0; i<32; i++)
		PAL_BUF[i] = data[i];
	PAL_UPDATE++;
}

void pal_bg(const char *data)
{
	for(char i=0; i<16; i++)
		PAL_BUF[i] = data[i];
	PAL_UPDATE++;
}	

void pal_spr(const char *data)
{
	for(char i=0; i<16; i++)
		PAL_BUF[i + 16] = data[i];
	PAL_UPDATE++;	
}

void pal_col(unsigned char index,unsigned char color)
{
	PAL_BUF[index & 0x1f] = color;
	PAL_UPDATE++;
}

void pal_clear(void)
{
	for(char i=0; i<32; i++)
		PAL_BUF[i] = 0x0f;
	PAL_UPDATE++;
}

void pal_spr_bright(unsigned char bright)
{
	PAL_SPR_PTR = palBrightTable + 16 * bright;
	PAL_UPDATE++;
}

void pal_bg_bright(unsigned char bright)
{
	PAL_BG_PTR = palBrightTable + 16 * bright;
	PAL_UPDATE++;	
}



void pal_bright(unsigned char bright)
{
	pal_spr_bright(bright);
	pal_bg_bright(bright);
}

void ppu_off(void)
{
	PPU_MASK_VAR &= 0b11100111;
	ppu_wait_nmi();
}

void ppu_on_all(void)
{
	PPU_MASK_VAR|= 0b00011000;
	ppu_wait_nmi();	
}

void ppu_on_bg(void)
{
	PPU_MASK_VAR |= 0b00001000;
	ppu_wait_nmi();		
}

void ppu_on_spr(void)
{
	PPU_MASK_VAR |= 0b00010000;
	ppu_wait_nmi();			
}

void ppu_mask(unsigned char mask)
{
	PPU_MASK_VAR = mask;
}

unsigned char ppu_system(void)
{
	return NTSC_MODE;
}

unsigned char get_ppu_ctrl_var(void)
{
	return PPU_CTRL_VAR;
}

void set_ppu_ctrl_var(unsigned char var)
{
	PPU_CTRL_VAR = var;
}

void oam_clear(void)
{
	char i = 0;
	do {
		OAM_BUF[i] = 255;	//	off screen not top corner.
		i += 4;
	} while (i);
}

void oam_size(unsigned char size)
{
	if (size & 1)
		PPU_CTRL_VAR |= 0x20;
	else
		PPU_CTRL_VAR &= ~0x20;
}

unsigned char oam_spr(unsigned char x,unsigned char y,unsigned char chrnum,unsigned char attr,unsigned char sprid)
{
	OAM_BUF[sprid + 2] = attr;
	OAM_BUF[sprid + 1] = chrnum;
	OAM_BUF[sprid + 0] = y;
	OAM_BUF[sprid + 3] = x;
	return sprid + 4;
}

unsigned char oam_meta_spr(unsigned char x,unsigned char y,unsigned char sprid,const unsigned char *data)
{
	char i = 0;
	while (data[i] != 0x80)
	{
		OAM_BUF[sprid + 3] = x + data[i + 0];
		OAM_BUF[sprid + 0] = y + data[i + 1];
		OAM_BUF[sprid + 1] = data[i + 2];
		OAM_BUF[sprid + 2] = data[i + 3];

		sprid += 4;
		i += 4;
	}
	return sprid;
}


void oam_hide_rest(unsigned char sprid)
{
	do {
		OAM_BUF[sprid] = 240;
		sprid += 4;
	} while (sprid);
}

void ppu_wait_frame(void)
{
	VRAM_UPDATE = 1;
	char c = FRAME_CNT1;
	while (c == FRAME_CNT1) ;
	if (NTSC_MODE)
	{
		while (FRAME_CNT2 == 5) ;
	}
}

void ppu_wait_nmi(void)
{
	VRAM_UPDATE = 1;
	char c = FRAME_CNT1;
	while (c == FRAME_CNT1) ;	
}

void vram_unrle(const unsigned char *data)
{
	char tag = *data++;
	char b;

	for(;;)
	{
		char c = *data++;
		if (c != tag)
		{
			ppu.data = c;
			b = c;
		}
		else
		{
			c = *data++;
			if (!c)
				return;
			while (c)
			{
				ppu.data = b;
				c--;
			}
		}
	}
}

void scroll(unsigned int x,unsigned int y)
{
	char b = (PPU_CTRL_VAR & 0xfc) | ((x >> 8) & 1);

	if (y >= 240)
	{
		y -= 240;
		b |= 2;
	}

	SCROLL_Y = y;
	SCROLL_X = x;

	PPU_CTRL_VAR = b;
}

void split(unsigned int x,unsigned int y)
{
	char b = (PPU_CTRL_VAR & 0xfc) | ((x >> 8) & 1);

	SCROLL_X1 = x;
	PPU_CTRL_VAR1 = b;

	while (ppu.status & 0x40) ;
	while (!(ppu.status & 0x40)) ;

	ppu.scroll = SCROLL_X1;
	ppu.scroll = 0;
	ppu.control = PPU_CTRL_VAR1;
}

void bank_spr(unsigned char n)
{
	if (n & 1)
		PPU_CTRL_VAR |= 0x08;
	else
		PPU_CTRL_VAR &= ~0x08;
}

void bank_bg(unsigned char n)
{
	if (n & 1)
		PPU_CTRL_VAR |= 0x10;
	else
		PPU_CTRL_VAR &= ~0x10;	
}

void vram_read(unsigned char *dst,unsigned int size)
{
	for(unsigned i=size; i!=0; i--)
		*dst++ = ppu.data;	
}

void vram_write(const unsigned char *src,unsigned int size)
{
	for(unsigned i=size; i!=0; i--)
		ppu.data = *src++;	
}

void music_play(unsigned char song)
{
	//_music_play=FamiToneMusicPlay
}

void music_stop(void)
{
	//_music_stop=FamiToneMusicStop

}

void music_pause(unsigned char pause)
{
	//_music_pause=FamiToneMusicPause
}

void sfx_play(unsigned char sound,unsigned char channel)
{
#if 0
_sfx_play:

.if(FT_SFX_ENABLE)

	and #$03
	tax
	lda @sfxPriority,x
	tax
	jsr popa
	jmp FamiToneSfxPlay

@sfxPriority:

	.byte FT_SFX_CH0,FT_SFX_CH1,FT_SFX_CH2,FT_SFX_CH3

.else
	rts
.endif
#endif
}

void sample_play(unsigned char sample)
{
#if 0
.if(FT_DPCM_ENABLE)
_sample_play=FamiToneSamplePlay
.else
_sample_play:
	rts
.endif
#endif
}

unsigned char pad_poll(unsigned char pad)
{
	char buf[3];

	for(char j=0; j<3; j++)
	{
		nesio.input[0] = 1;
		nesio.input[0] = 0;

		char c = 0;
		for(char i=0; i<8; i++)
		{
			c = (c | (nesio.input[pad] << 8)) >> 1;
		}
		buf[j] = c;
	}

	char b = buf[0];
	if (b != buf[1] && b != buf[2])
		b = buf[1];

	PAD_STATE[pad] = b;
	PAD_STATET[pad] = (b ^ PAD_STATEP[pad]) & PAD_STATE[pad];
	PAD_STATEP[pad] = b;

	return b;
}

unsigned char pad_trigger(unsigned char pad)
{
	pad_poll(pad);
	return PAD_STATET[pad];
}

unsigned char pad_state(unsigned char pad)
{
	return PAD_STATE[pad];
}

unsigned char rand1(void)
{
	if (RAND_SEED[0] & 0x80)
	{
		RAND_SEED[0] <<= 1;
		RAND_SEED[0] ^= 0xcf;
	}
	else
		RAND_SEED[0] <<= 1;
	return RAND_SEED[0];
}


unsigned char rand2(void)
{
	if (RAND_SEED[1] & 0x80)
	{
		RAND_SEED[1] <<= 1;
		RAND_SEED[1] ^= 0xd7;
	}
	else
		RAND_SEED[1] <<= 1;
	return RAND_SEED[1];
}

unsigned char rand8(void)
{
	return rand1() + rand2();
}

unsigned int rand16(void)
{
	return (rand1() << 8) | rand2();
}

void set_rand(unsigned seed)
{
	RAND_SEED[0] = seed & 0xff;
	RAND_SEED[1] = seed >> 8;
}

void set_vram_update(unsigned char *buf)
{
	NAME_UPD_ADR = buf;
	NAME_UPD_ENABLE = buf != nullptr;
}

void flush_vram_update(unsigned char *buf)
{
	char i = 0;
	for(;;)
	{
		char c = buf[i++];
		if (c < 0x40)
		{
			ppu.addr = c;
			ppu.addr = buf[i++];
			ppu.data = buf[i++];
		}
		else
		{
			if (c < 0x80)
				ppu.control = PPU_CTRL_VAR & ~0x04;
			else if (c != 0xff)
				ppu.control = PPU_CTRL_VAR | 0x04;
			else
				return;

			ppu.addr = c & 0x3f;
			ppu.addr = buf[i++];
			c = buf[i++];
			do 	{
				ppu.data = buf[i++];
				c--;
			} while (c);
			ppu.control = PPU_CTRL_VAR;
		}
	}
}

void vram_adr(unsigned int addr)
{
	ppu.addr = addr >> 8;
	ppu.addr = addr & 0xff;	
}

void vram_put(unsigned char n)
{
	ppu.data = n;
}

void vram_fill(unsigned char n,unsigned int size)
{
	for(unsigned i=size; i!=0; i--)
		ppu.data = n;		
}

void vram_inc(unsigned char n)
{
	if (n)
		PPU_CTRL_VAR |= 0x04;
	else
		PPU_CTRL_VAR &= ~0x04;
	ppu.control = PPU_CTRL_VAR;
}

void memfill(void *dst,unsigned char value,unsigned int size)
{
	for(unsigned i=size; i!=0; i--)
		*dst++ = value;	
}

unsigned char nesclock(void)
{
	return FRAME_CNT1;
}

void delay(unsigned char frames)
{
	while (frames)
	{
		ppu_wait_nmi();
		frames--;
	}
}

#pragma data(boot)

__export struct Boot
{
	void * nmi, * reset, * irq;
}	boot = {
	nmi,
	(void *)0xff80,
	nullptr
};

#pragma data(data)

