/* 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 "supplemental/docutils.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)).
        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.__requests.size());
    ASSERT_EQ("provides", module.__requests.front().getValueString());

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

    ASSERT_EQ(1, module.__discoveryPaths.size());
    ASSERT_EQ("/tmp/root/", module.__discoveryPaths.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();

    ModulesSolver solver;
    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(\"/tmp/testModulesDiscovery1_t54723/a.xreate\", name(testA))."));
    ASSERT_NE(string::npos, output.find("bind_module(\"/tmp/testModulesDiscovery1_t54723/b.xreate\", status(needToTestEvenMore))."));
}

TEST(Modules, Requests1){

}

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

    string codeMain =
R"Code(
module {
    discover("/tmp/testModulesDiscovery1_t54726/").
    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(Request, Y):-
    bind_module(Y, provide(Request)).

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

modules_resolution(Request, Y) :-
    module_include_winner(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/").
    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(
  modules_resolution(superService, "/tmp/testModulesDiscovery1_t54725/a.xreate").
)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);
}

TEST(Modules, Doc_Requesting_Modules_1){
  string codeExcerpt = getDocumentationExampleById("documentation/Syntax/modules.xml", "Requesting_Modules_1");

  string codeController = R"Code(
      modules_resolution(logger, "/tmp/logger").
  )Code";

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

  ModulesSolver solver;
  solver.init(move(codeController), parser.module);
  std::list<string> result = solver.run(parser.module);
  ASSERT_EQ(1, result.size());
  ASSERT_STREQ("/tmp/logger", result.front().c_str());
}

TEST(Modules, Doc_Requesting_Modules_2){
  string codeExcerpt = getDocumentationExampleById("documentation/Syntax/modules.xml", "Requesting_Modules_2");

  string codeController = R"Code(
      modules_resolution(stringslib, "/tmp/stringslib").
      modules_resolution(mathlib, "/tmp/mathlib").
  )Code";

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

  ModulesSolver solver;
  solver.init(move(codeController), parser.module);
  std::list<string> result = solver.run(parser.module);
  ASSERT_EQ(2, result.size());
  ASSERT_STREQ("/tmp/stringslib", result.front().c_str());
  ASSERT_STREQ("/tmp/mathlib", result.back().c_str());
}

TEST(Modules, Doc_ModuleAnnotations_1){
  string codeExcerpt = getDocumentationExampleById("documentation/Syntax/modules.xml", "ModuleAnnotations_1");

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

  ModulesSolver solver;
  solver.init("", parser.module);

  string program = solver.__program.str();
  ASSERT_NE(string::npos, program.find("bind_module(\"\", status(obsolete))."));
}

TEST(Modules, Doc_ModulesResolution_1){
    string codeExcerpt = getDocumentationExampleById("documentation/Syntax/modules.xml", "ModulesResolution_1");

    string codeModule = R"Code(
         module {require(numlib). require(strings)}
    )Code";

    //use ModuleA as a current module
    codeExcerpt.replace(codeExcerpt.find("moduleA"), 7, "");
    
    Scanner scanner(reinterpret_cast<const unsigned char*>(codeModule.c_str()), codeModule.size());
    Parser parser(&scanner);
    parser.Parse();

    ModulesSolver solver;
    solver.init(codeExcerpt, parser.module);
    std::list<string> result = solver.run(parser.module);
    ASSERT_EQ(2, result.size());
    ASSERT_STREQ("/path/to/numlib", result.front().c_str());
    ASSERT_STREQ("/path/to/ansi-lib", result.back().c_str());
}

TEST(Modules, Doc_AdvModRes_1){
  string code = getDocumentationExampleById("documentation/Syntax/modules.xml", "AdvModRes_1");
  size_t posCodeA = code.find("//First Module");
  size_t posCodeB = code.find("//Second Module");
  size_t posCodeMain = code.find("//Third Module");
  string codeA = code.substr(posCodeA, posCodeB - posCodeA);
  string codeB = code.substr(posCodeB, posCodeMain - posCodeB);
  string codeMain = code.substr(posCodeMain);
          
  const std::string dirModulesRoot = "/tmp/testModulesDiscovery1_t54724/";
  replace(codeMain, string("/modules/path/"), dirModulesRoot);
  replace(codeMain, string("/path/to/controller"), dirModulesRoot + "controller");
  cout << codeMain << endl;

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

module_include_candidate(Request, Y):-
  bind_module(Y, provide(Request)).

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

modules_resolution(Request, Y) :-
  module_include_winner(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();

  ModulesSolver solver;
  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  + "b.xreate";
  ASSERT_EQ(moduleExpected, moduleActualRequired);
}

TEST(Modules_API, Doc_ModAnn_1){
  string program = getDocumentationExampleById("documentation/Transcend/modules-api.xml", "ModAnn_1");
  string outputExpected = getDocumentationExampleById("documentation/Transcend/modules-api.xml", "Output_ModAnn_1");
  replace(outputExpected, "/path/to/module", "");
  
  auto xreate = new xreate::details::tier1::XreateManagerImpl<XreateManagerDecoratorModules<XreateManagerDecoratorFull>>();
  testing::internal::CaptureStdout();
  
  xreate->prepareCode(std::move(program));
  xreate->analyse();
  
  std::string outputActual = testing::internal::GetCapturedStdout();
  ASSERT_NE(std::string::npos, outputActual.find(outputExpected));
}

TEST(Modules_API, Doc_ReqMod_1){
  string program = getDocumentationExampleById("documentation/Transcend/modules-api.xml", "ReqMod_1");
  string outputExpected = getDocumentationExampleById("documentation/Transcend/modules-api.xml", "Output_ReqMod_1");
  replace(outputExpected, "/path/to/module", "");
  
  Scanner scanner(reinterpret_cast<const unsigned char*>(program.c_str()), program.size());
  Parser parser(&scanner);
  parser.Parse();

  ModulesSolver solver;
  testing::internal::CaptureStdout();
  
  solver.init("", parser.module);
  
  std::string outputActual = testing::internal::GetCapturedStdout();
  ASSERT_NE(std::string::npos, outputActual.find(outputExpected));
}