/*
	FurbyKill 3D
	============
	License: GPL
	Author: Andre Schnabel
	E-Mail: thefrogs@web.de
	Homepage: http://www.semler-service.de/andre/
*/

// Game.cpp

#include "Game.h"
#include "Raycaster.h"
#include "Sprite.h"
#include "Player.h"
#include "Furby.h"

//
// Game
// Constructor
//
Game::Game(const char *mapFilename, bool lowQuality, bool badQuality)
{
	if(mapFilename)
	{
		strcpy(this->mapFilename, DATA_PREFIX "maps/");
		strcat(this->mapFilename, mapFilename);
	}
	else
		strcpy(this->mapFilename, DATA_PREFIX "maps/map1");
	
	curMapNr = 1;
	lNeedKeyMsg = 0;
	
	this->lowQuality = lowQuality;
	this->badQuality = badQuality;
}

//
// Init
//
void Game::Init()
{
	int i;
	RaycasterSetup setup;
	char spriteFilename[MAX_STR_LENGTH];
	
	strcpy(setup.mapFilename, mapFilename);
	
	strcpy(spriteFilename, mapFilename);
	strcat(spriteFilename, "sprites");
	
	LoadResources();
	LoadSpriteMap(spriteFilename);
	
	setup.numTextures = numTextures;
	
	for(i=0; i<setup.numTextures; i++)
	{
		setup.textures[i][0] = textures[i][0];
		setup.textures[i][1] = textures[i][1];
	}
	
	setup.sprites = &sprites;
	
	setup.target = framework->GetScreen();
	setup.lowQuality = lowQuality;
	setup.badQuality = badQuality;
	
	setup.crosshairRect = &crosshairRect;
	
	fireVisible = false;
	fireStart = SDL_GetTicks();
	
	raycaster = new Raycaster(&setup);
	player = new Player();
	
	if(!framework->GetNoSound())
	{
		framework->PlayMusic(music, -1);
		framework->PlaySound(startSnd);
	}
	
	showExitConfirm = false;
}

//
// Quit
//
void Game::Quit()
{
	list<Sprite *>::iterator it;
	Sprite *curSprite;
	
	delete player;
	
	for(it = sprites.begin(); it != sprites.end(); it++)
	{
		curSprite = *it;
		delete curSprite;
	}
	
	sprites.clear();
	FreeResources();
	delete raycaster;
}

#define REACT_ON_PICKING() \
	if(wasPickup) \
	{ \
		switch(pickupId) \
		{ \
			case PT_MEDPACK: \
				player->IncrementHealth(10); \
				AddMessage("You picked up a Medpack!"); \
				break; \
			case PT_AMMO: \
				player->IncrementAmmo(10); \
				framework->PlaySound(pickAmmoSnd); \
				AddMessage("You picked up an Ammopack!"); \
				break; \
			case PT_KEY: \
				player->SetHasKey(true); \
				AddMessage("You picked up the key!"); \
				break; \
		} \
		wasPickup = false; \
	}

//
// Input
//
void Game::Input()
{
	 int mouseMovX, pickupId;
	 float movSpeed;
	 bool wasPickup = false, needAKey = false;
	
	movSpeed = 2.0f;
	
	if(framework->KeyPressed(SDLK_ESCAPE, true))
		showExitConfirm = !showExitConfirm;	
	
	if(showExitConfirm)
	{
		if(framework->KeyPressed(SDLK_y))
			SetDone(true);
		else if(framework->KeyPressed(SDLK_n))
			showExitConfirm = false;
				
		return;
	}
	
	if(framework->KeyPressed(SDLK_F1, true))
	{
		int i;
		char shotFilename[MAX_STR_LENGTH];
		for(i=0; i<MAX_SCREENSHOTS; i++)
		{
			sprintf(shotFilename, "screenshots/shot%d.bmp", i+1);
			
			if(!framework->FileExists(shotFilename))
			{
				SDL_SaveBMP(framework->GetScreen(), shotFilename);
				break;
			}
		}
	}
	
	if(framework->KeyPressed(SDLK_LSHIFT, false))
		movSpeed *= 2.0f;
	
	if(framework->KeyPressed(SDLK_LEFT, false))
		raycaster->Rotate(ROT_SPEED);
	
	if(framework->KeyPressed(SDLK_RIGHT, false))
		raycaster->Rotate(-ROT_SPEED);
	
	if(framework->KeyPressed(SDLK_UP, false) || framework->KeyPressed(SDLK_w, false))
		raycaster->MoveForward(movSpeed, &wasPickup, &pickupId);
	
	REACT_ON_PICKING();
	
	if(framework->KeyPressed(SDLK_DOWN, false) || framework->KeyPressed(SDLK_s, false))
		raycaster->MoveBackward(movSpeed, &wasPickup, &pickupId);
	
	REACT_ON_PICKING();
	
	if(framework->KeyPressed(SDLK_a, false))
		raycaster->StepLeft(movSpeed / 2.0f, &wasPickup, &pickupId);
	
	REACT_ON_PICKING();
	
	if(framework->KeyPressed(SDLK_d, false))
		raycaster->StepRight(movSpeed / 2.0f, &wasPickup, &pickupId);
	
	REACT_ON_PICKING();
	
	needAKey = false;
	
	if(framework->KeyPressed(SDLK_SPACE, false))
		raycaster->OpenDoor(player->HasKey(), &needAKey);
	
	if(needAKey && SDL_GetTicks() - lNeedKeyMsg > 2000)
	{
		AddMessage("You need a key to unlock this door!");
		lNeedKeyMsg = SDL_GetTicks();
	}
	
	if(framework->KeyPressed(SDLK_LCTRL, false))
		Shoot();
	
	framework->GetMouseMovement(&mouseMovX, NULL);
	raycaster->Rotate((float)mouseMovX/10.0f*-1.0f*ROT_SPEED);
}

