/* Any copyright is dedicated to the Public Domain.
 * http://creativecommons.org/publicdomain/zero/1.0/
 *
 * latereasoning.cpp
 *
 * Author: pgess <v.melnychenko@xreate.org>
 * Created on April 21, 2018, 5:10 PM
 */

#include "xreatemanager.h"
#include "transcendlayer.h"
#include "pass/latereasoningpass.h"
#include "aux/latereasoning.h"
#include "pass/dfapass.h"
#include <boost/format.hpp>
#include "gtest/gtest.h"

using namespace std;
using namespace xreate;
using namespace xreate::latereasoning;

TEST(LateReasoning, Syntax1) {
    XreateManager* man = XreateManager::prepare(R"Code(
test = function:: int {
            x = 0::int.
            y1= switch late (x)::int{0}.
            y2= switch late(x+y1->a::int)::int{1}.
            y1+y2
}
)Code");

    CodeScope* scope = man->root->findFunction("test")->getEntryScope();
    Expression y1 = scope->getDefinition(scope->getSymbol("y1"));
    Expression y2 = scope->getDefinition(scope->getSymbol("y2"));

    ASSERT_EQ(1, y1.bindings.size());
    ASSERT_STRCASEEQ("x", y1.bindings.at(0).c_str());

    ASSERT_EQ(1, y2.bindings.size());
    ASSERT_STRCASEEQ("a", y2.bindings.at(0).c_str());
}

TEST(LateReasoning, Pass_DFAPassDec_1){
    typedef LateReasoningTranscendDecorator<TranscendLayer> LRTranscend;

    auto man = details::tier2::XreateManager::prepare(R"Code(
Dom = type slave dom.

test = function::   int; entry
{
    LateIdent = 0:: Dom.
    0::             int; ann1(LateIdent)
}
)Code");

    CodeScope* scopeEntry = man->root->findFunction("test")->getEntryScope();
    ScopedSymbol keyS = scopeEntry->getSymbol("LateIdent");
    SymbolPacked keySP = man->transcend->pack(Symbol{keyS, scopeEntry});

    std::shared_ptr<LateReasoningScope> scopeLateEntry(new LateReasoningScope(nullptr));
    scopeLateEntry->addIdentifier("LateIdent", keySP);

    typedef LateReasoningDFAPassDecorator<dfa::DFAPass> LRDFAPass;
    LRDFAPass* dfaPass = new LRDFAPass(man);
    dfaPass->registerLateScope(scopeEntry, scopeLateEntry.get());

    man->transcend->addRawScript("dom(guard1; guard2).\n");
    
    man->registerPass(dfaPass, PassId::DFAPass, nullptr);
    man->executePasses();

    testing::internal::CaptureStdout();
    man->analyse();
    std::string outputActual = testing::internal::GetCapturedStdout();
    cout << outputActual << endl;
    
    string outputExpected = "late(s(0,-2,0), (s(1,-2,0)), (LateIdent), ann1(LateIdent)):- dom(LateIdent).";
    ASSERT_NE(std::string::npos, outputActual.find(outputExpected));
}

