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

#include "xreatemanager.h"
#include "supplemental/basics.h"
#include "llvmlayer.h"
#include "pass/compilepass.h"
#include "compilation/lambdas.h"

#include "gtest/gtest.h"

using namespace xreate;
using namespace xreate::compilation;
using namespace std;
//DEBT implement no pkgconfig ways to link libs
//TOTEST FunctionUnit::compileInline

TEST(Compilation, functionEntry1){
    std::unique_ptr<XreateManager> program(XreateManager::prepare(
        "func1 = function(a:: int):: int {a+8} \
         func2 = function::int; entry {12 + func1(4)}      \
        "));

    void* entryPtr = program->run();
    int (*entry)() = (int (*)())(intptr_t)entryPtr;

    int answer = entry();
    ASSERT_EQ(24, answer);
}

TEST(Compilation, full_IFStatementWithVariantType){
    XreateManager* man = XreateManager::prepare(
        "Color = type variant {RED, BLUE, GREEN}.\n"
        "\n"
        "   main = function(x::int):: bool; entry {\n"
        "       color = if (x == 0 )::Color {RED()} else {BLUE()}.\n"
        "       if (color == BLUE())::bool {true} else {false}\n"
        "   }"
    );

    bool (*main)(int) = (bool (*)(int)) man->run();
    ASSERT_FALSE(main(0));
    ASSERT_TRUE(main(1));
}

TEST(Compilation, full_Variant1){
    XreateManager* man = XreateManager::prepare(R"Code(
        global = type predicate {
          entry
        }

        Command= type variant{
            Add(x::int, y::int),
            Dec(x::int)
        }.

        main = function::Command; entry() {
            Dec(2) ::Command
        }
    )Code");

    void (*main)() = (void (*)()) man->run();
}

TEST(Compilation, full_SwitchVariant1){
    XreateManager* man = XreateManager::prepare(R"Code(
        Command= type variant{
            Add(x::int, y::int),
            Dec(x::int)
        }.

        main = function::int; entry {
            command = Add(3, 5):: Command.

            switch variant(command)::int
            case(Add){command["x"] + command["y"]}
            case(Dec){command["x"]}
        }
    )Code");

    int (*mainFn)() = (int (*)()) man->run();
    int result = mainFn();
    ASSERT_EQ(8, result);
}

TEST(Compilation, full_SwitchVariantNoArguments2){
    XreateManager* man = XreateManager::prepare(R"Code(
        Command= type variant{Add, Dec}.

        main = function::int; entry {
            command = Dec():: Command.

            switch variant(command)::int
            case(Add){0}
            case(Dec){1}
        }
    )Code");

    int (*mainFn)() = (int (*)()) man->run();
    int result = mainFn();
    ASSERT_EQ(1, result);
}

TEST(Compilation, full_SwitchVariantMixedArguments3){
    XreateManager* man = XreateManager::prepare(R"Code(
        Command= type variant{
            Add(x::int, y::int),
            Dec
        }.

        main = function(arg::int)::     int; entry {
            command = if (arg > 0)::Command {Dec()} else {Add(1, 2)}.

            switch variant(command)::int
            case(Add){0}
            case(Dec){1}
        }
    )Code");

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

TEST(Compilation, full_StructUpdate){
    XreateManager* man = XreateManager::prepare(
    R"Code(

    Rec = type {
        a :: int,
        b:: int
    }.

    test= function:: int; entry {
        a = {a = 18, b = 20}:: Rec.

        b = a + {a = 11}:: Rec.
        b["a"]
    }
    )Code");

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

    ASSERT_EQ(11, result);
}

TEST(Compilation, AnonymousStruct_init_index){
    std::string code =
R"Code(

    main = function:: int; entry {
        x = {10, 15} :: {int, int}.
        x[1]
    }

)Code";

    std::unique_ptr<XreateManager> man(XreateManager::prepare(move(code)));
    int (*main)() = (int (*)()) man->run();

    EXPECT_EQ(15, main());
}

TEST(Compilation, AnonymousStruct_init_update){
    std::string code =
R"Code(

    main = function:: int; entry {
        x = {10, 15} :: {int, int}.
        y = x + {6}:: {int, int}.
        y[0]
    }

)Code";

    std::unique_ptr<XreateManager> man(XreateManager::prepare(move(code)));
    int (*main)() = (int (*)()) man->run();

    EXPECT_EQ(6, main());
}

TEST(Compilation, BugIncorrectScopes1){
    std::string code =
R"Code(

    init = function:: int {10}
    main = function(cmd:: int):: int; entry {
        x = init():: int.
        if(cmd > 0):: int { x + 1 } else { x }
    }
)Code";

    std::unique_ptr<XreateManager> man(XreateManager::prepare(move(code)));
    int (*mainFn)(int) = (int (*)(int)) man->run();

    EXPECT_EQ(11, mainFn(1));
}

TEST(Compilation, Sequence1){
        std::string code =
R"Code(
    interface(extern-c){
      libbsd = library:: pkgconfig("libbsd").

      include {
        libbsd = {"bsd/stdlib.h", "string.h"}
      }.
    }

    start = function:: i32; entry {
        seq {
            nameNew = "TestingSequence":: string.
            setprogname(nameNew)

        } {strlen(getprogname())}::i32
    }
)Code";

    std::unique_ptr<XreateManager> man(XreateManager::prepare(move(code)));
    int (*startFn)() = (int (*)()) man->run();

    int nameNewLen = startFn();
    ASSERT_EQ(15, nameNewLen);
}