//
// Update
//
void Game::Update()
{
	list<Sprite *>::iterator it;
	Sprite *curSpr;
	
	if(showExitConfirm)
		return;
	
	UpdateMessages();
	
	// Update sprites
	for(it = sprites.begin(); it != sprites.end(); it++)
	{
		curSpr = *it;
		
		if(curSpr->GetId() == ET_FURBY)
		{
			Furby *fb = (Furby *)curSpr;
			fb->Update(raycaster);
		}
		else if(curSpr->GetId() == SHOT_INDEX)
		{
			Shot *shot = (Shot *)curSpr;
			shot->Update(raycaster);
			
			if(shot->PointCollWith(raycaster->GetPosX(), raycaster->GetPosY()))
			{
				AddMessage("You got hit by a shot from a furby!");
				player->DecrementHealth(5);
				it = sprites.erase(it);
			}
			
			if(shot->IsVanished())
				it = sprites.erase(it);
		}
		else if(curSpr->GetId() == DEATH_ANIMATION_INDEX)
		{
			DeathAnimation *da = (DeathAnimation *)curSpr;
			da->Update();
			
			if(da->HasEnded())
			{
				float itsX, itsY;
				itsX = da->GetPosX();
				itsY = da->GetPosY();
				
				delete da;
				it = sprites.erase(it);
				
				Sprite *corpse = new Sprite(spriteImgs[2], itsX, itsY, 2);
				sprites.push_back(corpse);				
			}
		}
	}
	
	if(player->GetHealth() == 0)
		GameOver();
	
	if(raycaster->MapChangeNeeded())
		NextMap();	
}

//
// Draw
//
void Game::Draw()
{
	if(!showExitConfirm)
	{
		raycaster->Draw();
		DrawHUD();
		DrawMessages();
		//framework->ShowFPS();
	}
	else
	{
		framework->FillRect(NULL, Colors::black.ToUint32());
		framework->DrawTextCentered("Do you really want to quit this game?"
				" Press Y for Yes and N for No...", Colors::white);
	}
}

#define UPDATE_STATUS(str) \
	framework->FillRect(NULL, Colors::black.ToUint32()); \
	framework->DrawTextCentered(str, Colors::white); \
	SDL_Flip(framework->GetScreen());

