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

#include "SimpleElastixRegistrationAction.h"
#include "ElastixRunner.h"

#include <ImageComponent.h>
#include <Property.h>
#include <Core.h>
#include <Application.h>
#include <Log.h>

#include <QStandardPaths>
#include <QProcess>
#include <QTextStream>
#include <QTemporaryDir>
#include <QRegularExpression>
// to get the enum as a string
#include <QMetaEnum>

// for elastix executable selection wizard
#include <QWizard>
#include <QVBoxLayout>
#include <QLabel>
#include <QLineEdit>
#include <QPushButton>
#include <QFileDialog>
#include <QStyle>

using namespace camitk;


// --------------- constructor -------------------
SimpleElastixRegistrationAction::SimpleElastixRegistrationAction(ActionExtension* extension) : Action(extension) {
    // Setting name, description and input component
    setName("Simple Elastix Registration");
    setDescription("<p>Registration using Elastix</p><p>Note that elastix should be installed on your machine.</p><p>This action can only modifies the following three main parameters:</p><ul><li><b>Initialization:</b> whether or not the initial translation between images should be estimated as the distance between their centers.</li><li><b>Transformation model:</b> determines what type of deformations between the fixed and moving image is handled. There are 6 models available in elastix. Only three different types are available in this action: translation only, affine transformation (includes translation, rotation, scaling and shearing, and the nonrigid (i.e., elastic) B-spline transformation (uses cubic multidimensional B-spline polynomial control points). See section \"2.6 Transform\" of the elastix manual.</li><li><b>Similarity metrics:</b> the similarity measure uses to compare the voxel values and find the best match during the registration. There are 5 different similarity measures available in elastix, the main one being: Mean Squared Difference (MSD) and Mutual Information (MI). See section \"2.3 Metrics\" of the elastix manual.</li></ul>");
    setComponentClassName("ImageComponent");

    // Setting classification family and tags
    this->setFamily("Registration");
    this->addTag("matching");

    // check for elastix
    QString elastixPath = QStandardPaths::findExecutable("elastix"); // using system PATH
    if (elastixPath.isEmpty()) {
        // check in the same directory as the current application / CamiTK install directories
        elastixPath = QStandardPaths::findExecutable("elastix", Core::getInstallDirectories(""));
    }
    inConstructor = true;
    addParameter(new Property(tr("Elastix Executable"), elastixPath, "Path to the elastix executable. If empty, i.e., not found automatically in the system path or in the installation directories, you will require to set the path manually.", ""));

    // Setting parameters default values by using properties
    Property* elastixVersion = new Property(tr("Elastix Version"), "", "", "");
    elastixVersion->setReadOnly(true);
    addParameter(elastixVersion);

    // Script parameter
    addParameter(new Property(tr("Translation Initialization"), false, tr("Whether or not the initial translation between images should be estimated as the distance between their centers"), ""));

    Property* transformProp = new Property(tr("Transform Type"), SimpleElastixRegistrationAction::TRANSLATION, tr("This parameter set the transformation model used by the application to determine what type of deformations between the fixed and moving image is handled. There are 6 models available in elastix. Only three different types are available in this action: translation only, affine transformation (includes translation, rotation, scaling and shearing, and the nonrigid (i.e., elastic) B-spline transformation (uses cubic multidimensional B-spline polynomial control points). See section \"2.6 Transform\" of the elastix manual."), "");
    // auto set the name from the enum literals
    transformProp->setEnumTypeName("TransformationType", this);
    addParameter(transformProp);

    Property* metricProp = new Property(tr("Similarity Metric"), SimpleElastixRegistrationAction::ADVANCED_MEAN_SQUARES, tr("This parameter set the similarity measure uses to compare the voxel values and find the best match during the registration. There are 5 different similarity measures available in elastix, the main one being: Mean Squared Difference (MSD) and Mutual Information (MI). See section \"2.3 Metrics\" of the elastix manual."), "");
    metricProp->setEnumTypeName("MetricType", this);
    addParameter(metricProp);

    // comboboxes for selecting fixed and moving images
    // Start with an empty values
    QStringList componentListNames;
    Property* movingImage = new Property(tr("Moving Image"), 0, tr("Choose the moving image"), "");
    // Set the enum type
    movingImage->setEnumTypeName("ImageComponentList");
    // Start with an empty values
    movingImage->setAttribute("enumNames", componentListNames);
    // Add the property as an action parameter
    addParameter(movingImage);

    Property* fixedImage = new Property(tr("Fixed Image"), 0, tr("Choose the fixed image"), "");
    // Set the enum type
    fixedImage->setEnumTypeName("ImageComponentList");
    fixedImage->setAttribute("enumNames", componentListNames);
    // Add the property as an action parameter
    addParameter(fixedImage);

    addParameter(new Property(tr("Overwrite Results"), true, tr("Overwrite previous elastix results when registration is rerun"), ""));

    addParameter(new Property(tr("Result Name"), "", tr("The name used to create a subdirectory. Automatically updated depending on the parameters and the input images. You can modify it just before clicking on \"Apply\""), ""));
    inConstructor = false;

    // immediately take the property changes into account
    setAutoUpdateProperties(true);
}

