/*****************************************************************************
 * $CAMITK_LICENCE_BEGIN$
 *
 * CamiTK - Computer Assisted Medical Intervention ToolKit
 * (c) 2001-2018 Univ. Grenoble Alpes, CNRS, TIMC-IMAG UMR 5525 (GMCAO)
 *
 * Visit http://camitk.imag.fr for more information
 *
 * This file is part of CamiTK.
 *
 * CamiTK is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License version 3
 * only, as published by the Free Software Foundation.
 *
 * CamiTK 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 Lesser General Public License version 3 for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * version 3 along with CamiTK.  If not, see <http://www.gnu.org/licenses/>.
 *
 * $CAMITK_LICENCE_END$
 ****************************************************************************/
#include "VolumeRenderingAction.h"

// -- vtk stuff --
#include <vtkImageCast.h>
#include <vtkVolume.h>
#include <vtkVolumeRayCastMapper.h>
#include <vtkPiecewiseFunction.h>
#include <vtkVolumeRayCastCompositeFunction.h>
#include <vtkColorTransferFunction.h>
#include <vtkRenderWindowInteractor.h>
#include <vtkRenderWindow.h>
#include <vtkRenderer.h>
#include <vtkVolumeProperty.h>
#include <vtkPolyData.h>
#include <vtkImageMagnitude.h>
#include <vtkCellArray.h>
#include <vtkFloatArray.h>
#include <vtkPointData.h>
#include <vtkPoints.h>
#include <vtkPolyData.h>

// -- Application --
#include <Application.h>
#include <MeshComponent.h>
#include <InteractiveViewer.h>
#include <RendererWidget.h>
#include <Log.h>

using namespace camitk;


// --------------- constructor -------------------
VolumeRenderingAction::VolumeRenderingAction(ActionExtension* extension) : Action(extension) {
    // Init fields
    myWidget = nullptr;
    volumeName = "volume-rendering";


    // Setting name, description and input component
    setName("Volume Rendering");
    setEmbedded(false);
    setDescription(tr("Volume rendering of 3D medical image using ray casting<br/>\
				   (Use Ctrl+R to render a selected image volume)"));
    setComponent("ImageComponent");

    // Setting classification family and tags
    setFamily("Display");
    addTag(tr("Volume rendering"));
    addTag(tr("volume"));
    addTag(tr("render"));

    // add shortcut
    getQAction()->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_R));
    getQAction()->setShortcutContext(Qt::ApplicationShortcut);

    /*
    // Define default colors
    defaultTransparencies.insert(  0, 0.0);
    defaultTransparencies.insert(255, 1.0);

    defaultColors.insert(  0, QColor(204,  77,  51, 255));
    defaultColors.insert(255, QColor(128, 128, 128, 255));

    defaultGradientOpacities.insert(0, 0.0);
    defaultGradientOpacities.insert(90, 0.5);
    defaultGradientOpacities.insert(150, 1.0);

    defaultAmbiant	= 0.4;
    defaultDiffuse	= 0.6;
    defaultSpecular = 0.2;
    */
}

// --------------- getWidget -------------------
QWidget* VolumeRenderingAction::getWidget() {
    return nullptr;
}

// --------------- apply -------------------
Action::ApplyStatus VolumeRenderingAction::apply() {
    foreach (Component* comp, getTargets()) {
        ImageComponent* input = dynamic_cast<ImageComponent*>(comp);
        process(input);
    }
    return SUCCESS;
}

void VolumeRenderingAction::process(ImageComponent* comp) {
    bool isVisible	= comp->getVolumeRenderingChild()->getVisibility(InteractiveViewer::get3DViewer());
    bool existVR	= (comp->getVolumeRenderingChild()->getProp(volumeName) != NULL);
    if (existVR) {
        if (isVisible) {
            comp->getVolumeRenderingChild()->setVisibility(InteractiveViewer::get3DViewer(), false);
            if (myWidget) {
                myWidget->close();
            }
            Application::refresh();
        }
        else {
            comp->getVolumeRenderingChild()->setVisibility(InteractiveViewer::get3DViewer(), true);
            if (myWidget != nullptr) {
                myWidget->show();
                Application::refresh();
            }
        }
    }
    else {
        if (myWidget == nullptr) {
            myWidget = new VolumeRenderingWidget(this);
        }

        myWidget->updateImage(comp);
        myWidget->show();
    }
}