//
// LoadResources
//
void Game::LoadResources()
{
	int i;
	char filename[MAX_STR_LENGTH], completeFilename[MAX_STR_LENGTH];
	
	UPDATE_STATUS("Loading resources...");
	
	for(i=0; i<MAX_SPRITES; i++)
		spriteImgs[i] = NULL;
	
	for(i=0; i<MAX_TEXTURES; i++)
	{
		textures[i][0] = NULL;	
		textures[i][1] = NULL;
	}
	
	UPDATE_STATUS("Loading textures...");
	
	for(i=0; i<MAX_TEXTURES; i++)
	{
		sprintf(filename, "textures/tex%d.bmp", i+1);
		sprintf(completeFilename, "%s%s", DATA_PREFIX, filename);
		if(framework->FileExists(completeFilename))
		{
			textures[i][0] = framework->LoadImage(filename, false);
			sprintf(filename, "textures/tex%dDark.bmp", i+1);
			textures[i][1] = framework->LoadImage(filename, false);
		}
		else
			break;
	}
	
	numTextures = i;
	
	UPDATE_STATUS("Loading sprites...");
	
	for(i=0; i<MAX_SPRITES; i++)
	{
		sprintf(filename, "sprites/sprite%d.bmp", i+1);
		sprintf(completeFilename, "%s%s", DATA_PREFIX, filename);
		if(framework->FileExists(completeFilename))
			spriteImgs[i] = framework->LoadImage(filename, true);
		else
			break;
	}
	
	UPDATE_STATUS("Loading ui images...");
	
	if(lowQuality)
	{
		hudBgImg = framework->LoadImage("images/lowQuality/hudBg.bmp", false);
		crosshairImg = framework->LoadImage("images/lowQuality/crosshair.bmp", true);
		headImg = framework->LoadImage("images/lowQuality/head.bmp", true);
		headRedImg = framework->LoadImage("images/lowQuality/headRed.bmp", true);
		gunHudImg = framework->LoadImage("images/lowQuality/gunHud.bmp", true);
		gunImg = framework->LoadImage("images/lowQuality/gun.bmp", true);
		fireImg = framework->LoadImage("images/lowQuality/fire.bmp", true);
		keyImg = framework->LoadImage("images/lowQuality/key.bmp", true);
	}
	else
	{		
		hudBgImg = framework->LoadImage("images/hudBg.bmp", false);
		crosshairImg = framework->LoadImage("images/crosshair.bmp", true);
		headImg = framework->LoadImage("images/head.bmp", true);
		headRedImg = framework->LoadImage("images/headRed.bmp", true);
		gunHudImg = framework->LoadImage("images/gunHud.bmp", true);	
		gunImg = framework->LoadImage("images/gun.bmp", true);
		fireImg = framework->LoadImage("images/fire.bmp", true);
		keyImg = framework->LoadImage("images/key.bmp", true);
		
		SDL_SetAlpha(hudBgImg, SDL_SRCALPHA, SDL_ALPHA_OPAQUE / 2);		
		SDL_SetAlpha(headRedImg, SDL_SRCALPHA, SDL_ALPHA_OPAQUE / 2);
	}
	
	crosshairRect.x = (framework->GetScrW() - crosshairImg->w) / 2;
	crosshairRect.y = (framework->GetScrH() - crosshairImg->h) / 2;
	crosshairRect.w = crosshairImg->w;
	crosshairRect.h = crosshairImg->h;	
	
	UPDATE_STATUS("Loading audio resources...");
	
	if(!framework->GetNoSound())
	{
		music = framework->LoadMusic("music/music.s3m");
	
		startSnd = framework->LoadSound("sounds/start_round.wav");	
		shootSnd = framework->LoadSound("sounds/shot.wav");
		killSnd = framework->LoadSound("sounds/scream.wav");
		pickAmmoSnd = framework->LoadSound("sounds/pickammo.wav");
	}
}

//
// FreeResources
//
void Game::FreeResources()
{
	int i;
	
	if(!framework->GetNoSound())
	{
		Mix_HaltChannel(-1);
		Mix_FreeChunk(pickAmmoSnd);
		Mix_FreeChunk(killSnd);
		Mix_FreeChunk(shootSnd);
		Mix_FreeChunk(startSnd);
		Mix_HaltMusic();
		Mix_FreeMusic(music);
	}
	
	SDL_FreeSurface(keyImg);
	SDL_FreeSurface(fireImg);
	SDL_FreeSurface(gunImg);
	SDL_FreeSurface(gunHudImg);
	SDL_FreeSurface(headRedImg);
	SDL_FreeSurface(hudBgImg);
	SDL_FreeSurface(headImg);
	SDL_FreeSurface(crosshairImg);
	
	for(i=0; i<MAX_SPRITES; i++)
	{
		if(textures[i] != NULL)
			SDL_FreeSurface(spriteImgs[i]);
		else
			break;
	}
	
	for(i=0; i<MAX_TEXTURES; i++)
	{
		if(textures[i][0] != NULL)
		{
			SDL_FreeSurface(textures[i][0]);
			SDL_FreeSurface(textures[i][1]);
		}
		else
			break;
	}
}


