/* Any copyright is dedicated to the Public Domain.
 * http://creativecommons.org/publicdomain/zero/1.0/
 *
 * interpretation.cpp
 *
 *  Created on: -
 *      Author: pgess <v.melnychenko@xreate.org>
 */

#include "attachments.h"

using namespace xreate;

#include "xreatemanager.h"
#include "compilation/targetinterpretation.h"
#include "supplemental/docutils.h"

#include "gtest/gtest.h"
#include "boost/scoped_ptr.hpp"

//#define FRIENDS_INTERPRETATION_TESTS               \
//    friend class ::Modules_AST2_Test;       \
//    friend class ::Modules_Discovery1_Test; \
//    friend class ::Modules_Solve1_Test;

#include "pass/interpretationpass.h"

using namespace xreate::grammar::main;
using namespace xreate::interpretation;
using namespace std;

TEST(Interpretation, Analysis_StatementIF_1) {
  XreateManager* man = XreateManager::prepare(

    R"Code(
    main = function::bool {
        x = "a":: string.

        y = if (x=="b"):: bool; i12n(on()) {
            true

        } else {
            false
        }.

        y
    }
)Code");

  InterpretationPass* pass = new InterpretationPass(man);
  pass->run();

  CodeScope* scopeEntry = man->root->findFunction("main")->getEntryScope();
  Symbol symbolY{scopeEntry->getSymbol("y"), scopeEntry};
  InterpretationData &dataSymbolY = Attachments::get<InterpretationData>(symbolY);

  ASSERT_EQ(INTR_ONLY, dataSymbolY.resolution);
}

TEST(Interpretation, Analysis_StatementIF_InterpretCondition_1) {
  XreateManager* man = XreateManager::prepare(

    R"Code(
    main = function(x:: int):: int {
        comm= "inc":: string; i12n(on()).

        y = if (comm == "inc")::int {x+1} else {x}.
        y
    }
)Code");

  InterpretationPass* pass = new InterpretationPass(man);
  pass->run();

  CodeScope* scopeEntry = man->root->findFunction("main")->getEntryScope();
  Symbol symbolY{scopeEntry->getSymbol("y"), scopeEntry};
  InterpretationData &dataSymbolY = Attachments::get<InterpretationData>(symbolY);

  ASSERT_EQ(CMPL_ONLY, dataSymbolY.resolution);
  ASSERT_EQ(IF_INTERPRET_CONDITION, dataSymbolY.op);
}

TEST(Interpretation, StatementCall_RecursionIndirect_1) {
  XreateManager* man = XreateManager::prepare(
    R"Code(
    searchNemo = function(data:: Data):: bool
    {
        if (data == "nemo")::            bool
            {false} else {searchDory(data)}
    }

    searchDory = function(data:: Data):: bool
    {
        if (data == "dory")::            bool
            {true} else {searchNemo(data)}
    }

    entry = function::                   bool; entry() {
        searchNemo("")::                 bool; i12n(on())
    }
)Code");

  InterpretationPass* pass = new InterpretationPass(man);
  ASSERT_DEATH(pass->run(), "Indirect recursion detected");
}

TEST(Interpretation, PartialIntr_1) {
  XreateManager* man = XreateManager::prepare(
    R"Code(
    evaluate= function(argument:: int, code:: string; i12n(on()))::  int {
        switch(code)::                                             int
        case ("inc")    {argument + 1}
        case ("dec")    {argument - 1}
        case ("double") {argument * 2}
        case default {argument}
    }

    main = function:: int; entry() {
        commands= {"inc", "double", "dec"}::              [string]; i12n(on()).

        loop fold(commands->comm::string, 10->operand)::  int
        {
            evaluate(operand, comm)
        }
    }
)Code");

  InterpretationPass* pass = new InterpretationPass(man);
  pass->run();

  ManagedFnPtr fnEvaluate = man->root->findFunction("evaluate");
  InterpretationResolution resFnEvaluate = pass->process(fnEvaluate);
  ASSERT_EQ(CMPL_ONLY, resFnEvaluate);
  ASSERT_TRUE(FunctionInterpretationHelper::needPartialInterpretation(fnEvaluate));

  const Expression &exprLoop = man->root->findFunction("main")->__entry->getBody();
  Symbol symbCallEv{{0, versions::VERSION_NONE}, exprLoop.blocks.front()};
  InterpretationData dataCallEv = Attachments::get<InterpretationData>(symbCallEv);
  ASSERT_EQ(CMPL_ONLY, dataCallEv.resolution);
  ASSERT_EQ(CALL_INTERPRET_PARTIAL, dataCallEv.op);
}

