/*****************************************************************************
* $CAMITK_LICENCE_BEGIN$
*
* CamiTK - Computer Assisted Medical Intervention ToolKit
* (c) 2001-2024 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 image component stuff
#include "ImageComponent.h"
#include "ImageComponentExtension.h"
#include "SingleImageComponent.h"
#include "ArbitrarySingleImageComponent.h"
#include "ImageOrientationHelper.h"

// -- Core stuff
#include "MeshComponent.h"
#include "Property.h"
#include "Frame.h"
// DEPRECATED (CamiTK 6.0) -> to be removed
#include "Viewer.h"

#include "Log.h"

// -- vtk stuff
// disable warning generated by clang about the surrounded headers
#include <CamiTKDisableWarnings>
#include <vtkColorTransferFunction.h>
#include <vtkVolumeProperty.h>
#include <vtkImageCast.h>
#include <vtkVolume.h>
#include <CamiTKReEnableWarnings>

#include <vtkUniformGrid.h>
#include <vtkTransformFilter.h>
#include <vtkImageReslice.h>
#include <vtkImageFlip.h>
#include <vtkUnstructuredGrid.h>
#include <vtkVertex.h>
#include <vtkActor.h>

#include <vtkPiecewiseFunction.h>

#include <vtkCellArray.h>
#include <vtkFloatArray.h>
#include <vtkPointData.h>
#include <vtkPoints.h>
#include <vtkPolyData.h>


// -- QT stuff
#include <QTableView>
#include <QStandardItemModel>

// -- stl stuff
#include <iostream>



namespace camitk {
// -------------------- constructors --------------------
ImageComponent::ImageComponent(const QString& file) : Component(file, "Image Volume") {
    init();
}

ImageComponent::ImageComponent(vtkSmartPointer<vtkImageData> anImageData, const QString& name, bool copy, ImageOrientationHelper::PossibleImageOrientations initialOrientation) : Component("", name) {
    init();
    setImageData(anImageData, copy, initialOrientation);
    setName(name);
    setModified();
}

// -------------------- destructor  --------------------
ImageComponent::~ImageComponent() {

    if (model != nullptr) {
        delete model;
    }

    if (selectionView != nullptr) {
        delete selectionView;
    }

    originalImageData = nullptr;
}

// -------------------- init --------------------
void ImageComponent::init() {

    originalImageData = nullptr;
    axialSlices     = nullptr;
    coronalSlices   = nullptr;
    sagittalSlices  = nullptr;
    arbitrarySlices = nullptr;
    volumeRenderingChild = nullptr;

    currentPixelPicked[0] = -1;
    currentPixelPicked[1] = -1;
    currentPixelPicked[2] = -1;

    lut = vtkSmartPointer<vtkWindowLevelLookupTable>::New();

    // Properties
    Property* nameProp  = new Property("Image Name", QVariant(getName()), tr("Name of the image displayed in the explorer"), "");
    addProperty(nameProp);

    Property* imageDims = new Property("Image Dimensions", QVariant("(0, 0, 0)"), tr("Dimensions of the image in each direction (x,y,z)"), "voxel number");
    imageDims->setReadOnly(true);
    addProperty(imageDims);

    Property* imageSize = new Property("Image Size", QVariant("(0.0, 0.0, 0.0)"), tr("Size of the image in each direction (x, y, z)"), "mm<sup>3</sup>");
    imageSize->setReadOnly(true);
    addProperty(imageSize);

    Property* voxelSize = new Property("Voxel Size", QVariant("(0.0, 0.0, 0.0)"), tr("Size of an image voxel in each direction (x, y, z)"), "mm <sup>3</sup>");
    voxelSize->setReadOnly(true);
    addProperty(voxelSize);

    Property* voxelDataType = new Property("Voxel Data Type", QVariant("None"), tr("Type of the data stored in each voxel"), "data type");
    voxelDataType->setReadOnly(true);
    addProperty(voxelDataType);

    addProperty(new Property("Display Image in 3D Viewer", QVariant(false), tr("Displays or not the image slices in the 3D viewer"), ""));

    // Picking properties
    selectionView = new QTableView();
    selectionView->setObjectName("Selection");
    selectionView->setSelectionBehavior(QAbstractItemView::SelectRows);
    model = new QStandardItemModel(10, 2, this);

    model->setData(model->index(0, 0), QVariant("Value"));
    model->setData(model->index(1, 0), QVariant("Voxel: x"));
    model->setData(model->index(2, 0), QVariant("Voxel: y"));
    model->setData(model->index(3, 0), QVariant("Voxel: z"));
    model->setData(model->index(4, 0), QVariant("Img coords: x"));
    model->setData(model->index(5, 0), QVariant("Img coords: y"));
    model->setData(model->index(6, 0), QVariant("Img coords: z"));
    model->setData(model->index(7, 0), QVariant("World coords: x"));
    model->setData(model->index(8, 0), QVariant("World coords: y"));
    model->setData(model->index(9, 0), QVariant("World coords: z"));

    selectionView->setModel(model);
    selectionView->setSelectionMode(QAbstractItemView::SingleSelection);
}

// -------------------- initImageProperties --------------------
void ImageComponent::initImageProperties() {
    // from QObject documentation, section "Detailed Description":
    // "To avoid never ending notification loops you can temporarily block signals with blockSignals()."
    setProperty("Image Name", QVariant(getName()));

    if (originalImageData != nullptr) {

        int* imgDims = originalImageData->GetDimensions();
        QString imageDims = "(";
        imageDims += QString::number(imgDims[0]);
        imageDims +=  ", ";
        imageDims += QString::number(imgDims[1]);
        imageDims +=  ", ";
        imageDims += QString::number(imgDims[2]);
        imageDims +=  ")";
        setProperty("Image Dimensions", QVariant(imageDims));

        double* spacing = originalImageData->GetSpacing();
        QString imageSpacing = "(";
        imageSpacing += QString::number(spacing[0]);
        imageSpacing += ", ";
        imageSpacing += QString::number(spacing[1]);
        imageSpacing += ", ";
        imageSpacing += QString::number(spacing[2]);
        imageSpacing += ")";
        setProperty("Voxel Size", QVariant(imageSpacing));

        QString imageSize = "(";
        imageSize += QString::number(imgDims[0] * spacing[0], 'f', 2);
        imageSize += ", ";
        imageSize += QString::number(imgDims[1] * spacing[1], 'f', 2);
        imageSize += ", ";
        imageSize += QString::number(imgDims[2] * spacing[2], 'f', 2);
        imageSize += ")";
        setProperty("Image Size", QVariant(imageSize));

        setProperty("Voxel Data Type", QString(originalImageData->GetScalarTypeAsString()));

        setProperty("Display Image in 3D Viewer", QVariant(true));

    }
}

// -------------------- updateProperty --------------------
void ImageComponent::updateProperty(QString name, QVariant value) {
    // update the non-read only properties
    if (name == "Display Image in 3D Viewer") {
        update3DViewer();
    }
    else if (name == "Image Name") {
        setName(value.toString());
    }
    // bypass read-only properties
    // (other specific properties are read only and should not be sent up to Component::updateProperty)
    else if (name != "Image Dimensions"
             && name != "Image Size"
             && name != "Voxel Size"
             && name != "Voxel Data Type"
             && name != "Initial Image Orientation") {
        Component::updateProperty(name, value);
    }
}

// -------------------- setImageData --------------------
void ImageComponent::setImageData(vtkSmartPointer<vtkImageData> anImageData,
                                  bool copy,
                                  ImageOrientationHelper::PossibleImageOrientations initialOrientation,
                                  vtkSmartPointer<vtkMatrix4x4> initialRotationMatrix) {

    this->initialOrientation = initialOrientation;

    originalImageData = nullptr;
    vtkSmartPointer<vtkImageData> inputImage = nullptr;

    if (copy) {
        // We need to use a shall because of some strange behaviour of mingw
        // (In mingw variable seems to become out of scope when going from one DLL to another one,
        // which generates a loss of smartness in the vtkSmartPointer)
        // Disconnect the previous Vtk Pipeline without memcopy
        // (equivalent of ITK DisconnectPipeline()...)
        // Note : this (i.e disconnect/deepcopy/shallowcopy) should not have to be done
        // in the components...
        inputImage = vtkSmartPointer<vtkImageData>::New();
        inputImage->ShallowCopy(anImageData);
    }
    else {
        inputImage = anImageData;
    }

    // 1. Get / compute the initial translation of the image
    double t_x, t_y, t_z;
    inputImage->GetOrigin(t_x, t_y, t_z);
    vtkSmartPointer<vtkTransform> initialTranslation = vtkSmartPointer<vtkTransform>::New();
    initialTranslation->Identity();
    initialTranslation->Translate(t_x, t_y, t_z);
    initialTranslation->Update();

    // 2. Get the orientation of the image
    double* imgSpacing = inputImage->GetSpacing();
    int* imgDims    = inputImage->GetDimensions();
    double dims[3];
    dims[0] = imgSpacing[0] * imgDims[0];
    dims[1] = imgSpacing[1] * imgDims[1];
    dims[2] = imgSpacing[2] * imgDims[2];
    vtkSmartPointer<vtkMatrix4x4> orientationToRAIMatrix = ImageOrientationHelper::getTransformToRAI(initialOrientation, dims[0], dims[1], dims[2]);

    // TODO: clean this lines by moving them to the constructor
    // 3. Store the orientation information as a property of the Image
    Property* orientationProperty = new Property(tr("Initial Image Orientation"), ImageOrientationHelper::getOrientationAsQString(initialOrientation), tr("The initial orientation of the image when it was acquired by the medical device. \nCamiTK transforms the read image and display it in the RAI format. This initial orientation is therefore informative only."), "");
    orientationProperty->setReadOnly(true);
    addProperty(orientationProperty);


    // 4. Store the Translation * orientation -> RAI = initialImageDataTransform
    initialImageDataTransform = vtkSmartPointer<vtkTransform>::New();
    vtkSmartPointer<vtkMatrix4x4> initialImageDataMatrix = vtkSmartPointer<vtkMatrix4x4>::New();
    vtkMatrix4x4::Multiply4x4(initialTranslation->GetMatrix(), orientationToRAIMatrix, initialImageDataMatrix);
    initialImageDataTransform->SetMatrix(initialImageDataMatrix);

    // 5. Store the translation * rotation * orientation -> RAI = initialFrameTransform
    initialFrameTransform = vtkSmartPointer<vtkTransform>::New();
    vtkSmartPointer<vtkMatrix4x4> initialFrameMatrix = vtkSmartPointer<vtkMatrix4x4>::New();
    rotationMatrix = initialRotationMatrix;

    if (!rotationMatrix) {
        rotationMatrix = vtkSmartPointer<vtkMatrix4x4>::New();
        rotationMatrix->Identity();
    }

    vtkMatrix4x4::Multiply4x4(initialTranslation->GetMatrix(), rotationMatrix, initialFrameMatrix);
    vtkMatrix4x4::Multiply4x4(initialFrameMatrix, orientationToRAIMatrix, initialFrameMatrix);
    initialFrameTransform->SetMatrix(initialFrameMatrix);

    // 6. Apply initialImageDataTransform to the image data
    vtkSmartPointer<vtkImageReslice> imageResliceFilter = vtkSmartPointer<vtkImageReslice>::New();
    imageResliceFilter->SetInputData(inputImage);
    imageResliceFilter->SetOutputDimensionality(3);
    imageResliceFilter->SetResliceAxes(initialImageDataTransform->GetMatrix());
    imageResliceFilter->Update();
    originalImageData = imageResliceFilter->GetOutput();

    // 7. Store initialFrameTransform as the current frame
    // note: we need to get another matrix instance for the transformation (else it is deleted)
    vtkSmartPointer<vtkMatrix4x4> originalMatrix = this->getTransform()->GetMatrix();
    vtkSmartPointer<vtkMatrix4x4> updatedMatrix = vtkSmartPointer<vtkMatrix4x4>::New();
    vtkMatrix4x4::Multiply4x4(originalMatrix, initialFrameTransform->GetMatrix(), updatedMatrix);
    vtkSmartPointer<vtkTransform> updatedTransform = vtkSmartPointer<vtkTransform>::New();
    updatedTransform->SetMatrix(updatedMatrix);
    setTransform(updatedTransform);

    // Clean smart pointers
    inputImage = nullptr;
    imageResliceFilter = NULL;

    // Build default lookup table
    initLookupTable();

    // Build the Axial, Sagittal and Coronal SingleImageComponents...
    buildImageComponents();

    // Init or update camitk:Properties
    initImageProperties();
}

// -------------------- getImageDataWithFrameTransform --------------------
vtkSmartPointer<vtkImageData> ImageComponent::getImageDataWithFrameTransform() {
    vtkSmartPointer<vtkImageData> imageDataFramed = vtkSmartPointer<vtkImageData>::New();
    imageDataFramed->DeepCopy(originalImageData);

    // 1. Put back image data to its original state
    vtkSmartPointer<vtkMatrix4x4> backToOriginalImageDataMatrix = vtkSmartPointer<vtkMatrix4x4>::New();
    backToOriginalImageDataMatrix->DeepCopy(initialImageDataTransform->GetMatrix());
    backToOriginalImageDataMatrix->Invert();
    vtkSmartPointer<vtkImageReslice> backToOrigingImageDataFilter = vtkSmartPointer<vtkImageReslice>::New();
    backToOrigingImageDataFilter->SetInputData(imageDataFramed);
    backToOrigingImageDataFilter->SetOutputDimensionality(3);
    backToOrigingImageDataFilter->SetResliceAxes(backToOriginalImageDataMatrix);
    imageDataFramed = backToOrigingImageDataFilter->GetOutput();
    backToOrigingImageDataFilter->Update();

    // 2. Retrieve the matrice of the user Muser which has been applied to the initial frame
    // Assuming Mframe = MinitFrame * Muser
    vtkSmartPointer<vtkMatrix4x4> initialFrameMatrixInverse = vtkSmartPointer<vtkMatrix4x4>::New();
    initialFrameMatrixInverse->DeepCopy(initialFrameTransform->GetMatrix());
    initialFrameMatrixInverse->Invert();
    const vtkSmartPointer<vtkTransform> frame = getTransform();
    vtkSmartPointer<vtkMatrix4x4> frameMatrix = frame->GetMatrix();
    vtkSmartPointer<vtkMatrix4x4> userMatrix = vtkSmartPointer<vtkMatrix4x4>::New();
    vtkMatrix4x4::Multiply4x4(initialFrameMatrixInverse, frameMatrix, userMatrix);

    vtkSmartPointer<vtkTransform> userTransform = vtkSmartPointer<vtkTransform>::New();
    userTransform->SetMatrix(userMatrix);

    // 3. Retrieve the translation of the user matrix and apply it to the image data
    // This way, the translation will be saved under the Offset tag
    double pos[3];
    userTransform->GetPosition(pos);
    vtkSmartPointer<vtkTransform> translationTransform = vtkSmartPointer<vtkTransform>::New();
    translationTransform->Identity();
    translationTransform->Translate(pos[0], pos[1], pos[2]);
    translationTransform->Update();
    vtkSmartPointer<vtkImageReslice> translationFilter = vtkSmartPointer<vtkImageReslice>::New();
    translationFilter->SetInputData(imageDataFramed);
    translationFilter->SetOutputDimensionality(3);
    translationFilter->SetResliceAxes(translationTransform->GetMatrix());
    imageDataFramed = translationFilter->GetOutput();
    translationFilter->Update();

    // 4. Retrieve the rotation matrix from the user matrix to manually save it
    // only if user has modify it
    if ((userMatrix->GetElement(0, 0) != 1) ||
            (userMatrix->GetElement(1, 1) != 1) ||
            (userMatrix->GetElement(2, 2) != 1)) {

        rotationMatrix = vtkSmartPointer<vtkMatrix4x4>::New();
        rotationMatrix->DeepCopy(userMatrix);
        rotationMatrix->SetElement(0, 3, 0.0);
        rotationMatrix->SetElement(1, 3, 0.0);
        rotationMatrix->SetElement(2, 3, 0.0);
    }

    return imageDataFramed;
}

// -------------------- initLookupTable --------------------
void ImageComponent::initLookupTable() {

    // TODO set in a betterway information about the lut thanks to image data
    if (originalImageData->GetNumberOfScalarComponents() == 1) {
        // Default values for grey level lut
        lut->SetTableRange(0, 255);
        lut->SetSaturationRange(1.0, 1.0);
        lut->SetHueRange(0.667, 0.667);
        lut->SetValueRange(1.0, 1.0);
        lut->SetAlphaRange(1.0, 1.0);
        lut->SetLevel((int)((getActualMaxColor() + getActualMinColor()) / 2));
        lut->SetWindow(getActualNumberOfColors());
        lut->SetRampToSCurve();
        lut->Build();  // effective build
    }
    else {
        // For colored image, we need to extract the information from the image
        // but a classic lut is useless (if there is one, it displays wrong information)
        // do not consider a lut for colored images
        lut = nullptr;
    }
}

// -------------------- replaceImageData --------------------
void ImageComponent::replaceImageData(vtkSmartPointer<vtkImageData> anImageData, bool copy, ImageOrientationHelper::PossibleImageOrientations initialOrientation) {
    // Delete ImageComponents
    if (axialSlices != nullptr) {
        removeChild(axialSlices);
        delete axialSlices;
        axialSlices = nullptr;
    }

    if (coronalSlices != nullptr) {
        removeChild(coronalSlices);
        delete coronalSlices;
        coronalSlices = nullptr;
    }

    if (sagittalSlices != nullptr) {
        removeChild(sagittalSlices);
        delete sagittalSlices;
        sagittalSlices = nullptr;
    }

    if (arbitrarySlices != nullptr) {
        removeChild(arbitrarySlices);
        delete arbitrarySlices;
        arbitrarySlices = nullptr;
    }

    if (volumeRenderingChild != nullptr) {
        removeChild(volumeRenderingChild);
        delete volumeRenderingChild;
        volumeRenderingChild = nullptr;
    }

    setImageData(anImageData, copy, initialOrientation);
}

// -------------------- getAxialSlices --------------------
SingleImageComponent* ImageComponent::getAxialSlices() {
    return axialSlices;
}

// -------------------- getCoronalSlices --------------------
SingleImageComponent* ImageComponent::getCoronalSlices() {
    return coronalSlices;
}

// -------------------- getSagittalSlices --------------------
SingleImageComponent* ImageComponent::getSagittalSlices() {
    return sagittalSlices;
}

// -------------------- getArbitrarySlices --------------------
ArbitrarySingleImageComponent* ImageComponent::getArbitrarySlices() {
    return arbitrarySlices;
}

// -------------------- setSingleImageComponents --------------------
void ImageComponent::setSingleImageComponents(SingleImageComponent* axialSlices, SingleImageComponent* sagittalSlices, SingleImageComponent* coronalSlices, ArbitrarySingleImageComponent* arbitrarySlices) {
    if (this->axialSlices != nullptr && axialSlices != this->axialSlices) {
        removeChild(this->axialSlices);
        delete this->axialSlices;
        this->axialSlices = axialSlices;
    }

    if (this->coronalSlices != nullptr && coronalSlices != this->coronalSlices) {
        removeChild(this->coronalSlices);
        delete this->coronalSlices;
        this->coronalSlices = coronalSlices;
    }

    if (this->sagittalSlices != nullptr && sagittalSlices != this->sagittalSlices) {
        removeChild(this->sagittalSlices);
        delete this->sagittalSlices;
        this->sagittalSlices = sagittalSlices;
    }

    if (this->arbitrarySlices != nullptr && arbitrarySlices != this->arbitrarySlices) {
        removeChild(this->arbitrarySlices);
        delete this->arbitrarySlices;
        this->arbitrarySlices = arbitrarySlices;
    }
}

// ---------------- getVolumeRenderingChild -------------------
MeshComponent* ImageComponent::getVolumeRenderingChild() {
    return volumeRenderingChild;
}

// -------------------- setImageName --------------------
void ImageComponent::setImageName(const QString& imageName) {
    // Change name in the ImageFilterAddon
    Component::setName(imageName);
}

// -------------------- setImageName --------------------
QString ImageComponent::getImageName() const {
    return getName();
}

// -------------------- setVisibility --------------------
void ImageComponent::setVisibility(QString viewerName, bool b) {
    if (viewerName == "3D Viewer") {
        // if setVisibility was called and the current state is different, update it
        if (property("Display Image in 3D Viewer").toBool() != b) {
            setProperty("Display Image in 3D Viewer", b);
        }
    }

    Component::setVisibility(viewerName, b);
}

void ImageComponent::setVisibility(Viewer* v, bool b) {
    CAMITK_WARNING(tr("setVisibility(Viewer*,bool) method is now deprecated and will be removed from CamiTK API, please use setVisibility(QString,bool) instead"))

    if (v != nullptr) {
        if (v->getName() == "3D Viewer") {
            // if setVisibility was called and the current state is different, update it
            if (property("Display Image in 3D Viewer").toBool() != b) {
                setProperty("Display Image in 3D Viewer", b);
            }
        }
        Component::setVisibility(v->getName(), b);
    }
}

// -------------------- update3DViewer --------------------
void ImageComponent::update3DViewer() {
    bool viewIn3D = property("Display Image in 3D Viewer").toBool();
    setVisibility("3D Viewer", viewIn3D);

    if (axialSlices != nullptr) {
        axialSlices->setVisibility("3D Viewer", viewIn3D);
    }

    if (coronalSlices != nullptr) {
        coronalSlices->setVisibility("3D Viewer", viewIn3D);
    }

    if (sagittalSlices != nullptr) {
        sagittalSlices->setVisibility("3D Viewer", viewIn3D);
    }

}

// -------------------- buildImageComponents --------------------
void ImageComponent::buildImageComponents() {
    if (axialSlices == nullptr) {
        axialSlices  = new SingleImageComponent(this, Slice::AXIAL, "Axial view", lut);
    }

    if (this->originalImageData->GetDataDimension() == 3) {
        if (coronalSlices == nullptr) {
            coronalSlices = new SingleImageComponent(this, Slice::CORONAL, "Coronal view", lut);
        }

        if (sagittalSlices == nullptr) {
            sagittalSlices = new SingleImageComponent(this, Slice::SAGITTAL, "Sagittal view", lut);
        }

        if (arbitrarySlices == nullptr) {
            arbitrarySlices = new ArbitrarySingleImageComponent(this, "Arbitrary view", lut);
        }

        if (volumeRenderingChild != nullptr) {
            delete volumeRenderingChild;
        }

        // compute bounding box
        vtkSmartPointer<vtkPolyData> bbox = getBoundingBox();
        volumeRenderingChild = new MeshComponent(this, bbox, "Volume Rendering");
        volumeRenderingChild->setRenderingModes(InterfaceGeometry::Wireframe);
        volumeRenderingChild->setActorColor(InterfaceGeometry::Wireframe, 0.0, 0.0, 0.0);
        volumeRenderingChild->setParentFrame(getFrame());
    }
    else {
        if (coronalSlices != nullptr) {
            delete coronalSlices;
            coronalSlices = nullptr;
        }

        if (sagittalSlices != nullptr) {
            delete sagittalSlices;
            sagittalSlices = nullptr;
        }

        if (arbitrarySlices != nullptr) {
            delete arbitrarySlices;
            arbitrarySlices = nullptr;
        }

        if (volumeRenderingChild != nullptr) {
            delete volumeRenderingChild;
            volumeRenderingChild = nullptr;
        }
    }

    // Let there be slices...
    setProperty("Display Image in 3D Viewer", true);

}

// -------------------- updateImageComponents --------------------
void ImageComponent::updateImageComponents() {
    if (axialSlices != nullptr) {
        axialSlices->setOriginalVolume(originalImageData);
    }

    if (coronalSlices != nullptr) {
        coronalSlices->setOriginalVolume(originalImageData);
    }

    if (sagittalSlices != nullptr) {
        sagittalSlices->setOriginalVolume(originalImageData);
    }

    if (arbitrarySlices != nullptr) {
        arbitrarySlices->setOriginalVolume(originalImageData);
    }

}

// -------------------- pixelPicked --------------------
void ImageComponent::pixelPicked(double x, double y, double z) {
    // x, y z are expressed in Real Image coordinates.
    currentPixelPicked[0] = x;
    currentPixelPicked[1] = y;
    currentPixelPicked[2] = z;

    // Get Voxel Index Coordinates
    int i, j, k;
    getLastPixelPicked(&i, &j, &k);

    double wx, wy, wz;
    getLastPointPickedWorldCoords(&wx, &wy, &wz);

    // Update each child even the one who is asking i order to display correctly the pixel pixed.
    foreach (Component* dc, getChildren()) {
        SingleImageComponent* child = dynamic_cast<SingleImageComponent*>(dc);

        if (child != nullptr) {
            child->setSlice(x, y, z);
        }
    }

    // Show picked pixel data in Selection property tab
    model->setData(model->index(0, 1), originalImageData->GetScalarComponentAsDouble(i, j, k, 0));
    model->setData(model->index(1, 1), i);
    model->setData(model->index(2, 1), j);
    model->setData(model->index(3, 1), k);
    model->setData(model->index(4, 1), x);
    model->setData(model->index(5, 1), y);
    model->setData(model->index(6, 1), z);
    model->setData(model->index(7, 1), wx);
    model->setData(model->index(8, 1), wy);
    model->setData(model->index(9, 1), wz);
    selectionView->setModel(model);

    // Select the PropertyExplorer's Selection tab
    setIndexOfPropertyExplorerTab(1);
}

// -------------------- getNumberOfColors --------------------
int ImageComponent::getNumberOfColors() const {
    double minColor = getMinColor();
    double maxColor = getMaxColor();
    int nbColors = (int)(maxColor - minColor + 1);
    return nbColors;
}

// -------------------- getMinColor --------------------
double ImageComponent::getMinColor() const {
    return (double) originalImageData->GetScalarTypeMin();
}

// -------------------- getMaxColor --------------------
double ImageComponent::getMaxColor() const {
    return (double)(originalImageData->GetScalarTypeMax());
}


// -------------------- getNumberOfColors --------------------
int ImageComponent::getActualNumberOfColors() const {
    int nbColors = (int)(originalImageData->GetScalarRange()[1] - originalImageData->GetScalarRange()[0] + 1);
    return nbColors;
}

// -------------------- getMinColor --------------------
double ImageComponent::getActualMinColor() const {
    return (double)(originalImageData->GetScalarRange()[0]);
}

// -------------------- getMaxColor --------------------
double ImageComponent::getActualMaxColor() const {
    return (double)(originalImageData->GetScalarRange()[1]);
}

// -------------------- getNumberOfSlices --------------------
int ImageComponent::getNumberOfSlices() const {
    return axialSlices->getNumberOfSlices();
}

// -------------------- setLut --------------------
void ImageComponent::setLut(vtkSmartPointer<vtkWindowLevelLookupTable> lookupTable) {
    lut = lookupTable;
}

// -------------------- getLut --------------------
vtkSmartPointer<vtkWindowLevelLookupTable> ImageComponent::getLut() {
    return lut;
}

const vtkSmartPointer<vtkWindowLevelLookupTable> ImageComponent::getLut() const {
    return lut;
}

// -------------------- setSelected --------------------
void ImageComponent::setSelected(const bool b, const bool) {
    foreach (Component* dc, getChildren()) {
        SingleImageComponent* child = dynamic_cast<SingleImageComponent*>(dc);

        if (child) {
            child->singleImageSelected(b);
        }
    }

    // highlight bounding box
    if (volumeRenderingChild != nullptr) {
        // red
        if (b) {
            volumeRenderingChild->setActorColor(InterfaceGeometry::Wireframe, 1.0, 0.0, 0.0);
        }
        else {
            // back in black
            volumeRenderingChild->setActorColor(InterfaceGeometry::Wireframe, 0.0, 0.0, 0.0);
        }
    }

    // do that only in the end, so that last selected will be this manager Component
    Component::setSelected(b, false);
}

// -------------------- getLastPixelPicked --------------------
void ImageComponent::getLastPixelPicked(int* x, int* y, int* z) {
    // Get Voxel Index Corrdinates
    double* spacing = originalImageData->GetSpacing();

    *x = rint(currentPixelPicked[0] / spacing[0]);
    *y = rint(currentPixelPicked[1] / spacing[1]);
    *z = rint(currentPixelPicked[2] / spacing[2]);
}

// -------------------- getLastPointPickedImageCoords --------------------
void ImageComponent::getLastPointPickedImageCoords(double* x, double* y, double* z) {
    *x = currentPixelPicked[0];
    *y = currentPixelPicked[1];
    *z = currentPixelPicked[2];
}

// -------------------- getLastPointPickedWorldCoords --------------------
void ImageComponent::getLastPointPickedWorldCoords(double* x, double* y, double* z) {
    // TODO Celine: Use Frame Service
    auto* wxyz = new double[3];
    getTransformFromWorld()->TransformPoint(currentPixelPicked, wxyz);

    *x = wxyz[0];
    *y = wxyz[1];
    *z = wxyz[2];
}

// -------------------- refresh --------------------
void ImageComponent::refresh() {
    foreach (Component* dc, childrenComponent) {
        dc->refresh();
    }
}

// -------------------- getBoundingBox --------------------
vtkSmartPointer<vtkPolyData> ImageComponent::getBoundingBox() {
    if (originalImageData == nullptr) {
        return nullptr;
    }

    double* bounds = originalImageData->GetBounds();
    double x[8][3] = {{bounds[0], bounds[2], bounds[4]}, {bounds[1], bounds[2], bounds[4]},
        {bounds[1], bounds[3], bounds[4]}, {bounds[0], bounds[3], bounds[4]},
        {bounds[0], bounds[2], bounds[5]}, {bounds[1], bounds[2], bounds[5]},
        {bounds[1], bounds[3], bounds[5]}, {bounds[0], bounds[3], bounds[5]}
    };
    vtkIdType pts[6][4] = {{0, 1, 2, 3}, {4, 5, 6, 7}, {0, 1, 5, 4},
        {1, 2, 6, 5}, {2, 3, 7, 6}, {3, 0, 4, 7}
    };
    vtkSmartPointer<vtkPolyData>    bbox    = vtkSmartPointer<vtkPolyData>::New();
    vtkSmartPointer<vtkPoints>      points  = vtkSmartPointer<vtkPoints>::New();
    vtkSmartPointer<vtkCellArray>   polys   = vtkSmartPointer<vtkCellArray>::New();
    vtkSmartPointer<vtkFloatArray>  scalars  = vtkSmartPointer<vtkFloatArray>::New();

    for (int i = 0; i < 8; i++) {
        points->InsertPoint(i, x[i]);
    }

    for (int i = 0; i < 6; i++) {
        polys->InsertNextCell(4, pts[i]);
    }

    bbox->SetPoints(points);
    bbox->SetPolys(polys);

    return bbox;
}

// -------------------- getNumberOfPropertyWidget --------------------
unsigned int ImageComponent::getNumberOfPropertyWidget() {
    return 1;
}

// -------------------- getPropertyWidgetAt --------------------
QWidget* ImageComponent::getPropertyWidgetAt(unsigned int i) {
    switch (i) {
        case 0:
            return selectionView;
            break;

        default:
            return nullptr;
    }
}

// -------------------- toVariant --------------------
QVariant ImageComponent::toVariant() const {
    // Call standard component method
    QVariant variant = Component::toVariant();
    QVariantMap variantMap = variant.toMap();
    // Add LUT data
    const auto lut = getLut();
    if (lut != nullptr) {
        double minColor[4], maxColor[4], firstColor[4], lastColor[4];
        lut->GetMinimumTableValue(minColor);
        lut->GetMaximumTableValue(maxColor);
        lut->GetTableValue(0, firstColor);
        lut->GetTableValue(lut->GetNumberOfTableValues() - 1, lastColor);

        // set window level and width according to user set up
        QVariantMap lutVariant {
            {"level", QVariant(lut->GetLevel())},
            {"window", QVariant(lut->GetWindow())},
            {"numberColorValues", QVariant(lut->GetNumberOfTableValues())},
            {"minimumTableValue", QVariant(QList<QVariant>({minColor[0], minColor[1], minColor[2], minColor[3]}))},
            {"maximumTableValue", QVariant(QList<QVariant>({maxColor[0], maxColor[1], maxColor[2], maxColor[3]}))},
            {"firstValueAlpha", QVariant(firstColor[3])},
            {"lastValueAlpha", QVariant(lastColor[3])}
        };

        variantMap.insert("LUT", lutVariant);
    }
    return variantMap;
}

// -------------------- fromVariant --------------------
void ImageComponent::fromVariant(const QVariant& variant) {
    Component::fromVariant(variant);
    QVariantMap newValuesMap = variant.toMap();
    if (newValuesMap.contains("LUT")) {
        auto lut = getLut();
        if (lut != nullptr) {
            QVariantMap lutVariant = newValuesMap.value("LUT").toMap();
            lut->SetLevel(lutVariant.value("level").toDouble());
            lut->SetWindow(lutVariant.value("window").toDouble());
            lut->SetNumberOfTableValues(lutVariant.value("numberColorValues").toInt());
            QList<QVariant> minimumTableValue = lutVariant.value("minimumTableValue").toList();
            lut->SetMinimumTableValue(minimumTableValue[0].toDouble(), minimumTableValue[1].toDouble(), minimumTableValue[2].toDouble(), minimumTableValue[3].toDouble());
            QList<QVariant> maximumTableValue = lutVariant.value("maximumTableValue").toList();
            lut->SetMaximumTableValue(maximumTableValue[0].toDouble(), maximumTableValue[1].toDouble(), maximumTableValue[2].toDouble(), maximumTableValue[3].toDouble());
            lut->Build();
            // In ImageLutWidget::applyLUT there is first a SetMaximumTableValue with alpha = 1.0, then build, then setting r,g,b,a with SetTableValue
            lut->SetTableValue(0, minimumTableValue[0].toDouble(), minimumTableValue[1].toDouble(), minimumTableValue[2].toDouble(), lutVariant.value("firstValueAlpha").toDouble());
            lut->SetTableValue(lutVariant.value("numberColorValues").toInt() - 1, maximumTableValue[0].toDouble(), maximumTableValue[1].toDouble(), maximumTableValue[2].toDouble(), lutVariant.value("lastValueAlpha").toDouble());
        }
        else {
            CAMITK_ERROR("Current ImageComponent does not have a LUT, cannot load its values");
        }
    }

}

}
