#include "Level.h"
#include "Screen.h"
#include "CommandManager.h"
#include "Object.h"
#include "Player.h"
#include "Control.h"
#include "Camera.h"
#include "Parser.h"
#include "Bullet.h"
#include "Command.h"

#include <io.h>
#include <iostream>
#include <sstream>
#include <cassert>

using namespace std;

Uint32 Level::WALKBEHIND = 0;
Uint32 Level::WALKINTO = 0;
Uint32 Level::WALKINFRONT = 0;
bool Level::_objectsLoaded = false;
map<Uint32,Sprite *> Level::TEXTURES;
map<Uint32,string> Level::OBJECTS;
Level *Level::_instance = NULL;

Level::Level()
:_cutScene(false),_score(0),_won(0), _music(NULL), _timed(false), _levelTimer(-1),_background(NULL), _totBad(0), _killed(0)
{
	loadObjects();
	

	_instance = this;
}

Level::~Level()
{
	//Halt the music since the level no longer exists
	Mix_HaltMusic();
	Mix_FreeMusic(_music);
	
	CommandManager::instance()->flush();

	Sprite::cleanSprites();
	
	if(_background)
		delete _background;

	for(map<std::string, Mix_Chunk*>::iterator i = _sounds.begin(); i!=_sounds.end();i++)
	{
		Mix_FreeChunk((*i).second);
		(*i).second = NULL;
	}
	_sounds.clear();

	for(int i=0; i<_objects.size(); i++)
	{
		Control::detachController( _objects[i] );
		delete _objects[i];
	}
	for(int i=0; i<_tiles.size(); i++)
		delete _tiles[i];

	for(int i=0; i<_save.size(); i++)
		delete _save[i];
}

void Level::save()
{
	for(size_t i = 0; i < _save.size(); i++)
			delete _save[i];

	_save.clear();

	for(size_t i = 0; i < _objects.size(); i++)
		if(_objects[i]->getType() != BULLET)
			_save.push_back(_objects[i]->copy());
}

void Level::restore()
{
	if(!_save.empty()) {

		for(int i=0; i<_objects.size(); i++)
		{
			Control::detachController( _objects[i] );
			_drawnObjects.erase( remove( _drawnObjects.begin(), _drawnObjects.end(), _objects[i] ), _drawnObjects.end() ); 
			delete _objects[i];
		}

		CommandManager::instance()->flush();
		_ids.clear();
		_objects.clear();
		_deleteQueue.clear();

		while(!_addQueue.empty())
		{
			delete _addQueue.front();
			_addQueue.pop();
		}

		for(size_t i = 0; i < _save.size(); i++) {
			Object *object = _save[i]->copy();

			addObject(object,_objects);

			//FIXME: All bullets saved automagically have their shooter set to the player.
			// How wrong can I possible be?!
			if(object->getType() == BULLET)
				((Bullet *)object)->setShooter(_player);
		}

		_player = (Player*)_ids["goudy1"];
		
		SetControlCommand(_player).execute();

		//Control::registerController(_player);
		Screen::instance()->registerFollowObj(_player);
	}
}

void Level::reset()
{
	restore();
	/*
	for(int i=0; i<_objects.size(); i++)
	{
		Control::detachController( _objects[i] );
		delete _objects[i];
	}
	for(int i=0; i<_tiles.size(); i++)
		delete _tiles[i];

	_objects.clear();
	_tiles.clear();
	_drawnObjects.clear();
	_deleteQueue.clear();

	_ids.clear();

	while(!_addQueue.empty())
	{
		delete _addQueue.front();
		_addQueue.pop();
	}

	levelLoad(_filenamebase);
	*/
}

void Level::loadObjects()
{
	if(!_objectsLoaded) {
		readTextures("png");
		readTextures("txt");
		
		WALKBEHIND = loadPixelFile("levels/walkbehind_p.png");
		WALKINFRONT = loadPixelFile("levels/walkinfront_p.png");
		WALKINTO = loadPixelFile("levels/walkinto_p.png");

		readObjects("characters");
		readObjects("events");
		readObjects("cutscenes");
		readObjects("entities");
		//readTextures("bmp");
		_objectsLoaded = true;
	}
}

Uint32 Level::loadPixelFile(std::string filename)
{
	SDL_Surface *surface = glfuncs::instance()->load_image(filename);
	SDL_LockSurface(surface);
	Uint32 pixel=*((Uint32*)surface->pixels);
	SDL_UnlockSurface(surface);
	SDL_FreeSurface(surface);
	return pixel;
}

