/* 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/cfagraph.h"
#include "pass/cfatemporalseqpass.h"
#include "analysis/temporalseqgraph.h"

#include "pass/compilepass.h"
#include "compilation/decorators.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, TemporalFnCall) {
  details::tier2::XreateManager* man = details::tier2::XreateManager::prepare(
    R"Code(
    a = function::int{
      seq
        {x = b():: int. x}
        {x = 0  :: 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();
  CodeScope* scopeA =  man->root->findFunction("a")->getEntryScope();

  ScopePacked psSeq1 = man->transcend->pack(scopeSeq1);
  ScopePacked psSeq2 = man->transcend->pack(scopeSeq2);
  ScopePacked psB = man->transcend->pack(scopeB);
  ScopePacked psA =  man->transcend->pack(scopeA);

  CFATemporalSeqPass* pass = new CFATemporalSeqPass(man);
  man->registerPass(pass, PassId::CFATemporalSeqPass);
  man->executePasses();
  
  const TemporalSeqGraph* report = dynamic_cast<CFATemporalSeqPass*> (
    man->getPassById(PassId::CFATemporalSeqPass))->getReport();
  delete pass;

  std::ostringstream output;
  report->print(output);
  cout << output.str() << endl;
  ASSERT_TRUE(report->isOrdered(psSeq2, psSeq1));
  //ASSERT_TRUE(report->isOrdered(psSeq2, psB));
  ASSERT_FALSE(report->isOrdered(psSeq1, psA));
}

TEST(CFA, TemporalChildScope) {
  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);

  CFATemporalSeqPass* pass = new CFATemporalSeqPass(man);
  man->registerPass(pass, PassId::CFATemporalSeqPass);
  man->executePasses();
  const TemporalSeqGraph* report = dynamic_cast<CFATemporalSeqPass*> (
    man->getPassById(PassId::CFATemporalSeqPass))->getReport();
  delete pass;

  std::ostringstream output;
  report->print(output);
  cout << output.str() << endl;
  
  ASSERT_TRUE(report->isOrdered(psSeq2, psSeq1));
  ASSERT_TRUE(report->isOrdered(psIf1, psSeq1));
  ASSERT_TRUE(report->isOrdered(psIf2, psSeq1));
  ASSERT_FALSE(report->isOrdered(psIf2, psSeq2));
}

TEST(CFA, Temporal_Payload_PathCollapse_1){
  details::tier2::XreateManager* man = details::tier2::XreateManager::prepare(
    R"Code(
      import raw("scripts/cfa/payload.lp").
      b = function::int {d()}
      c = function::int {d()}
      d = function::int {0}
      a = function:: int {
        seq {context:: test. 0} {b() + c()}
      }
    )Code");

  string script = 
  R"Code(
    cfa_payload(P, scope(Scope), ()):-
      bind_scope(Scope, P, strong).
  )Code";
  
  CFATemporalSeqPass* pass = new CFATemporalSeqPass(man);
  man->registerPass(pass, PassId::CFATemporalSeqPass);
  man->registerPass(new CFAPass(man), PassId::CFAPass);
  man->executePasses();
  const TemporalSeqGraph* report = dynamic_cast<CFATemporalSeqPass*> (
    man->getPassById(PassId::CFATemporalSeqPass))->getReport();
  
  man->transcend->addRawScript(move(script));
  testing::internal::CaptureStdout();
  man->analyse();
  std::string outputActual = testing::internal::GetCapturedStdout();
  cout << outputActual << endl;

  string outputExpected = "cfa_payload(test,scope(2),())";
  ASSERT_NE(std::string::npos, outputActual.find(outputExpected));
  delete pass;
}