/****************************************************************
 * Copyright (C) 2002  Joel Muzzerall
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 ******************************************************************/

/******************************************************
 * This is by far the most important part of the game.
 * It is the engine by which it runs.  It is also just
 * one long, well commented method.  Manage.  It will
 * recieve a filestream with levels in it.
 *******************************************************/

#include "Manager.h"
#include "SysMusic.h"
#include <time.h>

Manager::Manager(){
	myGlobals = Globals::Instance();
	buf = myGlobals->getGraphics()->get_buffer();

	levelup_bmp = load_bitmap("gfx//default//screens//levelup.jpg", NULL);
	gameover_bmp = load_bitmap("gfx//default//screens//gameover.jpg", NULL);
	if (levelup_bmp == NULL || gameover_bmp == NULL){
		allegro_message("Could not load a screen for the manager.");
		allegro_exit();
	}
}

void Manager::init(ifstream &the_fin){
	fix_difficulty();
	gameOver = 0;
	gameWon = 0;
	lives = 2;
	numBalls = 0;
	score = 0;
	levelNum = 0;
	levelOver = 0;
	speed = myGlobals->getStartSpeed();
	ticks = 0;
	bonus = 0;

	for (int k = 0; k < 100; k++)
		balls[k] = Init_Ball();

	// ***** The double buffer and the levels initialization *****
	currentLevel = new GameLevel();

	the_fin.read((char *) &numLevels, sizeof(numLevels));
	fin = &the_fin;

	// ***** Initialization of sound *****
	hitPaddleSound = load_wav("snd/snd1.wav");
	hitBlockSound = load_wav("snd/snd2.wav");
	hitSideSound = load_wav("snd/snd3.wav");
	hitPowerUpSound = load_wav("snd/snd4.wav");

	if (hitPaddleSound == NULL || hitBlockSound == NULL || hitSideSound == NULL || hitPowerUpSound == NULL){
		allegro_message("Error loading a sound.");
		allegro_exit();
	}

	// ***** Initialization of Music *****
	Music = load_datafile("dat/Music.dat");
	if (Music == NULL){
		set_gfx_mode(GFX_TEXT, 320, 240, 0, 0);
		allegro_message("Cannot load Music.dat");
		allegro_exit(); }

		//MIDI *BGMusic = (MIDI *)Music[retrace_count % 9].dat;
		//play_midi(BGMusic, 0);

		SysMusic = load_datafile("dat/SysMusic.dat");
		if (SysMusic == NULL){
			set_gfx_mode(GFX_TEXT, 320, 240, 0, 0);
			allegro_message("Cannot load SysMusic.dat");
			allegro_exit(); 
		}

		LevelupMIDI = (MIDI *)SysMusic[LevelupMusic].dat;

		// ***** Loading the backgrounds *****
		for (int i = 0; i < NUM_BACKGROUNDS; i++){
			char charbuffer[128];
			sprintf(charbuffer, "gfx//default//bgs//bg%d.jpg", i + 1);
			backgrounds[i] = load_bitmap(charbuffer, NULL);
			if (backgrounds[i] == NULL){
				allegro_message("Could not load a background image.");
				allegro_exit();
			}
		}

		ballPic = getBallPic();
}

/*****************************************************************************
 * This sets some variables depending on the difficulty of the game.  For
 * example, the ball will generally move faster on harder difficulties
 * **************************************************************************/
void Manager::fix_difficulty(){
	switch (myGlobals->getDifficulty()){
		case 1:
			myGlobals->setStartSpeed(3); 
			myGlobals->setMaxSpeed(8); 
			break;

		case 2:
			myGlobals->setStartSpeed(5); 
			myGlobals->setMaxSpeed(11); 
			break;

		case 3: 
			myGlobals->setStartSpeed(5); 
			myGlobals->setMaxSpeed(16); 
			break;

		case 4:
			myGlobals->setStartSpeed(9); 
			myGlobals->setMaxSpeed(16); 
			break;
	}
}

/***********************************************************
 * This function has been a learning experience for me.
 * As you can see, it is several hundred lines long, and
 * it does a lot.  What has my lesson been, you might ask?
 * I've learned to never, EVER, --==EVER==-- make a
 * function this long again.  It just doesn't work.  It's
 * an invitation to have a project collapse under its own
 * weight.  A function this long simply cannot be organized.
 * I don't know what I was thinking.
 ***********************************************************/
