#include <nds.h>
#include <nds/arm9/console.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>

#include <fat.h>

#include <dswifi9.h>

#include "sys.h"

#include "quake_ipc.h"
#include "fixed_point.h"

#include "ram.h"
#include "ds.h"

//#define USE_EXCEPTION_HANDLER
#define USE_WIFI
//#define WIFI_ON_DEMAND
//#define USE_ADHOC
//#define USE_DEBUGGER
#define USE_3D
//#define TIME_RAM
//#define USE_MP3
//#define USE_QUAKE_CONSOLE
#define FAKE_QUAKE_CONSOLE
#define USE_REAL_TIMEBASE
#define DO_FP_TESTS
#define CAN_DISABLE_QDSOPTS

//#define TEXTURES_HAVE_NAMES
#define TEXTURE_CACHE_IN_VRAM

//#define IPC_IN_HBLANK
//#define IPC_IN_VBLANK
#define IPC_IN_TIMER

#ifdef USE_ADHOC
#include "libwifi/include/messagequeue.h"
#include "libwifi/include/802.11.h"
#include "libwifi/include/lobby.h"
#include "libwifi/include/safe_malloc.h"
#endif



#ifdef USE_DEBUGGER

#include <user_debugger.h>
#define DEBUG_MSG(x) debug_msg(x)

#else
#define DEBUG_MSG(x)
#endif

extern void quake_main (int argc, char **argv);

extern "C" void debug_print_stub(char* string)
{
	printf(string);
}

volatile unsigned int decoder_stopped = 1;
volatile unsigned int in_file_stuff = 0;

#ifdef DO_FP_TESTS
#define FABS(x) ((x) < 0 ? (-(x)) : ((x)))
void test_number(float f, bool fail_expected) 
{ 
	float f1 = fixed_to_float(float_to_fixed(f)); 
	float f2 = fixed_to_float(float_to_fixed_fpu(f)); 
	bool fail = false; 

	if (FABS(f1 - f) > 1.f / (1 << FP_SCALE_SHIFT)) fail = true; 
	if (f1 != f2) fail = true; 

	fail = (fail == !fail_expected);
	if (fail)
	{ 
		printf("%s %f -> %f (%f) / %f (%f)\n", false == fail ? "SUCCESS" : "FAIL", f, f1, FABS(f1 - f), f2, FABS(f2 - f));
		Sys_Error("failed fp tests\n");
	} 
}
#undef FABS

void do_fp_tests(void)
{
	test_number(0.01f, false); 
   test_number(0.1f, false); 
   test_number(128.1f, false); 
   test_number(99999.1f, false); 
   /* biggest representable value */ 
   test_number(float((1 << 18) - 1), false); 
   /* this is expected to fail, since it can't be represented in the fixed point format */ 
   test_number(float((1 << 18)), true); 

   test_number(1e-20, false); 


   test_number(-0.01f, false); 
   test_number(-0.1f, false); 
   test_number(-128.1f, false); 
   test_number(-99999.1f, false); 
   test_number(-float((1 << 18) - 1), false); 
   /* this is expected to work, since it can be represented in the fixed point format (there's one more encoding in negative values)*/ 
   test_number(-float((1 << 18)), false); 

   /* this should fail */ 
   test_number(-float((1 << 18) + 1), true); 
   
   printf("passed fp tests!\n");
//   while(1);
}
#endif

unsigned short *frame_buffer_base;
unsigned short *console_base;
int frame_buffer_width;
int frame_buffer_height;

void drawKeyboard(int tileBase, int mapBase);
void draw_input(unsigned short *);
void set_keyb_show_mode(int mode);

extern bool use_osk;
extern int show_mode;

void enable_keyb(void)
{
	use_osk = true;
	videoSetModeSub(MODE_0_2D | DISPLAY_BG0_ACTIVE | DISPLAY_BG1_ACTIVE);
	SUB_BLEND_AB = (15 << 8) | 13;
}

void disable_keyb(void)
{
	use_osk = false;
	videoSetModeSub(MODE_0_2D | DISPLAY_BG1_ACTIVE);
}

void toggle_keyb(void)
{
	if (use_osk)
	{
		if (show_mode == 0)
			set_keyb_show_mode(1);
		else
		{
			set_keyb_show_mode(0);
			disable_keyb();
		}
	}
	else
		enable_keyb();
}

int blanks = 0;

int arm7_initialised = 0;

void init_arm7(void)
{
	printf("Init ARM7...");
	quake_ipc_9to7_buf = (unsigned char *)malloc(100);
	quake_ipc_7to9_buf = (unsigned char *)malloc(100);
	
	if ((quake_ipc_9to7_buf == NULL) || (quake_ipc_7to9_buf == NULL))
	{
		Sys_Error("couldn\'t alloc ARM9<->ARM7 IPC space\n");
		*(int *)0 = 0;
		while(1);
	}
	
	quake_ipc_9to7_buf = (unsigned char *)((unsigned int)quake_ipc_9to7_buf | 0x400000);
	quake_ipc_7to9_buf = (unsigned char *)((unsigned int)quake_ipc_7to9_buf | 0x400000);
	
//	printf("9: 9->7 %08x 7->9 %08x\n", quake_ipc_9to7_buf, quake_ipc_7to9_buf);
	
	//send the init message
	quake_ipc_9to7->message_type = kInit;
	ipc_set_ready_9to7();
	
	//the ARM7 will now change the message to say it's ready
	ipc_block_ready_9to7();
	
//	printf("9: 7 has begun init\n");
	
	quake_ipc_9to7->message = (unsigned int)quake_ipc_9to7_buf;
	
	//it'll change this when it's read it
	while (quake_ipc_9to7->message != 0xffffffff);
	
//	printf("9: 7 has read 9to7\n");
	
	quake_ipc_9to7->message = (unsigned int)quake_ipc_7to9_buf;
	
	//it'll change this when it's read it
	while (quake_ipc_9to7->message == (unsigned int)quake_ipc_7to9_buf);
	
//	printf("9: 7 has read 7to9\n");
	
	quake_ipc_9to7->message = 0;
	
	arm7_initialised = 1;
	printf("ARM7 is initialised\n");
}

