/******************************************************************************
*	File		-	renderer2D.h
*	Author		-	Joey Pollack
*	Date		-	2022/07/13 (y/m/d)
*	Mod Date	-	2022/07/13 (y/m/d)
*	Description	-	The main 2D render class
******************************************************************************/

#include "renderer2D.h"
#include "orthographic_camera.h"
#include "shaders/defaultShaders.h"
#include <internal_data/data_headers/open_font.h>
#include <internal_data/data_headers/roboto_font.h>

#include <utils/logger.h>
#include <utils/helpers.h>
#include <glad/gl.h>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>

#include <core/core.h>

namespace lunarium
{
    Renderer2D::Renderer2D()
        : mpCamera(nullptr)
    {

    }


    OpRes Renderer2D::Initialize()
    {
        // Configure OpenGL
        glPixelStorei(GL_UNPACK_ALIGNMENT, 1); // Disable byte-alignment restriction for texture loading
        glPixelStorei(GL_PACK_ALIGNMENT, 1); // Disable byte-alignment restriction for texture storing

        float max_tex_size = 0.0f;
        glGetFloatv(GL_MAX_TEXTURE_SIZE, &max_tex_size);
        Logger::Info(LogCategory::GRAPHICS, "OpenGL max texture size is: %f", max_tex_size);

        mpCamera = nullptr;

        // INIT QUAD DATA
        OpRes result = InitQuadData();
        if (Failed(result)) { return OpRes::Fail("Could not initialize the 2D Renderer: %s", result.Description.c_str()); }
        
        // INIT LINE DATA
        result = InitLineData();
        if (Failed(result)) { return OpRes::Fail("Could not initialize the 2D Renderer: %s", result.Description.c_str()); }

        // INIT ELLIPSE DATA
        result = InitEllipseData();
        if (Failed(result)) { return OpRes::Fail("Could not initialize the 2D Renderer: %s", result.Description.c_str()); }

        // INIT TEXT SYSTEM
        mTextAPI.Initialize().LogIfFailed(LogCategory::GRAPHICS);
        mDefaultFont = mTextAPI.LoadFont(lunarium::OpenFontData, lunarium::OpenDataSize, "Open Font");
        //mDefaultFont = mTextAPI.LoadFont(lunarium::RobotoFontData, lunarium::RobotoDataSize, "Roboto Font");

       return OpRes::OK();
    }
    
    void Renderer2D::Shutdown()
    {
        VertexBuffer::Destroy(&mQuadData.VertexBuffer);
        //delete[] mQuadData.mIndexBuffer;
    }

    void Renderer2D::SetClearColor(Color color)
    {
        mClearColor = color;
        glClearColor(color.R, color.G, color.B, color.A);
    }

    Color Renderer2D::GetClearColor()
    {
        return mClearColor;
    }

    void Renderer2D::ResetFrameStats()
    {
        mFrameStats.DrawCalls = 0;
        mFrameStats.NumTris = 0;
        mFrameStats.NumVerts = 0;
    }

    Renderer2D::FrameStats Renderer2D::GetFrameStats() const
    {
        return mFrameStats;
    }

    void Renderer2D::BeginDraw(OrthographicCamera* pCamera)
    {
        mpCamera = pCamera;
        glViewport(0, 0, pCamera->GetViewport().Width, pCamera->GetViewport().Height);
        // int w, h;
        // Core::MainWindow().GetFramebufferSize(&w, &h);
        // glViewport(0, 0, w, h);
        glClear(GL_COLOR_BUFFER_BIT);
        //ResetFrameStats();
    }

    void Renderer2D::EndDraw()
    {
        Flush();
    }

    /////////////////////////////////////////////////////////////////////
    //				DRAW METHODS
    /////////////////////////////////////////////////////////////////////
    
