/* This file is part of KDevelop
    Copyright 2010 Esben Mose Hansen<kde@mosehansen.dk>

    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Library General Public
    License as published by the Free Software Foundation; either
    version 2 of the License, or (at your option) any later version.

    This library 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
    Library General Public License for more details.

    You should have received a copy of the GNU Library General Public License
    along with this library; see the file COPYING.LIB.  If not, write to
    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
    Boston, MA 02110-1301, USA.
*/

#include "test_cmakemanager.h"
#include "testhelpers.h"
#include "cmakemodelitems.h"
#include <icmakemanager.h>

#include <qtest.h>

#include <interfaces/iplugincontroller.h>
#include <interfaces/icore.h>
#include <project/interfaces/iprojectfilemanager.h>
#include <project/interfaces/ibuildsystemmanager.h>
#include <tests/autotestshell.h>
#include <tests/testproject.h>
#include <tests/testcore.h>

QTEST_MAIN(TestCMakeManager)

using namespace KDevelop;

void TestCMakeManager::initTestCase()
{
    AutoTestShell::init();
    TestCore::initialize();

    cleanup();
}

void TestCMakeManager::cleanupTestCase()
{
    TestCore::shutdown();
}

void TestCMakeManager::cleanup()
{
    foreach(IProject* p, ICore::self()->projectController()->projects()) {
        ICore::self()->projectController()->closeProject(p);
    }
    QVERIFY(ICore::self()->projectController()->projects().isEmpty());
}

void TestCMakeManager::testWithBuildDirProject()
{
    loadProject("with_build_dir");
}

void TestCMakeManager::testIncludePaths()
{
    IProject* project = loadProject("single_subdirectory");
    Path sourceDir = project->path();

    Path fooCpp(sourceDir, "subdir/foo.cpp");
    QVERIFY(QFile::exists(fooCpp.toLocalFile()));
    QList< ProjectBaseItem* > items = project->itemsForPath(IndexedString(fooCpp.pathOrUrl()));
    QEXPECT_FAIL("", "Will fix soon, hopefully", Continue);
    QCOMPARE(items.size(), 2); // once the target, once the plain file
    ProjectBaseItem* fooCppItem = items.first();

    Path::List includeDirs = project->buildSystemManager()->includeDirectories(fooCppItem);
    QVERIFY(includeDirs.size() >= 3);

    Path buildDir(sourceDir, "build/");
    QVERIFY(includeDirs.contains(buildDir));

    Path subBuildDir(sourceDir, "build/subdir/");
    QVERIFY(includeDirs.contains(subBuildDir));

    Path subDir(sourceDir, "subdir/");
    QVERIFY(includeDirs.contains(subDir));
}

void TestCMakeManager::testRelativePaths()
{
    IProject* project = loadProject("relative_paths", "/out");

    Path codeCpp(project->path(), "../src/code.cpp");
    QVERIFY(QFile::exists( codeCpp.toLocalFile()));
    QList< ProjectBaseItem* > items = project->itemsForPath(IndexedString(codeCpp.pathOrUrl()));
    QEXPECT_FAIL("", "Will fix soon, hopefully", Abort);
    QCOMPARE(items.size(), 1); // once in the target
    ProjectBaseItem* fooCppItem = items.first();

    Path::List includeDirs = project->buildSystemManager()->includeDirectories(fooCppItem);

    Path incDir(project->path(), "../inc/");
    QVERIFY(includeDirs.contains( incDir ));
}

void TestCMakeManager::testTargetIncludePaths()
{
    IProject* project = loadProject("target_includes");

    Path mainCpp(project->path(), "main.cpp");
    QVERIFY(QFile::exists(mainCpp.toLocalFile()));
    QList< ProjectBaseItem* > items = project->itemsForPath(IndexedString(mainCpp.pathOrUrl()));
    QEXPECT_FAIL("", "Will fix soon, hopefully", Continue);
    QCOMPARE(items.size(), 2); // once the plain file, once the target

    bool foundInTarget = false;
    foreach(ProjectBaseItem* mainCppItem, items) {
        ProjectBaseItem* mainContainer = mainCppItem->parent();

        Path::List includeDirs = project->buildSystemManager()->includeDirectories(mainCppItem);

        if (mainContainer->target()) {
            foundInTarget = true;
            Path targetIncludesDir(project->path(), "includes/");
            QVERIFY(includeDirs.contains(targetIncludesDir));
        }
    }
    QEXPECT_FAIL("", "Will fix soon, hopefully", Continue);
    QVERIFY(foundInTarget);
}

