#include <c64/vic.h>
#include <c64/memmap.h>
#include <string.h>
#include <stdlib.h>

static char * const Screen	=	(char *)0xc800;
static char * const Color	=	(char *)0xd800; 
static char * const Hires	=	(char *)0xe000;


// Single particle, with position, veloicty and color pattern, using a next
// index for single linked list
struct Particle
{
	int				px, py, vx, vy;
	char			pat;
	char			next;
};

// Striped storage of particles, using an index for linkage
__striped	Particle	particles[256];

#pragma align(particles, 256)

// Index for used and free list heads
char	pfirst, pfree;

static char * Hirows[25];

static const char setmask[4] = {0xc0, 0x30, 0x0c, 0x03};
static const char clrmask[4] = {0x3f, 0xcf, 0xf3, 0xfc};

// Set a pixel at the given coordinate
void pix_set(char px, char py, char pat)
{
	__assume(px < 160);
	__assume(py < 100);

	// Calculate base position in hires
	char * dp = Hirows[py >> 2] + 2 * (px & ~3);

	// Set two pixels for a square pixel look
	char ly = 2 * (py & 3);
	dp[ly + 1] = dp[ly + 0] |= setmask[px & 3] & pat;
}

// Clear a pixel at the given coordinate
void pix_clr(char px, char py)
{
	__assume(px < 160);
	__assume(py < 100);	

	// Calculate base position in hires
	char * dp = Hirows[py >> 2] + 2 * (px & ~3);

	// Clear two pixels for a square pixel look
	char ly = 2 * (py & 3);
	dp[ly + 1] = dp[ly + 0] &= clrmask[px & 3];
}

// Init free list of particles
void particle_init(void)
{
	// Init address table for hires
	for(int i=0; i<25; i++)
		Hirows[i] = Hires + 320 * i;

	// Init list heads, using index 0 for list termination
	pfirst = 0;
	pfree = 1;

	// Link all particles in free list
	for(int i=1; i<255; i++)
		particles[i].next = i + 1;	
}

// Add a particle to the list
void particle_add(int px, int py, int vx, int vy, char pat)
{
	// Check if we have a particle left
	if (pfree)
	{
		// Use "auto" to generate a striped pointer
		char	i = pfree;
		auto	p = particles + pfree;

		// Remove from free list
		pfree = p->next;
		p->next = pfirst;

		// Add to used list
		pfirst = i;

		// Init particle data
		p->px = px;
		p->py = py;
		p->vx = vx;
		p->vy = vy;
		p->pat = pat;
	}
}

// Move particles in used list
void particle_move(void)
{
	// Start with first particle, remember previous
	// particle for list removal, using indices instead of pointers
	char	i = pfirst, pi = 0;

	// Zero is still list termination
	while (i)
	{
		// Use "auto" to generate a striped pointer
		auto	p = particles + i;

		// Clear previous particle image, using 9.7 fixed point
		pix_clr(p->px >> 7, p->py >> 7);
		
		// Advance position by velocity
		p->px += p->vx;
		p->py += p->vy;

		// Apply gravity
		p->vy += 8;

		// Check if particle is still on screen
		if (p->px < 0 || p->px >= 160 * 128 || p->py < 0 || p->py >= 100 * 128)
		{
			// Particle is offscreen, so we remove it from the used list

			// Remember next particle in used list
			char pn = p->next;

			// Remove from used list
			if (pi)
				particles[pi].next = pn;
			else
				pfirst = pn;

			// Attach to free list			
			p->next = pfree;
			pfree = i;

			// Advance to next particle
			i = pn;
		}
		else
		{
			// Set image at new position
			pix_set(p->px >> 7, p->py >> 7, p->pat);

			// Advance to next particle
			pi = i;
			i = p->next;
		}
	}
}

// Normalized random function
int rnorm(void)
{
	int l0 = (rand() & 0xfff) - 0x800;
	int l1 = (rand() & 0xfff) - 0x800;
	int l2 = (rand() & 0xfff) - 0x800;
	int l3 = (rand() & 0xfff) - 0x800;

	return l0 + l1 + l2 + l3;
}

int main(void)
{
	// Turn off BASIC ROM
	mmap_set(MMAP_NO_BASIC);

	// Install IRQ trampoline
	mmap_trampoline();

	// Turn off kernal ROM
	mmap_set(MMAP_NO_ROM);

	// Switch to hires multicolor mode
	vic_setmode(VICM_HIRES_MC, Screen, Hires);

	// Clear screen
	memset(Screen, 0x78, 1000);
	memset(Color, 0x0e, 1000);
	memset(Hires, 0x00, 8000);

	// Black background
	vic.color_border = 0x00;
	vic.color_back = 0x00;

	// Init particle system
	particle_init();

	char k = 0;
	for(int i=0; i<10000; i++)
	{
		// Advance particles
		particle_move();

		if (k < 25)
		{
			// Add a particle from the left for the first third
			particle_add(4 * 64, 196 * 64, 256 + (rnorm() >> 6), -(384 + (rnorm() >> 6)), 0x55);
		}
		else if (k < 50)
		{
			// Add a particle from the right for the second third
			particle_add(316 * 64, 196 * 64, - (256 + (rnorm() >> 6)), -(384 + (rnorm() >> 6)), 0xaa);
		}
		else if (k < 75)
		{
			// Add a particle from the middle for the final third
			particle_add(160 * 64, 196 * 64, rnorm() >> 6, -(384 + (rnorm() >> 6)), 0xff);
		}
		
		// Advance thirds counter
		k++;
		if (k == 75)
			k = 0;
	}

	return 0;

}
