// ----------------------------------------------------------------------------
// Another Assimp OpenGL sample including texturing.
// Note that it is very basic and will only read and apply the model's diffuse
// textures (by their material ids)
//
// Don't worry about the "Couldn't load Image: ...dwarf2.jpg" Message.
// It's caused by a bad texture reference in the model file (I guess)
//
// If you intend to _use_ this code sample in your app, do yourself a favour
// and replace immediate mode calls with VBOs ...
//
// Thanks to NeHe on whose OpenGL tutorials this one's based on! :)
// http://nehe.gamedev.net/
// ----------------------------------------------------------------------------
#include <windows.h>
#include <shellapi.h>
#include <stdio.h>
#include <GL/gl.h>
#include <GL/glu.h>

#pragma warning(disable: 4100) // Disable warning 'unreferenced formal parameter'
#define STB_IMAGE_IMPLEMENTATION
#include "contrib/stb_image/stb_image.h"
#pragma warning(default: 4100) // Enable warning 'unreferenced formal parameter'

#include <fstream>

//to map image filenames to textureIds
#include <string.h>
#include <map>

// assimp include files. These three are usually needed.
#include <assimp/Importer.hpp>
#include <assimp/postprocess.h>
#include <assimp/scene.h>
#include <assimp/DefaultLogger.hpp>
#include <assimp/LogStream.hpp>
#include "UTFConverter.h"

// The default hard-coded path. Can be overridden by supplying a path through the command line.
static std::string modelpath = "../../test/models/OBJ/spider.obj";

HGLRC       hRC=nullptr;            // Permanent Rendering Context
HDC         hDC=nullptr;            // Private GDI Device Context
HWND        g_hWnd=nullptr;         // Holds Window Handle
HINSTANCE   g_hInstance=nullptr;    // Holds The Instance Of The Application

bool		keys[256];			// Array used for Keyboard Routine;
bool		active=TRUE;		// Window Active Flag Set To TRUE by Default
bool		fullscreen=TRUE;	// full-screen Flag Set To full-screen By Default

GLfloat		xrot;
GLfloat		yrot;
GLfloat		zrot;


LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);	// Declaration For WndProc
GLboolean abortGLInit(const char*);

const char* windowTitle = "OpenGL Framework";

GLfloat LightAmbient[]= { 0.5f, 0.5f, 0.5f, 1.0f };
GLfloat LightDiffuse[]= { 1.0f, 1.0f, 1.0f, 1.0f };
GLfloat LightPosition[]= { 0.0f, 0.0f, 15.0f, 1.0f };



// the global Assimp scene object
const aiScene* g_scene = nullptr;
GLuint scene_list = 0;
aiVector3D scene_min, scene_max, scene_center;

// images / texture
std::map<std::string, GLuint*> textureIdMap;	// map image filenames to textureIds
GLuint*		textureIds;							// pointer to texture Array

// Create an instance of the Importer class
Assimp::Importer importer;

using namespace AssimpSamples::SharedCode;

void createAILogger()
{
    // Change this line to normal if you not want to analyse the import process
	//Assimp::Logger::LogSeverity severity = Assimp::Logger::NORMAL;
	Assimp::Logger::LogSeverity severity = Assimp::Logger::VERBOSE;

	// Create a logger instance for Console Output
	Assimp::DefaultLogger::create("",severity, aiDefaultLogStream_STDOUT);

	// Create a logger instance for File Output (found in project folder or near .exe)
	Assimp::DefaultLogger::create("assimp_log.txt",severity, aiDefaultLogStream_FILE);

	// Now I am ready for logging my stuff
	Assimp::DefaultLogger::get()->info("this is my info-call");
}

void destroyAILogger()
{
	// Kill it after the work is done
	Assimp::DefaultLogger::kill();
}

void logInfo(std::string logString)
{
	// Will add message to File with "info" Tag
	Assimp::DefaultLogger::get()->info(logString.c_str());
}

void logDebug(const char* logString)
{
	// Will add message to File with "debug" Tag
	Assimp::DefaultLogger::get()->debug(logString);
}