void Level::readObjects(std::string dir)
{
	struct _finddata_t file;
	intptr_t hFile;
	string extension = "txt";

	if( (hFile = _findfirst( (dir + "/*." + extension).c_str(), &file )) != -1L ) {
		do {
			string filename(file.name);
			
			string end = filename.substr(filename.size() - 6,2);
			if(end != "_p") {

				filename = dir + "/" + filename;
				//Sprite * sprite = new Sprite(filename, tileWidth, tileHeight);
				string objFile = filename;

				//filename.insert(filename.find("."),"_p");
				filename.erase(filename.find("."),4);
				filename += "_p.png";

				SDL_Surface *surface = glfuncs::instance()->load_image(filename);
				SDL_LockSurface(surface);
 				Uint32 pixel=*((Uint32*)surface->pixels);
				
				//cerr << (int) pixel << endl;

				SDL_UnlockSurface(surface);
				SDL_FreeSurface(surface);

				OBJECTS[pixel] = objFile;
			}
			else
				continue;
		}
		while( _findnext( hFile, &file ) == 0 );

		_findclose( hFile );
	}
}

void Level::readTextures(std::string extension)
{
	struct _finddata_t file;
	intptr_t hFile;

	if( (hFile = _findfirst( ("textures/*." + extension).c_str(), &file )) != -1L ) {
		do {
			string filename(file.name);
			
			string end = filename.substr(filename.size() - 6,2);
			if(end != "_p") {

				filename = "textures/" + filename;
				//Sprite * sprite = new Sprite(filename, tileWidth, tileHeight);
				Sprite * sprite = Sprite::loadSprite(filename);

				//filename.insert(filename.find("."),"_p");
				filename.erase(filename.find("."),4);
				filename += "_p.png";

				SDL_Surface *surface = glfuncs::instance()->load_image(filename);
				SDL_LockSurface(surface);
				Uint32 pixel=*((Uint32*)surface->pixels);
				
				//cerr << (int) pixel << endl;

				SDL_UnlockSurface(surface);
				SDL_FreeSurface(surface);

				TEXTURES[pixel] = sprite;
			}
			else
				continue;
		}
		while( _findnext( hFile, &file ) == 0 );

		_findclose( hFile );
	}
}

void Level::levelLoad(std::string filenamebase)
{
	_filenamebase = filenamebase;

	levelTextureLoad(filenamebase);
	levelObjectLoad(filenamebase);
	levelSpecsLoad(filenamebase);


	//FIXME (Have the player object register this itself) 
	//This will require a text file for each level (which will have to be loaded here)
	//goudy1 is the id of the player
	

	_player = (Player *)_ids["goudy1"];

	/*if(prevplayer != NULL)
	{

		prevplayer->setPos(_player->getBox().x, _player->getBox().y);
		prevplayer->setVelocity(0,0);
		prevplayer->setXDirection(_player->getXDirection());
		prevplayer->uncrouch();
		prevplayer->setCollidable(true);

		_objects.erase(remove(_objects.begin(),_objects.end(),_player),_objects.end());
		_drawnObjects.erase(remove(_drawnObjects.begin(),_drawnObjects.end(),_player),_drawnObjects.end());
		delete _ids["goudy1"];
		
		_player = prevplayer;
		addObject(prevplayer,_objects);
	}*/


	Screen::instance()->registerFollowObj( (Object*)_player );
	SetControlCommand(_player).execute();
	//Control::registerController((Object*)_player);

	SaveCommand().execute();
	//CommandManager::instance()->startPlaying("scripts/newDemo.txt");
	//CommandManager::instance()->startRecording();
}

void Level::levelSpecsLoad(std::string filenamebase)
{
	string specsFilename = filenamebase + ".txt";
	List *specsList = Parser::parse(specsFilename);

	List *specs = specsList;
	
	while(!specs->empty()) {
		List *current = specs->firstList();
		if(current->firstString() == "timer")
		{
			_timed = true;
			_levelTimer = current->rest()->firstInt();

		}
		else if(current->firstString() == "totBad")
		{
			_totBad = current->rest()->firstInt();
		}
		else
		{
			Object *object = getObject(current->firstString());
			object->load(current->rest(), false);
		}

		specs = specs->rest();
	}

	delete specsList;
}

void Level::setMusic( Mix_Music* music )
{
	if(_music)
		Mix_FreeMusic(_music);			
	_music = music;
}

