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

#include "xreatemanager.h"
#include "ast.h"
#include "transcendlayer.h"
#include "aux/latereasoning.h"
#include <list>
#include "gtest/gtest.h"
#include "query/polymorph.h"
#include "supplemental/docutils.h"

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

TEST(Polymorphs, ast1) {
    xreate::XreateManager* man = xreate::XreateManager::prepare(
        R"CODE(
    guard:: a {
        test = function:: int {0}
    }

    guard:: b {
        test = function:: int {1}
    }

    main = function:: int; entry { test() }

)CODE");

    const std::list<ManagedFnPtr>& specs = man->root->getFunctionSpecializations("test");
    ASSERT_EQ(2, specs.size());

    auto itSpecs = specs.begin();
    ASSERT_EQ("a", (*itSpecs)->guard.getValueString());
    itSpecs++;
    ASSERT_EQ("b", (*itSpecs)->guard.getValueString());
}

TEST(Polymorphs, PolymorphQuery_Static_1) {
    xreate::details::tier1::XreateManager* man = xreate::details::tier1::XreateManager::prepare(
        R"CODE(
    import raw("scripts/dfa/polymorphism.lp").

    guard:: a {
        test = function:: int {0}
    }

    guard:: b {
        test = function:: int {1}
    }

    main = function:: int; entry { test()::int; callguard(b); dfa_polym(ret)}

)CODE");

    man->analyse();
    PolymorphQuery* query = dynamic_cast<PolymorphQuery*> (man->transcend->getQuery(QueryId::PolymorphQuery));
    
    const Expression& bodyE = man->root->findFunction("main")->getEntryScope()->getBody();
    LateAnnotation decisionLA = query->get(bodyE);
    ASSERT_EQ(1, decisionLA.guardedContent.size());
    
    auto decisionOptSymb = decisionLA.select({}, man->root, man->transcend);
    ASSERT_TRUE(decisionOptSymb);
    
    decisionOptSymb->print(cout);
    cout << endl;
    string guard = query->getValue(*decisionOptSymb).getValueString();
    ASSERT_STREQ("b", guard.c_str());
}

TEST(Polymorphs, PolymorphQuery_Late_1){
    xreate::details::tier1::XreateManager* man = xreate::details::tier1::XreateManager::prepare(
R"CODE(
    Late = type variant{a, b}.
    main = function:: int; entry{key= a():: Late; test. key::int}
)CODE");

    man->transcend->addRawScript(
R"RULE(
    late(S, S, a, dfa_callguard(S, a)):-
        bind(S, test).
    
    late(S, S, b, dfa_callguard(S, b)):-
        bind(S, test).
)RULE");
    man->analyse();
    PolymorphQuery* query = dynamic_cast<PolymorphQuery*> (man->transcend->getQuery(QueryId::PolymorphQuery));

    CodeScope* scopeMain = man->root->findFunction("main")->getEntryScope();
    Symbol keyS = Symbol{scopeMain->getSymbol("key"), scopeMain};

    Expression keyE = scopeMain->getDefinition(keyS);
    latereasoning::LateAnnotation answerLA = query->get(keyE);
    Expression valueB(Operator::VARIANT, {}); valueB.setValueDouble(1);
    auto answerRaw = answerLA.select({valueB}, man->root, man->transcend);
    ASSERT_TRUE(answerRaw);
    Expression answerE = query->getValue(*answerRaw);
    ASSERT_STREQ("b", answerE.getValueString().c_str());
}

TEST(Polymorphs, PSCU_1){
    auto man = details::tier1::XreateManager::prepare(R"Code(
        Dom = type variant {guard1, guard2}.

        guard:: guard1 {
        compute = function  ::              int
            {0}
        }

        guard:: guard2 {
        compute = function  ::              int
            {1}
        }

        test = function::   int; entry
        {
            xLate = guard2()::              Dom.
            y1= switch late (xLate:: Dom; alias(xLate))::  int
                {
                    compute()::             int; guardkey(xLate)
                }.
            y1
        }
    )Code");

    man->transcend->addRawScript(R"RAW(
        dom(guard1; guard2).
        late(Target, Key, Variant, dfa_callguard(Target, Variant)):-
            bind(Target, guardkey(Alias));
            bind(Key, alias(Alias));
            dom(Variant).
    )RAW");
    man->analyse();
    int (*program)() = (int (*)())man->run();
    int result = program();

    ASSERT_EQ(1, result);
}

TEST(Polymorphs, Doc_FnLvlPoly_1){
  string example = getDocumentationExampleById("documentation/Concepts/polymorphism.xml", "FnLvlPoly_1");
  auto man = XreateManager::prepare(move(example));
  ASSERT_TRUE(true);
}

TEST(Polymorphs, Doc_LatePoly_1){
  string example = getDocumentationExampleById("documentation/Concepts/polymorphism.xml", "LatePoly_1");
  auto man = XreateManager::prepare(move(example));
  ASSERT_TRUE(true);
}