/******************************************************************************
*	File		-	OGLGraphics.cpp
*	Author		-	Joey Pollack
*	Date		-	2021/08/30 (y/m/d)
*	Mod Date	-	2021/09/02 (y/m/d)
*	Description	-	An openGL implementation of the engine Graphics interface.
******************************************************************************/

#include "glGraphics.h"
#include "defaultShaders.h"
#include <platform/window.h>
#include <assets/types/image.h>
#include <internal_data/data_headers/open_font.h>
#include <utils/logger.h>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>

#include <utils/stb/stb_image.h>

namespace lunarium
{
    ////////////////////////////////////////////////////////////
    //				DEBUG MESSAGE CALLBACK
    ////////////////////////////////////////////////////////////
    std::map<int, std::string> OglGraphics::mDebugMsgTypes;
	std::map<int, std::string> OglGraphics::mDebugMsgSources;
	std::map<int, std::string> OglGraphics::mDebugMsgSeverity;

	void GLAPIENTRY MessageCallback(GLenum source, GLenum type, GLuint id,
		GLenum severity, GLsizei length, const GLchar* message, const void* userParam)
	{
		
		if (type == GL_DEBUG_TYPE_ERROR)
		{
			Logger::GIError(LogCategory::GRAPHICS,
			"%s (type: %s, source: %s, severity: %s), message: %s",
			(type == GL_DEBUG_TYPE_ERROR ? "** GL ERROR **" : ""),
			OglGraphics::mDebugMsgSources[source].c_str(),
			OglGraphics::mDebugMsgTypes[type].c_str(),
			OglGraphics::mDebugMsgSeverity[severity].c_str(),
			message);
		}
		else
		{
			Logger::GIDebug(LogCategory::GRAPHICS,
			"%s (type: %s, source: %s, severity: %s), message: %s",
			(type == GL_DEBUG_TYPE_ERROR ? "** GL ERROR **" : ""),
			OglGraphics::mDebugMsgSources[source].c_str(),
			OglGraphics::mDebugMsgTypes[type].c_str(),
			OglGraphics::mDebugMsgSeverity[severity].c_str(),
			message);
		}

		
	}

    ////////////////////////////////////////////////////////////
    //				RESTRICTED METHODS
    ////////////////////////////////////////////////////////////
    OglGraphics::OglGraphics()
    {
        
    }

    OpRes OglGraphics::Initialize(Window* pWindow, bool enableDebugMessages)
    {
        if (!pWindow->IsInit())
        {
            return OpRes::Fail("Can not initialize Graphics interface. The Window must be initialized first!");
        }

        if (mbIsInit)
        {
            return OpRes::Fail("Can not initialize Graphics interface. It is already initialized");
        }

        mpWindow = pWindow;
		ResizeCanvas();	// Also calls InitTextureFrameBuffer

		glEnable(GL_BLEND);
		glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

        if (enableDebugMessages)
		{
			InitDebugMsgSystem();
		}

		// Initialize the 2D drawing systems
		InitImageSystem();
		InitShapeSystem();

		// Init text rendering
		OpRes res = mText.Initialize();
		if (Failed(res))
		{
			Logger::Warn(LogCategory::GRAPHICS, "Could not initialized the text renderer - %s", res.Description);
		}

		// Load the default internal font
		const char* font = "OpenSans-Regular.ttf";
		// GenerateFontFileAt(font);
		mDefaultFont = mText.LoadFont(OpenFontData, OpenDataSize, font);
		if (mDefaultFont < 0)
		{
			Logger::Warn(LogCategory::GRAPHICS, "Unable to load the default font: %s", font);
		}
		else
		{
			Logger::Info(LogCategory::GRAPHICS, "Successfully created default font: %s", font);
		}

        return OpRes::OK();
    }

    void OglGraphics::Shutdown()
    {
        // Clean up frame buffers
		std::vector<int> ids;
		for (auto iter = mFrameBuffers.begin(); iter != mFrameBuffers.end(); iter++)
		{
			ids.push_back(iter->first);
		}

		for (int i = 0; i < ids.size(); i++)
		{
			DestroyRenderTexture(ids[i]);
		}

		mFrameBuffers.clear();
    }
    

