//-----------------------------------------------------------------------------
// Copyright (c) 2009-2010 dhpoware. All Rights Reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//-----------------------------------------------------------------------------

#pragma comment (lib, "gdiplus.lib")

#include <windows.h>
#include <gdiplus.h>
#include <algorithm>
#include <cctype>
#include <cmath>
#include <cstdlib>
#include <cstring>
#include <exception>
#include <fstream>
#include <functional>
#include <string>
#include <sstream>

#if defined(DEBUG) || defined(_DEBUG)
#include <crtdbg.h>
#endif

#include "application.h"

// GL_EXT_texture_filter_anisotropic

#if !defined(GL_TEXTURE_MAX_ANISOTROPY_EXT)
#define GL_TEXTURE_MAX_ANISOTROPY_EXT 0x84FE
#endif

#if !defined(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT)
#define GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT 0x84FF
#endif

Mouse &GL3Application::m_mouse = Mouse::instance();
Keyboard &GL3Application::m_keyboard = Keyboard::instance();
INT64 GL3Application::m_freq;
INT64 GL3Application::m_lastTime;
int   GL3Application::m_sampleCount;
float GL3Application::m_timeScale;
float GL3Application::m_actualElapsedTimeSec;
float GL3Application::m_frameTimes[MAX_SAMPLE_COUNT];
bool  GL3Application::m_initialized;

//-----------------------------------------------------------------------------
// Public methods.
//-----------------------------------------------------------------------------

GL3Application::GL3Application()
{
    initApplication("");
}

GL3Application::GL3Application(const char *pszWindowName)
{
    initApplication(pszWindowName);
}

GL3Application::~GL3Application()
{
}

void GL3Application::preferAnisotropicTextureFiltering()
{
    m_degreeOfAnisotropy = BEST_DEGREE_OF_ANISOTROPY;
}

void GL3Application::preferAnisotropicTextureFiltering(int degreeOfAnisotrophy)
{
    m_degreeOfAnisotropy = degreeOfAnisotrophy;
}

void GL3Application::preferAntiAliasing()
{
    m_antiAliasingSamples = BEST_ANTI_ALIASING_SAMPLES;
}

void GL3Application::preferAntiAliasing(int samples)
{
    m_antiAliasingSamples = samples;
}

void GL3Application::preferFullScreen()
{
    m_startFullScreen = true;
}

void GL3Application::preferWindowed()
{
    m_startFullScreen = false;
}

int GL3Application::run()
{
#if defined(DEBUG) | defined(_DEBUG)
    _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);
#endif

    int status = 0;
    ULONG_PTR gdiplusToken = 0;
	Gdiplus::GdiplusStartupInput gdiplusStartupInput;
    
    Gdiplus::GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, 0);
    getEncoderClsid(L"image/png", &m_clsidEncoderPng);

    try
    {
        if (create())
        {
            try
            {
                initOpenGL();
                init(__argc, __argv);                
                status = mainLoop();
                shutdown();
            }
            catch (...)
            {
                shutdown();
                throw;
            }
            
            destroy();
        }
    }
    catch (const std::exception &e)
    {
        destroy();
        status = EXIT_FAILURE;

        MessageBox(0, e.what(), "GL3Application Unhandled Exception", MB_ICONERROR);
    }

    Gdiplus::GdiplusShutdown(gdiplusToken);

    return status;
}

//-----------------------------------------------------------------------------
// Protected methods.
//-----------------------------------------------------------------------------

void GL3Application::displayFullScreen()
{
    if (!m_isFullScreen)
        toggleFullScreen();
}

void GL3Application::displayWindowed()
{
    if (m_isFullScreen)
        toggleFullScreen();
}

void GL3Application::enableVerticalSync(bool enableVerticalSync)
{
    wglSwapIntervalEXT(enableVerticalSync ? 1 : 0);
}

GLuint GL3Application::loadFragmentShader(const char *pszFilename)
{
    return loadShader(pszFilename, GL_FRAGMENT_SHADER);
}

GLuint GL3Application::loadFragmentShaderFromResource(int resourceId)
{
	return loadShaderFromResource(resourceId, GL_FRAGMENT_SHADER);
}

GLuint GL3Application::loadVertexShader(const char *pszFilename)
{
    return loadShader(pszFilename, GL_VERTEX_SHADER);
}

GLuint GL3Application::loadVertexShaderFromResource(int resourceId)
{
	return loadShaderFromResource(resourceId, GL_VERTEX_SHADER);
}

