//##################################################################################################
//
//   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 "cvfPartHighlighter.h"
#include "cvfRenderSequence.h"
#include "cvfFramebufferObject.h"
#include "cvfRenderbufferObject.h"
#include "cvfSampler.h"
#include "cvfSingleQuadRenderingGenerator.h"
#include "cvfRenderStateDepth.h"
#include "cvfShaderSourceProvider.h"
#include "cvfCamera.h"
#include "cvfScene.h"
#include "cvfModelBasicList.h"
#include "cvfShaderProgramGenerator.h"
#include "cvfShaderProgram.h"
#include "cvfRenderStateStencil.h"
#include "cvfEffect.h"
#include "cvfUniform.h"
#include "cvfGaussianBlur.h"
#include "cvfRenderStateBlending.h"
#include "cvfRenderStatePolygonMode.h"
#include "cvfRenderStateLine.h"
#include "cvfClipPlaneSet.h"

namespace cvf {



//==================================================================================================
///
/// \class cvf::PartHighlighter
/// \ingroup Viewing
///
/// 
///
//==================================================================================================

//--------------------------------------------------------------------------------------------------
/// 
//--------------------------------------------------------------------------------------------------
PartHighlighter::PartHighlighter(Model* highlightModel, Camera* mainCamera)
:   m_highlightModel(highlightModel),
    m_mainCamera(mainCamera),
    m_highlightColor(Color3::WHITE)
{
    m_highlightModel = highlightModel;
    m_mainCamera = mainCamera;

    //ref<Texture> selectedColorTexture = new Texture(Texture::TEXTURE_RECTANGLE, Texture::RGBA);
    ref<Texture> selectedColorTexture = new Texture(Texture::TEXTURE_2D, Texture::RGBA);
    selectedColorTexture->setSize(1, 1);
    m_drawFbo = new FramebufferObject;
    m_drawFbo->attachColorTexture2d(0, selectedColorTexture.p());

    // Note that we clear alpha to 0
    m_highlightCamera = new Camera;
    m_highlightCamera->viewport()->setClearColor(Color4f(Color3::GRAY, 0)); 

    // Main 'solid' rendering
    m_drawRendering = new Rendering;
    m_drawRendering->setTargetFramebuffer(m_drawFbo.p());
    m_drawRendering->setCamera(m_highlightCamera.p());
    m_drawRendering->setScene(new Scene);
    m_drawRendering->scene()->addModel(m_highlightModel.p());
    m_drawRendering->setRenderingName("PartHighlighter : Main 'solid' rendering (m_drawRendering)");

    // Draw geometry again using lines to increase footprint of slim details 
    bool growFootprintUsingLineDrawing = true;
    if (growFootprintUsingLineDrawing)
    {
        m_drawRenderingLines = new Rendering;
        m_drawRenderingLines->setTargetFramebuffer(m_drawFbo.p());
        m_drawRenderingLines->setCamera(m_highlightCamera.p());
        m_drawRenderingLines->setScene(new Scene);
        m_drawRenderingLines->scene()->addModel(m_highlightModel.p());
        m_drawRenderingLines->setClearMode(Viewport::DO_NOT_CLEAR);
        m_drawRenderingLines->setRenderingName("PartHighlighter : Main 'solid' rendering (lines) (m_drawRenderingLines)");
    }

    configureOverrideEffects(false);

    // Setup the blur helper
    // The sigma value her should be tuned to the line width used above
    // If sigma gets larger that the line width we will get artifacts during the blur pass
    m_blur = new GaussianBlur(selectedColorTexture.p(), 7, 2);

    // Mix rendering
    {
        ref<Sampler> sampler = new Sampler;
        sampler->setWrapMode(Sampler::CLAMP_TO_EDGE);
        sampler->setMinFilter(Sampler::LINEAR);
        sampler->setMagFilter(Sampler::LINEAR);

        ref<RenderStateBlending> rsBlending = new RenderStateBlending;
        rsBlending->configureTransparencyBlending();

        m_highlightClrUniform = new UniformFloat("u_highlightColor", m_highlightColor);

        SingleQuadRenderingGenerator quadRenderGen("MixRendering");
        //quadRenderGen.addTexture(selectedColorTexture.p(), sampler.p(), "u_texture2DRect");
        quadRenderGen.addTexture(selectedColorTexture.p(), sampler.p(), "u_texture2D");
        quadRenderGen.addFragmentShaderCode(ShaderSourceProvider::instance()->getSourceFromRepository(ShaderSourceRepository::fs_HighlightMix));
        quadRenderGen.setRenderState(rsBlending.p());
        quadRenderGen.setUniform(m_highlightClrUniform.p());
        m_mixRendering = quadRenderGen.generate();
        m_mixRendering->setClearMode(Viewport::DO_NOT_CLEAR);
        m_mixRendering->setRenderingName("PartHighlighter : Mix Rendering (m_mixRendering)");
    }
}


//--------------------------------------------------------------------------------------------------
/// 
//--------------------------------------------------------------------------------------------------
void PartHighlighter::setHighlightColor(const Color3f& color)
{
    m_highlightColor = color;
    m_highlightClrUniform->set(m_highlightColor);
}


//--------------------------------------------------------------------------------------------------
/// 
//--------------------------------------------------------------------------------------------------
void PartHighlighter::addRenderingsToSequence(RenderSequence* renderSequence)
{
    CVF_ASSERT(renderSequence);
    
    if (m_drawRendering.notNull())  
    {
        renderSequence->addRendering(m_drawRendering.p());
    }

    if (m_drawRenderingLines.notNull())  
    {
        renderSequence->addRendering(m_drawRenderingLines.p());
    }

    if (m_blur.notNull())
    {
        m_blur->addRenderingsToSequence(renderSequence);
    }

    if (m_mixRendering.notNull())
    {
        renderSequence->addRendering(m_mixRendering.p());
    }
}


//--------------------------------------------------------------------------------------------------
/// 
//--------------------------------------------------------------------------------------------------
void PartHighlighter::removeRenderingsFromSequence(RenderSequence* renderSequence)
{
    CVF_ASSERT(renderSequence);

    if (m_drawRendering.notNull())  renderSequence->removeRendering(m_drawRendering.p());
    if (m_drawRenderingLines.notNull())  renderSequence->removeRendering(m_drawRenderingLines.p());
    if (m_mixRendering.notNull())   renderSequence->removeRendering(m_mixRendering.p());

    if (m_blur.notNull())
    {
        m_blur->removeRenderingsFromSequence(renderSequence);
    }
}


//--------------------------------------------------------------------------------------------------
/// 
//--------------------------------------------------------------------------------------------------
void PartHighlighter::resize(int x, int y, uint width, uint height)
{
    m_drawFbo->resizeAttachedBuffers(width, height);

    // Since draw and drawLines shares a camera, this call will set both
    m_drawRendering->camera()->setViewport(0, 0, width, height);

    // Mix rendering is the only "on-screen" rendering so here we do need to set the position of the viewport
    m_mixRendering->camera()->setViewport(x, y, width, height);

    if (m_blur.notNull())
    {
        m_blur->resizeFromTextureSize();
    }
}


//--------------------------------------------------------------------------------------------------
/// 
//--------------------------------------------------------------------------------------------------
void PartHighlighter::prepareForRedraw()
{
    if (m_mainCamera->projection() == Camera::PERSPECTIVE)
    {
        m_highlightCamera->setProjectionAsPerspective(m_mainCamera->fieldOfViewYDeg(), m_mainCamera->nearPlane(), m_mainCamera->farPlane());
    }
    else
    {
        m_highlightCamera->setProjectionAsOrtho(m_mainCamera->frontPlaneFrustumHeight(), m_mainCamera->nearPlane(), m_mainCamera->farPlane());
    }

    m_highlightCamera->setViewMatrix(m_mainCamera->viewMatrix());
}


//--------------------------------------------------------------------------------------------------
/// 
//--------------------------------------------------------------------------------------------------
void PartHighlighter::setClipPlaneSet(ClipPlaneSet* clipPlaneSet)
{
    m_drawRendering->removeAllGlobalDynamicUniformSets();
    
    if (m_drawRenderingLines.notNull())
    {
        m_drawRenderingLines->removeAllGlobalDynamicUniformSets();
    }

    if (clipPlaneSet)
    {
        m_drawRendering->addGlobalDynamicUniformSet(clipPlaneSet);
        if (m_drawRenderingLines.notNull())
        {
            m_drawRenderingLines->addGlobalDynamicUniformSet(clipPlaneSet);
        }

        configureOverrideEffects(true);
    }
    else
    {
        configureOverrideEffects(false);
    }
}


//--------------------------------------------------------------------------------------------------
/// 
//--------------------------------------------------------------------------------------------------
void PartHighlighter::configureOverrideEffects(bool useClipping)
{
    ShaderProgramGenerator spGen("DrawHighlightGeo", ShaderSourceProvider::instance());

    if (useClipping)
    {
        spGen.addVertexCode(cvf::ShaderSourceRepository::calcClipDistances);
        spGen.addFragmentCode(cvf::ShaderSourceRepository::checkDiscard_ClipDistances);
    }

    spGen.addVertexCode(ShaderSourceRepository::vs_Standard);
    spGen.addFragmentCode(ShaderSourceRepository::src_Color);
    spGen.addFragmentCode(ShaderSourceRepository::fs_Unlit);

    ref<ShaderProgram> shaderProg = spGen.generate();

    if (useClipping)
    {
        shaderProg->setDefaultUniform(new cvf::UniformInt(  "u_clipPlaneCount",   0));
        shaderProg->setDefaultUniform(new cvf::UniformFloat("u_ecClipPlanes",     cvf::Vec4f(0, 0, 1, 0)));
    }

    ref<Effect> eff = new Effect;
    eff->setShaderProgram(shaderProg.p());
    // Use magenta to detect if something isn't working as it should
    eff->setUniform(new UniformFloat("u_color", Color4f(Color3::MAGENTA)));
    m_drawRendering->setEffectOverride(eff.p());

    if (m_drawRenderingLines.notNull())
    {
        ref<Effect> eff = new Effect;
        eff->setShaderProgram(shaderProg.p());
        eff->setRenderState(new RenderStatePolygonMode(RenderStatePolygonMode::LINE));
        eff->setRenderState(new RenderStateLine(3));
        // Use cyan to detect if something isn't working as it should
        eff->setUniform(new UniformFloat("u_color", Color4f(Color3::CYAN)));
        m_drawRenderingLines->setEffectOverride(eff.p());
    }
}



//==================================================================================================
///
/// \class cvf::PartHighlighterStencil
/// \ingroup Viewing
///
/// 
///
//==================================================================================================

//--------------------------------------------------------------------------------------------------
/// 
//--------------------------------------------------------------------------------------------------
PartHighlighterStencil::PartHighlighterStencil(Model* highlightModel, Camera* mainCamera)
:   m_highlightModel(highlightModel),
    m_mainCamera(mainCamera),
    m_highlightColor(Color3::WHITE)
{
    m_highlightModel = highlightModel;
    m_mainCamera = mainCamera;

    // Selected rendering
    {
        ref<RenderbufferObject> depthStencilRBO = new RenderbufferObject(RenderbufferObject::DEPTH24_STENCIL8, 1, 1);
        ref<Texture> selectedColorTexture = new Texture(Texture::TEXTURE_RECTANGLE, Texture::RGBA);
        selectedColorTexture->setSize(1, 1);
        m_drawFbo = new FramebufferObject;
        m_drawFbo->attachDepthStencilRenderbuffer(depthStencilRBO.p());
        m_drawFbo->attachColorTexture2d(0, selectedColorTexture.p());

        m_highlightCamera = new Camera;
        m_highlightCamera->viewport()->setClearColor(Color4f(0, 0, 0, 0));
        m_drawRendering = new Rendering;
        m_drawRendering->setTargetFramebuffer(m_drawFbo.p());
        m_drawRendering->setClearMode(Viewport::CLEAR_COLOR_DEPTH_STENCIL);
        m_drawRendering->setCamera(m_highlightCamera.p());
        m_drawRendering->setScene(new Scene);
        m_drawRendering->scene()->addModel(m_highlightModel.p());

        {
            ShaderProgramGenerator spGen("DrawSelected", ShaderSourceProvider::instance());
            spGen.addVertexCode(ShaderSourceRepository::vs_Standard);
            spGen.addFragmentCode(ShaderSourceRepository::fs_HighlightStencilDraw);
            ref<ShaderProgram> prog  = spGen.generate();

            ref<RenderStateStencil> rsStencil = new RenderStateStencil;
            rsStencil->setFunction(RenderStateStencil::ALWAYS, 1, 0xffffffff);
            rsStencil->setOperation(RenderStateStencil::KEEP, RenderStateStencil::REPLACE, RenderStateStencil::REPLACE);
            rsStencil->enableStencilTest(true);

            ref<Effect> eff = new Effect;
            eff->setShaderProgram(prog.p());
            eff->setUniform(new UniformFloat("u_color", Color4f(m_highlightColor)));
            eff->setRenderState(new RenderStateDepth(true, RenderStateDepth::LEQUAL));
            eff->setRenderState(rsStencil.p());
            m_drawRendering->setEffectOverride(eff.p());
        }


        // Blur rendering
        {
            ref<Texture> blurredColorTexture = new Texture(Texture::TEXTURE_RECTANGLE, Texture::RGBA);
            blurredColorTexture->setSize(1, 1);
            m_blurFbo = new FramebufferObject;
            m_blurFbo->attachDepthStencilRenderbuffer(depthStencilRBO.p());
            m_blurFbo->attachColorTexture2d(0, blurredColorTexture.p());

            SingleQuadRenderingGenerator quadRenderGen("BlurRendering");
            {
                quadRenderGen.addFragmentShaderCode(ShaderSourceProvider::instance()->getSourceFromRepository(ShaderSourceRepository::fs_HighlightStencilBlur_v33));

                ref<Sampler> sampler = new Sampler;
                sampler->setWrapMode(Sampler::CLAMP_TO_EDGE);
                sampler->setMinFilter(Sampler::NEAREST);
                sampler->setMagFilter(Sampler::NEAREST);
                quadRenderGen.addTexture(selectedColorTexture.p(), sampler.p(), "u_texture2DRect");

                ref<RenderStateStencil> s = new RenderStateStencil;
                s->setFunction(RenderStateStencil::EQUAL, 0, 0xffffffff);
                s->enableStencilTest(true);
                quadRenderGen.setRenderState(s.p());

                quadRenderGen.setRenderState(new RenderStateDepth(false, RenderStateDepth::LESS, false));
            }

            m_blurRendering = quadRenderGen.generate();
            m_blurRendering->setTargetFramebuffer(m_blurFbo.p());
            m_blurRendering->setClearMode(Viewport::CLEAR_COLOR);
            m_blurRendering->camera()->viewport()->setClearColor(Color4f(0.0, 0.0, 0.0, 0));

            // Mix rendering
            {
                ref<Sampler> sampler = new Sampler;
                sampler->setWrapMode(Sampler::CLAMP_TO_EDGE);
                sampler->setMinFilter(Sampler::NEAREST);
                sampler->setMagFilter(Sampler::NEAREST);

                SingleQuadRenderingGenerator quadRenderGen("MixRendering");
                quadRenderGen.addTexture(blurredColorTexture.p(), sampler.p(), "u_texture2DRect");
                quadRenderGen.addFragmentShaderCode(ShaderSourceProvider::instance()->getSourceFromRepository(ShaderSourceRepository::fs_HighlightStencilMix_v33));
                m_mixRendering = quadRenderGen.generate();
                m_mixRendering->setClearMode(Viewport::DO_NOT_CLEAR);
            }
        }
    }
}


//--------------------------------------------------------------------------------------------------
/// 
//--------------------------------------------------------------------------------------------------
void PartHighlighterStencil::setHighlightColor(const Color3f& color)
{
    m_highlightColor = color;
    applyHighlightColor();
}

//--------------------------------------------------------------------------------------------------
/// Applies the currently set highlight color by modifying the override effect used in the draw pass
//--------------------------------------------------------------------------------------------------
void PartHighlighterStencil::applyHighlightColor()
{
    if (m_drawRendering.notNull())
    {
        ref<Effect> eff = m_drawRendering->effectOverride();
        if (eff.notNull())
        {
            eff->setUniform(new UniformFloat("u_color", Color4f(m_highlightColor)));
        }
    }
}


//--------------------------------------------------------------------------------------------------
/// 
//--------------------------------------------------------------------------------------------------
void PartHighlighterStencil::addRenderingsToSequence(RenderSequence* renderSequence)
{
    CVF_ASSERT(renderSequence);
    if (m_drawRendering.notNull())  renderSequence->addRendering(m_drawRendering.p());
    if (m_blurRendering.notNull())  renderSequence->addRendering(m_blurRendering.p());
    if (m_mixRendering.notNull())   renderSequence->addRendering(m_mixRendering.p());
}


//--------------------------------------------------------------------------------------------------
/// 
//--------------------------------------------------------------------------------------------------
void PartHighlighterStencil::removeRenderingsFromSequence(RenderSequence* renderSequence)
{
    CVF_ASSERT(renderSequence);
    if (m_drawRendering.notNull())  renderSequence->removeRendering(m_drawRendering.p());
    if (m_blurRendering.notNull())  renderSequence->removeRendering(m_blurRendering.p());
    if (m_mixRendering.notNull())   renderSequence->removeRendering(m_mixRendering.p());
}


//--------------------------------------------------------------------------------------------------
/// 
//--------------------------------------------------------------------------------------------------
void PartHighlighterStencil::resize(uint width, uint height)
{
    m_drawFbo->resizeAttachedBuffers(width, height);
    m_blurFbo->resizeAttachedBuffers(width, height);

    m_drawRendering->camera()->setViewport(0, 0, width, height);
    m_blurRendering->camera()->setViewport(0, 0, width, height);
    m_mixRendering->camera()->setViewport(0, 0, width, height);
}


//--------------------------------------------------------------------------------------------------
/// 
//--------------------------------------------------------------------------------------------------
void PartHighlighterStencil::prepareForRedraw()
{
    m_highlightCamera->setProjectionAsPerspective(m_mainCamera->fieldOfViewYDeg(), m_mainCamera->nearPlane(), m_mainCamera->farPlane());
    m_highlightCamera->setViewMatrix(m_mainCamera->viewMatrix());
}



}
