/***********************************************************************************
 *                                                                                 *
 * Voreen - The Volume Rendering Engine                                            *
 *                                                                                 *
 * Copyright (C) 2005-2013 University of Muenster, Germany.                        *
 * Visualization and Computer Graphics Group <http://viscg.uni-muenster.de>        *
 * For a list of authors please refer to the file "CREDITS.txt".                   *
 *                                                                                 *
 * This file is part of the Voreen software package. Voreen is free software:      *
 * you can redistribute it and/or modify it under the terms of the GNU General     *
 * Public License version 2 as published by the Free Software Foundation.          *
 *                                                                                 *
 * Voreen 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 in the file   *
 * "LICENSE.txt" along with this file. If not, see <http://www.gnu.org/licenses/>. *
 *                                                                                 *
 * For non-commercial academic use see the license exception specified in the file *
 * "LICENSE-academic.txt". To get information about commercial licensing please    *
 * contact the authors.                                                            *
 *                                                                                 *
 ***********************************************************************************/

#ifndef VRN_SHADOWRAYCASTER_H
#define VRN_SHADOWRAYCASTER_H

#include "voreen/core/processors/volumeraycaster.h"

#include "voreen/core/properties/transfuncproperty.h"
#include "voreen/core/properties/cameraproperty.h"
#include "voreen/core/properties/boolproperty.h"
#include "voreen/core/properties/optionproperty.h"
#include "voreen/core/properties/intproperty.h"
#include "voreen/core/properties/shaderproperty.h"

#include "voreen/core/ports/volumeport.h"

namespace tgt {
    class TextureUnit;
    class FramebufferObject;
}

class CameraProperty;

namespace voreen {

class LightVolumeGenerator;

class ShadowRaycaster : public VolumeRaycaster {
public:
    ShadowRaycaster();
    ~ShadowRaycaster();
    Processor* create() const;

    virtual std::string getCategory() const { return "Raycasting"; }
    virtual std::string getClassName() const { return "ShadowRaycaster"; }
    virtual CodeState getCodeState() const { return CODE_STATE_TESTING; }

protected:
    virtual void setDescriptions() {
        setDescription("This raycaster provides global illumination effects such as shadows and scattering, using a light volume in which lighting information \
                is propagated along one of the main axis before rendering. It is based on the publication \"Interactive Volumetric Lighting Simulating Scattering and Shadowing\" by Ropinski et al.");
    }

    void initialize() throw (tgt::Exception);
    void deinitialize() throw (tgt::Exception);

    void process();

    void compile();
    std::string generateHeader();

    void shadowModeChanged();
    void renderModeChanged();

private:
    void renderSlices();
    void renderVolume(tgt::TextureUnit* lightUnit, const tgt::ivec2& renderSize);

    VolumeBase* currentVolumeHandleBase_;

    ShaderProperty shaderProp_;
    TransFuncProperty transferFunc_;
    CameraProperty camera_;
    BoolProperty shadowsActive_;
    OptionProperty<bool>* renderMode_;
    IntProperty zSlice_;

    LightVolumeGenerator* lightVolumeGenerator_;
    std::vector<Property*> lightVolumeProperties_;

    VolumePort volumeInport_;
    RenderPort entryPort_;
    RenderPort exitPort_;
    RenderPort outport_;
    RenderPort internalRenderPort_;
};

//---------------------------------------------------------------------------

/**
 * Class that creates a lightvolume that can be used in raycasting to achieve shadows.
 * The lightvolume is a 3D texture that is generated by render to texture using a fbo.
 * Therefore the cubeface that points most to the lightsource or camera is calculated.
 * The texture is created by slicing through the z axis. For every pixel the vector to
 * the lightsource is calculated and the shadowvalue along the ray at the previous slice
 * is retrieved. According to this value a new shadowvalue for this pixel is calculated.
 * The generated lightvolume can be used by any raycaster. The methods setShaderUniforms(),
 * bindLightVolume() and generateShadowHeader() are provided for that case.
 */
class LightVolumeGenerator : public VolumeRaycaster {

    friend class ShadowRaycaster;

public:

    /**
     * Enumeration for viewpoint modes.
     */
    enum Viewpoint {
        LIGHTSOURCE_POSITION = 0, ///< use light source as viewpoint
        CAMERA_POSITION      = 1  ///< use camera as viewpoint
    };

    /**
     * Constructor
     */
    LightVolumeGenerator();

    /**
     * Destructor
     */
    ~LightVolumeGenerator();

