/*****************************************************************************
 * $CAMITK_LICENCE_BEGIN$
 *
 * CamiTK - Computer Assisted Medical Intervention ToolKit
 * (c) 2001-2025 Univ. Grenoble Alpes, CNRS, Grenoble INP - UGA, TIMC, 38000 Grenoble, France
 *
 * 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$
 ****************************************************************************/

// -- Core stuff
#include "RendererWidget.h"
#include "GeometricObject.h"
#include "ScreenshotFormatInfo.h"
#include "Log.h"

// -- QT stuff
#include <QApplication>
#include <QKeyEvent>
#include <QProcessEnvironment>
#include <QTemporaryFile>

// -- VTK stuff
// disable warning generated by clang about the surrounded headers
#include "CamiTKDisableWarnings"

// ---- Interactor
#include "vtkInteractorStylePick.h"
#include <vtkInteractorStyleTrackballCamera.h>
#include <vtkInteractorStyleJoystickCamera.h>
#include <vtkInteractorStyleImage.h>
// ---- Renderer
#include <vtkRenderWindow.h>
#include <vtkRendererCollection.h>
#include <vtkCamera.h>
// ---- Various vtk stuff for 3D
#include <vtkAssemblyPath.h>
#include <vtkProperty.h>
// ---- Screenshots
#include <vtkImageWriter.h>
#include <vtkExporter.h>
#include <vtkWindowToImageFilter.h>
#include <vtkBMPWriter.h>
#include <vtkJPEGWriter.h>
#include <vtkPNGWriter.h>
#include <vtkPostScriptWriter.h>
#include <vtkGL2PSExporter.h>
#include <vtkOBJExporter.h>
#include <vtkVRMLExporter.h>
#include <vtkRIBExporter.h>
// ---- Logo in the background
#include <vtkLogoWidget.h>
#include <vtkHandleWidget.h>
#include <vtkPNGReader.h>
#include <vtkLogoRepresentation.h>
// ---- Text in the background
#include <vtkTextMapper.h>
// ---- Picking
#include <vtkPicker.h>
// ---- Axes
#include <vtkAxesActor.h>
#include <vtkCaptionActor2D.h>
#include <vtkProperty2D.h>
// ---- Annotated cube
#include <vtkAnnotatedCubeActor.h>
// ---- Color scale
#include <vtkScalarBarWidget.h>
#include <vtkLookupTable.h>
#include <vtkScalarBarRepresentation.h>
// -- freetype crash
#include <vtkVectorText.h>
#include "CamiTKReEnableWarnings"

// ---- Interactor
#include <vtkCallbackCommand.h>
// ---- Various vtk stuff for 3D
#include <vtkTransform.h>
// ---- Text in the background
#include <vtkTextProperty.h>
// ---- Picking
#include <vtkProp3DCollection.h>
// ---- Axes
#include <vtkTextActor.h>
// ---- Annotated cube
#include <vtkAssembly.h>
// ---- Color scale
#include <vtkScalarBarActor.h>
// -- freetype crash
#include <vtkTextSource.h>
#include <vtkProperty.h>
#include <vtkTransformPolyDataFilter.h>

#include <QSurfaceFormat>
#include <vtkLogger.h>

// On some OpenGL drivers (e.g. Mesa 13), the OpenGL context has to be requested before hand
static void initialize() {

    // This initialization method is greatly inspired by
    // - MITK initialization (see https://github.com/MITK/MITK/blob/master/Modules/AppUtil/src/mitkBaseApplication.cpp#L751)
    // - Slicer initialization (see https://github.com/Slicer/Slicer/blob/master/Libs/MRML/Widgets/qMRMLWidget.cxx#L89)
    // All credits (code and comments) go to the MITK and Slicer teams

    //-- init VTK logger + verbosity
    vtkLogger::SetStderrVerbosity(vtkLogger::VERBOSITY_WARNING);

    //-- init OpenGL parameters
    vtkOpenGLRenderWindow::SetGlobalMaximumNumberOfMultiSamples(0);

    // Set default surface format for QVTKOpenGLWidget. Disable multisampling to
    // support volume rendering and other VTK functionality that reads from the
    // framebuffer; see https://gitlab.kitware.com/vtk/vtk/issues/17095.
    auto defaultFormat = QVTKOpenGLNativeWidget::defaultFormat();
    defaultFormat.setSamples(0);
    QSurfaceFormat::setDefaultFormat(defaultFormat);

#ifdef Q_OS_MACOS
    QCoreApplication::setAttribute(Qt::AA_DontCreateNativeWidgetSiblings);
#endif

#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
    // Enable automatic scaling based on the pixel density of the monitor
    QApplication::setAttribute(Qt::AA_EnableHighDpiScaling); // As this must be set before creating the QApplication, this line is probably useless
    QGuiApplication::setHighDpiScaleFactorRoundingPolicy(Qt::HighDpiScaleFactorRoundingPolicy::PassThrough);

    // Enable QIcon to provide higher-resolution pixmaps than the size in device independent units.
    // These pixmaps render sharply on a high-dpi display.
    // If Qt::AA_UseHighDpiPixmaps is enabled then when an icon is loaded by the filename "base.png" then the icon
    // class will also look for higher-resolution variants of this image by the names base@2x.png, base@3x.png, etc.
    // On a high-DPI monitor (with DevicePixelRatio > 1) QIcon will actually load a higher-resolution pixmap
    // that best matches the DevicePixelRatio of the display, resulting in crisp rendering of the icon.
    // See https://doc.qt.io/qt-5/qicon.html#pixmap for more details.
    QApplication::setAttribute(Qt::AA_UseHighDpiPixmaps);
#endif
    // Enables resource sharing between the OpenGL contexts used by classes like QOpenGLWidget and QQuickWidget
    QCoreApplication::setAttribute(Qt::AA_ShareOpenGLContexts);
}