GLuint GL3Application::loadShaderProgram(const char *pszVertexShaderFilename, const char *pszFragmentShaderFilename)
{
	GLuint vertexShader = loadVertexShader(pszVertexShaderFilename);
	GLuint fragmentShader = loadFragmentShader(pszFragmentShaderFilename);

	return loadShaderProgram(vertexShader, fragmentShader);
}

GLuint GL3Application::loadShaderProgramFromResource(int vertexShaderResourceId, int fragmentShaderResourceId)
{
	GLuint vertexShader = loadVertexShaderFromResource(vertexShaderResourceId);
	GLuint fragmentShader = loadFragmentShaderFromResource(fragmentShaderResourceId);

	return loadShaderProgram(vertexShader, fragmentShader);
}

const Texture2D *GL3Application::loadTexture2D(const char *pszFilename)
{
    return loadTexture2D(pszFilename, GL_LINEAR, GL_LINEAR_MIPMAP_LINEAR, GL_REPEAT, GL_REPEAT);
}

const Texture2D *GL3Application::loadTexture2D(const char *pszFilename, GLint magFilter, GLint minFilter, GLint wrapS, GLint wrapT)
{
    // GDI+ requires unicode strings.
    // This trick only works for strings containing English ASCII text.
    std::string asciiFilename(pszFilename);
    std::wstring unicodeFilename(asciiFilename.length(), L' ');
    std::copy(asciiFilename.begin(), asciiFilename.end(), unicodeFilename.begin());

    std::tr1::shared_ptr<Gdiplus::Bitmap> pBitmap(Gdiplus::Bitmap::FromFile(unicodeFilename.c_str(), FALSE));

    return loadTexture2D(pBitmap, magFilter, minFilter, wrapS, wrapT);
}

const Texture2D *GL3Application::loadTexture2DFromResource(int resourceId)
{
	return loadTexture2DFromResource(resourceId, GL_LINEAR, GL_LINEAR_MIPMAP_LINEAR, GL_REPEAT, GL_REPEAT);
}

const Texture2D *GL3Application::loadTexture2DFromResource(int resourceId, GLint magFilter, GLint minFilter, GLint wrapS, GLint wrapT)
{
	const Texture2D *pTexture = 0;
	HMODULE hModule = GetModuleHandle(0);
	HRSRC hResource = FindResource(hModule, MAKEINTRESOURCE(resourceId), RT_RCDATA);

	if (hResource)
	{
		if (DWORD dwSize = SizeofResource(hModule, hResource))
		{
			if (const void *pResourceData = LockResource(LoadResource(hModule, hResource)))
			{
				if (HGLOBAL hBuffer = GlobalAlloc(GMEM_MOVEABLE, dwSize))
				{
					if (void *pBuffer = GlobalLock(hBuffer))
					{
						CopyMemory(pBuffer, pResourceData, dwSize);

						IStream *pStream = 0;

						if (CreateStreamOnHGlobal(hBuffer, TRUE, &pStream) == S_OK)
						{
							std::tr1::shared_ptr<Gdiplus::Bitmap> pBitmap(Gdiplus::Bitmap::FromStream(pStream));
							
							pStream->Release();
							pTexture = loadTexture2D(pBitmap, magFilter, minFilter, wrapS, wrapT);
						}

						GlobalUnlock(hBuffer);
					}

					GlobalFree(hBuffer);
				}
			}
		}
	}

	return pTexture;	
}

std::string GL3Application::programInfoLog(GLuint program) const
{
    GLsizei infoLogSize = 0;
    std::string infoLog;

    glGetProgramiv(program, GL_INFO_LOG_LENGTH, &infoLogSize);
    infoLog.resize(infoLogSize);
    glGetProgramInfoLog(program, infoLogSize, &infoLogSize, &infoLog[0]);

    return infoLog;
}

std::string GL3Application::shaderInfoLog(GLuint shader) const
{
    GLsizei infoLogSize = 0;
    std::string infoLog;

    glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &infoLogSize);
    infoLog.resize(infoLogSize);
    glGetShaderInfoLog(shader, infoLogSize, &infoLogSize, &infoLog[0]);

    return infoLog;
}

void GL3Application::quit(int status)
{
    m_status = status;
    PostMessage(m_hWnd, WM_CLOSE, 0, 0);
}