// --------------- destructor -------------------
SimpleElastixRegistrationAction::~SimpleElastixRegistrationAction() {
}

// ---------------------- event ----------------------------
bool SimpleElastixRegistrationAction::event(QEvent* e) {
    if (e->type() == QEvent::DynamicPropertyChange) {
        e->accept();
        QDynamicPropertyChangeEvent* changeEvent = dynamic_cast<QDynamicPropertyChangeEvent*>(e);

        if (!changeEvent) {
            return false;
        }

        // if this is a property change event and the user is not
        // modifying "Result Name" itself, update using default value
        if (changeEvent->propertyName() != "Result Name" && !inConstructor) {
            setParameterValue("Result Name", defaultResultName());
        }

        return true;
    }

    // this is important to continue the process if the event is a different one
    return QObject::event(e);
}


// --------------- updateElastixVersion -------------------
void SimpleElastixRegistrationAction::updateElastixVersion() {
    if (getParameterValue("Elastix Executable").toString().isEmpty()) {
        defineElastixExecutable();
    }

    QString elasticVersionString = "";
    QString elasticVersionDescription = "";
    // First run elastix to check its version
    if (runElastix(QStringList("--version"))) {
        elasticVersionString = lastStdOutput.split(":").last().trimmed();
        elasticVersionDescription = tr("Using %1 executable").arg(getParameterValue("Elastix Executable").toString());
    }
    else {
        CAMITK_INFO(tr("Elastix not found in system or installation path (exit code %1, error: %2).").arg(lastExitCode).arg(lastProcessError))
        elasticVersionString = "Elastix not found.";
        elasticVersionDescription = getParameterValue("Elastix Executable").toString() + "<br/>Checked paths:<br/>- System path: " + QProcessEnvironment::systemEnvironment().value("PATH") + "<br/>-Installation path:" + Core::getInstallDirectories("").join(":");
    }

    //CAMITK_INFO(tr("%1 %2").arg(elasticVersionString).arg(elasticVersionDescription.replace("<br/>","\n")))
    setParameterValue("Elastix Version", elasticVersionString);
    getProperty("Elastix Version")->setDescription(elasticVersionDescription);

}

// --------------- getWidget -------------------
QWidget* SimpleElastixRegistrationAction::getWidget() {
    updateElastixVersion();

    //-- Update the list of possible parents
    imageComponentList.clear();

    // combobox strings
    QStringList componentListNames;

    ComponentList allComps = Application::getAllComponents();
    ImageComponent* inputImage;

    for (Component* comp : allComps) {
        inputImage = dynamic_cast<ImageComponent*>(comp);

        if (inputImage != nullptr) {
            componentListNames << inputImage->getName();
            imageComponentList.append(inputImage);
        }
    }

    if (imageComponentList.size() < 2) {
        CAMITK_WARNING(tr("SimpleElastixRegistrationAction requires at least two images. There are not enough ImageComponent opened. Aborting."))
        Application::showStatusBarMessage("SimpleElastixRegistrationAction: cannot apply action: please open at least two images.");
        return nullptr;
    }

    Property* movingImage = getProperty("Moving Image");
    movingImage->setAttribute("enumNames", componentListNames);
    // select the target component as moving image
    int movingImageIndex = componentListNames.indexOf(getTargets().at(0)->getName());
    setParameterValue("Moving Image", movingImageIndex);

    Property* fixedImage = getProperty("Fixed Image");
    fixedImage->setAttribute("enumNames", componentListNames);
    // select the next value in the list as fixed image
    setParameterValue("Fixed Image", (movingImageIndex + 1) % componentListNames.size());

    // reset result name
    setParameterValue("Result Name", defaultResultName());

    return Action::getWidget();
}

