/*
 * frame-context.cpp
 *
 *  Created on: Dec 3, 2015
 *      Author: pgess
 */

#include "passmanager.h"
#include "query/context.h"

#include "gtest/gtest.h"
#include <iostream>
#include <boost/scoped_ptr.hpp>

using namespace xreate;

TEST(Context, frame_Context1){
	PassManager* man = PassManager::prepareForCode(
		"	import raw (\"core/control-context.lp\")\n"
		"	testC = function::int {\n"
		"		context:: testC.\n"

		"		0\n"
		"	}\n"

		"	testA = function:: int {\n"
		"		context:: testA; test.\n"

		"		testC()\n"
		"	}\n"

		"	testB = function:: int {\n"
		"		context:: testB; test.\n"

		"		testC()\n"
		"	}\n"

                "test = function(cmnd:: int):: int; entry {\n"
                "   if (cmnd > 0)::int {testA()} else {testB()} \n"
                "}\n"
	);

	ContextQuery* query = (ContextQuery*) man->clasp->registerQuery(new ContextQuery(), QueryId::ContextQuery);
	man->runWithoutCompilation();

	CodeScope* scopeTestC = man->root->findFunction("testC")->getEntryScope();
	const Domain& context = query->getContext(man->clasp->pack(scopeTestC));

	int contextSize = context.size();
	EXPECT_EQ(2, contextSize);
}

TEST(Context, full_ContextBasedFunctionSpecialization){

	PassManager* man = PassManager::prepareForCode(
        "	import raw (\"core/control-context.lp\")\n"
	"	case context::toMillimeters {\n"
	"		convert = function(source:: num)::num {\n"
	"					10 * source \n"
	"		}\n"
	"	}\n"

	"	case context::toInches {\n"
	"		convert = function(source:: num)::num {\n"
	"			2 * source \n"
	"		}\n"
	"	}\n"

	"test = function(vrnt:: int)::int; entry {\n"
	"		switch(vrnt):: int\n"
	"		case 0 {\n"
	"			context:: toMillimeters.\n"
	"			convert(1)\n"
	"		}\n"
	"\n"
	"		case 1 {\n"
	"			context:: toInches.\n"
	"			convert(1)\n"
	"		}\n"
	"		case default {0}\n"
	"	}" );

	int (*main)(int) = (int (*)(int)) man->run();
	ASSERT_EQ(10, main(0));
	ASSERT_EQ(2, main(1));
}

//TOODO recover context loop and enable the test 
TEST(Context, DISABLED_full_LoopContext){

	PassManager* man = PassManager::prepareForCode(
        "	import raw (\"core/control-context.lp\")\n"
        "       case context:: a {\n"
	"	print = function:: string {\n"
	"		\"a\"\n"
	"	}}\n"
	"\n"
	"	case context:: b {\n"
	"	print = function:: string {\n"
	"		\"b\"\n"
	"	}}\n"
	"\n"
	"	case context:: c {\n"
	"	print = function:: string {\n"
	"		\"c\"\n"
	"	}}\n"
	"\n"
	"	case context:: d {\n"
	"	print = function:: string {\n"
	"		\"d\"\n"
	"	}}\n"
	"\n"
	"	start = function(command::int)::string; entry {\n"
	"		switch (command) :: string \n"
	"		case 0 {\n"
	"			context:: print(a); print(b); print(d).\n"
	"\n"
	"			loop context (\"print\") {\n"
	"				print()\n"
	"			}\n"
	"		}\n"
	"\n"
	"		case default {\n"
	"			context:: print(c).\n"
	"			loop context (\"print\") {\n"
	"				print()\n"
	"			}\n"
	"		}\n"
	"	}");


	char* (*main)(int)  =(char* (*)(int)) man->run();
	ASSERT_STREQ("c", main(1));
	ASSERT_STREQ("a", main(0));
}

TEST(Context, full_RuleContext){
	/*
	"rule context:: childs(Child)\n"
	"	case artefact(Item)\n"
	"	{\n"
	"		artefact_depends(Item, Child)\n"
	"	}";
	*/

	PassManager* man = PassManager::prepareForCode(
                "	import raw (\"core/control-context.lp\")\n"
		"	case context:: toMilli {\n"
		"		convert = function(length::int)::int{\n"
		"			10 * length\n"
		"		}\n"
		"	}\n"
		"\n"
		"	case context:: toCenti {\n"
		"		convert = function(length::int)::int{\n"
		"			length\n"
		"		}\n"
		"	}\n"
		"\n"
		"	main=function::int; entry {\n"
		"		context:: output(milli).\n"
		"\n"
		"		rule context::toMilli\n"
		"		case output(milli) {true}\n"
		"\n"
		"		convert(1)\n"
		"	}" );

	man->clasp->addRawScript("true.");
	int (*entry)() = (int (*)()) man->run();
	ASSERT_EQ(10, entry());
}

TEST(Context, full_InheritedRuleContext){
    PassManager* man = PassManager::prepareForCode(
"	import raw (\"core/control-context.lp\") \n"

"	case context:: toMilli {\n"
"		convert = function(length::int)::int{\n"
"			10 * length\n"
"		}\n"
"	}\n"

"	case context:: toCenti {\n"
"		convert = function(length::int)::int{\n"
"			length\n"
"		}\n"
"	}\n"
"\n"

"main = function(comm:: num)::num; entry{\n"
"		rule context::X case output(X) {true}\n"
"\n"
"		switch (comm)::num \n"
"			case 0 {\n"
"				context:: output(toMilli).\n"
"				convert(1)\n"
"			}\n"
"			case default {\n"
"				context:: output(toCenti).\n"
"				convert(1)\n"
"			}\n"
"	}");

    man->clasp->addRawScript("true.");
    int (*entry)(int) = (int (*)(int)) man->run();
    ASSERT_EQ(10, entry(0));
    ASSERT_EQ(1, entry(1));
}