void Level::levelObjectLoad(std::string filenamebase)
{
	string objFilename = filenamebase + "obj.bmp";
	SDL_Surface *surface = glfuncs::load_image(objFilename);
	SDL_LockSurface(surface);

	_height = surface->h*tileHeight;
	_width = surface->w*tileWidth;

	Uint32* objectPixels = (Uint32*)surface->pixels;

	//int maxoffset = surface->h*surface->w;
	for(int i=0; i < surface->h ; i++ ){
		for( int j=0 ; j < surface->w ;j++){

			if(OBJECTS[objectPixels[i*surface->w+j]] != "")
			{
				Object* o = Object::loadObject(OBJECTS[objectPixels[i*surface->w+j]], j*tileWidth, i*tileHeight);
				string id = o->id();
				
				//Append a number to the end of objects so we get EasyEnemy1, EasyEnemy2, ...
				int i = 0;
				do
				{
					i++;
					stringstream num;
					num << i;
					o->setId(id+num.str());
					//map<string,Object *>::iterator j = _ids.find(o->id());

				//}while (_ids.find(o->id()) != _ids.end());
				} while(_ids[o->id()] != NULL);

				cerr << o->id() << endl;

				addObject(o, _objects);
			}
		}
	}

	SDL_UnlockSurface(surface);
	SDL_FreeSurface(surface);
}


void Level::levelTextureLoad(std::string filenamebase)
{
	string texFilename = filenamebase + "tex.bmp";
	string colFilename = filenamebase + "col.bmp";
	string backFilename = filenamebase + "back.png";
	SDL_Surface *surface = glfuncs::load_image(texFilename);
	SDL_Surface* colSurface = glfuncs::load_image( colFilename );

	SDL_LockSurface(surface);
	SDL_LockSurface(colSurface);

	Tile* t = NULL;

	_height = surface->h*tileHeight;
	_width = surface->w*tileWidth;

	_tiles = vector< Tile* >( _height*_width, t );
	_background = new Background( backFilename, 0.5 );
	_background->setHeight( _height );
	_background->setWidth( _width );

	Uint32* texturePixels = (Uint32*)surface->pixels;
	Uint32* colPixels = (Uint32*)colSurface->pixels;
	Uint32 colPixel;
	


	//int maxoffset = surface->h*surface->w;
	for(int i=0; i < surface->h ; i++ ){
		for( int j=0 ; j < surface->w ;j++){
			if(TEXTURES[texturePixels[i*surface->w+j]]!=NULL)
			{
				t = new Tile(	TEXTURES[texturePixels[i*surface->w+j]]->instance(), 
					true, j*tileWidth, i*tileHeight);

				colPixel = colPixels[ i*colSurface->w + j ];
				if( colPixel == Level::WALKBEHIND )
				{
					t->setCollidable( false );
					t->setDepth( 5 );
				} else if( colPixel == Level::WALKINFRONT )
				{
					t->setCollidable( false );
					t->setDepth( -1 );
				} else if( colPixel == Level::WALKINTO )
				{
					t->setCollidable( true );
					t->setDepth( 0 );
				}

				registerTile( t, i, j );
				//cerr << i << " " << j << endl;
			}
		}
	}

	SDL_UnlockSurface(surface);
	SDL_UnlockSurface(colSurface);
	SDL_FreeSurface(surface);
	SDL_FreeSurface(colSurface);


	
	/*
	for(int i = _width - tileWidth; i >= 0; i -= tileWidth) {
		for(int j = _height - tileHeight; j >= 0; j -= tileHeight) {
			Tile* tile  = getTileFromPixels(i,j);
			if(tile != NULL)
				tile->getDrawBox();
		}
	}
	*/

	for(int j = 0; j < _height; j += tileHeight) {
		for(int i = 0; i < _width; i += tileWidth) {
			Tile* tile  = getTileFromPixels(i,j);
			if(tile && tile->getDraw()) {
				tile->calcDrawBox();
				//exit(0);
				//tile->getDrawBox();
			}
			//if(tile && tile->getDraw())
			//	cerr << i << " " << j << " " << tile->getDrawBox().w << " " << tile->getDrawBox().h << endl;
		}
	}
}

bool Level::update()
{
	if(_timed && !_cutScene)
		_levelTimer--;

	if(_levelTimer == 0)
	{
		_player->die();
		_won = true;
	}
	
	Object* o;

	while( !_deleteQueue.empty() )
	{
		o = _deleteQueue.front();
		_deleteQueue.erase( remove( _deleteQueue.begin(), _deleteQueue.end(), o ), _deleteQueue.end() ); 
		

		if(o == this->_player)
		{
			// this early return is important for dying to work properly
			_player = (Player *)_player->copy();
			deleteObject( o );
 			return false;
		}
		deleteObject( o );
	}

	while( !_addQueue.empty() )
	{
		o = _addQueue.front();
		addObject( o, _objects );
		_addQueue.pop();
	}

	CommandManager::instance()->startTimestep();

	Control::update();
	Screen::instance()->update();

	
	for( vector< Object* >::iterator i = _objects.begin(); i != _objects.end(); i++ )
	{
		(*i)->update();
	}

	return true;
}