    void Renderer2D::DrawSprite(Texture* texture, glm::vec2 position, Rectangle sub_texture_area, Color color, float angle, glm::mat4 parent_transform)
    {
        Rectangle quad = Rectangle::MakeFromTopLeft(position.x, position.y, sub_texture_area.width(), sub_texture_area.height());
        DrawQuad(quad, color, texture, angle, sub_texture_area, parent_transform);
    }
    
    // void Renderer2D::DrawSprite(Texture* texture, Rectangle destination, Rectangle sub_texture_area, Color color = Color::White(), float angle = 0.0f)
    // {
    //     DrawQuad(destination, color, texture, angle, sub_texture_area);
    // }

    void Renderer2D::DrawQuad(Rectangle quad, Color color, Texture* texture, float angle, Rectangle sub_texture_area, glm::mat4 parent_transform)
    {
        if (!mQuadData.VertexBuffer->WillFit(4))
        {
            Flush(FlushMode::Quads);
        }

        float parent_idx = -1.0f;
        if (parent_transform != glm::mat4(1.0f))
        {
            if (mQuadData.Parents.size() >= mQuadData.ParentSlots)
                Flush(FlushMode::Quads);

            mQuadData.Parents.push_back(parent_transform);
            parent_idx = mQuadData.Parents.size() - 1;
        }

        int texture_slot = -1;
        if (texture)
        {
            for (int i = 0; i < mQuadData.LoadedTextures.size(); i++)
            {
                if (texture == mQuadData.LoadedTextures[i])
                {
                    texture_slot = i;
                    break;
                }
            }

            if (-1 == texture_slot)
            {
                mQuadData.LoadedTextures.push_back(texture);
                texture_slot = mQuadData.LoadedTextures.size() - 1;
            }
        }
        else
        {
            texture_slot = 0;
        }


        float tex_is_grey_scale = (texture && texture->GetFormat() == TextureFormat::RED) ? 1.0f : 0.0f;
        float left = sub_texture_area.X;
        if (left < 0)
        {
            sub_texture_area = Rectangle::MakeFromTopLeft(0, 0, mQuadData.LoadedTextures[texture_slot]->GetWidth(), 
                                                                mQuadData.LoadedTextures[texture_slot]->GetHeight());
        }

        float scale_x = (sub_texture_area.HalfWidth * 2) / mQuadData.LoadedTextures[texture_slot]->GetWidth();
		float offset_x = sub_texture_area.left() / mQuadData.LoadedTextures[texture_slot]->GetWidth();
		float scale_y = (sub_texture_area.HalfHeight * 2) / mQuadData.LoadedTextures[texture_slot]->GetHeight();
		float offset_y = sub_texture_area.top() / mQuadData.LoadedTextures[texture_slot]->GetHeight();

        // Need to invert the offset_y because OpenGL likes being weird
        offset_y *= -1.0f;

        //glm::vec3 parent_pos = parent_transform[3];

        // FIRST
        QuadData::Vertex verts[4];
        int vert_size = sizeof(QuadData::Vertex);
        verts[0].pos = mQuadData.vert_pos[0];
        verts[0].tex_coord = glm::vec2(mQuadData.vert_tex[0].x * scale_x + offset_x, mQuadData.vert_tex[0].y * scale_y - offset_y);
        verts[0].color = color;
        verts[0].tex_slot = texture_slot;
        verts[0].translation = glm::vec3(quad.CenterPoint().x, quad.CenterPoint().y, 0.0f);
        verts[0].angle = angle;
        verts[0].scale = glm::vec3(quad.HalfWidth * 2, quad.HalfHeight * 2, 1.0f);
        verts[0].tex_is_grey_scale = tex_is_grey_scale;
        verts[0].parent_idx = parent_idx;
        //memcpy(&mQuadData.RawVertexBuffer[mQuadData.RawBufferIndex], &v1, vert_size);
        //mQuadData.RawBufferIndex += 1;
        //mQuadData.VertexBuffer->PushVertices((void*)&v1, 1);

        // SECOND
        verts[1].pos =  mQuadData.vert_pos[1];
        verts[1].tex_coord = glm::vec2(mQuadData.vert_tex[1].x * scale_x + offset_x, mQuadData.vert_tex[1].y * scale_y - offset_y);
        verts[1].color = color;
        verts[1].tex_slot = texture_slot;
        verts[1].translation = glm::vec3(quad.CenterPoint().x, quad.CenterPoint().y, 0.0f);
        verts[1].angle = angle;
        verts[1].scale = glm::vec3(quad.HalfWidth * 2, quad.HalfHeight * 2, 1.0f);
        verts[1].tex_is_grey_scale = tex_is_grey_scale;
        verts[1].parent_idx = parent_idx;
        //memcpy(&mQuadData.RawVertexBuffer[mQuadData.RawBufferIndex], &v2, vert_size);
        //mQuadData.RawBufferIndex += 1;        
       // mQuadData.VertexBuffer->PushVertices((void*)&v2, 1);

        // THIRD
        verts[2].pos = mQuadData.vert_pos[2];
        verts[2].tex_coord = glm::vec2(mQuadData.vert_tex[2].x * scale_x + offset_x, mQuadData.vert_tex[2].y * scale_y - offset_y);
        verts[2].color = color;
        verts[2].tex_slot = texture_slot;
        verts[2].translation = glm::vec3(quad.CenterPoint().x, quad.CenterPoint().y, 0.0f);
        verts[2].angle = angle;
        verts[2].scale = glm::vec3(quad.HalfWidth * 2, quad.HalfHeight * 2, 1.0f);
        verts[2].tex_is_grey_scale = tex_is_grey_scale;
        verts[2].parent_idx = parent_idx;
        //memcpy(&mQuadData.RawVertexBuffer[mQuadData.RawBufferIndex], &v3, vert_size);
        //mQuadData.RawBufferIndex += 1;
        //mQuadData.VertexBuffer->PushVertices((void*)&v3, 1);

        // FOURTH
        verts[3].pos = mQuadData.vert_pos[3];
        verts[3].tex_coord = glm::vec2(mQuadData.vert_tex[3].x * scale_x + offset_x, mQuadData.vert_tex[3].y * scale_y - offset_y);
        verts[3].color = color;
        verts[3].tex_slot = texture_slot;
        verts[3].translation = glm::vec3(quad.CenterPoint().x, quad.CenterPoint().y, 0.0f);
        verts[3].angle = angle;
        verts[3].scale = glm::vec3(quad.HalfWidth * 2, quad.HalfHeight * 2, 1.0f);
        verts[3].tex_is_grey_scale = tex_is_grey_scale;
        verts[3].parent_idx = parent_idx;
        //memcpy(&mQuadData.RawVertexBuffer[mQuadData.RawBufferIndex], &v4, vert_size);
        //mQuadData.RawBufferIndex += 1;

        mQuadData.NumQuads++;
        mQuadData.VertexBuffer->PushVertices((void*)verts, 4);

        // Update render stats
        mFrameStats.NumTris += 2;
        mFrameStats.NumVerts += 4;
        
    }

