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

#include "xreatemanager.h"
#include "pass/dfapass.h"
#include "pass/cfapass.h"
#include "analysis/DominatorsTreeAnalysisProvider.h"
#include "analysis/cfagraph.h"

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

using namespace xreate;
using namespace xreate::cfa;
using namespace std;

TEST(CFA, testFunctionAnnotationsClasp){
    string&& program =
        "f2 = function::int; annotationF2 {\n"
        "    0\n"
        "}\n"
        "\n"
        "f1 = function:: int; entry; annotationF1 {\n"
        "    f2() + 10\n"
        "}";


    details::tier1::XreateManager* man = details::tier1::XreateManager::prepare(move(program));
    man->analyse();

    StaticModel answer = man->clasp->query("annotationF1");
    EXPECT_EQ(1, answer.size());

    answer = man->clasp->query("annotationF2");
    EXPECT_EQ(1, answer.size());
}

TEST(CFA, testLoopContextExists){
    details::tier1::XreateManager* man = details::tier1::XreateManager::prepare (
        "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->analyse();
    StaticModel model = man->clasp->query("annotation1");
    ScopePacked scopeIdActual = std::get<0>(ClaspLayer::parse<ScopePacked>(model.begin()->second));

    CodeScope* scopeEntry = man->root->findFunction("main")->getEntryScope();
    const Expression& exprSum = scopeEntry->getDefinition(scopeEntry->getSymbol("sum"));
    CodeScope* scopeExpected = exprSum.blocks.front();

    ScopePacked scopeIdExpected = man->clasp->pack(scopeExpected);
    ASSERT_EQ(scopeIdExpected, scopeIdActual);
}

TEST(CFA, DependenciesFnCall){
    details::tier2::XreateManager* man = details::tier2::XreateManager::prepare(
    R"Code(
            a = function::int{
                seq
                    {x = 0:: int. x}
                    {x = b():: int. x}::int
            }

            b = function::int {y = 0. y}
    )Code");

    CodeScope* scopeSeq1 =  man->root->findFunction("a")->getEntryScope()->getBody().blocks.front();
    CodeScope* scopeSeq2 =  *(++man->root->findFunction("a")->getEntryScope()->getBody().blocks.begin());
    CodeScope* scopeB =  man->root->findFunction("b")->getEntryScope();

    ScopePacked psSeq1 = man->clasp->pack(scopeSeq1);
    ScopePacked psSeq2 = man->clasp->pack(scopeSeq2);
    ScopePacked psB = man->clasp->pack(scopeB);

    CFAPass* pass = new CFAPass(man);

    man->registerPass(pass, PassId::CFGPass);
    man->executePasses();
    const CFAGraph* report = dynamic_cast<CFAPassBasic*>(man->getPassById(PassId::CFGPass))->getReport();
    auto dependencies = report->__dependencyRelations;
    delete pass;

    ASSERT_EQ(1, dependencies.count(psSeq2));
    ASSERT_EQ(1, dependencies.count(psB));
}

TEST(CFA, DependenciesChildScope){
    details::tier2::XreateManager* man = details::tier2::XreateManager::prepare(
    R"Code(
            a = function::int{
                seq
                    {x = 0:: int. x}
                    {x=0::int. if(x>0)::int{1} else {0}}::int
            }
    )Code");

    CodeScope* scopeSeq1 =  man->root->findFunction("a")->getEntryScope()->getBody().blocks.front();
    CodeScope* scopeSeq2 =  *(++man->root->findFunction("a")->getEntryScope()->getBody().blocks.begin());
    CodeScope* scopeIf1 =  scopeSeq2->getBody().blocks.front();
    CodeScope* scopeIf2 =  *(++scopeSeq2->getBody().blocks.begin());

    ScopePacked psSeq1 = man->clasp->pack(scopeSeq1);
    ScopePacked psSeq2 = man->clasp->pack(scopeSeq2);
    ScopePacked psIf1  = man->clasp->pack(scopeIf1);
    ScopePacked psIf2  = man->clasp->pack(scopeIf2);

    CFAPass* pass = new CFAPass(man);

    man->registerPass(pass, PassId::CFGPass);
    man->executePasses();
    const CFAGraph* report = dynamic_cast<CFAPassBasic*>(man->getPassById(PassId::CFGPass))->getReport();
    auto dependencies = report->__dependencyRelations;
    delete pass;

    ASSERT_EQ(0, dependencies.count(psSeq1));
    ASSERT_EQ(1, dependencies.count(psSeq2));
    ASSERT_EQ(1, dependencies.count(psIf1));
    ASSERT_EQ(1, dependencies.count(psIf2));

    for(auto rec: dependencies)
    {
        std::cout << rec.first << " " << rec.second << std::endl;
    }
}


TEST(CFA, DomReportOneRoot){
    std::string program =
R"CODE(
            a = function:: int; entry{
                seq
                    {x = 0:: int. x}
                    {x = 1:: int. x}::int
            }
)CODE";

    std::unique_ptr<details::tier2::XreateManager> man(details::tier2::XreateManager::prepare(move(program)));

    CFAPass* pass = new CFAPass(man.get());
    man->registerPass(pass, PassId::CFGPass);
    pass->run();

    ScopePacked scope1 = man->clasp->pack(man->root->findFunction("a")->getEntryScope()->getBody().blocks.front());
    ScopePacked scope2 = man->clasp->pack(*++man->root->findFunction("a")->getEntryScope()->getBody().blocks.begin());

    dominators::DominatorsTreeAnalysisProvider* providerDomAnalysis = new dominators::DominatorsTreeAnalysisProvider();
    providerDomAnalysis->run(pass->getReport());

    dominators::DominatorsTreeAnalysisProvider::Dominators expectedFDom= {
         {1, {0, 3}}
        ,{2, {1, 2}}
    };

    dominators::DominatorsTreeAnalysisProvider::Dominators expectedPostDom= {
         {2, {0, 3}}
        ,{1, {1, 2}}
    };

    auto actualFDom = providerDomAnalysis->getForwardDominators();
    auto actualPostDom = providerDomAnalysis->getPostDominators();

    ASSERT_EQ(expectedFDom, actualFDom);
    ASSERT_EQ(expectedPostDom, actualPostDom);

    delete providerDomAnalysis;
    delete pass;
}
