/*****************************************************************************
 * $CAMITK_LICENCE_BEGIN$
 *
 * CamiTK - Computer Assisted Medical Intervention ToolKit
 * (c) 2001-2018 Univ. Grenoble Alpes, CNRS, TIMC-IMAG UMR 5525 (GMCAO)
 *
 * 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 "CamiTKVersion.h"
#include "ExtensionManager.h"
#include "Action.h"
#include "Application.h"

// -- QT stuff
#include <QDir>
#include <QApplication>
#include <QProcess>

#include <vtkVersion.h>
// @todo fix itk version matter
//#include <itkVersion.h>

// MSVC does not have the C++ Standard rint function (it should be included in any C99 implementation)
#if defined(_WIN32) && !defined(__MINGW32__) && (_MSC_VER < 1800)
#include <math.h>
double rint(double x) {
    // middle value point test
    if (ceil(x + 0.5) == floor(x + 0.5)) {
        int a = (int)ceil(x);
        if (a % 2 == 0) {
            return ceil(x);
        }
        else {
            return floor(x);
        }
    }
    else {
        return floor(x + 0.5);
    }
}
#endif

namespace camitk {

// ------------- getPaths (static) -----------------
const QString Core::getPaths() {
    QStringList diagnosis;

    QString compileType;
    if (isDebugBuild()) {
        compileType = "DEBUG";
    }
    else {
        compileType = "RELEASE";
    }

    QString stringOS;

#ifdef _WIN32
    stringOS = "WIN32";
#endif

#ifdef __APPLE__
    stringOS = "APPLE";
#endif

#ifdef __linux__
    stringOS = "LINUX";
#endif

    // for simplification reason, if globlal dir is equals to cwd, show it
    QString globalDir = Core::getGlobalInstallDir();
    QString userDir = Core::getUserInstallDir();
    QString currentWorkingDir = Core::getCurrentWorkingDir();
    if (globalDir == currentWorkingDir) {
        globalDir = "[not globally installed: using current working directory as repository]";
    }
    else if (globalDir == userDir) {
        globalDir = "[locally installed version: using user config directory as repository]";
    }

    diagnosis << "- CamiTK version........................... " + QString(Core::version);
    diagnosis << "- CamiTK Short Version..................... " + QString(Core::shortVersion);
    diagnosis << "- CamiTK SO NAME........................... " + QString(Core::soVersion);
    diagnosis << "- Operating System......................... " + stringOS;
    diagnosis << "- Build type............................... " + compileType;
    diagnosis << "- QT Version............................... " + QString(QT_VERSION_STR);
    diagnosis << "- VTK Version.............................. " + QString(vtkVersion::GetVTKVersion());
    diagnosis << "- Global Installation Directory [G]........ " + globalDir;
    diagnosis << "- Local Installation Directory [L]......... " + userDir;
    diagnosis << "- Current Working Directory [W]............ " + currentWorkingDir;
    diagnosis << "- Test Data Directory...................... " + Core::getTestDataDir();
    diagnosis << "- Component Extension Directories.......... " + Core::getComponentDirectories().join("\n                                            ");
    diagnosis << "- Action Extension Directories............. " + Core::getActionDirectories().join("\n                                            ");

    return diagnosis.join("\n");
}

// ------------- getConfig (static) -----------------
const QString Core::getConfig() {
    QStringList diagnosis;

    //-- first get all paths
    diagnosis << getPaths();

    // how many extensions of a givent type (component or action) are available
    unsigned int extensionCount = 0;
    // how many file formats are managed (for component extension) or how many single actions are available (for action extension)
    unsigned int extensionUnitCount = 0;
    // how many extensions installed globally (on the machine/CAMITK_DIR directory)
    unsigned int globalCount = 0;
    // how many extensions installed locally in the user global installation (%APPDATA% on win, and ~/.config on Linux/Mac)
    unsigned int localCount = 0;
    // how many extensions installed in the current working directory (generally the build directory)
    unsigned int workingDirCount = 0;
    // how many extensions installed manually in the application (using another specific path)
    unsigned int userCount = 0;
    QString installationDirectory;

    // get the global, user and current working directories
    QString globalDir = Core::getGlobalInstallDir();
    QString userDir = Core::getUserInstallDir();
    QString currentWorkingDir = Core::getCurrentWorkingDir();

    //-- component extensions
    QStringList components;

    // regular component extensions
    const QList< ComponentExtension* >& allCE = ExtensionManager::getComponentExtensionsList();

    foreach (ComponentExtension* ce, allCE) {
        installationDirectory = ExtensionManager::getInstallationString(ce->getLocation(), globalDir, userDir, currentWorkingDir);

        components << "  - " + installationDirectory + " " + ce->getName().leftJustified(35, '.')  + " " + ce->getFileExtensions().join(", "); // to get more information, use ce->getDescription();
        extensionCount++;
        extensionUnitCount += ce->getFileExtensions().size();
        if (installationDirectory == "[G]") {
            globalCount++;
        }
        else if (installationDirectory == "[L]") {
            localCount++;
        }
        else if (installationDirectory == "[W]") {
            workingDirCount++;
        }
        else if (installationDirectory == "[U]") {
            userCount++;
        }
    }

    // directory extensions
    const QList< ComponentExtension* >& allDCE = ExtensionManager::getDataDirectoryComponentsList();
    foreach (ComponentExtension* ce, allDCE) {
        installationDirectory = ExtensionManager::getInstallationString(ce->getLocation(), globalDir, userDir, currentWorkingDir);

        components << "  - " + installationDirectory + " " + ce->getName().leftJustified(35, '.') + " directory";
        extensionCount++;
        extensionUnitCount += ce->getFileExtensions().size();
        if (installationDirectory == "[G]") {
            globalCount++;
        }
        else if (installationDirectory == "[L]") {
            localCount++;
        }
        else if (installationDirectory == "[W]") {
            workingDirCount++;
        }
        else if (installationDirectory == "[U]") {
            userCount++;
        }
    }

    diagnosis << "- Number of Component Extensions........... " + QString::number(extensionCount) + " (locations: " + QString::number(globalCount) + " global, " + QString::number(localCount) + " local, " + QString::number(workingDirCount) + " in working directory, " + QString::number(userCount) + " manually installed by user)";
    diagnosis << "- Number of File Extensions Supported...... " + QString::number(extensionUnitCount);

    //-- action extensions
    extensionCount = extensionUnitCount = globalCount = localCount = workingDirCount = userCount = 0;

    QStringList actions;
    const QList< ActionExtension* >& allActions = ExtensionManager::getActionExtensionsList();

    foreach (ActionExtension* ae, allActions) {
        QStringList actionNames;
        for (Action* a : ae->getActions()) {
            actionNames << a->getName();
        }

        installationDirectory = ExtensionManager::getInstallationString(ae->getLocation(), globalDir, userDir, currentWorkingDir);

        actions << "  - " +  installationDirectory + " " + ae->getName().leftJustified(35, '.') + " " + QString::number(ae->getActions().size()) + " actions"; // + ": " + actionNames.join(", ");
        extensionCount++;
        extensionUnitCount += ae->getActions().size();
        if (installationDirectory == "[G]") {
            globalCount++;
        }
        else if (installationDirectory == "[L]") {
            localCount++;
        }
        else if (installationDirectory == "[W]") {
            workingDirCount++;
        }
        else if (installationDirectory == "[U]") {
            userCount++;
        }
    }

    diagnosis << "- Number of Action Extensions.............. " + QString::number(extensionCount) + " (locations: " + QString::number(globalCount) + " global, " + QString::number(localCount) + " local, " + QString::number(workingDirCount) + " in working directory, " + QString::number(userCount) + " manually installed by user)";
    diagnosis << "- Number of Actions........................ " + QString::number(extensionUnitCount);

    //-- details of the extensions
    diagnosis << "- Registered components:";
    diagnosis += components;

    diagnosis << "- Registered actions:";
    diagnosis += actions;

    return diagnosis.join("\n");
}

// ------------- getInstallDirectories (static) -----------------
const QStringList Core::getInstallDirectories(QString suffix) {
    QStringList dir;

    // find the build directory according to the current directory
    QDir currentWorkingDir(getCurrentWorkingDir());
    if (currentWorkingDir.cd(suffix)) {
        dir.append(currentWorkingDir.canonicalPath().toUtf8());
    }

    // user actions
    QDir userConfigDir(getUserInstallDir());
    if (userConfigDir.cd(suffix) && !dir.contains(userConfigDir.canonicalPath().toUtf8())) {
        dir.append(userConfigDir.canonicalPath().toUtf8());
    }

    // global actions
    QDir globalInstallDir(getGlobalInstallDir());
    if (globalInstallDir.cd(suffix) && !dir.contains(globalInstallDir.canonicalPath().toUtf8())) {
        dir.append(globalInstallDir.canonicalPath().toUtf8());
    }

    return dir;
}

// ------------- getActionDirectories (static) -----------------
const QStringList Core::getActionDirectories() {
    return getInstallDirectories("lib/" + QString(Core::shortVersion) + "/actions");
}

// ------------- getComponentDirectories (static) -----------------
const QStringList Core::getComponentDirectories() {
    return getInstallDirectories("lib/" + QString(Core::shortVersion) + "/components");
}

// ------------- getTestDataDir (static) -----------------
const QString Core::getTestDataDir() {
    QStringList testDataDirectories = getInstallDirectories("share/" + QString(Core::shortVersion) + "/testdata");
    // just returns the first one if exists
    if (testDataDirectories.size() > 0) {
        return testDataDirectories.at(0);
    }
    else
        // otherwise returns user home directory
    {
        return QDir::home().canonicalPath().toUtf8();
    }
}

// ------------- getGlobalInstallDir (static) -----------------
const QString Core::getGlobalInstallDir() {
    QDir camitkDir;
    QByteArray camitkDirectory;
    bool processOk = false;

    if (Application::getName() != "camitk-config") {
        // run camitk-imp to get the "installed" directory
        // note: camitk-imp must be in the path, or it is not considered as installed
        // (installed = available at any time)
        QProcess process;
        process.start("camitk-config", QStringList() << "--camitk-dir", QIODevice::ReadWrite | QIODevice::Text);

        if (process.waitForStarted(-1)) {
            while (process.waitForReadyRead(-1)) {
                camitkDirectory += process.readAllStandardOutput();
            }
            camitkDir = QString(camitkDirectory).trimmed();
            // if the directory does not exist check CamiTKDir.txt
            processOk = camitkDir.exists();
        }
    }

    // if there was a problem or if this is camitk-config, check CamiTKDir.txt
    if (!processOk) {
        // not found use the last installation path if available
        QFile file(getUserInstallDir() + "/CamiTKDir.txt");
        if (file.exists() && file.open(QIODevice::ReadOnly | QIODevice::Text)) {
            camitkDirectory = file.readLine();
            camitkDir = QString(camitkDirectory).trimmed();
        }
        else {
            // everything failed, use current application directory path (last chance)
            camitkDir = qApp->applicationDirPath();
            camitkDir.cdUp();
        }
    }

    return camitkDir.canonicalPath().toUtf8();
}

// ------------- getUserInstallDir (static) -----------------
const QString Core::getUserInstallDir() {
    // obtain (platform specific) application's data/settings directory from settings file
    return QFileInfo(Application::getSettings().fileName()).absolutePath();
}

// ------------- getCurrentWorkingDir (static) -----------------
const QString Core::getCurrentWorkingDir() {
    return QDir::currentPath();
}

// ------------- getBugReport (static) -----------------
const QString Core::getBugReport() {
    QStringList BugReport;
    QString     breakLine("\n\n");

    BugReport <<
              "About you:                           \n[Present yourself and your project you're working on with CamiTK]" + breakLine;
    BugReport <<
              "Overview:                            \n[Rewrite here a larger and more detailed restatement of your summary]" + breakLine;
    BugReport <<
              "Steps to Reproduce:                  \n[Write here the step - by - step process to reproduce the bug, including file to test (you can attach file on gitlab issue report system)]" + breakLine;
    BugReport <<
              "Actual VS Expected Result:           \n[Write here the result of the step - by - step process and explain why it is not what you expected]" + breakLine;
    BugReport << "Relevant logs and/or screenshots: \n[Paste any relevant logs - please use code blocks (```) to format console output, logs, and code as it's very hard to read otherwise.]" + breakLine;
    BugReport <<
              "Interpretation & Possible fixes:     \n[Write here your interpretation of this bug (If you can, link to the line of code that might be responsible for the problem)]" + breakLine;
    BugReport << "/label ~Bug" + breakLine;
    BugReport <<
              "CamiTK Version:                  \n\n" + Core::getConfig() + breakLine;

    return BugReport.join("\n");
}


// ------------- isDebugBuild (static) -----------------
const bool Core::isDebugBuild() {
#ifdef QT_DEBUG
    return true;
#else
    return false;
#endif
}


}