    void Renderer2D::DrawLine(glm::vec2 point_a, glm::vec2 point_b, Color color, float thickness, float angle)
    {
        if (thickness != mLineData.Thickness || !mLineData.VertexBuffer->WillFit(2))
        {
            Flush(FlushMode::Lines);
            mLineData.Thickness = thickness;
        }

        glm::mat4 model = glm::mat4(1.0f);
        model = glm::translate(model, glm::vec3(point_a.x, point_a.y, 0.0f));
        //model = glm::rotate(model, angle, glm::vec3(0.0f, 0.0f, 1.0f));

        // V1
        LineData::Vertex verts[2];
        int vert_size = sizeof(LineData::Vertex);
        glm::vec4 long_pos = model * glm::vec4(-0.5f, 0.0f, 0.0f, 1.0f);
        verts[0].pos = glm::vec2(long_pos.x, long_pos.y);
       // v1.translate = glm::vec3(point_a.x, point_a.y, 0.0f);
        verts[0].color = color;
        //v1.angle = angle;
        // memcpy(&mLineData.RawVertexBuffer[mLineData.RawBufferIndex], &v1, vert_size);
        // mLineData.RawBufferIndex += 1;

        model = glm::mat4(1.0f);
        model = glm::translate(model, glm::vec3(point_b.x, point_b.y, 0.0f));
       // model = glm::rotate(model, angle, glm::vec3(0.0f, 0.0f, 1.0f));

        // V2
        long_pos = model * glm::vec4(0.5f, 0.0f, 0.0f, 1.0f);
        verts[1].pos = glm::vec2(long_pos.x, long_pos.y);
       // v2.translate = glm::vec3(point_b.x, point_b.y, 0.0f);
        verts[1].color = color;
        //v2.angle = angle;
        // memcpy(&mLineData.RawVertexBuffer[mLineData.RawBufferIndex], &v2, vert_size);
        // mLineData.RawBufferIndex += 1;

        mLineData.VertexBuffer->PushVertices((void*)verts, 2);

        mLineData.NumLines++;
        mFrameStats.NumVerts += 2;
    }