    void OglGraphics::ResizeCanvas()
    {
		// NOTE: METHOD IS NO LONGER NEEDED
		
    }
    

    void OglGraphics::BeginDraw(int framebuffer)
    {
		mActiveFrameBuffer = framebuffer;

		if (mActiveFrameBuffer > -1)
		{
			mFBWidth = mFrameBuffers[mActiveFrameBuffer]->Texture.GetWidth();
			mFBHeight = mFrameBuffers[mActiveFrameBuffer]->Texture.GetHeight();
			glViewport(0, 0, mFBWidth, mFBHeight);
			mProjection = glm::ortho(0.0f, (GLfloat)mFBWidth, (GLfloat)mFBHeight, 0.0f, -1.0f, 1.0f);
			glBindFramebuffer(GL_FRAMEBUFFER, mFrameBuffers[mActiveFrameBuffer]->FBO);

		}
		else
		{
			int width = 0, height = 0;
			mpWindow->GetFramebufferSize(&width, &height);
			if (mFBWidth != width || mFBHeight != height)
			{
				mFBWidth = width;
				mFBHeight = height;
				glViewport(0, 0, mFBWidth, mFBHeight);
				mProjection = glm::ortho(0.0f, (GLfloat)mFBWidth, (GLfloat)mFBHeight, 0.0f, -1.0f, 1.0f);
			}
		}

		glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    }
    
    Image* OglGraphics::EndDraw()
    {
		if (mActiveFrameBuffer > -1)
		{
			FrameBuffer* fb = mFrameBuffers[mActiveFrameBuffer];
			glBindFramebuffer(GL_FRAMEBUFFER, 0);
			glBindTexture(GL_TEXTURE_2D, fb->Texture.GetGLTextureID());
			
			GLenum format = GL_RGBA;
			if (fb->Texture.GetFormat() == ImageFormat::RGB)
			{
				format = GL_RGB;
			}

			glGetTexImage(GL_TEXTURE_2D, 0, format, GL_UNSIGNED_BYTE, (void*)fb->Buffer);

			fb->Texture.SetData(fb->Buffer);

			return &fb->Texture;
		}
		else
		{
        	mpWindow->SwapBuffers();
			return nullptr;
		}

    }
    ////////////////////////////////////////////////////////////


    ////////////////////////////////////////////////////////////
    //				USER METHODS
    ////////////////////////////////////////////////////////////
    void OglGraphics::SetClearColor(Color c)
    {
        mClearColor = c;
		glClearColor(mClearColor.R, mClearColor.G, mClearColor.B, mClearColor.A);
    }

    Color OglGraphics::GetClearColor() const
    {
        return mClearColor;
    }

	int OglGraphics::CreateRenderTexture(int width, int height, int channels)
	{
		FrameBuffer* fb = new FrameBuffer;
		fb->Texture.SetWidth(width);
		fb->Texture.SetHeight(height);

		GLint format = -1;
		if (3 == channels)
		{
			fb->Texture.SetFormat(ImageFormat::RGB);
			format = GL_RGB;
		}
		else if (4 == channels)
		{
			fb->Texture.SetFormat(ImageFormat::RGBA);
			format = GL_RGBA;
		}
		else
		{
			Logger::Error(LogCategory::GRAPHICS, "OglGraphics::CreateRenderTexture failed - invalid channels value: %n", channels);
			return -1;
		}

		// Frame buffer
		glGenFramebuffers(1, &fb->FBO);
		glBindFramebuffer(GL_FRAMEBUFFER, fb->FBO);

		// Texture
		unsigned int id = -1;
		glGenTextures(1, &id);
		fb->Texture.SetGLTextureID(id);
		glBindTexture(GL_TEXTURE_2D, fb->Texture.GetGLTextureID());
		glTexImage2D(GL_TEXTURE_2D, 0, format, width, height, 0, format, GL_UNSIGNED_BYTE, nullptr);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
		glBindTexture(GL_TEXTURE_2D, 0);

		// Attach texture
		glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, fb->Texture.GetGLTextureID(), 0);