bool GL3Application::saveTexture2D(const Texture2D *pTexture, const char *pszFileName)
{
	if (!pTexture)
		return false;

	if (pTexture->type != GL_UNSIGNED_BYTE)
		return false;

	if (!pszFileName)
		return false;

	Gdiplus::PixelFormat pixelformat = 0;

	switch (pTexture->format)
	{
	case GL_BGRA:
		pixelformat = PixelFormat32bppARGB;
		break;

	case GL_BGR:
		pixelformat = PixelFormat24bppRGB;
		break;

	default:
		return false;
	}

	BYTE *pPixels = const_cast<BYTE *>(&pTexture->pixels[0]);
	std::tr1::shared_ptr<Gdiplus::Bitmap> pBitmap(new Gdiplus::Bitmap(pTexture->width, pTexture->height, pTexture->pitch, pixelformat, pPixels));

	if (pBitmap->GetLastStatus() != Gdiplus::Ok)
		return false;

	// GDI+ requires UNICODE strings. Convert ASCII path to UNICODE path.
	// HACK! Perform the conversion to UNICODE.
	// This will only work for strings containing English ASCII text.
	
	std::string asciiPath = pszFileName;
	std::wstring unicodePath(asciiPath.length(), L' ');
	std::copy(asciiPath.begin(), asciiPath.end(), unicodePath.begin());

	return pBitmap->Save(unicodePath.c_str(), &m_clsidEncoderPng, 0) == Gdiplus::Ok;
}

bool GL3Application::saveTexture2D(const Texture2D *pTexture, const char *pszSaveDirectory, const char *pszSaveFileName)
{
	// When no save directory is provided use the current directory.

	std::string asciiSaveDirectory;

	if (!pszSaveDirectory)
	{
		char szCurrentDirectory[MAX_PATH] = {'\0'};

		GetCurrentDirectory(MAX_PATH, szCurrentDirectory);
		asciiSaveDirectory = szCurrentDirectory;
	}
	else
	{
		asciiSaveDirectory = pszSaveDirectory;
	}

	// Make sure there's a directory path separator at the end.

	if (asciiSaveDirectory[asciiSaveDirectory.size() - 1] != '\\')
		asciiSaveDirectory += '\\';

	std::string asciiSaveFileName(pszSaveFileName);
	
	// Verify that the save file name contains a valid file extension.

	std::transform(asciiSaveFileName.begin(), asciiSaveFileName.end(), asciiSaveFileName.begin(), std::ptr_fun(tolower));

	if (asciiSaveFileName.rfind(".png") == std::string::npos)
	{
		asciiSaveFileName = pszSaveFileName;
		asciiSaveFileName += ".png";
	}
	else
	{
		asciiSaveFileName = pszSaveFileName;
	}

	std::string asciiPath = asciiSaveDirectory + asciiSaveFileName;

	return saveTexture2D(pTexture, asciiPath.c_str());
}

void GL3Application::toggleFullScreen()
{
    m_isFullScreen = !m_isFullScreen;

    if (m_isFullScreen)
    {
        // Moving to full screen mode.

        m_savedExStyle = GetWindowLong(m_hWnd, GWL_EXSTYLE);
        m_savedStyle = GetWindowLong(m_hWnd, GWL_STYLE);
        GetWindowRect(m_hWnd, &m_rcSaved);

        SetWindowLong(m_hWnd, GWL_EXSTYLE, 0);
        SetWindowLong(m_hWnd, GWL_STYLE, WS_POPUP | WS_CLIPCHILDREN | WS_CLIPSIBLINGS);
        SetWindowPos(m_hWnd, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_FRAMECHANGED | SWP_SHOWWINDOW);

        m_windowWidth = GetSystemMetrics(SM_CXSCREEN);
        m_windowHeight = GetSystemMetrics(SM_CYSCREEN);

        SetWindowPos(m_hWnd, HWND_TOPMOST, 0, 0, m_windowWidth, m_windowHeight, SWP_SHOWWINDOW);
    }
    else
    {
        // Moving back to windowed mode.

        SetWindowLong(m_hWnd, GWL_EXSTYLE, m_savedExStyle);
        SetWindowLong(m_hWnd, GWL_STYLE, m_savedStyle);
        SetWindowPos(m_hWnd, HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_FRAMECHANGED | SWP_SHOWWINDOW);

        m_windowWidth = m_rcSaved.right - m_rcSaved.left;
        m_windowHeight = m_rcSaved.bottom - m_rcSaved.top;

        SetWindowPos(m_hWnd, HWND_NOTOPMOST, m_rcSaved.left, m_rcSaved.top, m_windowWidth, m_windowHeight, SWP_SHOWWINDOW);
    }
}

bool GL3Application::verticalSyncEnabled() const
{
    return (wglGetSwapIntervalEXT() == 0) ? false : true;
}