void stop_mp3(void)
{
#ifdef USE_MP3
	printf("stopping track\n");
	ipc_block_ready_9to7();
	
	quake_ipc_9to7->message_type = kStopMP3;
	ipc_set_ready_9to7();
	
	ipc_block_ready_9to7();
	
	while (decoder_stopped == 0);
#endif
}

void play_mp3(unsigned char track)
{
#ifdef USE_MP3
	if (decoder_stopped == 0)
		stop_mp3();
	
	decoder_stopped = 0;
	
	ipc_block_ready_9to7();
	printf("playing track %d\n", track);

	quake_ipc_9to7->message_type = kPlayMP3;
	sprintf((char *)quake_ipc_9to7_buf, "/quake soundtrack/0%d-AudioTrack 0%d.mp3", track, track);
	ipc_set_ready_9to7();
	
	ipc_block_ready_9to7();
#else
	decoder_stopped = 1;
#endif
}

void load_textures(void);

void SCR_CheckDrawCenterString (void);
void SCR_DrawConsole (void);
void Draw_Crosshair(void);

int update_count = 0;

unsigned short *new_palette = NULL;
void ds_loadpalette(unsigned short *palette);
extern int in_menu;


//void vblank_handler(void) __attribute__((section(".itcm"), long_call));
void vblank_handler(void)
{
	update_count++;
	if (update_count > 20)
	{
		drawKeyboard(2, 10);
		update_count = 0;
	}

	if (new_palette != NULL)
	{
		ds_loadpalette(new_palette);
		new_palette = NULL;
	}
	
#ifdef IPC_IN_VBLANK
	handle_ipc();
#endif
	
	if (blanks & 0x1)
		*(u16*)SCREEN_BASE_BLOCK_SUB(9) = 0xf058;
	else
		*(u16*)SCREEN_BASE_BLOCK_SUB(9) = 0xf02b;
	
//	load_textures();

	draw_input((unsigned short *)SCREEN_BASE_BLOCK_SUB(9));
	
	blanks++;
	
#ifdef USE_ADHOC
//	IPC_RcvCompleteCheck() ;
	LOBBY_Update() ;
#endif
}

extern unsigned int super_framecount;
unsigned int hblanks = 0;

//extern "C" void hblank_handler(void) __attribute__((section(".itcm"), long_call));
extern "C" void hblank_handler(void)
{
//	unsigned short vcount = *((volatile unsigned short *)0x4000006);
	hblanks++;
//	if (vcount > 261)
//		hblanks = 0;
#ifdef IPC_IN_HBLANK
	handle_ipc();
#endif
#ifdef USE_ADHOC
	IPC_RcvCompleteCheck() ;
//	LOBBY_Update() ;
#endif
}

double hblank_ms = 1.0 / 15.7343;
unsigned int initial_hblank = -1;
double Sys_FloatTime (void)
{
#ifdef USE_REAL_TIMEBASE
	if (initial_hblank == -1)
		initial_hblank = hblanks;
	return (double)(hblanks - initial_hblank) / (262 * 60);
#else
	static double t;
	
	t += 0.1;
	
	return t;
#endif
}

bool ds_too_much_geometry(void)
{
	if (GFX_VERTEX_RAM_USAGE >= 6144)
		return true;
	
	if (GFX_POLYGON_RAM_USAGE >= 2048)
		return true;
	
	return false;
}

void ds_dcacheflush(void)
{
	DC_FlushAll();
}

float z_tri = 0.0f;
float z_quad = 0.0f;

int flush_val = 2;

float left_move = 0;
float up_move = 0;
float fov = 50;
float x_scale = 160;
float y_scale = 160;
float z_scale = 80;
float z_move = 0;
int palette_address = 0;

void ds_ortho(void)
{
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	glOrtho(-90, 130, -100, 60, 1.0f, 0x7fff);
	glMatrixMode(GL_TEXTURE);
	glIdentity();
	glMatrixMode(GL_MODELVIEW);
}

void ds_perspective(void)
{
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	gluPerspective(fov, 256.0 / 192.0, 1.0f, 0x7fff);
	glMatrixMode(GL_TEXTURE);
	glIdentity();
	glScalef(4, 4, 4);
	glMatrixMode(GL_MODELVIEW);
}

void ds_push_projection(void)
{
//	glMatrixMode(GL_PROJECTION);
//	glPushMatrix();
//	glMatrixMode(GL_MODELVIEW);
}

void ds_pop_projection(void)
{
//	glMatrixMode(GL_PROJECTION);
//	glPopMatrix(1);
//	glMatrixMode(GL_MODELVIEW);
}

bool use_wireframe = false;
void ds_setwireframe(bool wire)
{
	use_wireframe = wire;
}

/* from a thread in gbadev */

#define POLY_FOG  (1<<15) 

#define GL_FOG_ALPHA    (1<<6) 
#define GL_FOG          (1<<7) 
#define GL_FOG_SHIFT(n)   ((n)<<8) 

#define GL_RDLINE_UNDERFLOW   (1<<12) 
#define GL_VERTEX_OVERFLOW    (1<<13) 

#define GFX_FOG_COLOR   (*(vuint32*) 0x04000358) 
#define GFX_FOG_OFFSET  (*(vuint32*) 0x0400035C) 
#define GFX_FOG_TABLE   ((vuint8*)  0x04000360)

void glFogColor(uint8 red, uint8 green, uint8 blue, uint8 alpha) { GFX_FOG_COLOR = (red) | ((green)<<5) | ((blue)<<10) | ((alpha)<<16); } 
void glFogDepth(uint16 depth) { GFX_FOG_OFFSET = depth; }

int use_fog = 0;
int fog_depth = 0x200;
int fog_shift = 2;

void ds_set_fog(int shift, int depth)
{
	fog_shift = shift;
	fog_depth = depth;
}

void ds_enableAA(void)
{
//	glEnable(GL_ANTIALIAS);
}

void ds_enable_3d(bool enable)
{
//	if (enable)
		videoSetMode(MODE_0_3D);
//	else
//		videoSetMode(MODE_0_2D | DISPLAY_BG0_ACTIVE);
}

