/*****************************************************************************
 * $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$
 ****************************************************************************/

#ifndef CAMITK_TESTTRANSFORMATIONMANAGER_H
#define CAMITK_TESTTRANSFORMATIONMANAGER_H

#include <QTest>

#include <ranges>

#include "FrameOfReference.h"
#include "Transformation.h"
#include "TransformationManager.h"
#include "MeshComponent.h"
#include "ImageComponent.h"
#include "ArbitrarySingleImageComponent.h"
#include "Application.h"
#include "InteractiveViewer.h"
#include "ExtensionManager.h"
#include "Action.h"
#include "Log.h"

#include <vtkPolyData.h>
#include <vtkSphereSource.h>
#include <vtkCamera.h>
#include <vtkMatrix4x4.h>
#include <vtkTransform.h>

namespace camitk {
// For better test error messages
char* toString(const FrameOfReference& frame);
char* toString(const Transformation& tr);
QString toString(const vtkSmartPointer<vtkMatrix4x4>& m);
}

using namespace camitk;

class TestTransformationManager: public QObject {
    Q_OBJECT

    /**
     * Create two frames and a Transformation linking them for testing
     */
    auto createFramesAndTransformation(QString frame1, QString frame2) {
        std::shared_ptr<FrameOfReference> fr1 = TransformationManager::addFrameOfReference(frame1);
        std::shared_ptr<FrameOfReference> fr2 = TransformationManager::addFrameOfReference(frame2);

        std::shared_ptr<Transformation> tr = TransformationManager::addTransformation(fr1, fr2);
        return std::make_tuple(fr1, fr2, tr);
    }

    /**
     * Checking for two vtkMatrix4x4 equality
     */
    bool isEqual(vtkSmartPointer<vtkMatrix4x4> m1, vtkSmartPointer<vtkMatrix4x4> m2) {
        if (m1 == nullptr || m2 == nullptr) {
            return false;
        }
        for (int i = 0; i < 16; ++i) {
            if (m1->GetElement(i / 4, i % 4) != m2->GetElement(i / 4, i % 4)) {
                return false;
            }
        }
        return true;
    }

    /**
     * Create a basic MeshComponent for tests
     */
    MeshComponent* createSphere(float radius = 1.0) {
        vtkSmartPointer<vtkSphereSource> sphereSource = vtkSmartPointer<vtkSphereSource>::New();
        sphereSource->SetRadius(radius);
        vtkSmartPointer<vtkPointSet> sphere(sphereSource->GetOutput());
        return new MeshComponent(sphere, QString("Sphere %1").arg(radius));
    }

    ImageComponent* createImage(QString name) {
        vtkNew<vtkImageData> imageData;
        imageData->SetDimensions(5, 5, 5);
        imageData->AllocateScalars(VTK_UNSIGNED_CHAR, 1);
        unsigned char* scalars = static_cast<unsigned char*>(imageData->GetScalarPointer());
        memset(scalars, 255, 5 * 5 * 5);
        return new ImageComponent(imageData, name);
    }

    int countDefaultIdentityToWorldTransformations() {
        int nbDefault = 0;
        for (Transformation* tr : TransformationManager::getTransformations()) {
            if (TransformationManager::isDefaultIdentityToWorld(tr)) {
                nbDefault++;
            }
        }
        return nbDefault;
    }

private slots:

    /// Before all tests, create a TransformationManager
    void initTestCase() {
        // show all log messages in the console
        Log::getLogger()->setLogLevel(InterfaceLogger::TRACE);
        // no log messages will generate a QMessageBox
        Log::getLogger()->setMessageBoxLevel(InterfaceLogger::NONE);
        // no time stamp for reproducible log diff
        Log::getLogger()->setTimeStampInformation(false);
    }

    /// Run after testing (failed or not)
    void cleanupTestCase() {
        // smart pointers take care of cleaning up
    }

    /// Cleanup after each test
    void cleanup() {
        // Remove all components
        ComponentList components = Application::getTopLevelComponents();
        for (auto* comp : components) {
            // Do not ask the user if the component was not saved
            comp->setModified(false);
            Application::close(comp); // as there is no main window refresh won't be called here... (see below)
        }

        // Set all viewers' frames to nullptr (so their frames are unused)
        for (Viewer* viewer : Application::getViewers()) {
            // Forcing a refresh() to cleanup actors InteractiveGeometryViewer of closed components
            // is required as not cleaning up the actorMap generates a segfault during application exit.
            // Explanation:
            // - during exit, the InteractiveGeometryViewerExtension is deleting all its viewers.
            // - During the InteractiveGeometryViewer destruction, the cleaning up of the viewer's
            // actorMap decreases the vtkSmartPointer number of references to the vtkOpenGLActor to zero
            // - The vtkOpenGLActor has therefore to be deleted long after the data (vtkPointSet in Geometry)
            // was deleted in the memory (this was done when the component was deleted).
            // This generates a segfault.
            // This is probably due to some reference count bug in VTK (e.g. the vtkSmartPointer<vtkPointSet> does
            // not registered that it as some actors still being used), or due to our misusage of VTK (there is
            // only one mapper in Geometry that is shared by multiple actors: surface, wireframe and points,
            // see Geometry::getActor)
            viewer->refresh();
        }
        // cleanup transformationManager
        TransformationManager::cleanupFramesAndTransformations();
        // qDebug() << "After cleanup, nb of frames " << TransformationManager::getFramesOfReference().size() << "\n";
    }

    /// Test FrameOfReference constructors
    void createFrame() {
        // create world frame
        TransformationManager::getWorldFrame(); // world frame is index 0 and color is white (tested in test worldFrame()...)

        // Create Frame with a name
        std::shared_ptr<FrameOfReference> fr = TransformationManager::addFrameOfReference("myFrame", "Test frame");
        QVERIFY(fr != nullptr);
        QVERIFY(fr->getName() == "myFrame");
        QVERIFY(fr->getDescription() == "Test frame");
        // Calling getIndex must initialize index to 1
        QVERIFY(fr->getIndex() == 1);
        // subsequent calls to getColor and getIndex should not modify the index
        fr->getColor();
        QVERIFY(fr->getIndex() == 1);

        // Create a frame from a QVariant
        TransformationManager::fromVariant(
        QVariantMap{ {"frames", QVariantList{
                    QVariantMap{ {"anatomicalOrientation", QVariant()},
                        {"description", "Data frame for component 'Mesh'"},
                        {"dimensions", 3},
                        {"name", "Mesh (Data Frame)"},
                        {
                            "units", QVariantList({
                                "",
                                "",
                                "",
                                "",
                                ""})
                        },
                        {"uuid", QUuid("04ef78c0-8a8d-4d52-b888-b7ac2ef79c36")}}
                }
            }});

        fr = TransformationManager::getFrameOfReferenceOwnership(QUuid("04ef78c0-8a8d-4d52-b888-b7ac2ef79c36"));

        QCOMPARE(fr->getName(), "Mesh (Data Frame)");
        QCOMPARE(fr->getDescription(), "Data frame for component 'Mesh'");
        QCOMPARE(fr->getUuid(), QUuid("04ef78c0-8a8d-4d52-b888-b7ac2ef79c36"));
        QCOMPARE(fr->getNumberOfDimensions(), 3);

        // Calling getColor() must initialize index to the next available value (i.e., 2)
        fr->getColor();
        // subsequent calls to getIndex should not modify the index
        QVERIFY(fr->getIndex() == 2);

        // Create a copy of a FrameOfReference
        std::shared_ptr<FrameOfReference> fr3 = TransformationManager::addFrameOfReference(*fr);
        QVERIFY(fr3 != nullptr);
        QVERIFY(fr3 != fr);
        QVERIFY(fr3->getUuid() != fr->getUuid());
        QVERIFY(fr3->getIndex() > fr->getIndex());
        QVERIFY(fr3->getIndex() == 3);
        QVERIFY(fr3->getColor().isValid());
        QVERIFY(fr3->getColor() != fr->getColor());

        // Set dimensions
        QVERIFY(fr->getNumberOfDimensions() == 3);
        fr->setNumberOfDimensions(4);
        QVERIFY(fr->getNumberOfDimensions() == 4);

        // Set Unit
        fr->setUnit(0, "mm");
        fr->setUnit(3, "s");
        QVERIFY(fr->getUnit(0) == "mm");
        QVERIFY(fr->getUnit(3) == "s");
        QVERIFY(fr->getUnit(1) == "");

        // Check that we cannot set a UUID to a frame which already has one
        QVERIFY(!fr->setUuid(QUuid::createUuid()));

        // Now set it to null, and try again (it should work)
        QVariantMap frameBackup = fr->toVariant().toMap();
        frameBackup["uuid"] = QUuid(); // Set to null
        fr->fromVariant(frameBackup);
        QVERIFY(fr->getUuid().isNull());
        QVERIFY(fr->setUuid(QUuid::createUuid()));
        QVERIFY(!fr->getUuid().isNull());

        // Set Anatomical orientation
        fr->setAnatomicalOrientation("LPS");
        QVERIFY(fr->getAnatomicalOrientationLabel(0, true) == "L");
        QVERIFY(fr->getAnatomicalOrientationLabel(1, true) == "P");
        QVERIFY(fr->getAnatomicalOrientationLabel(2, true) == "S");

        // Save state
        QVariant frameVariant = fr->toVariant();

        // Test other three-letter code
        fr->setAnatomicalOrientation("RAI");
        QVERIFY(fr->getAnatomicalOrientationLabel(0, false) == "L");
        QVERIFY(fr->getAnatomicalOrientationLabel(1, false) == "P");
        QVERIFY(fr->getAnatomicalOrientationLabel(2, false) == "S");

        // Test custom label
        fr->getAnatomicalOrientation().setMinLabel(0, "Xmin");
        QVERIFY(fr->getAnatomicalOrientation().getMinLabel(0) == "Xmin");
        QVERIFY(fr->getAnatomicalOrientationLabel(0, true) == "Xmin");

        // Restore from variant
        fr->fromVariant(frameVariant);
        QVERIFY(fr->getAnatomicalOrientationLabel(0, true) == "L");

        // Test '+' convention
        fr->setAnatomicalOrientation("RAI+");
        QVERIFY(fr->getAnatomicalOrientationLabel(0, false) == "R");

    }

