//##################################################################################################
//
//   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 "cvfDrawableText.h"
#include "cvfOpenGL.h"
#include "cvfOpenGLResourceManager.h"
#include "cvfBufferObjectManaged.h"
#include "cvfDrawableGeo.h"
#include "cvfQuat.h"
#include "cvfUniform.h"
#include "cvfRay.h"
#include "cvfFont.h"
#include "cvfGlyph.h"
#include "cvfCamera.h"
#include "cvfShaderProgram.h"
#include "cvfViewport.h"
#include "cvfGeometryUtils.h"
#include "cvfMatrixState.h"
#include "cvfRenderStatePoint.h"
#include "cvfRenderStateDepth.h"
#include "cvfRenderStatePolygonOffset.h"
#include "cvfRenderStateBlending.h"

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

namespace cvf {


//==================================================================================================
///
/// \class cvf::DrawableText
/// \ingroup Render
///
/// DrawableText implements drawing of text strings in 2D space.
/// 
//==================================================================================================

//--------------------------------------------------------------------------------------------------
/// Constructor
//--------------------------------------------------------------------------------------------------
DrawableText::DrawableText()
:   m_verticalAlignment(TextDrawer::BASELINE),
    m_textColor(Color3::BLACK),
    m_backgroundColor(1.0f, 1.0f, 0.8f),
    m_borderColor(Color3::BLACK),
    m_drawBackground(true),
    m_drawBorder(true),
    m_checkPosVisible(true),
    m_useDepthBuffer(false)
{
}


//--------------------------------------------------------------------------------------------------
/// Destructor
//--------------------------------------------------------------------------------------------------
DrawableText::~DrawableText()
{
}


//--------------------------------------------------------------------------------------------------
/// Set font to be used for drawing text
//--------------------------------------------------------------------------------------------------
void DrawableText::setFont(Font* font)
{
    m_font = font;
}


//--------------------------------------------------------------------------------------------------
/// Get font used to draw text
//--------------------------------------------------------------------------------------------------
ref<Font> DrawableText::font() const
{
    return m_font;
}


//--------------------------------------------------------------------------------------------------
/// 
//--------------------------------------------------------------------------------------------------
void DrawableText::setVerticalAlignment(TextDrawer::Alignment alignment)
{
    m_verticalAlignment = alignment;
}


//--------------------------------------------------------------------------------------------------
/// Set color of the text to be rendered
//--------------------------------------------------------------------------------------------------
void DrawableText::setTextColor(const Color3f& color)
{
    m_textColor = color;
}


//--------------------------------------------------------------------------------------------------
/// 
//--------------------------------------------------------------------------------------------------
void DrawableText::setBackgroundColor(const Color3f& color)
{
    m_backgroundColor = color;
}


//--------------------------------------------------------------------------------------------------
/// 
//--------------------------------------------------------------------------------------------------
void DrawableText::setBorderColor(const Color3f& color)
{
    m_borderColor = color;
}


//--------------------------------------------------------------------------------------------------
/// 
//--------------------------------------------------------------------------------------------------
void DrawableText::setDrawBackground(bool drawBackground)
{
    m_drawBackground = drawBackground;
}


//--------------------------------------------------------------------------------------------------
/// 
//--------------------------------------------------------------------------------------------------
void DrawableText::setDrawBorder(bool drawBorder)
{
    m_drawBorder = drawBorder;
}


//--------------------------------------------------------------------------------------------------
/// 
//--------------------------------------------------------------------------------------------------
void DrawableText::setCheckPosVisible(bool checkpos)
{
    m_checkPosVisible = checkpos;
}


//--------------------------------------------------------------------------------------------------
/// 
//--------------------------------------------------------------------------------------------------
void DrawableText::setUseDepthBuffer(bool useDepthBuffer)
{
    m_useDepthBuffer = useDepthBuffer;
}


//--------------------------------------------------------------------------------------------------
/// 
//--------------------------------------------------------------------------------------------------
void DrawableText::addText(const String& text, const Vec3f& position)
{
    m_positions.push_back(position);
    m_texts.push_back(text);
    m_boundingBox.add(position);  
}


//--------------------------------------------------------------------------------------------------
/// Main shader based rendering path for the geometry 
//--------------------------------------------------------------------------------------------------
void DrawableText::render(OpenGLContext* oglContext, ShaderProgram* shaderProgram, const MatrixState& matrixState)
{
    renderText(oglContext, shaderProgram, matrixState);
}


//--------------------------------------------------------------------------------------------------
/// 
//--------------------------------------------------------------------------------------------------
void DrawableText::renderSoftware(OpenGLContext* oglContext, const MatrixState& matrixState)
{
    renderText(oglContext, NULL, matrixState);
}


//--------------------------------------------------------------------------------------------------
/// Get total number of vertices for all texts
//--------------------------------------------------------------------------------------------------
size_t DrawableText::vertexCount() const
{
    return faceCount() * 4; // Four vertices per face/quad 
}


//--------------------------------------------------------------------------------------------------
/// Get total number of triangles for all texts
//--------------------------------------------------------------------------------------------------
size_t DrawableText::triangleCount() const
{
    size_t numCharacters = 0;

    size_t numTexts = m_texts.size();
    size_t i;
    for (i = 0; i < numTexts; i++)
    {
        numCharacters += m_texts[i].size();
    }

    return faceCount() * 2;   // Two triangles per face/quad
}


//--------------------------------------------------------------------------------------------------
/// Get total number of faces for all texts
//--------------------------------------------------------------------------------------------------
size_t DrawableText::faceCount() const
{
    size_t numCharacters = 0;

    size_t numTexts = m_texts.size();
    size_t i;
    for (i = 0; i < numTexts; i++)
    {
        numCharacters += m_texts[i].size();
    }

    return numCharacters;   // One face/quad per character/glyph
}


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


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