TEST(Context, full_LateContext){
	PassManager* man = PassManager::prepareForCode(
		"import raw (\"core/control-context.lp\")\n"

		"    convert = function(length:: num)::num{\n"
		"        0\n"
		"    }\n"

		"case context:: milli {\n"
		"    convert = function(length:: num)::num{\n"
		"        1000 * length\n"
		"    }\n"
		"}\n"
		"\n"
		"case context:: centi {\n"
		"    convert = function(length:: num)::num{\n"
		"        100 * length\n"
		"    }\n"
		"}\n"
		"\n"
		"calculate = function(length:: num)::num {\n"
		"    convert(length)\n"
		"}\n"
		"\n"
		"main = function(com:: num):: num; entry {\n"
			"    switch (com):: num \n"
			"    case 0 {\n"
			"        context:: milli.\n"
			"        calculate(1)\n"
			"    }\n"
			"\n"
			"    case default{\n"
			"        context:: centi. \n"
			"        calculate(1)\n"
			"    }\n"
		"}");

	man->runWithoutCompilation();
	ContextQuery* queryContext = reinterpret_cast<ContextQuery*>(man->clasp->getQuery(QueryId::ContextQuery));
	Expression exprSwitch = man->root->findFunction("main")->__entry->getBody();
	CodeScope* blockDefault = man->root->findFunction("main")->__entry->getBody().operands[1].blocks.front();
	ScopePacked blockDefaultId = man->clasp->pack(blockDefault);
	const Domain& domDefault = queryContext->getContext(blockDefaultId);
	ASSERT_EQ(1, domDefault.count(Expression(Atom<Identifier_t>("centi"))));

	std::list<ManagedFnPtr> variants = man->root->getFunctionVariants("convert");
	for (ManagedFnPtr f: variants){
		const Expression guard = f->guardContext;
		bool result = (guard.getValueString() == "centi" || guard.getValueString() == "milli" || !guard.isValid());
		ASSERT_TRUE(result);
	}

	const FunctionDemand& demMain = queryContext->getFunctionDemand("main");
	ASSERT_EQ(0, demMain.size());

	const FunctionDemand& demCalculate = queryContext->getFunctionDemand("calculate");
	ASSERT_EQ(1, demCalculate.size());

	int (*entry)(int) = (int (*)(int)) man->run();
	ASSERT_EQ(1000, entry(0));
	ASSERT_EQ(100, entry(1));
}

TEST(Context, testLoopContextExists){
    PassManager* man = PassManager::prepareForCode (
        "import raw (\"core/control-context.lp\")\n"

        "interface(cfa){\n"
        "    operator fold:: annotation1.\n"
        "}\n"
        "\n"
        "main = function:: int; entry {\n"
        "    x = [1..10]:: [int].\n"
        "    sum = loop fold (x->el:: int, 0->sum):: int {\n"
        "        el + sum + f1()\n"
        "    }.  \n"
        "    sum\n"
        "}"
        "case context:: annotation1 {"
        " f1 = function::int {\n"
        "  	x = 0:: int.		 "
        "       x\n"
        " }"
        "}"
    );

    man->run();
}

TEST(Context, pathDependent_context){
    std::string program =
R"CODE(
import raw("core/control-context.lp")

convert = function(length:: num) :: num {
    0
}

case context:: convert(milli, meters) {
    convert = function(length:: num) :: num {
        1000 * length
    }
}

case context:: convert(centi, meters) {
    convert = function(length:: num) :: num {
        100 * length
    }
}

case context:: convert(centi, kilo) {
    convert = function(length:: num) :: num {
        100000 * length
    }
}

case context:: convert(milli, kilo) {
    convert = function(length:: num) :: num {
        1000000 * length
    }
}

main = function(value::num, unitsInput::num, unitsOutput::num)::num; entry{
    switch (unitsInput)::num
    case 0 {
        test_fromMilli(value, unitsOutput)
    }
    case 1 {
        test_fromCenti(value, unitsOutput)
    }

    case default {0}
}

test_fromCenti = function(value::num, output::num)::num{
    context:: input(centi).

    switch(output):: num
    case 0  {
        toMeters(value)
    }

    case 1 {
        toKilo(value)
    }

    case default {0}
}

test_fromMilli = function(value::num, output::num)::num{
    context:: input(milli).

    switch(output):: num
    case 0  {
        toMeters(value)
    }

    case 1 {
        toKilo(value)
    }

    case default {0}
}

toMeters = function(value::num)::num {
        rule context:: convert(X, meters) case input(X) {true}

        doConvert(value)
}

toKilo = function(value::num)::num {
        rule context:: convert(X, kilo) case input(X) {true}

        doConvert(value)
}

doConvert = function(value::num)::num{
   convert(value)
})CODE";

    boost::scoped_ptr<PassManager> man(PassManager::prepareForCode(move(program)));
    man->runWithoutCompilation();

    int (*test)(int, int, int) = (int (*)(int, int, int))man->run();

    enum {INPUT_MILLI, INPUT_CENTI};
    enum {OUTPUT_METERS, OUTPUT_KILO};

    ASSERT_EQ(1000000, test(1, INPUT_MILLI, OUTPUT_KILO));
    ASSERT_EQ(200, test(2, INPUT_CENTI, OUTPUT_METERS));
}

