/* Any copyright is dedicated to the Public Domain.
 * http://creativecommons.org/publicdomain/zero/1.0/
 *
 * Author: pgess <v.melnychenko@xreate.org>
 * Created on June 25, 2018, 5:42 PM
 *
 * \file    latex.cpp
 * \brief   Testing of latex
 */

#include "xreatemanager.h"
#include "pass/compilepass.h"
#include "transcendlayer.h"
#include "query/latex.h"
#include "compilation/demand.h"
#include "aux/xreatemanager-decorators.h"
#include "compilation/decorators.h"
#include "llvmlayer.h"
#include "supplemental/docutils.h"
#include "supplemental/basics.h"

#include <boost/format.hpp>
#include <gtest/gtest.h>

using namespace xreate::latex;
using namespace xreate::latereasoning;
using namespace xreate::compilation;
using namespace xreate;
using namespace std;

TEST(Latex, Script_NestedScopePropagation_1) {
    std::string program =
    R"CODE(
        import raw("scripts/cfa/context.lp").
        fn = function:: int; entry
        {
            context:: test1.
            if(1==11)::int {2} else {3}
        }
    )CODE";

    std::unique_ptr<details::tier1::XreateManager> man(details::tier1::XreateManager::prepare(move(program)));

    CodeScope* blockTrue = man->root->findFunction("fn")->getEntryScope()->getBody().blocks.front();
    auto blockTrueP = man->transcend->pack(blockTrue);
    boost::format formatAlias("alias(%1%, %2%).");
    man->transcend->addRawScript((formatAlias % blockTrueP % "block1").str());
    man->transcend->addRawScript(
        R"SCRIPT(
        success1:- bind_scope(Block1, test1, strong); alias(Block1, block1).
    )SCRIPT");
    man->analyse();

    ASSERT_EQ(1, man->transcend->query("success1").size());
}

TEST(Latex, Script_DemAndDecision_1) {
    std::string program =
        R"CODE(
        import raw("scripts/cfa/context.lp").

        a = function:: int
        {
            context:: forC(a).
            c()
        }

        b = function:: int
        {
            context:: forC(b).
            c()
        }

        c = function:: int {0}

        main = function:: int; entry
        {
            a() + b()
        }
    )CODE";

    std::unique_ptr<details::tier1::XreateManager> man(details::tier1::XreateManager::prepare(move(program)));

    CodeScope* blockC = man->root->findFunction("c")->getEntryScope();
    auto blockCP = man->transcend->pack(blockC);
    boost::format formatAlias("alias(%1%, %2%).");
    man->transcend->addRawScript((formatAlias % blockCP % "blockC").str());
    man->transcend->addRawScript(
        R"SCRIPT(
        latex_scope_demand(BlockC, forC):- alias(BlockC, blockC).
        latex_registered_subjects(forC, Variant):- bind_scope(_, Variant, strong); Variant = forC(_).
        )SCRIPT");
    man->analyse();

    ASSERT_EQ(1, man->transcend->query("latex_fn_demand").size());
    ASSERT_EQ(2, man->transcend->query("latex_decision").size());
}

TEST(Latex, LatexQuery_getFnDemand_1){
    std::string program =
        R"CODE(
        import raw("scripts/cfa/context.lp").
        main = function:: int; entry
        {
            context:: alias(blockMain).
            0
        }
        )CODE";
    
    std::unique_ptr<details::tier1::XreateManager> man(details::tier1::XreateManager::prepare(move(program)));
    man->transcend->addRawScript(
        R"SCRIPT(
        latex_scope_demand(BlockC, forC):- bind_scope(BlockC, alias(blockMain), strong).
        latex_registered_subjects(forC, decisionSome).
        )SCRIPT");
    
    LatexQuery* query = new LatexQuery();
    man->transcend->registerQuery(query, QueryId::LatexQuery);
    man->analyse();
    
    Demand demand = query->getFnDemand("main");
    ASSERT_EQ(1, demand.size());
    ASSERT_STREQ("forC", demand.front().c_str());
}