// --------------- apply -------------------
Action::ApplyStatus SimpleElastixRegistrationAction::apply() {
    updateElastixVersion();

    // set waiting cursor
    QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));

    // Create temp directory
    QTemporaryDir tempDir;

    //-- 1) Create new elastix parameter file from script resource
    // get parameter values
    QString initialization = getParameterValue("Translation Initialization").toString();

    // With Q_ENUM corresponding string values can be retrieved easily
    QString transformTypeEnum = QString(QMetaEnum::fromType<TransformationType>().valueToKey(getParameterValue("Transform Type").toInt()));
    QString transformType;
    for (const QString& subString : transformTypeEnum.split('_')) {
        transformType.append(subString.at(0).toUpper());
        transformType.append(subString.mid(1).toLower());
    }
    transformType.append("Transform");

    QString similarityMetricEnum = QMetaEnum::fromType<MetricType>().valueToKey(getParameterValue("Similarity Metric").toInt());
    QString similarityMetric;
    for (const QString& subString : similarityMetricEnum.split('_')) {
        if (!subString.isEmpty()) {
            similarityMetric.append(subString.at(0).toUpper());
            similarityMetric.append(subString.mid(1).toLower());
        }
    }

    QFileInfo extFilePath;
    QString paramFileName = "ElastixParameters-" + getParameterValue("Result Name").toString() + ".txt";
    extFilePath.setFile(tempDir.path(), paramFileName);

    if (!createParameterFile(extFilePath, initialization, transformType, similarityMetric)) {
        // restore the normal cursor
        QApplication::restoreOverrideCursor();
        CAMITK_WARNING(tr("IO Exception:\nCannot write file %1").arg(extFilePath.absoluteFilePath()))
        return ERROR;
    }

    //-- 2) Save images to temp
    QString fixedImageName = saveComponentAs(imageComponentList.value(getParameterValue("Fixed Image").toInt()), tempDir.path());
    if (fixedImageName.isEmpty()) {
        // restore the normal cursor
        QApplication::restoreOverrideCursor();
        return ERROR;
    }

    QString movingImageName = saveComponentAs(imageComponentList.value(getParameterValue("Moving Image").toInt()), tempDir.path());
    if (movingImageName.isEmpty()) {
        // restore the normal cursor
        QApplication::restoreOverrideCursor();
        return ERROR;
    }

    //-- 3) Run elastix in temp working directory
    QStringList arguments;
    arguments << "-f" << fixedImageName << "-m" << movingImageName << "-p" << paramFileName << "-out" << tempDir.path();

    if (!runElastix(arguments, tempDir.path())) {
        // restore the normal cursor
        QApplication::restoreOverrideCursor();
        CAMITK_WARNING(tr("Elastix failed (exit code: %1, error: %2). Please check the log.").arg(lastExitCode).arg(lastProcessError))
        return ERROR;
    }

    // 4) Copy the resulting image file to the moving image file directory
    QString resultingImage = copyResults(tempDir.path());
    if (resultingImage.isEmpty()) {
        QApplication::restoreOverrideCursor();
        return ERROR;
    }

    // 5) Open the resulting image
    Application::open(resultingImage);

    // restore the normal cursor
    QApplication::restoreOverrideCursor();

    return SUCCESS;
}

