/*********************************************************\
		 __      __           __  __
		/ /     / / _____    / / / / ______   __     __
	   / /     / / /___  \  / / / / / ___  | / /    / /
	  / /_____/ / _____| / / / / / / /__/ / / /    / /
	 / ______  / / ___  / / / / / / _____/ / /    / /
	/ /     / / / /__/ / / / / / / |____  / |____/ /
   /_/     /_/ |______/ /_/ /_/  \_____/  \_____  /
										 ______/ /
			High Level Game Framework   /_______/

  ------------------------------------------------------

  Copyright (c) 2007 - Rodrigo Braz Monteiro.
  This file is subject to the terms of license.txt.

\*********************************************************/

#include "SDL.h"
#include "SDL_opengl.h"
#include "GL/glew.h"
#include "game.h"
#include "input.h"
#include "exception.h"
#include "video.h"
#include "redirectstream.h"
#include "draw_text.h"
#include "draw.h"
#include "audio.h"
#include <ctime>
#include <fstream>
using namespace Halley;

///////////////
// Constructor
Game::Game(String gameName)
{
	name = gameName;
	run = true;
	fps = 60;
	srand((unsigned)time(0) ^ (unsigned)clock() ^ 0x3F29AB51);
}


//////////////
// Destructor
Game::~Game()
{
}


static void handleException(Exception& e)
{
	std::cout << e.what() << std::endl;
}


/////////
// Start
void Game::Start(StringArray args)
{
	// Parse program path
	if (args.size() > 0)
		ParseProgramPath(args[0]);

	// Redirect output
	shared_ptr<std::ostream> outStream = shared_ptr<std::ostream>(new std::ofstream(GetProgramPath() + "log.txt", std::ios::out));
	out = shared_ptr<RedirectStream>(new RedirectStreamToStream(std::cout, outStream, false));
	//out = shared_ptr<RedirectStream>(new RedirectStream(std::cout, false));

	// Initialize SDL
	int result;
	result = SDL_Init(SDL_INIT_EVERYTHING);
	if (result == -1) {
		throw Exception(SDL_GetError());
	}

	// Set stuff up in SDL
	SDL_EnableUNICODE(1);
	//SDL_EnableKeyRepeat(SDL_DEFAULT_REPEAT_DELAY, SDL_DEFAULT_REPEAT_INTERVAL);
	SDL_ShowCursor(SDL_DISABLE);

	// Initialize input
	Input::instance = shared_ptr<Input>(new Input());

	// Initialize audio
	Audio::get();

	// Initialize the game
	try {
		Init(StringArray(args.begin()+1, args.end()));
		SDL_WM_SetCaption(name.c_str(), name.c_str());
	}
	catch (Exception& e) {
		handleException(e);
	}
	catch (...) {}

	// Run the game
	try {
		Run();
	}
	catch (Exception& e) {
		handleException(e);
	}
	catch (...) {}

	// Deinitialize the game
	try {
		SetTopFrame(spFrame());
		DeInit();
	}
	catch (Exception& e) {
		handleException(e);
	}
	catch (...) {}

	// Deinit input
	Input::instance = shared_ptr<Input>();

	// Deinit other stuff
	TextDrawer::ClearDrawers();

	// Close SDL
	SDL_Quit();

	// Deinit console redirector
	out = shared_ptr<RedirectStream>();
}


///////
// Run
void Game::Run()
{
	// Set up the counters
	Uint32 startTime = SDL_GetTicks();
	Uint32 targetTime = startTime;
	Uint32 nSteps = 0;
	Uint32 skipped = 0;
	Uint32 margin = 1000/fps;

	// Run game
	while (run) {
		Uint32 curTime = SDL_GetTicks();
		if (curTime+margin >= targetTime) {
			onPreUpdate();
			run = UpdateLogic();
			if (!run) break;
			nSteps++;

			// Find the time of the next update
			targetTime = startTime + (nSteps * 1000)/fps;

			// If it has time, update the screen
			if (SDL_GetTicks() < targetTime || skipped > 30) {
				RenderScreen();
				skipped = 0;
			} else {
				skipped++;
			}
		} else {
			// Too much time left, give it away
			// IMPORTANT: do this with signed numbers!
			int toGiveAway = targetTime - curTime;
			toGiveAway -= margin/2;
			if (toGiveAway > 1) {
				SDL_Delay(toGiveAway);
			}
		}
	}
}


