#include "examples.h"
#include "example1.h"
#include "example2.h"
#include "example3.h"
#include "example4.h"
#include "example5.h"
#include "example6.h"
#include "example7.h"
#include "framerate.h"
#include <vector>

using namespace std;

PFNWGLSWAPINTERVALEXTPROC pwglSwapIntervalEXT;

vector<RatPhysicsExample *> examples;

rat_glyph_font *glyphfont;
rat_texture_font *outfont=NULL;
const unsigned int num_examples=7;
char keys[SDLK_LAST];
char numkeys[10];
int numkeypress=-1;
int curex=0;

bool arbshowdown=false;
bool aabbshowdown=false;
bool qdtshowdown=false;
bool linesmoothdown=false;
bool mousedown=false;

bool show_arbiters=false;
bool show_aabbs=false;
bool show_quadtree=false;
bool linesmoothing=true;

bool alt_down=false;
bool enter_down=false;

bool fullscreen=false;
bool run=true;

rat_body *grabbed_body;
rat_spring *grabbed_spring;
bool has_grabbed=false;

vector2 world_cursor;

rat_real fontsizey;

static void toggle_fullscreen()
{
	fullscreen=!fullscreen;
	SDL_QuitSubSystem(SDL_INIT_VIDEO);
	if (SDL_InitSubSystem(SDL_INIT_VIDEO)!=0)
	{
		printf("VIDEO SUBSYSTEM EPIC FAIL! ... \"%s\"\n",SDL_GetError());
		fflush(stdout);
		exit(1);
	}
}

static void create_video()
{
	SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER,1);
	SDL_GL_SetAttribute(SDL_GL_BUFFER_SIZE,16);
	SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE,16);

	SDL_SetVideoMode(WINDOW_WIDTH,WINDOW_HEIGHT,16,SDL_OPENGL|(fullscreen?SDL_FULLSCREEN:0));

	SDL_WM_SetCaption("Rat Physics Examples v2.0",NULL);
	SDL_EnableKeyRepeat(SDL_DEFAULT_REPEAT_DELAY,SDL_DEFAULT_REPEAT_INTERVAL/2);

	if (outfont) rat_texture_font_destroy(outfont);
	assert(glyphfont);
	outfont=rat_texture_font_from_glyph_font(glyphfont);

#ifdef RAT_PHYSICS_WINDOWS
	if (!pwglSwapIntervalEXT) pwglSwapIntervalEXT=(PFNWGLSWAPINTERVALEXTPROC)load_extension("wglSwapIntervalEXT");
	if (pwglSwapIntervalEXT)  pwglSwapIntervalEXT(0);
#endif
	
	glClearColor(0.8f,0.8f,0.8f,1);
}

static void handle_events()
{
	SDL_Event event;

	numkeypress=-1;
	while (SDL_PollEvent(&event))
	{
		switch (event.type)
		{
		case SDL_QUIT:
			run=false;
			break;
		case SDL_KEYDOWN:
			if (event.key.keysym.sym>=SDLK_0&&event.key.keysym.sym<=SDLK_9)
				numkeys[event.key.keysym.sym-SDLK_0]=(char)true;

			switch (event.key.keysym.sym)
			{
			case SDLK_ESCAPE:
				run=false;
				break;
			case SDLK_LALT:
			case SDLK_RALT:
				alt_down=true;
				break;
			case SDLK_RETURN:
				enter_down=true;
				break;
			default:
				keys[event.key.keysym.sym]=(char)true;
				break;
			};
			break;
		case SDL_KEYUP:
			if (event.key.keysym.sym>=SDLK_0&&event.key.keysym.sym<=SDLK_9)
			{
				numkeys[event.key.keysym.sym-SDLK_0]=(char)false;
				numkeypress=event.key.keysym.sym-SDLK_0;
			}
			
			switch (event.key.keysym.sym)
			{
			case SDLK_LALT:
			case SDLK_RALT:
				alt_down=false;
				if (enter_down)
				{
					enter_down=false;
					toggle_fullscreen();
					create_video();
				}
				break;
			case SDLK_RETURN:
				enter_down=false;
				if (alt_down)
				{
					alt_down=false;
					toggle_fullscreen();
					create_video();
				}
			default:
				keys[event.key.keysym.sym]=(char)false;
				break;
			};
			break;
		case SDL_MOUSEBUTTONDOWN:
			mousedown=true;
			break;
		case SDL_MOUSEBUTTONUP:
			mousedown=false;
			break;
		case SDL_MOUSEMOTION:
			world_cursor.x=((rat_real)event.motion.x/(rat_real)WINDOW_WIDTH)*SCENE_WIDTH;
			world_cursor.y=((rat_real)event.motion.y/(rat_real)WINDOW_HEIGHT)*SCENE_HEIGHT;
			break;
		default:
			break;
		};
	};
}

