#include "string.h"
#include "stdio.h"
#include <stdlib.h>

#include "system.h"

#include "pad.h"

#include "mesh.h"

#include <math3d.h>

#include <floatlib.h>

#include <timer.h>

#include "vu.h"

#include <gsKit.h>

#define QUAD_BUFFER	//quad buffer technic
//See http://www.hsfortuna.pwp.blueyonder.co.uk/buff4/buff4.htm
//and http://www.bringyou.to/games/PS2.htm for details about this technic
//If you want to drop down to double buffering technic for testing,
//coment QUAD_BUFFER above, comment xtop in vu1.vcl, uncomment line below xtop
//Difference is the FLUSH vif-tag : waits for end of vu1 microprogram

#define DEADZONE_JOYPAD		24
//Under that displacement, a dualshock stick will be considered in neutral position
//because when you push stick and release it it never returns completely to zero

extern unsigned char vu1_start  __attribute__((section(".vudata")));
extern unsigned char vu1_end  __attribute__((section(".vudata")));

//converts to screen space
#define cv(x) ((u32)((((float)(x))+2048.0f)*16.0f))

u32	dma_buffer_per_object[48*4]  __attribute__((aligned(16)));
u32	*dma_buffer_per_frame=dma_buffer_per_object+16*4;

extern u32 vifDMAtag[260] __attribute__((aligned(16)));

int verbose=0;

