/* Any copyright is dedicated to the Public Domain.
 * http://creativecommons.org/publicdomain/zero/1.0/
 * 
 * modules.cpp
 *
 * Author: pgess <v.melnychenko@xreate.org>
 * Created on June 18, 2017, 8:25 PM
 */

class Modules_AST2_Test;
class Modules_Discovery1_Test;
class Modules_Solve1_Test;

#define FRIENDS_MODULES_TESTS               \
    friend class ::Modules_AST2_Test;       \
    friend class ::Modules_Discovery1_Test; \
    friend class ::Modules_Solve1_Test;

#include "modules.h"
#include "aux/xreatemanager-decorators.h"
#include "aux/xreatemanager-modules.h"
#include "xreatemanager.h"
#include "modules/Parser.h"
#include "gtest/gtest.h"
#include <boost/filesystem.hpp>
#include <regex>
#include <clingo/clingocontrol.hh>

namespace fs = boost::filesystem;

using namespace std;
using namespace xreate;
using namespace xreate::modules;

TEST(Modules, AST1) {
    FILE* input = fopen("scripts/dsl/regexp.xreate","r");
    assert(input != nullptr);

    Scanner scanner(input);
    Parser parser(&scanner);
    parser.Parse();
    ASSERT_EQ(parser.errors->count, 0);
}

TEST(Modules, AST2){
    string code = R"Code(
    module {
        name(test1).
        status(untested).

        require provides(logging).
        include controller("/tmp/test-controller.ls").
        discover("/tmp/root/").
    }
    )Code";

    Scanner scanner(reinterpret_cast<const unsigned char*>(code.c_str()), code.size());
    Parser parser(&scanner);
    parser.Parse();

    ModuleRecord module = parser.module;

    ASSERT_EQ(2, module.__properties.size());
    ASSERT_EQ("name", module.__properties.front().getValueString());
    ASSERT_EQ("status", module.__properties.back().getValueString());

    ASSERT_EQ(1, module.__queries.size());
    ASSERT_EQ("provides", module.__queries.front().getValueString());

    ASSERT_EQ(1, module.__controllers.size());
    ASSERT_EQ("/tmp/test-controller.ls", module.__controllers.front());

    ASSERT_EQ(1, module.__discoveries.size());
    ASSERT_EQ("/tmp/root/", module.__discoveries.front());
}

TEST(Modules, Discovery1){
    const std::string dirModulesRoot = "/tmp/testModulesDiscovery1_t54723/";

    string codeA =
R"Code(module {
    name(testA).
    status(needToTestMore).
})Code";

    string codeB =
R"Code(module {
    name(testB).
    status(needToTestEvenMore).
})Code";

    string codeMain = string("module{discover \"") + dirModulesRoot + "\".}";

    fs::create_directories(dirModulesRoot);
    fs::ofstream fileA(dirModulesRoot + "a.xreate");
    fileA << codeA;
    fileA.close();

    fs::ofstream fileB(dirModulesRoot + "b.xreate");
    fileB << codeB;
    fileB.close();

    Scanner scanner(reinterpret_cast<const unsigned char*>(codeMain.c_str()), codeMain.size());
    Parser parser(&scanner);
    parser.Parse();

    ModulesRegistry registry;
    ModulesSolver solver(&registry);
    solver.discoverModules(parser.module);
    fs::remove_all(dirModulesRoot);

    std::string output = solver.__program.str();
    cout << output << endl;
    ASSERT_NE(string::npos, output.find("bind_module(0, name(testA))."));
    ASSERT_NE(string::npos, output.find("bind_module(1, status(needToTestEvenMore))."));
}

TEST(Modules, Requests1){

}

TEST(Modules, Solve1){
    const std::string dirModulesRoot = "/tmp/testModulesDiscovery1_t54724/";

    string codeA =
R"Code(module {
    name(testA).
    provide(superService).
    status(needToTestMore).
})Code";

    string codeB =
R"Code(module {
    name(testB).
    provide(superService).
    status(needToTestEvenMore).
})Code";

    string codeMain =
R"Code(module {
    discover("/tmp/testModulesDiscovery1_t54724/")
    include controller ("/tmp/testModulesDiscovery1_t54724/controller")

    require (superService)
})Code";

    string codeController =
R"Code(
status_score(0, needToTestEvenMore).
status_score(1, needToTestMore).

module_include_candidate(X, Y, Request):-
    module_require(X, Request); bind_module(Y, provide(Request)).

module_include_winner(X, Request, MaxScore) :-
    MaxScore = #max{Score: module_include_candidate(X, Y, Request), bind_module(Y, status(Status)), status_score(Score, Status)};
    module_require(X, Request).

