//##################################################################################################
//
//   Custom Visualization Core library
//   Copyright (C) 2011-2012 Ceetron AS
//    
//   This library 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 library 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 at <<http://www.gnu.org/licenses/gpl.html>> 
//   for more details. 
//
//##################################################################################################

#include "cvfBase.h"
#include "cvfDrawableVectors.h"
#include "cvfOpenGL.h"
#include "cvfOpenGLResourceManager.h"
#include "cvfBufferObjectManaged.h"
#include "cvfDrawableGeo.h"
#include "cvfQuat.h"
#include "cvfShaderProgram.h"
#include "cvfUniform.h"
#include "cvfPrimitiveSetIndexedUShort.h"
#include "cvfRay.h"

#ifndef CVF_OPENGL_ES
#include "cvfRenderState_FF.h"
#endif

namespace cvf {


//==================================================================================================
///
/// \class cvf::DrawableVectors
/// \ingroup Render
///
/// DrawableVectors implements drawing of vector arrows.
/// 
//==================================================================================================

//--------------------------------------------------------------------------------------------------
/// Constructor to use when using fixed function rendering
//--------------------------------------------------------------------------------------------------
DrawableVectors::DrawableVectors()
:   m_singleColor(Color3f(Color3::WHITE)),
    m_renderWithVBO(true)
{
}


//--------------------------------------------------------------------------------------------------
///  Constructor to use when using shader based rendering
//--------------------------------------------------------------------------------------------------
DrawableVectors::DrawableVectors(String vectorMatrixUniformName, String colorUniformName)
:   m_vectorMatrixUniformName(vectorMatrixUniformName),
    m_colorUniformName(colorUniformName),
    m_singleColor(Color3f(Color3::WHITE)),
    m_renderWithVBO(true)
{
}


//--------------------------------------------------------------------------------------------------
/// 
//--------------------------------------------------------------------------------------------------
DrawableVectors::~DrawableVectors()
{
    releaseBufferObjectsGPU();
}


//--------------------------------------------------------------------------------------------------
/// 
//--------------------------------------------------------------------------------------------------
void DrawableVectors::setGlyph(UShortArray* triangles, Vec3fArray* vertices)
{
    m_vectorGlyph = new DrawableGeo;
    m_vectorGlyphPrimSet = new PrimitiveSetIndexedUShort(PT_TRIANGLES);

    m_vectorGlyphPrimSet->setIndices(triangles);
    m_vectorGlyph->setVertexArray(vertices);
    m_vectorGlyph->addPrimitiveSet(m_vectorGlyphPrimSet.p());
    m_vectorGlyph->computeNormals();

    if (m_renderWithVBO)
    {
        m_vectorGlyph->setRenderMode(DrawableGeo::BUFFER_OBJECT);
    }

    releaseBufferObjectsGPU();
}


//--------------------------------------------------------------------------------------------------
/// 
//--------------------------------------------------------------------------------------------------
void DrawableVectors::setSingleColor(Color3f color)
{
    m_singleColor = color;
}


//--------------------------------------------------------------------------------------------------
/// 
//--------------------------------------------------------------------------------------------------
void DrawableVectors::setVectors(Vec3fArray* vertexArray, Vec3fArray* vectorArray)
{
    CVF_ASSERT(vertexArray && vectorArray);
    CVF_ASSERT(vertexArray->size() && vectorArray->size());

    m_vertexArray = vertexArray;
    m_vectorArray = vectorArray;

    m_boundingBox.add(*m_vertexArray);
}


//--------------------------------------------------------------------------------------------------
/// Set color of each corresponding vector
///
/// \param vectorColorArray Array of colors to be mapped one-by-one to the vector array
/// \param colorUniformName Name of uniform when used by a shader program. Ignored if not.
/// 
/// \sa setVectors
//--------------------------------------------------------------------------------------------------
void DrawableVectors::setColors(Color3fArray* vectorColorArray) 
{
    m_colorArray = vectorColorArray;
}


//--------------------------------------------------------------------------------------------------
/// 
//--------------------------------------------------------------------------------------------------
size_t DrawableVectors::vectorCount() const
{
    CVF_ASSERT(m_vertexArray->size() == m_vectorArray->size());

    return m_vertexArray->size();
}


//--------------------------------------------------------------------------------------------------
/// 
//--------------------------------------------------------------------------------------------------
void DrawableVectors::render(OpenGLContext* oglContext, ShaderProgram* shaderProgram, const MatrixState&)
{
    CVF_CALLSITE_OPENGL(oglContext);

    CVF_ASSERT(shaderProgram);
    CVF_ASSERT(shaderProgram->isProgramUsed(oglContext));
    CVF_ASSERT(m_vertexArray->size() == m_vectorArray->size());
    CVF_ASSERT(m_colorArray.isNull() || (m_colorArray->size() == m_vectorArray->size()));
    CVF_ASSERT(m_vectorGlyph.notNull());
    CVF_ASSERT(m_vectorGlyph->primitiveSetCount() == 1);

    // Setup Vertex Arrays/vbos 
    const GLvoid* ptrOrOffset = 0;

    if (m_renderWithVBO && m_glyphVerticesAndNormalsBO.notNull() && m_glyphVerticesAndNormalsBO->isUploaded())
    {
        // Bind VBO for vertex and normal data
        m_glyphVerticesAndNormalsBO->bindBuffer(oglContext);

        glVertexAttribPointer(ShaderProgram::NORMAL, 3, GL_FLOAT, GL_FALSE, sizeof(float)*6, (void*)(sizeof(float)*3));
        glVertexAttribPointer(ShaderProgram::VERTEX, 3, GL_FLOAT, GL_FALSE, sizeof(float)*6, 0);
        glEnableVertexAttribArray(ShaderProgram::NORMAL);
        glEnableVertexAttribArray(ShaderProgram::VERTEX);

        m_indicesBO->bindBuffer(oglContext);
    }
    else
    {
        glBindBuffer(GL_ARRAY_BUFFER, 0);
        glVertexAttribPointer(ShaderProgram::NORMAL, 3, GL_FLOAT, GL_FALSE, 0, m_vectorGlyph->normalArray()->ptr()->ptr());
        glEnableVertexAttribArray(ShaderProgram::NORMAL);
        glVertexAttribPointer(ShaderProgram::VERTEX, 3, GL_FLOAT, GL_FALSE, 0, m_vectorGlyph->vertexArray()->ptr()->ptr());
        glEnableVertexAttribArray(ShaderProgram::VERTEX);

        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
        ptrOrOffset = m_vectorGlyphPrimSet->indices()->ptr();
    }

    // Must use manual uniform setting, as setting the uniform through the Uniform* class requires a lookup for location
    // every time, and this is always the same
    GLint vectorMatrixUniformLocation = shaderProgram->uniformLocation(m_vectorMatrixUniformName.toAscii().ptr());
    CVF_ASSERT(vectorMatrixUniformLocation != -1);
    GLint colorUniformLocation = shaderProgram->uniformLocation(m_colorUniformName.toAscii().ptr());
    CVF_ASSERT(colorUniformLocation != -1);

#ifndef CVF_OPENGL_ES
    uint minIndex = m_vectorGlyphPrimSet->minIndex();
    uint maxIndex = m_vectorGlyphPrimSet->maxIndex();
#endif
    GLsizei indexCount = static_cast<GLsizei>(m_vectorGlyphPrimSet->indexCount());

    // Set the single color to use
    if (m_colorArray.isNull())
    {
        glUniform3fv(colorUniformLocation, 1, m_singleColor.ptr());
    }

    float vectorMat[16];
    size_t numVectors = m_vectorArray->size();
    size_t i;
    for (i = 0; i < numVectors; i++)
    {
        // Compute the transformation matrix
        vectorMatrix(i, vectorMat);

        // Set this as a uniform to the shader program
        glUniformMatrix4fv(vectorMatrixUniformLocation, 1, GL_FALSE, vectorMat); 

        if (m_colorArray.notNull())
        {
            glUniform3fv(colorUniformLocation, 1, m_colorArray->get(i).ptr());
        }

        // Draw the arrow
#ifdef CVF_OPENGL_ES
        glDrawElements(GL_TRIANGLES, indexCount, GL_UNSIGNED_SHORT, ptrOrOffset);
#else
        glDrawRangeElements(GL_TRIANGLES, minIndex, maxIndex, indexCount, GL_UNSIGNED_SHORT, ptrOrOffset);
#endif
    }

    // Cleanup
    glDisableVertexAttribArray(ShaderProgram::VERTEX);
    glDisableVertexAttribArray(ShaderProgram::NORMAL);

    CVF_CHECK_OGL(oglContext);

    // Things to consider for performance:

    // 1) Uniform arrays evt. Uniform buffer objects
    // 2) Texture
    // 3) Texture array

    // GL_ARB_draw_instanced();
    // glDrawElementsInstanced
}


//--------------------------------------------------------------------------------------------------
/// 
//--------------------------------------------------------------------------------------------------
void DrawableVectors::renderFixedFunction(OpenGLContext* oglContext, const MatrixState& matrixState)
{
#ifdef CVF_OPENGL_ES
    CVF_FAIL_MSG("Not supported on OpenGL ES");
#else    
    CVF_ASSERT(oglContext);
    CVF_ASSERT(m_vertexArray->size() == m_vectorArray->size());
    CVF_ASSERT(m_vectorGlyph.notNull());

    glEnable(GL_NORMALIZE);
    glColorMaterial(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE);
    glEnable(GL_COLOR_MATERIAL);

    if (m_colorArray.isNull())
    {
        glColor3fv(m_singleColor.ptr());
    }

    float vectorMat[16];
    size_t numVectors = m_vectorArray->size();
    size_t i;
    for (i = 0; i < numVectors; i++)
    {
        // Compute/retrieve "matrix"
        vectorMatrix(i, vectorMat);

        glPushMatrix();
        glMultMatrixf(vectorMat);

        if (m_colorArray.notNull())
        {
             glColor3fv(m_colorArray->get(i).ptr());
        }

        // Draw the geometry
        m_vectorGlyph->renderFixedFunction(oglContext, matrixState);

        glPopMatrix();
    }

    glDisable(GL_NORMALIZE);

    CVF_CHECK_OGL(oglContext);
#endif  // CVF_OPENGL_ES
}


//--------------------------------------------------------------------------------------------------
/// 
//--------------------------------------------------------------------------------------------------
void DrawableVectors::renderImmediateMode(OpenGLContext* oglContext, const MatrixState& matrixState)
{
#ifdef CVF_OPENGL_ES
    CVF_FAIL_MSG("Not supported on OpenGL ES");
#else    
    CVF_ASSERT(oglContext);
    CVF_ASSERT(m_vertexArray->size() == m_vectorArray->size());
    CVF_ASSERT(m_vectorGlyph.notNull());

    glEnable(GL_NORMALIZE);
    glColorMaterial(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE);
    glEnable(GL_COLOR_MATERIAL);

    if (m_colorArray.isNull())
    {
        glColor3fv(m_singleColor.ptr());
    }

    float vectorMat[16];
    size_t numVectors = m_vectorArray->size();
    size_t i;
    for (i = 0; i < numVectors; i++)
    {
        // Compute/retrieve "matrix"
        vectorMatrix(i, vectorMat);

        glPushMatrix();
        glMultMatrixf(vectorMat);

        if (m_colorArray.notNull())
        {
            glColor3fv(m_colorArray->get(i).ptr());
        }

        // Draw the geometry
        m_vectorGlyph->renderImmediateMode(oglContext, matrixState);

        glPopMatrix();
    }

    glDisable(GL_NORMALIZE);

    CVF_CHECK_OGL(oglContext);
#endif  // CVF_OPENGL_ES
}


//--------------------------------------------------------------------------------------------------
/// 
//--------------------------------------------------------------------------------------------------
void DrawableVectors::createUploadBufferObjectsGPU(OpenGLContext* oglContext)
{
    if (!m_renderWithVBO || m_vertexArray->size() == 0 || m_vectorGlyph.isNull() || m_vectorGlyphPrimSet.isNull())
    {
        return;
    }

    if (m_glyphVerticesAndNormalsBO.isNull() || !m_glyphVerticesAndNormalsBO->isUploaded())
    {
        // Build a interleaved VBO for glyphs vertices and normals to get better performance 
        // during the main shader based draw path
        size_t numVertices = m_vectorGlyph->vertexArray()->size();

        Vec3fArray data;
        data.reserve(numVertices*2);
        size_t i;
        for (i = 0; i < numVertices; i++)
        {
            data.add(m_vectorGlyph->vertexArray()->get(i));
            data.add(m_vectorGlyph->normalArray()->get(i));
        }

        GLuint uiSizeInBytes = static_cast<GLuint>(data.size()*3*sizeof(float));
        m_glyphVerticesAndNormalsBO = oglContext->resourceManager()->getOrCreateManagedBufferObject(oglContext, GL_ARRAY_BUFFER, uiSizeInBytes, data.ptr()->ptr());
    }


    if (m_indicesBO.isNull() || !m_indicesBO->isUploaded())
    {
        const UShortArray* indices = m_vectorGlyphPrimSet->indices();
        size_t numIndices = indices->size();
        if (numIndices > 0) 
        {
            GLuint uiSizeInBytes = static_cast<GLuint>(numIndices*sizeof(GLushort));
            m_indicesBO = oglContext->resourceManager()->getOrCreateManagedBufferObject(oglContext, GL_ELEMENT_ARRAY_BUFFER, uiSizeInBytes, indices->ptr());
            CVF_CHECK_OGL(oglContext);
        }
    }
}


//--------------------------------------------------------------------------------------------------
/// 
//--------------------------------------------------------------------------------------------------
void DrawableVectors::releaseBufferObjectsGPU()
{
    m_glyphVerticesAndNormalsBO = NULL;

    if (m_vectorGlyph.notNull())
    {
        m_vectorGlyph->releaseBufferObjectsGPU();
    }
}


//--------------------------------------------------------------------------------------------------
/// 
//--------------------------------------------------------------------------------------------------
size_t DrawableVectors::vertexCount() const
{
    if (m_vectorGlyph.isNull())
    {
        return 0;
    }

    return m_vectorGlyph->vertexCount()*vectorCount();
}


//--------------------------------------------------------------------------------------------------
/// 
//--------------------------------------------------------------------------------------------------
size_t DrawableVectors::triangleCount() const
{
    if (m_vectorGlyph.isNull())
    {
        return 0;
    }

    return m_vectorGlyph->triangleCount()*vectorCount();
}


//--------------------------------------------------------------------------------------------------
/// 
//--------------------------------------------------------------------------------------------------
size_t DrawableVectors::faceCount() const
{
    if (m_vectorGlyph.isNull())
    {
        return 0;
    }

    return m_vectorGlyph->faceCount()*vectorCount();
}


//--------------------------------------------------------------------------------------------------
/// 
//--------------------------------------------------------------------------------------------------
BoundingBox DrawableVectors::boundingBox() const
{
    return m_boundingBox;
}


//--------------------------------------------------------------------------------------------------
/// 
//--------------------------------------------------------------------------------------------------
bool DrawableVectors::rayIntersectCreateDetail(const Ray& ray, Vec3d* intersectionPoint, ref<HitDetail>* hitDetail) const
{
    CVF_UNUSED(ray);
    CVF_UNUSED(intersectionPoint);
    CVF_UNUSED(hitDetail);

    return false;
}


//--------------------------------------------------------------------------------------------------
/// 
//--------------------------------------------------------------------------------------------------
void DrawableVectors::vectorMatrix(size_t vectorIndex, float vectorMatArray[16])
{
    const Vec3f& anchorPt = m_vertexArray->get(vectorIndex);
    Vec3f vec = m_vectorArray->get(vectorIndex);
    float length = vec.length();

    Quatf rotQuat;

    // This should really be a ratio eval based on length of vector
    // Copied from VTOVectorDrawer.cpp. TODO: refactor
    static const float TINY_VEC_COMP = 0.0000000001f;
    bool negZOnly = vec.z() < 0.0f && Math::abs(vec.x()) < TINY_VEC_COMP && Math::abs(vec.y()) < TINY_VEC_COMP;
    
    if (negZOnly) 
    {
        rotQuat.set(0.0f, 1.0f, 0.0f, 0.0f);
    }
    else
    {
        const Vec3f s = Vec3f::Z_AXIS;
        Vec3f d = vec/length;

        Vec3f v = s + d;
        Vec3f cross = v ^ d;
        float dot = v*d;

        rotQuat.set(cross.x(), cross.y(), cross.z(), dot);
    }

    float Nq = rotQuat.x()*rotQuat.x() + rotQuat.y()*rotQuat.y() + rotQuat.z()*rotQuat.z() + rotQuat.w()*rotQuat.w();
    float s = (Nq > 0.0f) ? (2.0f/Nq) : 0.0f;
    float xs = rotQuat.x()*s;	      
    float ys = rotQuat.y()*s;
    float zs = rotQuat.z()*s;
    float wx = rotQuat.w()*xs;
    float wy = rotQuat.w()*ys;
    float wz = rotQuat.w()*zs;
    float xx = rotQuat.x()*xs;
    float xy = rotQuat.x()*ys;
    float xz = rotQuat.x()*zs;
    float yy = rotQuat.y()*ys;
    float yz = rotQuat.y()*zs;
    float zz = rotQuat.z()*zs;

    vectorMatArray[0] = length*(1.0f - (yy + zz)); 
    vectorMatArray[1] = length*(xy + wz); 
    vectorMatArray[2] = length*(xz - wy);
    vectorMatArray[3] = 0.0f;

    vectorMatArray[4] = length*(xy - wz); 
    vectorMatArray[5] = length*(1.0f - (xx + zz)); 
    vectorMatArray[6] = length*(yz + wx);
    vectorMatArray[7] = 0.0f;

    vectorMatArray[8]  = length*(xz + wy); 
    vectorMatArray[9]  = length*(yz - wx); 
    vectorMatArray[10] = length*(1.0f - (xx + yy));
    vectorMatArray[11] = 0.0f;

    // Set the translation
    vectorMatArray[12] = anchorPt.x();
    vectorMatArray[13] = anchorPt.y();
    vectorMatArray[14] = anchorPt.z();
    vectorMatArray[15] = 1.0f;
}


} // namespace cvf
