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

// -- Core stuff
#include <Application.h>
#include <AbortException.h>
#include <Component.h>
#include <MainWindow.h>
#include <Core.h>
#include <ExtensionManager.h>
#include <Log.h>

// Different test levels to
const int LEVEL1 = 1;// Open application - Open extension - Open component
const int LEVEL2 = 2;// Open application - Open extension - Open component - Save component - Close component

using namespace camitk;

// CLI stuff
#include "CommandLineOptions.hxx"

// description of the application. Please update the manpage-prologue.1.in also if you modify this string.
const char* description = "camitk-testcomponents aims at testing a component extension\n" "individually. There are two possible levels of test:\n"
                          "- level 1 will create a CamiTK application, load the extension\n"
                          "and then open the provided component file. It verifies that the\n"
                          "extension can be loaded without error and that the managed file (of\n" "corresponding extension) can be opened without error.\n"
                          "- level 2 will do the same as level 1 plus save the component to\n"
                          "a new file and then close the component. It verifies that saving\n"
                          "the file and closing the file can be done without error.\n\n"
                          "Please visit http://camitk.imag.fr for more information.\n"
                          "(c) Univ. Grenoble Alpes, CNRS, Grenoble INP - UGA, TIMC, France";

// usage for this application
void usage(std::string msg = "") {
    if (msg != "") {
        std::cerr << msg << std::endl;
    }
    std::cerr << "Usage: camitk-testcomponents [options]" << std::endl << std::endl;
    std::cerr << description << std::endl;
    std::cerr << std::endl;
    std::cerr << "Version: " << Core::version() << std::endl;
    std::cerr << std::endl;
    std::cerr << "Options:" << std::endl;
    options::print_usage(std::cerr);
}

void testInit(std::string msg) {
    std::cout << msg << std::endl;
}

void testFailed() {
    std::cout << "[FAIL]" << std::endl;
}

void testPassed() {
    std::cout << "[OK]" << std::endl;
}

/**
 * @ingroup group_sdk_application_testapplications
 *
 * @brief
 * Testing tool to check component extension validity.
 *
 */
