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

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

#include "gtest/gtest.h"
#include <iostream>

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

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

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

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

TEST(Context, full_ContextBasedFunctionSpecialization){

	PassManager* man = PassManager::prepareForCode(
	"	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));
}

TEST(Context, full_LoopContext){

	PassManager* man = PassManager::prepareForCode("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(
		"	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->__body;
	CodeScope* blockDefault = man->root->findFunction("main")->__entry->__body.operands[1].blocks.front();
	ScopePacked blockDefaultId = man->clasp->pack(blockDefault);
	const ContextDomain& 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 FunctionContextDemand& demMain = queryContext->getFunctionDemand("main");
	ASSERT_EQ(0, demMain.size());

	const FunctionContextDemand& demCalculate = queryContext->getFunctionDemand("calculate");
	ASSERT_EQ(1, demCalculate.size());
	ASSERT_EQ(1, demCalculate.right.count("convert"));

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