//-----------------------------------------------------------------------------
// Private methods.
//-----------------------------------------------------------------------------

bool GL3Application::create()
{      
    if (!RegisterClassEx(&m_wcl))
        return false;

    // Create a window that is centered on the desktop. It's exactly 1/4 the
    // size of the desktop. Don't allow it to be resized.

    DWORD wndExStyle = WS_EX_OVERLAPPEDWINDOW;
    DWORD wndStyle = WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX | WS_CLIPCHILDREN | WS_CLIPSIBLINGS;

    m_hWnd = CreateWindowEx(wndExStyle, m_wcl.lpszClassName, m_pszWindowName, wndStyle, 0, 0, 0, 0, 0, 0, m_wcl.hInstance, this);

    if (!m_hWnd)
    {
        UnregisterClass(m_wcl.lpszClassName, m_hInstance);
        return false;
    }
    else
    {
        int screenWidth = GetSystemMetrics(SM_CXSCREEN);
        int screenHeight = GetSystemMetrics(SM_CYSCREEN);
        int halfScreenWidth = screenWidth / 2;
        int halfScreenHeight = screenHeight / 2;
        int left = (screenWidth - halfScreenWidth) / 2;
        int top = (screenHeight - halfScreenHeight) / 2;
        RECT rc = {0};

        SetRect(&rc, left, top, left + halfScreenWidth, top + halfScreenHeight);
        AdjustWindowRectEx(&rc, wndStyle, FALSE, wndExStyle);
        MoveWindow(m_hWnd, rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top, TRUE);

        GetClientRect(m_hWnd, &rc);
        m_windowWidth = rc.right - rc.left;
        m_windowHeight = rc.bottom - rc.top;   

        return true;
    }
}

void GL3Application::destroy()
{
    for (std::vector<std::tr1::shared_ptr<Texture2D> >::const_iterator i = m_loaded2DTextures.begin(); i != m_loaded2DTextures.end(); ++i)
        glDeleteTextures(1, &(*i)->texture);

    if (m_hDC)
    {
        if (m_hRC)
        {
            wglMakeCurrent(m_hDC, 0);
            wglDeleteContext(m_hRC);
            m_hRC = 0;
        }

        ReleaseDC(m_hWnd, m_hDC);
        m_hDC = 0;
    }
    
    UnregisterClass(m_wcl.lpszClassName, m_hInstance);
}

void GL3Application::initOpenGL()
{
    if (!(m_hDC = GetDC(m_hWnd)))
        throw std::runtime_error("GetDC() failed.");

    int pf = 0;
    PIXELFORMATDESCRIPTOR pfd = {0};
    OSVERSIONINFO osvi = {0};

    // Create and set a pixel format for the window.

    pfd.nSize = sizeof(pfd);
    pfd.nVersion = 1;
    pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER;
    pfd.iPixelType = PFD_TYPE_RGBA;
    pfd.cColorBits = 24;
    pfd.cDepthBits = 16;
    pfd.iLayerType = PFD_MAIN_PLANE;

    osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);

    if (!GetVersionEx(&osvi))
        throw std::runtime_error("GetVersionEx() failed.");

    if (m_antiAliasingSamples == 0)
        pf = ChoosePixelFormat(m_hDC, &pfd);
    else if (m_antiAliasingSamples == BEST_ANTI_ALIASING_SAMPLES)
        gl::ChooseBestAntiAliasingPixelFormat(pf);
    else
        gl::ChooseAntiAliasingPixelFormat(pf, m_antiAliasingSamples);

    if (!pf)
        pf = ChoosePixelFormat(m_hDC, &pfd);

    if (!SetPixelFormat(m_hDC, pf, &pfd))
        throw std::runtime_error("SetPixelFormat() failed.");

    // When running under Windows Vista or later support desktop composition.
    // This doesn't really apply when running in full screen mode.

    if (osvi.dwMajorVersion > 6 || (osvi.dwMajorVersion == 6 && osvi.dwMinorVersion >= 0))
        pfd.dwFlags |= PFD_SUPPORT_COMPOSITION;

    // Verify that this OpenGL implementation supports the required extensions.

	if (!gl::ExtensionSupported("WGL_ARB_create_context"))
        throw std::runtime_error("Required extension WGL_ARB_create_context is not supported.");
    
	// Create an OpenGL 3.x rendering context.
	// Order of preference is: OpenGL 3.2, OpenGL 3.1, and OpenGL 3.0 forward compatible.

	int attribListGL32[] =
	{
		WGL_CONTEXT_MAJOR_VERSION_ARB, 3,
    	WGL_CONTEXT_MINOR_VERSION_ARB, 2,
        WGL_CONTEXT_FLAGS_ARB, WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB,
		WGL_CONTEXT_PROFILE_MASK_ARB, WGL_CONTEXT_CORE_PROFILE_BIT_ARB,
		0
	};

	int attribListGL31[] =
	{
		WGL_CONTEXT_MAJOR_VERSION_ARB, 3,
        WGL_CONTEXT_MINOR_VERSION_ARB, 1,
        WGL_CONTEXT_FLAGS_ARB, WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB,
        0
	};

	int attribListGL30[] =
	{
		WGL_CONTEXT_MAJOR_VERSION_ARB, 3,
        WGL_CONTEXT_MINOR_VERSION_ARB, 0,
        WGL_CONTEXT_FLAGS_ARB, WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB,
        0
	};

	if (!(m_hRC = wglCreateContextAttribsARB(m_hDC, 0, attribListGL32)))
	{
		if (!(m_hRC = wglCreateContextAttribsARB(m_hDC, 0, attribListGL31)))
		{
			if (!(m_hRC = wglCreateContextAttribsARB(m_hDC, 0, attribListGL30)))
				throw std::runtime_error("wglCreateContextAttribsARB() failed for OpenGL 3 context.");
		}
	}

    if (!wglMakeCurrent(m_hDC, m_hRC))
        throw std::runtime_error("wglMakeCurrent() failed for OpenGL 3 context.");

	// Lookup the maximum supported level of anisotropic texture filtering.
	// This must be performed after a valid rendering context is made active.
	// Otherwise the glGetIntegerv() call will report that the maximum degree
	// of anisotropic texture filtering is 1.

	if (gl::ExtensionSupported("GL_EXT_texture_filter_anisotropic"))
		glGetIntegerv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &m_maxDegreeOfAnisotropy);
	else
		m_maxDegreeOfAnisotropy = 1;
}