    /// Test identity transformation
    void createIdentityTransformation() {
        auto [frame1, frame2, tr] = createFramesAndTransformation("Frame1", "Frame2");
        QVERIFY(frame1 != nullptr && frame2 != nullptr);

        QVERIFY(tr != nullptr);
        QVERIFY(tr->getFrom() == frame1.get());
        QVERIFY(tr->getTo() == frame2.get());

        // Check that it is an identity transform
        vtkMatrix4x4* matrix = tr->getMatrix();
        QVERIFY(matrix != nullptr);
        QVERIFY(matrix->IsIdentity());

        // Try to create a transformation between a frame and itself (should fail)
        std::shared_ptr<Transformation> invalidTr = TransformationManager::addTransformation(frame1, frame1);
        QVERIFY(invalidTr == nullptr);
        QVERIFY(!TransformationManager::getTransformation(frame1.get(), frame1.get()));

        TransformationManager::cleanupFramesAndTransformations();
    }

    /// Test a custom Transformation
    void createSpecificTransformation() {
        auto [frame1, frame2, tr] = createFramesAndTransformation("Frame3", "Frame4");
        auto m = vtkSmartPointer<vtkMatrix4x4>::New();
        m->SetElement(1, 1, 2.0); // Scaling axis X
        m->SetElement(1, 3, 12.0); // Translation axis Y
        m->SetElement(3, 3, -5.0); // Translation axis Z
        TransformationManager::updateTransformation(tr.get(), m.Get());
        vtkMatrix4x4* m2 = tr->getTransform()->GetMatrix();
        // Default is identity
        QVERIFY(m2->GetElement(2, 2) == 1 && m2->GetElement(3, 1) == 0);
        // Check values that were set manually
        QCOMPARE(m2->GetElement(1, 1), 2.0);
        QCOMPARE(m2->GetElement(1, 3), 12.0);
        QCOMPARE(m2->GetElement(3, 3), -5.0);

        // Set name and description
        tr->setName("New name");
        QCOMPARE(tr->getName(), "New name");
        tr->setDescription("New description");
        QCOMPARE(tr->getDescription(), "New description");

        // Check that this transformation has no sources
        QVERIFY(!TransformationManager::hasSources(tr.get()));
    }


    ///  Test that TransformationManager can compute inverse transformations and keep them updated
    void invertTransformation() {
        int nbTrBefore = TransformationManager::getTransformations().size();

        // Create two frames and a transformation
        auto [frame1, frame2, tr] = createFramesAndTransformation("Frame5", "Frame6");
        QVERIFY(tr != nullptr);

        int nbTrAfter = TransformationManager::getTransformations().size();

        // Check that there are two additional transformations in the TransformationManager: the new transform and its inverse
        QVERIFY(nbTrBefore + 2 == nbTrAfter);

        // Check that it is identity
        QVERIFY(tr->getMatrix()->IsIdentity());

        // Check that the inverse was added too
        QVERIFY(TransformationManager::getTransformation(frame2.get(), frame1.get()) != nullptr);

        // Get the inverse transformation
        Transformation* tr2 = TransformationManager::getTransformation(frame2.get(), frame1.get());
        QVERIFY(tr2 != nullptr);

        // Check it is identity
        QVERIFY(tr2->getMatrix()->IsIdentity());

        // Check that this transformation has a source
        QVERIFY(TransformationManager::hasSources(tr2));

        // Check that it has one source
        QVERIFY(TransformationManager::getSources(tr2).size() == 1);

        // Check that it is present in the transformations computed from tr
        //QVERIFY(TransformationManager::getTransformationsComputedFrom(tr.get()).contains(tr2));

        // Now add a scaling
        tr->getTransform()->Scale(1, 1, 4);
        // qDebug() << "Direct matrix " << toString(tr->getMatrix());
        // qDebug() << "Inverse matrix " << toString(tr2->getMatrix());
        QCOMPARE(tr2->getMatrix()->GetElement(2, 2), 0.25);

        // Set a translation using setTransform and check if the inverse is updated
        auto vtkTr = vtkSmartPointer<vtkTransform>::New();
        vtkTr->Identity();
        vtkTr->Translate(10.0, 0, 0);
        TransformationManager::updateTransformation(tr.get(), vtkTr);
        QCOMPARE(tr2->getMatrix()->GetElement(0, 3), -10.0);
        // qDebug() << toString(tr2->getMatrix());

        // Set a translation using setMatrix and check if the inverse is updated
        auto vtkMat = vtkSmartPointer<vtkMatrix4x4>::New();
        vtkMat->Identity();
        vtkMat->SetElement(1, 3, 123.0);
        TransformationManager::updateTransformation(tr.get(), vtkMat.Get());
        QCOMPARE(tr2->getMatrix()->GetElement(1, 3), -123.0);
    }

