#include <Redacted3D/engine/engine.h>
#include <Redacted3D/types/vertex.h>
#include <Redacted3D/engine/collision.h>
#include <cmath>
#include <Collage/types/model.h>
void VertexArray::load (const std::string& filename) {
    auto* m = new Model(filename);
    vertices = m->getVertices();
    indices = m->getIndices();
    texCoords = m->getTextureInformation()[0].textureCoordinates;
    delete m;

    if (engine->useVBO) {
        if (vbo == 0) {
            //Copy Vertices to vram.
            glGenBuffers(1, &vbo);
            glBindBuffer(GL_ARRAY_BUFFER, vbo);
            glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat)*vertices.size()*3, vertices.data(), GL_STATIC_DRAW); //GL_STATIC_DRAW means the VBO won't be changed atall.
            glBindBuffer(GL_ARRAY_BUFFER, 0);

            //Copy Texture Coordinates to vram.
            glGenBuffers(1, &tbo);
            glBindBuffer(GL_ARRAY_BUFFER, tbo);
            glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat)*texCoords.size()*2, texCoords.data(), GL_STATIC_DRAW);
            glBindBuffer(GL_ARRAY_BUFFER, 0);

            //copy Indices to vram.
            glGenBuffers(1, &ebo);
            glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo);
            glBufferData(GL_ELEMENT_ARRAY_BUFFER, (indices.size() * sizeof(GLuint)), indices.data(), GL_STATIC_DRAW);
            glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
        }
    }

    //Cached OBB.
    this->cachedOBB = Collision::genMinimallyEnclosingOBB(this, {0, 0, 0});
}

void VertexArray::draw() {
    if (!indices.empty()) {
        glEnable(GL_TEXTURE_2D);
        glEnableClientState(GL_VERTEX_ARRAY);
        glEnableClientState(GL_TEXTURE_COORD_ARRAY);
        if (engine->useVBO && vbo != 0 && tbo != 0 && ebo != 0) {
            //Vertices
            glBindBuffer(GL_ARRAY_BUFFER, vbo);
            glVertexPointer(3, GL_FLOAT, 0, nullptr);

            //Texture Coordinates
            glBindBuffer(GL_ARRAY_BUFFER, tbo);
            glTexCoordPointer(2, GL_FLOAT, 0, nullptr);

            //Indices
            glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo);
            glDrawElements(GL_TRIANGLES, indices.size(), GL_UNSIGNED_INT, nullptr);

            //Reset selected buffer.
            glBindBuffer(GL_ARRAY_BUFFER, 0);
            glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);

            glDisableClientState(GL_TEXTURE_COORD_ARRAY);
            glDisableClientState(GL_VERTEX_ARRAY);
            glDisable(GL_TEXTURE_2D);
            return;
        }

        //Immediate rendering.
        glVertexPointer(3, GL_FLOAT, sizeof(Vertex), &vertices[0].x), //Vertices
        glTexCoordPointer(2, GL_FLOAT, sizeof(TextureCoordinate), &texCoords[0].x), //Texture Coordinates
        glDrawElements(GL_TRIANGLES, indices.size(), GL_UNSIGNED_INT, &indices[0]); //Indices

        glDisableClientState(GL_TEXTURE_COORD_ARRAY);
        glDisableClientState(GL_VERTEX_ARRAY);
        glDisable(GL_TEXTURE_2D);
    }
}

void VertexArray::drawWireframe() {
    glBegin(GL_LINES);
    for (size_t i = 0; i < indices.size(); i += 2) {
        if (i + 1 < indices.size()) {
            int index1 = static_cast<int>(indices[i]);
            int index2 = static_cast<int>(indices[i + 1]);

            if (index1 >= 0 && index1 < vertices.size() && index2 >= 0 && index2 < vertices.size())
                glVertex3f(vertices[index1].x, vertices[index1].y, vertices[index1].z),
                glVertex3f(vertices[index2].x, vertices[index2].y, vertices[index2].z);
        }
    }
    glEnd();
}

void VertexArray::erase() const {
    if (!usedBy.empty())
        engine->setError(ENGINE_ERROR_CODE::VERTEX_ARRAY_ERASED_WHILE_IN_USE, false);

    for (int i = 0; i < engine->world->geometryList.size(); i++)
        if (engine->world->geometryList[i] == this)
            engine->world->geometryList.erase(engine->world->geometryList.begin() + i);
    delete this;
}

VertexArray::VertexArray(Entity* entity, const std::string& filename, bool storeOnGeometryList) {
    load(filename);
    usedBy.push_back(entity);
    if (storeOnGeometryList)
        engine->world->geometryList.push_back(new VertexArray(*this));
}

OBB VertexArray::getCachedOBB(const Matrix3x3& rotationMatrix) const {
    Vector3 axis0 = Vector3(rotationMatrix[0][0], rotationMatrix[1][0], rotationMatrix[2][0]);
    Vector3 axis1 = Vector3(rotationMatrix[0][1], rotationMatrix[1][1], rotationMatrix[2][1]);
    Vector3 axis2 = Vector3(rotationMatrix[0][2], rotationMatrix[1][2], rotationMatrix[2][2]);
    return {cachedOBB.pos, cachedOBB.r, axis0, axis1, axis2};
}

OBB VertexArray::getCachedOBB(const Vector3& rotation) const {
    Matrix3x3 rotationMatrix = Matrix3x3::FromQuat(Quaternion((EulerAngle) rotation));
    Vector3 axis0 = {rotationMatrix[0][0], rotationMatrix[1][0], rotationMatrix[2][0]};
    Vector3 axis1 = {rotationMatrix[0][1], rotationMatrix[1][1], rotationMatrix[2][1]};
    Vector3 axis2 = {rotationMatrix[0][2], rotationMatrix[1][2], rotationMatrix[2][2]};
    return {cachedOBB.pos, cachedOBB.r, axis0, axis1, axis2};
}

OBB VertexArray::getCachedOBB(const EulerAngle& rotation) const {
    Matrix3x3 rotationMatrix = Matrix3x3::FromQuat(Quaternion( rotation));
    Vector3 axis0 = {rotationMatrix[0][0], rotationMatrix[1][0], rotationMatrix[2][0]};
    Vector3 axis1 = {rotationMatrix[0][1], rotationMatrix[1][1], rotationMatrix[2][1]};
    Vector3 axis2 = {rotationMatrix[0][2], rotationMatrix[1][2], rotationMatrix[2][2]};
    return {cachedOBB.pos, cachedOBB.r, axis0, axis1, axis2};
}