void GL3Application::initApplication(const char *pszWindowName)
{
    m_pszWindowName = pszWindowName;
    m_hWnd = 0;
    m_hInstance = GetModuleHandle(0);
    m_hDC = 0;
    m_hRC = 0;
    m_windowWidth = 0;
    m_windowHeight = 0;
    m_framesPerSecond = 0;
    m_status = 0;
    m_antiAliasingSamples = 0;
    m_maxDegreeOfAnisotropy = 1;
    m_degreeOfAnisotropy = 1;
    m_savedExStyle = 0;
    m_savedStyle = 0;
    m_isFullScreen = false;
    m_hasFocus = false;
    m_startFullScreen = false;

    memset(&m_rcSaved, 0, sizeof(m_rcSaved));
    memset(&m_wcl, 0, sizeof(m_wcl));
    
    m_wcl.cbSize = sizeof(m_wcl);
    m_wcl.style = CS_OWNDC | CS_HREDRAW | CS_VREDRAW;
    m_wcl.lpfnWndProc = windowProc;
    m_wcl.cbClsExtra = 0;
    m_wcl.cbWndExtra = 0;
    m_wcl.hInstance = m_hInstance;
    m_wcl.hIcon = LoadIcon(0, IDI_APPLICATION);
    m_wcl.hCursor = LoadCursor(0, IDC_ARROW);
    m_wcl.hbrBackground = 0;
    m_wcl.lpszMenuName = 0;
    m_wcl.lpszClassName = "GL3ApplicationWindowClass";
    m_wcl.hIconSm = 0;

    memset(&m_clsidEncoderPng, 0, sizeof(m_clsidEncoderPng));
}

GLuint GL3Application::loadShader(const char *pszFilename, GLenum shaderType)
{
    std::string source;
    std::ifstream file(pszFilename, std::ios::binary);

    if (file.is_open())
    {
        file.seekg(0, std::ios::end);

        unsigned int fileSize = static_cast<unsigned int>(file.tellg());

        source.resize(fileSize);
        file.seekg(0, std::ios::beg);
        file.read(&source[0], fileSize);
    }

    GLuint shader = glCreateShader(shaderType);
    const GLchar *pszSource = reinterpret_cast<const GLchar *>(source.c_str());

    glShaderSource(shader, 1, &pszSource, 0);
	m_shaderFilenames[shader] = std::string(pszFilename);

    return shader;
}

