/*
   KallistiOS ##version##
   serpent.c

   Copyright (C)2002,2004 Dan Potter
   Copyright (C)2004 Jim Ursetto
*/

#include <kos.h>
#include <math.h>
#include <assert.h>
#include <plx/matrix.h>
#include <plx/prim.h>
#include <plx/context.h>

/*

This is a copy of the Parallax "bubbles" demo that has been heavily modified
to work with the new mat_transform_sq and vertex DMA. It demonstrates basic
vertex DMA usage as well as showing off interleaved polygon type usage, etc.

*/

// These are in perfmeter.c
void pm_init();
void pm_draw();

/* Draws a sphere out of GL_QUADS according to the syntax of glutSolidSphere

   This now uses triangle strips and PVR Direct Rendering for the best
   possible performance.

   Based very loosely on a routine from James Susinno.
*/
static float phase = 0.0f;
static pvr_poly_cxt_t cxt[2];
static pvr_poly_hdr_t hdr[2];

typedef struct {
	float radius;
	int slices;
	int stacks;
	pvr_vertex_t *data;
} sphere_t;

static void sphere(sphere_t *s) { /* {{{ */
	int	i, j;
	float	pitch, pitch2;
	float	r, g, b;
	float	yaw, yaw2;
	pvr_vertex_t *v;

	v = (pvr_vertex_t *)malloc(s->stacks * (s->slices+2) * sizeof(pvr_vertex_t) + 32);
	v = (pvr_vertex_t *)(((uint32)v & ~31) + 32); /* align to 32 bytes */
 	s->data = v;
	// s->data_trans = (pvr_vertex_t *)malloc(s->stacks * (s->slices+2) * sizeof(pvr_vertex_t));
	/* transformed data -- for testing mat_transform */
	printf("allocated %d bytes for %d stacks, %d + 2 slices, and %d-byte pvr_vertex_t\n",
			s->stacks * (s->slices+2) * sizeof(pvr_vertex_t), s->stacks, s->slices, sizeof(pvr_vertex_t));

	/* Iterate over stacks */
	for ( i=0; i<s->stacks; i++ ) {
		pitch = 2*M_PI * ( (float)i/(float)s->stacks );
		pitch2 = 2*M_PI * ( (float)(i+1)/(float)s->stacks );

		/* Iterate over slices: each entire stack will be one
		   long triangle strip. */
		for ( j=0; j<=s->slices/2; j++ ) {
			yaw = 2*M_PI * ( (float)j/(float)s->slices );
			yaw2 = 2*M_PI * ( (float)(j+1)/(float)s->slices );

			/* x, y+1 */
			v->x = s->radius * fcos( yaw ) * fcos( pitch2 );
			v->y = s->radius * fsin( pitch2 );
			v->z = s->radius * fsin( yaw ) * fcos( pitch2 );

			
			r = 0.75f;
			g = fcos( yaw*2 ) / 3.0f + 0.35f;
			b = 0.0f;
	
			v->argb = plx_pack_color(0.5f, r, g, b);
			v->flags = PVR_CMD_VERTEX;
			v++;


			/* x, y */
			v->x = s->radius * fcos( yaw ) * fcos( pitch );
			v->y = s->radius * fsin( pitch );
			v->z = s->radius * fsin( yaw ) * fcos( pitch );

			//r = 0.5f;
			r = 0.75f;
			g = fsin( yaw*2 ) / 3.0f + 0.35f;
			b = 0.0f;
			// b = fsin( phase + pitch ) / 2.0f + 0.5f;
			
			v->argb = plx_pack_color(0.5f, r, g, b);
			if (j == s->slices/2)
				v->flags = PVR_CMD_VERTEX_EOL;
			else
				v->flags = PVR_CMD_VERTEX;
			v++;
		}
	}
}

static void draw_sphere(sphere_t *s, int list) {
	void * sqd;
	pvr_vertex_t *v, *vd;

	v = s->data;
	assert(v != NULL);

	/* Initialize xmtrx with the values from KGL */
	plx_mat_identity();
	plx_mat3d_apply(PLX_MAT_SCREENVIEW);
	plx_mat3d_apply(PLX_MAT_PROJECTION);
	plx_mat3d_apply(PLX_MAT_MODELVIEW);

	irq_disable();

	/* Put our own polygon header */
	if (list == PVR_LIST_OP_POLY)
		pvr_list_prim(list, &hdr[0], sizeof(hdr[0]));
	else
		pvr_list_prim(list, &hdr[1], sizeof(hdr[1]));

	/* Transform and write vertices to the TA via the store queues */
	vd = (pvr_vertex_t *)pvr_vertbuf_tail(list);
	QACR0 = ((((uint32)vd) >> 26) << 2) & 0x1c;
	QACR1 = ((((uint32)vd) >> 26) << 2) & 0x1c;
	sqd = (void *)
		(0xe0000000 | (((uint32)vd) & 0x03ffffe0));
	/* {
		int o = irq_disable();
		printf("transforming to %p, len %d\n",
			vd, 32 * s->stacks * (s->slices + 2));
		irq_restore(o);
	} */
		
	mat_transform_sq(v, sqd, s->stacks * (s->slices + 2));

	pvr_vertbuf_written(list, 32 * s->stacks * (s->slices + 2));

	irq_enable();
}