    /// @brief TransformationManager should be able to combine multiple Transformations
    void combineTransformations() {
        // Add four frames and two transformations f1->f2, f3->f4, then add a Transformation f2->f3 and get f1->f4 transformation
        auto [frame1, frame2, tr1] = createFramesAndTransformation("Frame7", "Frame8");
        QVERIFY(tr1 != nullptr);
        tr1->getTransform()->Translate(1, 2, 3);
        auto [frame3, frame4, tr3] = createFramesAndTransformation("Frame9", "Frame10");
        tr3->getTransform()->Translate(1, -5, 30);
        Transformation* tr2 = TransformationManager::addTransformation(frame2, frame3).get();
        tr2->getTransform()->Translate(8, 3, -13);
        // Check that the path is found
        QVERIFY(TransformationManager::hasPath(frame1.get(), frame4.get()));
        // Check that we get the Transformation with combined translations
        Transformation* tr4 = TransformationManager::getTransformation(frame1.get(), frame4.get());
        QVERIFY(tr4 != nullptr);
        QCOMPARE(tr4->getTransform()->GetNumberOfConcatenatedTransforms(), 3);
        QCOMPARE(tr4->getMatrix()->GetElement(0, 3), 10.0);
        QCOMPARE(tr4->getMatrix()->GetElement(1, 3), 0.0);
        QCOMPARE(tr4->getMatrix()->GetElement(2, 3), 20.0);

        // Now update tr2, check that tr4 is updated
        // Update the Transform
        tr2->getTransform()->Translate(-8, 0, 0); // Becomes a translation (0, 3, -13)
        QCOMPARE(tr4->getMatrix()->GetElement(0, 3), 2.0); // Combined translation should be 2

        // Update the Matrix of the Transform
        auto vtkMat = vtkSmartPointer<vtkMatrix4x4>::New();
        vtkMat->Identity();
        vtkMat->SetElement(0, 3, 98.0);
        TransformationManager::updateTransformation(tr2, vtkMat.Get());
        QCOMPARE(tr4->getMatrix()->GetElement(0, 3), 100.0); // Combined translation should be 100
    }

    /// @brief Check that TransformationManager can compute a complex path of Transformations
    void findTransformationsPath() {
        // Create 8 frames and transformations fr1->fr2<-fr3<-fr4 fr5->fr6->fr2 fr7->fr8
        auto [frame1, frame2, tr1_2] = createFramesAndTransformation("Frame11", "Frame12");
        auto [frame4, frame3, tr4_3] = createFramesAndTransformation("Frame14", "Frame13");
        auto [frame5, frame6, tr5_6] = createFramesAndTransformation("Frame15", "Frame16");
        auto [frame7, frame8, tr7_8] = createFramesAndTransformation("Frame17", "Frame18");

        // Find a non-existing tranformation
        QVERIFY(!TransformationManager::hasPath(frame1.get(), frame4.get()));
        QVERIFY(!TransformationManager::hasPath(frame8.get(), frame2.get()));

        // Try to find an impossible transformation
        QVERIFY(!TransformationManager::hasPath(frame1.get(), nullptr));
        QVERIFY(!TransformationManager::hasPath(nullptr, frame1.get()));
        QVERIFY(TransformationManager::hasPath(frame1.get(), frame1.get()));

        // Try to compute a path, it should fail
        QVERIFY(TransformationManager::getTransformation(frame8.get(), frame2.get()) == nullptr);
        // Try again to check if there is a path (to detect possible side effects of getTransformation)
        QVERIFY(!TransformationManager::hasPath(frame8.get(), frame2.get()));

        Transformation* tr3_2 = TransformationManager::addTransformation(frame3, frame2).get();
        Transformation* tr6_2 = TransformationManager::addTransformation(frame6, frame2).get();

        // Find an existing Transformation
        Transformation* wantedTransform = TransformationManager::getTransformation(frame1.get(), frame2.get());
        QVERIFY(wantedTransform != nullptr);
        wantedTransform = TransformationManager::getTransformation(frame6.get(), frame2.get());
        QVERIFY(wantedTransform != nullptr);

        // Get a complex path with inversions fr5->fr4 and check sources
        Transformation* tr5_4 = TransformationManager::getTransformation(frame5.get(), frame4.get());
        QVERIFY(tr5_4 != nullptr);

        // Try to get a path which does not exist fr1->fr8
        Transformation* tr1_8 = TransformationManager::getTransformation(frame1.get(), frame8.get());
        QVERIFY(tr1_8 == nullptr);

        // Multipath should fail: adding a path that creates a circle fr5->fr1
        Transformation* tr5_1 = TransformationManager::addTransformation(frame5, frame1).get();
        QVERIFY(tr5_1 == nullptr);
    }

    /// @brief Check that TransformationManager sets the worldFrame as needed
    /// Also check that default identity transforms are created as needed, and they are overriden
    /// when a Transformation is added
    void worldFrame() {
        int nbTrBefore = TransformationManager::getTransformations().size();
        QVERIFY(nbTrBefore == 0);
        int nbFrBefore = TransformationManager::getFramesOfReference().size();
        QVERIFY(nbFrBefore == 0);

        // check world frame is created with index 0 and white color
        const FrameOfReference* worldFrame = TransformationManager::getWorldFrame();
        QVERIFY(TransformationManager::getFramesOfReference().size() == 1);
        QVERIFY(TransformationManager::getFramesOfReference().at(0)->getIndex() == 0);
        QVERIFY(QVariant(TransformationManager::getFramesOfReference().at(0)->getColor()).toString() == "#ffffff");
        nbFrBefore = TransformationManager::getFramesOfReference().size();

        // Create a Component
        auto component = std::make_unique<MeshComponent>(vtkSmartPointer<vtkPolyData>::New(), "EmptyMeshComponent");
        QVERIFY(component->getFrame() != nullptr);

        // Create a second one
        auto component2 = std::make_unique<MeshComponent>(vtkSmartPointer<vtkPolyData>::New(), "EmptyMeshComponent2");
        QVERIFY(component2->getFrame() != nullptr);

        // Should have added 2 frames
        int nbFrAfter = TransformationManager::getFramesOfReference().size();
        QCOMPARE(nbFrBefore + 2, nbFrAfter);

        // This should add a default identity transform from both components' frames to world frame
        TransformationManager::ensurePathToWorld(component->getFrame());
        TransformationManager::ensurePathToWorld(component2->getFrame());
        int nbTrAfter = TransformationManager::getTransformations().size();
        // 2 transformations (default identity to world frame) and their 2 inverse added (4 in total)
        QCOMPARE(nbTrBefore + 4, nbTrAfter);

        // Check if the default transformation is identity
        Transformation* trWorldFrame = TransformationManager::getTransformation(component->getFrame(), TransformationManager::getWorldFrame());
        QVERIFY(trWorldFrame != nullptr);
        QVERIFY(trWorldFrame->getMatrix()->IsIdentity());

        // Try to replace it and check if the default identity was replaced
        vtkSmartPointer<vtkTransform> vtkTr2_1 = vtkSmartPointer<vtkTransform>::New();
        vtkTr2_1->Translate(1, 2, 3);
        std::shared_ptr<Transformation> tr2_1 = TransformationManager::addTransformation(component2->getFrame(), component->getFrame(), vtkTr2_1);
        // If it was replaced correctly, this should not be null
        QVERIFY(tr2_1 != nullptr);
    }