static void populate_list()
{
	examples.push_back(new RatPhysicsExample1("Pinchinco Pegs"));
	examples.push_back(new RatPhysicsExample2("Pyramid Stress Test"));
	examples.push_back(new RatPhysicsExample3("Varying Restitution"));
	examples.push_back(new RatPhysicsExample4("Varying Friction"));
	examples.push_back(new RatPhysicsExample5("Collision Filters"));
	examples.push_back(new RatPhysicsExample6("Pivot/Link Chains"));
	examples.push_back(new RatPhysicsExample7("Newtons Cradle"));
}

static void destroy_list()
{
	vector<RatPhysicsExample *>::iterator it;
	for (it=examples.begin(); it!=examples.end(); it++) delete *it;
}

static void draw_text_bg(rat_real x,rat_real y,char *text)
{
	rat_real sizex=rat_texture_font_text_length(outfont,text);
	float rgba[4]={0,0,0,1};

	glColor4f(1,1,1,0.6);
	glMatrixMode(GL_MODELVIEW);
	glPushMatrix();
		glTranslatef(x,y,0);
		glScalef(sizex,fontsizey,0);
		glBegin(GL_QUADS);
			glVertex2f(0,0);
			glVertex2f(0,1);
			glVertex2f(1,1);
			glVertex2f(1,0);
		glEnd();
	glPopMatrix();

	rat_set_text_color(rgba);
	rat_texture_font_render_text(outfont,x,y,text);
}

