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

#include "gtest/gtest.h"
#include "xreatemanager.h"
#include "llvmlayer.h"
#include "main/Parser.h"
#include "transcendlayer.h"
#include "analysis/typeinference.h"
#include "analysis/interpretation.h"

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

TEST(Types, DependantTypes1) {
    string&& code = "XmlNode = type {\n"
        "        tag:: string,\n"
        "        attrs:: [string], \n"
        "        content:: string\n"
        "}.\n";

    std::unique_ptr<XreateManager> program(XreateManager::prepare(move(code)));
    ExpandedType typeXmlNode = program->root->findType("XmlNode");

    ASSERT_EQ(TypeOperator::LIST_NAMED, typeXmlNode->__operator);
    ASSERT_EQ(3, typeXmlNode->__operands.size());
    ASSERT_EQ(TypePrimitive::String, typeXmlNode->__operands.at(0).__value);
    ASSERT_EQ(TypeOperator::LIST, typeXmlNode->__operands.at(1).__operator);
    ASSERT_EQ(TypePrimitive::String, typeXmlNode->__operands.at(2).__value);
}

TEST(Types, ast_ParameterizedTypes_FeatureTypeIndex_1) {
    string&& code = "XmlNode = type {\n"
        "        tag:: string,\n"
        "        attrs:: [string],\n"
        "        content:: string\n"
        "}.\n"
        ""
        "Template = type(Leaf) {Leaf, [Leaf[content]]}."
        "Concrete = type Template(XmlNode).";

    std::unique_ptr<XreateManager> program(XreateManager::prepare(move(code)));
    ExpandedType typeConcrete = program->root->findType("Concrete");

    ASSERT_EQ(TypeOperator::LIST_NAMED, typeConcrete->__operator);
    ASSERT_EQ(2, typeConcrete->__operands.size());
    ASSERT_EQ(TypeOperator::LIST_NAMED, typeConcrete->__operands.at(0).__operator);

    ASSERT_EQ(TypeOperator::LIST, typeConcrete->__operands.at(1).__operator);
    ASSERT_EQ(TypePrimitive::String, typeConcrete->__operands.at(1).__operands.at(0).__value);
}

TEST(Types, TreeType1) {
    string&& code = "XmlNode = type {\n"
        "        tag:: string,\n"
        "        attrs:: [string],\n"
        "        content:: string\n"
        "}.\n"
        ""
        "Tree = type(Leaf) {Leaf, [Tree(Leaf)]}."
        "Concrete = type Tree(XmlNode).";

    std::unique_ptr<XreateManager> program(XreateManager::prepare(move(code)));
    ExpandedType typeConcrete = program->root->findType("Concrete");

    ASSERT_EQ(TypeOperator::LIST_NAMED, typeConcrete->__operator);
    ASSERT_EQ(2, typeConcrete->__operands.size());
    ASSERT_EQ(TypeOperator::LIST_NAMED, typeConcrete->__operands.at(0).__operator);

    ASSERT_EQ(TypeOperator::LIST, typeConcrete->__operands.at(1).__operator);

    auto typeLink = typeConcrete->__operands.at(1).__operands.at(0);
    ASSERT_EQ(TypeOperator::LINK, typeLink.__operator);
    ASSERT_EQ(typeConcrete->conjuctionId, typeLink.conjuctionId);
}

TEST(Types, TreeType1LLvm) {
    string&& code = "XmlNode = type {\n"
        "        tag:: string,\n"
        "       /* attrs:: [string],*/\n"
        "        content:: string\n"
        "}.\n"
        ""
        "Tree = type(Leaf) {Leaf, [Tree(Leaf)]}."
        "Concrete = type Tree(XmlNode).";

    std::unique_ptr<XreateManager> program(XreateManager::prepare(move(code)));
    ExpandedType typeConcrete = program->root->findType("Concrete");

    llvm::Type* raw = program->llvm->toLLVMType(typeConcrete);
}

TEST(Types, ArrayOfExternal1) {
    FILE* input = fopen("scripts/containers/Containers_Implementation_LinkedList1.xreate", "r");
    assert(input != nullptr);

    Scanner scanner(input);
    Parser parser(&scanner);

    parser.Parse();
    AST* ast = parser.root->finalize();

    CodeScope* body = ast->findFunction("test")->getEntryScope();

    const ExpandedType& t2 = ast->getType(body->getDefinition(body->getSymbol("childrenRaw")));
    EXPECT_EQ(t2->__operator, TypeOperator::LIST);
}

TEST(Types, ExternType1) {
    FILE* input = fopen("scripts/containers/Containers_Implementation_LinkedList1.xreate", "r");
    assert(input != nullptr);

    Scanner scanner(input);
    Parser parser(&scanner);
    parser.Parse();

    AST* ast = parser.root->finalize();
    CodeScope* body = ast->findFunction("test")->getEntryScope();

    const ExpandedType& t2 = ast->getType(body->getDefinition(body->getSymbol("tree")));
    EXPECT_EQ(t2->__operator, TypeOperator::CUSTOM);
}

