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

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

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

using namespace xreate;
using namespace xreate::context;

TEST(Context, frame_Context1){
	details::tier1::XreateManager* man = details::tier1::XreateManager::prepare(
		"	import raw (\"core/control-context.lp\").\n"
		"	compute = function::int {\n"
		"		0\n"
		"	}\n"

		"	computeFast = function:: int {\n"
		"		context:: computation(fast).\n"

		"		compute()\n"
		"	}\n"

		"	computePrecisely = function:: int {\n"
		"		context:: computation(precise). \n"

		"		compute()\n"
		"	}\n"

                "test = function(cmnd:: int):: int; entry {\n"
                "   context:: arithmetic(iee754). \n"

                "   if (cmnd > 0)::int {computePrecisely()} else {computeFast()} \n"
                "}\n"
	);

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

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

	int contextSize = context.size();
	EXPECT_EQ(1, contextSize); //arithmetic(iee754)
}

TEST(Context, contextAsRequirementSuccessful1){
	XreateManager* man = XreateManager::prepare(
		"       import raw (\"core/control-context.lp\").\n"

                "       case context::safe {\n"
		"	funcSensitive = function::int {\n"
		"		0\n"
		"	}}\n"

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

		"		funcSensitive()\n"
		"	}\n"
	);

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

TEST(Context, contextAsRequirementFailed){
	XreateManager* man = XreateManager::prepare(
		"       import raw (\"core/control-context.lp\").\n"

                "       case context::safe {\n"
		"	funcSensitive = function::int {\n"
		"		0\n"
		"	}}\n"

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

		"		funcSensitive()\n"
		"	}\n"
	);

	ASSERT_DEATH(man->run(), "findFunction");
}

TEST(Context, ContextPropagationNested){
	XreateManager* man = XreateManager::prepare(
		"       import raw (\"core/control-context.lp\").\n"

                "       case context::safe {\n"
		"	square = function(x:: int) ::int {\n"
		"		x * x\n"
		"	}}\n"

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

                "               range = [1..10]:: [int].   \n"
                "               loop fold(range->x::int, 0->acc):: int {   \n"
                "                   acc + square(x)    \n"
                "               }  \n"
                "       }\n"
	);

	int (*main)() = (int (*)()) man->run();
	ASSERT_EQ(385, main());
}

TEST(Context, ContextPropagationNestedInterfunction){

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

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

        "convert= function(source:: num):: num { \n"
                "convertConcrete(source) \n"
        "} \n"

	"test = function(source:: num):: num; entry {\n"
	"	context:: toMillimeters.\n"
	"	convert(1)\n"
	"}\n" );

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

TEST(Context, full_ContextBasedFunctionSpecialization){

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

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

	XreateManager* man = XreateManager::prepare(
                "	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)) {truth}\n"
		"\n"
		"		convert(1)\n"
		"	}" );

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

TEST(Context, full_InheritedRuleContext){
    XreateManager* man = XreateManager::prepare(
"	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)) {truth}\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("truth.");
    int (*entry)(int) = (int (*)(int)) man->run();
    ASSERT_EQ(10, entry(0));
    ASSERT_EQ(1, entry(1));
}



TEST(Context, full_LateContext){
    details::tier1::XreateManager* man = details::tier1::XreateManager::prepare(
            "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->analyse();
    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->getFunctionSpecializations("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, loopContextExists){
    XreateManager* man = XreateManager::prepare (
        "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, pathDependentContext){
    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)) {truth}

        doConvert(value)
}

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

        doConvert(value)
}

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

    boost::scoped_ptr<details::tier1::XreateManager> man(details::tier1::XreateManager::prepare(move(program)));
    man->clasp->addRawScript("truth.");
    man->analyse();

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

//TODO recover context loop and enable the test
TEST(Context, DISABLED_full_LoopContext){
    XreateManager* man = XreateManager::prepare(
    "	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));
}