int Manager::manage_game()
{
	// ***** The main loop, of the whole game *****
	while (gameOver == 0)
	{
		init_level();

		// ***** The subloop, for every life *****
		while (lives != 0 && levelOver == 0)
		{
			init_life();
			if (!hover_ball()) // Wait for click
				return 0; 

			// ***** Another subloop, for every tick *****
			while (numBalls > 0 && levelOver == 0)
			{
				if (!init_tick())
					return 0;

				for (int i = 0; i < numBalls; i++){
					moveball(i);
					paddle_ball(i);

					// collide detect with bricks
					// Don't try to understand this segment.
					// I barely understand it myself.
					// It works.  It is magic.
					if (balls[i]->y < 20 + 18 * 14 && balls[i]->y > 20)
					{
						block_ball_collision(i);
						// Activate special block if one or two or three are hit
						for (int k = 0; k < 3; k++){
							do_collision(k, i);
						} // For each collision 
					} // If the balls are in range of the bricks 
				} // For all balls

				// Store the next frame in the buffer
				draw_screen();

				// Delay till beginning of next tick
				while (clock() < startoftick + 5)
					;

				// Actually stick that screen down
				myGlobals->getGraphics()->update();
			}// while there are balls

			if (numBalls == 0)
				lives--;

			if (lives == 0)
				gameOver = 1;
		} // End subloop

		if (numLevels == 0 && lives != 0){
			gameOver = 1;
			gameWon = 1; 
		}
	} // End main loop

	if (gameWon == 1)
		showWin();
	else showLose();

	return score;
}

/*****************************************************************************
 * init_level initializes a bunch of values, to be reset every level.  It also
 * tests whether or not the game has been won (no more levels) or lost (no
 * more lives)
 * ***************************************************************************/
void Manager::init_level(){
	// Check for game over and load level if not
	if (numLevels == 0){
		gameWon = lives;
		lives = 0;
	}
	else
		currentLevel->load(*fin);

	// Update variables / flags
	numLevels--;
	levelNum++;
	levelOver = 0;
	bonus = POTENTIAL_BONUS;

	// Choose a new BG and BG music for the new level
	currentBG = retrace_count % 5;
	MIDI *BGMusic = (MIDI *)Music[retrace_count % 9].dat;
	play_midi(BGMusic, 1);
}

/*****************************************************************************
 * init_life sets some variables for every life
 * ***************************************************************************/
void Manager::init_life(){
	// Reset the counters to prepare for new life
	speed = myGlobals->getStartSpeed();
	ticks = 0;
	thePaddle = new Paddle();
	numBalls = 1;
	Reset_Ball(balls[0]);
}

/*****************************************************************************
 * hoverball draws the ball above the paddle, and waits for the player to
 * click, in order to make the game start.  Returns whether the game should
 * contine (false if the player decides to quit)
 * ***************************************************************************/
bool Manager::hover_ball(){
	while (mouse_b & 1)
		;
	while (keypressed())
		readkey();

	// Draw the screen with a hovering ball until the player clicks
	while (!(mouse_b & 1) && !key[KEY_SPACE])
	{
		if (key[KEY_ESC])
			if (quitquery() == 1){
				return false;
			}
		buffer_wait();
		myGlobals->getGraphics()->update();
	}
	return true;
}

/*****************************************************************************
 * init_tick initializes some values for the beginning of each tick, and
 * updates others.  Returns whether or not the game should continue, which
 * is false if the player decides to quit
 * ***************************************************************************/
bool Manager::init_tick(){
	startoftick = clock();
	ticks++;
	bonus--;
	if (bonus < 0)
		bonus = 0;

	if (ticks % 1000 == 0)
		speed++;

	if (speed > myGlobals->getMaxSpeed())
		speed = myGlobals->getMaxSpeed();

	if (key[KEY_ESC])
		if (quitquery() == 1)
			return false;
	return true;
}


/*****************************************************************************
 * Draws the backbuffer for when the game is waiting for a mouse click, and
 * the ball is hovering above the paddle 
 * **************************************************************************/