void init_vu1(GSTEXTURE *texture)
{
	u32		*p;
	float		x=0.0f;
	float		y=0.0f;
	u32		z=0; //clears Zbuffer
	float		w=640.0f;
	float		h=450.0f;
	int		tw,th;

	vu1UploadCode(&vu1_start, (&vu1_end - &vu1_start), 0, 1);

	//vu1 memory map:
	
	//per frame (in dma_buffer_per_frame, initialized here)
	//qword #0-31	: const, Gif-Tag (clears ZBuffer, draws performance bar, sets triangle primitive)

	//per object (in dma_buffer_per_object, initialized before drawing batches)
	//qword #0	: const, local screen matrix
	//qword #4	: const, local light matrix
	//qword #8	: const, light direction
	//qword #9	: const, light color
	//qword #10	: const, final 2D scales vector
	//qword #11	: const, final 2D decals vector
	
	//per batch (even time, BASE)
	//qword #16	: input, number of vertices, performance bar length
	//qword #17	: input, Gif-Tag for each batch (nloop=number of vertices)
	//qword #18	: input, all the data (x nloop)

	//per batch (odd time, BASE+OFFSET)
	//qword #512	: input, number of vertices, performance bar length
	//qword #513	: input, Gif-Tag for each batch (nloop=number of vertices)
	//qword #514	: input, all the data (x nloop)

	p=(u32*)(0x30000000|((u32)dma_buffer_per_frame));

	//qword #0-15: Giftag(trianglestrip to clear screen, line for performance, point)

	//manuals : http://www.gameus.co.kr/imsidata/1/%b2%d2%b2%bf%b8%ae/PS2_Hardware_Manual_Set-v6.0-1/
	
	//p151-152 EE user manual (Giftag formats)
	
	//33222222222211111111110000000000 
	//10987654321098765432109876543210
	//                L__________nloop (L:last packet)
	
	*(p++)=0x00000001;	//nloop=1, last packet=0

	//33222222222211111111110000000000
	//10987654321098765432109876543210
	//nregDF_______primP

	*(p++)=0xd0024000;	//nreg=14, P=1(do not ignore prim), DF=0(packed), Prim=4(trianglestrip)
	*(p++)=0xe515151e;	//A+D(0xe), then rgbqa(1), then xyz2(5), etc...
	*(p++)=0x0005151e;	//regs slot #13-15 aren't used

	//p124 GS user manual (TEST_1)

	//33222222222211111111110000000000 
	//10987654321098765432109876543210
	//             MeZMDOfRefalphaFunA
	
	//ZTEST Off
	*(p++)=0x30000; *(p++)=0; //Data=0x30000 (Alphatest=0 FunctionAlphaTest=Never Refalpha=0 OnfailAlphaTest=Keep DestAlphaTest=Off ModeAlphaTest='0 pass' ZTest=On MethodZTest=Always)
	*(p++)=0x00047; *(p++)=0; //Addr=0x00047 (TEST_1 register)

	//p153 EE user manual (packed data formats)

	//clears frame buffer and Z buffer
	*(p++)=0; *(p++)=0; *(p++)=255; *(p++)=0; //R,G,B,A (implicitly Q=1.0f)
	*(p++)=cv(x); *(p++)=cv(y); *(p++)=z; *(p++)=0; //xyz2 coordinate
	*(p++)=0; *(p++)=0; *(p++)=255; *(p++)=0; //R,G,B,A (implicitly Q=1.0f)
	*(p++)=cv(x+2*w); *(p++)=cv(y); *(p++)=z; *(p++)=0; //xyz2 coordinate
	*(p++)=0; *(p++)=0; *(p++)=255; *(p++)=0; //R,G,B,A (implicitly Q=1.0f)
	*(p++)=cv(x); *(p++)=cv(y+2*h); *(p++)=z; *(p++)=0; //xyz2 coordinate

	//ZTEST On
	*(p++)=0x50000; *(p++)=0; //Data=0x50000 (Alphatest=0 FunctionAlphaTest=Never Refalpha=0 OnfailAlphaTest=Keep DestAlphaTest=Off ModeAlphaTest='0 pass' ZTest=On MethodZTest=GreaterOrEqual)
	*(p++)=0x00047; *(p++)=0; //Addr=0x00047 (TEST_1 register)

	//draws performance bar (tells time we spent drawing in previous frame : 100% screen width=100% frame time)
	*(p++)=0x01; *(p++)=0; //Data=0x0001 (Prim=1:line, Shading=0:flat, Texture=0, Fog=0, alphaBlending=0, AA=0, Coords=0:STQ, ctX=0, ?=0:unfixed)
	*(p++)=0x00; *(p++)=0; //Addr=0x0000 (PRIM register)

	*(p++)=255; *(p++)=255; *(p++)=0; *(p++)=0; //R,G,B,A (implicitly Q=1.0f)
	*(p++)=cv(0); *(p++)=cv(100); *(p++)=0; *(p++)=0; //xyz2 coordinate
	*(p++)=255; *(p++)=255; *(p++)=0; *(p++)=0; //R,G,B,A (implicitly Q=1.0f)
	*(p++)=cv(600); *(p++)=cv(100); *(p++)=0; *(p++)=0; //xyz2 coordinate

	*(p++)=0x00008001;	//nloop=1, last packet=1
	*(p++)=0x40000000;	//nreg=14, P=0(ignore prim), DF=0(packed)
	*(p++)=0x0000eeee;	//A+D(0xe), several times
	*(p++)=0x00000000;	//regs slot #4-15 aren't used

	//texture mapping preparation

	//p63 GS user manual
	
	//TEXA
	//TEX0_1
	//TEX1_1 (mipmaplevels automatic base address calculation method: automatic, p61 GS user manual)
	//MIPTBP1_1 (no need to set it up, we will set auto method in TEX1_1)
	//MIPTBP2_1 (no need to set it up, we will set auto method in TEX1_1)

	//p129 GS user manual (TEXA)

	//TEXA (lower dword)
	//33222222222211111111110000000000 
	//10987654321098765432109876543210
	//                E       _____tA0
	
	*(p++)=0; //tA0=0 Expandingmethodforalpha=0(no transparency)
	*(p++)=0; //ta1=0
	*(p++)=0x3b; *(p++)=0; //addr (TEXA register, mainly useful only for RGBA16 and RGB24 formats)
	
	//p125 GS user manual (TEX0_1)

	//TEX0_1 (lower dword)
	//33222222222211111111110000000000 
	//10987654321098765432109876543210
	//tH__tW___Psm___Tbw__________Vram/256
	
	tw=0;
	while ((1<<tw)<texture->Width) tw++;
	th=0;
	while ((1<<th)<texture->Height) th++;
	
	*(p++)=	((th&3)<<30)|
			(tw<<26)|
			(texture->PSM<<20)|
			(texture->TBW<<14)|
			(texture->Vram>>8);

	//TEX0_1 (upper dword)
	//33222222222211111111110000000000 
	//10987654321098765432109876543210
	//cLd__csAMcpSm___________cbPFxC__tH
	*(p++)=(th>>2); //tCc=0:RGB tFx=00:Modulate cbP=0(clut vram addr/256) cpSm=0:PSMCT32 csM=0:CSM1 csA=0 cLd=000
	*(p++)=0x06; *(p++)=0; //addr (TEX0_1 register)

	//TEX1_1 (lower dword)
	//33222222222211111111110000000000 
	//10987654321098765432109876543210
	//           LL         BMinMmXl D
	*(p++)=0x201; //levelofDetailscalculationmethod=1(LOD=K) maXmipmaplevels=0 Magfilter(LOD<0)=0:nearest mInfilter(LOD>=0)=000:nearest Baseaddrcalculationmethod=1:auto L=0, for other method LOD=(log2(1/|Q|)<<L)+K
	//TEX1_1 (upper dword)
	*(p++)=0; //K
	*(p++)=0x14; *(p++)=0; //addr (TEX1_1 register)

	//p116 GS user manual (primitives formats)
	//p36  GS user manual (primitives parameters)

	//33222222222211111111110000000000 
	//10987654321098765432109876543210
	//                     ?XCABFTSPrim

//	*(p++)=0x00; *(p++)=0; //Data=0x0000 (Prim=0:point, Shading=0:flat, Texture=0, Fog=0, alphaBlending=0, AA=0, Coords=0:STQ, ctX=0, ?=0:unfixed)
//	*(p++)=0x02; *(p++)=0; //Data=0x0002 (Prim=2:linestrip, Shading=0:flat, Texture=0, Fog=0, alphaBlending=0, AA=0, Coords=0:STQ, ctX=0, ?=0:unfixed)
//	*(p++)=0x03; *(p++)=0; //Data=0x0003 (Prim=3:triangle, Shading=0:flat, Texture=0, Fog=0, alphaBlending=0, AA=0, Coords=0:STQ, ctX=0, ?=0:unfixed)
	*(p++)=0x113; *(p++)=0; //Data=0x0113 (Prim=3:triangle, Shading=0:flat, Texture=1, Fog=0, alphaBlending=0, AA=0, Coords=1:UV, ctX=0, ?=0:unfixed)
	*(p++)=0x000; *(p++)=0; //Addr=0x0000 (PRIM register)

#ifdef QUAD_BUFFER	
	//p99 EE user manual
	//prepares vu1 double buffering technic (i.e quad buffer technic)
	vu1Set(0x03000000,0); //BASE(0)
	vu1Set(0x02000000,512); //OFFSET(512)
#endif
}