#ifdef WIN32SUBSYS
int APIENTRY WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,LPSTR lpCmdLine,int nCmdShow)
{
#else
int main(int argc,char *argv[])
{
#endif
	char tempbuf[256];
	char *infotext="Press the number keys across the top row for different demos.";
	rat_real deltatime=1.0;
	cout.sync_with_stdio(true);

	memset(keys,0,SDLK_LAST);

	atexit(SDL_Quit);
	if (SDL_Init(SDL_INIT_VIDEO)!=0)
	{
		printf("VIDEO SUBSYSTEM EPIC FAIL! ... \"%s\"\n",SDL_GetError());
		fflush(stdout);
		return 1;
	}

	rat_start_font_system();
	if (!(glyphfont=rat_glyph_font_load("small.ttf",12)))
	{
		cout<<"failed to load font!"<<endl;
		SDL_Quit();
		return 1;
	}

	create_video();

	fontsizey=rat_texture_font_height(outfont)*1.5;
	rat_real infotextx=(rat_real)SCENE_WIDTH-rat_texture_font_text_length(outfont,(char *)infotext);
	rat_real infotexty=(rat_real)SCENE_HEIGHT-fontsizey;

	rat_ortho_set_mode(rat_ortho_res_vres);
	rat_ortho_set_res(WINDOW_WIDTH,WINDOW_HEIGHT);
	rat_ortho_set_virtual_res(SCENE_WIDTH,SCENE_HEIGHT);

	bool fail=false;

	if (!rat_physics_is_init())
	{
		if (!rat_physics_init())
		{
			printf("Failed to initialize Rat Physics!\n");
			fflush(stdout);
			return 1;
		}
	}

	populate_list();

	if (!examples[curex]->InitWorld())
	{
		cout<<"init world fail!"<<endl;
		SDL_Quit();
		return 1;
	}

	glEnable(GL_BLEND);
	glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA);

	rat_frame_counter *framectr=rat_frame_counter_create(60.0,0.1);
	rat_frame_counter *frameratetracker=rat_frame_counter_create(60.0,1.0);
	while (run)
	{
		// what do you think this does?  ;)
		handle_events();
		if (keys[SDLK_F1])
			arbshowdown=true;
		else if (arbshowdown)
		{
			show_arbiters=!show_arbiters;
			arbshowdown=false;
		}
		if (keys[SDLK_F2])
			aabbshowdown=true;
		else if (aabbshowdown)
		{
			show_aabbs=!show_aabbs;
			aabbshowdown=false;
		}
		if (keys[SDLK_F3])
			qdtshowdown=true;
		else if (qdtshowdown)
		{
			show_quadtree=!show_quadtree;
			qdtshowdown=false;
		}
		if (keys[SDLK_F4])
			linesmoothdown=true;
		else if (linesmoothdown)
		{
			linesmoothing=!linesmoothing;
			linesmoothdown=false;
		}

		if (keys[SDLK_PAGEDOWN])
		{
			unsigned int solvacc=rat_world_get_solver_iterations(examples[curex]->world);
			solvacc=(--solvacc)<1?1:solvacc;
			rat_world_set_solver_iterations(examples[curex]->world,solvacc);
		}
		if (keys[SDLK_PAGEUP])
		{
			unsigned int solvacc=rat_world_get_solver_iterations(examples[curex]->world);
			solvacc++;
			rat_world_set_solver_iterations(examples[curex]->world,solvacc);
		}

		if (mousedown)
		{
			if (has_grabbed)
				rat_spring_update_global_anchor(grabbed_spring,vector2_add(world_cursor,examples[curex]->pickoffset));
			else
			{
				vector2 global_anchor;
				vector2 local_anchor;
				rat_body *picked_body;

				if (examples[curex]->WorldPointPick(vector2_add(world_cursor,examples[curex]->pickoffset),&picked_body,&local_anchor))
				{
					// we grab this puppy
					has_grabbed=true;
					grabbed_body=picked_body;
					grabbed_spring=rat_spring_create(grabbed_body,NULL,
						local_anchor,world_cursor,92.0,0.0,46.0);

					rat_world_add_spring(examples[curex]->world,grabbed_spring);
				}
			}
		}
		else if (has_grabbed)
		{
			has_grabbed=false;
			rat_world_remove_and_destroy_spring(examples[curex]->world,grabbed_spring);
		}

		// switch demos based on number pressed
		if (numkeypress>-1)
		{
			if (numkeypress<1)
				numkeypress=9;
			else
				numkeypress--;

			if (numkeypress<num_examples)
			{
				if (!examples[curex]->FreeWorld())
				{
					cout<<"free world on example change fail!"<<endl;
					run=false;
					fail=true;
				}
				has_grabbed=false;
				rat_physics_cleanup();
				rat_physics_init();
				curex=numkeypress;
				if (!examples[curex]->InitWorld())
				{
					cout<<"init world on example change fail!"<<endl;
					run=false;
					fail=true;
				}
			}
		}

		// update the world using a smooth and corrective timestepping scheme
		deltatime=(deltatime*24.0+framectr->delta_time)/25.0;
		if (!keys[SDLK_p])
		{
			if (!examples[curex]->UpdateWorld(deltatime))
			{
				cout<<"update world fail!"<<endl;
				run=false;
				fail=true;
			}
		}

		// render the output
		glClear(GL_COLOR_BUFFER_BIT);
		rat_ortho_push();
			glPushAttrib(GL_CURRENT_BIT);
				if (linesmoothing)
				{
					glEnable(GL_LINE_SMOOTH);
					glEnable(GL_POINT_SMOOTH);

					glHint(GL_LINE_SMOOTH_HINT,GL_NICEST);
					glHint(GL_POINT_SMOOTH_HINT,GL_NICEST);
				}

				if (!examples[curex]->UpdateVisuals())
				{
					cout<<"update visual fail!"<<endl;
					run=false;
					fail=true;
				}
				sprintf(tempbuf,"  FPS %u",frameratetracker->fps);
				string title_str=examples[curex]->title+tempbuf;
				draw_text_bg(0,0,(char *)title_str.c_str());

				rat_mem_alloced_string(tempbuf+128);
				sprintf(tempbuf,"solver islands  %03u     memory usage  %s",examples[curex]->world->numislands,tempbuf+128);
				draw_text_bg(0,fontsizey,tempbuf);

				sprintf(tempbuf,"solver iterations %04u  (pgup/pgdn to change)",
					rat_world_get_solver_iterations(examples[curex]->world));
				draw_text_bg(0,fontsizey*2.0,tempbuf);

				draw_text_bg(infotextx,infotexty,infotext);
			glPopAttrib();
		rat_ortho_pop();

		SDL_GL_SwapBuffers();
		rat_frame_counter_update(framectr,1);
		rat_frame_counter_update(frameratetracker,1);
	}
	rat_frame_counter_destroy(framectr);
	rat_frame_counter_destroy(frameratetracker);

	if (!fail)
	{
		if (!examples[curex]->FreeWorld())
		{
			cout<<"free world fail!"<<endl;
			fail=true;
		}
	}

	destroy_list();

	rat_physics_cleanup();
	SDL_Quit();

	return fail?1:0;
}