void ds_reset_render(void)
{
//	printf("gxstat %08x at %d %s\n", GFX_STATUS, __LINE__, __FILE__);
	glReset();
//	printf("gxstat %08x at %d %s\n", GFX_STATUS, __LINE__, __FILE__);
//	printf("vertex %08x %d %s\n", GFX_VERTEX_RAM_USAGE, __LINE__, __FILE__);
//	printf("polyg  %08x %d %s\n", GFX_POLYGON_RAM_USAGE, __LINE__, __FILE__);
//	GFX_STATUS |= 1 << 15;
//	printf("gxstat %08x at %d %s\n", GFX_STATUS, __LINE__, __FILE__);
//	glMatrixMode(GL_PROJECTION);
//	glPopMatrix(1);
//	printf("gxstat %08x at %d %s\n", GFX_STATUS, __LINE__, __FILE__);
//	glMatrixMode(GL_MODELVIEW);
}

unsigned char fog_red = 0xff;
unsigned char fog_green = 0xde;
unsigned char fog_blue = 0xad;

extern unsigned short current_bound;

void ds_begin_render(void)
{
#ifdef USE_3D
//	glReset();
//	printf("gxstat %08x at %d %s\n", GFX_STATUS, __LINE__, __FILE__);
//	printf("vertex %08x %d %s\n", GFX_VERTEX_RAM_USAGE, __LINE__, __FILE__);
//	printf("polyg  %08x %d %s\n", GFX_POLYGON_RAM_USAGE, __LINE__, __FILE__);
	glMatrixMode(GL_PROJECTION);
//	printf("gxstat %08x at %d %s\n", GFX_STATUS, __LINE__, __FILE__);
//	printf("vertex %08x %d %s\n", GFX_VERTEX_RAM_USAGE, __LINE__, __FILE__);
//	printf("polyg  %08x %d %s\n", GFX_POLYGON_RAM_USAGE, __LINE__, __FILE__);
	
//	ds_perspective();
	ds_ortho();
	
//	printf("gxstat %08x at %d %s\n", GFX_STATUS, __LINE__, __FILE__);
//	printf("vertex %08x %d %s\n", GFX_VERTEX_RAM_USAGE, __LINE__, __FILE__);
//	printf("polyg  %08x %d %s\n", GFX_POLYGON_RAM_USAGE, __LINE__, __FILE__);
	
	glMaterialf(GL_AMBIENT, RGB15(0,0,0));
	glMaterialf(GL_DIFFUSE, RGB15(0,0,0));
	glMaterialf(GL_SPECULAR, BIT(15) | RGB15(0,0,0));
	glMaterialf(GL_EMISSION, RGB15(0,0,0));

	//ds uses a table for shinyness..this generates a half-ass one
	glMaterialShinyness();
	
//	printf("gxstat %08x at %d %s\n", GFX_STATUS, __LINE__, __FILE__);
//	printf("vertex %08x %d %s\n", GFX_VERTEX_RAM_USAGE, __LINE__, __FILE__);
//	printf("polyg  %08x %d %s\n", GFX_POLYGON_RAM_USAGE, __LINE__, __FILE__);
	
	glFogColor(fog_red >> 2, fog_green >> 2, fog_blue >> 2, 31);
//	glFogDepth(0x200); 
//	glFogDepth(0x60); 
	int i;
	for (i = 0; i < 32; i++) { 
		GFX_FOG_TABLE[i] = i << 2; 
	}
	
	ds_polyfmt(0, 31, 0);
	glEnable(GL_BLEND);
	glMatrixMode(GL_TEXTURE);
	glIdentity();
//	glScalef(4, 4, 4);
	glMatrixMode(GL_MODELVIEW);
//	glPushMatrix();
	glIdentity();
	
	if (use_fog)
	{
		glFogDepth(fog_depth);
		glEnable((1 << 7) | (GL_FOG_SHIFT(fog_shift)));
	}
	
	glTranslatef(left_move, up_move, z_move);
	glScalef(x_scale, y_scale, z_scale);
	
	current_bound = -1;
#endif
}

void unload_textures_2(void);

#define HBLANK_START_LOAD 150
#define HBLANK_START_LATE_LOAD 200
#define HBLANK_END_LOAD 205

void ds_end_render(void)
{
#ifdef USE_3D
//	glPopMatrix(1);

//	if ((GFX_VERTEX_RAM_USAGE >= 6144) || (GFX_POLYGON_RAM_USAGE >= 2048))
//	{
//		printf("gxstat %08x at %d %s\n", GFX_STATUS, __LINE__, __FILE__);
//		printf("vertex %d %d %s\n", GFX_VERTEX_RAM_USAGE, __LINE__, __FILE__);
//		printf("polyg  %d %d %s\n", GFX_POLYGON_RAM_USAGE, __LINE__, __FILE__);
//	}
	unsigned short *text_map = (unsigned short *)SCREEN_BASE_BLOCK_SUB(9);
	if (GFX_VERTEX_RAM_USAGE >= 6144)
		text_map[32 * 23 + 30] = 0xf056;
	else
		text_map[32 * 23 + 30] = 0xf000;
	
	if (GFX_POLYGON_RAM_USAGE >= 2048)
		text_map[32 * 23 + 31] = 0xf050;
	else
		text_map[32 * 23 + 31] = 0xf000;
		
	while (*((volatile unsigned short *)0x4000006) < HBLANK_START_LOAD);
	
	load_textures();

//	ds_unlock_vram();
//	
//	while (*((volatile unsigned short *)0x4000006) < 214);
//	ds_lock_vram();
	
	//glFlush();
	GFX_FLUSH = flush_val;
	
//	unload_textures_2();
#endif
}

void ds_pushmatrix(void)
{
#ifdef USE_3D
	glPushMatrix();
#endif
}

void ds_popmatrix(void)
{
#ifdef USE_3D
	glPopMatrix(1);
#endif
}

void ds_translatef(float x, float y, float z)
{
#ifdef USE_3D
	glTranslatef(x, y, z);
#endif
}

void ds_scalef(float x, float y, float z)
{
#ifdef USE_3D
	glScalef(x, y, z);
#endif
}

void ds_rotateX(float angle)
{
#ifdef USE_3D
	glRotateX(angle);
#endif
}

void ds_rotateY(float angle)
{
#ifdef USE_3D
	glRotateY(angle);
#endif
}

void ds_rotateZ(float angle)
{
#ifdef USE_3D
	glRotateZ(angle);
#endif
}