    void Renderer2D::DrawBox(Rectangle box, Color color, float thickness, float angle)
    {
        DrawLine(glm::vec2(box.left(), box.top()), glm::vec2(box.right(), box.top()), color, thickness, angle);
        DrawLine(glm::vec2(box.right(), box.top()), glm::vec2(box.right(), box.bottom()), color, thickness, angle);
        DrawLine(glm::vec2(box.right(), box.bottom()), glm::vec2(box.left(), box.bottom()), color, thickness, angle);
        DrawLine(glm::vec2(box.left(), box.bottom()), glm::vec2(box.left(), box.top()), color, thickness, angle);
    }   

    void Renderer2D::DrawEllipse(glm::vec2 center_point, glm::vec2 radii, Color color, int resolution, float thickness, float angle)
    {
        float stride = 362.0f / resolution;
        float cur_deg = 0.0f;
        float rad = cur_deg * 3.14159f / 180.0f;
        glm::vec2 prev_point = glm::vec2(center_point.x + (radii.x * cos(rad)), center_point.y + (radii.y * sin(rad)));
        glm::vec2 first_point = prev_point;
        cur_deg += stride;
        for (int i = 1; i <= resolution; i++, cur_deg += stride)
        {
            rad = cur_deg * 3.14159f / 180.0f;
            glm::vec2 cur_point(center_point.x + (radii.x * cos(rad)), center_point.y + (radii.y * sin(rad)));
            
            DrawLine(prev_point, cur_point, color, thickness, angle);
            prev_point = cur_point;
        }

        DrawLine(prev_point, first_point, color, thickness, angle);
    }