void VolumeRenderingAction::createVolumeRendering(ImageComponent* comp) {
    // Get the transfer function from the widget
    QMap<int, double> transparencies = myWidget->getTransparencyPoints();
    QMap<int, double>::const_iterator transIt;

    QMap<int, QColor> transferColors = myWidget->getColorPoints();
    QMap<int, QColor>::const_iterator colorIt;

    QMap<int, double> gradientOpacities = myWidget->getOpacityPoints();
    QMap<int, double>::const_iterator gradientIt;

    double ambiant	= myWidget->getAmbiant();
    double diffuse	= myWidget->getDiffuse();
    double specular = myWidget->getSpecular();

    // FILTER
    // Cast element type of the image of type unigned short for mapper to work on the image
    vtkSmartPointer<vtkImageCast> imageCast = vtkImageCast::New();
    imageCast->SetInputData(comp->getImageData()); // reader->GetOutput());
    imageCast->SetOutputScalarTypeToUnsignedChar();

    // Convert RGB images
    vtkSmartPointer<vtkImageMagnitude> magnitudeFilter = vtkSmartPointer<vtkImageMagnitude>::New();
    magnitudeFilter->SetInputConnection(imageCast->GetOutputPort());
    magnitudeFilter->Update();

    // MAPPER
    vtkSmartPointer<vtkVolumeRayCastMapper> volumeMapper = vtkSmartPointer<vtkVolumeRayCastMapper>::New();
    volumeMapper->SetInputData(magnitudeFilter->GetOutput());

    // The volume will be displayed by ray-cast alpha compositing.
    // A ray-cast mapper is needed to do the ray-casting, and a
    // compositing function is needed to do the compositing along the ray.
    vtkSmartPointer<vtkVolumeRayCastCompositeFunction> rayCastFunction =
        vtkSmartPointer<vtkVolumeRayCastCompositeFunction>::New();
    volumeMapper->SetVolumeRayCastFunction(rayCastFunction);

    // RENDERER
    // Here, there is only one renderer : the one of the volume, but contains many properties
    vtkSmartPointer<vtkRenderer> renderer = vtkSmartPointer<vtkRenderer>::New();

    // 1st property : volume color
    // This function associates a voxel gray level valut to a color one (r, g, b)
    // Function definition
    // f : gray |-> (r, g, b)
    // where gray is the grayscale value (of type unsigned short)
    vtkSmartPointer<vtkColorTransferFunction> volumeColor = vtkSmartPointer<vtkColorTransferFunction>::New();
    // Set the value that the user has entered in the ui sping box
    for (colorIt = transferColors.begin(); colorIt != transferColors.end(); colorIt++) {
        int gray = colorIt.key();
        QColor color = colorIt.value();
        volumeColor->AddRGBPoint((double) gray, color.redF(), color.greenF(), color.blueF());
    }

    // 2nd property : volume opacity
    // The opacity transfer function is used to control the opacity of different tissue types.
    // This function associate to a gray scale value a transparancy value (alpha)
    // Function definition
    // f : gray |-> alpha
    // where gray is the grayscale value (of type unsigned short)
    vtkSmartPointer<vtkPiecewiseFunction> volumeScalarOpacity = vtkSmartPointer<vtkPiecewiseFunction>::New();
    for (transIt = transparencies.begin(); transIt != transparencies.end(); transIt++) {
        int gray = transIt.key();
        double trans = transIt.value();
        volumeScalarOpacity->AddPoint(gray, trans);
    }

    // 3rd property : gradient of opacity function function of the distance
    // The gradient opacity function is used to decrease the opacity
    // in the "flat" regions of the volume while maintaining the opacity
    // at the boundaries between tissue types.  The gradient is measured
    // as the amount by which the intensity changes over unit distance.
    // For most medical data, the unit distance is 1mm.
    // This function associates a distance in mm to an opacity.
    // The higher the distance is, the less opacity it has
    // It enables the function to better render the boundaries between different tissues
    vtkSmartPointer<vtkPiecewiseFunction> volumeGradientOpacity = vtkSmartPointer<vtkPiecewiseFunction>::New();

    for (gradientIt = gradientOpacities.begin(); gradientIt != gradientOpacities.end(); gradientIt++) {
        int gray = gradientIt.key();
        double opacity = gradientIt.value();
        volumeGradientOpacity->AddPoint(gray, opacity);
    }

    // Finally : set the properties
    // The VolumeProperty attaches the color and opacity functions to the
    // volume, and sets other volume properties.  The interpolation should
    // be set to linear to do a high-quality rendering.  The ShadeOn option
    // turns on directional lighting, which will usually enhance the
    // appearance of the volume and make it look more "3D".  However,
    // the quality of the shading depends on how accurately the gradient
    // of the volume can be calculated, and for noisy data delete readerthe gradient
    // estimation will be very poor.  The impact of the shading can be
    // decreased by increasing the Ambient coefficient while decreasing
    // the Diffuse and Specular coefficient.  To increase the impact
    // of shading, decrease the Ambient and increase the Diffuse and Specular.

    vtkSmartPointer<vtkVolumeProperty> volumeProperty = vtkSmartPointer<vtkVolumeProperty>::New();
    volumeProperty->SetColor(volumeColor);
    volumeProperty->SetScalarOpacity(volumeScalarOpacity);
    volumeProperty->SetGradientOpacity(volumeGradientOpacity);
    volumeProperty->SetInterpolationTypeToLinear();
    volumeProperty->ShadeOn();
    volumeProperty->SetAmbient(ambiant);
    volumeProperty->SetDiffuse(diffuse);
    volumeProperty->SetSpecular(specular);

    // The vtkVolume is a vtkProp3D (like a vtkActor) and controls the position
    // and orientation of the volume in world coordinates.
    vtkSmartPointer<vtkVolume> volume = vtkSmartPointer<vtkVolume>::New();
    volume->SetUserTransform(comp->getTransform());
    volume->SetMapper(volumeMapper);
    volume->SetProperty(volumeProperty);
    volume->Update();

    // Remove old volume from the component and the 3D viewer
    vtkSmartPointer<vtkProp> oldVolume = comp->getVolumeRenderingChild()->getProp(volumeName);

    if (oldVolume) {
        comp->getVolumeRenderingChild()->removeProp(volumeName);
        //InteractiveViewer::get3DViewer()->getRendererWidget()->removeProp(oldVolume);
    }

    // Add the new computed volume to the component and display it in the 3D viewer
    comp->getVolumeRenderingChild()->addProp(volumeName, volume);
    comp->getVolumeRenderingChild()->getProp(volumeName)->SetVisibility(true);

    //InteractiveViewer::get3DViewer()->getRendererWidget()->addProp(volume);
    comp->getVolumeRenderingChild()->setVisibility(InteractiveViewer::get3DViewer(), true);
    InteractiveViewer::get3DViewer()->refresh();
}



