//  SuperTuxKart - a fun racing game with go-kart
//  Copyright (C) 2014-2015 SuperTuxKart-Team
//
//  This program is free software; you can redistribute it and/or
//  modify it under the terms of the GNU General Public License
//  as published by the Free Software Foundation; either version 3
//  of the License, or (at your option) any later version.
//
//  This program is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU General Public License for more details.
//
//  You should have received a copy of the GNU General Public License
//  along with this program; if not, write to the Free Software
//  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

#include "ls_mesh.h"

#include "../config/user_config.h"
#include "central_settings.h"
#include "camera.h"
#include "glwrap.h"
#include "irr_driver.h"
#include "shaders.h"
#include "../modes/world.h"
#include "../maps/map.h"
#include "../utils/helpers.h"

#include <ISceneManager.h>
#include <IMaterialRenderer.h>


// ============================================================================
Material::ShaderType getMeshMaterialFromType(video::E_MATERIAL_TYPE material_type,
                                              video::E_VERTEX_TYPE tp,
                                              Material* material,
                                              Material* layer2_material)
{
    if (material_type == NULL)
    {
    Log::error("lsmesh","no material-type, shader = solid");
    return Material::SHADERTYPE_SOLID;
    }
    else
    {
    Log::info("lsmesh","material-type ok");

    }

    if (layer2_material != NULL &&
        layer2_material->getShaderType() == Material::SHADERTYPE_SPLATTING)
        return Material::SHADERTYPE_SPLATTING;

    switch (material->getShaderType())
    {
    default:
        return material->getShaderType();
    case Material::SHADERTYPE_SOLID:
        if (material_type == Shaders::getShader(ES_NORMAL_MAP))
            return Material::SHADERTYPE_NORMAL_MAP;
        else if (tp == video::EVT_2TCOORDS)
            return Material::SHADERTYPE_DETAIL_MAP;
        return Material::SHADERTYPE_SOLID;
    }
}   // getMeshMaterialFromType

// ----------------------------------------------------------------------------
TransparentMaterial getTransparentMaterialFromType(video::E_MATERIAL_TYPE type,
                                                   f32 MaterialTypeParam,
                                                   Material* material)
{
    if (type == Shaders::getShader(ES_DISPLACE))
        return TM_DISPLACEMENT;
    if (material->getShaderType() == Material::SHADERTYPE_ADDITIVE)
        return TM_ADDITIVE;
    return TM_DEFAULT;
}

// ----------------------------------------------------------------------------
video::E_VERTEX_TYPE getVTXTYPEFromStride(size_t stride)
{
    if (stride == sizeof(video::S3DVertex))
        return video::EVT_STANDARD;
    else if (stride == sizeof(video::S3DVertex2TCoords))
        return video::EVT_2TCOORDS;
    assert(stride == sizeof(video::S3DVertexTangents));
    return video::EVT_TANGENTS;
}   // getVTXTYPEFromStride

