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

#include <HotPlugActionExtension.h>
#include <HotPlugAction.h>
#include <HotPlugExtensionManager.h>

#include <ExtensionManager.h>
#include <Application.h>
#include <Action.h>
#include <Log.h>

#include <TransformEngine.h>
#include <CamiTKExtensionModel.h>

class TestCppHotPlug : public QObject {
    Q_OBJECT
private:
    QDir tempDir;
    bool allTestPassed;
    QDir currentTestTempDir;
    QString camitkFileBaseName;
    QString camitkFileFullPath;
    QString resourceSubdirectory; // resources subdirectory (where the test files for this qtest app are stored)

    void initCamiTKFile(QString camitkFileBaseName) {
        // copy the camitk file using provided base name into the current test temp directory
        this->camitkFileBaseName = camitkFileBaseName;
        camitkFileFullPath = currentTestTempDir.filePath(camitkFileBaseName + ".camitk");
        QVERIFY(QFile::copy(resourceSubdirectory + "/" + camitkFileBaseName + ".camitk", camitkFileFullPath));
    }

    void testCppHotPlugFile(QString camitkFileBaseName) {
        initCamiTKFile(camitkFileBaseName);

        // check the initial state
        QCOMPARE(camitk::HotPlugExtensionManager::getLoadedExtensions().size(), 0);
        QCOMPARE(camitk::Application::getActions().size(), 0);

        //-- load (create the instances and rebuild)
        camitk::HotPlugExtensionManager::setVerifyOrRebuildOption(true);

        // instantiate all actions
        camitk::HotPlugActionExtension* actionExtension = nullptr;
        actionExtension = camitk::HotPlugExtensionManager::load(camitkFileFullPath);
        QVERIFY(actionExtension != nullptr);

        QVERIFY(actionExtension->getName() != "");
        QVERIFY(actionExtension->getDescription() != "");
        QCOMPARE(actionExtension->getLocation(), camitkFileFullPath);
        QCOMPARE(actionExtension->getLocation(), camitkFileFullPath);

        // test without the application
        QVERIFY(actionExtension->getActions().size() > 0);
        for (auto a : actionExtension->getActions()) {
            camitk::HotPlugAction* hotPlugAction = dynamic_cast<camitk::HotPlugAction*>(a);
            QVERIFY(hotPlugAction != nullptr);
            QVERIFY(hotPlugAction->update());
        }

        // compare instantiated actions in the action extension with the expected values from the data model
        TransformEngine transformEngine;
        CamiTKExtensionModel camitkExtensionModel(camitkFileFullPath);
        VariantDataModel& dataModel = camitkExtensionModel.getModel();
        QString extensionName = transformEngine.transformToString("$title(name)$", QJsonObject::fromVariantMap(dataModel.getValue().toMap()));
        QCOMPARE(actionExtension->getName(), extensionName);
        QCOMPARE(actionExtension->getDescription(), dataModel["description"].toString());
        QCOMPARE(actionExtension->getActions().size(), dataModel["actions"].size());
        for (int i = 0; i < dataModel["actions"].size(); i++) {
            VariantDataModel& actionDataModel = dataModel["actions"][i];
            QString actionName = transformEngine.transformToString("$title(name)$", QJsonObject::fromVariantMap(actionDataModel.getValue().toMap()));
            camitk::Action* a = actionExtension->getActions().at(i);
            QVERIFY(a != nullptr);
            QCOMPARE(a->getDescription(), actionDataModel["description"].toString());
            QCOMPARE(a->getComponentClassName(), actionDataModel["componentClass"].toString());
            QCOMPARE(a->getFamily(), actionDataModel["classification"]["family"].toString());
            QCOMPARE(a->apply(), camitk::Action::SUCCESS);
        }

        // compare instantiated actions registered in the Application with the expected values from the data model
        QCOMPARE(camitk::Application::getActions().size(), dataModel["actions"].size());
        for (int i = 0; i < dataModel["actions"].size(); i++) {
            VariantDataModel& actionDataModel = dataModel["actions"][i];
            QString actionName = transformEngine.transformToString("$title(name)$", QJsonObject::fromVariantMap(actionDataModel.getValue().toMap()));
            camitk::Action* a = camitk::Application::getAction(actionName);
            QVERIFY(a != nullptr);
            QCOMPARE(a->getDescription(), actionDataModel["description"].toString());
            QCOMPARE(a->getComponentClassName(), actionDataModel["componentClass"].toString());
            QCOMPARE(a->getFamily(), actionDataModel["classification"]["family"].toString());
            QCOMPARE(a->apply(), camitk::Action::SUCCESS);
        }

        //-- unload (this will delete actionExtension)
        QVERIFY(camitk::HotPlugExtensionManager::unload(camitkFileFullPath));

        // check the final state
        QCOMPARE(camitk::Application::getActions().size(), 0);
        QCOMPARE(camitk::HotPlugExtensionManager::getLoadedExtensions().size(), 0);

        // should return false as ExtensionManager does not manage any .camitk extension
        QCOMPARE(camitk::ExtensionManager::unloadActionExtension(camitkFileFullPath), false);
        QCOMPARE(camitk::ExtensionManager::getActionExtensionsList().size(), 0);
    }

private slots:

    // called once before any tests are run.
    void initTestCase() {
        // the resource subdirectory corresponding to this this qtest app (see also corresponding .qrc)
        resourceSubdirectory = ":/cpphotplug/";

        // Ensure all log messages are visible in the standard output
        camitk::Log::getLogger()->setLogLevel(camitk::InterfaceLogger::TRACE);
        camitk::Log::getLogger()->setMessageBoxLevel(camitk::InterfaceLogger::NONE);

        // create temporary location for all the tests (tmpDir)
        QString tempPath = QStandardPaths::writableLocation(QStandardPaths::TempLocation) + "/CamiTKExtensionCheck_" + QDateTime::currentDateTime().toString("yyyyMMddHHmmss");
        QVERIFY(QDir().mkpath(tempPath));
        tempDir.setPath(tempPath);

        // init global test status
        allTestPassed = true;
    }

    // called once after all tests have been run.
    void cleanupTestCase() {
        if (allTestPassed) {
            qDebug().noquote().nospace() << "Removing test application temp directory recursively: " << tempDir.absolutePath();
            tempDir.removeRecursively();
        }
        else {
            qDebug().noquote().nospace() << "Temporary test application directory not removed: " << tempDir.path();
        }
    }

    // called before each test function
    void init() {
        // Create temporary dir just for this test that has the name of the current test
        QString tempPath = tempDir.filePath(QTest::currentTestFunction());
        QVERIFY(QDir().mkpath(tempPath));
        currentTestTempDir.setPath(tempPath);
    }

    // called after each test function.
    void cleanup() {
        if (!QTest::currentTestFailed()) {
            qDebug().noquote().nospace() << "Removing current test temp directory recursively: " << currentTestTempDir.absolutePath();
            currentTestTempDir.removeRecursively();
        }
        else {
            allTestPassed = false;
            qDebug().noquote().nospace() << "Temporary directory not removed: " << currentTestTempDir.path();
        }
    }

    void parameters() {
        testCppHotPlugFile("parametersCppHotPlug");
    }

    void thresholdExtension() {
        testCppHotPlugFile("thresholdExtensionCppHotPlug");
    }

    void multiActions() {
        testCppHotPlugFile("multiActionsCppHotPlug");
    }

    void registering() {
        initCamiTKFile("multiActionsCppHotPlug");

        qDebug() << "initial state : no loaded extensions, no loaded actions";
        QCOMPARE(camitk::HotPlugExtensionManager::getLoadedExtensions().size(), 0);
        QCOMPARE(camitk::Application::getActions().size(), 0);

        camitk::HotPlugExtensionManager::setVerifyOrRebuildOption(true);

        qDebug() << "Register and try to load the extension";
        QVERIFY(camitk::HotPlugExtensionManager::registerExtension(camitkFileFullPath));
        QVERIFY(camitk::Application::getActions().size() > 0);
        QVERIFY(camitk::HotPlugExtensionManager::getRegisteredExtensionFiles().size() == 1);

        qDebug() << "Unload from memory";
        QVERIFY(camitk::HotPlugExtensionManager::unload(camitkFileFullPath));
        QCOMPARE(camitk::HotPlugExtensionManager::getLoadedExtensions().size(), 0);
        QCOMPARE(camitk::Application::getActions().size(), 0);

        qDebug() << "Load all registered (= reload)";
        QVERIFY(camitk::HotPlugExtensionManager::loadAll());
        QVERIFY(camitk::Application::getActions().size() > 0);
        QVERIFY(camitk::HotPlugExtensionManager::getLoadedExtensions().size() > 0);

        qDebug() << "Unload all from memory";
        QVERIFY(camitk::HotPlugExtensionManager::unloadAll());
        QCOMPARE(camitk::Application::getActions().size(), 0);
        QCOMPARE(camitk::HotPlugExtensionManager::getLoadedExtensions().size(), 0);

        qDebug() << "Load all registered (= reload)";
        QVERIFY(camitk::HotPlugExtensionManager::loadAll());
        QVERIFY(camitk::Application::getActions().size() > 0);
        QVERIFY(camitk::HotPlugExtensionManager::getLoadedExtensions().size() > 0);

        qDebug() << "Unregistered (and unload from memory)";
        QVERIFY(camitk::HotPlugExtensionManager::unregisterExtension(camitkFileFullPath));
        QCOMPARE(camitk::HotPlugExtensionManager::getRegisteredExtensionFiles().size(), 0);
        QCOMPARE(camitk::Application::getActions().size(), 0);

        qDebug() << "Load all registered (while none are registered)";
        QVERIFY(camitk::HotPlugExtensionManager::loadAll());
        QCOMPARE(camitk::HotPlugExtensionManager::getLoadedExtensions().size(), 0);
        QCOMPARE(camitk::Application::getActions().size(), 0);
    }
};

QTEST_MAIN(TestCppHotPlug)

#include "TestCppHotPlug.moc"