bool Import3DFromFile( const std::string& pFile)
{
	// Check if file exists
	std::ifstream fin(pFile.c_str());
	if(!fin.fail())
	{
		fin.close();
	}
	else
	{
		MessageBox(nullptr, UTFConverter("Couldn't open file: " + pFile).c_wstr() , TEXT("ERROR"), MB_OK | MB_ICONEXCLAMATION);
		logInfo( importer.GetErrorString());
		return false;
	}

	g_scene = importer.ReadFile(pFile, aiProcessPreset_TargetRealtime_Quality);

	// If the import failed, report it
	if(!g_scene)
	{
		logInfo( importer.GetErrorString());
		return false;
	}

	// Now we can access the file's contents.
	logInfo("Import of scene " + pFile + " succeeded.");

	// We're done. Everything will be cleaned up by the importer destructor
	return true;
}

// Resize And Initialize The GL Window
void ReSizeGLScene(GLsizei width, GLsizei height)
{
    // Prevent A Divide By Zero By
	if (height==0)
	{
        // Making Height Equal One
        height=1;
	}

	glViewport(0, 0, width, height);					// Reset The Current Viewport

	glMatrixMode(GL_PROJECTION);						// Select The Projection Matrix
	glLoadIdentity();							// Reset The Projection Matrix

	// Calculate The Aspect Ratio Of The Window
	gluPerspective(45.0f,(GLfloat)width/(GLfloat)height,0.1f,100.0f);

	glMatrixMode(GL_MODELVIEW);						// Select The Modelview Matrix
	glLoadIdentity();							// Reset The Modelview Matrix
}


std::string getBasePath(const std::string& path)
{
	size_t pos = path.find_last_of("\\/");
	return (std::string::npos == pos) ? "" : path.substr(0, pos + 1);
}

void freeTextureIds()
{
	textureIdMap.clear(); //no need to delete pointers in it manually here. (Pointers point to textureIds deleted in next step)

	if (textureIds)
	{
		delete[] textureIds;
		textureIds = nullptr;
	}
}