    void Renderer2D::DrawEllipseFilled(glm::vec2 center_point, glm::vec2 radii, Color color, int resolution, float angle)
    {
        float stride = 362.0f / resolution;
        u32 idx_offset = mEllipseData.VertexBuffer->GetNumVerts();

        mEllipseData.verts[0].pos = center_point;
        mEllipseData.verts[0].color = color;
        mEllipseData.indices[0] = idx_offset;

        float cur_deg = 0.0f;
        int cur_idx = 0;
        for (int i = 1; i <= resolution; i++, cur_deg += stride)
		{
			float rad = cur_deg * 3.14159f / 180.0f;
			mEllipseData.verts[i].pos.x = center_point.x + (radii.x * cos(rad));
			mEllipseData.verts[i].pos.y = center_point.y + (radii.y * sin(rad));
            mEllipseData.verts[i].color = color;

            if (i == 1)
            {
                mEllipseData.indices[cur_idx] = idx_offset;
                mEllipseData.indices[cur_idx + 1] = idx_offset + 1;
                mEllipseData.indices[cur_idx + 2] = idx_offset + 2;
                cur_idx += 3;
            }
            else if (i == resolution)
            {
                mEllipseData.indices[cur_idx] = idx_offset;
                mEllipseData.indices[cur_idx + 1] = mEllipseData.indices[cur_idx - 1];
                mEllipseData.indices[cur_idx + 2] = idx_offset + 1;
                cur_idx += 3;
            }
            else
            {
                mEllipseData.indices[cur_idx] = idx_offset;
                mEllipseData.indices[cur_idx + 1] = mEllipseData.indices[cur_idx - 1];
                mEllipseData.indices[cur_idx + 2] = mEllipseData.indices[cur_idx - 1] + 1;
                cur_idx += 3;
            }
		}


        mEllipseData.VertexBuffer->PushVertices((void*)mEllipseData.verts, resolution + 1);
        mEllipseData.VertexBuffer->PushIndices(mEllipseData.indices, cur_idx);
        mEllipseData.NumEllipses++;
        mEllipseData.NumVerts += resolution + 1;
        mEllipseData.NumIndices += cur_idx;

        
        // Update render stats
        mFrameStats.NumTris += cur_idx / 3;
        mFrameStats.NumVerts += resolution;
    }

    void Renderer2D::DrawString(const char* string, Rectangle bounding_box, Color color, float scale, int font)
    {
        u32 f = font < 0 ? mDefaultFont : font;
        mTextAPI.DrawString(*this, f, string, bounding_box, color, scale);
    }


