#include "utils.h"
#include "glm/ext.hpp"
//#include "scene/scenetree.h"
#include "iostream"
#include "sparrowshell/scriptnode.h"
#include "transform.h"
#include "loader.h"
#include "SparrowRenderer/mesh.h"
#include "SparrowRenderer/parametricmesh.h"
#include "SparrowRenderer/pbrmaterial.h"
#include "SparrowRenderer/image.h"
#include "SparrowRenderer/texture.h"

#include <btBulletCollisionCommon.h>
#include <btBulletDynamicsCommon.h>

std::vector<std::string> utils::split(const std::string &line, char sep){
    std::vector<std::string> tokens;
    std::size_t start=0, end=0;
    while((end = line.find(sep,start)) != std::string::npos){
        tokens.push_back(line.substr(start,end-start));
        start=end+1;
    }
    tokens.push_back(line.substr(start));
    return tokens;
}

void utils::initScriptingUtilsFunctions(ScriptNode* scriptNode)
{
    scriptNode->getScriptEngine().new_usertype<Transform>("Transform",
                                                          "getPosition",&Transform::getPosition,
                                                          "setPosition",&Transform::setPosition,
                                                          "getRotation",&Transform::getRotation,
                                                          "setRotation",&Transform::setRotation);
}

void utils::initStandardScene()
{
    /*
     * GUI
     *
     * ENTITIES
     *      PLAYER
     *      MOBS
     *
     * OBJECTS
     *      ITEMS // the player can interact with it, it is permanent and is saved in the map
     *      GIBS // only for decorative purposes and ephemere
     *      PROJECTILES // ballistic object
     *
     * MAP
     *      ENVIRONMENT
     *          FOG
     *          SUN
     *          SKY
     *      GEOMETRY
     *          CHUNK_NODES // procedural geometry
     *          MODEL_NODES // modelized geometry
     *
     *
     *
     *
     */
}

/*
void utils::setPosition2D(MeshNode *mnode, glm::vec2 pos){
    const glm::mat4 &tr = mnode->getTransform();
    mnode->setTransform(glm::translate(tr,glm::vec3(pos.x,pos.y,0) - glm::vec3(tr[3])));
}

void utils::resize2D(MeshNode *mnode, glm::vec2 dim, glm::vec2 new_dim){
    scale2D(mnode,glm::vec2(new_dim / dim));
}

void utils::scale2D(MeshNode* mnode, glm::vec2 ratio){
    mnode->setTransform(glm::scale(mnode->getTransform(), glm::vec3(ratio.x,ratio.y,1)));
}

void utils::rotate2D(MeshNode* mnode, glm::vec2 center, float angle){
    glm::mat4 tr = mnode->getTransform();
    //glm::vec3 pos(tr[3]);
    //tr = glm::translate(tr,pos-glm::vec3(center.x,center.y,0));
    tr = glm::rotate(mnode->getTransform(),angle,glm::vec3(0,0,1));
    //tr = glm::translate(tr,glm::vec3(center.x,center.y,0)-pos );
    mnode->setTransform(tr);
}

void setDepth2D(MeshNode* mnode, float depth){
    //Mesh* mesh; //= getMesh here
    //mesh.setDepth(depth);
}
*/

btRigidBody* utils::buildStaticCollider(GeometryNode* node)
{
    btIndexedMesh *bulletMesh = new btIndexedMesh();
    Mesh* m = node->mesh;
    // vertices
    bulletMesh->m_numVertices = m->m_positions3D.size();
    bulletMesh->m_vertexBase = (unsigned char*)(m->m_positions3D.data());
    bulletMesh->m_vertexStride = sizeof(glm::vec3);
    bulletMesh->m_vertexType = PHY_FLOAT;
    // indices
    bulletMesh->m_numTriangles = m->m_indices.size()/3;
    bulletMesh->m_triangleIndexBase = (unsigned char*)(m->m_indices.data());
    bulletMesh->m_triangleIndexStride = 3*sizeof(GLuint);
    bulletMesh->m_indexType = PHY_INTEGER;

    // building bullet rigidbody
    btTriangleIndexVertexArray* indexArray = new btTriangleIndexVertexArray();
    indexArray->addIndexedMesh(*bulletMesh, PHY_INTEGER);
    btBvhTriangleMeshShape *shape = new btBvhTriangleMeshShape(indexArray, true);
    shape->setLocalScaling(btVector3(node->modelMatrix[0].x, node->modelMatrix[1].y, node->modelMatrix[2].z));

    btTransform transform;
    transform.setIdentity();
    transform.setOrigin(btVector3(node->modelMatrix[3].x, node->modelMatrix[3].y, node->modelMatrix[3].z));
    // IMPORTANT ! collisions on static meshes does not work if the transform is modified after the rigidbody construction
    btRigidBody* rigidBody = new btRigidBody(0., nullptr, shape);
    rigidBody->setWorldTransform(transform);
    return rigidBody;
}

SceneNode* utils::createTerrain(const std::string& heightMap, const std::string& colorMap, float maxHeight)
{
    GridGenerator grid;
    PBRMaterial* mat = new PBRMaterial();
    Image* heightMapImg = Loader::loadImage(heightMap, 24);
    if(heightMapImg == nullptr)
        return nullptr;
    Image* colorMapImg = Loader::loadImage(colorMap, 24);
    if(colorMapImg == nullptr)
        return nullptr;
    mat->setTexture(PBRMaterial::ALBEDO_SLOT, new Texture(colorMapImg));
    mat->roughness = 0.9f;
    mat->metallic = 0.1f;
    int size = std::min(heightMapImg->width, heightMapImg->height);
    Mesh* mesh = grid.generateParametricMesh(mat, size-1, size-1, size);
    for(int x=0; x<heightMapImg->width; ++x)
    {
        for(int y=0; y<heightMapImg->height; ++y)
        {
            int height = heightMapImg->pixels[(y*heightMapImg->width + x)*3];
            mesh->m_positions3D[x*heightMapImg->width + y].y = height*maxHeight/255;
        }
    }
    delete heightMapImg;
    delete colorMapImg;
    SceneNode* node = new SceneNode();
    mesh->computeNormals();
    mesh->mergeVertices();
    mesh->initGL();
    node->setMesh(mesh);
    return node;
}