int LoadGLTextures(const aiScene* scene)
{
	freeTextureIds();

	//ILboolean success;

	/* Before calling ilInit() version should be checked. */
	/*if (ilGetInteger(IL_VERSION_NUM) < IL_VERSION)
	{
		/// wrong DevIL version ///
		std::string err_msg = "Wrong DevIL version. Old devil.dll in system32/SysWow64?";
		char* cErr_msg = (char *) err_msg.c_str();
		abortGLInit(cErr_msg);
		return -1;
	}*/

	//ilInit(); /* Initialization of DevIL */

    if (scene->HasTextures()) return 1;
        //abortGLInit("Support for meshes with embedded textures is not implemented");

	/* getTexture Filenames and Numb of Textures */
	for (unsigned int m=0; m<scene->mNumMaterials; m++)
	{
		int texIndex = 0;
		aiReturn texFound = AI_SUCCESS;

		aiString path;	// filename

		while (texFound == AI_SUCCESS)
		{
			texFound = scene->mMaterials[m]->GetTexture(aiTextureType_DIFFUSE, texIndex, &path);
			textureIdMap[path.data] = nullptr; //fill map with textures, pointers still NULL yet
			texIndex++;
		}
	}

	const size_t numTextures = textureIdMap.size();


	/* array with DevIL image IDs */
	//ILuint* imageIds = NULL;
//	imageIds = new ILuint[numTextures];

	/* generate DevIL Image IDs */
//	ilGenImages(numTextures, imageIds); /* Generation of numTextures image names */

	/* create and fill array with GL texture ids */
	textureIds = new GLuint[numTextures];
	glGenTextures(static_cast<GLsizei>(numTextures), textureIds); /* Texture name generation */

	/* get iterator */
	std::map<std::string, GLuint*>::iterator itr = textureIdMap.begin();

	std::string basepath = getBasePath(modelpath);
	for (size_t i=0; i<numTextures; i++)
	{

		//save IL image ID
		std::string filename = (*itr).first;  // get filename
		(*itr).second =  &textureIds[i];	  // save texture id for filename in map
		itr++;								  // next texture


		//ilBindImage(imageIds[i]); /* Binding of DevIL image name */
		std::string fileloc = basepath + filename;	/* Loading of image */
		//success = ilLoadImage(fileloc.c_str());
        int x, y, n;
        unsigned char *data = stbi_load(fileloc.c_str(), &x, &y, &n, STBI_rgb_alpha);

		if (nullptr != data )
		{
            // Convert every colour component into unsigned byte.If your image contains
            // alpha channel you can replace IL_RGB with IL_RGBA
            //success = ilConvertImage(IL_RGB, IL_UNSIGNED_BYTE);
			/*if (!success)
			{
				abortGLInit("Couldn't convert image");
				return -1;
			}*/
            // Binding of texture name
            glBindTexture(GL_TEXTURE_2D, textureIds[i]);
			// redefine standard texture values
            // We will use linear interpolation for magnification filter
            glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
            // We will use linear interpolation for minifying filter
            glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
            // Texture specification
            glTexImage2D(GL_TEXTURE_2D, 0, n, x, y, 0, GL_RGBA, GL_UNSIGNED_BYTE, data);// Texture specification.

            // we also want to be able to deal with odd texture dimensions
            glPixelStorei( GL_UNPACK_ALIGNMENT, 1 );
            glPixelStorei( GL_UNPACK_ROW_LENGTH, 0 );
            glPixelStorei( GL_UNPACK_SKIP_PIXELS, 0 );
            glPixelStorei( GL_UNPACK_SKIP_ROWS, 0 );
            stbi_image_free(data);
        }
		else
		{
			/* Error occurred */
			MessageBox(nullptr, UTFConverter("Couldn't load Image: " + fileloc).c_wstr(), TEXT("ERROR"), MB_OK | MB_ICONEXCLAMATION);
		}
	}
    // Because we have already copied image data into texture data  we can release memory used by image.
//	ilDeleteImages(numTextures, imageIds);

	// Cleanup
	//delete [] imageIds;
	//imageIds = NULL;

	return TRUE;
}

// All Setup For OpenGL goes here
int InitGL()
{
	if (!LoadGLTextures(g_scene))
	{
		return FALSE;
	}


	glEnable(GL_TEXTURE_2D);
	glShadeModel(GL_SMOOTH);		 // Enables Smooth Shading
	glClearColor(1.0f, 1.0f, 1.0f, 0.0f);
	glClearDepth(1.0f);				// Depth Buffer Setup
	glEnable(GL_DEPTH_TEST);		// Enables Depth Testing
	glDepthFunc(GL_LEQUAL);			// The Type Of Depth Test To Do
	glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);	// Really Nice Perspective Calculation


	glEnable(GL_LIGHTING);
	glEnable(GL_LIGHT0);    // Uses default lighting parameters
	glLightModeli(GL_LIGHT_MODEL_TWO_SIDE, GL_TRUE);
	glEnable(GL_NORMALIZE);

	glLightfv(GL_LIGHT1, GL_AMBIENT, LightAmbient);
	glLightfv(GL_LIGHT1, GL_DIFFUSE, LightDiffuse);
	glLightfv(GL_LIGHT1, GL_POSITION, LightPosition);
	glEnable(GL_LIGHT1);

	return TRUE;					// Initialization Went OK
}


// Can't send color down as a pointer to aiColor4D because AI colors are ABGR.
void Color4f(const aiColor4D *color)
{
	glColor4f(color->r, color->g, color->b, color->a);
}

void set_float4(float f[4], float a, float b, float c, float d)
{
	f[0] = a;
	f[1] = b;
	f[2] = c;
	f[3] = d;
}

void color4_to_float4(const aiColor4D *c, float f[4])
{
	f[0] = c->r;
	f[1] = c->g;
	f[2] = c->b;
	f[3] = c->a;
}

