/**************************************************************************
 *                                                                        *
 *               Copyright (C) 1995, Silicon Graphics, Inc.               *
 *                                                                        *
 *  These coded instructions, statements, and computer programs  contain  *
 *  unpublished  proprietary  information of Silicon Graphics, Inc., and  *
 *  are protected by Federal copyright  law.  They  may not be disclosed  *
 *  to  third  parties  or copied or duplicated in any form, in whole or  *
 *  in part, without the prior written consent of Silicon Graphics, Inc.  *
 *                                                                        *
 *************************************************************************/

/*---------------------------------------------------------------------*
        Copyright (C) 1998 Nintendo. (Originated by SGI)
        
        $RCSfile: main.c,v $
        $Revision: 1.14 $
        $Date: 1999/04/16 09:51:15 $
 *---------------------------------------------------------------------*/

/*
 * File:  main.c
 *
 *
 */

#include <ultra64.h>
#include <PR/ramrom.h>
#include <assert.h>

#include "tile_rect.h"
#include "controller.h"

/*
 *  Textures
 */
#include "cafe.h"
#include "road.h"
#include "world.h"


/*
 * tile rectangle function
 */
void tileRectangle (   
	Vtx **vtx, Gfx **gfx,
	void *tex, u32 txlfmt, u32 txlsz,
	u32 txwd, u32 txht,
	u32 tilewd, u32 tileht,
	f32 x0, f32 y0, f32 z0, char plane );


/*
 * Symbol genererated by "makerom" to indicate the end of the code segment
 * in virtual (and physical) memory
 */
extern char     _codeSegmentEnd[];
extern char     _codeSegmentTextEnd[];
extern char     _codeSegmentTextStart[];

/*
 * Symbols generated by "makerom" to tell us where the static segment is
 * in ROM.
 */
extern char     _staticSegmentRomStart[],
                _staticSegmentRomEnd[];
extern char     _cafeSegmentRomStart[],
                _cafeSegmentRomEnd[];
extern char     _roadSegmentRomStart[],
                _roadSegmentRomEnd[];
extern char     _worldSegmentRomStart[],
                _worldSegmentRomEnd[];

/*
 * Stacks for the threads as well as message queues for synchronization
 * This stack is ridiculously large, and could also be reclaimed once
 * the main thread is started.
 */
u64             bootStack[STACKSIZE / sizeof(u64)];

static void     idle(void *);
static void     mainproc(void *);

static OSThread idleThread;
static u64      idleThreadStack[STACKSIZE / sizeof(u64)];

static OSThread mainThread;
static u64      mainThreadStack[STACKSIZE / sizeof(u64)];

/*
 * this number (the depth of the message queue) needs to be equal
 * * to the maximum number of possible overlapping PI requests.
 * * For this app, 1 or 2 is probably plenty, other apps might
 * * require a lot more.
 */
#define NUM_PI_MSGS     8

static OSMesg   PiMessages[NUM_PI_MSGS];
static OSMesgQueue PiMessageQ;

OSMesgQueue     dmaMessageQ,
                rdpMessageQ,
                retraceMessageQ;
OSMesg          dmaMessageBuf,
                rdpMessageBuf,
                retraceMessageBuf;
OSIoMesg        dmaIOMessageBuf;


/*
 * Dynamic data.
 */
Dynamic         dynamic;


/*
 * Task descriptor.
 */
OSTask          tlist =
{
	M_GFXTASK,               /* task type */
	OS_TASK_DP_WAIT,         /* task flags */
	NULL,                    /* boot ucode pointer (fill in later) */
	0,                       /* boot ucode size (fill in later) */
	NULL,                    /* task ucode pointer (fill in later) */
	SP_UCODE_SIZE,           /* task ucode size */
	NULL,                    /* task ucode data pointer (fill in later) */
	SP_UCODE_DATA_SIZE,      /* task ucode data size */
	&dram_stack[0],          /* task dram stack pointer */
	SP_DRAM_STACK_SIZE8,     /* task dram stack size */
	NULL,                    /* task output buffer ptr (not always used) */
	NULL,                    /* task output buffer size ptr */
	NULL,                    /* task data pointer (fill in later) */
	0,                       /* task data size (fill in later) */
	NULL,                    /* task yield buffer ptr (not used here) */
	0                        /* task yield buffer size (not used here) */
};