    /// @brief Check that TransformationManager can provide transforms between any Component and the frame of a viewer
    void viewerFrame() {

        // Create a Component
        auto component = std::make_unique<MeshComponent>(vtkSmartPointer<vtkPolyData>::New(), "EmptyMeshComponent");
        QVERIFY(component->getFrame() != nullptr);

        // Create a frame for the viewer
        std::shared_ptr<FrameOfReference> viewerFr = TransformationManager::addFrameOfReference("Viewer", "");
        QVERIFY(viewerFr != nullptr);

        // This will set an identity transformation between worldFrame and the component's main frame
        // compFrame ---default---> worldFrame
        TransformationManager::ensurePathToWorld(component->getFrame());

        // There is no transformation path from viewerFr to worldFrame, a default one (identity) should be generated
        // viewerFr ---default---> worldFrame
        TransformationManager::ensurePathToWorld(viewerFr.get());
        Transformation* viewer2world = TransformationManager::getTransformation(viewerFr.get(), TransformationManager::getWorldFrame());
        QVERIFY(TransformationManager::isDefaultIdentityToWorld(viewer2world));
        QVERIFY(viewer2world->getMatrix()->IsIdentity());
        QVERIFY(TransformationManager::isDefaultIdentityToWorld(viewer2world));

        // check that we cannot get ownership of a default identity to world
        QVERIFY(TransformationManager::getTransformationOwnership(viewerFr.get(), TransformationManager::getWorldFrame()) == nullptr);
        QVERIFY(TransformationManager::getTransformationOwnership(viewer2world->getUuid()) == nullptr);
        QVERIFY(TransformationManager::getTransformationOwnership(viewer2world) == nullptr);

        // Add a Transformation, check that it was added (replacing the default ones)
        // viewerFr ------> compFrame
        auto comp2viewerTransform = vtkSmartPointer<vtkTransform>::New();
        comp2viewerTransform->Translate(1, 2, 3);
        Transformation* comp2viewerAdded = TransformationManager::addTransformation(viewerFr.get(), component->getFrame(), comp2viewerTransform).get();
        QVERIFY(comp2viewerAdded != nullptr);

        // Check that we get it when we ask for it again
        Transformation* viewer2comp = TransformationManager::getTransformation(viewerFr.get(), component->getFrame());
        QCOMPARE(viewer2comp->getMatrix()->GetElement(0, 3), 1);
        QCOMPARE(viewer2comp->getMatrix()->GetElement(1, 3), 2);
        QCOMPARE(viewer2comp->getMatrix()->GetElement(2, 3), 3);
        // And its inverse works too
        Transformation* comp2viewer = TransformationManager::getTransformation(component->getFrame(), viewerFr.get());
        QCOMPARE(comp2viewer->getMatrix()->GetElement(0, 3), -1);
        QCOMPARE(comp2viewer->getMatrix()->GetElement(1, 3), -2);
        QCOMPARE(comp2viewer->getMatrix()->GetElement(2, 3), -3);

        // The default transformations to world were removed
        QVERIFY(TransformationManager::getTransformationOwnership(viewerFr.get(), TransformationManager::getWorldFrame()) == nullptr);
        QVERIFY(TransformationManager::getTransformationOwnership(TransformationManager::getWorldFrame(), viewerFr.get()) == nullptr);

        // Ask for a transformation from Component to World (recreating compFrame ---default---> worldFrame)
        TransformationManager::ensurePathToWorld(component->getFrame());
        QVERIFY(TransformationManager::getTransformation(component->getFrame(), TransformationManager::getWorldFrame()) != nullptr);

        // Ask for the composite viewerFr------>worldFrame = viewerFrame --> compFrame --> worldFrame
        viewer2world = TransformationManager::getTransformation(viewerFr.get(), TransformationManager::getWorldFrame());
        QVERIFY(viewer2world != nullptr);
        QCOMPARE(viewer2world->getMatrix()->GetElement(0, 3), 1);
        QCOMPARE(viewer2world->getMatrix()->GetElement(1, 3), 2);
        QCOMPARE(viewer2world->getMatrix()->GetElement(2, 3), 3);

    }

    /// @brief Check that World is managed correctly by TransformationManager
    void checkTransformationToWorld() {

        // Ensure World Frame exists
        const FrameOfReference* worldFrame = TransformationManager::getWorldFrame();
        QVERIFY(worldFrame != nullptr);

        // Create a basic sphere Component
        Component* comp = createSphere(2.0);

        // Check it has a valid frame
        const FrameOfReference* sphereFrame = comp->getFrame();
        QVERIFY(sphereFrame != nullptr);

        // Check there is no transformation yet to the world
        QVERIFY(TransformationManager::getTransformationOwnership(sphereFrame, worldFrame) == nullptr);

        // Create a default one
        TransformationManager::ensurePathToWorld(sphereFrame);
        Transformation* sphere2world = TransformationManager::getTransformation(sphereFrame, TransformationManager::getWorldFrame());

        // Check it exists, and it is default
        QVERIFY(sphere2world != nullptr);
        QVERIFY(TransformationManager::isDefaultIdentityToWorld(sphere2world) == true);

        // Check its inverse was also created and is also in the default identity to world set
        QVERIFY(TransformationManager::isDefaultIdentityToWorld(TransformationManager::getTransformation(worldFrame, sphereFrame)));
        QVERIFY(TransformationManager::getSources(TransformationManager::getTransformation(worldFrame, sphereFrame)).size() == 1);

        // Now try to create a translation
        auto vtkSphere2worldTranslation = vtkSmartPointer<vtkTransform>::New();
        vtkSphere2worldTranslation->Translate(1, 2, 3);
        auto sphere2worldTranslation = TransformationManager::addTransformation(sphereFrame, TransformationManager::getWorldFrame(), vtkSphere2worldTranslation).get();

        // Check it was created
        QVERIFY(sphere2worldTranslation != nullptr);

        // Check it is still a Translation
        QCOMPARE(sphere2worldTranslation->getMatrix()->GetElement(0, 3), 1);

        // Check if it is default
        QVERIFY(TransformationManager::isDefaultIdentityToWorld(sphere2worldTranslation) == false);

        // Check if its inverse exists and is default
        QVERIFY(TransformationManager::getTransformation(worldFrame, sphereFrame) != nullptr);
        QVERIFY(TransformationManager::isDefaultIdentityToWorld(TransformationManager::getTransformationOwnership(worldFrame, sphereFrame).get()) == false);

        // Check its inverse is a Translation
        QCOMPARE(TransformationManager::getTransformation(worldFrame, sphereFrame)->getMatrix()->GetElement(0, 3), -1);

    }

    /// Check that removing a Transformation destroys its dependants
    void checkRemoveTransformation() {
        auto [frameA, frameB, trA_B] = createFramesAndTransformation("FrameA", "FrameB");
        auto [frameD, frameC, trD_C] = createFramesAndTransformation("FrameD", "FrameC");
        auto [frameF, frameE, trF_E] = createFramesAndTransformation("FrameF", "FrameE");
        TransformationManager::ensurePathToWorld(frameB.get());
        auto trB_World = TransformationManager::getTransformation(frameB.get(), TransformationManager::getWorldFrame());
        TransformationManager::ensurePathToWorld(frameC.get());
        auto trC_World = TransformationManager::getTransformation(frameC.get(), TransformationManager::getWorldFrame());

        // Create composite not using trE_A
        QVERIFY(TransformationManager::getTransformation(frameB.get(), frameC.get()) != nullptr);

        // Creating trE_A
        auto matrixTransl10 = vtkMatrix4x4::New();
        matrixTransl10->SetElement(0, 3, 10.0);
        auto trE_A = TransformationManager::addTransformation(frameE, frameA, matrixTransl10);

        // Create composite transformations using trE_A
        QVERIFY(TransformationManager::getTransformation(frameF.get(), frameA.get()) != nullptr);
        QVERIFY(TransformationManager::getTransformation(frameF.get(), frameB.get()) != nullptr);
        QVERIFY(TransformationManager::getTransformation(frameF.get(), frameD.get()) != nullptr);
        QVERIFY(TransformationManager::getTransformation(frameB.get(), frameE.get()) != nullptr);
        // This one does not need trE_A, but will be computed with it
        QVERIFY(TransformationManager::getTransformation(frameA.get(), frameD.get()) != nullptr);
        // for (auto myTr : TransformationManager::getSources(TransformationManager::getTransformation(frameA.get(), frameD.get()))) {
        //     qDebug() << myTr->getName();
        // }
        TransformationManager::removeTransformation(trE_A);
        // Check that all dependants have been removed
        QVERIFY(TransformationManager::getTransformationOwnership(frameF.get(), frameA.get()) == nullptr);
        QVERIFY(TransformationManager::getTransformationOwnership(frameF.get(), frameB.get()) == nullptr);
        QVERIFY(TransformationManager::getTransformationOwnership(frameF.get(), frameD.get()) == nullptr);
        QVERIFY(TransformationManager::getTransformationOwnership(frameB.get(), frameE.get()) == nullptr);

        // Check they cannot be recomputed (there is no path)
        QVERIFY(TransformationManager::getTransformation(frameF.get(), frameA.get()) == nullptr);
        QVERIFY(TransformationManager::getTransformation(frameF.get(), frameB.get()) == nullptr);
        QVERIFY(TransformationManager::getTransformation(frameF.get(), frameD.get()) == nullptr);
        QVERIFY(TransformationManager::getTransformation(frameB.get(), frameE.get()) == nullptr);

        // These ones should still be there or recomputable
        QVERIFY(TransformationManager::getTransformation(frameB.get(), frameC.get()) != nullptr);
        QVERIFY(TransformationManager::getTransformationOwnership(frameA.get(), frameD.get()) == nullptr);
        QVERIFY(TransformationManager::getTransformation(frameA.get(), frameD.get()) != nullptr);

    }