void Manager::buffer_wait(){
	clear_bitmap(buf);
	blit(backgrounds[currentBG], buf, 0, 0, 0, 0, SCN_WIDTH, SCN_HEIGHT);
	currentLevel->draw(buf);
	thePaddle->draw(buf);
	sprintf(theString, "Score: %d          Level: %d          Bonus: %d          Lives: %d", score, levelNum, bonus, lives);
	textout_centre_ex(buf, font, theString, SCN_WIDTH / 2, 1, 10000, -1);
	masked_blit(ballPic, buf, 0, 0, 310, 300, 10, 10);
	// TODO: Get rid of these once the graphics manages the particles
	// like it should be doing
}

/*****************************************************************************
 * Moveball moves ball #i, and rebounds it off the edge of the screen if
 * necessary
 * ***************************************************************************/
void Manager::moveball(int i){
	// Move the ball
	Update_Ball(balls[i]);

	// Keep ball on the screen
	if (balls[i]->y < 20 && balls[i]->yv < 0){
		play_sample(hitSideSound, myGlobals->getSoundVolume(), 128, 1000, 0);
		balls[i]->yv = balls[i]->yv * -1;
		balls[i]->y = 20; 
	}
	if (balls[i]->x < 1 && balls[i]->xv < 0){
		play_sample(hitSideSound, myGlobals->getSoundVolume(), 128, 1000, 0);
		balls[i]->xv = balls[i]->xv * -1;
		balls[i]->x = 1; 
	}
	if (balls[i]->x > SCN_WIDTH - 10 && balls[i]->xv > 0){
		play_sample(hitSideSound, myGlobals->getSoundVolume(), 128, 1000, 0);
		balls[i]->xv = balls[i]->xv * -1;
		balls[i]->x = SCN_WIDTH - 10; 
	}
}

/*****************************************************************************
 * Rebound the given ball off the paddle if appropriate.  Delete the ball
 * if it has slipped by the paddle
 * ***************************************************************************/
void Manager::paddle_ball(int i){
	// Rebound off the paddle
	if (balls[i]->y > 455)
		if (thePaddle->rebound(balls[i], speed) == 1)
			play_sample(hitPaddleSound, myGlobals->getSoundVolume(), 128, 1000, 0);

	// Lose a life if past the paddle
	if (balls[i]->y > 465){
		numBalls--;
		for (int j = i; j < numBalls; j++)
			balls[j] = balls[j+1];
		balls[numBalls + 1] = Init_Ball();
		i--;
	}
}

/*****************************************************************************
 * Tests for collision between a block and a ball.  If there is one, it will
 * test to see if there are multiple collisions.  There can be up to three.
 * save all of them in the collisionType array.  They will be handled later.
 * ***************************************************************************/
void Manager::block_ball_collision(int i)
{
	Xblock = (balls[i]->x + 4) / 32;
	Yblock = (balls[i]->y - 15) / 18;

	// The Xblock and YBlock are the array positions of the blocks
	// This is their location
	int block_x_pos = Xblock * 32;
	int block_y_pos = (Yblock+1) * 18;

	collisionType[0] = ZERO;
	collisionType[1] = ZERO;
	collisionType[2] = ZERO;

	if (Xblock < 20 && Xblock > -1 && Yblock < 14 && Yblock > -1)
	{
		int dir = (balls[i]->yvi > 0) ? down : up;
		collisionType[0] = currentLevel->collide(Xblock, Yblock, dir);

		if (collisionType[0] > 0)
		{
			// Throw in some particles for the collision
			for (int jopa = 0; jopa < EXPLOSION_PARTICLES; jopa++){
				int pxvel = rand() % (PARTICLE_MAX_VELOCITY * 2) - PARTICLE_MAX_VELOCITY;
				int pyvel = rand() % (PARTICLE_MAX_VELOCITY * 2) - PARTICLE_MAX_VELOCITY;
				BITMAP *the_particle = currentLevel->get_block_pic(Xblock, Yblock);
				myGlobals->getGraphics()->add_particle(255, 30, the_particle, block_x_pos, block_y_pos, pxvel, pyvel);
			}
			
			// Figure out if the ball collided with two blocks, side by side
			if (((balls[i]->x - balls[i]->xvi) + 4) / 32 < Xblock && Xblock != 0)
				collisionType[1] = currentLevel->collide(Xblock - 1, Yblock, dir);
			else if (((balls[i]->x - balls[i]->xvi) + 4) / 32 > Xblock && Xblock != 19)
				collisionType[1] = currentLevel->collide(Xblock + 1, Yblock, dir);

			// Figure out it the ball collided with two blocks, top on bottom
			if (((balls[i]->y - balls[i]->yvi) - 15) / 18 < Yblock && Yblock != 0)
				collisionType[2] = currentLevel->collide(Xblock, Yblock -1, dir);
			else if (((balls[i]->y - balls[i]->yvi) - 15) / 18 > Yblock && Yblock != 13)
				collisionType[2] = currentLevel->collide(Xblock, Yblock +1, dir);

			// Rebound the ball hopefully in the right direction
			if (collisionType[1] > 0)
				balls[i]->yv = balls[i]->yv * -1;
			else if (collisionType[2] > 0)
				balls[i]->xv = balls[i]->xv * -1;
			else
			{
				if (((balls[i]->x - balls[i]->xvi) + 4) / 32 != Xblock)
					balls[i]->xv = balls[i]->xv * -1;
				if (((balls[i]->y - balls[i]->yvi) - 15) / 18 != Yblock)
					balls[i]->yv = balls[i]->yv * -1;
			}
		}
	}
}