//
// LoadSpriteMap
//
void Game::LoadSpriteMap(const char *filename)
{
	int i, sprType, numSprites;
	float x, y;
	FILE *fp;
	SDL_Surface *image;
	Sprite *sprite;
	
	numSprites = framework->CountRowsInFile(filename);
	
	fp = fopen(filename, "r");
	
	for(i=0; i<numSprites; i++)
	{
		fscanf(fp, "%d, %f, %f\n", &sprType, &x, &y);
		
		//  object
		if(sprType < PICKUP_INDEX)
		{
			image = spriteImgs[sprType];
			sprite = new Sprite(image, x, y, sprType);
		}
		// Object you can pick up
		else if(sprType < ENEMY_INDEX)
		{
			switch(sprType)
			{
				case PT_MEDPACK:
					image = spriteImgs[3];
					sprite = new Sprite(image, x, y, PT_MEDPACK);
					break;				
				case PT_AMMO:
					image = spriteImgs[4];
					sprite = new Sprite(image, x, y, PT_AMMO);
					break;
				case PT_KEY:
					image = spriteImgs[10];
					sprite = new Sprite(image, x, y, PT_KEY);
					break;
			}
		}
		// Enemy
		else
		{
			switch(sprType)
			{
				case ET_FURBY:
					SDL_Surface *furbyImgs[NUM_FB_IMGS];					
					furbyImgs[0] = spriteImgs[0];
					furbyImgs[1] = spriteImgs[8];
					furbyImgs[2] = spriteImgs[9];
					sprite = new Furby(furbyImgs, spriteImgs[7], x, y, &sprites);
					break;
			}
		}
		
		sprites.push_back(sprite);
	}
	
	fclose(fp);
}

//
// DrawHUD
//
void Game::DrawHUD()
{
	 int health, ammunition;
	 char healthStr[MAX_STR_LENGTH], ammoStr[MAX_STR_LENGTH];
	
	health = player->GetHealth();
	ammunition = player->GetAmmo();
	
	sprintf(healthStr, "%d", health);
	sprintf(ammoStr, "%d", ammunition);
	
	framework->DrawCentered(crosshairImg);
	
	if(fireVisible)
	{
		if(SDL_GetTicks() - fireStart < FIRE_SHOW_TIME)
		{
			if(!lowQuality)
				framework->Draw(fireImg, (framework->GetScrW() - fireImg->w) / 2 + 100, framework->GetScrH() - fireImg->h - 50);
			else
				framework->Draw(fireImg, (framework->GetScrW() - fireImg->w) / 2 + 50, framework->GetScrH() - fireImg->h - 25);
		}
		else
			fireVisible = false;
	}
	
	framework->Draw(gunImg, framework->GetScrW() - gunImg->w, framework->GetScrH() - gunImg->h);
	framework->Draw(hudBgImg, (framework->GetScrW() - hudBgImg->w) / 2, framework->GetScrH() - hudBgImg->h);
	framework->Draw(headImg, (framework->GetScrW() - headImg->w) / 2, framework->GetScrH() - headImg->h);
	
	if(health < 100)
	{
		 SDL_Rect srcRect, destRect;
		
		srcRect.x = 0;
		srcRect.y = (int)((float)headRedImg->h * ((float)health / 100.0f));
		srcRect.w = headRedImg->w;
		srcRect.h = headRedImg->h - srcRect.y;
				
		destRect.w = srcRect.w;
		destRect.h = srcRect.h;
		
		destRect.x = (framework->GetScrW() - headImg->w) / 2;
		destRect.y = framework->GetScrH() - headImg->h + srcRect.y;
		
		SDL_BlitSurface(headRedImg, &srcRect, framework->GetScreen(), &destRect);
	}
	
	framework->Draw(gunHudImg, (framework->GetScrW() - hudBgImg->w) / 2 + 2, framework->GetScrH() - gunHudImg->h);
	
	if(player->HasKey())
	{
		if(!lowQuality)
			framework->Draw(keyImg, framework->GetScrW() - keyImg->w - 100, framework->GetScrH() - keyImg->h - 10);
		else
			framework->Draw(keyImg, framework->GetScrW() - keyImg->w - 50, framework->GetScrH() - keyImg->h - 5);
	}
	
	framework->DrawText(ammoStr,
		(framework->GetScrW() - hudBgImg->w) / 2 + gunHudImg->w + 8,
		framework->GetScrH() - gunHudImg->h,
		Colors::white, true);
	framework->DrawText(healthStr,
		(framework->GetScrW() - headImg->w) / 2 + headImg->w + 4,
		framework->GetScrH() - gunHudImg->h,
		Colors::white, true);
}

