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

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

