///////////////////////////////////////////////////////////////////////////////
// MechVM
// Copyright Bjoern Ganster
// Developed since 2007
///////////////////////////////////////////////////////////////////////////////

#include <GL/glew.h>

#include "ArchiveViewer.h"
#include "BGBase.h"
#include "Config.h"
#include "dialogs.h"
#include "Font.h"
#include "MWBase.h"
#include "MechVM.h"
#include "MechWarriorInstallers.h"
#include "Installers.h"
#include "MechLab.h"
#include "StartGameScreen.h"

#include <GL/glu.h>

#include <SDL_ttf.h>

#ifdef _WIN32
#include <eh.h>
#include <shellApi.h>
#else
#include <signal.h>
#ifdef USE_QT
#include <QtWidgets/QApplication>
#endif
#endif

#include "IL/il2.h"

#include <iostream>

#define FILE_NUMBER FILE_NO_MECHVM_CPP

///////////////////////////////////////////////////////////////////////////////
// Global constants

bool s_done;
GLWindow* focused;
MechVM* mainWin;

///////////////////////////////////////////////////////////////////////////////

MechVM* getMechVm()
{
	return mainWin;
}

const char* getVersionStr ()
{
	return "MechVM " __DATE__;
}

///////////////////////////////////////////////////////////////////////////////
// Constructor, destructor

MechVM::MechVM()
: GLWindowContainer("MechVM", MechVM_ID),
  mScreen(nullptr),
  smkView(nullptr),
  mSdlWindow(nullptr),
  mFullScreen(false)
{
	createToolbar(this);
}

MechVM::~MechVM()
{
}

///////////////////////////////////////////////////////////////////////////////

void MechVM::setScreen(GLWindow* newScreen)
{
	if (newScreen != nullptr) {
		mScreen = newScreen;
	} else {
		mScreen = this;
		restore();
	}
	focused = nullptr;
}

void MechVM::deleteLater(VirtualClass* object)
{
	SET_SCOPE;
	mDeleteLater.push_back(object);
	RETURN;
}

void MechVM::deleteNow()
{
	SET_SCOPE;
	for (size_t i = 0; i < mDeleteLater.size(); ++i) {
		delete mDeleteLater[i];
	}
	SET_OP;
	mDeleteLater.clear();
	RETURN;
}

///////////////////////////////////////////////////////////////////////////////
// Create a button

GLButton* MechVM::addButton(GLWindowContainer* container, int num, const BGCPString& name, int ID)
{
	SET_SCOPE;
	Font* font = getDefaultFont();
	int buttonWidth = 12 * font->getMaxCharWidth();
	int x = buttonLeft + num * (buttonWidth + buttonDist);
	GLButton* newButton = new GLButton(container, x, buttonTop, buttonWidth, getDefaultButtonHeight());
	newButton->setTitle(name);
	Config* cfg = getConfig();
	Color backgroundColor = cfg->getViewerBackgroundColor();
	newButton->userData = ID;
	newButton->setListener(container);
	container->add(newButton);
	RETURN newButton;
}

///////////////////////////////////////////////////////////////////////////////
// Called to set new layout

void MechVM::createToolbar(GLWindowContainer* container)
{
	SET_SCOPE;
	addButton(container, 0, tr(0), playGameButtonID);
	addButton(container, 1, tr(1), MechLabButtonID);
	addButton(container, 2, tr(2), viewFileButtonID);
	addButton(container, 3, tr(3), installButtonID);
	RETURN;
}

///////////////////////////////////////////////////////////////////////////////

void openFile(const TCHAR* FN)
{
	#ifdef _WIN32
	ShellExecute(0, L"open", FN, NULL, NULL, SW_SHOWNORMAL);
	#else
	printf("Executing %s is not supported\n", FN);
	#endif
}

///////////////////////////////////////////////////////////////////////////////
// View external file

