/* 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"
#include "supplemental/basics.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(
  global-ann = type predicate {
    entry
  }

  context-ann = type predicate {
    a, b
  }

  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->getFnSpecializations("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, Compile1){
  xreate::XreateManager* man = xreate::XreateManager::prepare(R"CODE(
  global-ann = type predicate {
    entry, guarded(value::int)
  }

  context-ann = type predicate {
    first(value::int), second
  }

  guard(data::first) {
    test = function:: int {data["value"]}
  }

  guard(::second) {
    test = function:: int {1}
  }

  main = function:: int; entry()
  {
    x = 8:: int.
    test()::int; guarded(x)
  }
  )CODE");

  man->transcend->addRawScript(R"(func_supply_guard(Site, first(Data), "context-ann"):- bind(Site, guarded(Data)).)");
  man->run();
}

TEST(Polymorphs, Compile2){
  xreate::XreateManager* man = xreate::XreateManager::prepare(R"CODE(
  guard-pred = type predicate {
    first(value::int), second
  }

  global-ann = type predicate {
    entry, guarded(guard-pred)
  }

  guard(data ::first) {
    test = function:: int {data["value"]}
  }

  guard(::second) {
    test = function:: int {1}
  }

  main = function:: int; entry()
  {
    x = first(8):: guard-pred.
    test()::int; guarded(x)
  }
  )CODE");

  man->transcend->addRawScript(R"(func_supply_guard(Site, Guard, "guard-pred"):- bind(Site, guarded(Guard)).)");
  man->run();
}

TEST(Polymorphs, CompileException1){

  xreate::XreateManager* man = xreate::XreateManager::prepare(R"CODE(
  Global-ann = type predicate {
    entry
  }

  HandlerDivByZero = type {
    ret:: int
  }

  Guards = type predicate {
    fast,
    safe(handlerZeroDiv:: HandlerDivByZero)
  }

  guard(:: fast) {
    div = function(a::int, b::int) { a / b}
  }

  guard(handlers:: safe) {
    div = function(a::int, b::int)::int
    {
      if (b != 0):: int
        {a / b} else { handlers["handlerZeroDiv", "ret"] }
    }
  }

  main = function:: int; entry()
  {
    handler = { ret = 11 } ::HandlerDivByZero.
    div(8, 0):: int; safe(handler)
  }
)CODE");

  man->transcend->addRawScript(R"(func_supply_guard(Site, Guard, "Guards"):- bind(Site, Guard).)");
  FnNoArgs fn = (FnNoArgs) man->run();
  int result = fn();
  ASSERT_EQ(11, result);
}

//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->findSymbolByAlias("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);
//}