void ds_usematrix3x3(float *up, float *right, float *forward)
{
#ifdef USE_3D
	int upf32[3], rightf32[3], forwardf32[3];
	int count;
	
	for (count = 0; count < 3; count++)
	{
		upf32[count] = floattof32(up[count]);
		rightf32[count] = floattof32(right[count]);
		forwardf32[count] = floattof32(forward[count]);
	}
	
//	MATRIX_MULT3x3 = upf32[0];
//	MATRIX_MULT3x3 = upf32[1];
//	MATRIX_MULT3x3 = upf32[2];
//	
//	MATRIX_MULT3x3 = rightf32[0];
//	MATRIX_MULT3x3 = rightf32[1];
//	MATRIX_MULT3x3 = rightf32[2];
//	
//	MATRIX_MULT3x3 = forwardf32[0];
//	MATRIX_MULT3x3 = forwardf32[1];
//	MATRIX_MULT3x3 = forwardf32[2];
	
	MATRIX_MULT3x3 = upf32[0];
	MATRIX_MULT3x3 = rightf32[0];
	MATRIX_MULT3x3 = forwardf32[0];
	
	MATRIX_MULT3x3 = upf32[1];
	MATRIX_MULT3x3 = rightf32[1];
	MATRIX_MULT3x3 = forwardf32[1];
	
	MATRIX_MULT3x3 = upf32[2];
	MATRIX_MULT3x3 = rightf32[2];
	MATRIX_MULT3x3 = forwardf32[2];
#endif
}

void ds_begin_triangle(void)
{
#ifdef USE_3D
	glBegin(GL_TRIANGLE);
#endif
}

void ds_begin_quad(void)
{
#ifdef USE_3D
	glBegin(GL_QUAD);
#endif
}

void ds_begin_triangle_strip(void)
{
#ifdef USE_3D
	glBegin(GL_TRIANGLE_STRIP);
#endif
}

void ds_end(void)
{
#ifdef USE_3D
	glEnd();
#endif
}

void ds_vertex3f(float x, float y, float z)
{
#ifdef USE_3D
	glVertex3f(x, y, z);
#endif
}

void ds_vertex3v16(short int x, short int y, short int z)
{
#ifdef USE_3D
	glVertex3v16(x, y, z);
#endif
}

void ds_color3f(float red, float green, float blue)
{
#ifdef USE_3D
	glColor3f(red, green, blue);
#endif
}

void ds_color3b(unsigned char red, unsigned char green, unsigned char blue)
{
#ifdef USE_3D
	glColor3b(red, green, blue);
#endif
}

void ds_texcoord2t16(short u, short v)
{
#ifdef USE_3D
	glTexCoord2t16(u, v);
#endif
}

void ds_texcoord2f(float u, float v)
{
#ifdef USE_3D
	glTexCoord2f(u, v);
#endif
}

void BindNoTexture(void);
void ResetMenuCache(void);
void Sbar_Init(void);
void SCR_Init(void);

extern int reloading_gui_textures;
int vram_used = 0;
void vram_init(void);
void unload_textures(void);
void ds_resettextures(void)
{
	printf("resetting textures\n");
	unload_textures();
}

int ds_gettexwidth(void)
{
#ifdef USE_3D
	int i;
	glGetInt(GL_GET_TEXTURE_WIDTH, &i);
	
	return i;
#else
	return 0;
#endif
}

int ds_gettexheight(void)
{
#ifdef USE_3D
	int i;
	glGetInt(GL_GET_TEXTURE_HEIGHT, &i);
	
	return i;
#else
	return 0;
#endif
}

extern unsigned int *textures;
extern unsigned int activeTexture;
/**void ds_bindtexture(int id)
{
#ifdef USE_3D
	glBindTexture(0, id);
#endif
}*/

int ds_gentexture(void)
{
#ifdef USE_3D
	int temp_texture;
	glGenTextures(1, &temp_texture);
	return temp_texture;
#endif
}

extern "C" int getNextPaletteSlot(unsigned short count, unsigned char format);

//bool palette_loaded = false;
void ds_schedule_loadpalette(unsigned short *palette)
{
//	if (palette_loaded == false)
		new_palette = palette;
//	palette_loaded = true;
}

void ds_loadpalette(unsigned short *palette)
{
#ifdef USE_3D
	memcpy(BG_PALETTE_SUB, palette, 256 * 2);
	
	if (palette_address == 0)
	{
//		int addr = gluTexLoadPal(palette, 256, GL_RGB256);
		int addr = getNextPaletteSlot(256, GL_RGB256);
		  
		if( addr>=0 )
		{
			vramSetBankF(VRAM_F_LCD);
	 		swiCopy( palette, &VRAM_F[addr>>1] , 256 / 2 | COPY_MODE_WORD);
		 	vramSetBankF(VRAM_F_TEX_PALETTE);
		}
		
		if (addr == -1)
		{
			printf("couldn\'t load palette!");
			*(int *)0 = 0;
		}
		palette_address = addr;
	}
	else
//		glTexLoadPal(palette, 256, palette_address);
	{
		vramSetBankF(VRAM_F_LCD);
 		swiCopy( palette, &VRAM_F[palette_address>>1] , 256 / 2 | COPY_MODE_WORD);
	 	vramSetBankF(VRAM_F_TEX_PALETTE);
	}
		
	glColorTable(GL_RGB256, palette_address);
#endif
}

void ds_lcdswap(int sub_as_main)
{
	if (sub_as_main)
		lcdMainOnBottom();
	else
		lcdMainOnTop();
}


#define HINT_GET_READY 		1
#define HINT_RUNNING_OUT	2
#define HINT_FREE_TIME		3

void ds_hint_arm7(int hint)
{
	ipc_block_ready_9to7();
	
	switch(hint)
	{
		case HINT_GET_READY:
			quake_ipc_9to7->message_type = kGetReady;
			break;
		case HINT_RUNNING_OUT:
			quake_ipc_9to7->message_type = kRunningOut;
			break;
		case HINT_FREE_TIME:
			quake_ipc_9to7->message_type = kFreeTime;
			break;
		default:
			Sys_Error("invalid ARM7 hint, %d!\n", hint);
			break;
	}
	
	ipc_set_ready_9to7();
}

extern "C" void ds_soundsetup(unsigned char vol, unsigned char pan)
{
	setGenericSound(11025, vol, pan, 1);
}