    /**
     * Creates a new instance of this class.
     *
     * @return new instance of this class.
     */
    Processor* create() const;

    /**
     * This method is called when the processor should process. In that case its never called
     * because this class is not in the processor vecto in the evaluator.
     */
    void process() {};

    /**
     * Returns a string that contains informations about the processor.
     *
     * @return string that conatins informations about the processor.
     */
    virtual std::string getProcessorInfo() const;

    virtual std::string getCategory() const { return "Raycasting"; }
    virtual std::string getClassName() const { return "LightVolumeGenerator"; }

    /**
     * Initializes the OpenGL part of this procesor, e.g. the shader is loaded
     * and the framebuffer objects are created. Returns VRN_OK if everything was
     * successful and VRN_ERROR otherwise.
     *
     * @return VRN_OK if initialization was successfull and VRN_ERROR otherwise.
     */
    void initialize() throw (tgt::Exception);

    void deinitialize() throw (tgt::Exception);

    /**
     * Loads the shader for this processor if this was not already done.
     */
    void loadShader();

    /**
     * Rebuilds the shader of this processor.
     */
    void compile();

    /**
     * Generates header defines for the shader and returns them.
     *
     * @return header defines for the shader.
     */
    std::string generateHeader();

    /**
     * Generates header defines for shaders that wanna use the lightvolume.
     *
     * @return header defines for shaders that wanna use the lightvolume
     */
    std::string generateShadowHeader();

    /**
     * Creates the lightvolume if a recompute is necessary.
     */
    void generateLightVolume(tgt::TextureUnit* lightUnit, tgt::TextureUnit* blendUnit);

    /**
     * Renders the given slice of the lightvolume to a quad.
     * The quad is placed at the loctaion of the slice.
     *
     * @param sliceNumber number of the slice to be rendered.
     */
    void renderSlice(int sliceNumber);

    /**
     * Binds the texture of the lightvolume, so that it can be used in
     * a raycaster.
     */
    void bindLightVolume(tgt::TextureUnit* lightUnit);

    /**
     * Passes all uniforms to the given shader that are needed for the use of
     * the lightvolume in a raycaster.
     *
     * @param shader shader the uniforms are passed to.
     */
    void setShaderUniforms(tgt::Shader* shader, tgt::TextureUnit* lightUnit);

    /**
     * Sets the currentVolumeHandle to the given one. The cubefaces and textures are reset.
     * New cubefaces are calculated as well because the cubeSize could have changed.
     *
     * @param handle the volumeHandle that is rendered.
     */
    void setVolumeHandle(const VolumeBase* handle);

    /**
     * Sets the transfer function property to the given one. Additionally the
     * onChange()-method is set.
     *
     * @param tf the transfer function property
     */
    void setTransFunc(TransFuncProperty* tf);

    /**
     * Method that is called when the light position has changed. Recalculates the cubeFaces
     * and verifies that the light position is not inside the volume.
     *
     * @param lightPosition lightPosition property of the raycaster that uses the lightvolume
     */
    void setLightPosition(LightSourceProperty* lightPosition);

    /**
     * Method that is called when the transfer function changes. Adapts the borderColor and forces
     * the regeneration of the lightvolume in next rendering pass.
     */
    void transferFunctionChanged();

    /**
     * Handler that is called when the scaling property changes. Updates the texture dimensions
     * according to new scaling.
     */
    void scalingChanged();

    /**
     * Handler for change of camera position. Forces regeneration of lightvolume in next rendering pass.
     */
    void cameraChanged();

    /**
     * Forces regeneration of the lightvolume in the next rendering pass.
     */
    void recomputeNeeded();

    /**
     * Handler for change of viewpoint mode. Recalculates the cubefaces and forces a regeneration
     * of the light volume in the next rendering pass.
     */
    void viewpointModeChanged();

private:
    virtual void setDescriptions() {
        setDescription("");
    }

    /**
     * Verirfies whether the cubefaces for blend texture and light are still correct and updates
     * the texture dimenions if necessary.
     */
    void verifyCubeFaces();

    /**
     * Calculates the best or second best cubeface that points to the lightsource or camera.
     * The angle between the cubeface normal and the vector from the middle of a cubeface
     * to the lightsource is used for determination of the best or second best cubeface.
     *
     * @param calculateSecondBest should the secon best cubeface be calculated?
     * @return number of the calculated cubeface
     */
    int calculateCubeFace(bool calculateSecondBest = false);