void apply_material(const aiMaterial *mtl)
{
	float c[4];

	GLenum fill_mode;
	int ret1, ret2;
	aiColor4D diffuse;
	aiColor4D specular;
	aiColor4D ambient;
	aiColor4D emission;
	ai_real shininess, strength;
	int two_sided;
	int wireframe;
	unsigned int max;	// changed: to unsigned

	int texIndex = 0;
	aiString texPath;	//contains filename of texture

	if(AI_SUCCESS == mtl->GetTexture(aiTextureType_DIFFUSE, texIndex, &texPath))
	{
		//bind texture
		unsigned int texId = *textureIdMap[texPath.data];
		glBindTexture(GL_TEXTURE_2D, texId);
	}

	set_float4(c, 0.8f, 0.8f, 0.8f, 1.0f);
	if(AI_SUCCESS == aiGetMaterialColor(mtl, AI_MATKEY_COLOR_DIFFUSE, &diffuse))
		color4_to_float4(&diffuse, c);
	glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, c);

	set_float4(c, 0.0f, 0.0f, 0.0f, 1.0f);
	if(AI_SUCCESS == aiGetMaterialColor(mtl, AI_MATKEY_COLOR_SPECULAR, &specular))
		color4_to_float4(&specular, c);
	glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, c);

	set_float4(c, 0.2f, 0.2f, 0.2f, 1.0f);
	if(AI_SUCCESS == aiGetMaterialColor(mtl, AI_MATKEY_COLOR_AMBIENT, &ambient))
		color4_to_float4(&ambient, c);
	glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT, c);

	set_float4(c, 0.0f, 0.0f, 0.0f, 1.0f);
	if(AI_SUCCESS == aiGetMaterialColor(mtl, AI_MATKEY_COLOR_EMISSIVE, &emission))
		color4_to_float4(&emission, c);
	glMaterialfv(GL_FRONT_AND_BACK, GL_EMISSION, c);

	max = 1;
	ret1 = aiGetMaterialFloatArray(mtl, AI_MATKEY_SHININESS, &shininess, &max);
	max = 1;
	ret2 = aiGetMaterialFloatArray(mtl, AI_MATKEY_SHININESS_STRENGTH, &strength, &max);
	if((ret1 == AI_SUCCESS) && (ret2 == AI_SUCCESS))
		glMaterialf(GL_FRONT_AND_BACK, GL_SHININESS, shininess * strength);
	else {
		glMaterialf(GL_FRONT_AND_BACK, GL_SHININESS, 0.0f);
		set_float4(c, 0.0f, 0.0f, 0.0f, 0.0f);
		glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, c);
	}

	max = 1;
	if(AI_SUCCESS == aiGetMaterialIntegerArray(mtl, AI_MATKEY_ENABLE_WIREFRAME, &wireframe, &max))
		fill_mode = wireframe ? GL_LINE : GL_FILL;
	else
		fill_mode = GL_FILL;
	glPolygonMode(GL_FRONT_AND_BACK, fill_mode);

	max = 1;
	if((AI_SUCCESS == aiGetMaterialIntegerArray(mtl, AI_MATKEY_TWOSIDED, &two_sided, &max)) && two_sided)
		glEnable(GL_CULL_FACE);
	else
		glDisable(GL_CULL_FACE);
}


