/*****************************************************************************
 * $CAMITK_LICENCE_BEGIN$
 *
 * CamiTK - Computer Assisted Medical Intervention ToolKit
 * (c) 2001-2016 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 "Log.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

    //diagnosis << "- ITK Version : " + QString(itkVersion::GetITKVersion());

    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 << "- CamiTK Global Installation Directory... " + Core::getGlobalInstallDir();
    diagnosis << "- User Installation Directory............ " + Core::getUserInstallDir();
    diagnosis << "- Current Working Directory.............. " + Core::getCurrentWorkingDir();
    diagnosis << "- Test Data Directory.................... " + Core::getTestDataDir();
    diagnosis << "- Component Extension Directories........ " + Core::getComponentDirectories().join(",");
    diagnosis << "- Action Extension Directories........... " + Core::getActionDirectories().join(",");

    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 (%APP_DATA% 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;

    //-- component extensions
    QStringList components;

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

    foreach(ComponentExtension *ce, allCE) {
        installationDirectory = ExtensionManager::getInstallationString(ce->getLocation());
        components << "  - " + ce->getName() + " (" + ce->getFileExtensions().join(", ") + ") " + installationDirectory; // 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());
        components << "  - " + ce->getName() + " (directory) " + installationDirectory;
        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) + " 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 (int i = 0; i < ae->getActions().size(); i++) {
            actionNames << ae->getActions().at(i)->getName(); // + ": " + a->getDescription();
        }
        installationDirectory = ExtensionManager::getInstallationString(ExtensionManager::getActionExtensions().key(ae));
        actions << "  - " + ae->getName() + " " + installationDirectory + ": " + QString::number(ae->getActions().size()) + " actions (" + actionNames.join(", ") + ")"; //+ ae->getDescription();
        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) + " by user)";
    diagnosis << "- Number of Actions...................... " + QString::number(extensionUnitCount);

    //-- details of the extensions
    diagnosis << "- Registered components (G=Global, L=Local, W=Working, U=User):";
    diagnosis += components;

    diagnosis << "- Registered actions (G=Global, L=Local, W=Working, U=User):";
    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() << "--camitkDir", 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 BugZilla / the forum)]" + 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 <<
              "Interpretation:            \n[Write here your interpretation of this 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
}


// ----------------------------------------------------------------
//
//                     OBSOLETE PUBLIC METHODS
//
// TODO CAMITK_DEPRECATED. This section list all the methods marked as deprecated.
// They are to be removed in CamiTK 4.0
// ----------------------------------------------------------------


// ------------- getComponentDir (static) -----------------
//TODO remove in CamiTK 4.0 CAMITK_DEPRECATED
const QString Core::getComponentDir() {
    QDir appDir(getGlobalInstallDir());
    appDir.cd("lib/" + QString(Core::shortVersion) + "/components");
    return appDir.canonicalPath().toUtf8();
}


// ------------- getActionDir (static) -----------------
//TODO remove in CamiTK 4.0 CAMITK_DEPRECATED
const QString Core::getActionDir() {
    QDir appDir(getGlobalInstallDir());
    appDir.cd("lib/" + QString(Core::shortVersion) + "/actions");
    return appDir.canonicalPath().toUtf8();
}


// ------------- getModuleDir (static) -----------------
//TODO remove in CamiTK 4.0 CAMITK_DEPRECATED
const QString Core::getModuleDir(const QString & subpath) {
    QDir appDir(getGlobalInstallDir());
    appDir.cd("lib/" + QString(Core::shortVersion) + "/" + subpath);
    return appDir.canonicalPath().toUtf8();
}

// ------------- getLibDir (static) -----------------
//TODO remove in CamiTK 4.0 CAMITK_DEPRECATED
const QString Core::getCoreLibDir() {
    return getGlobalInstallDir();
}

}