int test(void)
{
	int 			x=320,y=200;
	int			i,dx,dy,n;
	PadButtonStatus		*pPad;
	u32			*p;
	int			perf; //performance bar length (time spent rendering in a frame)
	
	float			aspect=1.78f; //16/9, 4/3=1.33f

	GSTEXTURE		*texture;
	u32			*ptr_dma_buffer;	//prepared vertices(uv,normal,xyz) data
	u32			*vertices;
	u32			dma_size;
	u32			num_vertices;
	u32			num_batches;
	int			frames=0;
	int			max_vertices_per_batch=85;

	int			done=0;

	int			max;

	u32			frame_start_ticks;
	u32			frame_end_ticks;
	u32			loop_start_ticks;

	VECTOR object_position = { 0.00f, 0.00f, 0.00f, 1.00f };
	VECTOR object_rotation = { 0.00f, 0.00f, 0.00f, 1.00f };

	VECTOR camera_position = { 0.00f, 0.00f, 100.00f, 1.00f };
	VECTOR camera_rotation = { 0.00f, 0.00f,   0.00f, 1.00f };

	VECTOR light_direction = { 0.577f,  0.577f,  -0.577f, 0.00f };
	VECTOR light_color     = { 255.00f, 255.00f, 255.00f, 0.00f };

	MATRIX			local_world;
	MATRIX			local_light;
	MATRIX			world_view;
	MATRIX			view_screen;
	MATRIX			local_screen;

	//clear screen
	pb_wait_for_vbl();
	pb_reset(); //do it at beginning of the frame (does nothing on PS2)
	pb_fill(0,0,640,450,0);
	while(pb_finished());
	pb_wait_for_vbl();
	pb_reset(); //do it at beginning of the frame (does nothing on PS2)
	pb_fill(0,0,640,450,0);
	while(pb_finished());
	
	//objects names seem to be limited to 10 chars in 3ds files (truncate)

	vertices=NULL;
	num_vertices=get_mesh_batches("SpaceFight",&vertices,max_vertices_per_batch);
//	num_vertices=get_mesh_batches("Fuselage",&vertices,max_vertices_per_batch);
	if (num_vertices==0) return 0;
	if (vertices==NULL) return 0;
	
	texture=get_mesh_texture("SpaceFight");
//	texture=get_mesh_texture("Fuselage");
	if (texture==NULL)
	{
		free(vertices);
		return 0;
	}

	
	//see ps2sdk teapot sample for a non-vu1 3D demo using same matrices
	//math3d functions use vu0 as coprocessor

	//create the view_screen matrix.
	create_view_screen(view_screen, aspect, -3.00f, 3.00f, -3.00f, 3.00f, 1.00f, 2000.00f);

	max=num_vertices;

	init_vu1(texture); //sets dma_buffer_per_frame contents

	perf=0;

	while(done==0)
	{
		//per frame stuff:
		
 		pb_wait_for_vbl(); //necessary, will handle screen swapping if pb_finished was called before (double buffering technic in gsKit)

		frame_start_ticks=cpu_ticks(); //remember when vblank events occured
		frames++;

		pb_reset(); //do it at beginning of the frame (does nothing on PS2)

		pb_erase_text_screen();

		p=(u32*)(0x30000000|((u32)dma_buffer_per_frame));
		p[13*4]=cv(perf); //performance bar length

		//uploads qwords #16-#47 (per frame stuff : clears ZBuffer, init registers, etc...)
		//uploads qwords #0-#15 (per object stuff : matrices, lights, etc...)
		vu1UploadData(dma_buffer_per_object, 48*16, 0, 1);
		vu1Start(0); //MSCAL(0) (start execution at qword #0)

		//per object stuff:

		//spin the object a bit.
		//object_rotation[0] += 0.008f; while (object_rotation[0] > 3.14f) { object_rotation[0] -= 6.28f; }
		//object_rotation[1] += 0.052f; while (object_rotation[1] > 3.14f) { object_rotation[1] -= 6.28f; }
		object_rotation[1]=(x-640/2)/16.0f;
		while (object_rotation[1] > 3.14f) { object_rotation[1] -= 6.28f; }
		while (object_rotation[1] < -3.14f) { object_rotation[1] += 6.28f; }

		camera_position[0]=0.0f+0*(x-640/2)*10.0f;
		camera_position[2]=500.0f+(y-450/2)*4.0f;

		//create the local_world matrix.
		create_local_world(local_world, object_position, object_rotation);

		//create the local_light matrix.
		create_local_light(local_light, object_rotation);
		//rotate light_dirs with local_light
   
		//create the world_view matrix.
		create_world_view(world_view, camera_position, camera_rotation);

		//create the local_screen matrix.
		create_local_screen(local_screen, local_world, world_view, view_screen);

		//0x30000000 means we bypass cache
		//(mandatory, otherwise use SyncDCache before transferts)
		p=(u32*)(0x30000000|((u32)dma_buffer_per_object)); 
				
		memcpy(p,local_screen,4*4*4); p+=4*4;
		memcpy(p,local_light,4*4*4); p+=4*4;
		memcpy(p,light_direction,1*4*4); p+=4;
		memcpy(p,light_color,1*4*4); p+=4;
		
		*((float *)(p++))=500.0f; //final 2D scales (better apply this at the very end, in order to not disrupt clipping)
		*((float *)(p++))=500.0f;
		*((float *)(p++))=65536.0f;
		*((float *)(p++))=0.0f;

		*((float *)(p++))=2048.0f+640.0f/2; //final 2D decals
		*((float *)(p++))=2048.0f+450.0f/2;
		*((float *)(p++))=65536.0f;
		*((float *)(p++))=0.0f; //unused

		//uploads qwords #0-#11 (per object stuff)
		dma_size=12*16;
		vu1UploadData(dma_buffer_per_object, dma_size, 0, 1);

		//per batch stuff:
		loop_start_ticks=cpu_ticks();

		num_batches=(max+max_vertices_per_batch-1)/max_vertices_per_batch;
		ptr_dma_buffer=(u32 *)vertices;
		//while(num_batches) //partial progressive method (necessary if vu1UploadHugeData is not used)
		{
			if (num_batches>1)
				n=max_vertices_per_batch;
			else
				n=max%max_vertices_per_batch;

			//uploads qwords #0-255 or #512-767 (per batch stuff)
			//dma_size=(1+3*n)*16; //partial progressive method
			dma_size=(num_batches+3*max)*16; //global method (all batches sent at once)
#ifdef QUAD_BUFFER
			//FLG=1 in Unpack Vif-tag (double buffer technic thru Vif1,
			//i.e quad buff technic, BASE set, OFFSET set and xtop used in vu1 code)
			vu1UploadHugeDataCont(ptr_dma_buffer, dma_size, 0x8000, 1);
#else
			vu1UploadHugeDataContFlush(ptr_dma_buffer, dma_size, 0, 0);
#endif
			ptr_dma_buffer+=dma_size/4;
			num_batches--;
		}

		//If global method is used, we can use complete parallelism!
		//Do anything you want here but don't mess up with vu1 & GS
		//We obtain the hand here right at beginning of the frame!
		//Try to see what is longer, the vu1 rendering or EE+vu0
		//and try to optimize both to get same duration at 60fps

		vu1WaitEndOfVu1(); //since we do nothing on EE side, let's force the waiting

		//We have finished rendering with vu1 here
		//You can use gsKit again

		frame_end_ticks=cpu_ticks();

		//let's calculate next performance bar length
		perf=640*(frame_end_ticks-frame_start_ticks)/5000000;

		pb_print("%d verts (vu0:%d%% vu1:%d%% %f v/f max)\n",
			max,
			100*(loop_start_ticks-frame_start_ticks)/5000000,
			100*(frame_end_ticks-loop_start_ticks)/5000000,
			max*5000000.0f/(frame_end_ticks-loop_start_ticks)
			);

verbose=0;
if (verbose) get_mesh_vertices("x",NULL);
verbose=0;	
		pb_draw_text_screen();

		i = PAD_Read ( 0, 0, &pPad );

		if (i&PAD_TRIANGLE) done=1;
		if (i&PAD_SQUARE) max=((max/max_vertices_per_batch)-1)*max_vertices_per_batch;
		if (i&PAD_CIRCLE) max=((max/max_vertices_per_batch)+1)*max_vertices_per_batch;

		if (max<max_vertices_per_batch) max=max_vertices_per_batch;
		if (max>num_vertices) max=num_vertices;

		if (pPad) //p can be null, so always test it!
		{
			dx=pPad->m_RJoyH-128;
			dy=pPad->m_RJoyV-128;

			if (dy<-DEADZONE_JOYPAD) y+=(dy+DEADZONE_JOYPAD)/8;
			if (dy>DEADZONE_JOYPAD) y+=(dy-DEADZONE_JOYPAD)/8;
			if (dx<-DEADZONE_JOYPAD) x+=(dx+DEADZONE_JOYPAD)/8;
			if (dx>DEADZONE_JOYPAD) x+=(dx-DEADZONE_JOYPAD)/8;

			if (x<0) x=0;
			if (y<0) y=0;
			if (x>640) x=640;
			if (y>450) y=450;
		}
	
		while (pb_finished()); //we declare that the frame drawing has been finished
	}

	if (vertices) free(vertices);

	return 0;
}


int main(void)
{
	pb_init();

	while(test());

	pb_kill(0);
	
	return 0;
}