//
// Shoot
//
void Game::Shoot()
{
	 list<SprDraw> killableSprites;
	 list<SprDraw>::iterator ksIt;
	 float distance, a, b;
	
	if(SDL_GetTicks() - lShot > RELOAD_TIME)
	{
		list<Sprite *>::iterator it;
		Sprite *spr;
		
		player->DecrementAmmo(1);
		
		if(player->GetAmmo() == 0)
		{
			AddMessage("Out of ammo!");
			return;
		}
		
		fireStart = SDL_GetTicks();
		fireVisible = true;
		
		framework->PlaySound(shootSnd);
		
		for(it = sprites.begin(); it != sprites.end(); it++)
		{
			spr = *it;
			if(spr->GetLookingAt() && spr->GetId() == ET_FURBY)
			{				
				SprDraw ks;
				ks.original = spr;
				ks.x = spr->GetPosX();
				ks.y = spr->GetPosY();
				ks.image = spr->GetImage();
				a = raycaster->GetPosX() - ks.x;
				b = raycaster->GetPosY() - ks.y;
				distance = sqrt(a*a+b*b);
				ks.distance = distance;
				killableSprites.push_back(ks);				
			}
		}
		
		if(!killableSprites.empty())
		{
			killableSprites.sort();
			killableSprites.reverse();
		
			ksIt = killableSprites.begin();
			
			Furby *thisFurby = (Furby *)(*ksIt).original;
				
			if(!thisFurby->IsDead())
				thisFurby->NextImage();
			else
			{
				/*Sprite *corpse = new Sprite(spriteImgs[2], (*ksIt).original->GetPosX(), (*ksIt).original->GetPosY(), 2);*/
				SDL_Surface *daFrames[NUM_DEATH_ANIM_FRAMES];
				
				daFrames[0] = spriteImgs[11];
				daFrames[1] = spriteImgs[12];
				daFrames[2] = spriteImgs[13];
				
				DeathAnimation *deathAnim = new DeathAnimation(daFrames, (*ksIt).original->GetPosX(), (*ksIt).original->GetPosY());
				
				//sprites.push_back(corpse);
				sprites.push_back(deathAnim);
				
				delete (*ksIt).original;				
				sprites.remove((*ksIt).original);
				
				framework->PlaySound(killSnd);
				
				AddMessage("You killed a furby with your laser pistol!");
			}
			
			killableSprites.clear();		
		}

		lShot = SDL_GetTicks();
	}
}

//
// NextMap
//
void Game::NextMap()
{	
	char spriteMapFilename[MAX_STR_LENGTH];
	
	framework->FillRect(NULL, Colors::black.ToUint32());
	framework->DrawTextCentered("Entering next map...", Colors::white, true);
	SDL_Flip(framework->GetScreen());
	SDL_Delay(1500);
	
	curMapNr++;

buildNameLbl:
	sprintf(mapFilename, DATA_PREFIX "maps/map%d", curMapNr);
	strcpy(spriteMapFilename, mapFilename);
	strcat(spriteMapFilename, "sprites");
	
	if(!framework->FileExists(mapFilename))
	{
		curMapNr = 1;
		goto buildNameLbl;
	}
	
	raycaster->LoadMap(mapFilename);
	sprites.clear();
	
	LoadSpriteMap(spriteMapFilename);
	raycaster->SetAlpha(0.0f);
	
	delete player;
	player = new Player();
}

//
// UpdateMessages
//
void Game::UpdateMessages()
{
	list<TextMessage>::iterator it;
	TextMessage *curMsg;
	
	for(it = msgs.begin(); it != msgs.end(); it++)
	{
		curMsg = &(*it);
		
		if(SDL_GetTicks() - curMsg->sTime > MSG_SHOW_TIME)
			it = msgs.erase(it);
	}
}

//
// DrawMessages
//
void Game::DrawMessages()
{
	list<TextMessage>::iterator it;
	int drawY = 0;
	TextMessage *curMsg;
	
	for(it = msgs.begin(); it != msgs.end(); it++)
	{
		curMsg = &(*it);
		
		framework->DrawText(curMsg->text, 0, drawY, Colors::white);
		
		if(!lowQuality)
			drawY += 20;
		else
			drawY += 10;
	}
}

//
// AddMessage
//
void Game::AddMessage(const char *str)
{
	TextMessage msg;
	
	msg.sTime = SDL_GetTicks();
	strcpy(msg.text, str);
	
	msgs.push_back(msg);
}

//
// GameOver
//
void Game::GameOver()
{
	framework->DrawTextCentered("Game Over!", Colors::red, true);
	SDL_Flip(framework->GetScreen());
	SDL_Delay(GAME_OVER_TIME);
	restartMe = true;
}