TEST(Latex, LatexQuery_getDecision_static_1){
    std::string program =
    R"CODE(
    import raw("scripts/cfa/context.lp").
        a = function:: int {context::decisionSome. c()}
        b = function:: int {c()}
        c = function:: int {context:: alias(blockC). 0}
    )CODE";
    
    std::unique_ptr<details::tier1::XreateManager> man(details::tier1::XreateManager::prepare(move(program)));
    man->transcend->addRawScript(
        R"SCRIPT(
        latex_scope_demand(BlockC, forC):- bind_scope(BlockC, alias(blockC), strong).
        latex_registered_subjects(forC, decisionSome).
        )SCRIPT");
    
    LatexQuery* query = new LatexQuery();
    man->transcend->registerQuery(query, QueryId::LatexQuery);
    man->analyse();
    
    LateAnnotation decisionLA = query->getValue("forC", man->root->findFunction("a")->getEntryScope());
    auto decisionGS = decisionLA.select({}, man->root, man->transcend);
    ASSERT_TRUE(decisionGS);
    
    decisionGS->print(cout);
    cout << endl;
    auto decisionTuple = man->transcend->parse<Gringo::Symbol, Gringo::Symbol, string>(*decisionGS);
    string decision = get<2>(decisionTuple);
    ASSERT_STREQ("decisionSome", decision.c_str());
}
  
TEST(Latex, Compilation_1) {
    std::string program =
        R"CODE(

        a = function:: int
        {0}

        main = function:: int; entry
        {
            a()
        }
        )CODE";

    string script =
        R"SCRIPT(
            latex_fn_demand(%1%, subject1).
            latex_decision(%2%, subject1, 5).
            latex_registered_subjects(subject1, 1).
            latex_registered_subjects(subject1, 5).
        )SCRIPT";

    typedef ExtraArgsBruteFnDecorator<compilation::BasicFunctionUnit> FnImpl;
    typedef LatexBruteScopeDecorator<compilation::CachedScopeDecorator<compilation::BasicCodeScopeUnit>> ScopeImpl;

    std::unique_ptr<details::tier1::XreateManager> man(details::tier1::XreateManager::prepare(move(program)));
    ScopePacked scopeMainP = man->transcend->pack(man->root->findFunction("main")->getEntryScope());
    boost::format format(script);
    man->transcend->addRawScript((format % "a" % scopeMainP).str());
    man->transcend->registerQuery(new LatexQuery(), QueryId::LatexQuery);
    man->analyse();

    std::unique_ptr<CompilePass> compiler(new compilation::CompilePassCustomDecorators<FnImpl, ScopeImpl>(man.get()));
    compiler->run();
    man->llvm->initJit();
    int(*fnMain)() = (int(*)())man->llvm->getFunctionPointer(compiler->getEntryFunction());
    ASSERT_EQ(0, fnMain());
}

//
//TEST(Latex, Full1) {
//    std::string program =
//        R"CODE(
//            import raw("scripts/cfa/context.lp").
//
//            a = function:: int
//            {
//                context:: forC(a).
//                c()
//            }
//
//            b = function:: int
//            {
//                context:: forC(b).
//                c()
//            }
//
//            c = function:: int {0}
//
//            main = function:: int; entry
//            {
//                a() + b()
//            }
//        )CODE";
//
//    string script =
//        R"SCRIPT(
//            alias(%1%, scopeC).
//            latex_scope_demand(ScopeC, forC) :- alias(ScopeC, scopeC).
//            latex_registered_subjects(forC, forC(a)).
//            latex_registered_subjects(forC, forC(b)).
//        )SCRIPT";
//
//    typedef DemandBruteFnDecorator<compilation::BasicFunctionUnit> FnImpl;
//    typedef LatexBruteScopeDecorator<compilation::CachedScopeDecorator<compilation::BasicCodeScopeUnit>> ScopeImpl;
//
//    std::unique_ptr<details::tier1::XreateManager> man(details::tier1::XreateManager::prepare(move(program)));
//    ScopePacked scopeMainP = man->transcend->pack(man->root->findFunction("main")->getEntryScope());
//    auto scopeCP = man->transcend->pack(man->root->findFunction("c")->getEntryScope());
//    boost::format format(script);
//    man->transcend->addRawScript((format %scopeCP).str());
//    man->transcend->registerQuery(new LatexQuery(), QueryId::LatexQuery);
//    man->analyse();
//
//    std::unique_ptr<CompilePass> compiler(new compilation::CompilePassCustomDecorators<FnImpl, ScopeImpl>(man.get()));
//    compiler->run();
//    man->llvm->print();
//}
//
TEST(Latex, Compilation_TransitFn1){
    std::string program =
        R"CODE(
            import raw("scripts/cfa/context.lp").

            branchA = function:: int
            {
                context:: sink_a.
                fnTransit()
            }

            branchB = function:: int
            {
                context:: sink_b.
                fnTransit()
            }

            fnSink = function:: int {0}
            fnTransit = function:: int {fnSink()}

            main = function:: int; entry
            {
                branchA() + branchB()
            }
        )CODE";

    string script =