RatPhysicsExample::RatPhysicsExample(string name)
{
	title=string("Rat Physics Examples - (")+name+string(")");
	bounds.x=SCENE_WIDTH;
	bounds.y=SCENE_HEIGHT;
	pickoffset.x=0;
	pickoffset.y=0;
}

RatPhysicsExample::RatPhysicsExample(const char *name)
{
	title=string("Rat Physics Examples - (")+string(name)+string(")");
	bounds.x=SCENE_WIDTH;
	bounds.y=SCENE_HEIGHT;
	pickoffset.x=0;
	pickoffset.y=0;
}

RatPhysicsExample::RatPhysicsExample(char *name)
{
	title=string("Rat Physics Examples - (")+string(name)+string(")");
	bounds.x=SCENE_WIDTH;
	bounds.y=SCENE_HEIGHT;
	pickoffset.x=0;
	pickoffset.y=0;
}

bool RatPhysicsExample::WorldPointPick(vector2 point,rat_body **bodypicked,vector2 *local_picked)
{
	rat_body *thisbody;
	rat_iterator *iter=rat_body_manager_all_bodies_iterator(world->body_manager);
	for (; rat_iterator_finished(iter); rat_iterator_next(iter))
	{
		thisbody=(rat_body *)rat_iterator_value(iter);
		if (rat_hull_point_query(thisbody->hull,point))
		{
			rat_iterator_destroy(iter);
			*bodypicked=thisbody;
			*local_picked=vector2_world_to_local(
				point,rat_hull_get_pos(thisbody->hull),
				rat_hull_get_angle(thisbody->hull));

			return true;
		}
	}
	rat_iterator_destroy(iter);
	return false;
}


void rat_examples_create_walls(rat_world *world)
{
	static vector2 tmpvec,tmpvec2;

	rat_hull **wallhulls=(rat_hull **)rat_stack_alloc(world->sal,4*sizeof(rat_hull *));

	vector2_set(&tmpvec2,20,SCENE_HEIGHT/2.0);
	vector2_set(&tmpvec,0,SCENE_HEIGHT/2.0);
	wallhulls[0]=rat_hull_polygon_create_box(tmpvec,tmpvec2);
	vector2_set(&tmpvec,SCENE_WIDTH,SCENE_HEIGHT/2.0);
	wallhulls[1]=rat_hull_polygon_create_box(tmpvec,tmpvec2);

	vector2_set(&tmpvec2,SCENE_WIDTH/2.0,20);
	vector2_set(&tmpvec,SCENE_WIDTH/2.0,0);
	wallhulls[2]=rat_hull_polygon_create_box(tmpvec,tmpvec2);
	vector2_set(&tmpvec,SCENE_WIDTH/2.0,SCENE_HEIGHT);
	wallhulls[3]=rat_hull_polygon_create_box(tmpvec,tmpvec2);

	rat_world_add_body(world,rat_body_create(wallhulls[0],1.0,1.0,0.0,0.0));
	rat_world_add_body(world,rat_body_create(wallhulls[1],1.0,1.0,0.0,0.0));
	rat_world_add_body(world,rat_body_create(wallhulls[2],1.0,1.0,0.0,0.0));
	rat_world_add_body(world,rat_body_create(wallhulls[3],1.0,1.0,0.0,0.0));

	rat_stack_dealloc(world->sal,(void *)wallhulls);
}

void rat_examples_create_circle_row(rat_world *world,rat_real diameter,rat_real padx,rat_real y,rat_real xcenter,unsigned int numcircs,int tag)
{
	register unsigned int i;
	register rat_real halfwidth=((diameter+padx)/2.0)*numcircs;
	rat_real radius=diameter*0.5;

	for (i=0; i<numcircs; i++)
	{
		vector2 center={xcenter-halfwidth+(diameter+padx)*i+radius+padx/2.0,y};
		rat_hull *circ=rat_hull_circle_create(center,radius);

		rat_body *body=rat_body_create(circ,0.7,0.65,rat_area_circle(radius),
			rat_moi_circle(rat_area_circle(radius),radius));
		rat_body_set_filter_tag(body,tag);

		rat_world_add_body(world,body);
	}
}

void rat_examples_create_box_row(rat_world *world,vector2 boxsize,rat_real padx,rat_real y,rat_real xcenter,unsigned int numboxes,int tag)
{
	register unsigned int i;
	register rat_real halfwidth=((boxsize.x+padx)/2.0)*numboxes;
	vector2 halfsize=vector2_multf(boxsize,0.5);

	for (i=0; i<numboxes; i++)
	{
		vector2 *boxverts;
		vector2 boxpos={xcenter-halfwidth+(boxsize.x+padx)*i+halfsize.x+padx/2.0,y};
		rat_hull *box=rat_hull_polygon_create_box(boxpos,halfsize);

		rat_hull_polygon_get_verts(box,&boxverts);

		rat_body *body=rat_body_create(box,0.7,0.65,boxsize.x*boxsize.y,
			rat_moi_polygon(boxsize.x*boxsize.y,boxverts,4));
		rat_body_set_filter_tag(body,tag);

		rat_world_add_body(world,body);
	}
}