    /// Check creating and updating transformations, default transformations, and commposite transformations
    void checkUpdateTransformation() {
        // Create multiple transformations A ---> B --default--> World <--default-- C <--- D
        //                                                         ^
        //                                                         |
        //                                                         +------default-- E <--- F
        auto [frameA, frameB, trA_B] = createFramesAndTransformation("FrameA", "FrameB");
        auto [frameD, frameC, trD_C] = createFramesAndTransformation("FrameD", "FrameC");
        auto [frameF, frameE, trF_E] = createFramesAndTransformation("FrameF", "FrameE");
        TransformationManager::ensurePathToWorld(frameB.get());
        Transformation* trB_World = TransformationManager::getTransformation(frameB.get(), TransformationManager::getWorldFrame());
        QUuid trB_World_Uuid = trB_World->getUuid();
        TransformationManager::ensurePathToWorld(frameC.get());
        Transformation* trC_World = TransformationManager::getTransformation(frameC.get(), TransformationManager::getWorldFrame());
        TransformationManager::ensurePathToWorld(frameE.get());
        Transformation* trE_World = TransformationManager::getTransformation(frameE.get(), TransformationManager::getWorldFrame());

        //-- 1. Try to update the default transformation B->World

        // Check that they are all default
        QVERIFY(TransformationManager::isDefaultIdentityToWorld(trB_World));
        QVERIFY(TransformationManager::isDefaultIdentityToWorld(TransformationManager::getTransformation(frameC.get(), TransformationManager::getWorldFrame())));
        QVERIFY(TransformationManager::isDefaultIdentityToWorld(TransformationManager::getTransformation(frameE.get(), TransformationManager::getWorldFrame())));
        // Check that we cannot get shared ptr of a default identity to world
        QVERIFY(TransformationManager::getTransformationOwnership(frameC.get(), TransformationManager::getWorldFrame()) == nullptr);

        // Add a non-default
        vtkMatrix4x4* matrixTransl10 = vtkMatrix4x4::New();
        matrixTransl10->SetElement(0, 3, 10.0);
        TransformationManager::addTransformation(frameB.get(), TransformationManager::getWorldFrame(), matrixTransl10);
        // New state:
        // A ---> B --matrixTransl10--> World <--default-- C <--- D
        //                                ^
        //                                |
        //                                +------default-- E <--- F

        // Check it is not default anymore and a new transformation was created
        Transformation* nonDefault_trB_World = TransformationManager::getTransformation(frameB.get(), TransformationManager::getWorldFrame());
        QVERIFY(trB_World_Uuid != nonDefault_trB_World->getUuid());
        QVERIFY(!TransformationManager::isDefaultIdentityToWorld(nonDefault_trB_World));

        // Check World->B is not default either, and World->B is the inverse of the new transformation
        Transformation* trWorld_B = TransformationManager::getTransformation(TransformationManager::getWorldFrame(), frameB.get());
        QVERIFY(trWorld_B != nullptr);
        QVERIFY(!TransformationManager::isDefaultIdentityToWorld(trWorld_B));
        QCOMPARE(trWorld_B->getMatrix()->GetElement(0, 3), -10.0);

        //-- 2. Try to update the inverse default World->C

        // Check the inverse is default and has one source
        QVERIFY(TransformationManager::isDefaultIdentityToWorld(TransformationManager::getTransformation(TransformationManager::getWorldFrame(), frameC.get())));
        QCOMPARE(TransformationManager::getSources(TransformationManager::getTransformation(TransformationManager::getWorldFrame(), frameC.get())).size(), 1);

        // Add a non-default inverse
        std::shared_ptr<Transformation> trWorld_C = TransformationManager::addTransformation(TransformationManager::getWorldFrame(), frameC.get(), matrixTransl10);
        // New state:
        // A ---> B --[matrixTransl10]--> World <--[matrixTransl10^{-1}]-- C <--- D
        //                                 ^
        //                                 |
        //                                 +------default-- E <--- F

        // We expect that C-> World is not default anymore, World->C is not default, and C->World is the inverse of the updated transformation
        QVERIFY(trWorld_C != nullptr);
        QVERIFY(trWorld_C.get() == TransformationManager::getTransformation(TransformationManager::getWorldFrame(), frameC.get()));
        QVERIFY(!TransformationManager::isDefaultIdentityToWorld(TransformationManager::getTransformation(TransformationManager::getWorldFrame(), frameC.get())));
        QVERIFY(!TransformationManager::isDefaultIdentityToWorld(TransformationManager::getTransformation(frameC.get(), TransformationManager::getWorldFrame())));
        QCOMPARE(TransformationManager::getTransformation(frameC.get(), TransformationManager::getWorldFrame())->getMatrix()->GetElement(0, 3), -10.0);

        //-- 3. get the Transformation from A to F (e.g. composite transformation) then try to update it

        // Get the composite transformation
        Transformation* trA_F = TransformationManager::getTransformation(frameA.get(), frameF.get());
        // New state:
        // A ---> B --[matrixTransl10]--> World <--[matrixTransl10^{-1}]-- C <--- D
        //                                 ^
        //                                 |
        //                                 +------default-- E <--- F
        // trA_F = composition of trA_B, trB_World, trE_World^{-1}, trF_E^{-1}
        QVERIFY(trA_F != nullptr);
        // Should be A-->B-->World-->E-->F so 4 transformations
        //qDebug().noquote() << TransformationManager::toString();
        QCOMPARE(TransformationManager::getSources(trA_F).size(), 4);
        QVERIFY(TransformationManager::isCompositeTransformation(trA_F));
        QVERIFY(TransformationManager::isDefaultIdentityToWorld(trE_World));

        // Set values in the matriw
        vtkMatrix4x4* matrixTransl15 = vtkMatrix4x4::New();
        matrixTransl15->SetElement(0, 3, 15.0);
        TransformationManager::addTransformation(frameA, frameF, matrixTransl15);
        trA_F = TransformationManager::getTransformation(frameA.get(), frameF.get());
        // New state:
        // A ---> B --[matrixTransl10}--> World <--[matrixTransl10^{-1}]-- C <--- D
        // |
        // |
        // |
        // +-----[matrixTransl15]-----+---> F --> E
        //qDebug().noquote() << TransformationManager::toString();

        // We expect A to F to have no source
        QVERIFY(trA_F != nullptr);
        QVERIFY(!TransformationManager::isDefaultIdentityToWorld(trA_F));
        QVERIFY(!TransformationManager::isCompositeTransformation(trA_F));
        QCOMPARE(TransformationManager::getSources(trA_F).size(), 0);

        // the default E->World that was used to compose A to C should have been removed
        // re-create it
        trE_World = TransformationManager::getTransformation(frameE.get(), TransformationManager::getWorldFrame());
        //qDebug().noquote() << TransformationManager::toString();
        QVERIFY(trE_World != nullptr);
        QVERIFY(!TransformationManager::isDefaultIdentityToWorld(trE_World));
        QVERIFY(TransformationManager::isCompositeTransformation(trE_World));
        QCOMPARE(TransformationManager::getSources(trE_World).size(), 4); // It is now a composite of 4 transformations

        Transformation* trE_D = TransformationManager::getTransformation(frameE.get(), frameD.get());
        QVERIFY(trE_D != nullptr);
        QCOMPARE(TransformationManager::getSources(trE_D).size(), 6);  // 6 because all sources of E->World, World->C, C->D are included
        // for (auto ti : TransformationManager::getSources(trE_D)) {
        //     qDebug() << ti->getName() << ", ";
        // }

        Transformation* trF_World = TransformationManager::getTransformation(frameF.get(), TransformationManager::getWorldFrame());
        QCOMPARE(TransformationManager::getSources(trF_World).size(), 3); // F -> A -> B -> World

        // Should be a translation of -5 (E -- id --> F -- -15 --> A -- id --> B -- +10 --> World)
        QCOMPARE(trE_World->getMatrix()->GetElement(0, 3), -5.0);

        // Replace matrix translation 15 by matrix translation 10 in A --> F
        TransformationManager::updateTransformation(frameA.get(), frameF.get(), matrixTransl10);

        // The E->World transformation should now be identity (automatically updated because it depends on trA_F)
        QCOMPARE(trE_World->getMatrix()->GetElement(0, 3), 0.0);

        //-- 4. get the Transformation from A to D, get its inverse, update its inverse
        Transformation* trA_D = TransformationManager::getTransformation(frameA.get(), frameD.get());
        QVERIFY(trA_D != nullptr);
        Transformation* trD_A = TransformationManager::getTransformation(frameD.get(), frameA.get());
        QVERIFY(trD_A != nullptr);
        // This should fail, there is a non-default path already from D to A
        QVERIFY(TransformationManager::updateTransformation(trD_A, matrixTransl15) == false);

        //-- 5. Add trG_H but no trH_World and ask for trA_G which should return nullptr
        auto [frameG, frameH, trG_H] = createFramesAndTransformation("FrameG", "FrameH");
        QVERIFY(TransformationManager::getTransformation(frameA.get(), frameG.get()) == nullptr);
    }