// --------------- defineElastixExecutable -------------------
void SimpleElastixRegistrationAction::defineElastixExecutable() {
    //- Installation wizard
    QWizard wizard;
    wizard.setWindowTitle("Setting the Elastix Executable Manually");
    wizard.setMinimumSize(400, 300);

    //-- Introduction Page
    QWizardPage* introPage = new QWizardPage();
    introPage->setTitle("Elastix Executable Not Found");
    introPage->setSubTitle("Problem");

    QLabel* introLabel = new QLabel("<p>Elastix Executable was not found in the system and installation path. Please select the elastix executable on your computer.</p><p><b>Note:</b> you can download the latest elastix version <a href=\"https://github.com/SuperElastix/elastix/releases\">from the official elastix github pages</a>.", introPage);
    introLabel->setOpenExternalLinks(true);
    introLabel->setWordWrap(true);
    QVBoxLayout* introLayout = new QVBoxLayout();
    introLayout->addWidget(introLabel);
    introPage->setLayout(introLayout);

    wizard.addPage(introPage);

    //-- File Selection Page
    QWizardPage* fileSelectionPage = new QWizardPage();
    fileSelectionPage->setTitle("Select Elastix Executable");
    fileSelectionPage->setSubTitle("Please find and select the elastix executable");

    QVBoxLayout* fileLayout = new QVBoxLayout();
    QLabel* fileLabel = new QLabel("Select the file 'elastix':", fileSelectionPage);

    QString fileFilter;
#ifdef Q_OS_WIN
    fileFilter = "Executable Files (*.exe)";
#else
    fileFilter = "All Files (*)";
#endif
    QHBoxLayout* lineEditLayout = new QHBoxLayout();
    QLineEdit* fileLineEdit = new QLineEdit(fileSelectionPage);
    QPushButton* browseButton = new QPushButton("Browse", fileSelectionPage);

    //-- verification page labels and layout (updated during file selection)
    QLabel* executableLabel = new QLabel("Elastix Executable: undefined");
    QLabel* versionLabel = new QLabel("Elastix Version: not found");
    QHBoxLayout* warningLayout = new QHBoxLayout();
    QHBoxLayout* okLayout = new QHBoxLayout();
    QVBoxLayout* verificationLayout = new QVBoxLayout();

    connect(browseButton, &QPushButton::clicked, [this, &wizard, fileLineEdit, fileFilter, executableLabel, versionLabel, warningLayout, okLayout, verificationLayout]() {
        QString fileName = QFileDialog::getOpenFileName(&wizard, "Select Elastix Executable", QDir::homePath(), fileFilter);
        if (!fileName.isEmpty()) {
            fileLineEdit->setText(fileName);
            executableLabel->setText(tr("Elastix Executable: %1").arg(fileName));
            setParameterValue("Elastix Executable", fileName);
            // run elastix to check if everything is setup properly
            if (runElastix(QStringList("--version"))) {
                // elastix ran without error
                versionLabel->setText(tr("Elastix Version: %1").arg(lastStdOutput.split(":").last().trimmed()));
                verificationLayout->addLayout(okLayout);
            }
            else {
                // an error occurred
                setParameterValue("Elastix Executable", "");
                verificationLayout->addLayout(warningLayout);
            }
        }
    });
    lineEditLayout->addWidget(fileLineEdit);
    lineEditLayout->addWidget(browseButton);
    fileLayout->addWidget(fileLabel);
    fileLayout->addLayout(lineEditLayout);
    fileSelectionPage->setLayout(fileLayout);

    wizard.addPage(fileSelectionPage);

    //-- Verification page
    QWizardPage* verificationPage = new QWizardPage();
    verificationPage->setTitle("Elastix Executable Verification");
    verificationLayout->addWidget(executableLabel);
    verificationLayout->addWidget(versionLabel);

    //-- Add message in case of error
    QLabel* warningIconLabel = new QLabel();
    warningIconLabel->setPixmap(Application::style()->standardIcon(QStyle::SP_MessageBoxWarning).pixmap(32, 32));
    QLabel* warningTextLabel = new QLabel("<p>An error occurred when testing the elastix executable.</p> <p><b>Note :</b><ul><li>On Windows, please make sure you have installed the <a href=\"https://learn.microsoft.com/en-US/cpp/windows/latest-supported-vc-redist\">latest supported version of the Microsoft Visual C++ Redistributable</a>.</li><li>On Linux, please make sure that the elastix ANNlib shared objects are in your link directories as well</li></ul>");
    warningTextLabel->setOpenExternalLinks(true);
    warningTextLabel->setWordWrap(true);
    warningLayout->addWidget(warningIconLabel);
    warningLayout->addWidget(warningTextLabel);
    warningIconLabel->setFixedWidth(32);

    //-- Add reinsuring label in case everything looks good
    QLabel* okIconLabel = new QLabel();
    okIconLabel->setPixmap(Application::style()->standardIcon(QStyle::SP_MessageBoxInformation).pixmap(32, 32));
    QLabel* okTextLabel = new QLabel("Elastix executable is functional.");
    okTextLabel->setWordWrap(true);
    okLayout->addWidget(okIconLabel);
    okLayout->addWidget(okTextLabel);
    warningIconLabel->setFixedWidth(32);

    // add general layout (which will be updated during the file selection
    // page depending on the elastix execution results)
    verificationPage->setLayout(verificationLayout);

    // show the dialog to the used
    wizard.addPage(verificationPage);
    wizard.exec();
}