    /////////////////////////////////////////////////////////////////////
    //				HELPERS
    /////////////////////////////////////////////////////////////////////
    void Renderer2D::Flush(int flush_mode)
    {
        // Draw quads
        if (mQuadData.NumQuads > 0 && flush_mode & FlushMode::Quads > 0)
        {   
            mQuadData.MarkedForReset = true;
            // if (!mQuadData.VertexBuffer->PushVertices(mQuadData.RawVertexBuffer, mQuadData.NumQuads * 4))
            // {
            //     Logger::Error(LogCategory::GRAPHICS, "Could not push verts into the quad buffer!");
            //     ResetDrawingData();
            //     return;
            // }

            mQuadData.QuadShader->Use();
            for (int i = 0; i < mQuadData.LoadedTextures.size(); i++)
            {
                mQuadData.LoadedTextures[i]->Bind(i);
            }

            Uniform uprojview;
            uprojview.Type = UniformType::FMAT4;
            uprojview.Location = -1;
            uprojview.Name = "projview";

            mQuadData.QuadShader->SetUniform(uprojview, (void*)glm::value_ptr(mpCamera->GetViewProjection())).LogIfFailed(LogCategory::GRAPHICS);

            Uniform uParents;
            uParents.Type = UniformType::FMAT4;
            uParents.Location = -1;
            uParents.Name = "parents";
            mQuadData.QuadShader->SetUniform(uParents, (void*)mQuadData.Parents.data(), mQuadData.Parents.size()).LogIfFailed(LogCategory::GRAPHICS);

            //mQuadData.VertexBuffer->Bind();

            mQuadData.VertexBuffer->DrawArray(mQuadData.NumQuads * 6);

           // glDrawElements(GL_TRIANGLES, mQuadData.NumQuads * 6,  GL_UNSIGNED_INT, nullptr);

            // Checking how the rotation looks when lines are drawn instead
            // glDrawArrays(GL_LINES, 0, mQuadData.NumQuads * 4);

            // mQuadData.WireFrameShader->Use();
            // uprojview.Location = -1;
            // mQuadData.WireFrameShader->SetUniform(uprojview, (void*)glm::value_ptr(mpCamera->GetViewProjection())).LogIfFailed(LogCategory::GRAPHICS);

            // Uniform ucolor;
            // ucolor.Type = UniformType::F4;
            // ucolor.Location = -1;
            // ucolor.Name = "in_color";
            // mQuadData.WireFrameShader->SetUniform(ucolor, (void*)glm::value_ptr(glm::vec4(0.2f, 0.1f, 1.0f, 1.0f))).LogIfFailed(LogCategory::GRAPHICS);
            // glPolygonMode( GL_FRONT_AND_BACK, GL_LINE );
            // glLineWidth(5.0f);
            // glDrawElements(GL_TRIANGLES, mQuadData.NumQuads * 6,  GL_UNSIGNED_INT, nullptr);
            // glPolygonMode( GL_FRONT_AND_BACK, GL_FILL );
            

            mFrameStats.DrawCalls++;
        }

        // Draw Lines
        int draw = flush_mode & FlushMode::Lines;
        if (mLineData.NumLines > 0 && draw > 0)
        {
            mLineData.MarkedForReset = true;
            // if (!mLineData.VertexBuffer->PushVertices(mLineData.RawVertexBuffer, mLineData.NumLines * 2))
            // {
            //     Logger::Error(LogCategory::GRAPHICS, "Could not push verts into the line buffer!");
            //     ResetDrawingData();
            //     return;
            // }

            mLineData.Shader->Use();
            Uniform uprojview;
            uprojview.Type = UniformType::FMAT4;
            uprojview.Location = -1;
            uprojview.Name = "projview";

            mLineData.Shader->SetUniform(uprojview, (void*)glm::value_ptr(mpCamera->GetViewProjection())).LogIfFailed(LogCategory::GRAPHICS);

            //mLineData.VertexBuffer->Bind();

            //glDrawElements(GL_LINES, mLineData.NumLines * 2,  GL_UNSIGNED_INT, nullptr);
            glLineWidth(mLineData.Thickness);
            //glDrawArrays(GL_LINES, 0, mLineData.NumLines * 2);
            mLineData.VertexBuffer->DrawArray(mLineData.NumLines * 2);
            mFrameStats.DrawCalls++;

        }

        // Draw Ellipses
        if (mEllipseData.NumEllipses > 0 && flush_mode & FlushMode::Ellipses > 0)
        {
            mEllipseData.MarkedForReset = true;

            mEllipseData.Shader->Use();
            Uniform uprojview;
            uprojview.Type = UniformType::FMAT4;
            uprojview.Location = -1;
            uprojview.Name = "projview";

            mEllipseData.Shader->SetUniform(uprojview, (void*)glm::value_ptr(mpCamera->GetViewProjection())).LogIfFailed(LogCategory::GRAPHICS);

           // mEllipseData.VertexBuffer->Bind();

            mEllipseData.VertexBuffer->DrawArray();
            mFrameStats.DrawCalls++;
        }

        // Reset drawing data
        ResetDrawingData();
    }

    /////////////////////////////////////////////////////////////////////
    //				HELPERS     HELPERS
    /////////////////////////////////////////////////////////////////////
    
