#include <allegro.h>

#include "main.h"
#include "game.h"

#include "flipper.h"
#include "line.h"
#include "squirrel.h"
#include "ball.h"
#include "ferret.h"
#include "plate.h"

#include "net.h"

Game *game = 0;

Game::Game() {
	netrate = 0;				
}

void Game::enter() {
	
	set_palette((RGB *)(numberfont[2].dat));
	
	play_midi(music, 1);
				
	wave = 0;
	squirrelinvasion = 0;
	newball = 0;
	
	game_over = 0;
	game_not_over = 1;
	
	game->rightramp = 0;
	game->leftramp = 0;
			
	leftpoints = 0;
	rightpoints = 0;
	
	leftballs = 2;
	rightballs = 2;
	
	Line::lines = 0;
	Part::first = 0;
	Squirrel::num = 0;
	Target::targets = 0;
	
	ball[0] = new Ball(SCREEN_W / 2, SCREEN_H / 2, 7);
	
	ball[1] = new Ball(SCREEN_W / 2, SCREEN_H / 2 + 50, 7);
	ball[1]->out = true;	
	
	ball[2] = new Ball(SCREEN_W / 2, SCREEN_H / 2 + 50, 7);
	ball[2]->out = true;	
	
	ball[3] = new Ball(SCREEN_W / 2, SCREEN_H / 2 + 50, 7);
	ball[3]->out = true;	
	
	flipperleft1 = new FlipperLeft(SCREEN_W / 4 - 80, SCREEN_H - 50 ,70);
	flipperright1 = new FlipperRight(SCREEN_W / 4 + 80, SCREEN_H - 50 ,70);
	
	flipperleft2 = new FlipperLeft(3 * SCREEN_W / 4 - 80, SCREEN_H - 50 ,70);
	flipperright2 = new FlipperRight(3 * SCREEN_W / 4 + 80, SCREEN_H - 50 ,70);
						
	// top right
	righttop = new Line(SCREEN_W - 1 - 10, 50, SCREEN_W / 2 - 1, 5, 2);
	// top left
	lefttop = new Line(SCREEN_W / 2 - 1, 5, 10, 51, 2);
			
	// left
	left = new Line(10, 50, SCREEN_W / 4 - 80, SCREEN_H - 60, 2);
	// right
	right = new Line(3 * SCREEN_W / 4 + 80, SCREEN_H - 60, SCREEN_W - 10, 50, 2);	
	
	
	// right middle top
	Line *rightmiddletop = new Line(SCREEN_W / 2 + 13, SCREEN_H / 2 + 10, 3 * SCREEN_W / 4 - 80, SCREEN_H - 60, 2);
	// left middle top
	Line *leftmiddletop = new Line(SCREEN_W / 4 + 80, SCREEN_H - 60, SCREEN_W / 2 - 13, SCREEN_H / 2 + 10, 2);	
	
	// left middle
	new Line(SCREEN_W / 2 - 11, SCREEN_H / 2 + 10, SCREEN_W / 2 - 8, SCREEN_H - 120, 2);
	// right middle
	new Line(SCREEN_W / 2 + 8, SCREEN_H - 120, SCREEN_W / 2 + 11, SCREEN_H / 2 + 10, 2);		
	
	// bottom
	bottomref = new Line(SCREEN_W / 2 - 10, SCREEN_H - 120, SCREEN_W / 2 + 10, SCREEN_H - 120, 2);
	
	// right launcher top
	rightref = new Line(SCREEN_W / 2 + 1, SCREEN_H / 4, SCREEN_W / 2 + 21, SCREEN_H / 4 + 20, 2);
	// left launcher top
	leftref = new Line(SCREEN_W / 2 - 21, SCREEN_H / 4 + 20, SCREEN_W / 2 - 1, SCREEN_H / 4, 2);	
	// right launcher bottom
	new Line(SCREEN_W / 2 - 8, SCREEN_H / 4 + 40, SCREEN_W / 2 - 21, SCREEN_H / 4 + 20, 2);	
	// left launcher bottom
	new Line(SCREEN_W / 2 + 21, SCREEN_H / 4 + 20, SCREEN_W / 2 + 8, SCREEN_H / 4 + 40, 2);		
		
	launcher = new Target(SCREEN_W / 2, SCREEN_H / 4 + 20, 0, 15, false, 7);
		
	new Ferret(SCREEN_W / 4, SCREEN_H / 4, 0, 25);	
	new Ferret(3 * SCREEN_W / 4, SCREEN_H / 4, 0, 25, true);	
		
	// left ramp left
	new Line(80, SCREEN_H / 3, 50, 100, 1);
	// left ramp top
	new Line(50, 100, SCREEN_W / 6, 70, 1);
	// left ramp diag
	new Line(SCREEN_W / 6, 70, 80, SCREEN_H / 3, 1);
	
	// left top edge
	new Line(70 - 20, 40, 20, 140 - 40, 2);
		
	new Target(40, 100, 0, 20, false, 10);	
	new Target(60,  70, 0, 20, false, 11);
	new Target(100, 50, 0, 20, false, 12);	
		
	// right ramp right
	new Line(SCREEN_W - 50, 100, SCREEN_W - 80, SCREEN_H / 3, 1);
	// right ramp top
	new Line(5 * SCREEN_W / 6, 70, SCREEN_W - 50, 100, 1);
	// right ramp top
	new Line(SCREEN_W - 80, SCREEN_H / 3, 5 * SCREEN_W / 6, 70, 1);
	
	// right top edge
	new Line(SCREEN_W - 20, 140 - 40, SCREEN_W - 70 + 20, 40, 2);
	
	new Target(SCREEN_W - 40, 100, 0, 20, false, 20);	
	new Target(SCREEN_W - 60,  70, 0, 20, false, 21);
	new Target(SCREEN_W - 100, 50, 0, 20, false, 22);	
				
	float x, y;	
	Squirrel *sq;
	
	lefttop->point(0.4, &x, &y);	
	sq = new Squirrel(x, y, 3 * M_PI / 2, 15, true);
	sq->line = lefttop;
	
	lefttop->point(0.6, &x, &y);	
	sq = new Squirrel(x, y, 3 * M_PI / 2, 15, true);
	sq->line = lefttop;
	
	righttop->point(0.4, &x, &y);	
	sq = new Squirrel(x, y, 3 * M_PI / 2, 15);
	sq->line = righttop;
	
	righttop->point(0.6, &x, &y);	
	sq = new Squirrel(x, y, 3 * M_PI / 2, 15);
	sq->line = righttop;
	
	left->point(0.4, &x, &y);	
	sq = new Squirrel(x, y, M_PI, 15, true);
	sq->line = left;
	
	left->point(0.6, &x, &y);	
	sq = new Squirrel(x, y, M_PI, 15, true);
	sq->line = left;
	
	right->point(0.4, &x, &y);	
	sq = new Squirrel(x, y, 0, 15);
	sq->line = right;
	
	right->point(0.6, &x, &y);	
	sq = new Squirrel(x, y, 0, 15);
	sq->line = right;
	
	leftmiddletop->point(0.4, &x, &y);	
	sq = new Squirrel(x, y, 0, 15);
	sq->line = leftmiddletop;
	
	leftmiddletop->point(0.6, &x, &y);	
	sq = new Squirrel(x, y, 0, 15);
	sq->line = leftmiddletop;
	
	rightmiddletop->point(0.4, &x, &y);	
	sq = new Squirrel(x, y, M_PI, 15, true);
	sq->line = rightmiddletop;
	
	rightmiddletop->point(0.6, &x, &y);	
	sq = new Squirrel(x, y, M_PI, 15, true);
	sq->line = rightmiddletop;
	
	lefttopplate = new Plate(0, 0, 100, 35, 1);
	righttopplate = new Plate(SCREEN_W - 100, 0, 100, 35, 2);
	leftbottomplate = new Plate(0, SCREEN_H - 30, 100, 30, 3);
	rightbottomplate = new Plate(SCREEN_W - 100, SCREEN_H - 30, 100, 30, 4);
	
	middleplate = new Plate(SCREEN_W / 2 - 50, SCREEN_H - 30, 100, 30, 5);
					
	background = load_bitmap("dat.dat#main.bmp", NULL);
	
	ball[0]->load();
	flipperleft1->load();
	flipperright1->load();
	for(int i = 0; i < 7; i++) {
		flipperleft2->bmp[i] = flipperleft1->bmp[i];
		flipperright2->bmp[i] = flipperright1->bmp[i];
	}
	Squirrel::squirrel = load_bitmap("dat.dat#squirrel.bmp", NULL);
	Ferret::ferret = load_bitmap("dat.dat#ferret.bmp", NULL);
	
	ankh = load_bitmap("dat.dat#ankh.bmp", NULL);
	
	redraw_background();
}

