/*****************************************************************************
 * $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 <PythonManager.h>
#include <ExtensionManager.h>
#include <Log.h>

#include <QTextStream>

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

    // -------------------- fileToString --------------------
    QString fileToString(const QString& filename) {
        QFile file(filename);
        QString fileContent;
        if (file.open(QFile::ReadOnly | QFile::Text)) {
            QTextStream in(&file);
            fileContent = in.readAll();
            file.close();
        }
        return fileContent;
    }

    /// Setup a directory with all the files declared under the given resource prefix.
    /// If there is a resource file in the prefix, use it to setup the virtual environment
    /// @param resourcePrefix is a prefix defined in the .qrc file
    void testPythonScript(QString resourcePrefix) {
        CAMITK_INFO_ALT("")
        CAMITK_INFO_ALT("Initializing CamiTK python interpreter...")
        camitk::PythonManager::initPython();
        QString pythonVersion = camitk::PythonManager::getPythonVersion();

        // create venv in current temp dir
        CAMITK_INFO_ALT("")
        CAMITK_INFO_ALT("Creating virtual env for '" + resourcePrefix + "' scripts...")
        QVERIFY(camitk::PythonManager::createVirtualEnv(currentTestTempDir.absolutePath()));
        QString venvPath = currentTestTempDir.absoluteFilePath(".venv");

        // get the list from the requirement file (if there is one)
        CAMITK_INFO_ALT("")
        CAMITK_INFO_ALT("Checking if scripts '" + resourcePrefix + "' has requirements...")
        QStringList packageList;
        // The requirement file is set in the resource with the alias "requirements.txt"
        QString requirementFilePath = ":/" + resourcePrefix + "/requirements.txt";
        QFile requirementFile(requirementFilePath);
        if (requirementFile.exists()) {
            // fill in the string list
            QString packageListString = fileToString(requirementFilePath);
            packageList = packageListString.split('\n');
            CAMITK_INFO_ALT(QString("Found a requirements.txt file with %1 requirements:\n- %2\nFrom: '%3'").arg(packageList.size()).arg(packageList.join("\n- ")).arg(packageListString))
            QVERIFY(packageList.size() >= 1);
        }

        CAMITK_INFO_ALT("")
        CAMITK_INFO_ALT("Installing requirements...")
        QVERIFY(camitk::PythonManager::installPackages(venvPath, packageList));

        CAMITK_INFO_ALT("Getting the list of scripts...")
        QString fullPrefix = ":/" + resourcePrefix + resourceSubdirectory + "/";
        QStringList scriptFiles = QDir(fullPrefix).entryList();
        QVERIFY(scriptFiles.size() >= 1); // at least one .py script
        CAMITK_INFO_ALT("Found " + QString::number(scriptFiles.size()) + " test files:\n- " + scriptFiles.join("\n- ") + "\n")

        for (QString scriptFile : scriptFiles) {
            QString filePath = fullPrefix + scriptFile;
            CAMITK_INFO_ALT("Running script " + scriptFile + " (from " + filePath + ")...")

            // load python script from the file
            QString pythonScript = fileToString(filePath);
            QVERIFY(pythonScript.size() > 0);

            // run it in the interpreter
            QString pythonError;
            camitk::PythonManager::runScript(venvPath, pythonScript, pythonError);
            QVERIFY(pythonError.isNull());

            CAMITK_INFO_ALT("Success...\n")
        }
    }

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 = "/pythonscript";

        // Ensure all log messages are visible in the standard output
        camitk::Log::getLogger()->setLogLevel(camitk::InterfaceLogger::TRACE);
        camitk::Log::getLogger()->setMessageBoxLevel(camitk::InterfaceLogger::NONE);
        // no time stamp for reproducible log diff
        camitk::Log::getLogger()->setTimeStampInformation(false);

        // Load all the component extensions in order to open file from python
        int argc = 1;
        char* args[] = {
            (char*)"test",
            NULL
        };

        // now load all the extensions
        camitk::ExtensionManager::autoload();

        // init global test status
        allTestPassed = true;

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

    // 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 the current test using the current test name
        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 scriptVariable() {
        // Test a script written directly as a QString and get the value back from the interpreter
        camitk::PythonManager::initPython();
        QVERIFY(camitk::PythonManager::getPythonVersion() != "");

        // create venv in current temp dir
        QVERIFY(camitk::PythonManager::createVirtualEnv(currentTestTempDir.absolutePath()));

        QString venvPath = currentTestTempDir.absoluteFilePath(".venv");

        QStringList packageList;
        // note: numpy and setuptools are not required by the script below itself,
        // but they are added to test that their addition is detected by installPackages
        packageList << "numpy";
        packageList << "setuptools";
        QVERIFY(camitk::PythonManager::installPackages(venvPath, packageList));

        QString pythonError;
        QMap<QString, QVariant> scriptVariables = camitk::PythonManager::runScript(venvPath, R"python(
import camitk
import sys
current_version = ".".join(map(str, sys.version_info[0:2]))
camitk.info(current_version)
print(f"from python: {current_version}")
        )python", pythonError);
        QString currentVersion = scriptVariables["current_version"].toString();
        QCOMPARE(camitk::PythonManager::getPythonVersion(), currentVersion);
        QVERIFY(pythonError.isNull());
    }

    void numpyImageComponent() {
        testPythonScript("numpy_imagecomponent");
    }

    void core() {
        testPythonScript("core");
    }

    void vtk() {
        // Note: do NOT call the script just vtk.py, python will not be able to load the vtk package!
        testPythonScript("test_vtk");
    }

};


QTEST_MAIN(TestPythonScript)

#include "TestPythonScript.moc"