void recursive_render (const struct aiScene *sc, const struct aiNode* nd, float scale)
{
	unsigned int i;
	unsigned int n=0, t;
	aiMatrix4x4 m = nd->mTransformation;

	aiMatrix4x4 m2;
	aiMatrix4x4::Scaling(aiVector3D(scale, scale, scale), m2);
	m = m * m2;

	// update transform
	m.Transpose();
	glPushMatrix();
	glMultMatrixf((float*)&m);

	// draw all meshes assigned to this node
	for (; n < nd->mNumMeshes; ++n)
	{
		const struct aiMesh* mesh = sc->mMeshes[nd->mMeshes[n]];

		apply_material(sc->mMaterials[mesh->mMaterialIndex]);


		if(mesh->mNormals == nullptr)
		{
			glDisable(GL_LIGHTING);
		}
		else
		{
			glEnable(GL_LIGHTING);
		}

		if(mesh->mColors[0] != nullptr)
		{
			glEnable(GL_COLOR_MATERIAL);
		}
		else
		{
			glDisable(GL_COLOR_MATERIAL);
		}

		for (t = 0; t < mesh->mNumFaces; ++t) {
			const struct aiFace* face = &mesh->mFaces[t];
			GLenum face_mode;

			switch(face->mNumIndices)
			{
				case 1: face_mode = GL_POINTS; break;
				case 2: face_mode = GL_LINES; break;
				case 3: face_mode = GL_TRIANGLES; break;
				default: face_mode = GL_POLYGON; break;
			}

			glBegin(face_mode);

			for(i = 0; i < face->mNumIndices; i++)		// go through all vertices in face
			{
				int vertexIndex = face->mIndices[i];	// get group index for current index
				if(mesh->mColors[0] != nullptr)
					Color4f(&mesh->mColors[0][vertexIndex]);
				if(mesh->mNormals != nullptr)

					if(mesh->HasTextureCoords(0))		//HasTextureCoords(texture_coordinates_set)
					{
						glTexCoord2f(mesh->mTextureCoords[0][vertexIndex].x, 1 - mesh->mTextureCoords[0][vertexIndex].y); //mTextureCoords[channel][vertex]
					}

					glNormal3fv(&mesh->mNormals[vertexIndex].x);
					glVertex3fv(&mesh->mVertices[vertexIndex].x);
			}
			glEnd();
		}
	}

	// draw all children
	for (n = 0; n < nd->mNumChildren; ++n)
	{
		recursive_render(sc, nd->mChildren[n], scale);
	}

	glPopMatrix();
}


void drawAiScene(const aiScene* scene)
{
	logInfo("drawing objects");

	recursive_render(scene, scene->mRootNode, 0.5);

}

int DrawGLScene()				//Here's where we do all the drawing
{
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);	// Clear The Screen And The Depth Buffer
	glLoadIdentity();				// Reset MV Matrix


	glTranslatef(0.0f, -10.0f, -40.0f);	// Move 40 Units And Into The Screen


	glRotatef(xrot, 1.0f, 0.0f, 0.0f);
	glRotatef(yrot, 0.0f, 1.0f, 0.0f);
	glRotatef(zrot, 0.0f, 0.0f, 1.0f);

	drawAiScene(g_scene);

	//xrot+=0.3f;
	yrot+=0.2f;
	//zrot+=0.4f;

	return TRUE;					// okay
}


void KillGLWindow()			// Properly Kill The Window
{
	if (fullscreen)					// Are We In Fullscreen Mode?
	{
		ChangeDisplaySettings(nullptr, 0);	// If So Switch Back To The Desktop
		ShowCursor(TRUE);					// Show Mouse Pointer
	}

	if (hRC)					// Do We Have A Rendering Context?
	{
		if (!wglMakeCurrent(nullptr, nullptr))	// Are We Able To Release The DC And RC Contexts?
		{
			MessageBox(nullptr, TEXT("Release Of DC And RC Failed."), TEXT("SHUTDOWN ERROR"), MB_OK | MB_ICONINFORMATION);
		}

		if (!wglDeleteContext(hRC))			// Are We Able To Delete The RC?
		{
			MessageBox(nullptr, TEXT("Release Rendering Context Failed."), TEXT("SHUTDOWN ERROR"), MB_OK | MB_ICONINFORMATION);
		}
		hRC = nullptr;
	}

	if (hDC)
	{
		if (!ReleaseDC(g_hWnd, hDC)) // Are We able to Release The DC?
			MessageBox(nullptr, TEXT("Release Device Context Failed."), TEXT("SHUTDOWN ERROR"), MB_OK | MB_ICONINFORMATION);
		hDC = nullptr;
	}

	if (g_hWnd)
	{
		if (!DestroyWindow(g_hWnd)) // Are We Able To Destroy The Window
			MessageBox(nullptr, TEXT("Could Not Release hWnd."), TEXT("SHUTDOWN ERROR"), MB_OK | MB_ICONINFORMATION);
		g_hWnd = nullptr;
	} 

	if (g_hInstance)
	{
		if (!UnregisterClass(TEXT("OpenGL"), g_hInstance)) // Are We Able To Unregister Class
			MessageBox(nullptr, TEXT("Could Not Unregister Class."), TEXT("SHUTDOWN ERROR"), MB_OK | MB_ICONINFORMATION);
		g_hInstance = nullptr;
	}
}