GLuint GL3Application::loadShaderFromResource(int resourceId, GLenum shaderType)
{
	// Embedding Your Shader Source Code In Your C/C++ Apps
	// ----------------------------------------------------
	//	1. Add a define to your resource.h file.
	//		#define MY_VERTEX_SHADER_FILE 1000
	//		#define MY_FRAGMENT_SHADER_FILE 1001
	//
	//	2. Add an entry in your resource.rc file.
	//		MY_VERTEX_SHADER_FILE RCDATA DISCARDABLE "myShader.vert"
	//		MY_FRAGMENT_SHADER_FILE RCDATA DISCARDABLE "myShader.frag"
	//
	//	3. Call GL3Application::loadShaderFromResource().
	//		loadShaderFromResource(MAKEINTRESOURCE(MY_VERTEX_SHADER_FILE), GL_VERTEX_SHADER)
	//		loadShaderFromResource(MAKEINTRESOURCE(MY_FRAGMENT_SHADER_FILE), GL_FRAGMENT_SHADER)

	GLuint shader = 0;
	std::string buffer;
	HMODULE hModule = GetModuleHandle(0);
	HRSRC hResource = FindResource(hModule, MAKEINTRESOURCE(resourceId), RT_RCDATA);

	if (hResource)
	{
		DWORD dwSize = SizeofResource(hModule, hResource);
		HGLOBAL hGlobal = LoadResource(hModule, hResource);

		if (hGlobal)
		{
			if (LPVOID pData = LockResource(hGlobal))
			{
				buffer.assign(reinterpret_cast<const char *>(pData), dwSize);
				UnlockResource(hGlobal);

				const GLchar *pszSource = reinterpret_cast<const GLchar *>(buffer.c_str());

				shader = glCreateShader(shaderType);
				glShaderSource(shader, 1, &pszSource, 0);

				std::ostringstream os;
				os << "Resource ID " << resourceId;
				m_shaderFilenames[shader] = os.str();
			}
		}
	}

	return shader;
}

GLuint GL3Application::loadShaderProgram(GLuint vertexShader, GLuint fragmentShader)
{
	GLint compiled = 0;
	GLint linked = 0;
	GLuint shaderProgram = 0;

	glCompileShader(vertexShader);
	glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &compiled);

	if (!compiled)
	{
		m_shaderInfoLog = shaderInfoLog(vertexShader);
		glDeleteShader(vertexShader);
		glDeleteShader(fragmentShader);

		std::ostringstream output;

		output << "Error compiling GLSL vertex shader: '";
		output << m_shaderFilenames[vertexShader];
		output << "'" << std::endl << std::endl;
		output << "Shader info log:" << std::endl;
		output << m_shaderInfoLog;

		m_shaderInfoLog = output.str();        
	}
	else
	{
		glCompileShader(fragmentShader);
		glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &compiled);

		if (!compiled)
		{
			m_shaderInfoLog = shaderInfoLog(fragmentShader);
			glDeleteShader(vertexShader);
			glDeleteShader(fragmentShader);

			std::ostringstream output;

			output << "Error compiling GLSL fragment shader: '";
			output << m_shaderFilenames[fragmentShader];
			output << "'" << std::endl << std::endl;
			output << "Shader info log:" << std::endl;
			output << m_shaderInfoLog;

			m_shaderInfoLog = output.str();
		}
		else
		{
			shaderProgram = glCreateProgram();
			glAttachShader(shaderProgram, vertexShader);
			glAttachShader(shaderProgram, fragmentShader);
			glLinkProgram(shaderProgram);
			glGetProgramiv(shaderProgram, GL_LINK_STATUS, &linked);

			// Mark the two attached shaders for deletion. These two shaders
			// aren't deleted right now because both are already attached to a
			// shader program. When the shader program is deleted these two
			// shaders will be automatically detached and deleted.

			glDeleteShader(vertexShader);
			glDeleteShader(fragmentShader);

			if (!linked)
			{
				m_programInfoLog = programInfoLog(shaderProgram);
				glDeleteProgram(shaderProgram);

				std::ostringstream output;

				output << "Error linking GLSL shaders into a shader program." << std::endl;
				output << "GLSL vertex shader: '" << m_shaderFilenames[vertexShader];
				output << "'" << std::endl;
				output << "GLSL fragment shader: '" << m_shaderFilenames[fragmentShader];
				output << "'" << std::endl << std::endl;
				output << "Program info log:" << std::endl;
				output << m_programInfoLog;

				m_programInfoLog = output.str();
			}
		}
	}

	return shaderProgram;
}