/*****************************************************************************
 * do_collision does the effect of the collision.  It is passed the collision
 * type, and the ball that caused this problem :)
 * ***************************************************************************/
void Manager::do_collision(int k, int i)
{
	switch (collisionType[k])
	{
		case BC_normal:
			score = score + 10;
			play_sample(hitBlockSound, myGlobals->getSoundVolume(), 128, 1000, 0);
			break;

		case BC_multi:
			score = score + 50;
			if (numBalls == MAX_BALLS)
				break;
			balls[numBalls] = Init_Ball();
			balls[numBalls]->yv = balls[i]->yv / 2;
			balls[numBalls]->xv = balls[i]->xv * -1;
			balls[numBalls]->xd = balls[i]->x;
			balls[numBalls]->yd = balls[i]->y;
			numBalls++;
			play_sample(hitPowerUpSound, myGlobals->getSoundVolume(), 128, 1000, 0);
			break;

		case BC_uber:
			score = score + 100;
			for (int j = 0; j < 8; j++)
			{
				if (numBalls > MAX_BALLS-1)
					break;
				balls[numBalls] = Init_Ball();
				balls[numBalls]->yv = 3 - j*0.2;
				balls[numBalls]->xv = 4 - j;
				if (balls[numBalls]->xv > myGlobals->getMaxSpeed())
					balls[numBalls]->xv = myGlobals->getMaxSpeed();
				if (balls[numBalls]->yv > myGlobals->getMaxSpeed())
					balls[numBalls]->yv = myGlobals->getMaxSpeed();
				balls[numBalls]->xd = balls[i]->x;
				balls[numBalls]->yd = balls[i]->y;
				if (balls[numBalls]->yv < 1 && balls[numBalls]->yv > -1)
					balls[numBalls]->yv = 1;
				numBalls++;
			}
			play_sample(hitPowerUpSound, myGlobals->getSoundVolume(), 128, 1000, 0);
			break;

		case BC_life:
			lives++;
			score = score + 100;
			play_sample(hitPowerUpSound, myGlobals->getSoundVolume(), 128, 1000, 0);
			break;

		case BC_fast:
			score = score + 50;
			speed = 10;
			play_sample(hitPowerUpSound, myGlobals->getSoundVolume(), 128, 1000, 0);
			break;

		case BC_slow:
			score = score + 50;
			speed = 3;
			play_sample(hitPowerUpSound, myGlobals->getSoundVolume(), 128, 1000, 0);
			break;

		case BC_grow:
			score = score + 50;
			thePaddle->grow();
			play_sample(hitPowerUpSound, myGlobals->getSoundVolume(), 128, 1000, 0);
			break;

		case BC_shrink:
			score = score + 50;
			thePaddle->shrink();
			play_sample(hitPowerUpSound, myGlobals->getSoundVolume(), 128, 1000, 0);
			break;

		case LC_leveldone:
			score = score + bonus;
			levelOver = 1;
			sprintf(scoreString, "Bonus award is %d point!", bonus);
			textout_centre_ex(buf, font, scoreString, SCN_WIDTH / 2, SCN_HEIGHT - 20, 165000, -1);
			play_midi(LevelupMIDI, 0);
			while (keypressed())
				readkey();
			while (mouse_b & 1){
				if (keypressed())
					break;
				blit(levelup_bmp, buf, 0, 0, 0, 0, SCN_WIDTH, SCN_HEIGHT);
				myGlobals->getGraphics()->update();
			}
			while (!(mouse_b & 1)){
				if (keypressed())
					break;
				blit(levelup_bmp, buf, 0, 0, 0, 0, SCN_WIDTH, SCN_HEIGHT);
				myGlobals->getGraphics()->update();
			}
			break;
	} // Switch on collision type
}

