/***

  World class

***/

#include <iostream.h>
#include <stdio.h>

#include <fstream.h>
#include "DisplayInterface.h"
#include "Globals.h"
#include "Object.h"
#include "World.h"
#include "Space.h"
#include "Wall.h"
#include "Pellet.h"
#include "Mobile.h"
#include "User.h"
#include "Ghost.h"

// default constructor
World::World( DisplayInterface &d )
{
	objects = 0;
	width = 0;
	height = 0;
	x = 0;
	y = 0;
	time = 0;

	lives = 3;
	score = 0;
	level = 1;
	new_lives = 0;

	pellets_in_world = 0;

	game_over = false;

	wallobject = new Wall( d, x, y );
}

// Change the score by given score_delta
void World::ChangeScoreBy (DisplayInterface &d, int score_delta)
{
	score += score_delta;
	
	// Incremants lives every time score_inc_new_life is reached
	if( (score / score_inc_new_life) > new_lives)
	{
		lives++;
		new_lives++;
		d.UpdateLivesStatus(lives);
		d.StopWAV();
		d.PlayWAV("newlife.wav");
	}

	
	d.UpdateScoreStatus(score);
}

void World::ChangeLivesBy (DisplayInterface &d, int lives_delta)
{
	lives += lives_delta;
	// update the display
	d.UpdateLivesStatus(lives);
}

// Dafault load function used when first level is loaded
//   (no previous world to load over)
bool World::load( DisplayInterface &d, int level_number)
{
	return load(d, level_number, NULL);
}

// returns true if a world was successfully loaded.
// Allows a newer world to be laid on top of oldworld.  Retains
// eaten pellets, redraws walls, ghost, spaces, and user
bool World::load( DisplayInterface &d, int level_number, World *oldworld)
{
	pellets_in_world = 0;

	// Level files will be in the format: "level01.txt"
	// Level number in form: 01, 02, 10, 50
	char filename[20];
	sprintf(filename, "level%02d.txt", level_number);

	ifstream in( filename, ios::nocreate | ios::in );
	if( !in ) return false;

	in >> width >> height;
	if (! oldworld)
	{
		// if reloading level, don't reallocate memory.
		// TODO: should ensure alloc'ed memory size still matches
		//       size of level.  somebody could've screwed with something.
		objects = new Object **[width];
		for( int k = 0; k < width; k++ ) 
		{
			objects[k] = new Object *[height];
		}
	}

	for( int j = 0; j < height; j++ ) 
	{
		for( int i = 0; i < width; i++ ) 
		{
			bool needshow = false;
			char c = '\n';
			// Allows for both Unix and Windows style end char
			while( c == '\n' || c == '\r')
				in.get( c );
			switch( c ) {
			case 'X': case 'x':
				if (oldworld)
					needshow = true;
				else
					objects[i][j] = new Wall( d, i, j );
				break;
			case ' ':
				if (oldworld)
				{
					if (oldworld->objects[i][j]->identify() == pellet_id ||
					    oldworld->objects[i][j]->identify() == space_id) 
					{ 
						needshow = true;
					}
					else
					{
						delete oldworld->objects[i][j];
						objects[i][j] = new Space( d, i, j );
					}
				}
				else
					objects[i][j] = new Space( d, i, j );

				break;
			case 'U': case 'u':
				if (oldworld)
					delete oldworld->objects[i][j];
				objects[i][j] = new User( d, i, j, user_speed, NONE );
				break;
			// captial O and capital zero (heh.. it's tall) are super pellets.
			// see the constructor?  yeah.
			case 'o': case '0': case 'O':
				if (oldworld)
				{
					if (oldworld->objects[i][j]->identify() == space_id) 
					{ 
						needshow = true;
					}
					else
					{
						delete oldworld->objects[i][j];
						objects[i][j] = new Pellet (d, i, j, c!='o');
						pellets_in_world++;
					}
				}
				else
				{
					objects[i][j] = new Pellet( d, i, j, c!='o' );
					pellets_in_world++;
				}
				break;
			case 'G': case 'g':
				if (oldworld)
					delete oldworld->objects[i][j];
				objects[i][j] = new Ghost( d, i, j, ghost_speed, NONE );
				break;
			default:
				cout << "oh no " << c << endl;
				cout << "terminate";
				exit(1);
			}

			if (needshow)
				objects[i][j]->show(d, this, i, j);
		}
	}

	x = 0;
	y = 0;

	time = 0;

	//Update the dislpay above gamemboard
	d.UpdateScoreStatus(score);
	d.UpdateLivesStatus(lives);
	d.UpdateLevelStatus(level);

	return true;
}