module_include(X, Y) :-
    module_include_winner(X, Request, MaxScore);
    bind_module(Y, provide(Request));
    bind_module(Y, status(Status));
    status_score(MaxScore, Status).
)Code";

    fs::create_directories(dirModulesRoot);
    fs::ofstream fileA(dirModulesRoot + "a.xreate");
    fileA << codeA;
    fileA.close();

    fs::ofstream fileB(dirModulesRoot + "b.xreate");
    fileB << codeB;
    fileB.close();

    fs::ofstream fileController(dirModulesRoot + "controller");
    fileController << codeController;
    fileController.close();

    Scanner scanner(reinterpret_cast<const unsigned char*>(codeMain.c_str()), codeMain.size());
    Parser parser(&scanner);
    parser.Parse();

    ModulesRegistry registry;
    ModulesSolver solver(&registry);
    solver.init("", parser.module);
    fs::remove_all(dirModulesRoot);

    cout << solver.__program.str() << endl;
    std::list<std::string> modulesRequired = solver.run(parser.module);

    ASSERT_EQ(1, modulesRequired.size());
    string moduleActualRequired = modulesRequired.front();

    string moduleExpected = dirModulesRoot  + "a.xreate";
    ASSERT_EQ(moduleExpected, moduleActualRequired);
}

TEST(Modules, Compilation1){
    const std::string dirModulesRoot = "/tmp/testModulesDiscovery1_t54726/";

    string codeMain =
R"Code(
module {
    discover("/tmp/testModulesDiscovery1_t54726/")
    include controller("/tmp/testModulesDiscovery1_t54726/controller")

    require (superService)
}

test = function::   int; entry {
    getYourNumber()
}
)Code";

        string codeA =
R"Code(module {
    name(testA).
    provide(superService).
    status(needToTestEvenMore).
}

getYourNumber= function:: int {0}
)Code";

    string codeB =
R"Code(module {
    name(testB).
    provide(superService).
    status(needToTestMore).
}

getYourNumber= function:: int {1}
)Code";

string codeController =
R"Code(
status_score(0, needToTestEvenMore).
status_score(1, needToTestMore).

module_include_candidate(X, Y, Request):-
    module_require(X, Request); bind_module(Y, provide(Request)).

module_include_winner(X, Request, MaxScore) :-
    MaxScore = #max{Score: module_include_candidate(X, Y, Request), bind_module(Y, status(Status)), status_score(Score, Status)};
    module_require(X, Request).

module_include(X, Y) :-
    module_include_winner(X, Request, MaxScore);
    bind_module(Y, provide(Request));
    bind_module(Y, status(Status));
    status_score(MaxScore, Status).
)Code";

    fs::create_directories(dirModulesRoot);
    fs::ofstream fileA(dirModulesRoot + "a.xreate");
    fileA << codeA;
    fileA.close();

    fs::ofstream fileB(dirModulesRoot + "b.xreate");
    fileB << codeB;
    fileB.close();

    fs::ofstream fileController(dirModulesRoot + "controller");
    fileController << codeController;
    fileController.close();

    auto man = new XreateManagerImpl<XreateManagerDecoratorModules<XreateManagerDecoratorFull>>();
    man->prepareCode(std::move(codeMain));
    fs::remove_all(dirModulesRoot);

    int (*funcMain)() = (int (*)()) man->run();
    int result = funcMain();
    ASSERT_EQ(1, result);
}

TEST(Modules, Compilation_AssignModulePath1){
    const std::string dirModulesRoot = "/tmp/testModulesDiscovery1_t54725/";
    string codeMain =
R"Code(
module {
    discover("/tmp/testModulesDiscovery1_t54725/")
    include controller("/tmp/testModulesDiscovery1_t54725/controller")

    require (superService)
}

test = function::   int; entry {
    getYourNumber()
}
)Code";

    string codeA =
R"Code(module {
    name(testA).
    provide(superService).
    status(needToTestEvenMore).
}

getYourNumber= function:: int {0}
)Code";

    string codeController =
R"Code(
module_include(X, "/tmp/testModulesDiscovery1_t54725/a.xreate") :- module_require(X, superService).
)Code";

    fs::create_directories(dirModulesRoot);
    fs::ofstream fileA(dirModulesRoot + "a.xreate");
    fileA << codeA;
    fileA.close();

    fs::ofstream fileController(dirModulesRoot + "controller");
    fileController << codeController;
    fileController.close();

    auto man = new XreateManagerImpl<XreateManagerDecoratorModules<XreateManagerDecoratorFull>>();
    man->prepareCode(std::move(codeMain));
    fs::remove_all(dirModulesRoot);

    int (*funcMain)() = (int (*)()) man->run();
    int result = funcMain();
    ASSERT_EQ(0, result);
}