void TestCMakeManager::testTargetIncludeDirectories()
{
    IProject* project = loadProject("target_include_directories");

    Path mainCpp(project->path(), "main.cpp");
    QVERIFY(QFile::exists(mainCpp.toLocalFile()));
    QList< ProjectBaseItem* > items = project->itemsForPath(IndexedString(mainCpp.pathOrUrl()));
    QEXPECT_FAIL("", "Will fix soon, hopefully", Continue);
    QCOMPARE(items.size(), 2); // once the plain file, once the target

    bool foundInTarget = false;
    foreach(ProjectBaseItem* mainCppItem, items) {
        ProjectBaseItem* mainContainer = mainCppItem->parent();

        Path::List includeDirs = project->buildSystemManager()->includeDirectories(mainCppItem);

        if (mainContainer->target()) {
            foundInTarget = true;
            QVERIFY(includeDirs.contains(Path(project->path(), "includes/")));
            QVERIFY(includeDirs.contains(Path(project->path(), "libincludes/")));
        }
    }
    QEXPECT_FAIL("", "files aren't being added to the target", Continue);
    QVERIFY(foundInTarget);
}

void TestCMakeManager::testQt5App()
{
    IProject* project = loadProject("qt5_app");

    Path mainCpp(project->path(), "main.cpp");
    QVERIFY(QFile::exists(mainCpp.toLocalFile()));
    QList< ProjectBaseItem* > items = project->itemsForPath(IndexedString(mainCpp.pathOrUrl()));
    QEXPECT_FAIL("", "Will fix soon, hopefully", Continue);
    QCOMPARE(items.size(), 2); // once the plain file, once the target

    bool foundCore = false, foundGui = false, foundWidgets = false;
    foreach(ProjectBaseItem* mainCppItem, items) {
        Path::List includeDirs = project->buildSystemManager()->includeDirectories(mainCppItem);
        foreach(const Path& include, includeDirs) {
            QString filename = include.lastPathSegment();
            foundCore |= filename == "QtCore";
            foundGui |= filename == "QtGui";
            foundWidgets |= filename == "QtWidgets";
        }
    }
    QVERIFY(foundCore);
    QVERIFY(foundGui);
    QVERIFY(foundWidgets);
}

void TestCMakeManager::testQt5AppOld()
{
    IProject* project = loadProject("qt5_app_old");

    Path mainCpp(project->path(), "main.cpp");
    QVERIFY(QFile::exists(mainCpp.toLocalFile()));
    QList< ProjectBaseItem* > items = project->itemsForPath(IndexedString(mainCpp.pathOrUrl()));
    QEXPECT_FAIL("", "Will fix soon, hopefully", Continue);
    QCOMPARE(items.size(), 2); // once the plain file, once the target

    bool foundCore = false, foundGui = false, foundWidgets = false;
    foreach(ProjectBaseItem* mainCppItem, items) {
        Path::List includeDirs = project->buildSystemManager()->includeDirectories(mainCppItem);
        foreach(const Path& include, includeDirs) {
            QString filename = include.lastPathSegment();
            foundCore |= filename == "QtCore";
            foundGui |= filename == "QtGui";
            foundWidgets |= filename == "QtWidgets";
        }
    }
    QVERIFY(foundCore);
    QVERIFY(foundGui);
    QVERIFY(foundWidgets);
}

void TestCMakeManager::testKF5App()
{
    IProject* project = loadProject("kf5_app");

    Path mainCpp(project->path(), "main.cpp");
    QVERIFY(QFile::exists(mainCpp.toLocalFile()));
    QList< ProjectBaseItem* > items = project->itemsForPath(IndexedString(mainCpp.pathOrUrl()));
    QEXPECT_FAIL("", "Will fix soon, hopefully", Continue);
    QCOMPARE(items.size(), 2); // once the plain file, once the target

    bool foundCore = false, foundGui = false, foundWidgets = false, foundWidgetsAddons = false;
    foreach(ProjectBaseItem* mainCppItem, items) {
        Path::List includeDirs = project->buildSystemManager()->includeDirectories(mainCppItem);
        qDebug() << "xxxxxxxxx" << includeDirs;
        foreach(const Path& include, includeDirs) {
            QString filename = include.lastPathSegment();
            foundCore |= filename == "QtCore";
            foundGui |= filename == "QtGui";
            foundWidgets |= filename == "QtWidgets";
            foundWidgetsAddons |= filename == "KWidgetsAddons";
        }
    }
    QVERIFY(foundCore);
    QVERIFY(foundGui);
    QVERIFY(foundWidgets);
    QVERIFY(foundWidgetsAddons);
}