// acts on the current active object and advances to another object
// Keeps track of time
bool World::advance( DisplayInterface &d )
{
	if (game_over)
		return false;

	if( x < width - 1 ) {
		x++;
	}
	else if( y < height - 1 ) {
		x = 0;
		y++;
	}
	else {
		x = 0;
		y = 0;
		time++;
		if( time > 100000 )
			time = 1;
		d.pause( time_delay );
	}
	return objects[x][y]->act( d, this, x, y, time );
}

// set the current active object
void World::set_current( int _x, int _y )
{
	x = _x;
	y = _y;
}

// return a pointer to a specified object
Object *World::get_object( int _x, int _y )
{
	if( _x < 0 || _x >= width || _y < 0 || _y >= height )
		return wallobject;
	return objects[_x][_y];
}

// sets an object in the world
void World::set_object( DisplayInterface &d, int _x, int _y, Object *o )
{
	objects[_x][_y] = o;
	objects[_x][_y]->show(d, this, _x, _y);
}

// swaps two objects in the world
void World::swap_object( DisplayInterface &d, int x1, int y1, int x2, int y2 )
{
	Object *tmp = objects[x1][y1];
	objects[x1][y1] = objects[x2][y2];
	objects[x2][y2] = tmp;
	objects[x1][y1]->show(d, this, x1, y1);
	objects[x2][y2]->show(d, this, x2, y2);
}

int World::GetLevel () const
{
	return level;
}

int World::GetLives () const
{
	return lives;
}

int World::GetScore () const
{
	return score;
}

void World::UserDied (DisplayInterface &d, int dx, int dy)
{
	// If user has no more lives, game terminates
	if (--lives <= 0)
	{
		d.PlayWAV("ending.wav");
		d.DrawSprite( dx, dy, SPRITE_PACMAN_DEAD);
		d.pause( show_user_death_time );
		d.clear( );
		d.DrawString( 2, 5, "GAME OVER!");
		d.pause( ending_sound_time );
	}
	else
	{
		// we still gots us lives.  reload level.  play again.
		d.PlayWAV("userdie.wav");

		//show bloody pacman
		d.DrawSprite( dx, dy, SPRITE_PACMAN_DEAD);
		d.pause( show_user_death_time );

		d.clear();
		d.DrawString(5,10,"You's Dead.  But you gots lives left.");
		d.pause ( show_user_death_time * 2);
		d.clear();

		//destroy();
		// Reload the level, with the old pellets remaining
		load(d, level, this);
	}
}

void World::AtePellet (DisplayInterface &d)
{
	//If user ate all the pellets
	if (--pellets_in_world <= 0)
	{
		//destroy the old world, see if there is a new level to load
		destroy(); 
		if (! load(d, ++level))
		{
			d.clear();
			d.PlayWAV("userwin.wav");
			d.DrawString(2,5, "You won.  Yea'.");
			d.pause(5000);
			
			game_over = true;
		}
	}
}

// destructor
World::~World( )
{
	destroy( );
}

// destroys a world
void World::destroy( )
{	
	//if object array is allready empty, then we're done
	if (! objects)
		return;

	for( int i = 0; i < width; i++ ) {
		for( int j = 0; j < height; j++ ) {
			delete objects[i][j];
		}
	}

	for( int k = 0; k < width; k++ ) {
		delete [] objects[k];
	}

	delete [] objects;
	// clean up pointers
	objects = NULL;
}