/*****************************************************************************
 * Draws the screen when the game is being played.
 * ***************************************************************************/
void Manager::draw_screen(){
	blit(backgrounds[currentBG], buf, 0, 0, 0, 0, SCN_WIDTH, SCN_HEIGHT);
	currentLevel->draw(buf);
	thePaddle->draw(buf);
	for (int i = 0; i < numBalls; i++){
		masked_blit(ballPic, buf, 0, 0, balls[i]->x, balls[i]->y, 10, 10);
	}
	sprintf(theString, "Score: %d          Level: %d          Bonus: %d          Lives: %d", score, levelNum, bonus, lives);
	textout_centre_ex(buf, font, theString, SCN_WIDTH / 2, 1, 10000, -1);
}

/*********************************************
 * Ask the player if he really wants to quit
 *********************************************/
int Manager::quitquery()
{
	char theString[64];
	sprintf(theString, " Really Quit?  [Y/N] ");
	textout_centre_ex(screen, font, theString, SCN_WIDTH / 2, SCN_HEIGHT / 2, 0xffffff, 0x000000 );

	while (1){
		if (key[KEY_N])
			return 0;
		if (key[KEY_Y])
			return 1;
	}
}

/********************************************* * 
 * This is the function for inadequate players
 *********************************************/
void Manager::showLose()
{
	play_midi((MIDI *)SysMusic[GameoverMusic].dat, 0);
	char scoreString[64];
	sprintf(scoreString, "     Final Score: %d         ", score);

	blit(gameover_bmp, screen, 0, 0, 0, 0, SCN_WIDTH, SCN_HEIGHT);
	textout_centre_ex(screen, font, scoreString, SCN_WIDTH / 2, SCN_HEIGHT - 20, 165000, -1);
	while (keypressed())
		readkey();
	while (mouse_b & 1)
		if (keypressed())
			return;
	while (!(mouse_b & 1))
		if (keypressed())
			return;
}

/******************************************
 * This is the winning game sequence.
 * Yes, it's clunky, but that's okay.
 ******************************************/
void Manager::showWin()
{
	play_midi((MIDI *)SysMusic[WinMusic].dat, 0);
	char scoreString[64];
	sprintf(scoreString, "     Final Score: %d         ", score);
	char gfxstring[128];

	for (int i = 0; i < 2; i++){
		while(keypressed())
			readkey();	
		BITMAP *cur_screen;
		sprintf(gfxstring, "gfx//default//screens//win0%d.jpg", i);
		cur_screen = load_bitmap(gfxstring, NULL);

		if (cur_screen == NULL){
			char errstring[128];
			sprintf(errstring, "Could not load %s", gfxstring);
			allegro_message(errstring);
			allegro_exit();
		}

		blit(cur_screen, buf, 0, 0, 0, 0, SCN_WIDTH, SCN_HEIGHT);
		textout_centre_ex(buf, font, scoreString, SCN_WIDTH / 2, SCN_HEIGHT - 20, 165000, -1);
		
		// Delay for a while, or until the player hits a key
		for (int j = 0; j < 1000; j++){
			myGlobals->getGraphics()->update();
			if (keypressed()){
				readkey();
				j = 2000;
			}
		}
	}
}

/******************************************
 * This returns the picture of a ball
 ******************************************/
BITMAP *Manager::getBallPic()
{
	DATAFILE *bar = load_datafile("dat/Sprites.dat");
	if (bar == NULL){
		set_gfx_mode(GFX_TEXT, 320, 240, 0, 0);
		allegro_message("Cannot load Sprites.dat");
		rest(5000);
		allegro_exit(); }

		BITMAP *temp = create_bitmap(10, 10);

		blit((BITMAP *)bar[ball].dat, temp, 0, 0, 0, 0, 10, 10);
		return temp;
}