void TestCMakeManager::testDefines()
{
    IProject* project = loadProject("defines");

    Path mainCpp(project->path(), "main.cpp");
    QVERIFY(QFile::exists(mainCpp.toLocalFile()));
    QList< ProjectBaseItem* > items = project->itemsForPath(IndexedString(mainCpp.pathOrUrl()));
    QEXPECT_FAIL("", "Will fix soon, hopefully", Continue);
    QCOMPARE(items.size(), 2); // once the plain file, once the target

    bool foundInTarget = false;
    foreach(ProjectBaseItem* mainCppItem, items) {
        QHash<QString, QString> defines = project->buildSystemManager()->defines(mainCppItem);

        QCOMPARE(defines.value("B", QStringLiteral("not found")), QString());
        QCOMPARE(defines.value("BV", QStringLiteral("not found")), QStringLiteral("1"));
        QCOMPARE(defines.value("BV2", QStringLiteral("not found")), QStringLiteral("2"));

//         QCOMPARE(defines.value("BAR", QStringLiteral("not found")), QStringLiteral("foo"));
//         QCOMPARE(defines.value("FOO", QStringLiteral("not found")), QStringLiteral("bar"));
//         QCOMPARE(defines.value("BLA", QStringLiteral("not found")), QStringLiteral("blub"));
        QCOMPARE(defines.value("ASDF", QStringLiteral("not found")), QStringLiteral("asdf"));
        QCOMPARE(defines.value("XYZ", QStringLiteral("not found")), QString());
        QCOMPARE(defines.value("A", QStringLiteral("not found")), QString());
        QCOMPARE(defines.value("AV", QStringLiteral("not found")), QStringLiteral("1"));
        QCOMPARE(defines.value("AV2", QStringLiteral("not found")), QStringLiteral("2"));
        QCOMPARE(defines.value("C", QStringLiteral("not found")), QString());
        QCOMPARE(defines.value("CV", QStringLiteral("not found")), QStringLiteral("1"));
        QCOMPARE(defines.value("CV2", QStringLiteral("not found")), QStringLiteral("2"));
        QCOMPARE(defines.size(), 13);
        foundInTarget = true;
    }
    QVERIFY(foundInTarget);
}

void TestCMakeManager::testCustomTargetSources()
{
    IProject* project = loadProject("custom_target_sources");

    QEXPECT_FAIL("", "Will fix soon, hopefully", Abort);
    QList<ProjectTargetItem*> targets = project->buildSystemManager()->targets(project->projectItem());
    QVERIFY(targets.size() == 1);

    ProjectTargetItem *target = targets.first();
    QCOMPARE(target->fileList().size(), 1);
    QCOMPARE(target->fileList().first()->baseName(), QStringLiteral("foo.cpp"));
}

void TestCMakeManager::testConditionsInSubdirectoryBasedOnRootVariables()
{
    IProject* project = loadProject("conditions_in_subdirectory_based_on_root_variables");

    Path rootFooCpp(project->path(), "foo.cpp");
    QVERIFY(QFile::exists(rootFooCpp.toLocalFile()));
    QList< ProjectBaseItem* > rootFooItems = project->itemsForPath(IndexedString(rootFooCpp.pathOrUrl()));
    QEXPECT_FAIL("", "files aren't being added to the target", Continue);
    QCOMPARE(rootFooItems.size(), 4); // three items for the targets, one item for the plain file

    Path subdirectoryFooCpp(project->path(), "subdirectory/foo.cpp");
    QVERIFY(QFile::exists(subdirectoryFooCpp.toLocalFile()));
    QList< ProjectBaseItem* > subdirectoryFooItems = project->itemsForPath(IndexedString(subdirectoryFooCpp.pathOrUrl()));

    QEXPECT_FAIL("", "files aren't being added to the target", Continue);
    QCOMPARE(subdirectoryFooItems.size(), 4); // three items for the targets, one item for the plain file
}

void TestCMakeManager::testFaultyTarget()
{
    loadProject("faulty_target");
}