void MechVM::viewExternalFile()
{
	SET_SCOPE;
	BGCPString FN;
	BGCPString fnAscii;
	ArchiveViewer* screen = nullptr;

	if (getOpenFileName(FN)) {
		FileType fileType = getFileTypeFromExtension(FN.getAsPath());
		switch (fileType) {
		case FileType::BWD:
			clear();
			screen = new ArchiveViewer(0, 0, width, height);
			screen->exploreSingleFile(FN.getAsPath());
			mScreen = screen;
			break;
		case FileType::SMK:
			smkView = new SmackerView(this, margin, 2 * margin + getDefaultButtonHeight(), width - 2 * margin, height - top - margin);
			fnAscii = BGCPString(FN.getAsPath(), CP_SYSTEM);
			fnAscii.convertToCP(CP_ASCII);
			smkView->open(fnAscii.getPtr<char>());
			add(smkView);
			break;
		case FileType::PRJ:
		case FileType::MW2_DB:
		case FileType::ZBD:
		case FileType::ISO_9660:
		case FileType::ZFS:
		case FileType::MW4ResourceFile:
			screen = new ArchiveViewer(0, 0, width, height);
			if (screen->exploreArchive(FN.getAsPath())) {
				mScreen = screen;
			} else {
				delete screen;
			}
			break;
		default:
			/*MemoryBlock block;
			if (block.loadFromFile(FN.getAsPath())) {
				ArchiveViewer* screen = new ArchiveViewer(0, 0, width, height);
				addButton(screen, 0, "Exit", MW2PRJ_ExitButtonID);
				screen->viewFile(FN.getAsPath(), &block);
				setScreen(screen);
			}*/

			int pos = FN.findLastChar(OSPathSep);
			if (pos < 0) {
				RETURN;
			}

			BGCPString dir = FN.copyPart(0, pos);
			clear();
			ArchiveViewer* screen = new ArchiveViewer(0, 0, width, height);
			screen->viewDir(dir.getAsPath());
			mScreen = screen;
			break;
		}
	}
	RETURN;
}

///////////////////////////////////////////////////////////////////////////////
// Show widgets for starting installation

void MechVM::showInstallWidgets()
{
	// Remove other widgets
	SET_SCOPE;
	clear();
	createToolbar(this);

	Installers* installers = new Installers(0, 0, getWidth(), getHeight());
	installers->setup();
	setScreen(installers);
	RETURN;
}

///////////////////////////////////////////////////////////////////////////////

void MechVM::restore()
{
	SET_SCOPE;
	clear();
	createToolbar(this);
	mScreen = this;
	RETURN;
}

///////////////////////////////////////////////////////////////////////////////

void MechVM::startMechLab(GLWindow* returnWin)
{
	SET_SCOPE;
	MechLab* mechLab = new MechLab(MechLab_AllButtonsID, this);
	mechLab->setGeometry(0, 0, getWidth(), getHeight());
	mechLab->addWeapons("all", 3099);
	setScreen(mechLab);

	Config* cfg = getConfig();
	BGCPString mekFormatNum = cfg->getValue("MEKFileVersion");
	auto MEKFileVersion = mekFormatNum.toInt();

	mechLab->showChassisTable();
	mechLab->showMech((size_t)0);
	mechLab->setReturnVis(returnWin);
	RETURN;
}

///////////////////////////////////////////////////////////////////////////////
// Register the class passed as a listener for joystick events, if possible
// The returned joystick object should be passed to unregisterJoystickListener to free the SDL object again later

JoystickListener* gCurrJoystickListener;
SDL_Joystick* gStick;

SDL_Joystick* registerJoystickListener(JoystickListener* listener)
{
	SET_SCOPE;
	if (gCurrJoystickListener != nullptr) {
		RETURN nullptr;
	}

	gCurrJoystickListener = listener;

	if (gStick == nullptr) {
		SDL_JoystickEventState(SDL_ENABLE);
		gStick = SDL_JoystickOpen(0);
	}
	RETURN gStick;
}

///////////////////////////////////////////////////////////////////////////////
// Unregister the class passed as a listener for joystick events

void unregisterJoystickListener(JoystickListener* listener)
{
	SET_SCOPE;
	if (listener == gCurrJoystickListener) {
		gCurrJoystickListener = nullptr;
	}
	RETURN;
}