Gfx            *glistp;          /* global for test case procs */

/*
 * global variables
 */
static int      draw_buffer = 0;

int             PrintEn = 0;

/*
 *  initialized by tileRectangle
 */
#define ROOMVTXSIZE		4096
#define ROOMDLSIZE		4096

Vtx    roomVtx[ROOMVTXSIZE];
Gfx    roomDL[ROOMDLSIZE];
Vtx    *roomVtxp;
Gfx    *roomDLp;
int    roomNum;    /* number of room to display */

/*
 * Define data for environment 'room'
 *
 */
typedef struct  {
	u16 *map;	/* map pointer */
	u16 wd, ht; /* width and height of map */
	s16 x0, y0, z0;
	s16 x1, y1, z1;
} RoomDef;


/*
 *  Cafe Verona
 */
static RoomDef	Cafe[] = {
	{ RGBA16cv03, 256, 256,  -128, -128, -128,  -128,  128,  128 },/* left */
	{ RGBA16cv01, 256, 256,  -128, -128,  128,   128,  128,  128 },/* front */
	{ RGBA16cv04, 256, 256,   128, -128, -128,   128,  128,  128 },/* right */
	{ RGBA16cv01, 256, 256,  -128, -128, -128,   128,  128, -128 },/* back */
	{ RGBA16cv05, 256, 256,  -128,  128, -128,   128,  128,  128 },/* floor */
	{ RGBA16cv06, 256, 256,  -128, -128, -128,   128, -128,  128 },/* roof */
};

/*
 *  Road
 */
static RoomDef	Road[] = {
	{ RGBA16rd03, 256, 256,  -128, -128, -128,  -128,  128,  128 },/* left */
	{ RGBA16rd01, 256, 256,  -128, -128,  128,   128,  128,  128 },/* front */
	{ RGBA16rd04, 256, 256,   128, -128, -128,   128,  128,  128 },/* right */
	{ RGBA16rd01, 256, 256,  -128, -128, -128,   128,  128, -128 },/* back */
	{ RGBA16rd05, 256, 256,  -128,  128, -128,   128,  128,  128 },/* floor */
	{ RGBA16rd06, 256, 256,  -128, -128, -128,   128, -128,  128 },/* roof */
};

/*
 *  World Map
 */
static RoomDef	World[] = {
	{ RGBA16world03, 256, 256,  -128, -128, -128, -128,  128,  128 },/* left */
	{ RGBA16world01, 256, 256,  -128, -128,  128,  128,  128,  128 },/* front */
	{ RGBA16world04, 256, 256,   128, -128, -128,  128,  128,  128 },/* right */
	{ RGBA16world02, 256, 256,  -128, -128, -128,  128,  128, -128 },/* back */
	{ RGBA16world06, 256, 256,  -128,  128, -128,  128,  128,  128 },/* floor */
	{ RGBA16world05, 256, 256,  -128, -128, -128,  128, -128,  128 },/* roof */
};


OSPiHandle	*handler;

void
boot(void)
{
	/*
	 * notice that you can't call osSyncPrintf() until you set
	 * up an idle thread.
	 */

	osInitialize();

	handler = osCartRomInit();

	osCreateThread(&idleThread, 1, idle, (void *) 0,
				   idleThreadStack + STACKSIZE / sizeof(u64), 10);

	osStartThread(&idleThread);

	/*
	 * never reached 
	 */
}

