/* Any copyright is dedicated to the Public Domain.
 * http://creativecommons.org/publicdomain/zero/1.0/
 *
 * cfa.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/DominatorsAnalysisProvider.h"
#include "analysis/cfagraph.h"

#include "pass/compilepass.h"
#include "compilation/scopedecorators.h"

#include "gtest/gtest.h"
#include "aux/xreatemanager-decorators.h"
#include <boost/scoped_ptr.hpp>
#include <boost/smart_ptr/scoped_array.hpp>
#include <boost/format.hpp>

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

TEST(CFA, testFunctionAnnotations) {
    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->transcend->query("annotationF1");
    EXPECT_EQ(1, answer.size());

    answer = man->transcend->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"
        "}"
        "guard:: annotation1 {"
        " f1 = function::int {\n"
        "  	x = 0:: int.		 "
        "       x\n"
        " }"
        "}"
        );

    man->analyse();
    StaticModel model = man->transcend->query("annotation1");
    ScopePacked scopeIdActual = std::get<0>(TranscendLayer::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->transcend->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->transcend->pack(scopeSeq1);
    ScopePacked psSeq2 = man->transcend->pack(scopeSeq2);
    ScopePacked psB = man->transcend->pack(scopeB);

    CFAPass* pass = new CFAPass(man);

    man->registerPass(pass, PassId::CFAPass);
    man->executePasses();
    const CFAGraph* report = dynamic_cast<CFAPassBasic*> (man->getPassById(PassId::CFAPass))->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->transcend->pack(scopeSeq1);
    ScopePacked psSeq2 = man->transcend->pack(scopeSeq2);
    ScopePacked psIf1  = man->transcend->pack(scopeIf1);
    ScopePacked psIf2  = man->transcend->pack(scopeIf2);

    CFAPass* pass = new CFAPass(man);

    man->registerPass(pass, PassId::CFAPass);
    man->executePasses();
    const CFAGraph* report = dynamic_cast<CFAPassBasic*> (man->getPassById(PassId::CFAPass))->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, Dominators1){
    std::multimap<ScopePacked, ScopePacked> dataScopes = {
        {1, 0}, {2, 0}, {3, 1}, {4, 1}, {4, 2}
    };

    dominators::DominatorsAnalysisProvider domProvider;
    boost::scoped_ptr<dominators::CFAGraphAdapter> adapter(dominators::CFAGraphAdapter::build(dataScopes));
    domProvider.run(adapter.get());

    dominators::DominatorsAnalysisProvider::Dominators expectedFDom= {
         {0, {0, 9}}
        ,{1, {1, 4}}
        ,{2, {7, 8}}
        ,{3, {2, 3}}
        ,{4, {5, 6}}
    };

    dominators::DominatorsAnalysisProvider::Dominators expectedPostDom= {
         {0, {5, 6}}
        ,{1, {3, 4}}
        ,{2, {8, 9}}
        ,{3, {1, 2}}
        ,{4, {7, 10}}
    };

    ASSERT_EQ(expectedFDom, domProvider.getForwardDominators());
    ASSERT_EQ(expectedPostDom, domProvider.getPostDominators());
}

TEST(CFA, Dominators2) {
    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::CFAPass);
    pass->run();

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

    dominators::DominatorsAnalysisProvider* providerDomAnalysis = new dominators::DominatorsAnalysisProvider();
    boost::scoped_ptr<dominators::CFAGraphAdapter> adapter(dominators::CFAGraphAdapter::build(pass->getReport()));
    providerDomAnalysis->run(adapter.get());

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

    dominators::DominatorsAnalysisProvider::Dominators expectedPostDom = {
        {2, {1, 4}},
        {1, {2, 3}}
    };

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

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

    delete providerDomAnalysis;
    delete pass;
}