TEST(Interpretation, PartialIntr_3) {
  xreate::details::tier1::XreateManager* man = xreate::details::tier1::XreateManager::prepare(
    R"Code(
    Command= type variant {INC, DEC, DOUBLE}.

    evaluate= function(argument:: int, code:: Command; i12n(on())):: int {
        switch variant(code)::int
        case (INC)    {argument + 1}
        case (DEC)    {argument - 1}
        case (DOUBLE) {argument * 2}
    }

    main = function::int; entry() {
        commands= {INC(), DOUBLE(), DEC()}:: [Command]; i12n(on()).

        loop fold(commands->comm::Command, 10->operand):: int{
            evaluate(operand, comm)
        }
    }
)Code");

  man->analyse();
  if(!man->isPassRegistered(PassId::InterpretationPass)) {
    InterpretationPass* pass = new InterpretationPass(man);
    pass->run();
  }

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

  ASSERT_EQ(21, result);
}

TEST(Interpretation, PartialIntr_4) {
  xreate::details::tier1::XreateManager* man = xreate::details::tier1::XreateManager::prepare(
    R"Code(
    Command= type variant {INC, DEC, DOUBLE}.

    evaluate= function(argument:: int, code:: Command; i12n(on())):: int {
        switch variant(code)::int
            case (INC)    {argument + 1}
            case (DEC)    {argument - 1}
            case (DOUBLE) {argument * 2}
    }

    main = function::int; entry() {
        evaluate(4, DEC())
    }
)Code");

  man->analyse();
  if(!man->isPassRegistered(PassId::InterpretationPass)) {
    InterpretationPass* pass = new InterpretationPass(man);
    pass->run();
  }

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

  ASSERT_EQ(3, result);
}

TEST(Interpretation, SwitchVariant) {
  xreate::XreateManager* man = xreate::XreateManager::prepare(
    R"Code(
Command= type variant {
        ADD(x::int, y::int),
        DEC(x::int),
        DOUBLE(x::int)
}.

main = function::int; entry(){
    program = ADD(2, 3)::Command; i12n(on()).

    switch variant(program)::int
        case (ADD) {program["x"]+program["y"]}
        case (DEC) {1}
        case (DOUBLE) {2}
}
)Code");

  int (* main)() = (int (*)()) man->run();
  int result = main();
  ASSERT_EQ(5, result);

}

TEST(Interpretation, SwitchVariantAlias) {
  xreate::XreateManager* man = xreate::XreateManager::prepare(
    R"Code(
Command= type variant {
  ADD(x::int, y::int),
  DEC(x::int),
  DOUBLE(x::int)
}.

main = function::int; entry(){
  program = {ADD(2, 3), DEC(8)}::[Command]; i12n(on()).

  switch variant(program[0]->program::Command)::int
    case (ADD) {program["x"]+program["y"]}
    case (DEC) {1}
    case (DOUBLE) {2}
}
)Code");

  int (* main)() = (int (*)()) man->run();
  int result = main();
  ASSERT_EQ(5, result);
}

TEST(Interpretation, Multiindex1) {
  std::string script2 =
    R"Code(
        extract = function(program::unknType)::int; i12n(on()){
            program["arguments"][1]
        }

        main = function::int; entry() {
            x = {arguments = {10, 9, 8, 7}}:: unknType; i12n(on()).
            extract(x)
        }
)Code";

  std::unique_ptr<XreateManager> man(XreateManager::prepare(std::move(script2)));

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

  ASSERT_EQ(9, result);
}

TEST(InterpretationExamples, Regexp1) {
  FILE* input = fopen("scripts/dsl/regexp.xreate", "r");
  assert(input != nullptr);

  std::unique_ptr<XreateManager> man(XreateManager::prepare(input));

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

  ASSERT_EQ(4, result);
}

TEST(Interpretation, Variant1) {
  std::string script =
    R"Code(
DataPacked = type variant {
  Var1(Num:: [int], String::string),
  Var2(Num:: [int], String::string)
}.

extractInt = function(data::DataPacked):: int {
  resultWrong = 0 :: int.

  switch variant(data)::int
      case (Var1) {data["Num", 0]}
      case (Var2) {resultWrong}
}

main = function :: int; entry() {
  dataActual = {10}:: [int].
  dataPacked = Var1(dataActual, "whatever"):: DataPacked.

  extractInt(dataPacked):: int; i12n(on())
}
)Code";

  std::unique_ptr<XreateManager> man(XreateManager::prepare(std::move(script)));

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

  ASSERT_EQ(10, result);
}

TEST(Interpretation, Doc_Intr_1) {
  string example = getDocumentationExampleById("documentation/Concepts/interpretation.xml", "Intr_1");
  xreate::details::tier1::XreateManager* man = xreate::details::tier1::XreateManager::prepare(move(example));

  man->analyse();

  InterpretationPass* pass;
  if(man->isPassRegistered(PassId::InterpretationPass)) {
    pass = (InterpretationPass*) man->getPassById(PassId::InterpretationPass);
  } else {
    pass = new InterpretationPass(man);
    pass->run();
  }

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

  ASSERT_EQ(0, result);
}