static void
idle(void *arg)
{
	/*
	 * Initialize video 
	 */
	osCreateViManager(OS_PRIORITY_VIMGR);
	osViSetMode(&osViModeTable[OS_VI_NTSC_LAN1]);
	osViSetSpecialFeatures(OS_VI_DITHER_FILTER_ON | OS_VI_DIVOT_OFF | OS_VI_GAMMA_ON);

	/*
	 * Start PI Mgr for access to cartridge
	 */
	osCreatePiManager((OSPri) OS_PRIORITY_PIMGR, &PiMessageQ, PiMessages,
					  NUM_PI_MSGS);

	/*
	 * Create main thread
	 */
	osCreateThread(&mainThread, 3, mainproc, arg,
				   mainThreadStack + STACKSIZE / sizeof(u64), 10);
	osStartThread(&mainThread);

	/*
	 * Become the idle thread
	 */
	osSetThreadPri(0, 0);

	for (;;) ;
}


/*
 *  Load Texture Segments, create room display list
 */
static void
make_room( char *seg, int room)
{
	int i;
	char plane;
	RoomDef *rd;

	switch(room) {
		case 0:

			dmaIOMessageBuf.hdr.pri      = OS_MESG_PRI_NORMAL;
			dmaIOMessageBuf.hdr.retQueue = &dmaMessageQ;
			dmaIOMessageBuf.dramAddr     = seg;
			dmaIOMessageBuf.devAddr      = (u32)_cafeSegmentRomStart;
			dmaIOMessageBuf.size         = (u32)_cafeSegmentRomEnd-(u32)_cafeSegmentRomStart;

			osEPiStartDma(handler, &dmaIOMessageBuf, OS_READ);
			(void) osRecvMesg(&dmaMessageQ, NULL, OS_MESG_BLOCK);
			rd = Cafe;
			break;

		case 1:

			dmaIOMessageBuf.hdr.pri      = OS_MESG_PRI_NORMAL;
			dmaIOMessageBuf.hdr.retQueue = &dmaMessageQ;
			dmaIOMessageBuf.dramAddr     = seg;
			dmaIOMessageBuf.devAddr      = (u32)_roadSegmentRomStart;
			dmaIOMessageBuf.size         = (u32)_roadSegmentRomEnd-(u32)_roadSegmentRomStart;

			osEPiStartDma(handler, &dmaIOMessageBuf, OS_READ);
			(void) osRecvMesg(&dmaMessageQ, NULL, OS_MESG_BLOCK);
			rd = Road;
			break;

		case 2:

			dmaIOMessageBuf.hdr.pri      = OS_MESG_PRI_NORMAL;
			dmaIOMessageBuf.hdr.retQueue = &dmaMessageQ;
			dmaIOMessageBuf.dramAddr     = seg;
			dmaIOMessageBuf.devAddr      = (u32)_worldSegmentRomStart;
			dmaIOMessageBuf.size         = (u32)_worldSegmentRomEnd-(u32)_worldSegmentRomStart;

			osEPiStartDma(handler, &dmaIOMessageBuf, OS_READ);
			(void) osRecvMesg(&dmaMessageQ, NULL, OS_MESG_BLOCK);
			rd = World;
			break;
	}

	roomVtxp = &(roomVtx[0]);
	roomDLp = &(roomDL[0]);

	/*
	 *  Create environment mapped room
	 */
	for(i = 0; i < 6; i++) {
		if(rd[i].x0 == rd[i].x1)
			plane = 'x';
		else if(rd[i].y0 == rd[i].y1)
			plane = 'y';
		else
			plane = 'z';

		tileRectangle ( &roomVtxp, &roomDLp,
			rd[i].map, G_IM_FMT_RGBA, G_IM_SIZ_16b,
			rd[i].wd, rd[i].ht,
			32, 32,
			rd[i].x0, rd[i].y0, rd[i].z0, plane );

#ifdef DEBUG
#ifndef __MWERKS__
		assert((roomVtxp - roomVtx) < ROOMVTXSIZE);
		assert((roomDLp - roomDL) < ROOMDLSIZE);
#endif
#endif

	}
	gSPEndDisplayList(roomDLp++);
}