    /**
     * Initializes the textures of the light- and blendvolume. The textures are created with proper
     * dimensions and are then uploaded to the gpu.
     */
    void setupTextures();

    /**
     * Entrypoint for generation of the lightvolume. The blend texture is created in a first pass and
     * then used in a second pass for generation of light volume.
     */
    void createTexture(tgt::TextureUnit* lightUnit, tgt::TextureUnit* blendUnit);

    /**
     * Iterates through the slices of the blend texture on the z axis and renders a quad
     * for every slice while a fragment shader is active. During rendering a slice uses
     * the result of the previous slice. To avoid problems with access to not yet created parts of
     * the texture, nearest filtering is used. The created images are stored directly in the light volume.
     * That becomes possible with framebufferobjects.
     */
    void createBlendTexture(tgt::TextureUnit* blendUnit);

    /**
     * Iterates through the slices of the light volume on the z axis and renders a quad
     * for every slice while a fragment shader is active. During rendering a slice uses
     * the result of the previous slice. To avoid problems with access to not yet created parts of
     * the texture, nearest filtering is used. The created images are stored directly in the light volume.
     * That becomes possible with framebufferobjects.
     */
    void createShadowTexture(tgt::TextureUnit* lightUnit, tgt::TextureUnit* blendUnit);

    void createViewpointTexture();

    /**
     * Writes the given slice to an image file using DevIL. Works only when called during
     * creation of slices.
     *
     * @param slice number of the slice that will be writen to an image file.
     */
    void writeSliceToImage(int slice);

    /**
     * Verifies the light positions for lightvolume and blend texture so that they are not inside
     * of the volume.
     */
    void verifyLightPositions();

    /**
     * Recalculates the permutation vector and the signfactor for blend texture and
     * lightvolume. The dimensions of the textures are permuted according to the permutation vectors.
     */
    void updateTextureDimensions();

    const VolumeBase* currentVolume_;

    tgt::FramebufferObject* fbo_; ///< the framebufferobject that is used for rendering to a 3d texture

    tgt::Shader* raycastPrg_;   ///< The shader program used by this raycaster.

    bool recomputeNeeded_;   ///< when true the lightvolume needs to be recalculated
    int cubeFace_;           ///< number of the the cubeface that points to lightsource or camera
    tgt::Texture* shadowTexture_; ///< the light volume
    float signFactor_;      ///< only -1.f or 1.f, is used for correct order of vertices and texture coordinates
    tgt::ivec3 indexing_;   ///< permutation vector
    tgt::vec3 normals_[6];  ///< the 6 normals of a cube
    tgt::vec4 borderColor_; ///< color of the border, usually this is the value of transferfunction at
                            ///< intensity 0

    tgt::FramebufferObject* fboBlend_; ///< framebufferobject used for creation of blend texture
    int cubeFaceBlend_; ///< number of the second best cubeface, used for creation of blend texture
    tgt::Texture* shadowTextureBlend_; ///< the blend texture
    float signFactorBlend_; ///< only -1.f or 1.f, is used for correct order of vertices and texture coordinates
    tgt::ivec3 indexingBlend_; ///< permutation vector for lend texture
    tgt::vec3 lightPositionBlend_; ///< light position for generation of blend texture
    tgt::vec2 angles_; ///< angles between light source and cubeface normals

    tgt::Texture* viewpointTexture_;
    tgt::FramebufferObject* fboViewpoint_;
    int cubeFaceView_;
    float signFactorViewpoint_; ///< only -1.f or 1.f, is used for correct order of vertices and texture coordinates
    tgt::ivec3 indexingViewpoint_;

    // properties
    TransFuncProperty* transferFunc_; ///< the transfer function property
    CameraProperty camera_;
    FloatOptionProperty scalingMode_; ///< property to reduce the dimensions of the lightvolume
    IntOptionProperty neighborhood_; ///< which neighborhood should be used
    StringOptionProperty filterModes_; ///< which filter should be applied to the values in the shader
    IntOptionProperty viewpointModes_; ///< which viewpoint (camera or lightsource) should be used?

    BoolProperty writeSlices_; ///< enables and disables writing of the slices to image files
    BoolProperty recomputeOnEveryFrame_; ///< when true, the light volume is recomputed on every process of the raycaster

    bool lightInitialized_; ///< indicates whether light is intialized or not

    bool verifying_;
};

} // namespace voreen

#endif // VRN_SHADOWRAYCASTER_H