void rat_examples_create_chain(rat_world *world,bool anchored,vector2 boxsize,rat_real y,rat_real xcenter,unsigned int numboxes,int tag)
{
	register unsigned int i;
	register rat_real halfwidth=((boxsize.x)/2.0)*numboxes;
	vector2 halfsize=vector2_multf(boxsize,0.5);

	rat_body *last_body;
	if (anchored)
	{
		vector2 anchpos={xcenter-halfwidth,y};
		rat_body *anchor=rat_body_create(rat_hull_particle_create(anchpos),0.0,0.0,0.0,0.0);
		anchor->filtertag=tag;

		last_body=anchor;
		rat_world_add_body(world,anchor);

		vector2 boxverts[6]=
		{
			{halfsize.x*0.75,-halfsize.y},
			{-halfsize.x*0.75,-halfsize.y},
			{-halfsize.x*1.2,0.0},
			{-halfsize.x*0.75,halfsize.y},
			{halfsize.x*0.75,halfsize.y},
			{halfsize.x*1.2,0.0}
		};

		for (i=0; i<numboxes; i++)
		{
			vector2 boxpos={xcenter-halfwidth+(boxsize.x)*i+halfsize.x,y};
			rat_hull *box=rat_hull_polygon_create(boxpos,6,boxverts);

			rat_body *body=rat_body_create(box,0.7,0.35,boxsize.x*boxsize.y*0.5,
				rat_moi_polygon(boxsize.x*boxsize.y*0.5,boxverts,6));
			rat_body_set_filter_tag(body,tag);
			rat_body_enable_adjacency_filter(body);
		
			vector2 anch={boxpos.x-halfsize.x,y},zv={0,0};

			rat_world_add_body(world,body);
			rat_world_add_constraint(world,rat_constraint_pivot_create(last_body,body,anch));
			last_body=body;
		}
	}
	else
	{
		vector2 boxverts[6]=
		{
			{halfsize.x*0.75,-halfsize.y},
			{-halfsize.x*0.75,-halfsize.y},
			{-halfsize.x*1.2,0.0},
			{-halfsize.x*0.75,halfsize.y},
			{halfsize.x*0.75,halfsize.y},
			{halfsize.x*1.2,0.0}
		};

		vector2 boxpos={xcenter-halfwidth+halfsize.x,y};
		rat_hull *box=rat_hull_polygon_create(boxpos,6,boxverts);

		rat_body *body=rat_body_create(box,0.7,0.35,boxsize.x*boxsize.y*0.5,
			rat_moi_polygon(boxsize.x*boxsize.y*0.5,boxverts,6));
		rat_body_set_filter_tag(body,tag);
		rat_body_enable_adjacency_filter(body);

		rat_world_add_body(world,body);
		last_body=body;

		for (i=1; i<numboxes; i++)
		{
			vector2_set(&boxpos,xcenter-halfwidth+(boxsize.x)*i+halfsize.x,y);
			rat_hull *box=rat_hull_polygon_create(boxpos,6,boxverts);

			rat_body *body=rat_body_create(box,0.7,0.35,boxsize.x*boxsize.y*0.5,
				rat_moi_polygon(boxsize.x*boxsize.y*0.5,boxverts,6));
			rat_body_set_filter_tag(body,tag);
			rat_body_enable_adjacency_filter(body);
		
			vector2 anch={boxpos.x-halfsize.x,y},zv={0,0};

			rat_world_add_body(world,body);
			rat_world_add_constraint(world,rat_constraint_pivot_create(last_body,body,anch));
			last_body=body;
		}
	}
}

void rat_examples_leave_world_callback(rat_world *world,rat_body *body,void *userdata)
{
	// safe way to destroy and remove body in world.
	// since this callback is called in the middle of
	// a procedure that iterates through the bodies,
	// we must use the safe functions for manipulating
	// bodies inside a world callback.
	if (has_grabbed&&(body==grabbed_body))
	{
		//rat_world_remove_and_destroy_spring(world,grabbed_spring);
		has_grabbed=false;
	}
	rat_world_remove_and_destroy_body(world,body);
}