    return false;
}


//--------------------------------------------------------------------------------------------------
/// 
//--------------------------------------------------------------------------------------------------
void DrawableText::renderText(OpenGLContext* oglContext, ShaderProgram* shaderProgram, const MatrixState& matrixState)
{
    CVF_ASSERT(m_font.notNull());
    CVF_ASSERT(!m_font->isEmpty());
    CVF_ASSERT(oglContext);
    CVF_ASSERT(!shaderProgram || ShaderProgram::supportedOpenGL(oglContext));

    if (m_texts.size() == 0) return;
    CVF_ASSERT(m_positions.size() == m_texts.size());

    if (m_checkPosVisible)
    {
        if (shaderProgram)
        {
            ref<ShaderProgram> nudgeShader = oglContext->resourceManager()->getLinkedNudgeShaderProgram(oglContext);
            nudgeShader->useProgram(oglContext);
            nudgeShader->clearUniformApplyTracking();
            nudgeShader->applyFixedUniforms(oglContext, matrixState);

            RenderStatePoint point(RenderStatePoint::PROGRAM_SIZE);
            point.applyOpenGL(oglContext);
        }
        else
        {
            if (ShaderProgram::supportedOpenGL(oglContext))
            {
                ShaderProgram::useNoProgram(oglContext);
            }

#ifndef CVF_OPENGL_ES
            RenderStateMaterial_FF mat;
            mat.enableColorMaterial(true);

            RenderStateLighting_FF noLight(false);
            noLight.applyOpenGL(oglContext);
#endif
        }

        RenderStateDepth visibleCheckDepthRS(true, RenderStateDepth::LEQUAL, false);
        visibleCheckDepthRS.applyOpenGL(oglContext);

        RenderStatePolygonOffset po;
        po.enablePointMode(true);
        po.setFactor(-3.0f);
        po.setUnits(-3.0f);
        po.applyOpenGL(oglContext);

        RenderStateBlending addBlend;
        addBlend.enableBlending(true);
        addBlend.setFunction(RenderStateBlending::ONE, RenderStateBlending::ONE);
        addBlend.setEquation(RenderStateBlending::FUNC_ADD);
        addBlend.applyOpenGL(oglContext);
    }

    Mat4d modelViewProjectionMatrix = Mat4d(matrixState.modelViewProjectionMatrix());
    std::vector<Vec3f> projCoords;
    std::vector<String> textsToDraw;            // Text strings to be drawn
    size_t pos;
    for (pos = 0; pos < m_positions.size(); pos++)
    {
        Vec3d proj;
        GeometryUtils::project(modelViewProjectionMatrix, matrixState.viewportPosition(), matrixState.viewportSize(), Vec3d(m_positions[pos]), &proj);

        if (!m_checkPosVisible || labelAnchorVisible(oglContext, proj, m_positions[pos], shaderProgram == NULL))
        {
            // Note: Need to adjust for the current viewport, as the coords returned from project are in global windows coordinates
            projCoords.push_back(Vec3f(static_cast<float>(proj.x() - matrixState.viewportPosition().x()), static_cast<float>(proj.y() - matrixState.viewportPosition().y()), static_cast<float>(1.0 - 2.0*proj.z())));  // Map z into 1 .. -1
            textsToDraw.push_back(m_texts[pos]);
        }
    }

    if (m_checkPosVisible)
    {
        RenderStatePolygonOffset resetPO;
        resetPO.applyOpenGL(oglContext);

        RenderStateBlending resetBlend;
        resetBlend.applyOpenGL(oglContext);

#ifndef CVF_OPENGL_ES
        if (!shaderProgram)
        {
            RenderStateMaterial_FF mat;
            mat.applyOpenGL(oglContext);

            RenderStateLighting_FF light;
            light.applyOpenGL(oglContext);
        }
#endif
    }

    TextDrawer drawer(m_font.p());
    drawer.setVerticalAlignment(m_verticalAlignment);
    drawer.setTextColor(m_textColor);
    drawer.setBackgroundColor(m_backgroundColor);
    drawer.setBorderColor(m_borderColor);
    drawer.setDrawBorder(m_drawBorder);
    drawer.setDrawBackground(m_drawBackground);
    drawer.setUseDepthBuffer(m_useDepthBuffer);

    size_t i;
    for (i = 0; i < textsToDraw.size(); i++)
    {
        Vec3f pos = projCoords[i];
        drawer.addText(textsToDraw[i], pos);
    }

    if (shaderProgram)
    {
        drawer.render(oglContext, matrixState);

        // Keep current program in use when we exit
        shaderProgram->useProgram(oglContext);
    }
    else
    {
        drawer.renderSoftware(oglContext, matrixState);
    }
}