GLboolean abortGLInit(const char* abortMessage)
{
	KillGLWindow();									// Reset Display
	MessageBox(nullptr, UTFConverter(abortMessage).c_wstr(), TEXT("ERROR"), MB_OK|MB_ICONEXCLAMATION);
	return FALSE;									// quit and return False
}

BOOL CreateGLWindow(const char* title, int width, int height, int bits, bool fullscreenflag)
{
	GLuint		PixelFormat;		// Hold the result after searching for a match
	WNDCLASS	wc;					// Window Class Structure
	DWORD		dwExStyle;			// Window Extended Style
	DWORD		dwStyle;			// Window Style
	RECT		WindowRect;			// Grabs Rectangle Upper Left / Lower Right Values
	WindowRect.left		= (long)0;
	WindowRect.right	= (long)width;
	WindowRect.top		= (long)0;
	WindowRect.bottom	= (long)height;

	fullscreen = fullscreenflag;

	g_hInstance = GetModuleHandle(nullptr);	// Grab An Instance For Our Window
	wc.style		= CS_HREDRAW | CS_VREDRAW | CS_OWNDC; // Redraw On Move, And Own DC For Window
	wc.lpfnWndProc	= (WNDPROC) WndProc;		// WndProc handles Messages
	wc.cbClsExtra	= 0;	// No Extra Window Data
	wc.cbWndExtra	= 0;	// No Extra Window Data
	wc.hInstance	= g_hInstance;
	wc.hIcon		= LoadIcon(nullptr, IDI_WINLOGO);	// Load The Default Icon
	wc.hCursor		= LoadCursor(nullptr, IDC_ARROW);	// Load the default arrow
	wc.hbrBackground= nullptr;						// No Background required for OpenGL
	wc.lpszMenuName	= nullptr;						// No Menu
	wc.lpszClassName= TEXT("OpenGL");		        // Class Name

	if (!RegisterClass(&wc))
	{
		MessageBox(nullptr, TEXT("Failed to register the window class"), TEXT("ERROR"), MB_OK | MB_ICONEXCLAMATION);
		return FALSE;		//exit and return false
	}

	if (fullscreen)		// attempt fullscreen mode
	{
		DEVMODE dmScreenSettings;								// Device Mode
		memset(&dmScreenSettings, 0, sizeof(dmScreenSettings));	// Make Sure Memory's Cleared
		dmScreenSettings.dmSize = sizeof(dmScreenSettings);		// Size Of the devmode structure
		dmScreenSettings.dmPelsWidth	= width;				// Selected Screen Width
		dmScreenSettings.dmPelsHeight	= height;				// Selected Screen Height
		dmScreenSettings.dmBitsPerPel	= bits;					// bits per pixel
		dmScreenSettings.dmFields		= DM_BITSPERPEL|DM_PELSWIDTH|DM_PELSHEIGHT;

		// Try To Set Selected Mode and Get Results. NOTE: CDS_FULLSCREEN Gets Rid Of Start Bar.
		if (ChangeDisplaySettings(&dmScreenSettings, CDS_FULLSCREEN)!=DISP_CHANGE_SUCCESSFUL)
		{
			// If The Mode Fails, Offer Two Options.  Quit Or Run In A Window.
			if (MessageBox(nullptr,TEXT("The Requested Fullscreen Mode Is Not Supported By\nYour Video Card. Use Windowed Mode Instead?"),TEXT("NeHe GL"),MB_YESNO|MB_ICONEXCLAMATION)==IDYES)
			{
				fullscreen = FALSE;		// Select Windowed Mode (Fullscreen = FALSE)
			}
			else
			{
				//Popup Messagebox: Closing
				MessageBox(nullptr, TEXT("Program will close now."), TEXT("ERROR"), MB_OK|MB_ICONSTOP);
				return FALSE; //exit, return false
			}
		}
	}

	if (fullscreen)		// when mode really succeeded
	{
		dwExStyle=WS_EX_APPWINDOW;		// Window Extended Style
		dwStyle=WS_POPUP;
		ShowCursor(FALSE);
	}
	else
	{
		dwExStyle=WS_EX_APPWINDOW | WS_EX_WINDOWEDGE;	// Window extended style
		dwStyle=WS_OVERLAPPEDWINDOW;					// Windows style
	}

	AdjustWindowRectEx(&WindowRect, dwStyle, FALSE, dwExStyle);		// Adjust Window To True Requestes Size

	if (nullptr == (g_hWnd=CreateWindowEx(dwExStyle,			// Extended Style For The Window
								TEXT("OpenGL"),						// Class Name
								UTFConverter(title).c_wstr(),							// Window Title
								WS_CLIPSIBLINGS |				// Required Window Style
								WS_CLIPCHILDREN |				// Required Window Style
								dwStyle,						// Selected WIndow Style
								0, 0,							// Window Position
								WindowRect.right-WindowRect.left, // Calc adjusted Window Width
								WindowRect.bottom-WindowRect.top, // Calc adjustes Window Height
								nullptr,						// No Parent Window
								nullptr,						// No Menu
								g_hInstance,					// Instance
								nullptr )))						// Don't pass anything To WM_CREATE
	{
		abortGLInit("Window Creation Error.");
		return FALSE;
	}

	static	PIXELFORMATDESCRIPTOR pfd=					// pfd Tells Windows How We Want Things To Be
	{
		sizeof(PIXELFORMATDESCRIPTOR),					// Size Of This Pixel Format Descriptor
		1,												// Version Number
		PFD_DRAW_TO_WINDOW |							// Format Must Support Window
		PFD_SUPPORT_OPENGL |							// Format Must Support OpenGL
		PFD_DOUBLEBUFFER,								// Must Support Double Buffering
		PFD_TYPE_RGBA,									// Request An RGBA Format
		BYTE(bits),											// Select Our Color Depth
		0, 0, 0, 0, 0, 0,								// Color Bits Ignored
		0,												// No Alpha Buffer
		0,												// Shift Bit Ignored
		0,												// No Accumulation Buffer
		0, 0, 0, 0,										// Accumulation Bits Ignored
		16,												// 16Bit Z-Buffer (Depth Buffer)
		0,												// No Stencil Buffer
		0,												// No Auxiliary Buffer
		PFD_MAIN_PLANE,									// Main Drawing Layer
		0,												// Reserved
		0, 0, 0											// Layer Masks Ignored
	};

	if (nullptr == (hDC=GetDC(g_hWnd)))					// Did we get the Device Context?
	{
		abortGLInit("Can't Create A GL Device Context.");
		return FALSE;
	}

	if (0 == (PixelFormat=ChoosePixelFormat(hDC, &pfd))) // Did We Find a matching pixel Format?
	{
		abortGLInit("Can't Find Suitable PixelFormat");
		return FALSE;
	}

	if (!SetPixelFormat(hDC, PixelFormat, &pfd))
	{
		abortGLInit("Can't Set The PixelFormat");
		return FALSE;
	}

	if (nullptr == (hRC=wglCreateContext(hDC))) 
	{
		abortGLInit("Can't Create A GL Rendering Context.");
		return FALSE;
	}

	if (!(wglMakeCurrent(hDC,hRC)))						// Try to activate the rendering context
	{
		abortGLInit("Can't Activate The Rendering Context");
		return FALSE;
	}

	//// *** everything okay ***

	ShowWindow(g_hWnd, SW_SHOW);	// Show The Window
	SetForegroundWindow(g_hWnd);	// Slightly Higher Prio
	SetFocus(g_hWnd);				// Sets Keyboard Focus To The Window
	ReSizeGLScene(width, height);	// Set Up Our Perspective GL Screen

	if (!InitGL())
	{
		abortGLInit("Initialization failed");
		return FALSE;
	}

	return TRUE;
}