Q_COREAPP_STARTUP_FUNCTION(initialize)



namespace camitk {

//---------------------- Constructor ------------------------
RendererWidget::RendererWidget(QWidget* parent, ControlMode mode) : CamiTKQVTKWidget(parent) {

    setObjectName("RendererWidget");

    //-- Qt stuff
    setMinimumSize(150, 150);
    QSizePolicy policy(QSizePolicy::Expanding, QSizePolicy::Expanding);
    policy.setHeightForWidth(false);
    setSizePolicy(policy);

    //-- display options (this is not a state managed by the renderer, but a property of the actors)
    backfaceCulling = false;
    fxaaAntialiasing = false;
    displayLogo = true;
    rendering3DRedBlue = false;
    displayColorScale = false;
    pointSize = 4.0;

    //-- renderer
    renderer = vtkSmartPointer<vtkRenderer>::New();

    // initialize render window
    //-- render window
    // create and set a new render window
    setRenderWindow(vtkSmartPointer<vtkGenericOpenGLRenderWindow>::New());

    // depth peeling ensure that when slices are displayed properly independently to
    // the order in which the slice actors were added
    // This avoid a z-buffer problem resulting in strange transparency behaviour between slices
    // (i.e., the last added actor is transparent to all other actors, the one added before last
    // is transparent only to all but the last added actors, etc... the first added actor is
    // transparent to none)
    renderer->UseDepthPeelingOn();

    // setup depth peeling parameters
    getRenderWindow()->SetMultiSamples(0); // no MSAA as it is incompatible with depth peeling
    getRenderWindow()->SetAlphaBitPlanes(1); // Necessary for depth peeling
    getRenderWindow()->AddRenderer(renderer);

    //-- interactor
    renderWindowInteractor = vtkSmartPointer<QVTKInteractor>::New();
    renderWindowInteractor->SetRenderWindow(getRenderWindow());
    getRenderWindow()->SetInteractor(renderWindowInteractor);
    pickInteractorStyle = vtkSmartPointer<vtkInteractorStylePick>::New();
    pickInteractorStyle->SetDefaultRenderer(renderer);

    //-- camera control mode
    controlMode = NONE;
    cameraOrientation = RIGHT_DOWN;
    controlInteractorStyle = nullptr;
    setControlMode(mode);
    setCameraOrientation(cameraOrientation);
    renderWindowInteractor->Initialize();

    //-- init extra actors

    //-- axes
    axes = vtkSmartPointer<vtkAxesActor>::New();
    axes->SetShaftTypeToCylinder();
    axes->SetXAxisLabelText("x");
    axes->SetYAxisLabelText("y");
    axes->SetZAxisLabelText("z");
    axes->SetTotalLength(0.15, 0.15, 0.15); // 10% of unit length
    vtkSmartPointer<vtkTextProperty> axeXTextProp = vtkSmartPointer<vtkTextProperty>::New();
    axeXTextProp->SetFontSize(22);
    axeXTextProp->BoldOn();
    axeXTextProp->ItalicOn();
    axeXTextProp->ShadowOn();
    axeXTextProp->SetFontFamilyToArial();
    axes->GetXAxisCaptionActor2D()->SetCaptionTextProperty(axeXTextProp);
    axes->GetXAxisCaptionActor2D()->SetAttachmentPoint(1.0, 0.0, 0.0);
    // remove the autoscaling so that the font size is smaller than default autoscale
    axes->GetXAxisCaptionActor2D()->GetTextActor()->SetTextScaleModeToNone();
    // set the color
    axes->GetXAxisCaptionActor2D()->GetCaptionTextProperty()->SetColor(1, 0.3, 0.3);
    // make sure the label can be hidden by any geometry, like the axes
    axes->GetXAxisCaptionActor2D()->GetProperty()->SetDisplayLocationToBackground();

    vtkSmartPointer<vtkTextProperty> axeYTextProp = vtkSmartPointer<vtkTextProperty>::New();
    axeYTextProp->ShallowCopy(axeXTextProp);
    axes->GetYAxisCaptionActor2D()->SetAttachmentPoint(0.0, 1.0, 0.0);
    axes->GetYAxisCaptionActor2D()->SetCaptionTextProperty(axeYTextProp);
    axes->GetYAxisCaptionActor2D()->GetTextActor()->SetTextScaleModeToNone();
    axes->GetYAxisCaptionActor2D()->SetAttachmentPoint(0.0, 0.0, 1.0);
    axes->GetYAxisCaptionActor2D()->GetCaptionTextProperty()->SetColor(0.3, 1, 0.3);
    axes->GetYAxisCaptionActor2D()->GetProperty()->SetDisplayLocationToBackground();

    vtkSmartPointer<vtkTextProperty> axeZTextProp = vtkSmartPointer<vtkTextProperty>::New();
    axeZTextProp->ShallowCopy(axeXTextProp);
    axes->GetZAxisCaptionActor2D()->SetCaptionTextProperty(axeZTextProp);
    axes->GetZAxisCaptionActor2D()->GetTextActor()->SetTextScaleModeToNone();
    axes->GetZAxisCaptionActor2D()->GetCaptionTextProperty()->SetColor(0.3, 0.3, 1.0);
    axes->GetZAxisCaptionActor2D()->GetProperty()->SetDisplayLocationToBackground();

    // add the axes (not visible)
    axes->VisibilityOff();
    addProp(axes);

    //-- annotated cube (right, left, anterior, posterior, inferior, superior)
    // For image Display, the axis should be RAI :
    // - X: from Right    [R]  (the Right of the patient) to Left      [L] (the Left of the patient)
    // - Y: from Anterior [A]  (the Face of the patient)  to Posterior [P] (the Back of the patient)
    // - Z: from Inferior [I]  (the Feet of the patient)  to Superior  [S] (the Head of the patient)
    // But for a generic display, just set the axes names
    annotatedCube = vtkSmartPointer<vtkAnnotatedCubeActor>::New();
    annotatedCube->SetFaceTextScale(0.366667);
    annotatedCube->SetXMinusFaceText("X-");
    annotatedCube->SetXPlusFaceText("X+");
    annotatedCube->SetYMinusFaceText("Y-");
    annotatedCube->SetYPlusFaceText("Y+");
    annotatedCube->SetZMinusFaceText("Z-");
    annotatedCube->SetZPlusFaceText("Z+");

    vtkSmartPointer<vtkTransform> transform = vtkSmartPointer<vtkTransform>::New();
    transform->Identity();
    transform->Scale(0.05, 0.05, 0.05); // 5% of unit length
    annotatedCube->GetAssembly()->SetPosition(0.0, 0.0, 0.0);
    annotatedCube->GetAssembly()->SetUserTransform(transform);

    vtkSmartPointer<vtkProperty> acProp = annotatedCube->GetCubeProperty();
    acProp->SetColor(0.5, 1, 1);
    acProp = annotatedCube->GetTextEdgesProperty();
    acProp->SetLineWidth(4.0);
    acProp->SetDiffuse(0.0);
    acProp->SetAmbient(1.0);
    acProp->SetColor(0.0, 0.0, 0.0);
    acProp = annotatedCube->GetXPlusFaceProperty();
    acProp->SetColor(1, 0, 0);
    acProp->SetInterpolationToFlat();
    acProp = annotatedCube->GetXMinusFaceProperty();
    acProp->SetColor(1, 0, 0);
    acProp->SetInterpolationToFlat();
    acProp = annotatedCube->GetYPlusFaceProperty();
    acProp->SetColor(0, 1, 0);
    acProp->SetInterpolationToFlat();
    acProp = annotatedCube->GetYMinusFaceProperty();
    acProp->SetColor(0, 1, 0);
    acProp->SetInterpolationToFlat();
    acProp = annotatedCube->GetZPlusFaceProperty();
    acProp->SetColor(0, 0, 1);
    acProp->SetInterpolationToFlat();
    acProp = annotatedCube->GetZMinusFaceProperty();
    acProp->SetColor(0, 0, 1);
    acProp->SetInterpolationToFlat();
    acProp = annotatedCube->GetCubeProperty();
    acProp->SetColor(1.0, 1.0, 1.0);
    acProp->SetEdgeColor(0.0, 0.0, 0.0);
    acProp->SetEdgeVisibility(1);

    // add the annotatedCube
    annotatedCube->VisibilityOff();
    addProp(annotatedCube);

    // -- orientation decorations (that are set at the middle of each edge of the viewer)
    QString orientationDecorationLetters[4];
    orientationDecorationLetters[0] = "X-";
    orientationDecorationLetters[1] = "X+";
    orientationDecorationLetters[2] = "Y+";
    orientationDecorationLetters[3] = "Y-";

    vtkSmartPointer<vtkTextProperty> orientationDecorationsProp;
    orientationDecorationsProp = vtkSmartPointer<vtkTextProperty>::New();
    orientationDecorationsProp->SetFontSize(14);
    orientationDecorationsProp->ShadowOff();
    orientationDecorationsProp->SetColor(0.0, 1.0, 1.0);

    for (int i = 0; i < 4; i++) {
        orientationDecorationsTextMapper[i] = vtkSmartPointer<vtkTextMapper>::New();
        orientationDecorationsTextMapper[i]->SetInput(orientationDecorationLetters[i].toStdString().c_str());
        orientationDecorationsTextMapper[i]->GetTextProperty()->ShallowCopy(orientationDecorationsProp);
        orientationDecorationActors[i] = vtkSmartPointer<vtkActor2D>::New();
        orientationDecorationActors[i]->SetMapper(orientationDecorationsTextMapper[i]);
        orientationDecorationActors[i]->GetPositionCoordinate()->SetCoordinateSystemToNormalizedViewport();
    }

    //      y ^
    //        |
    //        |
    //  (0,0) +-----------> x
    // Bottom
    orientationDecorationActors[3]->GetPositionCoordinate()->SetValue(0.5, 0.05);
    // Top
    orientationDecorationActors[2]->GetPositionCoordinate()->SetValue(0.5, 0.95);
    // Right
    orientationDecorationActors[1]->GetPositionCoordinate()->SetValue(0.95, 0.5);
    // Left
    orientationDecorationActors[0]->GetPositionCoordinate()->SetValue(0.05, 0.5);

    for (const auto& orientationDecorationActor : orientationDecorationActors) {
        addProp(orientationDecorationActor);
    }

    //-- add logo
    // use the resource to create temporary file that can be read as a VTKImage
    QFile logoFile(":/camiTKIcon");
    QTemporaryFile* tempLogofile = QTemporaryFile::createNativeFile(logoFile);

    vtkSmartPointer<vtkPNGReader> imageReader = vtkPNGReader::New();
    imageReader->SetFileName(tempLogofile->fileName().toStdString().c_str());
    imageReader->Update();

    vtkSmartPointer<vtkLogoRepresentation> logoRepresentation = vtkSmartPointer<vtkLogoRepresentation>::New();
    logoRepresentation->SetImage(imageReader->GetOutput());
    // All position are relative to the window (= percentage = normalized viewport coordinate system)
    //
    // y
    // ^
    // +-------------+ (1,1)
    // |             |
    // |             |
    // |             | (1,0)
    // +-------------+----> x
    // (0,0)
    //
    // SetPosition = the lower left of the outline
    logoRepresentation->SetPosition(0.93, 0.01);
    // Position2 is relative to Position = width,height but still in percentage of the viewport
    logoRepresentation->SetPosition2(0.1, 0.05);
    logoRepresentation->GetImageProperty()->SetOpacity(0.7);
    logoRepresentation->SetShowBorderToOff(); // no border

    logoWidget = vtkSmartPointer<vtkLogoWidget>::New();
    logoWidget->SetInteractor(renderWindowInteractor);
    logoWidget->SetRepresentation(logoRepresentation);
    logoWidget->SetEnabled(true);

    // remove the temporary file
    QFile::remove(tempLogofile->fileName());

    //-- color scale widget
    // create an inverted lut
    vtkSmartPointer<vtkLookupTable> vtklup = vtkSmartPointer<vtkLookupTable>::New();
    // inverse hue (from blue to red)
    // other default ( saturation=[1,1] and value=[1,1]) are ok
    vtklup->SetHueRange(2.0 / 3.0, 0.0);
    //vtklup->SetTableRange(2.0 / 3.0, 0.0);
    vtklup->ForceBuild();

    // create the color bar widget itself
    colorBarWidget = vtkSmartPointer<vtkScalarBarWidget>::New();
    colorBarWidget->SetInteractor(renderWindowInteractor);
    colorBarWidget->RepositionableOn();
    colorBarWidget->SelectableOn();

    // set the the color bar parameters
    colorBarWidget->GetScalarBarActor()->SetLookupTable(vtklup);
    colorBarWidget->GetScalarBarActor()->SetOrientationToHorizontal();
    colorBarWidget->GetScalarBarActor()->SetNumberOfLabels(9);
    colorBarWidget->GetScalarBarActor()->SetTextPositionToPrecedeScalarBar();
    colorBarWidget->GetScalarBarActor()->DrawTickLabelsOn();
    colorBarWidget->GetScalarBarActor()->SetTextPad(3);
    colorBarWidget->GetScalarBarActor()->GetTitleTextProperty()->SetColor(0.0, 0.0, 0.0);
    colorBarWidget->GetScalarBarActor()->GetLabelTextProperty()->SetColor(0.0, 0.0, 0.0);

    // set the representation and the way the color bar is displayed
    vtkScalarBarRepresentation* rep =  vtkScalarBarRepresentation::SafeDownCast(colorBarWidget->GetRepresentation());
    rep->SetPosition(0.1, 0.01);
    rep->SetPosition2(0.8, 0.08);

    // default background
    displayGradient = false;

    // set the background color
    setBackgroundColor(0.0, 0.0, 0.0);
}


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

// --------------------- getInteractor ---------------------
vtkSmartPointer<QVTKInteractor> RendererWidget::getInteractor() {
    // get the current interactor (compatibility utility for VTK version)
    return interactor();
}

// --------------------- getRenderWindow ---------------------
vtkSmartPointer<vtkRenderWindow> RendererWidget::getRenderWindow() {
    return renderWindow();
}

//---------------------- screenshot ------------------------
void RendererWidget::screenshot(QString filename) {
    QString filePrefix = QFileInfo(filename).absolutePath() + "/" + QFileInfo(filename).baseName();

    // declare exporter/writer that can be used for more than one format
    vtkSmartPointer<vtkImageWriter> bitmapWriter = nullptr;
    vtkSmartPointer<vtkExporter> exporter = nullptr;
    vtkSmartPointer<vtkGL2PSExporter> gl2psExporter = nullptr;

    // check the extension
    QString extension = QFileInfo(filename).suffix().toLower();

    // use default extension if none given
    if (extension.length() == 0) {
        extension = ScreenshotFormatInfo::defaultFormat()->extension;
        filename += "." + extension;
    }

    if (ScreenshotFormatInfo::get(extension)->type == ScreenshotFormatInfo::EPS
            || ScreenshotFormatInfo::get(extension)->type == ScreenshotFormatInfo::PDF
            || ScreenshotFormatInfo::get(extension)->type == ScreenshotFormatInfo::TEX
            || ScreenshotFormatInfo::get(extension)->type == ScreenshotFormatInfo::SVG) {
        gl2psExporter = vtkSmartPointer<vtkGL2PSExporter>::New();
        CAMITK_WARNING_IF(gl2psExporter == nullptr, tr("vtkGL2PSExporter error: export to screenshot format %1 is not supported by your VTK version or installation: ").arg(ScreenshotFormatInfo::get(extension)->description));
        return;
    }

    // instanciate the exporter or set the correct file extenstion
    switch (ScreenshotFormatInfo::get(extension)->type) {
        case ScreenshotFormatInfo::BMP:
            bitmapWriter = vtkSmartPointer<vtkBMPWriter>::New();
            break;

        case ScreenshotFormatInfo::JPG:
            bitmapWriter = vtkSmartPointer<vtkJPEGWriter>::New();
            break;

        case ScreenshotFormatInfo::PS:
            bitmapWriter = vtkSmartPointer<vtkPostScriptWriter>::New();
            break;

        case ScreenshotFormatInfo::EPS:
            gl2psExporter->SetFileFormatToEPS();
            break;

        case ScreenshotFormatInfo::PDF:
            gl2psExporter->SetFileFormatToPDF();
            break;

        case ScreenshotFormatInfo::TEX:
            gl2psExporter->SetFileFormatToTeX();
            break;

        case ScreenshotFormatInfo::SVG:
            gl2psExporter->SetFileFormatToSVG();
            break;

        case ScreenshotFormatInfo::OBJ:
            exporter = vtkSmartPointer<vtkOBJExporter>::New();
            vtkOBJExporter::SafeDownCast(exporter)->SetFilePrefix(filePrefix.toStdString().c_str());
            break;

        case ScreenshotFormatInfo::RIB:
            exporter = vtkSmartPointer<vtkRIBExporter>::New();
            vtkRIBExporter::SafeDownCast(exporter)->SetFilePrefix(filePrefix.toStdString().c_str());
            break;

        case ScreenshotFormatInfo::VRML:
            exporter = vtkSmartPointer<vtkVRMLExporter>::New();
            vtkVRMLExporter::SafeDownCast(exporter)->SetFileName(filename.toStdString().c_str());
            break;

        case ScreenshotFormatInfo::NOT_SUPPORTED:
            break;

        case ScreenshotFormatInfo::PNG: // default
        default:
            bitmapWriter = vtkSmartPointer<vtkPNGWriter>::New();
            break;
    }

    vtkSmartPointer<vtkRenderWindow> currentRenderWindow = getRenderWindow();

    // write image
    if (exporter) {
        // remove gradient background temporarily during export
        bool displayGradientBackup = displayGradient;

        if (displayGradientBackup) {
            setGradientBackground(false);
        }

        exporter->SetInput(currentRenderWindow);
        exporter->Write();

        if (displayGradientBackup) {
            setGradientBackground(true);
        }

        refresh();
    }
    else {
        if (bitmapWriter != nullptr) {
            // create the proper vtk filter for export
            vtkSmartPointer<vtkWindowToImageFilter> imageFilter = vtkSmartPointer<vtkWindowToImageFilter>::New();
            imageFilter->SetInput(currentRenderWindow);

            // write image
            bitmapWriter->SetInputConnection(imageFilter->GetOutputPort());
            bitmapWriter->SetFileName(filename.toStdString().c_str());
            bitmapWriter->Write();
        }
        else {
            // specific options for GL2PS exporter
            if (gl2psExporter != nullptr) {
                gl2psExporter->CompressOff();
                gl2psExporter->SetSortToBSP();
                gl2psExporter->Write3DPropsAsRasterImageOff();
                gl2psExporter->SetFilePrefix(filePrefix.toStdString().c_str());
                gl2psExporter->DrawBackgroundOff();
                gl2psExporter->SetInput(currentRenderWindow);
                gl2psExporter->Write();
            }
        }
    }
}

//---------------------- refresh ------------------------
void RendererWidget::refresh() {
    // QVTKOpenGLWidget and QVTKOpenGLNativeWidget relies on VTK applications
    // calling `Render` on the window when data or rendering has changed.
    getRenderWindow()->Render();
}

//---------------------- setBackgroundColor ------------------------
void RendererWidget::setBackgroundColor(double r, double g, double b) {
    renderer->SetBackground(r, g, b);
}

//---------------------- getBackgroundColor ------------------------
void RendererWidget::getBackgroundColor(double& r, double& g, double& b) {
    renderer->GetBackground(r, g, b);
}

//--------------------- getGradientBackground ---------------------
bool RendererWidget::getGradientBackground() {
    return displayGradient;
}

//--------------------- setGradientBackground ---------------------
void RendererWidget::setGradientBackground(bool gb) {
    displayGradient = gb;

    if (displayGradient) {
        // Setup the background gradient
        renderer->GradientBackgroundOn();
        renderer->SetBackground(0.823, 0.823, 0.921); // light blue in the bottom
        renderer->SetBackground2(1, 1, 1); // white on top
    }
    else {
        renderer->GradientBackgroundOff();
    }
}

//--------------------- toogle3DRedBlue ---------------------
void RendererWidget::toogle3DRedBlue() {
    rendering3DRedBlue = !rendering3DRedBlue;
    vtkSmartPointer<vtkRenderWindow> renderWindow = getRenderWindow();

    if (rendering3DRedBlue) {
        // 3D red/blue
        renderWindow->StereoCapableWindowOn();
        renderWindow->SetStereoTypeToRedBlue();
        renderWindow->StereoRenderOn(); //On();
        renderWindow->StereoUpdate();
    }
    else {
        renderWindow->StereoRenderOff(); //On();
        renderWindow->StereoUpdate();
    }
}

//--------------------- toggleLogo ---------------------
void RendererWidget::toggleLogo(bool c) {
    displayLogo = c;
    logoWidget->SetEnabled(displayLogo);
}

//--------------------- setAreaPicking ---------------------
void RendererWidget::setAreaPicking(bool areaPicking) {
    pickInteractorStyle->SetAreaPicking(areaPicking);
}

// --------------------- getControlMode ---------------------
RendererWidget::ControlMode RendererWidget::getControlMode() const {
    return controlMode;
}

// --------------------- setControlMode ---------------------
void RendererWidget::setControlMode(ControlMode mode) {
    if (mode != controlMode) {
        controlMode = mode;

        switch (controlMode) {
            case JOYSTICK:
                controlInteractorStyle = vtkSmartPointer<vtkInteractorStyleJoystickCamera>::New();
                break;

            case TRACKBALL:
                controlInteractorStyle = vtkSmartPointer<vtkInteractorStyleTrackballCamera>::New();
                break;

            case TRACKBALL_2D: {
                // see https://vtk.org/doc/nightly/html/classvtkInteractorStyleImage.html
                // default "CTRL Left Mouse" spins the camera around its view plane normal
                // calling SetInteractionModeToImageSlicing() makes
                // "CTRL Left Mouse" to slice through the image instead
                vtkSmartPointer<vtkInteractorStyleImage> imageInteractor = vtkSmartPointer<vtkInteractorStyleImage>::New();
                imageInteractor->SetInteractionModeToImageSlicing();
                controlInteractorStyle = imageInteractor;
                break;
            }

            default : //NONE
                controlInteractorStyle = nullptr;
                break;
        }

        renderWindowInteractor->SetInteractorStyle(controlInteractorStyle);

        refresh();
    }
}

// --------------------- startPicking ---------------------
void RendererWidget::startPicking() {
    renderWindowInteractor->SetInteractorStyle(pickInteractorStyle);
    refresh();
}

// --------------------- endPicking ---------------------
void RendererWidget::endPicking() {
    renderWindowInteractor->SetInteractorStyle(controlInteractorStyle);
    refresh();
}

// ----------------------- setBackfaceCulling ----------------------
void RendererWidget::setBackfaceCulling(bool culling) {
    backfaceCulling = culling;

    // update all currently managed actors
    vtkSmartPointer<vtkActorCollection> actorCollection = renderer->GetActors();
    actorCollection->InitTraversal();
    vtkSmartPointer<vtkActor> actor;

    for (vtkIdType i = 0; i < actorCollection->GetNumberOfItems(); i++) {
        actor = actorCollection->GetNextActor();
        actor->GetProperty()->SetBackfaceCulling(backfaceCulling);
    }

    refresh();
}

// ----------------------- getBackfaceCulling -----------------------
bool RendererWidget::getBackfaceCulling() const {
    return backfaceCulling;
}

// ----------------------- setFxaaAntialiasing ----------------------
void RendererWidget::setFxaaAntialiasing(bool antialiasing) {
    // FXAA support was added in VTK 7.1
    renderer->SetUseFXAA(antialiasing);
}

// ----------------------- getFxaaAntialiasing -----------------------
bool RendererWidget::getFxaaAntialiasing() const {
    //FXAA support was added in VTK 7.1
    return renderer->GetUseFXAA();
}

// --------------------- setCameraOrientation ---------------------
void RendererWidget::setCameraOrientation(RendererWidget::CameraOrientation a) {
    cameraOrientation = a;
}

// --------------------- getCameraOrientation ---------------------
RendererWidget::CameraOrientation RendererWidget::getCameraOrientation() const {
    return cameraOrientation;
}

// ----------------------- setLightFollowCamera -----------------------
void RendererWidget::setLightFollowCamera(bool lightFollow) {
    lightFollowCamera = lightFollow;
    renderWindowInteractor->LightFollowCameraOff(); // legacy
    renderer->SetLightFollowCamera(lightFollowCamera);
}

// ----------------------- getLightFollowCamera -----------------------
bool RendererWidget::getLightFollowCamera() const {
    return lightFollowCamera;
}

// ----------------------- setPointSize -----------------------
void RendererWidget::setPointSize(double size) {
    pointSize = size;

    // update all currently managed actors
    vtkSmartPointer<vtkActorCollection> actorCollection = renderer->GetActors();
    actorCollection->InitTraversal();
    vtkSmartPointer<vtkActor> actor;

    for (vtkIdType i = 0; i < actorCollection->GetNumberOfItems(); i++) {
        actor = actorCollection->GetNextActor();
        actor->GetProperty()->SetPointSize(pointSize);
    }

    refresh();
}

// ----------------------- getPointSize -----------------------
double RendererWidget::getPointSize() const {
    return pointSize;
}

//----------------------- toggleAxes -------------------
void RendererWidget::toggleAxes(bool f) {
    axes->SetVisibility(f);
    annotatedCube->SetVisibility(f);
    // remove the axes and annotated cube from the computed bounds
    // (otherwise if they are the biggest object, they are going to set the bounds!)
    //axes->SetUseBounds(false);
    //annotatedCube->SetUseBounds(false);
    // update sizes
    updateAxes();
}

// ----------------------- updateAxes -----------------------
void RendererWidget::updateAxes() {
    // set the size depending on the bounding box
    // bounds[] = xmin, xmax, ymin, ymax, zmin, zmax
    double bounds[6];
    computeVisiblePropBounds(bounds);
    double xLength, yLength, zLength;
    xLength = fabs(bounds[1] - bounds[0]);
    yLength = fabs(bounds[3] - bounds[2]);
    zLength = fabs(bounds[5] - bounds[4]);
    double maxLength = (xLength > yLength) ?
                       ((xLength > zLength) ? xLength : zLength) :
                       ((yLength > zLength) ? yLength : zLength);

    // the size of the axe is about 10% the size of the bound
    maxLength /= 10.0;
    axes->SetTotalLength(maxLength, maxLength, maxLength);

    // the size of the annotated cube is about 5% of the size of the bound
    maxLength /= 2.0;
    vtkSmartPointer<vtkTransform> transform = vtkSmartPointer<vtkTransform>::New();
    transform->Identity();
    transform->Scale(maxLength, maxLength, maxLength);

    annotatedCube->GetAssembly()->SetUserTransform(transform);
}


// ----------------------- toogleOrientationDecorations ---------
void RendererWidget::toggleOrientationDecorations(bool f) {
    for (auto& orientationDecorationActor : orientationDecorationActors) {
        orientationDecorationActor->SetVisibility(f);
    }
}

// ----------------------- setOrientationDecorationsLetters ---------
void RendererWidget::setOrientationDecorationsLetters(QStringList letters) {
    if (letters.size() != 4) { // Check for validity
        CAMITK_TRACE("Warning : Trying to set invalid OrientationDecorationLetters!")
        return;
    }
    vtkSmartPointer<vtkTextProperty> orientationDecorationsProp;
    orientationDecorationsProp = vtkSmartPointer<vtkTextProperty>::New();
    orientationDecorationsProp->SetFontSize(14);
    orientationDecorationsProp->ShadowOff();
    orientationDecorationsProp->SetColor(0.0, 1.0, 1.0);

    for (int i = 0; i < 4; i++) {
        orientationDecorationsTextMapper[i]->SetInput(letters[i].toStdString().c_str());

        if ((letters[i] == "R") || (letters[i] == "L")) {
            orientationDecorationsTextMapper[i]->GetTextProperty()->SetColor(1.0, 0.0, 0.0);
        }
        else if ((letters[i] == "A") || (letters[i] == "P")) {
            orientationDecorationsTextMapper[i]->GetTextProperty()->SetColor(0.0, 1.0, 0.0);
        }
        else if ((letters[i] == "S") || (letters[i] == "I")) {
            orientationDecorationsTextMapper[i]->GetTextProperty()->SetColor(0.0, 0.0, 1.0);
        }
    }

}

// ----------------------- setColorScale -----------------------
void RendererWidget::setColorScale(bool state) {
    displayColorScale = state;

    if (displayColorScale) {
        colorBarWidget->EnabledOn();
    }
    else {
        colorBarWidget->EnabledOff();
    }
}

// ----------------------- getColorScale -----------------------
bool RendererWidget::getColorScale() const {
    return displayColorScale;
}

//------------------------- setColorScaleMinMax ---------------------
void RendererWidget::setColorScaleMinMax(double min, double max) {
    if (!displayColorScale) {
        setColorScale(true);
    }

    vtkLookupTable::SafeDownCast(colorBarWidget->GetScalarBarActor()->GetLookupTable())->SetTableRange(min, max);
}

//------------------------- setColorScaleTitle ---------------------
void RendererWidget::setColorScaleTitle(QString title) {
    if (!displayColorScale) {
        setColorScale(true);
    }

    colorBarWidget->GetScalarBarActor()->SetTitle(title.toStdString().c_str());
}

// ----------------------- addProp -----------------------
void RendererWidget::addProp(vtkSmartPointer<vtkProp> prop, bool refresh) {
    if (prop != nullptr && !containsProp(prop)) {
        //-- for actor set backface culling, point size
        vtkSmartPointer<vtkActor> aPart = vtkActor::SafeDownCast(prop);

        if (aPart) {
            aPart->InitPathTraversal();
            vtkSmartPointer<vtkAssemblyPath> path;

            while ((path = aPart->GetNextPath()) != nullptr) {
                vtkSmartPointer<vtkAssemblyNode> node = path->GetLastNode();
                vtkSmartPointer<vtkActor> a;

                if (node && (a = vtkActor::SafeDownCast(node->GetViewProp()))) {
                    a->GetProperty()->SetBackfaceCulling(backfaceCulling);

                    // check if the point representation is active, and if it is, set the proper size
                    if (a->GetProperty()->GetRepresentation() == VTK_POINTS) {
                        a->GetProperty()->SetPointSize(pointSize);
                    }
                }
            }
        }

        // now add it!
        renderer->AddViewProp(prop);

        // update axes size
        if (refresh) {
            updateAxes();
        }
    }

}

// ----------------------- containsProp -----------------------
bool RendererWidget::containsProp(vtkSmartPointer<vtkProp> prop) {
    return (renderer->GetViewProps()->IsItemPresent(prop) != 0);
}

// ----------------------- removeProp -----------------------
void RendererWidget::removeProp(vtkSmartPointer<vtkProp> prop, bool refresh) {
    if (prop != nullptr && containsProp(prop)) {
        renderer->RemoveViewProp(prop);

        // update axes size
        if (refresh) {
            updateAxes();
        }
    }
}

// ----------------------- computeVisiblePropBounds -----------------------
void RendererWidget::computeVisiblePropBounds(double* bounds) {
    renderer->ComputeVisiblePropBounds(bounds);
}

// ----------------------- resetClippingPlanes -----------------------
void RendererWidget::resetClippingPlanes(double* bounds) {
    if (bounds != nullptr) {
        renderer->ResetCameraClippingRange(bounds);
    }
    else {
        renderer->ResetCameraClippingRange();
    }

    refresh();
}

// --------------------- RotateCamera---------------------
void RendererWidget::rotateCamera(double angle, int axe) {
    vtkSmartPointer<vtkCamera> cam = renderer->GetActiveCamera();

    switch (axe) {
        case 0 : //axe X
            cam->Azimuth(0.0);
            cam->Elevation(angle);
            cam->OrthogonalizeViewUp();
            break;

        case 1: //axe y
            cam->Azimuth(angle);
            cam->Elevation(0.0);
            cam->OrthogonalizeViewUp();
            break;

        default :
            break;
    }

    resetClippingPlanes();
}


// --------------------- keyPressEvent -------------------------
void RendererWidget::keyPressEvent(QKeyEvent* event) {
    // NOTE: nothing but the following line should be here
    // All interaction should be defined directly in the class that use this instance
    // for example, see InteractiveViewer::keyPressEvent method
    event->ignore();
}


//--------------------- mousePressEvent ---------------------------
void RendererWidget::mousePressEvent(QMouseEvent* event) {
    if (QApplication::keyboardModifiers() == Qt::ControlModifier && event->button() == Qt::LeftButton) {
        startPicking();
        // The previous interactorStyle received the LeftButtonPressed event directly from VTK, but
        // as we just changed the interactorStyle to pickInteractorStyle, we have to send the event
        // again to the pickInteractorStyle.

        pickInteractorStyle->OnLeftButtonDown();
    }
    else {
        if (event->button() == Qt::RightButton) {
            emit rightButtonPressed();
        }
    }
}

//--------------------- mouseReleaseEvent ---------------------------
void RendererWidget::mouseReleaseEvent(QMouseEvent* event) {
    endPicking();
    // When picking started, the previous interactorStyle had received the OnLeftButtonDown event,
    // we must explicitely transmit the fact that the button is Up again
    auto style = vtkInteractorStyle::SafeDownCast(renderWindowInteractor->GetInteractorStyle());
    if (style != nullptr) {
        style->OnLeftButtonUp();
    }

}

// --------------------- mouseMoveEvent ---------------------------
void RendererWidget::mouseMoveEvent(QMouseEvent* event) {
}

// --------------------- resetCamera ----------------------
void RendererWidget::resetCamera() {
    resetCameraSettings();

    // auto calculate/update the bounds
    renderer->ResetCamera();
    resetClippingPlanes();
}

void RendererWidget::resetCamera(double* bounds) {
    resetCameraSettings();

    // set the proper bound
    renderer->ResetCamera(bounds);
    resetClippingPlanes(bounds);
}

// --------------------- resetCameraSettings ----------------------
void RendererWidget::resetCameraSettings() {
    vtkSmartPointer<vtkCamera> cam = renderer->GetActiveCamera();

    switch (cameraOrientation) {
        case LEFT_UP:
            // default position
            cam->SetPosition(0.0, 0.0, -1.0);
            cam->SetFocalPoint(0.0, 0.0, 0.0);
            cam->SetViewUp(0.0, 1.0, 0.0);
            cam->OrthogonalizeViewUp();
            break;

        case RIGHT_DOWN:
            // default position
            cam->SetPosition(0.0, 0.0, -1.0);
            cam->SetFocalPoint(0.0, 0.0, 0.0);
            cam->SetViewUp(0.0, -1.0, 0.0);
            cam->OrthogonalizeViewUp();
            break;

        // For Coronal view of Coronal Viewer
        // Image Reorientation Action Documentation...
        case LEFT_BACK:
            cam->SetPosition(0.0, -1.0, 0.0);
            cam->SetFocalPoint(0.0, 0.0, 0.0);
            cam->SetViewUp(0.0, 0.0, 1.0);
            cam->OrthogonalizeViewUp();
            break;

        case BACK_DOWN:
            cam->SetPosition(1.0, 0.0, 0.0);
            cam->SetFocalPoint(0.0, 0.0, 0.0);
            cam->SetViewUp(0.0, 0.0, 1.0);
            cam->OrthogonalizeViewUp();
            break;

        case RIGHT_UP:
        default:
            cam->SetPosition(0.0, 0.0, 1.0);
            cam->SetFocalPoint(0.0, 0.0, 0.0);
            cam->SetViewUp(0.0, 1.0, 0.0);
            cam->OrthogonalizeViewUp();
            break;
    }
}

// --------------------- getCameraSettings ----------------------
void RendererWidget::getCameraSettings(double* position, double* focalPoint, double* viewUp) {
    vtkSmartPointer<vtkCamera> cam = renderer->GetActiveCamera();

    cam->GetPosition(position);
    cam->GetFocalPoint(focalPoint);
    cam->GetViewUp(viewUp);
}

// --------------------- setActiveCamera --------------------------
void RendererWidget::setActiveCamera(vtkCamera* cam) {
    renderer->SetActiveCamera(cam);
}

// --------------------- setActiveCamera --------------------------
vtkCamera* RendererWidget::getActiveCamera() {
    return renderer->GetActiveCamera();
}

// --------------------- getMouse3DCoordinates ----------------------
void RendererWidget::getMouse3DCoordinates(double& x, double& y, double& z) {
    QPoint cursor_pos = mapFromGlobal(QCursor::pos());
    renderer = getInteractor()->FindPokedRenderer(cursor_pos.x(), cursor_pos.y());

    vtkSmartPointer<vtkCoordinate> c1 = vtkSmartPointer<vtkCoordinate>::New();
    c1->SetCoordinateSystemToDisplay();
    c1->SetValue(double(cursor_pos.x()), double(cursor_pos.y()));
    double* position = c1->GetComputedWorldValue(renderer);

    x = position[0];
    y = position[1];
    z = position[2];
}

// --------------------- setPicker ----------------------
void RendererWidget::setPicker(vtkSmartPointer<vtkAbstractPropPicker> woodyWood) {
    if (getInteractor()) {
        // I know, it is really Woody Wood "Pecker", but you know, with a french accent...
        getInteractor()->SetPicker(woodyWood);
    }
}

}