const Texture2D *GL3Application::loadTexture2D(std::tr1::shared_ptr<Gdiplus::Bitmap> &pBitmap, GLint magFilter, GLint minFilter, GLint wrapS, GLint wrapT)
{
	if (pBitmap->GetLastStatus() != Gdiplus::Ok || pBitmap.get() == 0)
		return 0;

	// GDI+ orients bitmap images top-bottom.
	// OpenGL expects bitmap images to be oriented bottom-top by default.
	pBitmap->RotateFlip(Gdiplus::RotateNoneFlipY);

	std::tr1::shared_ptr<Texture2D> pTexture(new Texture2D);

	int width = pBitmap->GetWidth();
	int height = pBitmap->GetHeight();
	int pitch = ((width * 32 + 31) & ~31) >> 3;

	pTexture->width = width;
	pTexture->height = height;
	pTexture->pitch = pitch;
	pTexture->format = GL_BGRA;
	pTexture->type = GL_UNSIGNED_BYTE;
	pTexture->pixels.resize(pitch * height);

	Gdiplus::BitmapData data;
	Gdiplus::Rect rect(0, 0, width, height);

	// Convert to 32-bit BGRA pixel format and fetch the pixel data.

	if (pBitmap->LockBits(&rect, Gdiplus::ImageLockModeRead, PixelFormat32bppARGB, &data) != Gdiplus::Ok)
		return 0;

	if (data.Stride == pitch)
	{
		memcpy(&pTexture->pixels[0], data.Scan0, pitch * height);
	}
	else
	{
		GLubyte *pSrcPixels = static_cast<GLubyte *>(data.Scan0);

		for (int i = 0; i < height; ++i)
			memcpy(&pTexture->pixels[i * pitch], &pSrcPixels[i * data.Stride], pitch);
	}

	pBitmap->UnlockBits(&data);

	// Create an OpenGL texture object to store the loaded bitmap image.

	glGenTextures(1, &pTexture->texture);
	glBindTexture(GL_TEXTURE_2D, pTexture->texture);

	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, magFilter);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, minFilter);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, wrapS);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, wrapT);

	// Enable anisotropic texture filtering if supported.

	if ((m_degreeOfAnisotropy > 1 || m_degreeOfAnisotropy == BEST_DEGREE_OF_ANISOTROPY) && m_maxDegreeOfAnisotropy > 1)
	{
		int anisotropy;

		if (m_degreeOfAnisotropy == BEST_DEGREE_OF_ANISOTROPY)
			anisotropy = m_maxDegreeOfAnisotropy;
		else
			anisotropy = min(m_degreeOfAnisotropy, m_maxDegreeOfAnisotropy);

		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, anisotropy);
	}

	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, pTexture->width, pTexture->height, 0, pTexture->format, pTexture->type, &pTexture->pixels[0]);

	if (minFilter == GL_LINEAR_MIPMAP_LINEAR)
	{
		// Generate the mipmaps. Don't use the GL_GENERATE_MIPMAP flag to the
		// glTexParameteri() function because that's deprecated in OpenGL 3.0.
		// Instead use the glGenerateMipmap() function originally introduced
		// in the GL_EXT_framebuffer_object extension and now part of core
		// OpenGL 3.0.

		glGenerateMipmap(GL_TEXTURE_2D);
	}

	glBindTexture(GL_TEXTURE_2D, 0);

	m_loaded2DTextures.push_back(pTexture);
	return pTexture.get();
}