extern "C" void ds_playsound(void *data, int length)
{
	playGenericSound(data, length);
}

void ds_stopallsounds(void)
{
	printf("ARM9: Stopping sounds\n");
	ipc_block_ready_9to7();
	
	quake_ipc_9to7->message_type = kStopAllSounds;
	ipc_set_ready_9to7();
	
	ipc_block_ready_9to7();
}

void ds_reinit_console(unsigned char *font)
{
//#ifndef USE_DEBUGGER
	printf("Changing console font\n");
	unsigned short *ptr = (u16*)CHAR_BASE_BLOCK_SUB(0);
	extern unsigned short *d_8to16table;
 
	for (int new_char = 0; new_char < 256; new_char++)
	{
		int row = new_char >> 4;
		int col = new_char & 15;
		unsigned char *source = font + (row << 10) + (col << 3);
		
		for (int count = 16 * new_char; count < 17 * new_char; count += 2)
		{
			unsigned short new_line = 0;
		
			ptr[count * 2] = source[0] | (source[1] << 8);
			ptr[count * 2 + 1] = source[2] | (source[3] << 8);
			ptr[count * 2 + 2] = source[4] | (source[5] << 8);
			ptr[count * 2 + 3] = source[6] | (source[7] << 8);
			
			source += 128;
		}
	}

//#endif
}

#ifdef USE_WIFI
#define WIFI_REFRESH_TIMER 50
void WifiTimer(void)
{
   Wifi_Timer(WIFI_REFRESH_TIMER);
}

void arm9_synctoarm7()
{ // send fifo message
   REG_IPC_FIFO_TX=0x87654321;
}

// interrupt handler to receive fifo messages from arm7
void arm9_fifo()
{ // check incoming fifo messages
   u32 value = REG_IPC_FIFO_RX;
   if(value == 0x87654321) Wifi_Sync();
}

int wifi_setup_state = 0;
void wifi_go(void)
{
#ifdef WIFI_ON_DEMAND
	if (wifi_setup_state == 0)
	{
		ipc_block_ready_9to7();
		
		quake_ipc_9to7->message_type = kStartWifi;
		ipc_set_ready_9to7();
		
		printf("Wireless init...........");
		
		REG_IPC_FIFO_CR = IPC_FIFO_ENABLE | IPC_FIFO_SEND_CLEAR; // enable & clear FIFO
	
		u32 Wifi_pass= Wifi_Init(WIFIINIT_OPTION_USELED);
		REG_IPC_FIFO_TX=0x12345678;
		REG_IPC_FIFO_TX=Wifi_pass;
		
		printf("success!\n");
		
		*((volatile u16 *)0x0400010E) = 0; // disable timer3
		
		irqSet(IRQ_TIMER3, WifiTimer); // setup timer IRQ
		irqEnable(IRQ_TIMER3);
		irqSet(IRQ_FIFO_NOT_EMPTY, arm9_fifo); // setup fifo IRQ
		irqEnable(IRQ_FIFO_NOT_EMPTY);
		
		REG_IPC_FIFO_CR = IPC_FIFO_ENABLE | IPC_FIFO_RECV_IRQ; // enable FIFO IRQ
	
		Wifi_SetSyncHandler(arm9_synctoarm7); // tell wifi lib to use our handler to notify arm7
	
		TIMER_DATA(3) = TIMER_FREQ_256(WIFI_REFRESH_TIMER);
	 	TIMER_CR(3) = TIMER_ENABLE | TIMER_IRQ_REQ | TIMER_DIV_256;
	
		while(Wifi_CheckInit()==0) { // wait for arm7 to be initted successfully
			while((*((u16 volatile *) 0x04000006)) > 192); // wait for vblank
			while((*((u16 volatile *) 0x04000006)) < 192);
		}
		
		printf("success!\n");
		
		wifi_setup_state = 1;
		
		ipc_block_ready_9to7();
		
		return;
	}
	
	if (wifi_setup_state == 1)
	{	
		printf("Connecting to AP.......");
		
		int i;
		Wifi_AutoConnect(); // request connect
		while(1) {
			i=Wifi_AssocStatus(); // check status
			if(i==ASSOCSTATUS_ASSOCIATED) {
				printf("success!\n");
				break;
			}
			if(i==ASSOCSTATUS_CANNOTCONNECT) {
				Sys_Error("failure!\n");
				break;
			}
		}
		
		wifi_setup_state = 2;
	}

#else
	printf("wireless init...........");
	
	REG_IPC_FIFO_CR = IPC_FIFO_ENABLE | IPC_FIFO_SEND_CLEAR; // enable & clear FIFO
	
	u32 Wifi_pass= Wifi_Init(WIFIINIT_OPTION_USELED);
	REG_IPC_FIFO_TX=0x12345678;
	REG_IPC_FIFO_TX=Wifi_pass;
	
	*((volatile u16 *)0x0400010E) = 0; // disable timer3
	
	irqSet(IRQ_TIMER3, WifiTimer); // setup timer IRQ
	irqEnable(IRQ_TIMER3);
	irqSet(IRQ_FIFO_NOT_EMPTY, arm9_fifo); // setup fifo IRQ
	irqEnable(IRQ_FIFO_NOT_EMPTY);
	
	REG_IPC_FIFO_CR = IPC_FIFO_ENABLE | IPC_FIFO_RECV_IRQ; // enable FIFO IRQ

	Wifi_SetSyncHandler(arm9_synctoarm7); // tell wifi lib to use our handler to notify arm7

	TIMER_DATA(3) = TIMER_FREQ_256(WIFI_REFRESH_TIMER);
 	TIMER_CR(3) = TIMER_ENABLE | TIMER_IRQ_REQ | TIMER_DIV_256;

	while(Wifi_CheckInit()==0) { // wait for arm7 to be initted successfully
		while((*((u16 volatile *) 0x04000006)) > 192); // wait for vblank
		while((*((u16 volatile *) 0x04000006)) < 192);
	}
	
	printf("success!");
	
	printf("Connecting to AP........");
	
	int i;
	Wifi_AutoConnect(); // request connect
	while(1) {
		i=Wifi_AssocStatus(); // check status
		if(i==ASSOCSTATUS_ASSOCIATED) {
			printf("success!");
			break;
		}
		if(i==ASSOCSTATUS_CANNOTCONNECT) {
			Sys_Error("failure!");
			break;
		}
	}
	
#endif
}
#endif