TEST(Interpretation, Doc_FnIntr_1) {
  string example1 = getDocumentationExampleById("documentation/Concepts/interpretation.xml", "FnIntr_1");
  string example2 = getDocumentationExampleById("documentation/Concepts/interpretation.xml", "Alt_FnIntr_1");
  xreate::details::tier1::XreateManager* man = xreate::details::tier1::XreateManager::prepare(move(example1));

  man->analyse();

  InterpretationPass* pass;
  if(man->isPassRegistered(PassId::InterpretationPass)) {
    pass = (InterpretationPass*) man->getPassById(PassId::InterpretationPass);
  } else {
    pass = new InterpretationPass(man);
    pass->run();
  }

  unsigned char (* main)() = (unsigned char (*)()) man->run();
  unsigned char result = main();

  ASSERT_EQ(1, result);

  XreateManager* man2 = XreateManager::prepare(move(example2));
  ASSERT_TRUE(true);
}

TEST(Interpretation, Doc_FnIntr_2) {
  string example = getDocumentationExampleById("documentation/Concepts/interpretation.xml", "FnIntr_2");

  xreate::details::tier1::XreateManager* man = xreate::details::tier1::XreateManager::prepare(move(example));

  man->analyse();

  InterpretationPass* pass;
  if(man->isPassRegistered(PassId::InterpretationPass)) {
    pass = (InterpretationPass*) man->getPassById(PassId::InterpretationPass);
  } else {
    pass = new InterpretationPass(man);
    pass->run();
  }

  unsigned char (* main)() = (unsigned char (*)()) man->run();
  unsigned char result = main();

  ASSERT_EQ(1, result);
}

TEST(Interpretation, Doc_FnIntr_3) {
  string example = getDocumentationExampleById("documentation/Concepts/interpretation.xml", "FnIntr_3");
  xreate::details::tier1::XreateManager* man = xreate::details::tier1::XreateManager::prepare(move(example));

  man->analyse();

  InterpretationPass* pass;
  if(man->isPassRegistered(PassId::InterpretationPass)) {
    pass = (InterpretationPass*) man->getPassById(PassId::InterpretationPass);
  } else {
    pass = new InterpretationPass(man);
    pass->run();
  }

  InterpretationResolution resolutionActual = pass->process(man->root->findFunction("unwrap"));
  ASSERT_EQ(ANY, resolutionActual);

  unsigned char (* main)() = (unsigned char (*)()) man->run();
  unsigned char result = main();

  ASSERT_NE(0, result);
}

TEST(Interpretation, Doc_LateIntr_1) {
  string example = getDocumentationExampleById("documentation/Concepts/interpretation.xml", "LateIntr_1");
  xreate::details::tier1::XreateManager* man = xreate::details::tier1::XreateManager::prepare(move(example));

  man->analyse();

  InterpretationPass* pass;
  if(man->isPassRegistered(PassId::InterpretationPass)) {
    pass = (InterpretationPass*) man->getPassById(PassId::InterpretationPass);
  } else {
    pass = new InterpretationPass(man);
    pass->run();
  }

  int (* main)(int) = (int (*)(int)) man->run();
  int result = main(1);

  ASSERT_EQ(2, result);
}

TEST(Interpretation, Doc_LateIntr_2) {
  string example = getDocumentationExampleById("documentation/Concepts/interpretation.xml", "LateIntr_2");
  string example2 = getDocumentationExampleById("documentation/Concepts/interpretation.xml", "Alt_LateIntr_2");
  xreate::details::tier1::XreateManager* man = xreate::details::tier1::XreateManager::prepare(move(example));

  man->analyse();

  InterpretationPass* pass;
  if(man->isPassRegistered(PassId::InterpretationPass)) {
    pass = (InterpretationPass*) man->getPassById(PassId::InterpretationPass);
  } else {
    pass = new InterpretationPass(man);
    pass->run();
  }

  const ManagedFnPtr &funcMain = man->root->findFunction("main");
  InterpretationData &dataBody = Attachments::get<InterpretationData>(funcMain);
  ASSERT_EQ(FOLD_INTERPRET_INPUT, dataBody.op);

  int (* main)(int) = (int (*)(int)) man->run();
  int result = main(10);

  ASSERT_EQ(21, result);

  XreateManager* man2 = XreateManager::prepare(move(example2));
  ASSERT_TRUE(true);
}

TEST(Interpretation, Doc_LateFnIntr_1) {
  string example = getDocumentationExampleById("documentation/Concepts/interpretation.xml", "LateFnIntr_1");
  string example2 = getDocumentationExampleById("documentation/Concepts/interpretation.xml", "Alt_LateFnIntr_1");
  xreate::details::tier1::XreateManager* man = xreate::details::tier1::XreateManager::prepare(move(example));

  man->analyse();
  if(!man->isPassRegistered(PassId::InterpretationPass)) {
    InterpretationPass* pass = new InterpretationPass(man);
    pass->run();
  }

  int (* main)(int) = (int (*)(int)) man->run();
  int result = main(10);

  ASSERT_EQ(21, result);

  XreateManager* man2 = XreateManager::prepare(move(example2));
  ASSERT_TRUE(true);
}

//TOTEST call indirect recursion(w/o tags)
//TASk implement and test Loop Inf (fix acc types in coco grammar)