TEST(LateReasoning, Transcend_LRTransDec_1) {
    Attachments::init<versions::VariableVersion>();
    typedef LateReasoningTranscendDecorator<TranscendLayer> LRTranscend;
    std::unique_ptr<LRTranscend> transcend(new LRTranscend());
    
    std::unique_ptr<CodeScope> scope(new CodeScope(nullptr));
    Symbol symbA = scope->addDefinition(Atom<Identifier_t>("a"), Expression());
    Symbol symbB = scope->addDefinition(Atom<Identifier_t>("b"), Expression());
    Symbol symbC = scope->addDefinition(Atom<Identifier_t>("c"), Expression());
    Symbol symbTarget1 = scope->addDefinition(Atom<Identifier_t>("target1"), Expression());
    Symbol symbTarget2 = scope->addDefinition(Atom<Identifier_t>("target2"), Expression());

    SymbolPacked symbpA = transcend->pack(symbA, "a");
    SymbolPacked symbpB = transcend->pack(symbB, "b");
    SymbolPacked symbpC = transcend->pack(symbC, "c");
    SymbolPacked symbTarget1P = transcend->pack(symbTarget1, "target1");
    SymbolPacked symbTarget2P = transcend->pack(symbTarget2, "target2");

    boost::format formatSymb("s(%1%,%2%,%3%)");
    boost::format formatLateAnnotation1("late(%1%, (%2%, %3%, %4%), (%5%, %6%, %7%), %8%).");
    boost::format formatLateAnnotation2("late(%1%, (%2%, %3%), (%4%, %5%), %6%).");
    #define FORMATSYMBOL(s) (formatSymb % s.identifier % s.version  % s.scope).str()

        // Ann1, `variant1`
    transcend->addRawScript((formatLateAnnotation1
        % FORMATSYMBOL(symbTarget1P)
        % FORMATSYMBOL(symbpA) % FORMATSYMBOL(symbpB) % FORMATSYMBOL(symbpC)
        % "guard1" % "guard1" % "guard1"
        % "result(variant1)"
    ). str());

    //Ann1 `result2` variant
    transcend->addRawScript((formatLateAnnotation1
        % FORMATSYMBOL(symbTarget1P)
        % FORMATSYMBOL(symbpA) % FORMATSYMBOL(symbpB) % FORMATSYMBOL(symbpC)
        % "guard2" % "guard2" % "guard2"
        % "result(variant2)"
    ). str());
    
    //Ann2 `result3` variant
    transcend->addRawScript((formatLateAnnotation2
        % FORMATSYMBOL(symbTarget2P)
        % FORMATSYMBOL(symbpA) % FORMATSYMBOL(symbpB)
        % "guard3" % "guard3"
        % "result(variant3)"
    ). str());
    
    transcend->run();
    LateAnnotationsGroup group = transcend->queryLate("result");
    ASSERT_EQ(2, group.annotations.size());
    
    for(const auto& annEntry: group.annotations){
        annEntry.first.print(cout);
        cout <<endl;
        
        Gringo::SymSpan symbsTuple;
        symbsTuple.first = &annEntry.first;
        symbsTuple.size = 1;
        Gringo::Symbol targetWrapped = Gringo::Symbol::createTuple(symbsTuple);
        SymbolPacked targetSP = get<0>(transcend->parse<SymbolPacked>(targetWrapped));
        if (targetSP == symbTarget1P){
            LateAnnotation ann = annEntry.second;
            ASSERT_EQ(2, ann.guardedContent.size());
            
            Expression selector(Operator::CALL, {Atom<Identifier_t>("guard2")});
            auto answer = ann.select({selector, selector, selector}man->root, man->transcend);
            ASSERT_TRUE(answer);
            string answerS = get<0>(transcend->parse<string>(*answer));
            ASSERT_STREQ("variant2", answerS.c_str());
            
        } else if (targetSP == symbTarget2P) {
            LateAnnotation ann = annEntry.second;
            ASSERT_EQ(1, ann.guardedContent.size());
            
            Expression selector(Operator::CALL, {Atom<Identifier_t>("guard3")});
            auto answer = ann.select({selector, selector, selector});
            ASSERT_TRUE(answer);
            ASSERT_STREQ("variant3", get<0>(transcend->parse<string>(*answer)).c_str());
            
        } else {
            ASSERT_TRUE(false);
        }
    }
}

TEST(LateReasoning, Compilation1){
    XreateManager* man = XreateManager::prepare(R"Code(
Color = type variant{RED, BLUE, GREEN}.

test = function:: int; entry {
    x = RED()::             Color.
    y1= switch late (x)::   int
        {0}.
    y1
}
)Code");

    int (*program)() = (int (*)())man->run();
    int result = program();

    ASSERT_EQ(0, result);
}


