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

#include <iostream>
#include <vector>

#include <QRegularExpression>

using namespace camitk;

void usage(std::string programName) {
    std::cerr << "usage: " << programName << " -d path" << std::endl;
    std::cerr << "\t-d path\tdirectory to write the log file to" << std::endl;
    std::cerr << Core::version() << std::endl;
}

void printLog() {
    std::cout << "TRACE message:" << std::endl;
    CAMITK_TRACE_ALT("Test Log: TRACE")

    std::cout << "TRACE_IF(true) message:" << std::endl;
    CAMITK_TRACE_IF_ALT(true, "Test Log: TRACE_IF");

    std::cout << "TRACE_IF(false) silent message:" << std::endl;
    CAMITK_TRACE_IF_ALT(false, "Test Log: TRACE_IF");

    std::cout << "INFO message:" << std::endl;
    CAMITK_INFO_ALT("Test Log: INFO")

    std::cout << "INFO_IF(true) message:" << std::endl;
    CAMITK_INFO_IF_ALT(true, "Test Log: INFO_IF");

    std::cout << "INFO_IF(false) silent message:" << std::endl;
    CAMITK_INFO_IF_ALT(false, "Test Log: INFO_IF");

    std::cout << "WARNING message:" << std::endl;
    CAMITK_WARNING_ALT("Test Log: WARNING")

    std::cout << "WARNING_IF(true) message:" << std::endl;
    CAMITK_WARNING_IF_ALT(true, "Test Log: WARNING_IF");

    std::cout << "WARNING_IF(false) silent message:" << std::endl;
    CAMITK_WARNING_IF_ALT(false, "Test Log: WARNING_IF");

    std::cout << "ERROR message:" << std::endl;
    CAMITK_ERROR_ALT("Test Log: ERROR")

    std::cout << "ERROR_IF(true) message:" << std::endl;
    CAMITK_ERROR_IF_ALT(true, "Test Log: ERROR_IF");

    std::cout << "ERROR_IF(false) silent message:" << std::endl;
    CAMITK_ERROR_IF_ALT(false, "Test Log: ERROR_IF");
}

int testAllLevels() {
    for (const InterfaceLogger::LogLevel currentLevel : {
                InterfaceLogger::NONE, InterfaceLogger::ERROR, InterfaceLogger::WARNING, InterfaceLogger::INFO, InterfaceLogger::TRACE
            }) {
        std::cout << "Log level \"" << Log::getLevelAsString(currentLevel).toStdString() << "\": begin test..." << std::endl;

        Log::getLogger()->setLogLevel(currentLevel);
        if (Log::getLogger()->getLogLevel() != currentLevel) {
            std::cout << "[FAILED] log level should be set to: " << Log::getLevelAsString(currentLevel).toStdString() << " currently set to " << Log::getLevelAsString(Log::getLogger()->getLogLevel()).toStdString() << std::endl;
            return EXIT_FAILURE;
        }

        std::cout << "With debug information:" << std::endl;
        Log::getLogger()->setDebugInformation(true);
        if (!Log::getLogger()->getDebugInformation()) {
            std::cout << "[FAILED] debug information should be on." << std::endl;
            return EXIT_FAILURE;
        }
        printLog();

        std::cout << "Without debug information:" << std::endl;
        Log::getLogger()->setDebugInformation(false);
        if (Log::getLogger()->getDebugInformation()) {
            std::cout << "[FAILED] debug information should be off." << std::endl;
            return EXIT_FAILURE;
        }
        printLog();

        std::cout << "Log level \"" << Log::getLevelAsString(currentLevel).toStdString() << "\": end test." << std::endl;
    }

    return EXIT_SUCCESS;
}