// ----------------------------------------------------------------------------
GLuint createVAO(GLuint vbo, GLuint idx, video::E_VERTEX_TYPE type)
{
    GLuint vao;
    glGenVertexArrays(1, &vao);
    glBindVertexArray(vao);
    assert(vbo);
    glBindBuffer(GL_ARRAY_BUFFER, vbo);
    switch (type)
    {
    case video::EVT_STANDARD:
        // Position
        glEnableVertexAttribArray(0);
        glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE,
                              getVertexPitchFromType(type), 0);
        // Normal
        glEnableVertexAttribArray(1);
        glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE,
                              getVertexPitchFromType(type), (GLvoid*)12);
        // Color
        glEnableVertexAttribArray(2);
        glVertexAttribPointer(2, 4, GL_UNSIGNED_BYTE, GL_TRUE,
                              getVertexPitchFromType(type), (GLvoid*)24);
        // Texcoord
        glEnableVertexAttribArray(3);
        glVertexAttribPointer(3, 2, GL_FLOAT, GL_FALSE,
                              getVertexPitchFromType(type), (GLvoid*)28);
        break;
    case video::EVT_2TCOORDS:
        // Position
        glEnableVertexAttribArray(0);
        glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE,
                              getVertexPitchFromType(type), 0);
        // Normal
        glEnableVertexAttribArray(1);
        glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE,
                              getVertexPitchFromType(type), (GLvoid*)12);
        // Color
        glEnableVertexAttribArray(2);
        glVertexAttribPointer(2, 4, GL_UNSIGNED_BYTE, GL_TRUE,
                              getVertexPitchFromType(type), (GLvoid*)24);
        // Texcoord
        glEnableVertexAttribArray(3);
        glVertexAttribPointer(3, 2, GL_FLOAT, GL_FALSE,
                              getVertexPitchFromType(type), (GLvoid*)28);
        // SecondTexcoord
        glEnableVertexAttribArray(4);
        glVertexAttribPointer(4, 2, GL_FLOAT, GL_FALSE,
                              getVertexPitchFromType(type), (GLvoid*)36);
        break;
    case video::EVT_TANGENTS:
        // Position
        glEnableVertexAttribArray(0);
        glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE,
                              getVertexPitchFromType(type), 0);
        // Normal
        glEnableVertexAttribArray(1);
        glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE,
                              getVertexPitchFromType(type), (GLvoid*)12);
        // Color
        glEnableVertexAttribArray(2);
        glVertexAttribPointer(2, 4, GL_UNSIGNED_BYTE, GL_TRUE,
                              getVertexPitchFromType(type), (GLvoid*)24);
        // Texcoord
        glEnableVertexAttribArray(3);
        glVertexAttribPointer(3, 2, GL_FLOAT, GL_FALSE,
                              getVertexPitchFromType(type), (GLvoid*)28);
        // Tangent
        glEnableVertexAttribArray(5);
        glVertexAttribPointer(5, 3, GL_FLOAT, GL_FALSE,
                              getVertexPitchFromType(type), (GLvoid*)36);
        // Bitangent
        glEnableVertexAttribArray(6);
        glVertexAttribPointer(6, 3, GL_FLOAT, GL_FALSE,
                              getVertexPitchFromType(type), (GLvoid*)48);
        break;
    default:
        assert(0 && "Wrong vertex type");
    }
    assert(idx);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, idx);
    return vao;
}   // createVAO

// ----------------------------------------------------------------------------
GLMesh allocateMeshBuffer(scene::IMeshBuffer* mb, const std::string& debug_name)
{
    GLMesh result = {};
    if (!mb)
        return result;
    result.mb = mb;

#ifdef DEBUG
    result.debug_name = debug_name;
#endif

    result.IndexCount = mb->getIndexCount();
    switch (mb->getIndexType())
    {
    case irr::video::EIT_16BIT:
    {
        result.IndexType = GL_UNSIGNED_SHORT;
        break;
    }
    case irr::video::EIT_32BIT:
    {
        result.IndexType = GL_UNSIGNED_INT;
        break;
    }
    default:
    {
        assert(0 && "Wrong index size");
    }
    }
    result.VAOType = mb->getVertexType();
    result.Stride = getVertexPitchFromType(result.VAOType);


    result.IndexCount = mb->getIndexCount();
    switch (mb->getPrimitiveType())
    {
    case scene::EPT_POINTS:
        result.PrimitiveType = GL_POINTS;
        break;
    case scene::EPT_TRIANGLE_STRIP:
        result.PrimitiveType = GL_TRIANGLE_STRIP;
        break;
    case scene::EPT_TRIANGLE_FAN:
        result.PrimitiveType = GL_TRIANGLE_FAN;
        break;
    case scene::EPT_LINES:
        result.PrimitiveType = GL_LINES;
        break;
    case scene::EPT_TRIANGLES:
        result.PrimitiveType = GL_TRIANGLES;
        break;
    case scene::EPT_POINT_SPRITES:
    case scene::EPT_LINE_LOOP:
    case scene::EPT_POLYGON:
    case scene::EPT_LINE_STRIP:
    case scene::EPT_QUAD_STRIP:
    case scene::EPT_QUADS:
        assert(0 && "Unsupported primitive type");
    }
    for (unsigned i = 0; i < 8; i++)
        result.textures[i] = mb->getMaterial().getTexture(i);
    result.TextureMatrix = 0;
    result.VAOType = mb->getVertexType();
    return result;
}   // allocateMeshBuffer