int main(int argc, char* argv[]) {
    std::exception_ptr otherException;
    try {
        int end; // End of options.
        options o(argc, argv, end);

        if (o.help() || (o.component().empty() && o.input().empty() && o.level().empty() && o.output_dir().empty())) {
            usage();
            return EXIT_SUCCESS;
        }

        if (o.component().empty()) {
            usage("Argument error: please provide a component dll/so file (needs complete path).");
            return EXIT_FAILURE;
        }

        if (o.input().empty()) {
            usage("Argument error: please provide a component test file to work with.");
            return EXIT_FAILURE;
        }

        if (o.level().empty() || (atoi(o.level().c_str()) != LEVEL1 && atoi(o.level().c_str()) != LEVEL2)) {
            usage("Argument error: please provide a valid test level (value is either 1 or 2).");
            return EXIT_FAILURE;
        }

        // check input files
        QFileInfo inputComponent(o.input().c_str());
        QFileInfo inputComponentExtension(o.component().c_str());
        QString outputDirectory;

        if (!inputComponent.exists()) {
            usage("Argument error: component test file \"" +  o.input() + "\" does not exist.");
            return EXIT_FAILURE;
        }

        if (!inputComponentExtension.exists()) {
            usage("Argument error: component dll/so file \"" +  o.component() + "\" does not exist.");
            return EXIT_FAILURE;
        }

        //-- optional argument
        if (o.output_dir() == "") {
            outputDirectory = QDir::temp().absolutePath();
        }
        else {
            // check given directory
            outputDirectory  = o.output_dir().c_str();
            if (! QDir(outputDirectory).exists()) {
                usage("Argument error: directory \"" +  outputDirectory.toStdString() + "\" does not exist.");
                return EXIT_FAILURE;
            }
        }

        int level = std::stoi(o.level());

        std::cout << "camitk-testcomponents run with arguments:" << std::endl;
        std::cout << "- component library file: \"" << o.component() << "\"" << std::endl;
        std::cout << "- input test file: \"" << o.input() << "\"" << std::endl;
        std::cout << "- level of test: \"" << level << "\"" << std::endl;
        std::cout << "- output directory: \"" << outputDirectory.toStdString() << "\"" << std::endl;

        testInit("Starting the camitk default application...");

        //-- init the camitk application context
        // no log , no time stamp for reproducible log diff
        Log::getLogger()->setLogLevel(InterfaceLogger::NONE);
        Log::getLogger()->setTimeStampInformation(false);
        Application a("camitk-testcomponents", argc, argv, false, false);// No autoload + registerFileExtension

        //-- avoid all redirection to console
        MainWindow* defaultMainWindow = dynamic_cast<MainWindow*>(a.getMainWindow());
        defaultMainWindow->redirectToConsole(false);

        testPassed();

        testInit("Loading extension: " + inputComponentExtension.absoluteFilePath().toStdString() + "...");

        //-- load the component extension defined in the command line
        if (!ExtensionManager::loadExtension(ExtensionManager::COMPONENT, inputComponentExtension.absoluteFilePath())) {
            testFailed();
            return EXIT_FAILURE;
        }

        // testing name and description
        ComponentExtension* componentExtension = ExtensionManager::getComponentExtensionsList().last();
        std::cout << "    Loaded extension: " << componentExtension->getName().toStdString()
                  << " (" << componentExtension->getDescription().toStdString() << "). "
                  << "Managed extension:" <<  componentExtension->getFileExtensions().join(", ").toStdString()
                  <<  std::endl;

        testPassed();

        testInit("Opening component: " + inputComponent.fileName().toStdString() + "...");
        Component* comp = Application::open(inputComponent.absoluteFilePath());

        if (comp == nullptr) {
            testFailed();
            return EXIT_FAILURE;
        }

        testPassed();

        // do not modify input file
        comp->setFileName(outputDirectory + "/" + inputComponent.fileName());

        switch (level) {
            case LEVEL2:
                testInit("Saving component to: " + inputComponent.fileName().toStdString() + "...");
                if (!Application::save(comp)) {
                    testFailed();
                    return EXIT_FAILURE;
                }

                testPassed();
            // no break as level 2 includes level 1
            case LEVEL1:
                testInit("Closing component: " + inputComponent.fileName().toStdString() + "...");
                if (!Application::close(comp)) {
                    testFailed();
                    return EXIT_FAILURE;
                }
                break;
            default:
                // should never comes to this!
                usage("Argument error: level \"" + std::to_string(level) + "\" is not implemented yet.");
                testFailed();
                return EXIT_FAILURE;
        }

        // whole test passed
        testPassed();
        return EXIT_SUCCESS;
    }
    catch (const cli::exception& e) {
        std::cerr << "camitk-testcomponents aborted due to invalid arguments: " << e.what() << "." << std::endl;
        e.print(std::cerr);
        std::cerr << std::endl;
        usage();
        return EXIT_FAILURE;
    }
    catch (const camitk::AbortException& e) {
        std::cerr << "camitk-testcomponents aborted by CamiTK abort exception: " << e.what() << "." << std::endl;
        testFailed();
        return EXIT_FAILURE;
    }
    catch (const std::exception& e) {
        std::cerr << "camitk-testcomponents aborted by std exception: " << e.what() << "." << std::endl;
        testFailed();
        return EXIT_FAILURE;
    }
    catch (...) {
        std::cerr << "camitk-testcomponents aborted by unknown exception" << std::endl;
        otherException = std::current_exception();
        try {
            if (otherException) {
                std::rethrow_exception(otherException);
            }
        }
        catch (const std::exception& e) {
            std::cerr << ": " << e.what() << ".";
        }
        std::cerr << std::endl;
        testFailed();
        return EXIT_FAILURE;
    }

}