bool Game::UpdateLogic()
{
	PollEvents();

	if (topFrame) {
		return topFrame->Update(1.0f/fps);
	}
	return false;
}


static void drawBox(float x, float y, float w, float h)
{
	//std::cout << x << "," << y << "," << w << "," << h << std::endl;
	//glDisable(GL_TEXTURE_2D);
	//glDisable(GL_BLEND);
	BindTexture(0);
	SetBlendType(Texture::ALPHA_OPAQUE);
	glColor3f(0, 0, 0);
	glBegin(GL_QUADS);
	glVertex2f(x, y);
	glVertex2f(x+w+1, y);
	glVertex2f(x+w+1, y+h+1);
	glVertex2f(x, y+h+1);
	glEnd();
}

void Game::RenderScreen()
{
	if (topFrame) {
		topFrame->Render();
	}

	// Draw letterbox
	Vector2f p = Video::GetOrigin();
	Vector2f s = Video::GetVirtualSize();
	if (p.y < 0) {
		drawBox(0, p.y, s.x, -p.y);
		drawBox(0, s.y+1, s.x, -p.y);
	}
	if (p.x < 0) {
		drawBox(p.x, 0, -p.x, s.y);
		drawBox(s.x+1, 0, -p.x, s.y);
	}

	SDL_GL_SwapBuffers();
}


/////////////
// Quit game
void Game::Quit()
{
	SetTopFrame(spFrame());
}


/////////////////////////////
// Child frame was destroyed
void Game::ChildFrameChange(spFrame from, spFrame to)
{
	if (from != topFrame)
		throw Exception("Invalid top frame");

	from->TryDeInit();

	topFrame = spFrame();
	if (!to) Quit();
	else SetTopFrame(to);
}


/////////////////
// Set top frame
void Game::SetTopFrame(spFrame frame)
{
	if (topFrame) topFrame->Die();
	if (frame) {
		topFrame = frame;
		frame->SetParent(instance, frame);
		frame->TryInit();
	}
	else run = false;
}


////////////////
// Get instance
spGame Game::GetInstance()
{
	return instance;
}
spGame Game::instance;


////////////////
// Set instance
void Game::SetInstance(spGame game)
{
	if (game && instance) throw Exception("Game already exists!");
	instance = game;
}


///////////////
// Poll events
void Game::PollEvents()
{
	Input::beginEvents();

	SDL_Event event;
	SDL_PumpEvents();
	while (SDL_PeepEvents(&event, 1, SDL_GETEVENT, SDL_ALLEVENTS) > 0) {
		switch (event.type) {
			case SDL_KEYDOWN:
			case SDL_KEYUP:
			case SDL_JOYAXISMOTION:
			case SDL_JOYBUTTONDOWN:
			case SDL_JOYBUTTONUP:
			case SDL_JOYHATMOTION:
			case SDL_JOYBALLMOTION:
			case SDL_MOUSEMOTION:
			case SDL_MOUSEBUTTONUP:
			case SDL_MOUSEBUTTONDOWN:
				{
					Input::processEvent(event);
					break;
				}
			case SDL_QUIT:
				{
					std::cout << "SDL_QUIT received." << std::endl;
					Quit();
					break;
				}
		}
	}
}


/////////////////
// Exit the game
void Game::Exit()
{
	run = false;
}


////////////////////////////
// Parses the program path
void Game::ParseProgramPath(String path)
{
	size_t len = path.Length();
	size_t last = 0;
	for (size_t i=0; i<len; i++) {
		if (path[i] == '/' || path[i] == '\\') last = i+1;
	}
	programPath = path.Left(last);
}


//////////////////////////////////////////
// Get the program path from the instance
String Game::GetProgramPath()
{
	return GetInstance()->programPath;
}