void Game::leave() {
		
	play_midi(NULL, 0);	
			
	destroy_bitmap(background);	
	destroy_bitmap(ball[0]->bmp); ball[0]->bmp = 0;
	destroy_bitmap(ball[0]->bmpl); ball[0]->bmpl = 0;
	destroy_bitmap(ball[0]->bmpr); ball[0]->bmpr = 0;
	
	destroy_bitmap(Squirrel::squirrel);
	destroy_bitmap(Ferret::ferret);
	
	destroy_bitmap(ankh);	
					
	for(int i = 0; i < 7; i++) {
		destroy_bitmap(flipperleft1->bmp[i]); flipperleft1->bmp[i] = 0;
		destroy_bitmap(flipperright1->bmp[i]); flipperright1->bmp[i] = 0;
		flipperleft2->bmp[i] = 0;
		flipperright2->bmp[i] = 0;
	}
	
	Part *part;
	for(part = Part::first; part; ) {
		Part *next = part->next;
		delete part;
		part = next;				
	}
}

void Game::dropem() {
	for(Target *target = Target::targets; target; target = target->nexttarget) {
		if(target->type == 1) {
			if((!target->hit) && target->yp - 50 < SCREEN_H) {
				
				if(game_over > 0) {
					leftpoints += 300;
					lefttopplate->rest = lefttopplate->redraw = true;
				}
				if(game_over < 0) {
					rightpoints += 300;					
					righttopplate->rest = righttopplate->redraw = true;
				}
				
				target->hit = true;
												
			}
		}
	}		
	
	if(game_over > 0) {
		leftpoints += leftballs * 1000;
	}
	if(game_over < 0) {
		rightpoints += rightballs * 1000;
	}
}