int GL3Application::mainLoop()
{
    if (m_startFullScreen)
        displayFullScreen();

    MSG msg;
    
    memset(&msg, 0, sizeof(msg));
    ShowWindow(m_hWnd, SW_SHOWDEFAULT);
    UpdateWindow(m_hWnd);

    while (true)
    {
        while (PeekMessage(&msg, 0, 0, 0, PM_REMOVE))
        {
            if (msg.message == WM_QUIT)
                break;

            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }

        if (msg.message == WM_QUIT)
            break;

        if (m_hasFocus)
        {
            Mouse::instance().update();
            Keyboard::instance().update();

            update(elapsedTimeInSeconds());
            render();
            SwapBuffers(m_hDC);
        }
        else
        {
            WaitMessage();
        }
    }

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

LRESULT GL3Application::windowProcImpl(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    Mouse::instance().handleMsg(hWnd, msg, wParam, lParam);
    Keyboard::instance().handleMsg(hWnd, msg, wParam, lParam);

    switch (msg)
    {
    case WM_ACTIVATE:
        switch (wParam)
        {
        default:
            break;

        case WA_ACTIVE:
        case WA_CLICKACTIVE:
            Mouse::instance().attach(hWnd);
            m_hasFocus = true;
            break;

        case WA_INACTIVE:
            if (m_isFullScreen)
                ShowWindow(hWnd, SW_MINIMIZE);
            Mouse::instance().detach();
            m_hasFocus = false;
            break;
        }
        break;

    case WM_DESTROY:
        PostQuitMessage(m_status);
        return 0;

    case WM_SIZE:
        m_windowWidth = static_cast<int>(LOWORD(lParam));
        m_windowHeight = static_cast<int>(HIWORD(lParam));
        break;

    default:
        break;
    }
    
    return wndProc(hWnd, msg, wParam, lParam);
}

//-----------------------------------------------------------------------------
// Static methods.
//-----------------------------------------------------------------------------

float GL3Application::elapsedTimeInSeconds()
{
    // Returns the elapsed time (in seconds) since the last time this method
    // was called. This elaborate setup is to guard against large spikes in
    // the time returned by QueryPerformanceCounter().
    
    INT64 time = 0;
    float elapsedTimeSec = 0.0f;

    if (!m_initialized)
    {
        m_initialized = true;
        QueryPerformanceFrequency(reinterpret_cast<LARGE_INTEGER*>(&m_freq));
        QueryPerformanceCounter(reinterpret_cast<LARGE_INTEGER*>(&m_lastTime));
        m_timeScale = 1.0f / m_freq;
    }

    QueryPerformanceCounter(reinterpret_cast<LARGE_INTEGER*>(&time));
    elapsedTimeSec = (time - m_lastTime) * m_timeScale;
    m_lastTime = time;

    if (fabsf(elapsedTimeSec - m_actualElapsedTimeSec) < 1.0f)
    {
        memmove(&m_frameTimes[1], m_frameTimes, sizeof(m_frameTimes) - sizeof(m_frameTimes[0]));
        m_frameTimes[0] = elapsedTimeSec;

        if (m_sampleCount < MAX_SAMPLE_COUNT)
            ++m_sampleCount;
    }

    m_actualElapsedTimeSec = 0.0f;

    for (int i = 0; i < m_sampleCount; ++i)
        m_actualElapsedTimeSec += m_frameTimes[i];

    if (m_sampleCount > 0)
        m_actualElapsedTimeSec /= m_sampleCount;

    return m_actualElapsedTimeSec;
}

bool GL3Application::getEncoderClsid(const wchar_t *pszFormat, CLSID *pClsid)
{
    UINT num = 0;
    UINT size = 0;

    Gdiplus::GetImageEncodersSize(&num, &size);

    if (!size)
        return false;

    Gdiplus::ImageCodecInfo *pImageCodecInfo = static_cast<Gdiplus::ImageCodecInfo *>(malloc(size));

    if (!pImageCodecInfo)
        return false;

    GetImageEncoders(num, size, pImageCodecInfo);

    for (UINT i = 0; i < num; ++i)
    {
        if (wcscmp(pImageCodecInfo[i].MimeType, pszFormat) == 0)
        {
            memcpy(pClsid, &pImageCodecInfo[i].Clsid, sizeof(CLSID));
            free(pImageCodecInfo);
            return true;
        }
    }

    free(pImageCodecInfo);
    return false;
}

LRESULT CALLBACK GL3Application::windowProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    GL3Application *pApplication = 0;

    if (msg == WM_NCCREATE)
    {
        pApplication = reinterpret_cast<GL3Application *>((reinterpret_cast<LPCREATESTRUCT>(lParam))->lpCreateParams);
        SetWindowLongPtr(hWnd, GWL_USERDATA, reinterpret_cast<LONG_PTR>(pApplication));
    }
    else
    {
        pApplication = reinterpret_cast<GL3Application *>(GetWindowLongPtr(hWnd, GWL_USERDATA));
    }

    if (!pApplication)
        return DefWindowProc(hWnd, msg, wParam, lParam);

    pApplication->m_hWnd = hWnd;
    return pApplication->windowProcImpl(hWnd, msg, wParam, lParam);
}

//-----------------------------------------------------------------------------
// Virtual methods.
//-----------------------------------------------------------------------------

void GL3Application::init(int argc, char *argv[])
{
}

void GL3Application::render()
{
    glViewport(0, 0, m_windowWidth, m_windowHeight);
    glClearColor(0.3f, 0.5f, 0.9f, 0.0f);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
}

void GL3Application::shutdown()
{
}

void GL3Application::update(float elapsedTimeSec)
{
}

LRESULT GL3Application::wndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
	return DefWindowProc(hWnd, msg, wParam, lParam);
}