    /// Check that the actor transform is defined relative to the viewerFrame
    void checkViewerFrame() {
        // Load all the viewer extensions in order to use the InteractiveGeometryViewer factory (Application::getNewViewer)
        ExtensionManager::autoload(ExtensionManager::VIEWER);

        // Create a new custom Geometry Viewer
        InteractiveViewer* viewer = dynamic_cast<InteractiveViewer*>(Application::getNewViewer("3D Viewer Test", "InteractiveGeometryViewer"));  // will be deleted when the InteractiveGeometryViewer extension is deleted (during exit)
        QVERIFY(viewer != nullptr);

        // Initialize the widget and show the widget in order to build the widget (and its frame)
        // and force the new viewer frame to be truly visible so that calling setFrame(..)
        // (that itself calls refresh(..)) really updates of the actor transformation, the aim of this test
        viewer->getWidget();

        // Register the viewer so that it can be used in comp->setVisibility(...)
        // and be automatically called during Application::refresh()
        Application::registerViewer(viewer);

        // Create a basic sphere Component
        Component* comp = createSphere(2.0); // will be deleted in cleanup()
        QVERIFY(comp != nullptr);
        // as MeshComponent does not create a default identity to world itself, its frame is not linked to world before visualization
        QVERIFY(TransformationManager::getTransformation(comp->getFrame(), TransformationManager::getWorldFrame()) == nullptr);

        // Ensure our component is visible in the custom new viewer
        comp->setVisibility(viewer->getName(), true);
        viewer->getWidget()->show();
        viewer->refresh();
        // refresh must have ensure a path from the viewer and component to world
        QVERIFY(TransformationManager::getTransformation(comp->getFrame(), TransformationManager::getWorldFrame()) != nullptr);

        // Set its main frame as worldFrame to get: dataFrame ---(Identity)---> MainFrame ---(identity)---> WorldFrame
        QVERIFY(TransformationManager::isDefaultIdentityToWorld(TransformationManager::getTransformation(comp->getFrame(), TransformationManager::getWorldFrame())));
        QVERIFY(TransformationManager::getTransformation(TransformationManager::getWorldFrame(), comp->getFrame())->getMatrix()->IsIdentity());

        // Create a viewerFrame
        std::shared_ptr<FrameOfReference> viewerFrame = TransformationManager::addFrameOfReference("Viewer Frame", "");
        QVERIFY(viewerFrame != nullptr);
        // there is no path yet
        QVERIFY(TransformationManager::getTransformation(viewerFrame.get(), TransformationManager::getWorldFrame()) == nullptr);

        // Set the viewer frame
        viewer->setFrame(viewerFrame);
        // now there should be a path (which is a default)
        QVERIFY(TransformationManager::getTransformation(viewerFrame.get(), TransformationManager::getWorldFrame()) != nullptr);
        QVERIFY(TransformationManager::isDefaultIdentityToWorld(TransformationManager::getTransformation(viewerFrame.get(), TransformationManager::getWorldFrame())));

        // We create invertAxes transformation to have: MainFrame[=worldFrame] ---(invertAxes)---> viewerFrame
        vtkSmartPointer<vtkTransform> invertAxes = vtkSmartPointer<vtkTransform>::New();
        invertAxes->Scale(1, -1, 1); // Reverse axis Y
        invertAxes->Translate(1, 2, 3);
        Transformation* trMain2Vtk = TransformationManager::addTransformation(comp->getFrame(), viewerFrame.get(), invertAxes).get();
        QVERIFY(trMain2Vtk != nullptr);

        // Set anatomical orientation information in the viewerFrame
        viewerFrame->setAnatomicalOrientation("RAI");
        viewer->refresh();

        // Put the vtkActor from the component in the viewer
        //      ->  Check that there is at least the surface actor
        vtkSmartPointer<vtkActor> actor = comp->getActor(InterfaceGeometry::Surface);
        QVERIFY(actor != nullptr);

        // Check that the User transformation is correctly set in the actor
        vtkLinearTransform* userTransform = actor->GetUserTransform();
        QVERIFY(userTransform != nullptr);
        // This should be: dataFrame ------> ViewerFrame
        //               = dataFrame ---(identity)---> MainFrame ---(invertAxes)---> ViewerFrame
        //               = invertAxes
        QVERIFY(isEqual(userTransform->GetMatrix(), invertAxes->GetMatrix()));

        // set different orientations (XY, XZ, YZ, Axial, Coronal, Sagittal) and check all the resulting camera transformations
        //QFAIL("What happens when changing camera orientation is not checked");
        // Change the viewer's frame to the Component's frame
        viewer->setFrame(TransformationManager::getFrameOfReferenceOwnership(comp->getFrame()));
        // Check that the UserView transformation is correctly set in the RendererWidget's camera to Identity
        userTransform = actor->GetUserTransform();
        QVERIFY(userTransform == nullptr || userTransform->GetMatrix()->IsIdentity());
    }