#define SPHERE_CNT 32
static float r = 0;
static void sphere_frame() {
	int i;
	uint64 start;
	static sphere_t big_sphere = { 1.2f, 20, 20, NULL };
	static sphere_t small_sphere = { 0.8f, 20, 20, NULL };

	if (!big_sphere.data)
		sphere(&big_sphere);
	if (!small_sphere.data)
		sphere(&small_sphere);

	pvr_wait_ready();
	pvr_scene_begin();

	start = timer_ms_gettime64();

	plx_mat3d_identity();
	plx_mat3d_translate(0.0f, 0.0f, -12.0f);
	plx_mat3d_rotate(r * 2, 0.75f, 1.0f, 0.5f);
	plx_mat3d_push();

	for (i=0; i<SPHERE_CNT; i++) {
		plx_mat3d_translate(6.0f * fcos(i * 2*M_PI / SPHERE_CNT), 0.0f, 6.0f * fsin(i * 2*M_PI / SPHERE_CNT));
		plx_mat3d_rotate(r, 1.0f, 1.0f, 1.0f);
		if (!(i % 2))
			draw_sphere(&big_sphere, PVR_LIST_OP_POLY);
		else
			draw_sphere(&big_sphere, PVR_LIST_TR_POLY);
		if (i < (SPHERE_CNT-1))
			plx_mat3d_peek();
	}
	plx_mat3d_pop();

	plx_mat3d_identity();
	plx_mat3d_translate(0.0f, 0.0f, -12.0f);
	plx_mat3d_rotate(-r * 2, 0.75f, 1.0f, 0.5f);
	plx_mat3d_push();

	for (i=0; i<SPHERE_CNT; i++) {
		plx_mat3d_translate(3.0f * fcos(i * 2*M_PI / SPHERE_CNT), 0.0f, 3.0f * fsin(i * 2*M_PI / SPHERE_CNT));
		plx_mat3d_rotate(r, 1.0f, 1.0f, 1.0f);
		if (!(i % 2))
			draw_sphere(&small_sphere, PVR_LIST_OP_POLY);
		else
			draw_sphere(&small_sphere, PVR_LIST_TR_POLY);
		if (i < (SPHERE_CNT-1))
			plx_mat3d_peek();
	}
	plx_mat3d_pop();

	// Fake spending more time on vertices
	//timer_spin_sleep(15);

	//printf("%d\n", (uint32)(timer_ms_gettime64() - start));

	r++;
	phase += 2*M_PI / 240.0f;

	pm_draw();

	pvr_scene_finish();
}

void do_sphere_test() {
	pvr_set_bg_color(0.2f, 0.0f, 0.4f);

	pvr_poly_cxt_col(&cxt[0], PVR_LIST_OP_POLY);
	pvr_poly_cxt_col(&cxt[1], PVR_LIST_TR_POLY);
	cxt[0].gen.culling = PVR_CULLING_NONE;
	cxt[1].gen.culling = PVR_CULLING_NONE;
	pvr_poly_compile(&hdr[0], &cxt[0]);
	pvr_poly_compile(&hdr[1], &cxt[1]);

	for (;;) {
		sphere_frame();

		MAPLE_FOREACH_BEGIN(MAPLE_FUNC_CONTROLLER, cont_state_t, st)
			if (st->buttons & CONT_START)
				return;
		MAPLE_FOREACH_END()
	}
}

pvr_init_params_t params = {
	/* Enable opaque and translucent polygons with binsize 32 */
	{ PVR_BINSIZE_32, PVR_BINSIZE_0, PVR_BINSIZE_32, PVR_BINSIZE_0, PVR_BINSIZE_0 },
	
	/* Vertex buffer size */
	2560*1024,

	/* Vertex DMA enabled */
	1
};

// DMA buffers. This should ideally be in separate memory banks to take
// advantage of greater speed, but this will work for now.
uint8 dmabuffers[2][4*1024*1024] __attribute__((aligned(32)));

extern uint8 romdisk[];
KOS_INIT_ROMDISK(romdisk);

int main(int argc, char **argv) {
	/* Init PVR API */
	pvr_stats_t stats;

	if (pvr_init(&params) < 0)
		return -1;

	pvr_set_vertbuf(PVR_LIST_OP_POLY, dmabuffers[0], 4*1024*1024);
	pvr_set_vertbuf(PVR_LIST_TR_POLY, dmabuffers[1], 4*1024*1024);

	// Escape hatch
	cont_btn_callback(0, CONT_START|CONT_A, (cont_btn_callback_t)arch_exit);

	/* Init matrices */
	plx_mat3d_init();
	plx_mat3d_mode(PLX_MAT_PROJECTION);
	plx_mat3d_identity();
	plx_mat3d_perspective(45.0f, 640.0f / 480.0f, 0.1f, 100.0f);
	plx_mat3d_mode(PLX_MAT_MODELVIEW);

	plx_cxt_init();
	pm_init();

	/* Do the test */
	printf("Serpent sample: press START to exit\n");
	do_sphere_test();

	pvr_get_stats(&stats);
	dbglog(DBG_DEBUG, "3D Stats: %ld vblanks, frame rate ~%f fps, max vertex used %d bytes\n",
			stats.vbl_count, (double)stats.frame_rate, stats.vtx_buffer_used_max);

	return 0;
}