int testAllParameters() {
    if (Log::getLogger()->getTimeStampInformation()) {
        std::cout << "[FAILED] time stamp information should be off." << std::endl;
        return EXIT_FAILURE;
    }

    if (Log::getLogger()->getMessageBoxLevel() != InterfaceLogger::NONE) {
        std::cout << "[FAILED] message box level should be set to NONE." << std::endl;
        return EXIT_FAILURE;
    }

    std::cout << "Testing with stdout output: begin test..." << std::endl;
    if (testAllLevels() == EXIT_FAILURE) {
        return EXIT_FAILURE;
    }
    std::cout << "Testing with stdout output: end test..." << std::endl;

    std::cout << "Testing without stdout output: begin test..." << std::endl;
    Log::getLogger()->setLogToStandardOutput(false);
    if (Log::getLogger()->getLogToStandardOutput()) {
        std::cout << "[FAILED] log to standard output should be off." << std::endl;
        return EXIT_FAILURE;
    }
    if (testAllLevels() == EXIT_FAILURE) {
        return EXIT_FAILURE;
    }
    std::cout << "Testing without stdout output: end test..." << std::endl;
    Log::getLogger()->setLogToStandardOutput(true);
    if (!Log::getLogger()->getLogToStandardOutput()) {
        std::cout << "[FAILED] log to standard output should be off." << std::endl;
        return EXIT_FAILURE;
    }

    return EXIT_SUCCESS;
}

QString cleanUp(QString input) {
    // remove full path (as build dir can be in different places)
    return input.replace(QRegularExpression("^.*/tutorials"), "$CMAKE_BINARY_DIR/tutorials");
}

int main(int argc, char* argv[]) {
    std::vector<std::string> argList(argv, argv + argc);
    std::exception_ptr otherException;
    try {
        if (argList.size() < 3) {
            usage(argList[0]);
            return EXIT_SUCCESS;
        }

        if (argList[1] != "-d") {
            std::cerr << "Argument errors: unknown argument \"" << argList[1] << "\"" << std::endl;
            usage(argList[0]);
            return EXIT_FAILURE;
        }

        QDir logDir(argList[2].c_str());
        if (!logDir.exists() && !logDir.mkpath(argList[2].c_str())) {
            std::cerr << "Argument errors: directory \"" << argList[2] << "\" does not exits and cannot be created." << std::endl;
            usage(argList[0]);
            return EXIT_FAILURE;
        }

        // Test CamiTK basic logger
        // Set logger parameters so that it can be compared reproductivily
        Log::getLogger()->setTimeStampInformation(false);
        // Cannot have any message box (no main application therefore no QWidget can be instantiated)
        Log::getLogger()->setMessageBoxLevel(InterfaceLogger::NONE);

        // Start test: this message should be visible as default level for CamiTK logger is warning
        CAMITK_WARNING_ALT("Application testlogger started. Log directory:" + cleanUp(logDir.canonicalPath()))

        if (testAllParameters() == EXIT_FAILURE) {
            return EXIT_FAILURE;
        }

        // check log to file features
        if (Log::getLogger()->getLogToFile()) {
            std::cout << "[FAILED] log to file should be off." << std::endl;
            return EXIT_FAILURE;
        }
        std::cout << "File info: " << Log::getLogger()->getLogFileInfo().absoluteFilePath().toStdString() << std::endl;

        Log::getLogger()->setLogLevel(InterfaceLogger::INFO); // so that the filename (that contains the time stamp is not printed)
        Log::getLogger()->setLogToFile(true);
        if (!Log::getLogger()->getLogToFile()) {
            std::cout << "[FAILED] log to file should be on." << std::endl;
            return EXIT_FAILURE;
        }
        /* problem: next line is not reproductible (filename has the time stamp). Obscure it but still compare the date for instance.
        std::cout << "File info: " << Log::getLogger()->getLogFileInfo().absoluteFilePath().toStdString() << std::endl;
        */
        // TODO test changing the directory during the same session
        // Then read the file and output it to stdout, to compare and test that everything is present

        // TODO a test application that crashes to check if log is still written to file up to the crash

        CAMITK_WARNING_ALT("Application testlogger finished.")
        return EXIT_SUCCESS;
    }
    catch (const camitk::AbortException& e) {
        std::cerr << argList[0] << "aborted by CamiTK abort exception: " << e.what() << "." << std::endl;
        return EXIT_FAILURE;
    }
    catch (const std::exception& e) {
        std::cerr << argList[0] << "aborted by std exception: " << e.what() << "." << std::endl;
        return EXIT_FAILURE;
    }
    catch (...) {
        std::cerr << argList[0] << "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;
        return EXIT_FAILURE;
    }
}