R"SCRIPT(
    alias(scopeSink, %1%).
    latex_scope_demand(ScopeSink, sink):- alias(scopeSink, ScopeSink).
    latex_registered_subjects(sink, sink_a).
    latex_registered_subjects(sink, sink_b).
)SCRIPT";

    typedef ExtraArgsBruteFnDecorator<compilation::BasicFunctionUnit> FnImpl;
    typedef LatexBruteScopeDecorator<compilation::CachedScopeDecorator<compilation::BasicCodeScopeUnit>> ScopeImpl;

    std::unique_ptr<details::tier1::XreateManager> man(details::tier1::XreateManager::prepare(move(program)));
    CodeScope* scopeSink = man->root->findFunction("fnSink")->getEntryScope();
    auto scopeSinkP = man->transcend->pack(scopeSink);
    ScopedSymbol argLatexSS{1, -1};
    Symbol argLatexS{argLatexSS, scopeSink};
    man->transcend->pack(argLatexS);
    boost::format format(script);
    man->transcend->addRawScript((format %scopeSinkP).str());
    man->transcend->registerQuery(new LatexQuery(), QueryId::LatexQuery);
    man->analyse();

    std::unique_ptr<CompilePass> compiler(new compilation::CompilePassCustomDecorators<FnImpl, ScopeImpl>(man.get()));
    compiler->run();
    man->llvm->print();
    man->llvm->initJit();
    int(*fnMain)() = (int(*)()) man->llvm->getFunctionPointer(compiler->getEntryFunction());
    int valueActual = fnMain();
    ASSERT_EQ(0, valueActual);
}

TEST(Latex, Doc_Examples1){
    std::string program = getDocumentationExampleById("documentation/Concepts/context.xml", "Examples_1");
    std::unique_ptr<details::tier1::XreateManager> man(details::tier1::XreateManager::prepare(move(program)));
}

TEST(Latex, Doc_Examples2){
  std::string program = getDocumentationExampleById("documentation/Concepts/context.xml", "Examples_2");

  std::unique_ptr<details::tier1::XreateManager> man(details::tier1::XreateManager::prepare(move(program)));
}

TEST(Latex, Doc_ContextPropagation1){
  std::string program =getDocumentationExampleById("documentation/Concepts/context.xml", "ContextPropagation1");

  std::unique_ptr<details::tier1::XreateManager> man(details::tier1::XreateManager::prepare(move(program)));
}

TEST(Latex, Doc_ContextPropagation2){
  std::string program = getDocumentationExampleById("documentation/Concepts/context.xml", "ContextPropagation2");

  std::unique_ptr<details::tier1::XreateManager> man(details::tier1::XreateManager::prepare(move(program)));
}

TEST(Latex, Doc_Latex1){
  std::string program = getDocumentationExampleById("documentation/Concepts/context.xml", "Latex1");

      string script =
R"SCRIPT(
  latef("compute").
        
  latex_scope_demand(Scope, f_guarded(F)):- 
    cfa_call(Scope, F);
    latef(F).
        
  latex_registered_subjects(f_guarded(F), Guard):- 
    cfa_function_specializations(F, Guard);
    latef(F).
        
  late(TargetS, LatexParam, Guard, dfa_callguard(TargetS, Guard)):-
    dfa_callfn(TargetS, FnGuarded);
    latef(FnGuarded);
    latex_symbol(FnCallerBody, f_guarded(FnGuarded), LatexParam);
    TargetS=s(_,_, TargetScope);
    scope_fnbody(TargetScope, FnCallerBody);
    cfa_function_specializations(FnGuarded, Guard).        
)SCRIPT";

  auto  man(XreateManager::prepare(move(program)));
  man->transcend->addRawScript(move(script));
  Fn3args fn = (Fn3args) man->run();
  
  int resultActual = fn(1, 4, 3);
  ASSERT_EQ(7, resultActual);
  
  resultActual = fn(0, 4, 3);
  ASSERT_EQ(1, resultActual);
}