TEST(Compilation, BoolInstructions1){
  std::string code =
R"Code(
test = function (a:: bool, b:: bool):: bool; entry
{
    -a
}
)Code";

    std::unique_ptr<XreateManager> man(XreateManager::prepare(move(code)));
    Fn2Args startFn = (Fn2Args) man->run();
}

TEST(Compilation, StructIndex1){
  std::string code =
  R"Code(
Anns = type predicate {
  entry()
}
test = function:: int; entry()
{
    x = {a = ({b = 3}::{b:: int})}:: {a:: {b:: int}}.
    2 + x["a", "b"] + x["a"]["b"]
}
)Code";

  std::unique_ptr<XreateManager> man(XreateManager::prepare(move(code)));
  FnNoArgs startFn = (FnNoArgs) man->run();
  int result = startFn();
  ASSERT_EQ(2, result);
}

TEST(Compilation, PreferredInt1){
  std::unique_ptr<XreateManager> man(XreateManager::prepare(""));
  TypesHelper utils(man->llvm);
  int bitwidth = utils.getPreferredIntTy()->getBitWidth();
  ASSERT_EQ(64, bitwidth);
}

TEST(Compilation, PredPredicates1){
  string code = R"(
    my-fn = function:: int; entry() {0}
  )";

  auto man = XreateManager::prepare(move(code));
  FnNoArgs startFn = (FnNoArgs) man->run();
  int result = startFn();
  ASSERT_EQ(0, result);
}

typedef intmax_t (*FnI_I)(intmax_t);
TEST(Compilation, Lambda1){
  string code = R"(
myfn = function:: int {
  a = [1..5]:: [int].
  loop map(a->x:: int):: [int] { x + 10:: int}
}
)";
  auto man = details::tier1::XreateManager::prepare(move(code));
  LLVMLayer* llvm = man->llvm;
  man->analyse();

  std::unique_ptr<CompilePass> compiler(new compilation::CompilePassCustomDecorators<>(man));
  compiler->prepare();
  LambdaIR compilerLambda(compiler.get());
  CodeScope* scopeLoop = man->root->findFunction("myfn")->getEntryScope()->getBody().blocks.front();
  auto fnRaw = compilerLambda.compile(scopeLoop, "loop");
  llvm->initJit();

  FnI_I fn = (FnI_I)llvm->getFunctionPointer(fnRaw);
  ASSERT_EQ(20, fn(10));
}

TEST(Compilation, Lambda_BoundVars1){
  string code = R"(
    myfn = function:: int {
      a = [1..5]:: [int].
      offset = 10:: int.
      loop map(a->x:: int):: [int] { x + offset:: int}
    }
  )";

  auto man = details::tier1::XreateManager::prepare(move(code));
  LLVMLayer* llvm = man->llvm;
  man->analyse();

  std::unique_ptr<CompilePass> compiler(new compilation::CompilePassCustomDecorators<>(man));
  compiler->prepare();
  LambdaIR compilerLambda(compiler.get());
  CodeScope* scopeLoop = man->root->findFunction("myfn")->getEntryScope()->getBody().blocks.front();
  auto fnRaw = compilerLambda.compile(scopeLoop, "loop");
  llvm->print();
  llvm->initJit();

  Fn2Args fn = (Fn2Args)llvm->getFunctionPointer(fnRaw);
  ASSERT_EQ(30, fn(10, 20));
}

struct Tuple3 {intmax_t a; intmax_t b; intmax_t c; };
typedef Tuple3 (*FnTuple3)();

intmax_t fn_BUG_Triple(FnTuple3 callee){
  Tuple3 result = callee();
  return result.a+ result.b + result.c;
}

TEST(Compilation, BUG_Triple){
  std::unique_ptr<XreateManager> man(XreateManager::prepare(R"(
Tuple2 = type {int, int}.
Tuple3 = type {int, int, int}.
Tuple4 = type {int, int, int, int}.

main = function:: Tuple3; entry()
{
  {1, 2, 3}
}
)"));
  FnTuple3 mainFn = (FnTuple3) man->run();
  intmax_t result = fn_BUG_Triple(mainFn);

  ASSERT_EQ(6, result);
//  ASSERT_EQ(2, result.b);
//  ASSERT_EQ(3, result.c);
}

TEST(Compilation, ExteriorFns1){
  std::unique_ptr<XreateManager> man(XreateManager::prepare(R"(
fn-a = function:: int; exterior() {1}
fn-b = function:: int; exterior() {2}
)"));

  man->options.requireEntryFn = false;
  man->run();
  FnNoArgs fnA = (FnNoArgs) man->getExteriorFn("fn-a");
  ASSERT_EQ(1, fnA());

  FnNoArgs fnB = (FnNoArgs) man->getExteriorFn("fn-b");
  ASSERT_EQ(2, fnB());
}

TEST(Compilation, LLVMAliases){
  FILE* code = fopen("scripts/compilation/llvmaliases.xreate", "r");
  assert(code != nullptr);
  std::unique_ptr<XreateManager> man(XreateManager::prepare(move(code)));
  man->run();
}