// ----------------------------------------------------------------------------
static size_t getUnsignedSize(unsigned tp)
{
    switch (tp)
    {
    case GL_UNSIGNED_SHORT:
        return sizeof(u16);
    case GL_UNSIGNED_INT:
        return sizeof(u32);
    default:
        assert(0 && "Unsupported index type");
        return 0;
    }
}   // getUnsignedSize

// ----------------------------------------------------------------------------
void fillLocalBuffer(GLMesh &mesh, scene::IMeshBuffer* mb)
{
    glBindVertexArray(0);
    glGenBuffers(1, &(mesh.vertex_buffer));
    glGenBuffers(1, &(mesh.index_buffer));

    glBindBuffer(GL_ARRAY_BUFFER, mesh.vertex_buffer);
    const void* vertices = mb->getVertices();
    const u32 vertexCount = mb->getVertexCount();

    const c8* vbuf = static_cast<const c8*>(vertices);
    glBufferData(GL_ARRAY_BUFFER, vertexCount * mesh.Stride, vbuf,
                 GL_STREAM_DRAW);
    assert(vertexCount);

    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mesh.index_buffer);
    const void* indices = mb->getIndices();
    mesh.IndexCount = mb->getIndexCount();

    glBufferData(GL_ELEMENT_ARRAY_BUFFER,
                 mesh.IndexCount * getUnsignedSize(mesh.IndexType),
                 indices, GL_STREAM_DRAW);

    glBindBuffer(GL_ARRAY_BUFFER, 0);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
}   // fillLocalBuffer

// ----------------------------------------------------------------------------
core::matrix4 computeMVP(const core::matrix4 &model_matrix)
{
/*
    core::matrix4 ModelViewProjectionMatrix = irr_driver->getProjMatrix();
    ModelViewProjectionMatrix *= irr_driver->getViewMatrix();
    ModelViewProjectionMatrix *= model_matrix;
    return ModelViewProjectionMatrix;
    */
    Log::fatal("ls_mesh", "irr-driver no getprojmatrix");
}   // computeMVP

// ----------------------------------------------------------------------------
core::vector3df getWindDir()
{
/*
    const float time = irr_driver->getDevice()->getTimer()->getTime() / 1000.0f;
    GrassShaderProvider *gsp =
        (GrassShaderProvider *)Shaders::getCallback(ES_GRASS);
    return (gsp->getSpeed() * cos(time)) * vector3df(1., 0., 0.);
    */
}   // getWindDir

// ----------------------------------------------------------------------------
bool isObject(video::E_MATERIAL_TYPE type)
{
    if (type == Shaders::getShader(ES_OBJECTPASS))
        return true;
    if (type == Shaders::getShader(ES_OBJECTPASS_REF))
        return true;
    if (type == Shaders::getShader(ES_OBJECTPASS_RIMLIT))
        return true;
    if (type == Shaders::getShader(ES_NORMAL_MAP))
        return true;
    if (type == Shaders::getShader(ES_SPHERE_MAP))
        return true;
    if (type == Shaders::getShader(ES_SPLATTING))
        return true;
    if (type == Shaders::getShader(ES_GRASS))
        return true;
    if (type == Shaders::getShader(ES_GRASS_REF))
        return true;
    if (type == Shaders::getShader(ES_DISPLACE))
        return true;
    if (type == Shaders::getShader(ES_OBJECT_UNLIT))
        return true;
    if (type == video::EMT_TRANSPARENT_ALPHA_CHANNEL)
        return true;
    if (type == video::EMT_ONETEXTURE_BLEND)
        return true;
    if (type == video::EMT_TRANSPARENT_ADD_COLOR)
        return true;
    if (type == video::EMT_SOLID)
        return true;
    if (type == video::EMT_LIGHTMAP_LIGHTING)
        return true;
    if (type == video::EMT_TRANSPARENT_ALPHA_CHANNEL_REF)
        return true;
    return false;
}   // isObject