void Level::draw()
{
	// Draw background
	_background->draw();

	// Draw all drawable objects
	for( vector< Object* >::iterator i = _drawnObjects.begin(); i != _drawnObjects.end(); i++ )
	{
		(*i)->draw();
	}

	drawHUD();

	if(_cutScene) {
		Screen::instance()->drawCutScene(_cutSceneText);
	}
}

void Level::drawHUD()
{
	SDL_Rect raster;

	Sprite *health = Sprite::loadSprite("sprites/health.png");
	Sprite *heart = Sprite::loadSprite("sprites/heart.png");

	raster.x = 5;
	raster.y = 5;

	health->draw(raster.x,raster.y,Sprite::SCREEN);

	raster.x += health->width() + 2;
	raster.y += 5;

	for(int i = 0; i < _player->getHealth(); i++) {
		heart->draw(raster.x,raster.y,Sprite::SCREEN);
		raster.x += heart->width() + 2;
	}
	
	raster.x = 5;
	raster.y += health->height();

	if(_timed)
	{
		int secs = ((int)_levelTimer/Screen::FRAMERATE)%5999;
		stringstream timer;
		timer << "Time Left: ";
		if(secs%60 < 10)
			timer << secs/60 << ":0" << secs%60;
		else
			timer << secs/60 << ":" << secs%60;
		glfuncs::instance()->glPrint(raster.x,raster.y,timer.str());
	}

	raster.x += 270;

	if(_totBad > 0)
	{
		stringstream bad;
		bad << "Enemies: ";
		bad << _killed << "/" << _totBad ;
		glfuncs::instance()->glPrint(raster.x,raster.y,bad.str());
	}

	//glfuncs::instance()->glDisable(GL_TEXTURE_2D);
	raster.x = 710;
	raster.y = 6;
	//glfuncs::instance()->glRasterPos2f(raster.x,raster.y);

	stringstream score;
	score << "Score: ";
	score << _score;
	glfuncs::instance()->glPrint(raster.x,raster.y,score.str());



	raster.y += 30;
	_player->drawSecondaryAmmo(raster);

	//glfuncs::instance()->glEnable(GL_TEXTURE_2D);
}

// Schedule an object to be added to the level
void Level::registerObject( Object* o )
{
	_addQueue.push( o );
}

void Level::registerTile( Tile* t, int h, int w )
{
	//if( t->collidable() )
		_tiles[ h*_width + w ] = t;

	// Add to _drawnObjects vector in depth sorted order
	vector< Object* >::iterator i = _drawnObjects.begin();
		
	while( i != _drawnObjects.end() && t->getDepth() > (*i)->getDepth() )
		i++;

	_drawnObjects.insert( i, t );
}


// Add the object to _drawnObjects, sorted in depth ordering,
// then add the object to vec for collision detection
void Level::addObject( Object* o, std::vector< Object* >& vec )
{
	// Add to collision detection vector
	vec.push_back( o );

	// Add to _drawnObjects vector in depth sorted order
	vector< Object* >::iterator i = _drawnObjects.begin();
		
	while( i != _drawnObjects.end() && o->getDepth() > (*i)->getDepth() )
		i++;

	_drawnObjects.insert( i, o );
	_ids[o->id()] = o;

	/*
	cerr << "Registering : " << endl;
	cerr << o->id() << endl;
	cerr << o->getDepth() << endl;
	cerr << j << endl << endl;
	*/
}

Object *Level::getObject( std::string id )
{
	return _ids[id];
}

Level *Level::instance()
{
	if(!_instance)
		return new Level();
	else
		return _instance;
}

void Level::scheduleDelete( Object* o )
{
	_deleteQueue.push_back( o );
}

void Level::deleteObject( Object* o )
{
	_objects.erase( remove( _objects.begin(), _objects.end(), o ), _objects.end() ); 
	_drawnObjects.erase( remove( _drawnObjects.begin(), _drawnObjects.end(), o ), _drawnObjects.end() ); 
	if(o->getType() != TILE)
		_ids.erase(o->id());
	Control::detachController( o );
	delete o;
}

void Level::setCutScene(bool cutScene)
{
	_cutScene = cutScene;
}

Mix_Chunk* Level::getSound( std::string filename )
{
	assert( _sounds.find( filename ) != _sounds.end() );
	return _sounds[ filename ];
}

void Level::loadSound( std::string filename )
{
	// If the sound isn't already in the map, load it in
	if( _sounds.find( filename ) == _sounds.end() )
	{
		_sounds[ filename ] = Mix_LoadWAV( filename.c_str() );
	}
}