// --------------- defaultResultName -------------------
QString SimpleElastixRegistrationAction::defaultResultName() const {
    QString testName = getParameterValue("Translation Initialization").toBool() ? "init-" : "noinit-";
    QString transformTypeEnum = QString(QMetaEnum::fromType<TransformationType>().valueToKey(getParameterValue("Transform Type").toInt()));
    testName += transformTypeEnum.mid(0, 2).toLower() + "-";
    QString similarityMetricEnum = QMetaEnum::fromType<MetricType>().valueToKey(getParameterValue("Similarity Metric").toInt());
    for (const QString& subString : similarityMetricEnum.split('_')) {
        testName += subString.at(0).toLower();
    }
    return testName;
}

// --------------- transformName -------------------
QString SimpleElastixRegistrationAction::transformName() const {
    QString movingName = imageComponentList.value(getParameterValue("Moving Image").toInt())->getName();
    QString fixedName = imageComponentList.value(getParameterValue("Fixed Image").toInt())->getName();
    return movingName + "-to-" + fixedName;
}

// --------------- saveComponentAs -------------------
QString SimpleElastixRegistrationAction::saveComponentAs(ImageComponent* comp, QString path) const {
    // check to avoid crash
    if (!Application::isAlive(comp)) {
        CAMITK_WARNING(tr("SimpleElastixRegistrationAction cannot find selected image %1. Aborting.").arg(getParameterValue("Fixed Image").toString()))
        Application::showStatusBarMessage("SimpleElastixRegistrationAction: cannot apply action: cannot find selected image.");
        return "";
    }
    QString originalFileName = comp->getFileName();
    QString fileNameWithoutPath = QFileInfo(originalFileName).fileName();
    QString newFileName = QFileInfo(path, fileNameWithoutPath).absoluteFilePath();
    comp->setFileName(newFileName);
    bool saveStatus = Application::save(comp);
    comp->setFileName(originalFileName); // reset
    // reset name if there was a problem
    if (!saveStatus) {
        CAMITK_WARNING(tr("Save component to temporary directory failed. Action Aborted."))
        return "";
    }
    else {
        return fileNameWithoutPath;
    }
}

// --------------- createParameterFile -------------------
bool SimpleElastixRegistrationAction::createParameterFile(QFileInfo parameterFileInfo, QString initialization, QString transformType, QString similarityMetric) const {
    QFile parameterFile(parameterFileInfo.absoluteFilePath());

    if (!parameterFile.open(QIODevice::WriteOnly | QIODevice::Text)) {
        return false;
    }

    QFile initFile(":/resources/ElastixParameter.txt.in");
    initFile.open(QIODevice::ReadOnly | QIODevice::Text);
    QTextStream in(&initFile);

    QTextStream out(&parameterFile);
    QString text;

    do {
        text = in.readLine();
        text.replace(QRegularExpression("@TRANSLATION_INITIALIZATION@"), initialization);
        text.replace(QRegularExpression("@TRANSFORM_TYPE@"), transformType);
        text.replace(QRegularExpression("@SIMILARITY_METRIC@"), similarityMetric);

        out << text << Qt::endl;
    }
    while (!text.isNull());

    parameterFile.close();
    initFile.close();

    return true;
}