    /// @brief Check resetFrame
    void checkResetFrame() {
        // -- 1. create mesh m image i
        MeshComponent* m = createSphere(3.14);
        ImageComponent* i = createImage("i");

        // -- 2. store all frames and transformation shared ptr
        std::shared_ptr<FrameOfReference> frM, frIMain, frIData, frIArbitrary;
        std::shared_ptr<Transformation> trIData_IMain, trIArbitrary_IData;
        frM = TransformationManager::getFrameOfReferenceOwnership(m->getFrame());
        frIMain = TransformationManager::getFrameOfReferenceOwnership(i->getFrame());
        frIData = TransformationManager::getFrameOfReferenceOwnership(i->getDataFrame());
        frIArbitrary = TransformationManager::getFrameOfReferenceOwnership(i->getArbitrarySlices()->getArbitraryFrame());
        trIData_IMain = TransformationManager::getTransformationOwnership(frIData.get(), frIMain.get());
        i->getArbitrarySlices()->setPropertyValue("Rotation", QVector3D(1.0, 0.0, 0.0));
        trIArbitrary_IData = TransformationManager::getTransformationOwnership(i->getArbitrarySlices()->getArbitraryTransformation());
        QVERIFY(frM != nullptr && frIMain != nullptr && frIData != nullptr && frIArbitrary != nullptr && trIData_IMain != nullptr && trIArbitrary_IData != nullptr);

        // -- 3. resetFrame
        m->resetFrame();
        QVERIFY(m->getFrame() != frM.get());

        i->resetFrame();
        QVERIFY(i->getFrame() != frIMain.get());
        QVERIFY(i->getDataFrame() != frIData.get());
        QVERIFY(i->getArbitrarySlices()->getArbitraryFrame() != frIArbitrary.get());
        std::shared_ptr<Transformation> new_trIData_IMain = TransformationManager::getTransformationOwnership(i->getDataFrame(), i->getFrame());
        QVERIFY(new_trIData_IMain != nullptr);
        QVERIFY(new_trIData_IMain != trIData_IMain);
        std::shared_ptr<Transformation> new_trIArbitrary_IData = TransformationManager::getTransformationOwnership(i->getArbitrarySlices()->getArbitraryTransformation());
        QVERIFY(new_trIArbitrary_IData != nullptr);
        QVERIFY(new_trIArbitrary_IData != trIArbitrary_IData);
        QVERIFY(isEqual(new_trIArbitrary_IData->getMatrix(), trIArbitrary_IData->getMatrix()));
    }

    /// @brief Check setFrame
    void checkSetFrame() {
        // -- 1. create two meshes m1, m2 and two images i1, i2
        MeshComponent* m1 = createSphere(3.14);
        ImageComponent* i1 = createImage("i1");
        MeshComponent* m2 = createSphere(2.71);
        ImageComponent* i2 = createImage("i2");

        // -- 2. store all frames and transformation shared ptr
        std::shared_ptr<FrameOfReference> frM1, frI1Main, frI1Data, frI1Arbitrary;
        std::shared_ptr<Transformation> trI1Data_I1Main, trI1Arbitrary_I1Data;
        frM1 = TransformationManager::getFrameOfReferenceOwnership(m1->getFrame());
        frI1Main = TransformationManager::getFrameOfReferenceOwnership(i1->getFrame());
        frI1Data = TransformationManager::getFrameOfReferenceOwnership(i1->getDataFrame());
        frI1Arbitrary = TransformationManager::getFrameOfReferenceOwnership(i1->getArbitrarySlices()->getArbitraryFrame());
        trI1Data_I1Main = TransformationManager::getTransformationOwnership(frI1Data.get(), frI1Main.get());
        i1->getArbitrarySlices()->setPropertyValue("Rotation", QVector3D(1.0, 0.0, 0.0));
        trI1Arbitrary_I1Data = TransformationManager::getTransformationOwnership(i1->getArbitrarySlices()->getArbitraryTransformation());

        // -- 3.setFrame
        // Set mesh 1 frame from mesh 2
        m1->setFrame(TransformationManager::getFrameOfReferenceOwnership(m2->getFrame()));
        QVERIFY(m1->getFrame() != frM1.get());
        QVERIFY(m1->getFrame() == m2->getFrame());

        // Set mesh 1 frame from image 1
        m1->setFrame(TransformationManager::getFrameOfReferenceOwnership(i1->getFrame()));
        QVERIFY(m1->getFrame() != frM1.get());
        QVERIFY(m1->getFrame() != m2->getFrame());
        QVERIFY(m1->getFrame() == frI1Main.get());

        // Set image 1 frame from mesh 2
        i1->setFrame(TransformationManager::getFrameOfReferenceOwnership(m2->getFrame()));
        QVERIFY(i1->getFrame() != frI1Main.get());
        QVERIFY(i1->getFrame() == m2->getFrame());
        QVERIFY(i1->getDataFrame() == frI1Data.get());
        QVERIFY(i1->getArbitrarySlices()->getArbitraryFrame() == frI1Arbitrary.get());
        std::shared_ptr<Transformation> new_trI1Data_I1Main = TransformationManager::getTransformationOwnership(i1->getDataFrame(), i1->getFrame());
        QVERIFY(new_trI1Data_I1Main != trI1Data_I1Main);
        // Check the main Transformation
        QVERIFY(i1->getMainTransformation()->getFrom() == i1->getDataFrame());
        QVERIFY(i1->getMainTransformation()->getTo() == m2->getFrame());
        // Check the Arbitrary Transformation
        QVERIFY(i1->getArbitrarySlices()->getArbitraryTransformation() == trI1Arbitrary_I1Data.get());
        QVERIFY(i1->getArbitrarySlices()->getArbitraryTransformation()->getFrom() == frI1Arbitrary.get());
        QVERIFY(i1->getArbitrarySlices()->getArbitraryTransformation()->getTo() == i1->getDataFrame());

        // Setimage 1 frame from image 2
        i1->setFrame(TransformationManager::getFrameOfReferenceOwnership(i2->getFrame()));
        // Only main frame and main transformation should be modified
        QVERIFY(i1->getFrame() != frI1Main.get());
        QVERIFY(i1->getFrame() == i2->getFrame());
        QVERIFY(i1->getDataFrame() == frI1Data.get());
        QVERIFY(i1->getArbitrarySlices()->getArbitraryFrame() == frI1Arbitrary.get());
        // Main Transformation
        std::shared_ptr<Transformation> new2_trI1Data_I1Main = TransformationManager::getTransformationOwnership(i1->getDataFrame(), i1->getFrame());
        QVERIFY(new2_trI1Data_I1Main != nullptr);
        QVERIFY(new2_trI1Data_I1Main != trI1Data_I1Main);
        QVERIFY(new2_trI1Data_I1Main != new_trI1Data_I1Main);
        QVERIFY(new2_trI1Data_I1Main.get() == i1->getMainTransformation());
        // Arbitrary Transformation
        QVERIFY(i1->getArbitrarySlices()->getArbitraryTransformation() == trI1Arbitrary_I1Data.get());
        QVERIFY(i1->getArbitrarySlices()->getArbitraryTransformation()->getFrom() == frI1Arbitrary.get());
        QVERIFY(i1->getArbitrarySlices()->getArbitraryTransformation()->getTo() == frI1Data.get());
    }

