#include "Engine.h"
#include "Logging.h"
#include "Variables.h"
#include "Commands.h"

#include "PhysicsBase.h"
#include "CameraBase.h"
#include "SoundBase.h"

#include "ViewBase.h"
#include "ModelBase.h"
#include "ControllerBase.h"

#include "EntityFactoryBase.h"
#include "EventManager.h"

#include <iostream>
#include <cstdio>
#include <cstdarg>
#include <cstdlib>
#include <assert.h>
#include <boost/filesystem.hpp>

int vasprintf (char **result, const char *format, va_list *string) {
	return 0;
}

namespace Engine {

/* Globaly visible classes */
Engine* EngineInstance = NULL;

/*
 * Inherited Module functions
 */
int Engine::OnInit (int argc, char* argv[]) {
	EngineInstance = this;
	mStatus = EngineStatusUndefined;

	/* Phase 0: Logging, Events, Variables, Commands, SDL */

	/* Initialization of the base modules */
	if (mLogging == NULL)
		mLogging = new Logging ();

	if (! mLogging ) {
		SetError ("Could not allocate memory for Logging!");
		exit (-1);
	}
	mLogging->Init (argc, argv);
	SetStatus (EngineStatusInitializing);

	if (mEventManager == NULL)
		mEventManager = new EventManager();

	mEventManager->Init (argc, argv);

	if (mVariables == NULL)
		mVariables = new Variables ();
	if (! mVariables ) {
		SetError ("Could not allocate memory for Variables!");
		exit (-1);
	}
	mVariables->Init (argc, argv);

	if (mCommands == NULL)
		mCommands = new Commands ();
	if (! mCommands ) {
		SetError ("Could not allocate memory for Commands!");
		exit (-1);
	}
	mCommands->Init (argc, argv);

	// Initialize the SDL
	LogDebug ("Initializing SDL");
	if ( SDL_Init ( SDL_INIT_VIDEO | SDL_INIT_AUDIO ) < 0) {
		LogError ("Error initializing SDL: %s", SDL_GetError ());
		exit (-1);
	}
	SDL_WM_SetCaption("Engine Initializing","Engine Initializing");

	/* Now register the commands */
	mLogging->RegisterCommands ();
	mVariables->RegisterCommands ();
	mCommands->RegisterCommands ();

	/* Phase 1: Sounds, Model, EntityFactory, Physics, View, Camera, Controller*/
	
	/* Sound */
	mSoundManager = new SoundBase();
	mSoundManager->Init (argc, argv);

	/* Model */
	if (mModel == NULL)
		mModel = new ModelBase ();

	if (! mModel ) {
		SetError ("Could not allocate memory for Model!");
		exit (-1);
	}
	
	/* Initialization of the modules for the model */
	if (mEntityFactory == NULL)
		mEntityFactory = new EntityFactoryBase;

	mModel->mEntityFactory = mEntityFactory;

	/* Physics */
	if (mPhysics == NULL) {
		mPhysics = new PhysicsBase ();

		if (! mPhysics ) {
			SetError ("Could not allocate memory for Physics!");
			exit (-1);
		}
	}

	mModel->mPhysics = mPhysics;
	
	mEntityFactory->Init (argc, argv);
		mModel->mPhysics->Init (argc, argv);
	mModel->Init (argc, argv);

	/* View */
	if (mView == NULL)
		mView = new ViewBase ();

	if (! mView ) {
		SetError ("Could not allocate memory for View!");
		exit (-1);
	}

	mView->mCamera = new CameraBase ();
	mView->mCamera->Init (argc, argv);

	mView->mModel = mModel;
	mView->Init (argc, argv);

	/* Controller */
	if (mController == NULL)
		mController = new ControllerBase ();

	if (! mController ) {
		SetError ("Could not allocate memory for Controller!");
		exit (-1);
	}

	mController->mModel = mModel;
	mController->mView = mView;

	mController->Init (argc, argv);


	mModel->mPhysics->RegisterCommands ();
	mModel->RegisterCommands ();

	mView->mCamera->RegisterCommands ();
	mView->RegisterCommands ();
	
	mController->RegisterCommands ();

	RegisterCommands ();
	
	/* Now we are done */
	SetStatus (EngineStatusInitialized);

	return 0;
}

void Engine::OnDestroy () {
	SetStatus (EngineStatusDestroying);

	if (mSoundManager) {
		mSoundManager->Destroy();
		delete mSoundManager;
		mSoundManager = NULL;
	}

	// Quit the SDL
	SDL_Quit ();

	if (mController) {
		mController->Destroy ();
		delete mController;
		mController = NULL;
	}

	if (mView) {
		if (mView->mCamera ) {
			mView->mCamera->Destroy ();
			delete mView->mCamera;
		}

		mView->Destroy ();
		delete mView;
		mView = NULL;
	}

	if (mModel) {
		mModel->Destroy ();

		if (mModel->mPhysics ) {
			mModel->mPhysics->Destroy ();
			delete mModel->mPhysics;
		}

	delete mModel;
		mModel = NULL;
	}

	if (mCommands) {
		mCommands->Destroy ();
		delete mCommands;
		mCommands = NULL;
	}

	if (mVariables) {
		mVariables->Destroy ();
		delete mVariables;
		mVariables = NULL;
	}

	SetStatus (EngineStatusStopped);

	if (mEventManager) {
		mEventManager->Destroy();
		delete mEventManager;
		mEventManager = NULL;
	}

	if (mLogging) {
		mLogging->Destroy ();
		delete mLogging;
		mLogging = NULL;
	}

	mStatus = EngineStatusUndefined;
	EngineInstance = NULL;
}

/*
 * Module specific functions
 */
void Engine::SetStatus (EngineStatus new_status) {
	LogDebug ("EngineStatus Change: '%s' -> '%s'", GetStringEngineStatus(mStatus), GetStringEngineStatus(new_status));
//	LogDebug ("EngineStatus Change: '%d' -> '%d'", mStatus, new_status);
	mStatus = new_status;
}

EngineStatus Engine::GetStatus () {
	return mStatus;
}

void Engine::OnMainLoop () {
	SetStatus (EngineStatusRunning);

	while (mStatus == EngineStatusRunning) {
		mController->Process ();
		mCommands->QueueExecute ();
		mEventManager->Process();
		mModel->UpdateTimer ();
		mModel->Process ();
		mController->IMGUIPrepare();
		mView->Draw ();
		mController->IMGUIFinish();
	}
}

void Engine::SetError (const char* str, ...) {
	static char msg[LOG_MAX_MESSAGE_LENGTH];
	static int last_length = LOG_MAX_MESSAGE_LENGTH;
	int i;

	for (i = 0; i < last_length; i++)
		msg[i] = 0;

	va_list ap;
	va_start(ap, str);
	vsprintf(msg, str, ap);
	va_end(ap);

	mErrorString = msg;

	last_length = strlen (msg);
}

const char* Engine::GetError () {
	return mErrorString.c_str();
}

/*
 * Global functions
 */
void EngineSetError (const char* str, ...) {
	if (EngineInstance == NULL) {
		std::cerr << "Error: Engine Instance not yet initialized!" << std::endl;
		assert (0);
	}

	static char msg[LOG_MAX_MESSAGE_LENGTH];
	static int last_length = LOG_MAX_MESSAGE_LENGTH;
	int i;

	for (i = 0; i < last_length; i++)
		msg[i] = 0;

	va_list ap;
	va_start(ap, str);
	vsprintf(msg, str, ap);
	va_end(ap);

	EngineInstance->SetError (msg);

	last_length = strlen (msg);
}

const char* EngineGetError () {
	if (EngineInstance == NULL) {
		std::cerr << "Error: Engine Instance not yet initialized!" << std::endl;
		assert (0);
	}

	return EngineInstance->GetError ();
}

void EngineSetStatus (const EngineStatus status) {
	if (EngineInstance == NULL) {
		std::cerr << "Error: Engine Instance not yet initialized!" << std::endl;
		assert (0);
	}
	
	EngineInstance->SetStatus (status);
}

EngineStatus EngineGetStatus () {
	if (EngineInstance == NULL) {
		std::cerr << "Error: Engine Instance not yet initialized!" << std::endl;
		assert (0);
	}
	
	return EngineInstance->GetStatus ();
}

ModelBase* EngineGetModel () {
	if (EngineInstance == NULL) {
		std::cerr << "Error: Engine Instance not yet initialized!" << std::endl;
		assert (0);
	}
	
	return EngineInstance->GetModel();
}

ViewBase* EngineGetView () {
	if (EngineInstance == NULL) {
		std::cerr << "Error: Engine Instance not yet initialized!" << std::endl;
		assert (0);
	}

	return EngineInstance->GetView();
}

ControllerBase* EngineGetController () {
	if (EngineInstance == NULL) {
		std::cerr << "Error: Engine Instance not yet initialized!" << std::endl;
		assert (0);
	}

	return EngineInstance->GetController();
}

std::string GetResourceFullPath (const std::string &resource) {
	if (EngineInstance == NULL) {
		std::cerr << "Error: Engine Instance not yet initialized!" << std::endl;
		assert (0);
	}

	return EngineInstance->GetResourceFullPath(resource);
}

std::string GetUserDirFullPath (const std::string &path) {
	if (EngineInstance == NULL) {
		std::cerr << "Error: Engine Instance not yet initialized!" << std::endl;
		assert (0);
	}

	return EngineInstance->GetUserDirFullPath(path);
}

bool FileExists (const std::string &path) {
	boost::filesystem::path file_path (path);

	if (!boost::filesystem::exists(file_path))
		return false;

	if (boost::filesystem::is_directory(file_path))
		return false;

	return true;
}

}

