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

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-testactions aims at testing an action extension individually.\n"
                          "It loads the given action extension library (dll or so), opens the\n"
                          "given component, and apply all the action that work on the given\n" "component.\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-testactions [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 action extension validity.
 *
 */
int main(int argc, char* argv[]) {
    std::exception_ptr otherException;
    try {
        int end; // End of options.
        options o(argc, argv, end);
        bool loadAdditionnalComponentExtension = false;

        // if specific help or no options provided
        if (o.help() || (o.action().empty() && o.input().empty())) {
            usage();
            return EXIT_SUCCESS;
        }

        if (o.action().empty()) {
            usage("Argument error: please provide an action 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;
        }

        // check input files
        QFileInfo inputComponent {o.input().c_str()};
        QFileInfo inputActionExtension {o.action().c_str()};
        QFileInfo additionalComponentExtension {o.component().c_str()};

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

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

        if (!o.component().empty()) {
            if (!additionalComponentExtension.exists()) {
                usage("Argument error: action dll/so file \"" +  o.component() + "\" does not exist.");
                return EXIT_FAILURE;
            }
            else {
                loadAdditionnalComponentExtension = true;
            }
        }

        std::cout << "camitk-testactions run with arguments:" << std::endl;
        std::cout << "- action library file: \"" << o.action() << "\"" << std::endl;
        std::cout << "- input test file: \"" << o.input() << "\"" << std::endl;
        if (loadAdditionnalComponentExtension) {
            std::cout << "- additional component extension: \"" << o.component() << "\"" << std::endl;
        }
        std::cout << "Working directory: " << QDir::currentPath().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-testactions", argc, argv, false, false); // No autoload + registerFileExtension false to avoid the execution of this testapp hampered by PopUp message

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

        //-- load all available component extension
        ExtensionManager::autoload(ExtensionManager::COMPONENT);

        testPassed();

        if (loadAdditionnalComponentExtension) {
            testInit("Loading additional component extension...");
            if (!ExtensionManager::loadExtension(ExtensionManager::COMPONENT, additionalComponentExtension.absoluteFilePath())) {
                testFailed();
                return EXIT_FAILURE;
            }
            testPassed();
        }

        testInit("Opening component: " + o.input() + "...");
        Component* comp = Application::open(o.input().data());

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

        comp->setSelected(true, true);

        testPassed();

        //-- load the action extension defined in the command line
        testInit("Loading extension: " + inputActionExtension.absoluteFilePath().toStdString() + "...");
        if (!ExtensionManager::loadExtension(ExtensionManager::ACTION, inputActionExtension.absoluteFilePath())) {
            testFailed();
            return EXIT_FAILURE;
        }

        testPassed();

        // get all the possible actions
        testInit("Checking number of actions that can be applied on component type \"" + comp->getHierarchy().constFirst().toStdString() + "\"...");
        ActionList allActions = Application::getActions(comp);

        if (allActions.isEmpty()) {
            // you cannot apply this action extension on this comp
            std::cout << "No action can be applied to component type \"" << comp->getHierarchy().constFirst().toStdString() << "\". Nothing to do." << std::endl;
            testPassed();
            return EXIT_SUCCESS;
        }

        std::cout << "Found " << allActions.size() << " actions." << std::endl;
        testPassed();

        std::map<Action::ApplyStatus, int> statusCount;
        int skippedCount = 0;

        for (int i = 0; i < allActions.size(); ++i) {
            std::cout << std::endl;
            Action* action = allActions.at(i);
            if (!action->getEmbedded()) {
                std::cout << (i + 1) << ". Skipping autotest for action " << action->getName().toStdString() << " as it is not embedded and might require user interaction." << std::endl;
                // post the events before calling the action
                skippedCount++;
            }
            else {
                std::cout << (i + 1) << ". Applying action " + action->getName().toStdString() << "..." << std::endl;
                action->setInputComponent(comp);

                Action::ApplyStatus status = action->apply();
                statusCount[status]++;

                std::string statusString;
                switch (status) {
                    case Action::ERROR:
                        statusString = "Action::ERROR";
                        break;
                    case Action::WARNING:
                        statusString = "Action::WARNING";
                        break;
                    case Action::ABORTED:
                        statusString = "Action::ABORTED";
                        break;
                    case Action::TRIGGERED:
                        statusString = "Action::TRIGGERED";
                        break;
                    case Action::SUCCESS:
                        statusString = "Action::SUCCESS";
                        break;
                }

                std::cout << "Return status is: " << statusString << std::endl;
            }
        }

        std::cout << std::endl;
        testInit("Overall tests results...");
        // Note: ABORTED is good: it means the action was not able to do something but reported it
        // it needs to be checked in case there is no reason for the apply() NOT to return SUCCESS.
        int passedCount = statusCount[Action::SUCCESS] + statusCount[Action::ABORTED];
        int failedCount = statusCount[Action::ERROR] + statusCount[Action::WARNING] + statusCount[Action::TRIGGERED];
        int totalCount = passedCount + failedCount;
        int percentage = passedCount * 100.0 / totalCount;
        std::cout << percentage << "% tests passed, " << failedCount << " tests failed out of " << totalCount << " runs, ";
        std::cout << skippedCount << " skipped." << std::endl;
        std::cout << statusCount[Action::SUCCESS] << " SUCCESS, " << statusCount[Action::ABORTED] << " ABORTED (please check if this is the expected behaviour)." << std::endl;

        if (failedCount == 0) {
            testPassed();
            return EXIT_SUCCESS;
        }
        else {
            testFailed();
            return EXIT_FAILURE;
        }

    }
    catch (const cli::exception& e) {
        std::cerr << "camitk-testactions aborted due to invalid arguments: " << e.what() << "." << std::endl;
        e.print(std::cerr);
        std::cerr << std::endl;
        usage();
        testFailed();
        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;
    }
}