//--------------------------------------------------------------------------------------------------
/// 
//--------------------------------------------------------------------------------------------------
bool DrawableText::labelAnchorVisible(OpenGLContext* oglContext, const Vec3d winCoord, const Vec3f& worldCoord, bool softwareRendering) const
{
    CVF_CALLSITE_OPENGL(oglContext);
    CVF_UNUSED(worldCoord);

    Vec2i coord(static_cast<GLint>(winCoord.x()), static_cast<GLint>(winCoord.y()));

    // Read out current z buffer contents
    GLubyte bufferBefore[9*4];
    glReadPixels(coord.x() - 1, coord.y() - 1, 3, 3, GL_RGBA, GL_UNSIGNED_BYTE, bufferBefore);

    if (softwareRendering)
    {
#ifndef CVF_OPENGL_ES
        glPointSize(3);
        glColor3f(0.02f, 0.02f, 0.02f);
        glBegin(GL_POINTS);
        glVertex3fv(worldCoord.ptr());
        glEnd();
#endif
    }
    else
    {
        glEnableVertexAttribArray(ShaderProgram::VERTEX);
        glVertexAttribPointer(ShaderProgram::VERTEX, 3, GL_FLOAT, GL_FALSE, 0, worldCoord.ptr());

        glDrawArrays(GL_POINTS, 0, 1);
    }

    // Now compare with previous z buffer contents to see if marker is visible
    GLubyte bufferAfter[9*4];
    glReadPixels(coord.x() - 1, coord.y() - 1, 3, 3, GL_RGBA, GL_UNSIGNED_BYTE, bufferAfter);

    size_t j;
    for (j = 0; j < 9*4; j++)
    {
        if (bufferBefore[j] != bufferAfter[j]) return true;
    }

    return false;
}


//--------------------------------------------------------------------------------------------------
/// 
//--------------------------------------------------------------------------------------------------
bool DrawableText::rayIntersect(const Ray& ray, const Camera& camera, Vec3d* intersectionPoint)
{
    Vec3d pickCoord2d;
    camera.project(ray.origin(), &pickCoord2d);

    size_t i;
    for (i = 0; i < m_positions.size(); i++)
    {
        Vec3d proj;
        camera.project(Vec3d(m_positions[i]), &proj);

        Vec3f posTextDrawer = Vec3f(static_cast<float>(proj.x() - camera.viewport()->x()), static_cast<float>(proj.y() - camera.viewport()->y()), static_cast<float>(1.0 - 2.0*proj.z()));  // Map z into 1 .. -1

        if (TextDrawer::pickText(Vec3f(pickCoord2d), m_texts[i], posTextDrawer, m_font.p()))
        {
            if (m_useDepthBuffer)
            {
                *intersectionPoint = Vec3d(m_positions[i]);
            }
            else
            {
                *intersectionPoint = ray.origin();
            }

            return true;
        }
    }

    return false;
}

} // namespace cvf