TEST(Types, ast_VariantType1) {
    string&& code =
        "	colors = type variant {RED, BLUE, GREEN}.\n"
        "	test = function:: colors; entry {GREEN()}";

    std::unique_ptr<XreateManager> program(XreateManager::prepare(move(code)));

    ExpandedType typ = program->root->findType("colors");
    EXPECT_EQ(TypeOperator::VARIANT, typ->__operator);

    Expression eRed = program->root->findFunction("test")->getEntryScope()->getBody();
    EXPECT_EQ(Operator::VARIANT, eRed.op);

    const ExpandedType& typ2 = program->root->getType(eRed);
    EXPECT_EQ(TypeOperator::VARIANT, typ2->__operator);
}

TEST(Types, full_VariantType_Switch1) {
    string&& code =
        "colors = type variant{RED, BLUE, GREEN}.   \n"
        "	test = function:: colors {GREEN()}          \n"

        "main = function:: int; entry {         \n"
        "	switch(test()):: int    \n"
        "	case (GREEN()) {0}                  \n"
        "	case default {1}                \n"
        "}";

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

    EXPECT_EQ(0, main());
}

TEST(Types, ast_VariantType2) {
    std::string script =
        R"Code(
    Annotation = type
        variant {
            Num:: int,
            String:: string,
            Func:: {name::string, arguments::[Expression]}
        }.
)Code";

    std::unique_ptr<XreateManager> program(XreateManager::prepare(move(script)));
    ExpandedType typ = program->root->findType("Annotation");
    ASSERT_EQ(3, typ.get().fields.size());
}

TEST(Types, SlaveTypes_UnwrapSlaveType1) {
    auto man = details::tier1::XreateManager::prepare(R"Code(
        AtomNumT = type slave atomNumT.
        AtomStrT = type slave atomStrT.
        CmpndIntStrT = type slave cmpndIntStrT.

        VariantT = type slave variantT.

        VariantComplicatedT = type slave variantComplicatedT.
    )Code");

    man->transcend->addRawScript(R"RAW(
        atomNumT(5). atomNumT(8).
        atomStrT("a"). atomStrT("b").
        cmpndIntStrT(1, "a").
        cmpndIntStrT(2, "b").

        variantT(first).
        variantT(second).
        variantT(third).

        variantComplicatedT(first(1, "a")).
        variantComplicatedT(second("b")).
    )RAW");

    man->analyse();
    ExpandedType AtomNumT = man->root->findType("AtomNumT");
    ASSERT_EQ(AtomNumT->__operator, TypeOperator::SLAVE);

    ExpandedType ContentAtomNumT = interpretation::dereferenceSlaveType(AtomNumT, man->transcend);
    ASSERT_EQ(TypePrimitive::Num, ContentAtomNumT->__value);

    ExpandedType AtomStrT = man->root->findType("AtomStrT");
    ExpandedType ContentAtomStrT = interpretation::dereferenceSlaveType(AtomStrT,
        man->transcend);
    ASSERT_EQ(TypePrimitive::String, ContentAtomStrT->__value);

    ExpandedType CmpndIntStrT = man->root->findType("CmpndIntStrT");
    ExpandedType ContentCmpndIntStrT = interpretation::dereferenceSlaveType(CmpndIntStrT,
        man->transcend);
    ASSERT_EQ(TypeOperator::LIST_NAMED, ContentCmpndIntStrT->__operator);
    ASSERT_EQ(2, ContentCmpndIntStrT->__operands.size());

    ExpandedType VariantT = man->root->findType("VariantT");
    ExpandedType ContentVariantT = interpretation::dereferenceSlaveType(VariantT,
        man->transcend);
    ASSERT_EQ(TypeOperator::VARIANT, ContentVariantT->__operator);
    ASSERT_EQ(3, ContentVariantT->fields.size());

    ExpandedType VariantComplicatedT = man->root->findType("VariantComplicatedT");
    ExpandedType ContentVariantComplicatedT = interpretation::dereferenceSlaveType(VariantComplicatedT,
        man->transcend);
    ASSERT_EQ(TypeOperator::VARIANT, ContentVariantComplicatedT->__operator);
    ASSERT_EQ(2, ContentVariantComplicatedT->fields.size());
    ASSERT_EQ(2, ContentVariantComplicatedT->__operands.at(0).__operands.size());
}

TEST(Types, IndexNumber_1)
{
    string&& code = 
R"CODE(
        Tuple = type {string, int}.
        Int = type Tuple[1].
)CODE";

    std::unique_ptr<XreateManager> program(XreateManager::prepare(move(code)));
    ExpandedType typeInt = program->root->findType("Int");
}