///////////////////////////////////////////////////////////////////////////////
// Receive messages

void MechVM::receiveMessage(VirtualClass* sender, int /*MessageType*/, VirtualClass* data)
{
	SET_SCOPE;
	GLWindow* win = dynamic_cast <GLWindow*> (sender);
	if (win != NULL) {
		switch (win->userData) {
			case playGameButtonID:
			{
				clear();
				StartGameScreen* screen = new StartGameScreen(0, 0, width, height);
				screen->setup();
				mScreen = screen;
				break;
			}
			case MechLabButtonID:
				startMechLab(this);
				break;
			case viewFileButtonID:
				viewExternalFile();
				break;
			case installButtonID:
				showInstallWidgets();
				break;
		}
	}
	RETURN;
}

///////////////////////////////////////////////////////////////////////////////
// Render

void MechVM::renderBase(int x, int y, int w, int h)
{
	SET_SCOPE;
	glViewport(0, 0, w, h);
	Config* cfg = getConfig();
	Color clearCol = cfg->getViewerBackgroundColor();
	glClearColor((GLclampf)clearCol.r, (GLclampf)clearCol.g, (GLclampf)clearCol.b, 0.0f);
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

	// Render widgets
	if (mScreen != nullptr && mScreen != this) {
		SET_OP;
		mScreen->render(0, 0, mainWin->getWidth(), mainWin->getHeight());
	} else {
		SET_OP;
		GLWindowContainer::render(x, y, w, h);
	}
	
	RETURN;
}

///////////////////////////////////////////////////////////////////////////////
// Forwards 

void MechVM::renderScene()
{
	SET_SCOPE;

	if (smkView != nullptr && smkView->done()) {
		safeDelete(smkView);
	}

	SET_OP;
	renderBase(0, 0, mainWin->getWidth(), mainWin->getHeight());
	SET_OP;
	SDL_GL_SwapWindow(mainWin->getWindow());
	
	RETURN;
}

bool MechVM::isInFullscreenMode() const
{
	return mFullScreen;
}

void MechVM::setFullscreenMode(bool newVal)
{
	SET_SCOPE;
	if (newVal) {
		SDL_SetWindowFullscreen(mSdlWindow, SDL_WINDOW_FULLSCREEN_DESKTOP);
	} else {
		SDL_SetWindowFullscreen(mSdlWindow, 0);
	}
	mFullScreen = newVal;
	RETURN;
}

////////////////////////////////////////////////////////////////////////////////
// Keyboard events

void MechVM::keyPressed(BGKey key, bool shiftHeld, bool controlHeld, bool down, bool repeat)
{
	try {
		SET_SCOPE;
		if (key == SDLK_RETURN && shiftHeld && down) {
			setFullscreenMode(!isInFullscreenMode());
		} else {
			if (focused != nullptr) {
				focused->keypressed(key, shiftHeld, controlHeld, down, repeat);
			} else if (mScreen != NULL) {
				mScreen->keypressed(key, shiftHeld, controlHeld, down, repeat);
			}
		}
		RETURN;
	}
	catch (...) { // Exception
		COUT << "Caught an unknown exception while handling key " << key << std::endl;
	}
}

////////////////////////////////////////////////////////////////////////////////
// Window size changed

void MechVM::changeSize(GLsizei width, GLsizei height)
{
	try {
		SET_SCOPE;
		printf("New window size is %ix%i\n", width, height);
		const int StrangeCorrectionConstant = 0; //10;
		if (mScreen != nullptr) {
			SET_OP;
			mScreen->setGeometry(0, 0, width, height + StrangeCorrectionConstant);
		}
		if (mScreen != this) {
			SET_OP;
			setGeometry(0, 0, width, height + StrangeCorrectionConstant);
		}

		RETURN;
	}
	catch (...) { // Exception
		COUT << "Caught an unknown exception while changing size to " << width << "x" << height << std::endl;
	}
}