    OpRes Renderer2D::InitQuadData()
    {
        u32* indices = new u32[mQuadData.MaxIndices];
        // Need to adjust the set of indices because they reference verts deeper into the buffer
        // See Hazel for example of this
        int offset = 0;
        for (int i = 0; i < mQuadData.MaxIndices; i+=6)
        {
            for ( int j = 0; j < 6; j++)
            {
                indices[i + j] = mQuadData.indices[(i + j) % 6] + offset;
            }
            offset += 4;
        }

        // INIT QUAD DATA
        mQuadData.BufferLayout.PushVertexAttribute(VertexAttribute { VertexAttributeType::FLOAT32, 3 }); // Position
        mQuadData.BufferLayout.PushVertexAttribute(VertexAttribute { VertexAttributeType::FLOAT32, 2 }); // Tex_coords
        mQuadData.BufferLayout.PushVertexAttribute(VertexAttribute { VertexAttributeType::FLOAT32, 4 }); // Color
        mQuadData.BufferLayout.PushVertexAttribute(VertexAttribute { VertexAttributeType::FLOAT32, 1 });   // Texture Sampler Index
        mQuadData.BufferLayout.PushVertexAttribute(VertexAttribute { VertexAttributeType::FLOAT32, 3 });   // translation
        mQuadData.BufferLayout.PushVertexAttribute(VertexAttribute { VertexAttributeType::FLOAT32, 1 });   // angle
        mQuadData.BufferLayout.PushVertexAttribute(VertexAttribute { VertexAttributeType::FLOAT32, 3 });   // Scale
        mQuadData.BufferLayout.PushVertexAttribute(VertexAttribute { VertexAttributeType::FLOAT32, 1 });   // tex_is_grey_scale
        mQuadData.BufferLayout.PushVertexAttribute(VertexAttribute { VertexAttributeType::FLOAT32, 1 });   // parent_idx
        mQuadData.VertexBuffer = VertexBuffer::Create(mQuadData.BufferLayout, mQuadData.MaxVertices, nullptr, mQuadData.MaxIndices, indices);

        // RAW VERTEX BUFFER
        // mQuadData.RawVertexBuffer = new QuadData::Vertex[mQuadData.MaxVertices];
        // mQuadData.RawBufferIndex = 0;

        // Safety check vertex struct size
        int layout_size = mQuadData.BufferLayout.GetLayoutSizeInBytes();
        int struct_size = sizeof(QuadData::Vertex);
        if (struct_size != layout_size)
        {
            Logger::Error(LogCategory::GRAPHICS, 
                    "Quad::Vertex struct size does not match the vertex layout size! struct: %d, layout: %d", struct_size, layout_size);
        }

        //mQuadData.mIndexBuffer = new IndexBuffer(mQuadData.MaxIndices * sizeof(u32));

        std::string vert_source = File::ReadTextFile("quad.vert");
        std::string frag_source = File::ReadTextFile("quad.frag");
        mQuadData.QuadShader = new Shader(vert_source.c_str(), nullptr, frag_source.c_str());
        if (!mQuadData.QuadShader->IsValid())
        {
            return OpRes::Fail("Failed to create the quad shader");
        }

        // Wireframe shader
        std::string wire_vert_source = File::ReadTextFile("quad_wireframe.vert");
        std::string wire_frag_source = File::ReadTextFile("quad_wireframe.frag");
        mQuadData.WireFrameShader = new Shader(wire_vert_source.c_str(), nullptr, wire_frag_source.c_str());
        if (!mQuadData.WireFrameShader->IsValid())
        {
            return OpRes::Fail("Failed to create the quad wireframe shader");
        }

        // INIT WHITE TEXTURE
        u8 data[4] = {255, 255, 255, 255};
        mQuadData.WhiteTexture = Texture::Create(data, 1, 1);
        mQuadData.LoadedTextures.push_back(mQuadData.WhiteTexture);

        return OpRes::OK();
    }
    