void cleanup()
{
	freeTextureIds();

	destroyAILogger();

	if (g_hWnd)
		KillGLWindow();
};

LRESULT CALLBACK WndProc(HWND hWnd,				// Handles for this Window
						 UINT uMsg,				// Message for this Window
						 WPARAM wParam,			// additional message Info
						 LPARAM lParam)			// additional message Info
{
	switch (uMsg)				// check for Window Messages
	{
		case WM_ACTIVATE:				// Watch For Window Activate Message
			{
				if (!HIWORD(wParam))	// Check Minimization State
				{
					active=TRUE;
				}
				else
				{
					active=FALSE;
				}

				return 0;				// return To The Message Loop
			}

		case WM_SYSCOMMAND:			// Interrupt System Commands
			{
				switch (wParam)
				{
					case SC_SCREENSAVE:		// Screen-saver trying to start
					case SC_MONITORPOWER:	// Monitor trying to enter power-safe
					return 0;
				}
				break;
			}

		case WM_CLOSE:			// close message received?
			{
				PostQuitMessage(0);	// Send WM_QUIT quit message
				return 0;			// Jump Back
			}

		case WM_KEYDOWN:		// Is a key pressed?
			{
				keys[wParam] = TRUE;	// If so, Mark it as true
				return 0;
			}

		case WM_KEYUP:			// Has Key Been released?
			{
				keys[wParam] = FALSE;	// If so, Mark It As FALSE
				return 0;
			}

		case WM_SIZE:			// Resize The OpenGL Window
			{
				ReSizeGLScene(LOWORD(lParam), HIWORD(lParam));	// LoWord-Width, HiWord-Height
				return 0;
			}
	}

	// Pass All unhandled Messaged To DefWindowProc
	return DefWindowProc(hWnd, uMsg, wParam, lParam);
}