////////////////////////////////////////////////////////////////////////////////
// Button state changed

void MechVM::buttonStateChange(int button, bool down, int x, int y)
{
	try {
		SET_SCOPE;
		if (mScreen != nullptr && mScreen != this) {
			focused = mScreen->buttonStateChanged(button, down, x, y);
		} else {
			if (focused != NULL
				&& !down
				&& x > focused->getLeft()
				&& y > focused->getTop()
				&& x < focused->getRight()
				&& y < focused->getBottom())
			{
				focused = focused->buttonStateChanged(button, down, x, y);
			} else {
				focused = GLWindowContainer::buttonStateChanged(button, down, x, y);
			}
		}
		RETURN;
	}
	catch (...) {
		log("Caught an unknown exception while processing a mouse"
			"button state change\n");
	}
}

////////////////////////////////////////////////////////////////////////////////
// Mouse moved

void MechVM::mouseMovedBroker(int x, int y)
{
	try {
		SET_SCOPE;
		if (mScreen != nullptr) {
			mScreen->mouseMoved(x, y);
		}
		if (focused != NULL) {
			focused->mouseMoved(x, y);
		} else {
			GLWindow::mouseMoved(x, y);
		}
		RETURN;
	}
	catch (...) { // Exception
		log("Caught an unknown exception while processing a mouse move\n");
	}
}

///////////////////////////////////////////////////////////////////////////////

void MechVM::mouseWheelMoved(int x, int y)
{
	try {
		SET_SCOPE;
		if (mScreen != nullptr) {
			mScreen->mouseWheelMoved(x, y);
		}
		RETURN;
	}
	catch (...) { // Exception
		log("Caught an unknown exception while processing a mouse move\n");
	}
}

///////////////////////////////////////////////////////////////////////////////

int gButtonHeight; // 1.2 * getDefaultFont()->getCharHeight

int getDefaultButtonHeight()
{
	return gButtonHeight;
}

///////////////////////////////////////////////////////////////////////////////
// Main function

void sdlQuickErrorCheck(int line)
{
	const char* msg = SDL_GetError();
	if (bgstrlen(msg) > 0) {
		COUT << "Line " << line << ", SDL reports error " << msg << std::endl;
		SDL_ClearError();
	}

	#ifdef _WIN32
	auto winError = GetLastError();
	if (winError != 0) {
		COUT << "Windows indicates error " << winError << std::endl;
		BGCPString::getOSError(winError).printLn();
	}
	#endif
}

void runTests();

#ifdef _WIN32
void se_catcher(unsigned int code, EXCEPTION_POINTERS* pExp)
{
	CONSTEXPR size_t bufSize = 2048;
	char buf[bufSize];
	buf[0] = 0;
	stGetCurrentThreadStackTrace(buf, bufSize);
	BGCPString msg = BGCPString("A Windows system exception was caught, code ") + 
		BGCPString::fromInt(code, 16) + BGCPString(". Stacktrace: ") + BGCPString(buf, CP_ASCII);
	msg.convertToCP(CP_UTF16_LE);
	MessageBoxW(0, msg.getPtr<wchar_t>(), L"MechVM crash", MB_OK | MB_ICONERROR);
	exit(0);
}
#else
void mySigHandler(int signum)
{
	COUT << "Signal received: "<< signum << std::endl;
	logStackTrace("Stacktrace: ");
	exit(0);
}
#endif

void ST_CALL_CONV MechVM_MissingReturnHandler()
{
	COUT << "Missing RETURN ..." << std::endl;
}

#define MECHDIR L"C:\\Data\\devel\\MechVM-2022-12-28\\mechs\\"
#define PRJDIR L"C:\\Data\\MW2\\mw2.prj\\"