		// Render Buffer for depth/stencil testing
		glGenRenderbuffers(1, &fb->RBO);
		glBindRenderbuffer(GL_RENDERBUFFER, fb->RBO);
		glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, width, height);
		glBindRenderbuffer(GL_RENDERBUFFER, 0);

		// Atach the render buffer
		glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, fb->RBO);


		if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
		{
			Logger::Warn(LogCategory::GRAPHICS, "Unable to initialize framebuffer for rendering to a texture");
			glBindFramebuffer(GL_FRAMEBUFFER, 0);
			return -1;
		}

		// finalize the FrameBuffer object
		fb->Buffer = new unsigned char[width * height * channels];
		fb->Texture.SetData(nullptr);
		int next_id = mFrameBuffers.size();
		mFrameBuffers[next_id] = fb;

		glBindFramebuffer(GL_FRAMEBUFFER, 0);

		return next_id;
	}

	void OglGraphics::DestroyRenderTexture(int texture)
	{
		if (mFrameBuffers.find(texture) == mFrameBuffers.end())
		{
			return;
		}

		FrameBuffer* rm = mFrameBuffers[texture];
		mFrameBuffers.erase(texture);

		glDeleteFramebuffers(1, &rm->FBO);
		delete rm;
	}

    // Draw Methods
    void OglGraphics::DrawLine(glm::vec2 point1, glm::vec2 point2, Color color, float lineWidth, float angle)
    {
        mShapeShader.MakeActive();
		mShapeShader.SetUniformMatrix("projection", 1, glm::value_ptr(mProjection));
		glm::mat4 model = glm::mat4(1.0f);
		model = glm::rotate(model, angle, glm::vec3(0.0f, 0.0f, 1.0f));
		mShapeShader.SetUniformMatrix("model", 1, glm::value_ptr(model));
		mShapeShader.SetUniformf("shapeColor", { color.R, color.G, color.B, color.A });
		glBindVertexArray(mRectVAO);

		GLfloat lineVerts[6][4] = {
					{ point1.x, point1.y, },
					{ point2.x, point2.y, },
					{ 0.0f,  0.0f,     },

					{ 0.0f,  0.0f, },
					{ 0.0f, 0.0f, },
					{ 0.0f, 0.0f, }
		};
		glBindBuffer(GL_ARRAY_BUFFER, mRectVBO);
		glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(lineVerts), lineVerts);
		glBindBuffer(GL_ARRAY_BUFFER, 0);

		glLineWidth(lineWidth);
		glDrawArrays(GL_LINES, 0, 2);

		glBindVertexArray(0);
    }

    void OglGraphics::DrawEllipse(glm::vec2 center, glm::vec2 radii, Color color, float thickness, int resolution)
    {
        mShapeShader.MakeActive();
		mShapeShader.SetUniformMatrix("projection", 1, glm::value_ptr(mProjection));
		glm::mat4 model = glm::mat4(1.0f);
		mShapeShader.SetUniformMatrix("model", 1, glm::value_ptr(model));
		mShapeShader.SetUniformf("shapeColor", { color.R, color.G, color.B, color.A });
		glBindVertexArray(mEllipseVAO);

		GLfloat points[360][2];
		for (int i = 0; i < 360; i++)
		{
			float rad = i * 3.14159f / 180.0f;
			points[i][0] = center.x + (radii.x * cos(rad));
			points[i][1] = center.y + (radii.y * sin(rad));
		}

		glBindBuffer(GL_ARRAY_BUFFER, mEllipseVBO);
		glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(points), points);
		glBindBuffer(GL_ARRAY_BUFFER, 0);

		glLineWidth(thickness);
		glDrawArrays(GL_LINE_LOOP, 0, 360);

		glBindVertexArray(0);
    }

	void OglGraphics::DrawFilledEllipse(glm::vec2 center, glm::vec2 radii, Color color, int resolution)
    {
        mShapeShader.MakeActive();
		mShapeShader.SetUniformMatrix("projection", 1, glm::value_ptr(mProjection));
		glm::mat4 model = glm::mat4(1.0f);
		mShapeShader.SetUniformMatrix("model", 1, glm::value_ptr(model));
		mShapeShader.SetUniformf("shapeColor", { color.R, color.G, color.B, color.A });
		glBindVertexArray(mEllipseVAO);

		GLfloat points[362][2];
		points[0][0] = center.x;
		points[0][1] = center.y;
		for (int i = 1; i < 362; i++)
		{
			float rad = (i - 1) * 3.14159f / 180.0f;
			points[i][0] = center.x + (radii.x * cos(rad));
			points[i][1] = center.y + (radii.y * sin(rad));
		}

		glBindBuffer(GL_ARRAY_BUFFER, mEllipseVBO);
		glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(points), points);
		glBindBuffer(GL_ARRAY_BUFFER, 0);

		glDrawArrays(GL_TRIANGLE_FAN, 0, 362);

		glBindVertexArray(0);
    }

	void OglGraphics::DrawFilledPolygon(float* pVerts, int numVerts, Color color, glm::vec2 position, float angle)
	{
		mShapeShader.MakeActive();
		mShapeShader.SetUniformMatrix("projection", 1, glm::value_ptr(mProjection));
		glm::mat4 model = glm::mat4(1.0f);
		model = glm::translate(model, glm::vec3(position.x, position.y, 0.0f));
		model = glm::rotate(model, angle, glm::vec3(0.0f, 0.0f, 1.0f));
		mShapeShader.SetUniformMatrix("model", 1, glm::value_ptr(model));
		mShapeShader.SetUniformf("shapeColor", { color.R, color.G, color.B, color.A });
		glBindVertexArray(mRectVAO);

		glBindBuffer(GL_ARRAY_BUFFER, mRectVBO);
		glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(pVerts), pVerts);
		glBindBuffer(GL_ARRAY_BUFFER, 0);
		
		glDrawArrays(GL_TRIANGLES, 0, numVerts);
		glBindVertexArray(0);
	}

    void OglGraphics::DrawFilledBox(Rectangle box, Color color, float angle)
    {
        mShapeShader.MakeActive();
		mShapeShader.SetUniformMatrix("projection", 1, glm::value_ptr(mProjection));
		glm::mat4 model = glm::mat4(1.0f);
		model = glm::translate(model, glm::vec3(box.X, box.Y, 0.0f));
		model = glm::rotate(model, angle, glm::vec3(0.0f, 0.0f, 1.0f));
		model = glm::scale(model, glm::vec3(box.HalfWidth * 2, box.HalfHeight * 2, 1.0f));
		mShapeShader.SetUniformMatrix("model", 1, glm::value_ptr(model));
		mShapeShader.SetUniformf("shapeColor", { color.R, color.G, color.B, color.A });
		glBindVertexArray(mRectVAO);

		GLfloat vertices[6][4] = {
					{ -0.5f,  0.5f, },
					{ 0.5f, -0.5f,  },
					{ -0.5f,  -0.5f, },

					{ -0.5f, 0.5f, },
					{  0.5f, 0.5f, },
					{ 0.5f, -0.5f,  }
				};
		glBindBuffer(GL_ARRAY_BUFFER, mRectVBO);
		glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(vertices), vertices);
		glBindBuffer(GL_ARRAY_BUFFER, 0);
		// Render quad
		glDrawArrays(GL_TRIANGLES, 0, 6);
		glBindVertexArray(0);
    }

    void OglGraphics::DrawBox(Rectangle dimensions, Color color, float thickness, float angle)
    {
        glm::vec2 topLeft(dimensions.left(), dimensions.top());
        glm::vec2 botRight(dimensions.right(), dimensions.bottom());

        // left side top to bottom
		// DrawLine(glm::vec2(topLeft.x, topLeft.y), glm::vec2(topLeft.x, botRight.y), color, thickness, angle);

		// // bottom left to right
		// DrawLine(glm::vec2(topLeft.x, botRight.y), glm::vec2(botRight.x, botRight.y), color, thickness, angle);

		// // right side bottom to top
		// DrawLine(glm::vec2(botRight.x, botRight.y), glm::vec2(botRight.x, topLeft.y), color, thickness, angle);

		// // top right to left
		// DrawLine(glm::vec2(botRight.x, topLeft.y), glm::vec2(topLeft.x, topLeft.y), color, thickness, angle);

		mShapeShader.MakeActive();
		mShapeShader.SetUniformMatrix("projection", 1, glm::value_ptr(mProjection));
		glm::mat4 model = glm::mat4(1.0f);
		model = glm::translate(model, glm::vec3(dimensions.CenterPoint().x, dimensions.CenterPoint().y, 0.0f));
		model = glm::scale(model, glm::vec3(dimensions.HalfWidth * 2, dimensions.HalfHeight * 2, 1.0f));
		model = glm::rotate(model, angle, glm::vec3(0.0f, 0.0f, 1.0f));
		mShapeShader.SetUniformMatrix("model", 1, glm::value_ptr(model));
		mShapeShader.SetUniformf("shapeColor", { color.R, color.G, color.B, color.A });
		glBindVertexArray(mRectVAO);

		GLfloat lineVerts[4][4] = {
					{ -0.5f, -0.5f, },
					{ 0.5f, -0.5f },

					{ 0.5f,  0.5f, },
					{ -0.5f,  0.5f, },
		};

		glBindBuffer(GL_ARRAY_BUFFER, mRectVBO);
		glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(lineVerts), lineVerts);
		glBindBuffer(GL_ARRAY_BUFFER, 0);

		glLineWidth(thickness);
		glDrawArrays(GL_LINE_LOOP, 0, 4);

		glBindVertexArray(0);



    }

    void OglGraphics::DrawImage(Image& image, glm::vec2 position, Color color, float angle)
    {
		float half_width = image.GetWidth() / 2;
		float half_height = image.GetHeight() / 2;
        DrawImage(image, Rectangle::MakeFromTopLeft(0, 0, image.GetWidth(), image.GetHeight()),
		 	Rectangle(position.x + half_width, position.y + half_height, half_width, half_height), color, angle);
    }

    void OglGraphics::DrawImage(Image& image, Rectangle source, Rectangle destination, Color color, float angle)
    {
        glm::mat4 id = glm::mat4(1.0f);
		glm::vec3 pos = glm::vec3(destination.X, destination.Y, 0.0f);
		glm::mat4 trans = glm::translate(id, pos);
		trans = glm::rotate(trans, angle, glm::vec3(0.0f, 0.0f, 1.0f));
		trans = glm::scale(trans, glm::vec3(destination.HalfWidth * 2, destination.HalfHeight * 2, 1.0f));


		mImageShader.MakeActive();

		float xScale = (source.HalfWidth * 2) / image.GetWidth();
		float xOffset = source.left() / image.GetWidth();
		float yScale = (source.HalfHeight * 2) / image.GetHeight();
		float yOffset = source.top() / image.GetHeight();


		// * -1.0f on yScale will flip the image vertically
		mImageShader.SetUniformf("uvManip", { xScale, xOffset, yScale * -1.0f, yOffset});
		mImageShader.SetUniformMatrix("model", 1, glm::value_ptr(trans));
		mImageShader.SetUniformMatrix("projection", 1, glm::value_ptr(mProjection));
		mImageShader.SetUniformf("spriteColor", { color.R, color.G, color.B, color.A });
		

        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_2D, image.GetGLTextureID());

        glBindVertexArray(mImageVAO);
        glDrawArrays(GL_TRIANGLES, 0, 6);
        glBindVertexArray(0);
    }

    void OglGraphics::DrawString(const char* string, Rectangle boundingArea, Color color, float scale, int font)
    {
		mText.DrawString(font, string, glm::vec2(boundingArea.left(), boundingArea.top()),
							 glm::vec2(boundingArea.right(), boundingArea.bottom()), color, scale, mProjection);
    }

    // Takes raw image data and creates and Image class instance out of it.
    // The raw data must be in one of the ImageFormats and it must be 1 byte per channel.
    void OglGraphics::RegisterImage(Image& image)
    {
		unsigned int glFormat[4] = { GL_RGB, GL_RGBA, GL_BGR, GL_BGRA };
		unsigned int textureID = 0;
		glGenTextures(1, &textureID);
		glBindTexture(GL_TEXTURE_2D, textureID);

		glTexImage2D(GL_TEXTURE_2D, 0, glFormat[image.GetFormat()], image.GetWidth(), 
												image.GetHeight(), 0, glFormat[image.GetFormat()], GL_UNSIGNED_BYTE, image.GetData());
		glGenerateMipmap(GL_TEXTURE_2D);


		// TODO: Move this to a different function to allow for more user options
		//		Or make a version of the method that takes a struct for these options
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

		image.SetGLTextureID(textureID);
    }

    // Fonts
    int OglGraphics::DefaultFont() const
    {
        return mDefaultFont;
    }

    // For weight, 400 is normal and 700 is bold
    int OglGraphics::CreateNewFont(const char* name, const unsigned char* fontData, int bufferSize, float size, int weight)
    {
        return mText.LoadFont(fontData, bufferSize, name, size, weight);
    }

    ////////////////////////////////////////////////////////////
    //				HELPER INIT METHODS
    ////////////////////////////////////////////////////////////

    void OglGraphics::InitDebugMsgSystem()
    {
        // DEBUG MESSAGE TYPES
		mDebugMsgTypes[GL_DEBUG_TYPE_ERROR] = "ERROR";
		mDebugMsgTypes[GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR] = "DEPRECATED_BEHAVIOR";
		mDebugMsgTypes[GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR] = "UNDEFINED_BEHAVIOR";
		mDebugMsgTypes[GL_DEBUG_TYPE_PORTABILITY] = "PORTABILITY";
		mDebugMsgTypes[GL_DEBUG_TYPE_PERFORMANCE] = "PERFORMANCE";
		mDebugMsgTypes[GL_DEBUG_TYPE_OTHER] = "OTHER";
		mDebugMsgTypes[GL_DEBUG_TYPE_MARKER] = "MARKER";
		mDebugMsgTypes[GL_DEBUG_TYPE_PUSH_GROUP] = "PUSH_GROUP";
		mDebugMsgTypes[GL_DEBUG_TYPE_POP_GROUP] = "POP_GROUP";

		// DEBUG MESSAGE SOURCES
		mDebugMsgSources[GL_DEBUG_SOURCE_API] = "API";
		mDebugMsgSources[GL_DEBUG_SOURCE_WINDOW_SYSTEM] = "WINDOW_SYSTEM";
		mDebugMsgSources[GL_DEBUG_SOURCE_SHADER_COMPILER] = "SHADER_COMPILER";
		mDebugMsgSources[GL_DEBUG_SOURCE_THIRD_PARTY] = "THIRD_PARTY";
		mDebugMsgSources[GL_DEBUG_SOURCE_APPLICATION] = "APPLICATION";
		mDebugMsgSources[GL_DEBUG_SOURCE_OTHER] = "OTHER";

		// DEBUG MESSAGE SEVERITY
		mDebugMsgSeverity[GL_DEBUG_SEVERITY_HIGH] =	"HIGH";
		mDebugMsgSeverity[GL_DEBUG_SEVERITY_MEDIUM] = "MEDIUM";
		mDebugMsgSeverity[GL_DEBUG_SEVERITY_LOW] = "LOW";
		mDebugMsgSeverity[GL_DEBUG_SEVERITY_NOTIFICATION] = "NOTIFICATION";

		glEnable(GL_DEBUG_OUTPUT);
		glDebugMessageCallback(MessageCallback, 0);
    }

    void OglGraphics::InitImageSystem()
    {
        // Setup geometry
		// https://learnopengl.com/In-Practice/2D-Game/Rendering-Sprites
		GLuint VBO;
		// GLfloat vertices[] = {
		// 	//Pos      // Tex
		// 	-0.5f, 0.5f, 0.0f, 0.0f,
		// 	0.5f, -0.5f, 1.0f, 1.0f,
		// 	-0.5f, -0.5f, 0.0f, 1.0f,

		// 	-0.5f, 0.5f, 0.0f, 0.0f,
		// 	0.5f, 0.5f, 1.0f, 0.0f,
		// 	0.5f, -0.5f, 1.0f, 1.0f
		// };

		GLfloat vertices[] = {
			//Pos      // Tex
			-0.5f, 0.5f, 0.0f, 1.0f,
			0.5f, -0.5f, 1.0f, 0.0f,
			-0.5f, -0.5f, 0.0f, 0.0f,

			-0.5f, 0.5f, 0.0f, 1.0f,
			0.5f, 0.5f, 1.0f, 1.0f,
			0.5f, -0.5f, 1.0f, 0.0f
		};

		// GLfloat vertices[] = {
		// 	// Pos      // Tex
		// 	0.0f, 1.0f, 0.0f, 0.0f,
		// 	1.0f, 0.0f, 1.0f, 1.0f,
		// 	0.0f, 0.0f, 0.0f, 1.0f,

		// 	0.0f, 1.0f, 0.0f, 0.0f,
		// 	1.0f, 1.0f, 1.0f, 0.0f,
		// 	1.0f, 0.0f, 1.0f, 1.0f
		// };

		glGenVertexArrays(1, &mImageVAO);
		glGenBuffers(1, &VBO);

		glBindBuffer(GL_ARRAY_BUFFER, VBO);
		glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

		glBindVertexArray(mImageVAO);
		glEnableVertexAttribArray(0);
		glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 4 * sizeof(GLfloat), (GLvoid*)0);
		glBindBuffer(GL_ARRAY_BUFFER, 0);
		glBindVertexArray(0);

		// Setup Shader Program
		mImageShader.SetSource(glShader::TYPE_VERTEX, OGLDefaultShaders.DefaultSpriteVertex, false);
		mImageShader.SetSource(glShader::TYPE_FRAGMENT, OGLDefaultShaders.DefaultSpriteFragment, false);
		if (!mImageShader.BuildProgram())
		{
			Logger::Error(LogCategory::GRAPHICS, "Unable to build default image shader program");
		}
    }
    
    void OglGraphics::InitShapeSystem()
    {
        // Create rectangle buffers
		glGenVertexArrays(1, &mRectVAO);
		glGenBuffers(1, &mRectVBO);
		glBindVertexArray(mRectVAO);
		glBindBuffer(GL_ARRAY_BUFFER, mRectVBO);
		glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat) * 6 * 4, NULL, GL_DYNAMIC_DRAW);
		glEnableVertexAttribArray(0);
		glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 4 * sizeof(GLfloat), 0);
		glBindBuffer(GL_ARRAY_BUFFER, 0);
		glBindVertexArray(0);

		// Create Ellipse buffers
		glGenVertexArrays(1, &mEllipseVAO);
		glGenBuffers(1, &mEllipseVBO);
		glBindVertexArray(mEllipseVAO);
		glBindBuffer(GL_ARRAY_BUFFER, mEllipseVBO);
		glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat) * 362 * 2, NULL, GL_DYNAMIC_DRAW);
		glEnableVertexAttribArray(0);
		glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(GLfloat), 0);
		glBindBuffer(GL_ARRAY_BUFFER, 0);
		glBindVertexArray(0);


		// Load shape shaders
		mShapeShader.SetSource(glShader::TYPE_VERTEX, OGLDefaultShaders.DefaultShapeVertex, false);
		mShapeShader.SetSource(glShader::TYPE_FRAGMENT, OGLDefaultShaders.DefaultShapeFragment, false);
		if (!mShapeShader.BuildProgram())
		{
			Logger::Error(LogCategory::GRAPHICS, "Unable to build shape shader program.");
		}
    }
}