    OpRes Renderer2D::InitLineData()
    {
        mLineData.BufferLayout.PushVertexAttribute(VertexAttribute { VertexAttributeType::FLOAT32, 2 }); // Position
       // mLineData.BufferLayout.PushVertexAttribute(VertexAttribute { VertexAttributeType::FLOAT32, 3 }); // Translate
        mLineData.BufferLayout.PushVertexAttribute(VertexAttribute { VertexAttributeType::FLOAT32, 4 }); // Color
       // mLineData.BufferLayout.PushVertexAttribute(VertexAttribute { VertexAttributeType::FLOAT32, 1 }); // angle
        mLineData.VertexBuffer = VertexBuffer::Create(mLineData.BufferLayout, mLineData.MaxVertices);
        mLineData.VertexBuffer->SetType(VertexBuffer::LayoutType::LINE);

        // mLineData.RawVertexBuffer = new LineData::Vertex[mLineData.MaxVertices];
        // mLineData.RawBufferIndex = 0;

        // Safety check vertex struct size
        int layout_size = mLineData.BufferLayout.GetLayoutSizeInBytes();
        int struct_size = sizeof(LineData::Vertex);
        if (struct_size != layout_size)
        {
            Logger::Error(LogCategory::GRAPHICS, 
                    "LineData::Vertex struct size does not match the vertex layout size! struct: %d, layout: %d", struct_size, layout_size);
        }

        std::string vert_source = File::ReadTextFile("line.vert");
        std::string frag_source = File::ReadTextFile("line.frag");
        mLineData.Shader = new Shader(vert_source.c_str(), nullptr, frag_source.c_str());
        if (!mLineData.Shader->IsValid())
        {
            return OpRes::Fail("Failed to create the line shader");
        }

        return OpRes::OK();
    }


    OpRes Renderer2D::InitEllipseData()
    {
        mEllipseData.BufferLayout.PushVertexAttribute(VertexAttribute { VertexAttributeType::FLOAT32, 2}); // Position
        mEllipseData.BufferLayout.PushVertexAttribute(VertexAttribute { VertexAttributeType::FLOAT32, 4}); // Color
        mEllipseData.VertexBuffer = VertexBuffer::Create(mEllipseData.BufferLayout, mEllipseData.MaxVertices, nullptr, mEllipseData.MaxIndices, nullptr);

        // Safety check vertex struct size
        int layout_size = mEllipseData.BufferLayout.GetLayoutSizeInBytes();
        int struct_size = sizeof(EllipseData::Vertex);
        if (struct_size != layout_size)
        {
            Logger::Error(LogCategory::GRAPHICS, 
                    "EllipseData::Vertex struct size does not match the vertex layout size! struct: %d, layout: %d", struct_size, layout_size);
        }

        // Shader
        std::string vert_source = File::ReadTextFile("ellipse.vert");
        std::string frag_source = File::ReadTextFile("ellipse.frag");
        mEllipseData.Shader = new Shader(vert_source.c_str(), nullptr, frag_source.c_str());
        if (!mEllipseData.Shader->IsValid())
        {
            return OpRes::Fail("Failed to create the ellipse shader");
        }

        return OpRes::OK();
    }
    
    void Renderer2D::ResetDrawingData()
    {
        // Reset Quad Data
        if (mQuadData.MarkedForReset)
        {
            // Clear loaded textures
            for (int i = 0; i < mQuadData.LoadedTextures.size(); i++)
            {
                mQuadData.LoadedTextures[i]->Unbind();
            }
            mQuadData.LoadedTextures.clear();

            // Except for the white texture
            mQuadData.LoadedTextures.push_back(mQuadData.WhiteTexture);

            mQuadData.VertexBuffer->Clear();
            mQuadData.NumQuads = 0;
            //mQuadData.RawBufferIndex = 0;
            mQuadData.Parents.clear();

            mQuadData.MarkedForReset = false;
        }

        // Reset Line Data
        if (mLineData.MarkedForReset)
        {
            mLineData.VertexBuffer->Clear();
            mLineData.NumLines = 0;
            // mLineData.RawBufferIndex = 0;
            mLineData.Thickness = 1.0f;

            mLineData.MarkedForReset = false;
        }

        // Reset Ellipse Data
        if (mEllipseData.MarkedForReset)
        {
            mEllipseData.NumEllipses = 0;
            mEllipseData.NumVerts = 0;
            mEllipseData.NumIndices = 0;

            mEllipseData.VertexBuffer->Clear();

            mEllipseData.MarkedForReset = false;
        }
    }
    
    Texture* Renderer2D::GetTextDebugTexture()
    {
        return mTextAPI.GetDebugTexture();
    }
}