#ifdef _WIN32
#ifdef UNICODE
int wmain(int argc, wchar_t* argv[])
#else
int main(int argc, char* argv[])
#endif
#else
int main(int argc, char* argv[])
#endif
{
	stInitialize();
	#ifdef _DEBUG
	setMissingReturnCallback(MechVM_MissingReturnHandler);
	#endif

	SET_SCOPE;
	#ifdef _WIN32
	SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOALIGNMENTFAULTEXCEPT | SEM_NOGPFAULTERRORBOX);
	_set_se_translator(se_catcher);
	#else
	sysv_signal(SIGSEGV, mySigHandler);
	sysv_signal(SIGABRT, mySigHandler);
	#endif

	SET_OP;
	if (argc == 1) {
		setDataDir();
	} else {
		setDataDir(BGCPString(argv[1], CP_SYSTEM), false);
	}

	#ifdef _DEBUG
	SET_OP;
	runTests();
	//activateSimpleMemoryTracker(); // Careful, takes a lot of performance!
	#endif

	// NetMech Mechs do not need to be encrypted?

	//xorEncrypt(MECHDIR L"mw2+Annihilator\\Annihilator.obj", PRJDIR L"mw2-gbl-tt\\SNDS\\PETE001S.SFL");
	//xorEncrypt(MECHDIR L"mw2+Executioner\\Executioner.obj", PRJDIR L"mw2-gbl-tt\\SNDS\\PETE001S.SFL");
	//xorEncrypt(MECHDIR L"mw2+Grizzly\\Grizzly.obj", PRJDIR L"mw2-gbl-tt\\SNDS\\PETE001S.SFL");
	//xorEncrypt(MECHDIR L"mw2+Hatamoto-Chi\\Hatamoto Chi.obj", PRJDIR L"mw2-gbl-tt\\SNDS\\PETE001S.SFL");
	//xorEncrypt(MECHDIR L"mw2+Horned Owl\\Horned Owl.obj", PRJDIR L"mw2-gbl-tt\\SNDS\\PETE001S.SFL");
	//xorEncrypt(MECHDIR L"mw2+Incubus\\Incubus.obj", PRJDIR L"mw2-gbl-tt\\SNDS\\PETE001S.SFL");
	//xorEncrypt(MECHDIR L"mw2+Horned Owl\\Horned Owl.obj", PRJDIR L"mw2-gbl-tt\\SNDS\\PETE001S.SFL");
	//xorEncrypt(MECHDIR L"mw2+Kodiak\\Kodiak.obj", PRJDIR L"mw2-gbl-tt\\SNDS\\PETE001S.SFL");
	//xorEncrypt(MECHDIR L"mw2+Linebacker\\Linebacker.obj", PRJDIR L"mw2-gbl-tt\\SNDS\\PETE001S.SFL");
	//xorEncrypt(MECHDIR L"mw2+Naga\\Naga.obj", PRJDIR L"mw2-gbl-tt\\SNDS\\PETE001S.SFL");
	//xorEncrypt(MECHDIR L"mw2+Phantom\\Phantom.obj", PRJDIR L"mw2-gbl-tt\\SNDS\\PETE001S.SFL");
	//xorEncrypt(MECHDIR L"mw2+Raven\\Raven.obj", PRJDIR L"mw2-gbl-tt\\SNDS\\PETE001S.SFL");
	//xorEncrypt(MECHDIR L"mw2+Stone Rhino\\Stone Rhino.obj", PRJDIR L"mw2-gbl-tt\\SNDS\\PETE001S.SFL");

	//xorEncrypt(MECHDIR L"mw2+Clint\\Clint.obj", PRJDIR L"mw2-mercs-dos\\SNDS\\WOL1001S.SFL");
	//xorEncrypt(MECHDIR L"mw2+Commando\\Commando.obj", PRJDIR L"mw2-mercs-dos\\SNDS\\WOL1001S.SFL");
	//xorEncrypt(MECHDIR L"mw2+Crab\\Crab.obj", PRJDIR L"mw2-mercs-dos\\SNDS\\WOL1001S.SFL");
	//xorEncrypt(MECHDIR L"mw2+Hunchback\\Hunchback.obj", PRJDIR L"mw2-mercs-dos\\SNDS\\WOL1001S.SFL");
	//xorEncrypt(MECHDIR L"mw2+Jenner\\Jenner.obj", PRJDIR L"mw2-mercs-dos\\SNDS\\WOL1001S.SFL");
	//xorEncrypt(MECHDIR L"mw2+Pegasus\\Pegasus.obj", PRJDIR L"mw2-mercs-dos\\SNDS\\WOL1001S.SFL");
	//xorEncrypt(MECHDIR L"mw2+Stalker\\Stalker.obj", PRJDIR L"mw2-mercs-dos\\SNDS\\WOL1001S.SFL");
	//xorEncrypt(MECHDIR L"mw2+Zeus\\Zeus.obj", PRJDIR L"mw2-mercs-dos\\SNDS\\WOL1001S.SFL");

	SET_OP;
	BGCPString msg(getVersionStr(), CP_ASCII);
	msg.printLn();
	printf("This is the message window for MechVM. If you close it, MechVM terminates.\n");

	SET_OP;
	gCurrJoystickListener = nullptr;
	gStick = nullptr;
	BGCPString configFN(getDataDir());
	configFN.addPathSep("MechVM.cfg");
	Config* cfg = createConfig();

	SET_OP;
	if (!cfg->readFromFile(configFN.getAsPath())) {
		BGCPString msg = BGCPString("Failed to read configuration file ") + configFN;
		msg.printLn();
	}

	#ifdef USE_QT
	SET_OP;
	QApplication app(argc, argv);
	#endif

	// Load language file
	SET_OP;
	BGCPString langFNbase = cfg->getValue("Language");
	BGCPString langFN(getDataDir());
	langFN.addPathSep(langFNbase);

	SET_OP;
	if (!loadLangFile(langFN.getAsPath(), CP_UTF8)) {
		BGCPString errStr = "Cannot load language definition file - ";
		errStr += BGCPString(langFN.getAsPath(), CP_SYSTEM);
		errStr += BGCPString(" - Exiting");
		#ifdef _WIN32
		messageBox("Error", errStr);
		#else
		errStr.printLn();
		#endif
		RETURN 0;
	}

	SET_OP;
	Config* config = getConfig();
	auto fontName = config->getValue("FontName");
	fontName.convertToCP(CP_ASCII);
	auto fontSizeStr = config->getValue("FontSize");
	int fontSize = (int)fontSizeStr.toInt();
	if (fontSizeStr == "auto") {
		#ifdef _WIN32
		int screenWidth = GetSystemMetrics(SM_CXSCREEN);
		fontSize = screenWidth / 60;
		#endif
	}

	SET_OP;
	#ifdef _WIN32
	Font* newFont = Font::loadFromSystem(fontName.get<char>(), fontSize, (fontSize + 9) / 10);
	if (newFont == nullptr) {
		COUT << "Unable to obtain system font ";
		fontName.printLn();
		RETURN 0;
	}
	#else
	MemoryBlock mb;
	if (!mb.loadFromFile(fontName.getAsPath())) {
		COUT << "Unable to load font ";
		fontName.printLn();
		RETURN 0;
	}
	COUT << "Font ";
	fontName.print();
	COUT << " has " << mb.getSize() << " bytes" << std::endl;
	TTF_Init();
	Font* newFont = Font::loadFromTTF(mb.getPtr<char>(), mb.getSize(), fontSize);
	if (newFont == nullptr) {
		COUT << "Unable to decode font ";
		fontName.printLn();
		RETURN 0;
	}
	#endif

	SET_OP;
	setDefaultFont(newFont);
	gButtonHeight = (int)(1.2 * newFont->getCharHeight());

	mainWin = new MechVM();
	// Want to be lazy about this ...
	fillWeaponsList();
	focused = NULL;

	// Init Renderer
	SET_OP;
	SDL_Init(SDL_INIT_VIDEO | SDL_INIT_JOYSTICK | SDL_INIT_EVENTS | SDL_INIT_AUDIO | SDL_INIT_TIMER);
	sdlQuickErrorCheck(__LINE__);
	SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24); // default: 16, 32 doesn't work ...
	sdlQuickErrorCheck(__LINE__);
	SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8); // default: 16, 32 doesn't work ...
	sdlQuickErrorCheck(__LINE__);

	// Can't switch to DosBox in fullscreen mode under Windows 7 ...
	// SDL_WINDOW_ALLOW_HIGHDPI seems to do nothing under Windows. The relevant setting seems to be 
	// configuration properties -> manifest -> input and output -> dpi in MSVC 2010
	//#ifdef _DEBUG
	int flags = SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE | SDL_WINDOW_MAXIMIZED | SDL_INIT_JOYSTICK;
	SDL_Window* sdlWindow = SDL_CreateWindow(getVersionStr(), SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
		InitialWindowWidth, InitialWindowHeight, flags);
	sdlQuickErrorCheck(__LINE__);
	/*#else
	SDL_Window* sdlWindow = SDL_CreateWindow(getVersionStr(), SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
		InitialWindowWidth, InitialWindowHeight, SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE | SDL_WINDOW_FULLSCREEN_DESKTOP | SDL_WINDOW_ALLOW_HIGHDPI);
	#endif*/
	SDL_GL_CreateContext(sdlWindow);
	sdlQuickErrorCheck(__LINE__);
	mainWin->setWindow(sdlWindow);
	sdlQuickErrorCheck(__LINE__);
	if (sdlWindow == NULL) {
		COUT <<"Unable to obtain rendering window - aborting" << std::endl;
		RETURN 0;
	}

	// Print OpenGL information
	SET_OP;
	const char* vendor = (const char*)glGetString(GL_VENDOR);
	const char* renderer = (const char*)glGetString(GL_RENDERER);
	const char* version = (const char*)glGetString(GL_VERSION);
	sdlQuickErrorCheck(__LINE__);

	COUT << "GL Vendor: " << vendor << std::endl;
	COUT << "GL Renderer: " << renderer << std::endl;
	COUT << "GL Version: " << version << std::endl;

	// Print depth & stencil bits
	/*int depthBits = 0;
	int stencilBits = 0;
	glGetIntegerv(GL_DEPTH_BITS, &depthBits);
	COUT << "Depth bits: " << depthBits << std::endl;
	glGetIntegerv(GL_STENCIL_BITS, &stencilBits);
	COUT << "Stencil bits: " << stencilBits << std::endl;*/

	/*const char* glExtensions = (const char*) glGetString(GL_EXTENSIONS);
	COUT << "Extensions: " << glExtensions << std::endl;*/

	SET_OP;
	glewExperimental = true;
	GLenum glewError = glewInit();
	if (glewError != GLEW_OK) {
		BGCPString str = BGCPString::fromInt(glewError);
		str.convertToCP(CP_ASCII);
		messageBox("glew init error", str.get<char>(), MB_OK | MB_ICONERROR);
		return 0;
	}

	// Prepare audio
	prepareAudio();

	// Load MechVM logo ... seems to work mostly now ... 
	SET_OP;
	sdlQuickErrorCheck(__LINE__);
	MemoryBlock logoBlock;
	BGCPString logoFN("MechVM logo.png"); // helps with cp conversion
	if (logoBlock.loadFromFile(logoFN.getAsPath())) {
		ILimage* image = il2GenImage();
		if (il2LoadL(image, IL_PNG, logoBlock.getPtr<char>(), logoBlock.getSize())) {
			if (il2ConvertImage(image, IL_RGB, IL_UNSIGNED_BYTE)) {
				int w = 0, h = 0;
				il2GetImageInteger (image, IL_IMAGE_WIDTH,  &w);
				il2GetImageInteger (image, IL_IMAGE_HEIGHT, &h);
				sdlQuickErrorCheck(__LINE__);
				SDL_Surface* logoSurface = SDL_CreateRGBSurface(0, w, h, 24, 0xff, 0xff00, 0xff0000, 0);
				sdlQuickErrorCheck(__LINE__);

				if (logoSurface != nullptr) {
					uint8_t* data = il2GetData(image);
					SDL_memcpy(logoSurface->pixels, data, 3 * w * h);
					sdlQuickErrorCheck(__LINE__);
					SDL_SetWindowIcon(sdlWindow, logoSurface);
					sdlQuickErrorCheck(__LINE__);
					SDL_FreeSurface(logoSurface);
					COUT << "Logo dimensions: " << w << "x" << h << std::endl;
				}
			}
			il2DeleteImage(image);
		}
	}

	s_done = false;
	do {
		SET_OP;
		try {
			mainWin->renderScene();

			SET_OP;
			SDL_Event event;
			while (SDL_PollEvent(&event)) {
				switch (event.type) {
					case SDL_QUIT:
						SET_OP;
						s_done = 1;
						break;
					case SDL_WINDOWEVENT:
						SET_OP;
						switch (event.window.event) {
						case SDL_WINDOWEVENT_RESIZED:
							mainWin->changeSize(event.window.data1, event.window.data2);
							break;
						case SDL_WINDOWEVENT_SIZE_CHANGED:
							mainWin->changeSize(event.window.data1, event.window.data2);
							break;
						}
						break;
					case SDL_KEYDOWN:  /* Handle a KEYDOWN event */
					{
						SET_OP;
						bool shiftDown = false, ctrlDown = false, altGrDown = false;
						checkShiftAndControl(&shiftDown, &ctrlDown, &altGrDown);
						bool repeat = (event.key.repeat > 0);
						mainWin->keyPressed(event.key.keysym.sym, shiftDown, ctrlDown, true, repeat);
						break;
					}
					case SDL_KEYUP:  /* Handle a KEYDOWN event */
					{
						SET_OP;
						bool shiftDown = false, ctrlDown = false, altGrDown = false;
						checkShiftAndControl(&shiftDown, &ctrlDown, &altGrDown);
						bool repeat = (event.key.repeat > 0);
						SET_OP;
						mainWin->keyPressed(event.key.keysym.sym, shiftDown, ctrlDown, false, repeat);
						break;
					}
					case SDL_MOUSEMOTION:
						SET_OP;
						mainWin->mouseMovedBroker(event.motion.x, event.motion.y);
						break;
					case SDL_MOUSEBUTTONDOWN:
						SET_OP;
						mainWin->buttonStateChange(event.button.button, true, event.button.x, event.button.y);
						break;
					case SDL_MOUSEBUTTONUP:
						SET_OP;
						mainWin->buttonStateChange(event.button.button, false, event.button.x, event.button.y);
						break;
					case SDL_JOYAXISMOTION:
					case SDL_JOYBUTTONDOWN:
					case SDL_JOYBUTTONUP:
						SET_OP;
						if (gCurrJoystickListener!= nullptr) {
							gCurrJoystickListener->joystickEvent(&event);
						}
						break;
					case SDL_MOUSEWHEEL:
						SET_OP;
						mainWin->mouseWheelMoved(event.wheel.x, event.wheel.y);
						break;
				}
			}

			SET_OP;
			mainWin->deleteNow();
		}
		catch (BGException& e) {
			COUT << "Caught a BGException ";
			e.printLn();
		}
		catch (std::exception& e) {
			CONSTEXPR size_t bufSize = 2048;
			char buf[bufSize];
			buf[0] = 0;
			stGetCurrentThreadStackTrace(buf, bufSize);
			printf("Caught an exception\n"); // does not catch any crash(x) calls
			COUT << e.what() << std::endl;
			COUT << "Stacktrace: " << buf << std::endl;
		}
		catch (...) {
			CONSTEXPR size_t bufSize = 2048;
			char buf[bufSize];
			buf[0] = 0;
			stGetCurrentThreadStackTrace(buf, bufSize);
			COUT << "Caught an unknown exception" << std::endl; // does not catch any crash(x) calls
			COUT << "Stacktrace: " << buf << std::endl;
		}
	} while (!s_done);

	SET_OP;
	#ifdef _DEBUG
	dumpMemoryUsage();
	#endif
	COUT << "Time to say goodbye" << std::endl;

	TTF_Quit();

	RETURN 0;
}