int Game::checkem() {
	int s = 0;
	int m = 1;
	for(Target *target = Target::targets; target; target = target->nexttarget) {
		if(target->type == 1) {
			if(target ->hit) {
				s += m;
			}
		}
		m *= 2;
	}
	
	if(game_over == 1) s += 65536;
	if(game_over == -1) s += 131072;
	
	return s;
}

void Game::fixem(int s) {	
	int m = 1;
	for(Target *target = Target::targets; target; target = target->nexttarget) {
		if(target->type == 1) {
			if((s & m) == m) {				
				if(!target->hit) {
					target->hit = true;
				}
			}
		}
		m *= 2;
	}
	
	if(s & 65536) game_over = 1; 
	if(s & 131072) game_over = -1; 
}

int Game::process(float d) {
	static float remain = 0;
	
					
	while(keypressed()) {
		int k = readkey();
		k >>= 8;
		if(k == KEY_ESC) {									
			return 1;
		}
		/*if(k == KEY_D) {
			clear(page);
		}	
		if(k == KEY_S) {
			dropem();
		}	*/			
	}
	
#ifndef NO_LIBNET	
		
	// Network Update
		
	if(netrate > 0) {	
		
		time2send -= d;
		
		// Receive
		
		while(net_query(network->channel))	{
			char buffer[256];
			float *f = (float *)buffer;
			int *i = (int *)buffer;
			int l = net_receive(network->channel, buffer, 256, NULL);
			
			// newball package (4 byte)
						
			if(l == 1 * 4) {																	
												
				int w = i[0];
				int b = w;
				b >>= 8;
				w &= 255; w--;
											
				ball[b]->xp = SCREEN_W / 2;
				ball[b]->yp = SCREEN_H / 2;
				ball[b]->xc = 0;
				ball[b]->yc = 0;
				ball[b]->who = w;
				ball[b]->out = false;
				ball[b]->in_charge = false;							
			}
			
			// update packages (20, 40, 60 bytes)
			if(l == 5 * 4 || l == 10 * 4 || l == 15 * 4 || l == 20 * 4 || l == 25 * 4) {
				
				int p = 0;
								
				for(int n = 5 * 4; n < l; n += 5 * 4) {
					int w = i[p++];
					int b = w;
					b >>= 8;
					w &= 255; w--;
														
					ball[b]->xp = f[p++];
					ball[b]->yp = f[p++];
					ball[b]->xc = f[p++];
					ball[b]->yc = f[p++];
						
					ball[b]->who = w;
					ball[b]->out = false;										
					
				}
												
				if(side < 0) {
					flipperleft2->rp = f[p++];
					flipperright2->rp = f[p++];
					rightpoints = i[p++];
					rightballs = i[p++];	
					
					righttopplate->rest = righttopplate->redraw = true;	
					rightbottomplate->rest = rightbottomplate->redraw = true;
					
					flipperleft2->rest = flipperleft2->redraw = true;		
					flipperright2->rest = flipperright2->redraw = true;				
				}
				if(side > 0) {
					flipperleft1->rp = f[p++];
					flipperright1->rp = f[p++];
					leftpoints = i[p++];
					leftballs = i[p++];	
																																	
					lefttopplate->rest = lefttopplate->redraw = true;
					leftbottomplate->rest = leftbottomplate->redraw = true;
					
					flipperleft1->rest = flipperleft1->redraw = true;
					flipperright1->rest = flipperright1->redraw = true;																				
					
				}
				
				fixem(i[p++]);
																											
			}
		}				
			
		
		// Determine who is in charge of what balls and adjust it
		
		// If you read this and have no clue what is going on, then better look
		// somewhere else - this is bad code and shouldn't actually be working.
		// Places to look for network code: Mail me, ask in the Allegro mailing
		// lists, ask in the http://allegro.cc forums, you will get *lots* of
		// links to good tutorials from any of them (Just I was too lazy to read
		// them)
						
		for(int b = 0; b < BALLNUM; b++) {
						
			if(!ball[b]->in_charge) {
				
				if(((ball[b]->xp > SCREEN_W / 2 + 4) && side == 1) ||
			   	((ball[b]->xp < SCREEN_W / 2 - 4) && side == -1))
			   
			   ball[b]->in_charge = true;								
			   
			}
				
			if(ball[b]->in_charge) {
					
				if(((ball[b]->xp > SCREEN_W / 2 - 4) && side == -1) ||			
				   ((ball[b]->xp < SCREEN_W / 2 + 4) && side == 1)) {
				  	
					ball[b]->in_charge = false;											
				}
			}
		}
		
		// Sending
							  	
		if(time2send <= 0) {
			time2send = netrate;
				
			char buffer[256];
			float *f = (float *)buffer;
			int *i = (int *)buffer;
			int l = 0;
				
			for(int b = 0; b < BALLNUM; b++) {	
				if(ball[b]->in_charge && !ball[b]->out) {	
					
					i[l++] = 1 + ball[b]->who + b * 256;         
					         
					f[l++] = ball[b]->xp;
					f[l++] = ball[b]->yp;
					f[l++] = ball[b]->xc;
					f[l++] = ball[b]->yc;
				}
			}
					
			if(side == 1) {
				f[l++] = flipperleft2->rp;
				f[l++] = flipperright2->rp;
				i[l++] = rightpoints;
				i[l++] = rightballs;				
			}
			if(side == -1) {
				f[l++] = flipperleft1->rp;
				f[l++] = flipperright1->rp;
				i[l++] = leftpoints;
				i[l++] = leftballs;												
			}	
			i[l++] = checkem();					
				
			net_send(network->channel, buffer, l * 4);	
									
		} 					
	}	
#endif	
	// Check for lost balls		
	
	for(int b = 0; b < BALLNUM; b++) {
		
		if(ball[b]->out) continue;
		
		if(ball[b]->yp - ball[b]->R > SCREEN_H) {
						
			{
			
				ball[b]->out = true;
				if(!ball[b]->in_charge) ball[b]->ghost = true;
				
				play_sample(bleam, 255, int(1 + 254.0 * (ball[b]->xp / SCREEN_W)), 1000, 0);
				
				middleplate->rest = middleplate->redraw = true;		
																	
				if(ball[b]->xp < SCREEN_W/2) {
					
					leftbottomplate->rest = leftbottomplate->redraw = true;
					
					if(ball[b]->in_charge || netrate <= 0) 
					if(ball[0]->out && ball[1]->out && ball[2]->out && ball[3]->out) {
						if(leftballs > 0) newball = 1; else {
							game_over = -1;														
						}
					}
					
					if(leftballs) leftballs--;
					
					
					rightpoints += 1000;					
					righttopplate->rest = righttopplate->redraw = true;										
											
				} else {
																							
					rightbottomplate->rest = rightbottomplate->redraw = true;
					
					if(ball[b]->in_charge || netrate <= 0) 
					if(ball[0]->out && ball[1]->out && ball[2]->out && ball[3]->out) {
						if(rightballs > 0) newball = -1; else {
							game_over = 1;
						}
					}
					
					if(rightballs) rightballs--;			
					
					leftpoints += 1000;
					lefttopplate->rest = lefttopplate->redraw = true;	
										
				}																			
			}
		}
	}
	
	// Handle events
	
	if(newball)  {
				
		int bn = 0;		
		if(!ball[0]->out) {
			bn = 1;
			if(!ball[1]->out) {
				bn = 2;
				if(!ball[2]->out) bn = 3;
			}
		}
		
		middleplate->rest = middleplate->redraw = true;		
		
		if(ball[bn]->out) {
									
			ball[bn]->out = false;
			ball[bn]->xp = SCREEN_W / 2;
			ball[bn]->yp = SCREEN_H / 2;
			ball[bn]->xc = 0;
			ball[bn]->yc = 0;
			ball[bn]->who = newball;
			ball[bn]->in_charge = false;
			
#ifndef NO_LIBNET														

			if(netrate > 0) {
				char buffer[256];
				//float *f = (float *)buffer;
				int *i = (int *)buffer;
				int l = 0;
											
				i[l++] = 1 + ball[bn]->who + bn * 256;			    
															
				net_send(network->channel, buffer, l * 4);
			}			
			
#endif			
			
		}
		
		newball = 0;
	}
						
	// Flipper Controls
	
	if(side <= 0) {											
		if((!AI_left && key[keyleft1]) || (AI_left && flipperleft1->rp > -M_PI / 4)) {
			if(flipperleft1->rp >= M_PI / 4) {
				flipperleft1->rc = -FLIPPER_ANGEL_SPEED;			
			}
		} else if(flipperleft1->rp <= -M_PI / 4) {
			flipperleft1->rc = FLIPPER_ANGEL_SPEED;		
		}
		
		if((!AI_left && key[keyright1]) || (AI_left && flipperright1->rp < 4.9 * M_PI / 4)) {
			if(flipperright1->rp <= 3.1 * M_PI / 4.0) {
				flipperright1->rc = FLIPPER_ANGEL_SPEED;			
			}
		} else if(flipperright1->rp >= 4.9 * M_PI / 4.0) {
			flipperright1->rc = -FLIPPER_ANGEL_SPEED;		
		}
	}
	
	if(side >= 0) {		
		if((!AI_right && key[keyleft2]) || (AI_right && flipperleft2->rp > -M_PI / 4)) {
			if(flipperleft2->rp >= M_PI / 4) {
				flipperleft2->rc = -FLIPPER_ANGEL_SPEED;
				
			}
		} else if(flipperleft2->rp <= -M_PI / 4) {
			flipperleft2->rc = FLIPPER_ANGEL_SPEED;
			
		}
	
		if((!AI_right && key[keyright2]) || (AI_right && flipperright2->rp < 4.9 * M_PI / 4)) {
			if(flipperright2->rp <= 3.1 * M_PI / 4.0) {
				flipperright2->rc = FLIPPER_ANGEL_SPEED;
				
			}
		} else if(flipperright2->rp >= 4.9 * M_PI / 4.0) {
			flipperright2->rc = -FLIPPER_ANGEL_SPEED;
		}		
	}
		
	// Physics	
								
	remain +=d;
	// do physics in 0.01 second steps
	while(remain > 0) {
		
		if(!ball[0]->out) ball[0]->move(0.01);
		if(!ball[1]->out) ball[1]->move(0.01);
		if(!ball[2]->out) ball[2]->move(0.01);
		if(!ball[3]->out) ball[3]->move(0.01);					
		
		flipperleft1->move(0.01);
		flipperright1->move(0.01);	
		flipperleft2->move(0.01);
		flipperright2->move(0.01);		
		remain -= 0.01;
			
		// balls collision
		// This doesn't quite work out, because, imagine this: a flipper moves
		// (and does no collision detection). So if it hits a ball, the ball
		// adjusts its position. Now it collides with another ball. We don't
		// know though which ball collides with the flipper, and which not.
		// So we can't adjust the position (without risking a ball going
		// through something else). But the below code does it anyway, only
		// it just adjusts it by one pixel..
		// I know, a good collision detection wouldn't require adjusting, but I
		// only designed this for one ball and there it would have worked out..
		for(int j = 0; j < BALLNUM; j++) {
			if(ball[j]->out) continue;
			for(int i = 0; i < BALLNUM; i++) {
				if(i == j) continue;
				if(ball[i]->out) continue;	
				float nx, ny;								
				if(ball[j]->ballcollision(ball[i], &nx, &ny)) {	
						
					ball[j]->collide(ball[i], nx, ny);
					ball[i]->collide(ball[j], -nx, -ny);
										
					float n = sqrt(nx * nx + ny * ny);		
					ball[j]->xp += 1 * -nx / n;
					ball[j]->yp += 1 * -ny / n;
					ball[i]->xp += 1 * nx / n;
					ball[i]->yp += 1 * ny / n;																								
					
				}
			}
		}
	}				
			
	if(game_over) {
		if(game_not_over) {
			game_not_over = 0;
			dropem();
		}
	}
	
	for(Target *target = Target::targets; target; target = target->nexttarget) {		
		target->move(d);		
	}
	
	if(squirrelinvasion) {
		nextsquirreltime -= d;
		if(nextsquirreltime <= 0) {
			nextsquirreltime = 5;
			nextsquirrel = true;
		}
	}
		
	// Sounds
		
	for(int b = 0; b < BALLNUM; b++) {		
		if(ball[b]->hit > 0) {		
			int v, s;
			if(ball[b]->hit < 255) v = ball[b]->hit / 2; else v = 255;
			s = int(-ball[b]->yc);
			if(s < -400) s = -400;
			if(s > 400) s = 400;
			play_sample(boing, v, int(1 + 254.0 * (ball[b]->xp / SCREEN_W)), 1000 + s, 0);
			ball[b]->hit = 0;
		}
		
		if(ball[b]->hit2 > 0) {		
			int v, s;
			if(ball[b]->hit2 < 255) v = ball[b]->hit2 / 2; else v = 255;
			s = int(-ball[b]->yc);
			if(s < -400) s = -400;
			if(s > 400) s = 400;
			play_sample(biong, v, int(1 + 254.0 * (ball[b]->xp / SCREEN_W)), 1000 + s, 0);
			ball[b]->hit2 = 0;
		}
		
		if(ball[b]->hit3 > 0) {				
			play_sample(squeak, 255, int(1 + 254.0 * (ball[b]->xp / SCREEN_W)), 1000, 0);
			ball[b]->hit3 = 0;
		}  
		 
		if(ball[b]->hit4 > 0) { 
			play_sample(vrumm, 255, int(1 + 254.0 * (ball[b]->xp / SCREEN_W)), 1000, 0);
			ball[b]->hit4 = 0;        
		}
	}	
	
	return 0;
}

void Game::redraw(void) {
	Part *part;

	for(part = Part::first; part; part = part->next) {
		part->restore();				
	}
	for(part = Part::first; part; part = part->next) {
		part->draw();				
	}
	for(part = Part::first; part; part = part->next) {
		part->draw2();				
	}
	/*
	text_mode(0);
	textprintf(page, font, SCREEN_W / 2, 0, makecol(255, 255, 255), "%3d %c%c %.2f%c", fps,
		ball[0]->in_charge ? 'C':'-',
		ball[1]->in_charge ? 'C':'-', netrate,
		side > 0 ? '>' : side < 0 ? '<' : 'X');
	*/
	if(game_over) {
		draw_sprite(page, ankh, SCREEN_W / 2 - 50, SCREEN_H / 2 - 50);
		text_mode(-1);
		textprintf_centre(page, (FONT *)(numberfont[0].dat),
			SCREEN_W / 2, SCREEN_H / 2 - 90, 
			-1, "(X-Viila) Ave atque Vale!");
	}		
}