    /// @brief Check setFrameFrom
    void checkSetFrameFrom() {
        // -- 1. create two meshes m1, m2 and two images i1, i2
        MeshComponent* m1 = createSphere(3.14);
        ImageComponent* i1 = createImage("i1");
        MeshComponent* m2 = createSphere(2.71);
        ImageComponent* i2 = createImage("i2");

        // -- 2. store all frames and transformation shared ptr
        std::shared_ptr<FrameOfReference> frM1, frI1Main, frI1Data, frI1Arbitrary;
        std::shared_ptr<Transformation> trI1Data_I1Main, trI1Arbitrary_I1Data;
        frM1 = TransformationManager::getFrameOfReferenceOwnership(m1->getFrame());
        frI1Main = TransformationManager::getFrameOfReferenceOwnership(i1->getFrame());
        frI1Data = TransformationManager::getFrameOfReferenceOwnership(i1->getDataFrame());
        frI1Arbitrary = TransformationManager::getFrameOfReferenceOwnership(i1->getArbitrarySlices()->getArbitraryFrame());
        trI1Data_I1Main = TransformationManager::getTransformationOwnership(frI1Data.get(), frI1Main.get());
        i1->getArbitrarySlices()->setPropertyValue("Rotation", QVector3D(1.0, 0.0, 0.0));
        trI1Arbitrary_I1Data = TransformationManager::getTransformationOwnership(i1->getArbitrarySlices()->getArbitraryTransformation());

        // -- 3. setFrameFrom
        // Set frame of m1 from m2
        m1->setFrameFrom(m2);
        QVERIFY(m1->getFrame() != frM1.get());
        QVERIFY(m1->getFrame() == m2->getFrame());

        // Set frame of m1 from i1
        m1->setFrameFrom(i1);
        QVERIFY(m1->getFrame() != frM1.get());
        QVERIFY(m1->getFrame() != m2->getFrame());
        QVERIFY(m1->getFrame() == frI1Main.get());

        // Set frame of image i1 from mesh m2
        i1->setFrameFrom(m2);
        // main frame should have changed
        QVERIFY(i1->getFrame() != frI1Main.get());
        QVERIFY(i1->getFrame() == m2->getFrame());
        // data and arbitrary frames should be identical
        QVERIFY(i1->getDataFrame() == frI1Data.get());
        QVERIFY(i1->getArbitrarySlices()->getArbitraryFrame() == frI1Arbitrary.get());
        // Arbitrary Transformation should be identical
        QVERIFY(i1->getArbitrarySlices()->getArbitraryTransformation() == trI1Arbitrary_I1Data.get());
        QVERIFY(i1->getArbitrarySlices()->getArbitraryTransformation()->getTo() == frI1Data.get());
        QVERIFY(i1->getArbitrarySlices()->getArbitraryTransformation()->getFrom() == frI1Arbitrary.get());
        // Main Transformation should link new main frame to data frame
        QVERIFY(i1->getMainTransformation()->getTo() == m2->getFrame());
        QVERIFY(i1->getMainTransformation()->getFrom() == frI1Data.get());
        // The new one should not be the old one
        std::shared_ptr<Transformation> new_trI1Data_I1Main = TransformationManager::getTransformationOwnership(i1->getDataFrame(), i1->getFrame());
        QVERIFY(new_trI1Data_I1Main != trI1Data_I1Main);
        // But it should be the one stored in the ImageComponent
        QVERIFY(i1->getMainTransformation() == new_trI1Data_I1Main.get());

        // Set frames and transformations of image i1 from image i2
        i1->setFrameFrom(i2);
        QVERIFY(i1->getFrame() != frI1Main.get());
        QVERIFY(i1->getFrame() == i2->getFrame());
        QVERIFY(i1->getDataFrame() != frI1Data.get());
        QVERIFY(i1->getDataFrame() == i2->getDataFrame());
        // Should have created a new Arbitrary Frame
        QVERIFY(i1->getArbitrarySlices()->getArbitraryFrame() != frI1Arbitrary.get());
        // New Main Transformation should be different from before
        std::shared_ptr<Transformation> new2_trI1Data_I1Main = TransformationManager::getTransformationOwnership(i1->getDataFrame(), i1->getFrame());
        QVERIFY(new2_trI1Data_I1Main != nullptr);
        QVERIFY(new2_trI1Data_I1Main != trI1Data_I1Main);
        QVERIFY(new2_trI1Data_I1Main != new_trI1Data_I1Main);
        QVERIFY(i1->getMainTransformation() == new2_trI1Data_I1Main.get());
        // Arbitrary transform should have the same matrix as before
        QVERIFY(isEqual(trI1Arbitrary_I1Data->getMatrix(), i1->getArbitrarySlices()->getArbitraryTransformation()->getMatrix()));
        // But it should go from the new Arbitrary frame to the new data frame (the one from i2)
        QVERIFY(i1->getArbitrarySlices()->getArbitraryTransformation()->getFrom() == i1->getArbitrarySlices()->getArbitraryFrame());
        QVERIFY(i1->getArbitrarySlices()->getArbitraryTransformation()->getTo() == i2->getDataFrame());

        // back to initial state using setFramesAndTransformation()
        i1->setFramesAndTransformation(frI1Main, frI1Data, trI1Data_I1Main);
        QVERIFY(i1->getFrame() != i2->getFrame());
        QVERIFY(i1->getDataFrame() != i2->getDataFrame());
        QVERIFY(isEqual(trI1Arbitrary_I1Data->getMatrix(), i1->getArbitrarySlices()->getArbitraryTransformation()->getMatrix()));
    }

    /// Test preferredDefaultIdentityToWorldLink
    void checkPreferredDefaultIdentityToWorldLink() {
        auto [frameA, frameB, trA2B] = createFramesAndTransformation("FrameA", "FrameB");
        auto [frameC, frameD, trC2D] = createFramesAndTransformation("FrameC", "FrameD");
        TransformationManager::ensurePathToWorld(frameA.get());
        TransformationManager::ensurePathToWorld(frameC.get());
        QCOMPARE(countDefaultIdentityToWorldTransformations(), 4); // A, C and their inverse

        TransformationManager::addTransformation(frameB, frameD);

        // check all default were delete by the new transformation
        QCOMPARE(countDefaultIdentityToWorldTransformations(), 0);

        TransformationManager::ensurePathToWorld(frameD.get());
        QCOMPARE(countDefaultIdentityToWorldTransformations(), 2); // D and its inverse

        // check the default is still the same
        TransformationManager::ensurePathToWorld(frameA.get());
        QCOMPARE(countDefaultIdentityToWorldTransformations(), 2); // D and its inverse
        QVERIFY(TransformationManager::isDefaultIdentityToWorld(TransformationManager::getTransformation(frameD.get(), TransformationManager::getWorldFrame())));

        // now we want a default from B not D
        TransformationManager::preferredDefaultIdentityToWorldLink(frameB.get());
        QCOMPARE(countDefaultIdentityToWorldTransformations(), 2); // B and its inverse
        QVERIFY(!TransformationManager::isDefaultIdentityToWorld(TransformationManager::getTransformation(frameD.get(), TransformationManager::getWorldFrame())));
        QVERIFY(TransformationManager::isDefaultIdentityToWorld(TransformationManager::getTransformation(frameB.get(), TransformationManager::getWorldFrame())));
    }

    /// Test methods getAllFrames and getAllTransformations from InterfaceFrame
    void checkGetAllFrames() {
        ImageComponent* myImage = createImage("My Image");
        MeshComponent* mySphere = createSphere();

        // There should be only one frame for a Mesh
        QCOMPARE(mySphere->getAllFrames().uniqueKeys().size(), 1);

        // There should be only two frames for an Image (without children), data and main
        QCOMPARE(myImage->getAllFrames(false).uniqueKeys().size(), 2);
        // There should be three frames for an Image with its children: data, main, arbitrary
        QCOMPARE(myImage->getAllFrames(true).uniqueKeys().size(), 3);
        // There should be three frames for an Image with its children (default call is with children): data, main, arbitrary
        QCOMPARE(myImage->getAllFrames().uniqueKeys().size(), 3);
        // Check that data frame is used by ImageComponent, as well as 3 singleImageComponent, an arbitrarySingleImageComponent and a VolumeRendering
        QCOMPARE(myImage->getAllFrames().values(myImage->getDataFrame()).size(), 6);
        // Check that the main frame is used only by the ImageComponent itself
        QCOMPARE(myImage->getAllFrames().values(myImage->getFrame()).size(), 1);

        // There should be one main transformation, and one arbitrary in the child for the Image
        // Test without children
        QCOMPARE(myImage->getAllTransformations(false).uniqueKeys().size(), 1);
        // Test with children
        QCOMPARE(myImage->getAllTransformations(true).uniqueKeys().size(), 2);
        // Default should be with children
        QCOMPARE(myImage->getAllTransformations().uniqueKeys().size(), 2);

        // There should be no Transformation for a Mesh
        QCOMPARE(mySphere->getAllTransformations().uniqueKeys().size(), 0);

        // Let's add the image as a child of the mesh
        mySphere->addChild(myImage);

        // Test again
        QCOMPARE(mySphere->getAllTransformations(true).uniqueKeys().size(), 2);
        QCOMPARE(mySphere->getAllFrames(true).uniqueKeys().size(), 4);
    }

};

// } // namespace camitk

#endif // CAMITK_TESTTRANSFORMATIONMANAGER_H