int WINAPI WinMain( HINSTANCE /*hInstance*/,     // The instance
				   HINSTANCE /*hPrevInstance*/,  // Previous instance
				   LPSTR /*lpCmdLine*/,          // Command Line Parameters
				   int /*nShowCmd*/ )            // Window Show State
{
	MSG msg = {};
	BOOL done=FALSE;

	createAILogger();
	logInfo("App fired!");

	// Check the command line for an override file path.
	int argc;
	LPWSTR* argv = CommandLineToArgvW(GetCommandLineW(), &argc);
	if (argv != nullptr && argc > 1)
	{
		std::wstring modelpathW(argv[1]);
		modelpath = UTFConverter(modelpathW).str();
	}

	if (!Import3DFromFile(modelpath))
	{
		cleanup();
		return 0;
	}

	logInfo("=============== Post Import ====================");

	if (MessageBox(nullptr, TEXT("Would You Like To Run In Fullscreen Mode?"), TEXT("Start Fullscreen?"), MB_YESNO|MB_ICONEXCLAMATION)==IDNO)
	{
		fullscreen=FALSE;
	}

	if (!CreateGLWindow(windowTitle, 640, 480, 16, fullscreen))
	{
		cleanup();
		return 0;
	}

	while(!done)	// Game Loop
	{
		if (PeekMessage(&msg, nullptr, 0,0, PM_REMOVE))
		{
			if (msg.message==WM_QUIT)
			{
				done=TRUE;
			}
			else
			{
				TranslateMessage(&msg);
				DispatchMessage(&msg);
			}
		}
		else
		{
			// Draw The Scene. Watch For ESC Key And Quit Messaged From DrawGLScene()
			if (active)
			{
				if (keys[VK_ESCAPE])
				{
					done=TRUE;
				}
				else
				{
					DrawGLScene();
					SwapBuffers(hDC);
				}
			}

			if (keys[VK_F1])
			{
				keys[VK_F1]=FALSE;
				KillGLWindow();
				fullscreen=!fullscreen;
				if (!CreateGLWindow(windowTitle, 640, 480, 16, fullscreen))
				{
					cleanup();
					return 0;
				}
			}
		}
	}

	// *** cleanup ***
	cleanup();
	return static_cast<int>(msg.wParam);
}
