/* 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 "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;

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

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

        y = if (x=="b"):: bool; interpretation(force) {
            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, Compilation_StatementIF_1){
    xreate::details::tier1::XreateManager* man = xreate::details::tier1::XreateManager::prepare(

R"Code(
    main = function::int; entry {
        x = "a":: string.

        y = if (x=="b"):: string; interpretation(force) {
            1

        } else {
            0
        }.

        y
    }
)Code"          );

    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, Analysis_StatementIF_InterpretCondition_1){
    XreateManager* man = XreateManager::prepare(

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

        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, Compilation_StatementIF_InterpretCondition_1){
    xreate::details::tier1::XreateManager* man = xreate::details::tier1::XreateManager::prepare(

R"Code(
    main = function(x:: int):: int; entry {
        comm= "inc":: string; interpretation(force).

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

    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, Compilation_StatementFOLD_INTERPRET_INPUT_1){
    xreate::details::tier1::XreateManager* man = xreate::details::tier1::XreateManager::prepare(

R"Code(
    main = function(x:: int):: int; entry {
        commands = ["inc", "double", "dec"]:: [string]; interpretation(force).

        loop fold(commands->comm::string, x->operand):: int{
            switch(comm)::int

            case ("inc"){
                operand + 1
            }

            case ("dec"){
                operand - 1
            }

            case ("double"){
                operand * 2
            }
        }
    }
)Code"          );

    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);
}

TEST(Interpretation, StatementCall_RecursionNo_1){
    xreate::details::tier1::XreateManager* man = xreate::details::tier1::XreateManager::prepare(
R"Code(
    unwrap = function(data::unknType, keys::unknType):: unknType; interpretation(force){
        loop fold(keys->key::string, data->a):: unknType {
           a[key]
        }
    }

    start = function::num; entry{
        result = unwrap(
            {
                a = {
                        b =
                        {
                            c = "core"
                        }
                    }
            }, ["a", "b", "c"])::unknType.

        result == "core"
    }
)Code" );

    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(1, result);
}

TEST(Interpretation, StatementCall_RecursionDirect_1){
    xreate::details::tier1::XreateManager* man = xreate::details::tier1::XreateManager::prepare(
R"Code(
    unwrap = function(data:: X):: Y {
        if (data[0] == "a")::Y {0} else {unwrap(data[0])}
    }

    entry = function:: i8; entry {
        unwrap([[[["a"]]]]):: i8; interpretation(force)
    }
)Code" );

    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);

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

    ASSERT_EQ(0, result);
}

TEST(Interpretation, StatementCall_RecursionIndirect_1){
        XreateManager* man = XreateManager::prepare(
R"Code(
    funcA = function(data:: X):: Y {
        if (data == "a")::Y {0} else {funcB(data)}
    }

    funcB = function(data:: X):: Y {
        if (data == "b")::Y {1} else {funcA(data)}
    }

    entry = function:: i8; entry {
        funcA(""):: i8; interpretation(force)
    }
)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:: num, code:: string; interpretation(force)):: num {
        switch(code)::int
         case ("inc")    {argument + 1}
         case ("dec")    {argument - 1}
         case ("double") {argument * 2}
    }

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

        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, Compilation_PartialIntr_2){
    xreate::details::tier1::XreateManager* man = xreate::details::tier1::XreateManager::prepare(
R"Code(
    evaluate= function(argument:: num, code:: string; interpretation(force)):: num {
        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]; interpretation(force).

        loop fold(commands->comm::string, 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_3){
    xreate::details::tier1::XreateManager* man = xreate::details::tier1::XreateManager::prepare(
R"Code(
    Command= type variant {INC, DEC, DOUBLE}.

    evaluate= function(argument:: num, code:: Command; interpretation(force)):: num {
        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]; interpretation(force).

        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, SwitchVariant){
    xreate::XreateManager* man = xreate::XreateManager::prepare(
R"Code(
OneArgument = type{x::int}.
TWoArgument = type {x::int, y::int}.

Command= type variant {
        ADD::TwoArguments,
        DEC:: OneArgument,
        DOUBLE::OneArgument
}.

main = function::int; entry{
    program = ADD({x=2, y=3})::Command; interpretation(force).

    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(
OneArgument = type{x::int}.
TWoArgument = type {x::int, y::int}.

Command= type variant {
        ADD::TwoArguments,
        DEC:: OneArgument,
        DOUBLE::OneArgument
}.

main = function::int; entry{
    program = [ADD({x=2, y=3}), DEC({x=8})]::[Command]; interpretation(force).

    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; interpretation(force){
            program["arguments"][1]
        }

        main = function::int; entry {
            x = {arguments = [10, 9, 8, 7]}:: unknType; interpretation(force).
            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);
}

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