// ----------------------------------------------------------------------------
static void setTexture(GLMesh &mesh, unsigned i, bool is_srgb,
                       const std::string &mat_name)
{
    if (!mesh.textures[i])
    {
        Log::error("STKMesh", "Missing texture %d for material %s", i,
                   mat_name.c_str());
        // use unicolor texture to replace missing texture
        mesh.textures[i] =
                      getUnicolorTexture(video::SColor(255, 127, 127, 127));
    }
    compressTexture(mesh.textures[i], is_srgb);
    if (CVS->isAZDOEnabled())
    {
        if (!mesh.TextureHandles[i])
        {
            mesh.TextureHandles[i] = glGetTextureSamplerHandleARB(
                getTextureGLuint(mesh.textures[i]),
                Shaders::ObjectPass1Shader::getInstance()->m_sampler_ids[0]);
        }
        if (!glIsTextureHandleResidentARB(mesh.TextureHandles[i]))
            glMakeTextureHandleResidentARB(mesh.TextureHandles[i]);
    }
}   // setTexture

// ----------------------------------------------------------------------------
static std::string getShaderTypeName(Material::ShaderType mat)
{
    switch (mat)
    {
    default:
    case Material::SHADERTYPE_SOLID:
        return "Solid";
    case Material::SHADERTYPE_ALPHA_TEST:
        return "Alpha Test";
    case Material::SHADERTYPE_VEGETATION:
        return "Grass";
    case Material::SHADERTYPE_SPHERE_MAP:
        return "Sphere Map";
    case Material::SHADERTYPE_SOLID_UNLIT:
        return "Unlit";
    case Material::SHADERTYPE_DETAIL_MAP:
        return "Detail";
    case Material::SHADERTYPE_NORMAL_MAP:
        return "Normal";
    case Material::SHADERTYPE_SPLATTING:
        return "Splatting";
    }
}   // getShaderTypeName

// ----------------------------------------------------------------------------
void initTextures(GLMesh &mesh, Material::ShaderType mat)
{
    switch (mat)
    {
    default:
    case Material::SHADERTYPE_SOLID:
    case Material::SHADERTYPE_ALPHA_TEST:
    case Material::SHADERTYPE_VEGETATION:
    case Material::SHADERTYPE_SPHERE_MAP:
    case Material::SHADERTYPE_SOLID_UNLIT:
        setTexture(mesh, 0, true, getShaderTypeName(mat));
        setTexture(mesh, 1, false, getShaderTypeName(mat));
        break;
    case Material::SHADERTYPE_DETAIL_MAP:
    case Material::SHADERTYPE_NORMAL_MAP:
        setTexture(mesh, 0, true, getShaderTypeName(mat));
        setTexture(mesh, 1, false, getShaderTypeName(mat));
        setTexture(mesh, 2, false, getShaderTypeName(mat));
        break;
    case Material::SHADERTYPE_SPLATTING:
        setTexture(mesh, 0, true, getShaderTypeName(mat));
        setTexture(mesh, 1, false, getShaderTypeName(mat));
        setTexture(mesh, 2, true, getShaderTypeName(mat));
        setTexture(mesh, 3, true, getShaderTypeName(mat));
        setTexture(mesh, 4, true, getShaderTypeName(mat));
        setTexture(mesh, 5, true, getShaderTypeName(mat));
        setTexture(mesh, 6, false, getShaderTypeName(mat));
        break;
    }
}   // initTextures

// ----------------------------------------------------------------------------
void initTexturesTransparent(GLMesh &mesh)
{
    if (!mesh.textures[0])
    {
        Log::fatal("STKMesh", "Missing texture for material transparent");
        return;
    }
    compressTexture(mesh.textures[0], true);
    if (CVS->isAZDOEnabled())
    {
        if (!mesh.TextureHandles[0])
        {
            mesh.TextureHandles[0] = glGetTextureSamplerHandleARB(
                getTextureGLuint(mesh.textures[0]),
                Shaders::ObjectPass1Shader::getInstance()->m_sampler_ids[0]);
        }
        if (!glIsTextureHandleResidentARB(mesh.TextureHandles[0]))
            glMakeTextureHandleResidentARB(mesh.TextureHandles[0]);
    }
}   // initTexturesTransparent