void ds_handle_sleep(void)
{
	printf("ARM9: going to sleep\n");
	
	irqDisable((IRQ_MASK)(IRQ_VBLANK | IRQ_HBLANK));
#ifdef IPC_IN_TIMER
	irqDisable(IRQ_TIMER0);
#endif
	
	REG_IPC_FIFO_TX = 0xabadbabe;
	for (volatile int wait_count = 0; wait_count < 200000; wait_count++);
	REG_IPC_FIFO_CR = REG_IPC_FIFO_CR | (1 << 3);
	
	powerOFF(POWER_ALL);
	
	while (REG_IPC_FIFO_CR&IPC_FIFO_RECV_EMPTY);
		
	unsigned int data = REG_IPC_FIFO_RX;
	
	powerON(POWER_ALL);
	irqEnable((IRQ_MASK)(IRQ_VBLANK | IRQ_HBLANK));
#ifdef IPC_IN_TIMER
	irqEnable(IRQ_TIMER0);
#endif
	
	if (data != 0xabadbabe)
	{
		printf("woke with the wrong message, %08x\n", data);
		*(int *)0 = 0;
		while(1);
	}
	
//	REG_IPC_FIFO_TX = 0xabadbabe;
//	for (volatile int wait_count = 0; wait_count < 200000; wait_count++);
//	REG_IPC_FIFO_CR = REG_IPC_FIFO_CR | (1 << 3);
	
	printf("ARM9: awake\n");
}

void time_ram(const char * const name, void *address, volatile unsigned short *timer);

//char *msg = 0 ;
//
//int recved = 0, sent = 0 ;
//
//void receiveText(unsigned char *data, int length, LPLOBBY_USER from)
//{
//	if (msg) free(msg) ;
//	msg = (char *)safe_malloc(length) ; // strdup((char *)data) ;
//	strcpy(msg, (const char *)data) ;
//	recved ++ ;
//}