// --------------- runElastix -------------------
bool SimpleElastixRegistrationAction::runElastix(QStringList arguments, QString workingDirectory) {
    CAMITK_INFO(tr("Please wait while elastix is running in directory %1 using arguments: \"%2\"").arg(workingDirectory).arg(arguments.join(" ")))

    ElastixRunner elastixRunner(getParameterValue("Elastix Executable").toString(), arguments, workingDirectory);
    // Connect to the outputReady signal
    QObject::connect(&elastixRunner, &ElastixRunner::outputReady, [](const QString & output) {
        std::cout << output.toStdString() << std::endl;
        qApp->processEvents(); // force camitk console refresh
    });
    elastixRunner.startProcess();
    lastExitCode = elastixRunner.getExitCode();
    lastStdOutput = elastixRunner.getStdOutput();
    lastProcessError = elastixRunner.getProcessError();

    CAMITK_INFO(tr("elastix has finished."))

    return (lastExitCode == 0 && lastProcessError.isEmpty());
}

// --------------- copyResults -------------------
QString SimpleElastixRegistrationAction::copyResults(QString tempDir) const {
    // Create the result subdirectory
    ImageComponent* moving = imageComponentList.value(getParameterValue("Moving Image").toInt());
    QDir resultDir = QDir(QFileInfo(moving->getFileName()).absolutePath() + "/" + transformName());

    if (!resultDir.mkpath(resultDir.path())) {
        CAMITK_WARNING(tr("Cannot create result directory %1.").arg(resultDir.absolutePath()))
        return "";
    }

    QString newBasename = transformName() + "-" + getParameterValue("Result Name").toString();

    if (!copyFile("result.0.mhd", QDir(tempDir), newBasename + ".mhd", resultDir)) {
        CAMITK_WARNING(tr("Cannot copy \"result.0.mhd\" from %1 to %2/%3.").arg(tempDir).arg(resultDir.absolutePath()).arg(newBasename + ".mhd"))
        return "";
    }

    if (!copyFile("result.0.raw", QDir(tempDir), newBasename + ".raw", resultDir)) {
        CAMITK_WARNING(tr("Cannot copy \"result.0.raw\" from %1 to %2/%3.").arg(tempDir).arg(resultDir.absolutePath()).arg(newBasename + ".raw"))
        return "";
    }

    // update mhd to reference proper raw file
    replaceInFile(resultDir.filePath(newBasename + ".mhd"), "result.0.raw", newBasename + ".raw");

    QString transformName = "Transform-" + newBasename + ".txt";
    if (!copyFile("TransformParameters.0.txt", QDir(tempDir), transformName, resultDir)) {
        CAMITK_WARNING(tr("Cannot copy \"TransformParameters.0.txt\" from %1 to %2/%3.").arg(tempDir).arg(resultDir.absolutePath()).arg(transformName))
        return "";
    }
    CAMITK_INFO(tr("Results copied from %1 to %2/%3.").arg(tempDir).arg(resultDir.absolutePath()).arg(newBasename + ".*"))

    // return the path to the image component filename that contains the result
    return resultDir.filePath(newBasename + ".mhd");
}

// --------------- copyFile -------------------
bool SimpleElastixRegistrationAction::copyFile(QString oldName, QDir source, QString newName, QDir destination) const {
    QString sourceFilename = source.filePath(oldName);
    QString destinationFilename = destination.filePath(newName);

    if (QFile::exists(destinationFilename)) {
        if (getParameterValue("Overwrite Results").toBool()) {
            QFile::remove(destinationFilename);
        }
        else {
            CAMITK_WARNING(tr("File %1 already exists. Set \"Overwrite Results\" parameter to true to overwrite").arg(destinationFilename))
            return false;
        }
    }

    // Check if the source file exists before moving
    return QFile::exists(sourceFilename) && QFile::copy(sourceFilename, destinationFilename);
}

// --------------- replaceInFile -------------------
bool SimpleElastixRegistrationAction::replaceInFile(const QString& fileName, const QString& searchString, const QString& replacementString) const {
    QFile file(fileName);

    if (!file.open(QIODevice::ReadWrite | QIODevice::Text)) {
        return false;
    }

    QTextStream in(&file);
    QString content = in.readAll();

    // Replace all occurrences of searchString with replacementString
    content.replace(searchString, replacementString);

    // Reset the file position to the beginning and truncate the file
    file.resize(0);
    in << content;

    file.close();
    return true;
}