/*
 * This is the main routine of the app.
 */
static void
mainproc(void *arg)
{
	OSTask         *tlistp;
	Dynamic        *dynamicp;
	char           *staticSegment, *textureSegment;
	u16     		perspNorm;

#ifdef DEBUG
	int             i;
	char           *ap;
	u32            *argp;
	u32             argbuf[16];

	argp = (u32 *) RAMROM_APP_WRITE_ADDR;
	for (i = 0; i < sizeof(argbuf) / 4; i++, argp++) {
		osEPiReadIo(handler, (u32) argp, &argbuf[i]);	/*
												 * Assume no DMA 
												 */
	}
	/*
	 * Parse the options 
	 */
	ap = (char *) argbuf;
	while (*ap != '\0') {
		while (*ap == ' ')
			ap++;
		if (*ap == '-' && *(ap + 1) == 'p') {
			PrintEn = 1;
			ap += 2;
		}
		else ap++;
	}
#endif

	/*
	 * Setup the message queues
	 */
	osCreateMesgQueue(&dmaMessageQ, &dmaMessageBuf, 1);

	osCreateMesgQueue(&rdpMessageQ, &rdpMessageBuf, 1);
	osSetEventMesg(OS_EVENT_DP, &rdpMessageQ, NULL);

	osCreateMesgQueue(&retraceMessageQ, &retraceMessageBuf, 1);
	osViSetEvent(&retraceMessageQ, NULL, 1);

	/*
	 * Stick the static segment right after the code/data segment
	 */
	staticSegment = _codeSegmentEnd;

	dmaIOMessageBuf.hdr.pri      = OS_MESG_PRI_NORMAL;
	dmaIOMessageBuf.hdr.retQueue = &dmaMessageQ;
	dmaIOMessageBuf.dramAddr     = staticSegment;
	dmaIOMessageBuf.devAddr      = (u32)_staticSegmentRomStart;
	dmaIOMessageBuf.size         = (u32)_staticSegmentRomEnd-(u32)_staticSegmentRomStart;

	osEPiStartDma(handler, &dmaIOMessageBuf, OS_READ);

	/*
	 * Wait for DMA to finish
	 */
	(void) osRecvMesg(&dmaMessageQ, NULL, OS_MESG_BLOCK);

	/*
	 *  DMA initial texture and create display list
	 */
	textureSegment = staticSegment + 
		(u32) _staticSegmentRomEnd - (u32) _staticSegmentRomStart;

	make_room(textureSegment, roomNum);

	initControllers();


	/*
	 * Main game loop
	 */
	while (1) {

		osContStartReadData(&controllerMsgQ);
		ReadController();

		/*
		 * pointers to build the display list.
		 */
		tlistp = &tlist;
		dynamicp = &dynamic;

		guPerspective(&dynamicp->projection, &perspNorm,
					  60, 320.0/240.0, 10, 1000, 1.0);

		guLookAt(&dynamicp->viewing, 
			0, 0, 0,
			0, 0, 128,
			0, -1, 0);

		guRotate(&dynamicp->xrot, CursorY, 1.0, 0.0, 0.0);
		guRotate(&dynamicp->yrot, CursorX, 0.0, 1.0, 0.0);

		glistp = dynamicp->glist;

		/*
		 * Tell RCP where each segment is
		 */
		gSPSegment(glistp++, 0, 0x0);	/*
										 * Physical address segment 
										 */
		gSPSegment(glistp++, STATIC_SEGMENT, OS_K0_TO_PHYSICAL(staticSegment));
		gSPSegment(glistp++, CFB_SEGMENT, OS_K0_TO_PHYSICAL(cfb[draw_buffer]));
		gSPSegment(glistp++, TEXTURE_SEGMENT, OS_K0_TO_PHYSICAL(textureSegment));

		gSPClipRatio(glistp++, FRUSTRATIO_2);

		gSPMatrix(glistp++, OS_K0_TO_PHYSICAL(&(dynamicp->projection)),
				  G_MTX_PROJECTION|G_MTX_LOAD|G_MTX_NOPUSH);
		gSPMatrix(glistp++, OS_K0_TO_PHYSICAL(&(dynamicp->viewing)),
				G_MTX_PROJECTION|G_MTX_MUL|G_MTX_NOPUSH);
		gSPMatrix(glistp++, OS_K0_TO_PHYSICAL(&(dynamicp->xrot)),
				G_MTX_MODELVIEW|G_MTX_LOAD|G_MTX_NOPUSH);
		gSPMatrix(glistp++, OS_K0_TO_PHYSICAL(&(dynamicp->yrot)),
				G_MTX_MODELVIEW|G_MTX_MUL|G_MTX_NOPUSH);

		gSPPerspNormalize(glistp++, perspNorm);

		/*
		 * Initialize RDP state.
		 */
		gSPDisplayList(glistp++, rdpinit_dl);

		/*
		 * Initialize RSP state.
		 */
		gSPDisplayList(glistp++, rspinit_dl);

		/*
		 * Clear color framebuffer.
		 */
		gSPDisplayList(glistp++, clearcfb_dl);

		/*
		 *  Filtering on/off
		 */
		if(FilterEn) {
			gDPSetTextureFilter(glistp++, G_TF_BILERP);
		} else {
			gDPSetTextureFilter(glistp++, G_TF_POINT);
		}

		/*
		 *  Dma new textures and build display list
		 */
		if(ButtonA) {
			roomNum = (roomNum + 1) % 3;
			make_room(textureSegment, roomNum);
		}

		/*
		 *  display the room
		 */
		gSPDisplayList(glistp++, roomDL);

		gDPFullSync(glistp++);
		gSPEndDisplayList(glistp++);

#ifdef DEBUG
#ifndef __MWERKS__
		assert((glistp - dynamicp->glist) < GLIST_LEN);
#endif
#endif

		/*
		 * Build graphics task:
		 *
		 */
		tlistp->t.ucode_boot = (u64 *) rspbootTextStart;
		tlistp->t.ucode_boot_size = (u32) rspbootTextEnd - (u32) rspbootTextStart;

		/*
		 * RSP output over XBUS to RDP: 
		 */
		tlistp->t.ucode = (u64 *) gspF3DEX2_xbusTextStart;
		tlistp->t.ucode_data = (u64 *) gspF3DEX2_xbusDataStart;

		/*
		 * initial display list: 
		 */
		tlistp->t.data_ptr = (u64 *) dynamicp->glist;
		tlistp->t.data_size = (u32) ((glistp - dynamicp->glist) * sizeof(Gfx));

		/*
		 * Write back dirty cache lines that need to be read by the RCP.
		 */
		osWritebackDCache(&dynamic, sizeof(dynamic));

		/*
		 * start up the RSP task
		 */
		osSpTaskStart(tlistp);

		/*
		 * wait for RDP completion 
		 */
		(void) osRecvMesg(&rdpMessageQ, NULL, OS_MESG_BLOCK);

		/*
		 * setup to swap buffers 
		 */
		osViSwapBuffer(cfb[draw_buffer]);

		/*
		 * Make sure there isn't an old retrace in queue 
		 * * (assumes queue has a depth of 1) 
		 */
		if (MQ_IS_FULL(&retraceMessageQ))
			(void) osRecvMesg(&retraceMessageQ, NULL, OS_MESG_BLOCK);

		/*
		 * Wait for Vertical retrace to finish swap buffers 
		 */
		(void) osRecvMesg(&retraceMessageQ, NULL, OS_MESG_BLOCK);
		draw_buffer ^= 1;

	}

}