int real_main(int argc, unsigned char **argv)
{
	bool disable_qdsopts = false;
	
	powerON(POWER_ALL);
	
	REG_IPC_FIFO_CR = IPC_FIFO_ENABLE | IPC_FIFO_SEND_CLEAR;
  
#ifndef USE_3D
	// Use the main screen for output
 	videoSetMode(MODE_FB0 | DISPLAY_BG0_ACTIVE);
 	vramSetBankA(VRAM_A_LCD);
#else
    
//    videoSetMode(MODE_0_2D | DISPLAY_BG0_ACTIVE);
// 	vramSetBankA(VRAM_A_MAIN_BG);
 	
 	BG0_CR = BG_MAP_BASE(31);
 	ds_enable_3d(true);
 	
 	vramSetBankA(VRAM_A_TEXTURE);
 	vramSetBankB(VRAM_B_TEXTURE);
 	vramSetBankC(VRAM_C_TEXTURE);
 	vramSetBankD(VRAM_D_TEXTURE);
//	vramSetBankC(VRAM_C_ARM7);
//	vramSetBankD(VRAM_D_ARM7);
#endif
	
#ifdef USE_QUAKE_CONSOLE
	videoSetModeSub(MODE_3_2D | DISPLAY_BG3_ACTIVE);
	vramSetBankH(VRAM_H_SUB_BG);
	SUB_BG3_CR = BG_BMP16_128x128 | BG_BMP_BASE(0);
	
	SUB_BG3_XDX = 1<<7;
 	SUB_BG3_YDY = 1<<7;
 	SUB_BG3_XDY = 0;
 	SUB_BG3_YDX = 0;
 	SUB_BG3_CX = 0;
 	SUB_BG3_CY = 0;
 #else
//	videoSetModeSub(MODE_0_2D | DISPLAY_BG1_ACTIVE);
	disable_keyb();
	
	vramSetBankH(VRAM_H_SUB_BG);
	vramSetBankI(VRAM_I_SUB_BG);
	
	SUB_BG0_CR = BG_MAP_BASE(10) | BG_16_COLOR | BG_TILE_BASE(2);
	SUB_BG1_CR = BG_MAP_BASE(9) | BG_256_COLOR;
	
	SUB_BLEND_CR = BLEND_SRC_BG0 | BLEND_DST_BG1 | BLEND_ALPHA;
	SUB_BLEND_AB = (15 << 8) | 13;
		
	BG_PALETTE_SUB[255] = RGB15(31,31,31);
	
	consoleInitDefault((u16*)SCREEN_BASE_BLOCK_SUB(9), (u16*)CHAR_BASE_BLOCK_SUB(0), 8);
//	drawKeyboard(2, 10);
 #endif
 
 	vramSetBankE(VRAM_E_LCD);
 	
 	vram_init();
	init_textures();
 
	glViewPort(0,0,255,191);
	glClearColor(0,0,0);

	glClearDepth(0x7FFF);

	printf("QuakeDS, by Simon Hall\n");
#ifdef USE_WIFI
	printf("\twifi is enabled\n");
#endif
#ifdef USE_EXTRA_RAM
	printf("\textended RAM is enabled\n\thold R for options\n");
#endif
	printf("\thold L to disable qdsopts.txt\n");
	printf("\n");

	printf("Starting up\n");
	
#if !(defined USE_DEBUGGER)
	irqInit();
#endif
	printf("FAT init...");
	if (fatInitDefault())
		printf("ok\n");
	else
	{
		printf("failed!\n");
		printf("\nthe libfat DLDI driver system\nhas failed to initialise\n\nconfirm that you have patched\nthis file with the correct DLDI driver file for your hardware.");
		while(1);
	}

#ifdef CAN_DISABLE_QDSOPTS
	scanKeys();
	if ((keysHeld() & KEY_L) == KEY_L)
		disable_qdsopts = true;
#endif
	
#ifdef USE_EXTRA_RAM
	scanKeys();
	int selected_card = 0;
	if ((keysHeld() & KEY_R) == KEY_R)
	{
#define RAM_MENU_START_AT 9
		printf("\nSelect your RAM card\n");
		printf("\tAuto-detect\n");
		printf("\tSuperCard\n");
		printf("\tM3 Perfect\n");
		printf("\tNDS Memory Expansion Pak\n");
		printf("\tG6 Flash\n");
		printf("\tEZ-Flash\n");
		
		printf("\nSelect RAM speed\n");
		printf("\tSuper-slow\n");
		printf("\tReally slow\n");
		printf("\tJust plain slow\n");
		
		printf("\n\n\tOK\n");
		
		bool setup_ram = false;
		int selected_position = 0;
		int selected_speed = 0;
		
		unsigned short *text_map = (unsigned short *)SCREEN_BASE_BLOCK_SUB(9);
		text_map[32 * (RAM_MENU_START_AT + selected_position)] = '>';
		text_map[32 * (RAM_MENU_START_AT + selected_card) + 1] = 0xf0 | '>';
		text_map[32 * (RAM_MENU_START_AT + 8 + selected_speed) + 1] = 0xf0 | '>';
		
		while (setup_ram == false)
		{
			scanKeys();
			unsigned int down = keysDown();
			
			if (down == 0)
				continue;
			
			if ((down & KEY_DOWN) == KEY_DOWN)
				selected_position++;
			if ((down & KEY_UP) == KEY_UP)
				selected_position--;
			
			if ((down & KEY_SELECT) == KEY_SELECT)
			{
				if (selected_position < 6)
					selected_card = selected_position;
				else if (selected_position < 12)
					selected_speed = selected_position - 8;
				else
				{
					setup_ram = true;
					printf("\x1b[2J");
					
					switch (selected_speed)
					{
					case 0:
						printf("Setting RAM speed to 10, 6\n\n");
						ds_set_exram_timings(0, 0);
						break;
					case 1:
						printf("Setting RAM speed to 8, 4\n\n");
						ds_set_exram_timings(1, 1);
						break;
					case 2:
						printf("Setting RAM speed to 6, 4\n\n");
						ds_set_exram_timings(2, 1);
						break;
					default:
						printf("invalid RAM speed, %d\n", selected_speed);
						while(1);
						break;
					}
					break;
				}
			}
			
			if (selected_position < 0)
				selected_position = 13;
			if (selected_position == 6)
				selected_position = 8;
			if (selected_position == 7)
				selected_position = 5;
			if (selected_position == 11)
				selected_position = 13;
			if (selected_position == 12)
				selected_position = 10;
			if (selected_position >= 14)
				selected_position = 0;
			
			for (int count = 0; count < 6; count++)
			{
				text_map[32 * (RAM_MENU_START_AT + count)] = 0xf000;
				text_map[32 * (RAM_MENU_START_AT + count) + 1] = 0xf000;
			}
			
			for (int count = 0; count < 3; count++)
			{
				text_map[32 * (RAM_MENU_START_AT + 8 + count)] = 0xf000;
				text_map[32 * (RAM_MENU_START_AT + 8 + count) + 1] = 0xf000;
			}
			
			text_map[32 * (RAM_MENU_START_AT + 13)] = 0xf000;
			
			text_map[32 * (RAM_MENU_START_AT + selected_position)] = '>';
			text_map[32 * (RAM_MENU_START_AT + selected_card) + 1] = 0xf0 | '>';
			text_map[32 * (RAM_MENU_START_AT + 8 + selected_speed) + 1] = 0xf0 | '>';
		}
	}

	printf("\tSlot %d FAT driver\n", ds_find_dldi_slot());
	
	printf("EXRAM init...");
	if (ram_init((RAM_TYPE)selected_card))
	{
		printf("ok\n");
		printf("\tusing %s\n", ram_type_string());
		
		ds_set_exram_base((void *)ram_unlock());
		ram_lock();
		ds_set_exram_size(ram_size());
		printf("\taddr %08x, size %.2fMB\n", ds_exram_base(), (float)ds_exram_size() / 1048576);
		
		if (ram_size() < 4 * 1024 * 1024)
		{
			printf("failed!\n\nRAM size looks too small to be\ncorrect.\n");
			while(1);
		}
	}
	else
	{
		printf("failed!\n");
		while(1);
	}
#endif
	printf("\n");

#ifdef USE_DEBUGGER
	printf("starting debugger\n");
	//start up the wireless, connect to the AP
	set_verbosity(VERBOSE_ERROR | VERBOSE_INFO/* | VERBOSE_TRACE | VERBOSE_MMAP*/);
	wireless_init(1);
	wireless_connect();

	printf("connecting...");
	//connect to the PC stub, initialise the debugger
	debugger_connect_tcp(192, 168, 0, 8);
	debugger_init();					//from here on in you can get the debugger's attention
	printf("ok\n");
	
	//this will check for new commands from the debugger - call this repeatedly
	//the first time it is call it will break until you decide to resume
	//you can call this more than once in your program
	user_debugger_update();			//the earlier you call it, the earlier you can pause your program
#endif
	
#ifdef USE_EXCEPTION_HANDLER
	defaultExceptionHandler() ;
#endif

#ifdef USE_WIFI
#ifndef WIFI_ON_DEMAND
	printf("Starting wireless\n");
	wifi_go();
#endif
#endif
	
#ifdef USE_ADHOC
	printf("Initialising ad-hoc wifi IPC system\n");
	IPC_Init();
	IPC_SetMsgCompleteCallback(&IPC_RcvCompleteCheck);
#endif
	
	irqSet(IRQ_VBLANK, vblank_handler);
	irqEnable(IRQ_VBLANK);
	irqSet(IRQ_HBLANK, hblank_handler);
	irqEnable(IRQ_HBLANK);
	
#ifdef IPC_IN_TIMER
	irqSet(IRQ_TIMER0, handle_ipc);
	TIMER_DATA(0) = TIMER_FREQ_256(10 * 60);
 	TIMER_CR(0) = TIMER_ENABLE | TIMER_IRQ_REQ | TIMER_DIV_256;
#endif

#ifdef DO_FP_TESTS
	do_fp_tests();
#endif

	FILE *fp = NULL;
	if (disable_qdsopts == false)
		fp = fopen("qdsopts.txt", "r");
	else
		printf("disabling qdsopts.txt\n");
	
	int proper_argc;
	char **proper_argv;
	char command_line[256];
	memset(command_line, 0, 256);
	
	if (fp == NULL)
	{
		printf("Using default command line\n");
		proper_argc = 3;
		proper_argv = (char **)malloc(proper_argc * sizeof(char *));
		
		if (proper_argv == NULL)
		{
			printf("couldn't allocate space for argv\n");
			*(int *)0 = 0;
			while(1);
		}
		
		int pos = 0;
		
		proper_argv[0] = command_line + pos;
		memcpy(command_line, "/_boot_mp.nds", strlen("/_boot_mp.nds") + 1);
		pos += strlen("/_boot_mp.nds") + 1;
		
		proper_argv[1] = command_line + pos;
		memcpy(command_line + pos, "-basedir", strlen("-basedir") + 1);
		pos += strlen("-basedir") + 1;
		
		proper_argv[2] = command_line + pos;
		memcpy(command_line + pos, "/", strlen("/") + 1);
		pos += strlen("/") + 1;
	}
	else
	{
		printf("Using command line from disk\n");
		fread(command_line + strlen("/_boot_mp.nds") + 1, 1, 256 - strlen("/_boot_mp.nds") - 1, fp);
		printf("command line is:\n%s\n", command_line);
		proper_argc = 0;
		
		for (int count = strlen("/_boot_mp.nds") + 1; count < 255; count++)		//yeah
		{
			//for windows etc
			if ((command_line[count] == 0x0d) && (command_line[count + 1] == 0x0a))
			{
				proper_argc++;
				count++;
				
				if (command_line[count + 2] == 0)
					break;
			}
			//for unix
			else if (command_line[count] == 0x0a)
			{
				proper_argc++;
				
				if (command_line[count + 2] == 0)
					break;
			}
			//for new style
			else if (command_line[count] == ' ')
			{
				proper_argc++;
				
				if (command_line[count + 2] == 0)
					break;
			}
			else if (command_line[count] == 0)
			{
				proper_argc++;
				break;
			}
		}
		
		proper_argc++;		//for the program name
		proper_argv = (char **)malloc(proper_argc * sizeof(char *));
		int pos = 0;
		
		proper_argv[0] = command_line + pos;
		memcpy(command_line, "/_boot_mp.nds", strlen("/_boot_mp.nds") + 1);
		pos += strlen("/_boot_mp.nds") + 1;
	
		int arg_no = 1;
		
		for (int count = pos; count < 255; count++)		//yeah
		{
			//for windows etc
			if ((command_line[count] == 0x0d) && (command_line[count + 1] == 0x0a))
			{
				proper_argv[arg_no] = command_line + pos;
				pos += count - pos + 2;
				
				command_line[count] = 0;
				command_line[count + 1] = 0;
				
				arg_no++;
				count++;
				
				if (arg_no == proper_argc)
					break;
				
				continue;
			}
			//for unix
			else if (command_line[count] == 0x0a)
			{
				proper_argv[arg_no] = command_line + pos;
				pos += count - pos + 1;
				
				command_line[count] = 0;
				
				arg_no++;
				
				if (arg_no == proper_argc)
					break;
			}
			//for new style
			else if (command_line[count] == ' ')
			{
				proper_argv[arg_no] = command_line + pos;
				pos += count - pos + 1;
				
				command_line[count] = 0;
				
				arg_no++;
				
				if (arg_no == proper_argc)
					break;
			}
			else if (command_line[count] == 0)
			{
				proper_argv[arg_no] = command_line + pos;
				pos += count - pos + 1;
				
				command_line[count] = 0;
				
				arg_no++;
				
				break;
			}
		}
		
		printf("Quake command line options:\n");
		for (int count = 1; count < proper_argc; count++)
			printf("%d:%s\n", count, proper_argv[count]);
	}

	frame_buffer_base = VRAM_A;
	frame_buffer_width = SCREEN_WIDTH;
	frame_buffer_height = SCREEN_HEIGHT;
	console_base = BG_GFX_SUB;

#ifdef TIME_RAM
	ds_try_exram_unlock();
	
	TIMER_DATA(1) = 0;
	TIMER_CR(1) = TIMER_ENABLE;
	
	printf("\nTiming RAM latencies...\n\t(sequential, random)\n");
	
	time_ram("cached internal memory:", (void *)0x2000000, (vuint16*)(0x04000100+((1)<<2)));
	time_ram("uncached internal memory:", (void *)0x2400000, (vuint16*)(0x04000100+((1)<<2)));
	time_ram("dtcm:", (void *)0x0b000000, (vuint16*)(0x04000100+((1)<<2)));
	time_ram("vram:", (void *)0x6880000, (vuint16*)(0x04000100+((1)<<2)));

#ifdef USE_EXTRA_RAM
	time_ram("cached slot-2 memory:", ds_exram_base(), (vuint16*)(0x04000100+((1)<<2)));
	
	ds_exram_cache_disable();
	time_ram("uncached slot-2 memory:", ds_exram_base(), (vuint16*)(0x04000100+((1)<<2)));
	ds_exram_cache_enable();
#endif
	ds_try_exram_lock();
	
	while(1);
#endif
	
#ifdef USE_EXTRA_RAM
	ds_try_exram_unlock();
	ram_test();
//	test_8bit_ram(ds_exram_base());
//	test_8bit_ram(malloc(16));
	clear_ram((unsigned int *)ds_exram_base(), ds_exram_size());
	ds_try_exram_lock();
#endif
	
	ds_try_exram_unlock();

	setGenericSound(11025, 127, 64, 1);
	init_arm7();

#ifdef USE_WIFI
#ifdef WIFI_ON_DEMAND
	printf("Starting wireless\n");
	wifi_go();
#endif
#endif
	
	REG_EXEMEMCNT = REG_EXEMEMCNT | (1 << 15);
	
//	volatile int count = 0;
//	for (count = 0; count < 1000000; count++)
//	play_mp3(3);
//	
//	while(1);
	
//	cygprofile_enable();

	quake_main(proper_argc, proper_argv);
		
	return 0;
}

void get_pen_pos(short *px, short *py)
{
	touchPosition touchXY = touchReadXY();
	*px = touchXY.px;
	*py = touchXY.py;
}

int main(int argc, unsigned char **argv)
{
//	asm volatile ( 
//	  "mov sp, #0x02400000\n" 
//	  "sub sp, sp, #4096\n"
//	  "sub sp, sp, #4\n" 
//	);
	
	real_main(argc, argv);
	
	return 0;
}
