#include "leak_detector.h"

#include <set>
#include <map>
#include <string>
#include <vector>

#include "errors.h"
#include "utils.h"
#include "graph_loops_func.h"
#include "graph_calls.h"
#include "graph_calls_func.h"
#include "directive_parser.h"
#include "../Distribution/DvmhDirective_func.h"
#include "SgUtils.h"
#include "expr_transform.h"

#include "CFGraph/CFGraph.h"

#include "shadow.h"
#include "dvm.h"

using std::set;
using std::map;
using std::string;
using std::vector;
using std::pair;
using std::make_pair;
using SAPFOR::CFG_Settings;

extern int debSh;

static void findShadowAndRemote(SgExpression *spec, SgExpression *&shadow, SgExpression *&remote, SgExpression *&beforeSh)
{
    remote = shadow = NULL;
    beforeSh = spec;

    for (auto iter = spec, iterB = spec; iter; iter = iter->rhs())
    {
        if (iter->lhs()->variant() == SHADOW_RENEW_OP)
        {
            beforeSh = iterB;
            shadow = iter->lhs();
        }
        else if (iter->lhs()->variant() == REMOTE_ACCESS_OP)
            remote = iter->lhs();

        if (iterB != iter)
            iterB = iterB->rhs();
    }
}

static set<string> getAllRemoteWithDDOT(SgExpression *remote)
{
    set<string> allRemoteWithDDOT;
    for (auto iter = remote->lhs(); iter; iter = iter->rhs())
    {
        SgExpression *elem = iter->lhs();
        if (elem->variant() == ARRAY_REF)
        {
            bool allDDOT = true;
            for (auto iterL = elem->lhs(); iterL; iterL = iterL->rhs())
                if (iterL->lhs()->variant() != DDOT)
                    allDDOT = false;

            if (allDDOT)
                allRemoteWithDDOT.insert(elem->symbol()->identifier());
        }
    }

    return allRemoteWithDDOT;
}

static DIST::Array* getArrayFromAttribute(SgExpression *elem)
{
    DIST::Array *currArray = NULL;
    for (int i = 0; i < elem->numberOfAttributes() && currArray == NULL; ++i)
        if (elem->attributeType(i) == ARRAY_REF)
            currArray = (DIST::Array *)(elem->getAttribute(i)->getAttributeData());

    if (currArray == NULL)
        printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

    return currArray;
}


static vector<pair<int, int>> fillShadowSpec(SgExpression *elem)
{
    vector<pair<int, int>> toDel;
    for (SgExpression *list = elem->lhs(); list; list = list->rhs())
        toDel.push_back(make_pair(list->lhs()->lhs()->valueInteger(), list->lhs()->rhs()->valueInteger()));

    return toDel;
}

static void devourShadow(SgExpression *spec, SgStatement *stat)
{
    if (spec)
    {
        SgExpression *shadow, *remote, *beforeSh;
        findShadowAndRemote(spec, shadow, remote, beforeSh);
        
        if (shadow && remote)
        {
            const set<string> allRemoteWithDDOT = getAllRemoteWithDDOT(remote);

            auto currShadowP = shadow;
            int numActiveSh = 0;

            for (auto iter = shadow->lhs(); iter; iter = iter->rhs())
            {
                SgExpression *elem = iter->lhs();
                //if shadow has CORNER
                if (elem->variant() == ARRAY_OP)
                    elem = elem->lhs();

                if (elem->variant() == ARRAY_REF)
                {
                    if (allRemoteWithDDOT.find(elem->symbol()->identifier()) != allRemoteWithDDOT.end())
                    {
                        DIST::Array *currArray = getArrayFromAttribute(elem);
                        vector<pair<int, int>> toDel = fillShadowSpec(elem); 

                        currArray->RemoveShadowSpec(toDel);
                        if (currShadowP == shadow)
                            shadow->setLhs(iter->rhs());
                        else
                            currShadowP->setRhs(iter->rhs());
                    }
                    else
                    {
                        ++numActiveSh;
                        if (currShadowP == shadow)
                            currShadowP = shadow->lhs();
                        else
                            currShadowP = currShadowP->rhs();
                    }
                }
            }

            //remove shadow dir
            if (numActiveSh == 0)
            {
                if (spec->lhs()->variant() == SHADOW_RENEW_OP)
                    stat->setExpression(1, *(spec->rhs()));
                else
                    beforeSh->setRhs(beforeSh->rhs()->rhs());
            }
        }
    }
}

extern vector<bool> isSimpleRef(SgStatement* stS, SgStatement* stE, SgExpression* subs, const set<int> noSimpleVars, const map<string, FuncInfo*>& funcMap, const set<string>& usedVars);
static void convertShadowToDDOTRemote(SgExpression *spec, const LoopGraph* loop, const map<string, FuncInfo*>& funcMap)
{
    //TODO: need to optimize
    //set<string> loopVars(loop->directive->parallel.begin(), loop->directive->parallel.end());
    //vector<bool> isSimple = isSimpleRef(loop->loop->GetOriginal(), loop->loop->GetOriginal()->lastNodeOfStmt(), spec, { ARRAY_OP, ARRAY_REF, VAR_REF }, funcMap, loopVars);

    while (spec)
    {
        spec->setLhs(new SgExpression(DDOT));
        spec = spec->rhs();
    }
}

extern int maxShadowWidth;
extern SgExpression* remoteAggregation(SgExpression* remote, const vector<SgExpression*>* newRemotes);
static void replaceShadowByRemote(SgExpression *specInDir, SgStatement *stat,
                                  const map<DIST::Array*, set<DIST::Array*>> &arrayLinksByFuncCalls, 
                                  const LoopGraph* loop, const map<string, FuncInfo*>& funcMap)
{
    if (specInDir)
    {
        SgExpression *shadow, *remote, *beforeSh;
        findShadowAndRemote(specInDir, shadow, remote, beforeSh);

        if (shadow)
        {            
            set<string> remotesNames;

            SgExpression *newRemote = NULL;
            SgExpression *pRem = NULL;

            map<pair<string, string>, Expression*> remotes;

            if (!remote)
                pRem = newRemote = new SgExpression(REMOTE_ACCESS_OP);
            else
            {
                fillRemoteFromComment(new Statement(stat), remotes, true, DVM_PARALLEL_ON_DIR);
                for (auto &elem : remotes)
                    remotesNames.insert(elem.first.first);
                pRem = remote;
            }
            
            bool remoteWasAdded = false;
            auto currShadowP = shadow;
            int numActiveSh = 0;

            for (auto iter = shadow->lhs(); iter; iter = iter->rhs())
            {
                SgExpression *elem = iter->lhs();
                //if shadow has CORNER
                if (elem->variant() == ARRAY_OP)
                    elem = elem->lhs();

                if (elem->variant() == ARRAY_REF)
                {
                    DIST::Array *currArray = getArrayFromAttribute(elem);
                    vector<pair<int, int>> spec = fillShadowSpec(elem);
                    

                    set<DIST::Array*> realRefs;
                    getRealArrayRefs(currArray, currArray, realRefs, arrayLinksByFuncCalls);

                    bool replaceByRemote = false;
                    for (auto &realArray : realRefs)
                    {
                        auto arraySizes = realArray->GetSizes();
                        //check sizes
                        for (auto &dim : arraySizes)
                            if (dim.first > dim.second || (dim.first == -1 && dim.second == -1))
                                printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

                        if (spec.size() != arraySizes.size())
                            printInternalError(convertFileName(__FILE__).c_str(), __LINE__);
                                                
                        for (int z = 0; z < spec.size(); ++z)
                        {
                            float maxSpec = std::max(spec[z].first, spec[z].second);
                            float dimSize = arraySizes[z].second - arraySizes[z].first + 1;

                            // 50 % by default
                            if (dimSize * (maxShadowWidth / 100.) < maxSpec)
                            {
                                replaceByRemote = true;
                                break;
                            }
                        }

                        if (replaceByRemote)
                            break;
                    }

                    if (replaceByRemote)
                    {
                        __spf_print(1, "RemoteAccess[devour]: replace shadow by remote for array '%s'\n", currArray->GetShortName().c_str());
                        currArray->RemoveShadowSpec(spec);
                        for (auto& realArray : realRefs)
                            realArray->RemoveShadowSpec(spec);

                        auto keyName = OriginalSymbol(elem->symbol())->identifier();
                        auto it = remotesNames.find(keyName);
                        if (it == remotesNames.end())
                        {
                            remotesNames.insert(it, OriginalSymbol(elem->symbol())->identifier());

                            SgExpression* toAdd = new SgExpression(EXPR_LIST);
                            toAdd->setLhs(elem);
                            toAdd->setRhs(pRem->lhs());
                            pRem->setLhs(toAdd);

                            convertShadowToDDOTRemote(elem->lhs(), loop, funcMap);
                        }
                        else 
                        {                            
                            for (auto& elem : remotes)
                                if (elem.first.first == keyName)
                                    convertShadowToDDOTRemote(elem.second->GetOriginal()->lhs(), loop, funcMap);
                        }                     

                        remoteWasAdded = true;
                        if (currShadowP == shadow)
                            shadow->setLhs(iter->rhs());
                        else
                            currShadowP->setRhs(iter->rhs());
                    }
                    else
                    {
                        ++numActiveSh;
                        if (currShadowP == shadow)
                            currShadowP = shadow->lhs();
                        else
                            currShadowP = currShadowP->rhs();
                    }
                }
            }

            //remove shadow dir
            if (numActiveSh == 0)
            {
                if (specInDir->lhs()->variant() == SHADOW_RENEW_OP)
                    stat->setExpression(1, *(specInDir->rhs()));
                else
                    beforeSh->setRhs(beforeSh->rhs()->rhs());
            }

            if (remoteWasAdded)
            {
                if (newRemote)
                    stat->setExpression(1, *new SgExpression(EXPR_LIST, newRemote, stat->expr(1), NULL));
                else                    
                    remoteAggregation(remote, NULL);
            }            
        }
    }
}

void devourShadowByRemote(void *file_, const map<string, FuncInfo*>& funcMap, const vector<LoopGraph*>& loops, 
                          const map<DIST::Array*, set<DIST::Array*>> &arrayLinksByFuncCalls)
{
    SgFile* file = static_cast<SgFile*>(file_);
    map<int, LoopGraph*> loopByLine;
    createMapLoopGraph(loops, loopByLine);

    for (SgStatement *stat = file->firstStatement(); stat; stat = stat->lexNext())
    {
        if (stat->variant() == DVM_PARALLEL_ON_DIR && stat->lineNumber() == 0) // except user dirs
        {
            devourShadow(stat->expr(1), stat);
            auto next = stat->lexNext();
            if (next->variant() != FOR_NODE)
                printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

            if (loopByLine.find(next->lineNumber()) == loopByLine.end())
                printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

            replaceShadowByRemote(stat->expr(1), stat, arrayLinksByFuncCalls, loopByLine[next->lineNumber()], funcMap);
        }
    }
}

static void transformShadowIfFull(SgExpression* shadowList, const map<DIST::Array*, set<DIST::Array*>>& arrayLinksByFuncCalls)
{
    if (shadowList)
    {
        for (auto iter = shadowList->lhs(); iter; iter = iter->rhs())
        {
            SgExpression* elem = iter->lhs();

            //if shadow has CORNER
            if (elem->variant() == ARRAY_OP)
                elem = elem->lhs();

            if (elem->variant() == ARRAY_REF)
            {
                DIST::Array* currArray = NULL;
                for (int i = 0; i < elem->numberOfAttributes() && currArray == NULL; ++i)
                    if (elem->attributeType(i) == ARRAY_REF)
                        currArray = (DIST::Array*)(elem->getAttribute(i)->getAttributeData());

                if (currArray == NULL)
                    printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

                set<DIST::Array*> arrays;
                getRealArrayRefs(currArray, currArray, arrays, arrayLinksByFuncCalls);

                if (arrays.size() == 0)
                    printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

                auto shadowSpec = (*arrays.begin())->GetShadowSpec();
                if (shadowSpec.size() != currArray->GetDimSize())
                    printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

                SgArrayRefExp* ref = (SgArrayRefExp*)elem;
                if (ref->numberOfSubscripts() != currArray->GetDimSize())
                    printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

                bool eq = true;
                for (int i = 0; i < currArray->GetDimSize(); ++i)
                {
                    SgExpression* subs = ref->subscript(i);
                    if (subs->variant() == DDOT)
                    {
                        if (subs->lhs()->isInteger() && subs->rhs()->isInteger())
                        {
                            if (subs->lhs()->valueInteger() != shadowSpec[i].first ||
                                subs->rhs()->valueInteger() != shadowSpec[i].second)
                            {
                                eq = false;
                                break;
                            }
                        }
                        else
                        {
                            eq = false;
                            break;
                        }
                    }
                    else
                    {
                        eq = false;
                        break;
                    }
                }

                //remove
                if (eq)
                    elem->setLhs(NULL);
            }
        }
    }
}

void transformShadowIfFull(void *file_, const map<DIST::Array*, set<DIST::Array*>> &arrayLinksByFuncCalls)
{
    SgFile* file = static_cast<SgFile*>(file_);
    for (SgStatement *first = file->firstStatement(); first; first = first->lexNext())
    {
        if (first->variant() == DVM_PARALLEL_ON_DIR && first->lineNumber() == 0) // except user dirs
        {
            SgExpression *spec = first->expr(1);
            if (spec)
            {
                SgExpression *shadowList = NULL;
                for (auto iter = spec; iter; iter = iter->rhs())
                {
                    if (iter->lhs()->variant() == SHADOW_RENEW_OP)
                    {
                        shadowList = iter->lhs();
                        break;
                    }
                }
                transformShadowIfFull(shadowList, arrayLinksByFuncCalls);
            }
        }
    }
}

static void addRealArraysRef(set<DIST::Array*>& writesTo, DIST::Array* array,
                             const map<DIST::Array*, set<DIST::Array*>>& arrayLinksByFuncCalls)
{
    set<DIST::Array*> realRef;
    getRealArrayRefs(array, array, realRef, arrayLinksByFuncCalls);

    for (auto& realR : realRef)
        writesTo.insert(realR);
}

static void findFuncCalls(SgExpression* ex, vector<NextNode>& next, 
                          const map<void*, ShadowNode*>& allShadowNodes,
                          const set<DIST::Array*>& writesTo)
{
    if (ex)
    {
        if (ex->variant() == FUNC_CALL)
        {
            if (isIntrinsicFunctionName(ex->symbol()->identifier()) == false)
                if (allShadowNodes.find(ex) != allShadowNodes.end())
                    next.push_back(NextNode(allShadowNodes.find(ex)->second, writesTo));
        }

        findFuncCalls(ex->lhs(), next, allShadowNodes, writesTo);
        findFuncCalls(ex->rhs(), next, allShadowNodes, writesTo);
    }
}

static void viewGraph(CBasicBlock *first)
{
    auto nexts = first->getSucc();
    auto items = first->getStart();
    auto itemsEnd = first->getEnd();
    while (items != itemsEnd)
    {
        auto stmt = items->getStatement();
        if (stmt)
            printf("%d tag %s\n", stmt->lineNumber(), tag[stmt->variant()]);

        stmt = items->getOriginalStatement();
        if (stmt)
            printf("ORIG %d %s\n", stmt->lineNumber(), tag[stmt->variant()]);

        items = items->getNext();
    }
    
    while (nexts)
    {
        viewGraph(nexts->block);
        nexts = nexts->next;
    }
}

static vector<PrevNode> getPrev(ShadowNode* curr, const set<ShadowNode*>& allNodes)
{
    vector<PrevNode> retVal;

    for (auto& node : allNodes)
        for (auto& next : node->next)
            if (next.shNode == curr)
                retVal.push_back(PrevNode(node, &next.isBackWard, &next.hasRealigns));

    return retVal;
}

static vector<PrevNode> getPrev(ShadowNode* curr, const map<string, vector<FuncInfo*>>& allFuncs)
{
    vector<PrevNode> retVal;

    for (auto& funcByFile : allFuncs)
        for (auto& currF : funcByFile.second)
            for (auto& node : currF->allShadowNodes)
                    for (auto& next : node.second->next)
                        if (next.shNode == curr)
                            retVal.push_back(PrevNode(node.second, &next.isBackWard, &next.hasRealigns));
    
    return retVal;
}

static bool isMoveValid(ShadowNode* moveTo, DIST::Array* array, const set<ShadowNode*>& allShadowNodes)
{
    if (array->GetLocation().first == DIST::l_MODULE)
    {
        auto func = moveTo->location.first->funcPointer;

        bool checkOk = true;
        try {
            array->GetNameInLocationS(moveTo->location.first->funcPointer);
        }
        catch (...) {
            checkOk = false;
        }

        if (!checkOk)
            return false;
    }

    //check added
    for (auto& elem : moveTo->newShadows)
        if (elem.first == array)
            return true;

    set<ShadowNode*> touched = { moveTo };
    set<ShadowNode*> next = { moveTo };

    while (next.size())
    {
        set<ShadowNode*> newNext;

        for (auto& node : next)
        {
            touched.insert(node);

            bool skip = false;
            for (auto& elem : node->shadows)
                if (elem.first == array)
                    skip = true;
            if (skip)
                continue;

            for (auto& edge : node->next)
            {
                if (edge.writeTo.find(array) != edge.writeTo.end())
                    return false;

                if (touched.find(edge.shNode) == touched.end() &&
                    next.find(edge.shNode) == next.end())
                {
                    newNext.insert(edge.shNode);
                }
            }
        }
        next = newNext;
    }
    return true;
}

extern int keepFiles;
static int groupingShadowNodes(set<ShadowNode*>& allShadowNodes, const map<string, vector<FuncInfo*>>& allFuncs,
                               const map<DIST::Array*, set<DIST::Array*>>& arrayLinksByFuncCalls)
{    
    int moveCount = 0;
    bool changes = true;
    while (changes)
    {
        //printf("iter\n");
        changes = false;
        for (auto& shadow : allShadowNodes)
        {
            ShadowNode* currNode = shadow;
            if (currNode->type != PARALLEL_DIR)
                continue;

            vector<set<DIST::Array*>> writes;
            auto prevForCurr = getPrev(currNode, allFuncs);
            for (auto& prev : prevForCurr)
            {
                for (int i = 0; i < prev.shNode->next.size(); ++i)
                {
                    if (prev.shNode->next[i].shNode == currNode)
                    {
                        writes.push_back(prev.shNode->next[i].writeTo);
                        break;
                    }
                }
            }
            if (writes.size() != prevForCurr.size())
                printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

            map<DIST::Array*, vector<ShadowElement>> rewriteSh;
            for (auto& sh : currNode->newShadows)
            {
                set<DIST::Array*> allRefs;
                getRealArrayRefs(sh.first, sh.first, allRefs, arrayLinksByFuncCalls);

                bool isMoved = false;
                for (int z = 0; z < prevForCurr.size(); ++z)
                {
                    auto& prev = prevForCurr[z];

                    //check writes to arrays from shadow
                    bool found = false;
                    for (auto& array : writes[z])
                        if (allRefs.find(array) != allRefs.end())
                            found = true;

                    //check writes to all arrays from init shadow
                    if (currNode->shadows.find(sh.first) != currNode->shadows.end())                    
                    {
                        for (int k = 0; k < writes.size() && !found; ++k)
                            for (auto& array : writes[k])
                                if (allRefs.find(array) != allRefs.end())
                                    found = true;
                    }

                    if (found)
                        continue;
                    //dont move back
                    if ((prev.isBackWard && *prev.isBackWard) ||
                        (prev.hasRealigns && *prev.hasRealigns) ||
                        (prev.shNode->type == START) ||
                        sh.first->GetLocation().first == DIST::l_PARAMETER && currNode->location.first != prev.shNode->location.first)
                    {
                        continue;
                    }

                    if (isMoveValid(prev.shNode, sh.first, allShadowNodes))
                    {
                        isMoved = true;
                        prev.shNode->MoveShadow(sh);
                        //printf("move from %d to %d %s\n", currNode->location.second, prev.shNode->location.second, sh.first->GetName().c_str());

                        changes = true;
                        ++moveCount;
                    }
                }

                if (!isMoved)
                {
                    //printf(" **add to re ELSE (%d)\n", currNode->location.second);
                    rewriteSh[sh.first] = sh.second;
                }
            }
            currNode->newShadows = rewriteSh;
            //if (rewriteSh.size())
            //    printf("set to sh %d - newSh %d\n", currNode->location.second, rewriteSh.size());
        }

        /*for (auto& sh : allShadowNodes)
        {
            if (sh->newShadows.size())
                printf("sh %d has %d newSh\n", sh->location.second, sh->newShadows.size());
        }*/
    }

    if (moveCount)
        for (auto& shadow : allShadowNodes)
            shadow->MergeNewShadowElements();

    return moveCount;
}

static bool checkCycleInGraph(ShadowNode* startFrom, const map<ShadowNode*, int>& shadowNum)
{
    bool isCycle = false;
    set<ShadowNode*> q = { startFrom };
    bool change = true;
    while (change)
    {
        change = false;
        

        for (auto& sh : q)
        {
            for (auto& next : sh->next)
            {
                auto nextNode = next.shNode;
                if (nextNode == startFrom)
                {
                    isCycle = true;
                    break;
                }

                if (q.find(nextNode) == q.end())
                {
                    auto it = shadowNum.find(nextNode);
                    if (it->second == 1 && !next.isBackWard)
                    {
                        q.insert(nextNode);
                        change = true;
                    }
                }
            }
        }
    }

    return isCycle;
}

static void detectBackWard(const map<string, FuncInfo*>& mapF, const set<ShadowNode*>& allNodes)
{
    set<ShadowNode*> done;

    map<ShadowNode*, int> shadowNum;
    int z = 0;
    for (auto& elem : allNodes)
        shadowNum[elem] = 0;

    FuncInfo* curr = NULL;
    for (auto& elem : mapF)
        if (elem.second->isMain)
            curr = elem.second;
    checkNull(curr, convertFileName(__FILE__).c_str(), __LINE__);

    set<ShadowNode*> q = { curr->shadowTreeStart };

    while (done.size() != allNodes.size())
    {
        if (q.size() == 0)
        {
            for (auto& elem : allNodes)
            {
                if (done.find(elem) == done.end())
                {
                    if (getPrev(elem, allNodes).size() == 0 && shadowNum[elem] == 0)
                    {
                        q.insert(elem);
                        break;
                    }
                }
            }
        }

        if (q.size() == 0)
            printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

        set<ShadowNode*> nextQ;
        for (auto& elem : q)
        {
            shadowNum[elem] = 1;
            done.insert(elem);

            for (auto& next : elem->next)
            {
                auto& nextNode = next.shNode;
                if (shadowNum[nextNode] == 0)
                    nextQ.insert(nextNode);
                else
                {
                    if (checkCycleInGraph(nextNode, shadowNum))
                        next.isBackWard = true;
                }
            }
        }
        q = nextQ;
    }
}

static void replacingShadowNodes(FuncInfo* currF)
{
    for (auto& shadow : currF->allShadowNodes)
    {
        SgStatement* parallelDir = (SgStatement*)shadow.first;
        if (shadow.second->newShadows != shadow.second->shadows &&
            parallelDir->variant() == DVM_PARALLEL_ON_DIR)
        {
            SgExpression* dirExp = parallelDir->expr(1);
            if (shadow.second->shadows.size() != 0 && shadow.second->newShadows.size() == 0) //remove shadows
            {
                vector<SgExpression*> newList;
                while (dirExp)
                {
                    if (dirExp->lhs()->variant() != SHADOW_RENEW_OP)
                        newList.push_back(dirExp->lhs());
                    dirExp = dirExp->rhs();
                }
                parallelDir->setExpression(1, *makeExprList(newList));
            }
            else if (shadow.second->newShadows.size() != 0)
            {
                vector<SgExpression*> shadowList;
                for (auto& currSh : shadow.second->newShadows)
                {
                    DIST::Array* currArray = currSh.first;
                    if (currSh.second.size() == 0)
                        continue;

                    const ShadowElement& currElement = currSh.second[0];
                    SgSymbol* s = (SgSymbol*)currArray->GetNameInLocationS(currF->funcPointer);

                    //TODO: if moved from other file
                    /*auto itTmp = currElement.origNameByProc.find(currF);
                    if (itTmp == currElement.origNameByProc.end())
                        printInternalError(convertFileName(__FILE__).c_str(), __LINE__);
                    const string nameInProc = itTmp->second;*/
                    const string nameInProc = s->identifier();

                    SgArrayRefExp* newArrayRef = NULL;
                    if (nameInProc == s->identifier())
                        newArrayRef = new SgArrayRefExp(*s);
                    else
                        newArrayRef = new SgArrayRefExp(*findSymbolOrCreate(current_file, nameInProc));
                    newArrayRef->addAttribute(ARRAY_REF, currArray, sizeof(DIST::Array));

                    auto zeroShifts = currElement.bounds;
                    std::fill(zeroShifts.begin(), zeroShifts.end(), make_pair(0, 0));

                    for (auto& elem : genSubscripts(currElement.bounds, zeroShifts))
                        newArrayRef->addSubscript(*elem);

                    SgExpression* p = NULL;
                    if (currElement.corner)
                    {
                        SgKeywordValExp* tmp1 = new SgKeywordValExp("CORNER");
                        p = new SgExpression(ARRAY_OP, newArrayRef, tmp1, NULL);
                    }
                    else
                        p = newArrayRef;
                    shadowList.push_back(p);
                }

                bool changed = false;
                while (dirExp)
                {
                    if (dirExp->lhs()->variant() == SHADOW_RENEW_OP)
                    {
                        dirExp->lhs()->setLhs(*makeExprList(shadowList));

                        changed = true;
                        break;
                    }
                    dirExp = dirExp->rhs();
                }

                if (!changed)
                {
                    SgExpression* newList = new SgExpression(SHADOW_RENEW_OP);
                    newList->setLhs(*makeExprList(shadowList));

                    SgExpression* dirExp = parallelDir->expr(1);
                    SgExpression* tmp = new SgExpression(EXPR_LIST);
                    tmp->setLhs(newList);
                    tmp->setRhs(dirExp);
                    dirExp = tmp;

                    parallelDir->setExpression(1, *dirExp);
                }
            }
        }
    }
}

static void fillShadowAcrossFromParallel(FuncInfo* currF, ShadowNode* newShNode, SgStatement* st, const DIST::Arrays<int>& allArrays)
{
    vector<pair<pair<string, string>, vector<pair<int, int>>>> shadows;
    set<string> corners;

    fillShadowAcrossFromParallel(SHADOW_RENEW_OP, new Statement(st), shadows, corners);

    for (auto& shadow : shadows)
    {
        DIST::Array* array = allArrays.GetArrayByName(shadow.first.second);
        
        string nameInFunc = shadow.first.first;
        auto isCorner = (corners.find(nameInFunc) != corners.end());
        newShNode->shadows[array].push_back(ShadowElement(shadow.second, make_pair(currF, nameInFunc), isCorner));
    }
}

static string nameWithContains(SgStatement* where, SgSymbol* s, const map<string, map<int, set<string>>>& mapCallsF)
{
    if (isSgProgHedrStmt(where))
        return isSgProgHedrStmt(where)->nameWithContains();

    const char* callName = s->identifier();
    auto itF = mapCallsF.find(where->fileName());
    if (itF == mapCallsF.end())
        printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

    auto itL = itF->second.find(where->lineNumber());
    if (itL == itF->second.end())
        printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

    for (auto& call : itL->second)
        if (call.find(callName) != string::npos)
            return call;

    if (IS_BY_USE(s))
    {
        // check in module use
        const map<string, set<SgSymbol*>> byUseInFunc = moduleRefsByUseInFunction(where);

        for (auto& byUse : byUseInFunc)
        {
            bool contains = false;
            for (auto& alt : byUse.second)
                if (alt->identifier() == callName)
                    contains = true;

            if (contains)
            {
                callName = byUse.first.c_str();
                break;
            }
        }

        for (auto& call : itL->second)
            if (call.find(callName) != string::npos)
                return call;
    }

    printInternalError(convertFileName(__FILE__).c_str(), __LINE__);
    return "";
}

static pair<string, string> getNodeName(ShadowNode* node, const map<FuncInfo*, int>& keyOfFunc, const map<string, map<int, set<string>>>& mapCallsF)
{
    int var = -1;
    string symb = "";
    if (node->type != FUNCTION_CALL)
    {
        auto st = ((SgStatement*)node->info);
        if (!st->switchToFile())
            printInternalError(convertFileName(__FILE__).c_str(), __LINE__);
        var = st->variant();
        if (st->symbol())
            symb = nameWithContains(st, st->symbol(), mapCallsF);
    }
    else
    {
        auto ex = ((SgExpression*)node->info);
        if (SgFile::switchToFile(node->location.first->fileName) == -1)
            printInternalError(convertFileName(__FILE__).c_str(), __LINE__);
        var = ex->variant();
        if (ex->symbol())
            symb = nameWithContains(SgStatement::getStatmentByExpression(ex), ex->symbol(), mapCallsF);
    }

    string ret = "F" + std::to_string(keyOfFunc.find(node->location.first)->second);
    if (node->type == START)
        ret += "_START";
    else if (node->type == PROCEDURE_CALL)
        ret += string("_call_") + symb;
    else if (node->type == FUNCTION_CALL)
        ret += string("_func_call_") + symb;
    else if (node->type == STOP)
        ret += "_STOP";
    else if (node->type == END)
        ret += "_END";
    else if (node->type != PARALLEL_DIR)
        ret += "_ERROR";

    ret += "_" + std::to_string(node->location.second);
    
    string color = "";
    if (node->shadows.size())
    {
        color += " [color=\"green\"]";

        vector<string> sh;
        for (auto& elem : node->shadows)
            sh.push_back(elem.first->GetShortName());

        for (int z = 0; z < sh.size(); ++z)
        {
            if (z == 0)
                ret += " (";
            ret += sh[z];
            if (z != sh.size() - 1)
                ret += ",";
            else
                ret += ")";
        }
    }
    return make_pair(ret, color);
}

static string getArrays(const NextNode& next)
{
    string ret = "(";
    for (auto& elem : next.writeTo)
        ret += elem->GetShortName() + ",";
    if (ret[ret.size() - 1] == ',')
        ret[ret.size() - 1] = ')';
    else
        ret += ")";
    return ret == "()" ? "" : ret;
}

static string buildGraphVizNext(FuncInfo* currF, const map<FuncInfo*, int>& keyOfFunc, set<pair<string, string>>& nodes, const map<string, map<int, set<string>>>& mapCallsF)
{
    string graph = "";

    vector<string> lines;
    for (auto& elem : currF->allShadowNodes)
    {
        if (elem.second->location.first != currF)
            continue;

        pair<string, string> curr = getNodeName(elem.second, keyOfFunc, mapCallsF);
        nodes.insert(curr);
        for (auto& next : elem.second->next)
        {
            pair<string, string> nextNode = getNodeName(next.shNode, keyOfFunc, mapCallsF);
            if (next.shNode->location.first == elem.second->location.first)
                nodes.insert(nextNode);
            string line = "\"" + curr.first + "\"->\"" + getNodeName(next.shNode, keyOfFunc, mapCallsF).first + "\"";
            string label = getArrays(next);

            line += "[";
            if (next.isBackWard)
                line += "color=red,penwidth=3.0,";
            if (next.hasRealigns)
                line += "style=dotted,";
            line += "label=\"" + label + "\"]";

            line += "\n";
            lines.push_back(line);
        }
    }

    for (auto& line : lines)
        graph += line;
    return graph;
}

static string buildGraphVizPrev(FuncInfo* currF, const map<FuncInfo*, int>& keyOfFunc, const map<string, vector<FuncInfo*>>& allFuncs, 
                                const map<string, map<int, set<string>>>& mapCallsF)
{
    string graph = "";

    vector<string> lines;
    for (auto& elem : currF->allShadowNodes)
    {
        if (elem.second->location.first != currF)
            continue;

        string curr = getNodeName(elem.second, keyOfFunc, mapCallsF).first;
        auto prevList = getPrev(elem.second, allFuncs);
        for (auto& prev : prevList)
        {
            string nextPrev = getNodeName(prev.shNode, keyOfFunc, mapCallsF).first;
            string line = "\"" + curr + "\"->" + "\"" + getNodeName(prev.shNode, keyOfFunc, mapCallsF).first + "\"";
            line += "[color=green,penwidth=0.5]\n";
            lines.push_back(line);
        }
    }

    for (auto& line : lines)
        graph += line;
    return graph;
}

static void buildGraphViz(const map<string, vector<FuncInfo*>>& allFuncs, const map<string, map<int, set<string>>>& mapCallsF, 
                          string fileOut = "", bool withPrev = false)
{
    string graph = "digraph G{\n";
    map<FuncInfo*, int> keyOfFunc;
    int key = 0;
    for (auto& funcByFile : allFuncs)
        for (auto& currF : funcByFile.second)
            keyOfFunc[currF] = key++;

    for (auto& funcByFile : allFuncs)
    {
        for (auto& currF : funcByFile.second)
        {
            set<pair<string, string>> nodes;
            string lines = buildGraphVizNext(currF, keyOfFunc, nodes, mapCallsF);
            if (nodes.size())
            {
                graph += lines;
                graph += "subgraph \"cluster_" + currF->funcName + ":" + currF->fileName + "\"{\n";
                for (auto& node : nodes)
                    graph += "\"" + node.first + "\"" + node.second + "\n";
                graph += "label=\"" + currF->funcName + ":" + currF->fileName + "\"\n";
                graph += "}\n";
            }
            if (withPrev)
                graph += buildGraphVizPrev(currF, keyOfFunc, allFuncs, mapCallsF);

        }
    }
    graph += "}\n";

    if (fileOut == "out")
        printf("%s\n", graph.c_str());
    else
    {
        FILE* f = NULL;

        if (fileOut == "")
            f = fopen("_shadowNodes.txt", "w");
        else
            f = fopen(fileOut.c_str(), "w");

        if (f == NULL)
            printInternalError(convertFileName(__FILE__).c_str(), __LINE__);
        fwrite(graph.c_str(), sizeof(char), graph.size(), f);
        fclose(f);
    }

    fflush(NULL);
}

static void removeUnusedEdges(const map<string, vector<FuncInfo*>>& allFuncs)
{
    //remove loops
    for (auto& funcByFile : allFuncs)
    {
        for (auto& currF : funcByFile.second)
        {
            for (auto& elem : currF->allShadowNodes)
            {
                ShadowNode* node = elem.second;
                vector<NextNode> newNext;
                set<DIST::Array*> writesToTheSame;
                bool hasTheSame = false;
                for (auto& next : node->next)
                {
                    if (next.shNode == node)
                    {
                        hasTheSame = true;
                        writesToTheSame.insert(next.writeTo.begin(), next.writeTo.end());
                    }
                }

                for (auto& next : node->next)
                {
                    if (next.shNode != node)
                    {
                        newNext.push_back(next);
                        newNext.back().writeTo.insert(writesToTheSame.begin(), writesToTheSame.end());
                    }
                }

                if (hasTheSame)
                {
                    node->next = newNext;
                    /*vector<PrevNode> newPrev;
                    for (auto& prev : node->prev)
                        if (prev.shNode != node)
                            newPrev.push_back(prev);

                    if (node->prev.size() == newPrev.size())
                        printInternalError(convertFileName(__FILE__).c_str(), __LINE__);
                    node->prev = newPrev;*/
                }
            }
        }
    }

    //remove same edges
    bool changed = false;
    for (auto& funcByFile : allFuncs)
    {
        for (auto& currF : funcByFile.second)
        {
            for (auto& elem : currF->allShadowNodes)
            {
                ShadowNode* node = elem.second;

                map<ShadowNode*, NextNode> newNext;
                for (auto& next : node->next)
                {
                    auto it = newNext.find(next.shNode);
                    if (it == newNext.end())
                        newNext[next.shNode] = next;
                    else // TODO: check this - may be unite realigns?
                        it->second.writeTo.insert(next.writeTo.begin(), next.writeTo.end());
                }

                if (newNext.size() != node->next.size())
                {
                    changed = true;
                    node->next.clear();
                    for (auto& elem : newNext)
                        node->next.push_back(elem.second);
                }
            }
        }
    }
}

static map<void*, ShadowNode*> allocated;
void clearAllocatedShadowNodes()
{
    for (auto& elem : allocated)
        delete elem.second;
    allocated.clear();
}

static void removeNode(ShadowNode* node, const map<string, vector<FuncInfo*>>& allFuncs)
{
    for (auto& prev : getPrev(node, allFuncs))
    {
        for (auto& needed : prev.shNode->next)
        {
            if (needed.shNode == node)
            {
                vector<NextNode> newEdges;
                for (auto& next : node->next)
                {
                    if (next.shNode == node)
                        continue;

                    NextNode tmp = needed;
                    tmp.isBackWard |= next.isBackWard;
                    tmp.hasRealigns |= next.hasRealigns;
                    tmp.writeTo.insert(next.writeTo.begin(), next.writeTo.end());
                    tmp.shNode = next.shNode;

                    newEdges.push_back(tmp);
                }

                if (newEdges.size())
                {
                    needed = newEdges[0];
                    for (int z = 1; z < newEdges.size(); ++z)
                        prev.shNode->next.push_back(newEdges[z]);
                }
                break;
            }
        }
    }
}

static void removeStartEnd(ShadowNode* node, FuncInfo* currF, const map<string, vector<FuncInfo*>>& allFuncs)
{
    removeNode(node, allFuncs);
    for (auto& elem : currF->allShadowNodes)
    {
        if (elem.second == node)
        {
            currF->allShadowNodes.erase(elem.first);
            break;
        }
    }
}

static void removeStartEnd(const map<string, vector<FuncInfo*>>& allFuncs)
{
    set<ShadowNode*> toDel;
    for (auto& funcByFile : allFuncs)
    {
        for (auto& currF : funcByFile.second)
        {
            auto nodeS = currF->shadowTreeStart;
            auto nodeE = currF->shadowTreeEnd;

            if (nodeS && getPrev(nodeS, allFuncs).size())
            {
                removeStartEnd(nodeS, currF, allFuncs);
                toDel.insert(currF->shadowTreeStart);
                currF->shadowTreeStart = NULL;
            }

            if (nodeE && nodeE->next.size())
            {
                removeStartEnd(nodeE, currF, allFuncs);
                toDel.insert(currF->shadowTreeEnd);
                currF->shadowTreeEnd = NULL;
            }
        }
    }

    for (auto& del : toDel)
        delete del;
}

static void removeStopStats(const map<string, vector<FuncInfo*>>& allFuncs)
{
    for (auto& funcByFile : allFuncs)
    {
        for (auto& currF : funcByFile.second)
        {
            map<void*, ShadowNode*> newNodes;
            set<ShadowNode*> toDel;
            for (auto& elem : currF->allShadowNodes)
            {
                ShadowNode* node = elem.second;
                if (node->type == STOP)
                {
                    toDel.insert(node);
                    removeNode(node, allFuncs);
                }
                else
                    newNodes[elem.first] = elem.second;
            }

            if (toDel.size())
                currF->allShadowNodes = newNodes;
        }
    }
}

//TODO: what about realigns?
static bool isFunctionEmpty(FuncInfo* func)
{
    if (func->shadowTreeStart &&
        func->shadowTreeStart->next.size() == 1 &&
        func->shadowTreeStart->next[0].shNode == func->shadowTreeEnd)
        return true;
    else
        return false;
}

static bool isFunctionEmptyWithWrites(FuncInfo* func)
{
    if (func->shadowTreeStart &&
        func->shadowTreeStart->next.size() == 1 &&
        func->shadowTreeStart->next[0].shNode == func->shadowTreeEnd &&
        func->shadowTreeStart->next[0].writeTo.size() == 0)
        return true;
    else
        return false;
}

static void replaceCalls(const map<string, vector<FuncInfo*>>& allFuncs, const map<string, FuncInfo*>& mapF, const map<string, map<int, set<string>>>& mapCallsF)
{
    set<ShadowNode*> toDel;

    for (auto& funcByFile : allFuncs)
    {
        if (SgFile::switchToFile(funcByFile.first) == -1)
            printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

        for (auto& currF : funcByFile.second)
        {
            map<void*, ShadowNode*> newNodes;
            for (auto& nodePair : currF->allShadowNodes)
            {
                ShadowNode* node = nodePair.second;
                if (node->type == PROCEDURE_CALL || node->type == FUNCTION_CALL)
                {
                    string callName = "no";
                    if (node->type == PROCEDURE_CALL)
                    {
                        auto st = ((SgStatement*)node->info);
                        callName = nameWithContains(st, st->symbol(), mapCallsF);
                    }
                    else
                    {
                        auto ex = ((SgExpression*)node->info);
                        callName = nameWithContains(SgStatement::getStatmentByExpression(ex), ex->symbol(), mapCallsF);
                    }

                    auto it = mapF.find(callName);
                    if (it != mapF.end() && it->second->shadowTreeStart && it->second->shadowTreeEnd)
                    {
                        if (!isFunctionEmpty(it->second))
                        {
                            //for (auto& prev : node->prev)
                            //    it->second->shadowTreeStart->prev.push_back(prev);
                            for (auto& prev : getPrev(node, allFuncs))
                                for (auto& next : prev.shNode->next)
                                    if (next.shNode == node)
                                        next.shNode = it->second->shadowTreeStart;
                            //node->prev.clear();

                            for (auto& realNext : node->next)
                                it->second->shadowTreeEnd->next.push_back(realNext);
                            /*for (auto& realNext : node->next)
                                for (auto& prev : realNext.shNode->prev)
                                    if (prev.shNode == node)
                                        prev.shNode = it->second->shadowTreeEnd;*/
                            node->next.clear();
                        }
                        else
                        {
                            for (auto& elem : node->next)
                                elem.writeTo.insert(it->second->shadowTreeStart->next[0].writeTo.begin(), it->second->shadowTreeStart->next[0].writeTo.end());
                            removeNode(node, allFuncs);
                            node->next.clear();
                        }
                        toDel.insert(node);
                    }
                    else
                        newNodes.insert(nodePair);
                }
                else
                    newNodes.insert(nodePair);
            }
            currF->allShadowNodes = newNodes;
        }
    }

    for (auto& del : toDel)
        if (del->type == START || del->type == END)
            delete del;
}



static ShadowNode* allocateNode(SgStatement *st,
                                const vector<bool>& reaching, const map<int, int>& numMap,
                                const map<FuncInfo*, vector<SAPFOR::BasicBlock*>>& cfg,
                                void* info, nodeType type, FuncInfo* func, int line,
                                map<void*, ShadowNode*>& addTo)
{
    ShadowNode* added = NULL;
    auto instBlock = getInstructionAndBlockByStatement(cfg, st);
    if (reaching[numMap.at(instBlock.second->getNumber())])
    {
        auto it = allocated.find(info);
        if (it == allocated.end())
            it = allocated.insert(it, make_pair(info, new ShadowNode(info, type, func, line)));
        added = addTo[info] = it->second;
    }
    return added;
}

static void findFuncCalls(SgStatement* currSt, SgExpression* ex, FuncInfo* currF, const map<string, FuncInfo*>& mapF, const int line, 
                          const map<string, map<int, set<string>>>& mapCallsF,
                          const vector<bool>& reaching, const map<int, int>& numMap,
                          const map<FuncInfo*, vector<SAPFOR::BasicBlock*>>& cfg)
{
    if (ex)
    {
        if (ex->variant() == FUNC_CALL)
        {
            if (isIntrinsicFunctionName(ex->symbol()->identifier()) == false)
            {
                auto it = mapF.find(nameWithContains(currSt, ex->symbol(), mapCallsF));
                if (it != mapF.end() && !isFunctionEmptyWithWrites(it->second))
                {
                    if (currF->allShadowNodes.find(ex) != currF->allShadowNodes.end())
                        printInternalError(convertFileName(__FILE__).c_str(), __LINE__);
                    else
                        allocateNode(currSt, reaching, numMap, cfg, ex, FUNCTION_CALL, currF, line, currF->allShadowNodes);
                }
            }
        }

        findFuncCalls(currSt, ex->lhs(), currF, mapF, line, mapCallsF, reaching, numMap, cfg);
        findFuncCalls(currSt, ex->rhs(), currF, mapF, line, mapCallsF, reaching, numMap, cfg);
    }
}

static bool intersectAndAdd(map<ShadowNode*, set<DIST::Array*>>& s1, const map<ShadowNode*, set<DIST::Array*>>& s2)
{
    bool retVal = false;
    
    for (auto& map2 : s2)
    {
        if (map2.first) 
        {
            if (s1.find(map2.first) == s1.end())
            {
                s1[map2.first] = map2.second;
                retVal = true;
            }
            else
            {
                bool res = intersectAndAdd(s1.at(map2.first), map2.second);
                retVal = retVal || res;
            }
        }
    }

    if (s2.find(NULL) != s2.end()) // add to all
    {
        for (auto& map1 : s1)
        {
            if (map1.first)
            {
                bool res = intersectAndAdd(map1.second, s2.at(NULL));
                retVal = retVal || res;
            }
        }
    }

    return retVal;
}

static void fillInitial(map<SAPFOR::BasicBlock*, map<ShadowNode*, set<DIST::Array*>>>& OUT_nodes,
                        map<SAPFOR::BasicBlock*, bool>& OUT_realign,
                        const vector<SAPFOR::BasicBlock*>& blocks, 
                        const map<void*, ShadowNode*>& allShadowNodes,
                        const map<DIST::Array*, set<DIST::Array*>>& arrayLinksByFuncCalls)
{
    for (auto& block : blocks)
    {
        auto& instrs = block->getInstructions();
        bool specific = false;
        for (auto& ir : instrs)
        {
            const auto oper = ir->getInstruction()->getOperation();
            SgStatement* st = ir->getInstruction()->getOperator();

            if ((oper == SAPFOR::CFG_OP::DVM_DIR && st->variant() == DVM_PARALLEL_ON_DIR) ||
                oper == SAPFOR::CFG_OP::EXIT ||
                oper == SAPFOR::CFG_OP::F_CALL)
            {
                auto it = allShadowNodes.find(st);
                if (it == allShadowNodes.end())
                {
                    if (oper == SAPFOR::CFG_OP::F_CALL)
                        it = allShadowNodes.find(ir->getInstruction()->getExpression());
                    else
                        printInternalError(convertFileName(__FILE__).c_str(), __LINE__);
                }

                if (it != allShadowNodes.end())
                    OUT_nodes[block][it->second] = set<DIST::Array*>();
                specific = true;
            }
            else if (oper == SAPFOR::CFG_OP::DVM_DIR &&
                (st->variant() == DVM_REALIGN_DIR || st->variant() == DVM_REDISTRIBUTE_DIR))
            {
                OUT_realign[block] = true;
                specific = true;
            }
        }

        if (!specific)
        {
            for (auto& ir : instrs)
            {
                auto instr = ir->getInstruction();
                if (instr->getOperation() == SAPFOR::CFG_OP::STORE)
                {
                    SgStatement* st = instr->getOperator();
                    checkNull(st, convertFileName(__FILE__).c_str(), __LINE__);

                    if (st->variant() == ASSIGN_STAT)
                    {
                        if (isArrayRef(st->expr(0)))
                        {
                            auto s = OriginalSymbol(st->expr(0)->symbol());
                            DIST::Array* array = getArrayFromDeclarated(declaratedInStmt(s), s->identifier());
                            if (array && !array->IsNotDistribute())
                            {
                                set<DIST::Array*> toAdd;
                                addRealArraysRef(toAdd, array, arrayLinksByFuncCalls);
                                OUT_nodes[block][(ShadowNode*)NULL].insert(toAdd.begin(), toAdd.end());
                            }
                        }
                    }
                }
            }
        }
    }
}

static void aggregateInformation(map<SAPFOR::BasicBlock*, map<ShadowNode*, set<DIST::Array*>>>& OUT_nodes,
                                 map<SAPFOR::BasicBlock*, bool>& OUT_realign,
                                 map<SAPFOR::BasicBlock*, map<ShadowNode*, set<DIST::Array*>>>& IN_nodes,
                                 map<SAPFOR::BasicBlock*, bool>& IN_realign,
                                 const vector<SAPFOR::BasicBlock*>& blocks,
                                 const map<void*, ShadowNode*>& allShadowNodes)
{
    bool changed = true;
    //aggregate information
    while (changed)
    {
        changed = false;
        for (auto& block : blocks)
        {
            auto& instrs = block->getInstructions();
            bool specific = false;
            for (auto& ir : instrs)
            {
                const auto oper = ir->getInstruction()->getOperation();
                SgStatement* st = ir->getInstruction()->getOperator();
                SgExpression* ex = ir->getInstruction()->getExpression();
                bool call_spec = false;

                if (oper == SAPFOR::CFG_OP::F_CALL)
                {
                    if (allShadowNodes.find(st) != allShadowNodes.end() ||
                        allShadowNodes.find(ex) != allShadowNodes.end())
                        call_spec = true;
                }


                if ((oper == SAPFOR::CFG_OP::DVM_DIR && st->variant() == DVM_PARALLEL_ON_DIR) ||
                    oper == SAPFOR::CFG_OP::EXIT ||
                    oper == SAPFOR::CFG_OP::F_CALL && call_spec)
                {
                    specific = true;
                    for (auto& prev : block->getPrev())
                    {
                        bool res = intersectAndAdd(IN_nodes[block], OUT_nodes[prev]);
                        if (IN_realign[block] == false && OUT_realign[prev])
                        {
                            IN_realign[block] = true;
                            res = true;
                        }
                        changed = changed || res;
                    }
                }
            }

            if (!specific)
            {
                for (auto& prev : block->getPrev())
                {
                    bool res = intersectAndAdd(IN_nodes[block], OUT_nodes[prev]);
                    if (IN_realign[block] == false && OUT_realign[prev])
                    {
                        IN_realign[block] = true;
                        res = true;
                    }
                    changed = changed || res;
                }

                bool res = intersectAndAdd(OUT_nodes[block], IN_nodes[block]);
                if (OUT_realign[block] == false && IN_realign[block])
                {
                    OUT_realign[block] = true;
                    res = true;
                }
                changed = changed || res;
            }
        }
    }
}

void GroupShadow(const map<string, vector<FuncInfo*>>& allFuncs,
                 const map<string, vector<LoopGraph*>>& loops,
                 const DIST::Arrays<int>& allArrays,
                 const map<DIST::Array*, set<DIST::Array*>>& arrayLinksByFuncCalls,
                 const map<string, CommonBlock*>& commonBlocks)
{
    map<string, FuncInfo*> mapF;
    map<string, map<int, set<string>>> mapCallsF;

    for (auto& byFile : allFuncs)
        for (auto& elem : byFile.second)
        {
            mapF[elem->funcName] = elem;
            for (auto& callInfo : elem->callsFromDetailed)
            {
                auto& call = callInfo.detailCallsFrom;
                mapCallsF[elem->fileName][call.second].insert(call.first);
            }
        }

    map<FuncInfo*, set<FuncInfo*>> callDeps;    
    for (auto& byFunc : mapF)
        callDeps[byFunc.second].insert(byFunc.second->callsFromV.begin(), byFunc.second->callsFromV.end());

    vector<set<FuncInfo*>> ssc;
    vector<set<FuncInfo*>> callLvls = groupByCallDependencies(callDeps, ssc);

    bool deb = (debSh == 1);
    bool withPrev = false;

    //building shadow graph
    for (auto& byLvl : callLvls)
    {
        for (auto& currF : byLvl)
        {
            map<int, LoopGraph*> mapLoops;
            auto itL = loops.find(currF->fileName);
            if (itL != loops.end())
                createMapLoopGraph(itL->second, mapLoops);

            if (SgFile::switchToFile(currF->fileName) == -1)
                printInternalError(convertFileName(__FILE__).c_str(), __LINE__);
        
            SgStatement* func = currF->funcPointer->GetOriginal();

            const auto settings = CFG_Settings({ CFG_Settings::CFG_atLeastOneIterInLoop, CFG_Settings::CFG_withDVM, 
                                                 CFG_Settings::CFG_withCallsInBlocks, CFG_Settings::CFG_withUnreachable });
            auto cfg = buildCFGforCurrentFunc(func, settings, commonBlocks, allFuncs);
            if (cfg.size() != 1)
                printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

            auto& blocks = cfg.begin()->second;

            //create reaching blocks
            vector<bool> reaching(blocks.size(), false);
            map<int, int> numMap;
            for (int z = 0; z < blocks.size(); ++z)
                numMap[blocks[z]->getNumber()] = z;

            reaching[numMap[blocks[0]->getNumber()]] = true; // first == START
            bool changed = true;
            while (changed)
            {
                changed = false;
                for (auto& block : blocks)
                {
                    if (reaching[numMap[block->getNumber()]])
                        continue;

                    bool prevReached = false;
                    for (auto& prev : block->getPrev())
                    {
                        if (reaching[numMap[prev->getNumber()]])
                        {
                            if (reaching[numMap[block->getNumber()]] == false)
                            {
                                changed = reaching[numMap[block->getNumber()]] = true;
                                break;
                            }
                        }
                    }
                }
            }

            //start node
            if (func->variant() == ENTRY_STAT)
            {
                auto cp = func->controlParent();
                currF->shadowTreeStart = currF->allShadowNodes[cp] = new ShadowNode(cp, START, currF, func->lineNumber());
                func = cp;
            }
            else
                currF->shadowTreeStart = currF->allShadowNodes[func] = new ShadowNode(func, START, currF, func->lineNumber());

            //find all NODES
            for (SgStatement* st = func; st != func->lastNodeOfStmt(); st = st->lexNext())
            {
                if (st->variant() == DVM_PARALLEL_ON_DIR)
                {
                    auto newShNode = allocateNode(st, reaching, numMap, cfg, st, PARALLEL_DIR, currF, st->lexNext()->lineNumber(), currF->allShadowNodes);
                    if (newShNode)
                        fillShadowAcrossFromParallel(currF, newShNode, st, allArrays);

                    st = st->lexNext();
                    if (st->variant() != FOR_NODE)
                    {
                        if (st->variant() != ASSIGN_STAT)
                            printInternalError(convertFileName(__FILE__).c_str(), __LINE__);
                        else
                        {
                            bool found = false;
                            for (auto& data : getAttributes<SgStatement*, SgStatement*>(st, set<int>{ ASSIGN_STAT }))
                            {
                                if (mapLoops.find(data->lineNumber()) != mapLoops.end())
                                    found = true;
                            }

                            if (!found)
                                printInternalError(convertFileName(__FILE__).c_str(), __LINE__);
                        }
                    }
                    st = st->lastNodeOfStmt();
                }
                else if (st->variant() == CONTAINS_STMT)
                    break;
                else if (st->variant() == STOP_STAT)
                    allocateNode(st, reaching, numMap, cfg, st, STOP, currF, st->lineNumber(), currF->allShadowNodes);
                else if (st->variant() == PROC_STAT)
                {
                    if (isIntrinsicFunctionName(st->symbol()->identifier()) == false)
                    {
                        auto it = mapF.find(nameWithContains(st, st->symbol(), mapCallsF));
                        if (it != mapF.end() && !isFunctionEmptyWithWrites(it->second))
                            allocateNode(st, reaching, numMap, cfg, st, PROCEDURE_CALL, currF, st->lineNumber(), currF->allShadowNodes);
                    }
                }
                else
                {
                    for (int z = 0; z < 3; ++z)
                        findFuncCalls(st, st->expr(z), currF, mapF, st->lineNumber(), mapCallsF, reaching, numMap, cfg);
                }
            }

            auto lastNode = func->lastNodeOfStmt(); // end node
            auto natureLast = currF->allShadowNodes[lastNode] = new ShadowNode(lastNode, END, currF, lastNode->lineNumber());
            currF->shadowTreeEnd = natureLast;

            map<SAPFOR::BasicBlock*, map<ShadowNode*, set<DIST::Array*>>> IN_nodes;
            map<SAPFOR::BasicBlock*, map<ShadowNode*, set<DIST::Array*>>> OUT_nodes;

            map<SAPFOR::BasicBlock*, bool> IN_realign;
            map<SAPFOR::BasicBlock*, bool> OUT_realign;

            if (blocks.size())
                IN_nodes[blocks[0]][currF->shadowTreeStart] = { };

            fillInitial(OUT_nodes, OUT_realign, blocks, currF->allShadowNodes, arrayLinksByFuncCalls);

            aggregateInformation(OUT_nodes, OUT_realign, IN_nodes, IN_realign, blocks, currF->allShadowNodes);

            // check correctness
            set<ShadowNode*> added;
            for (auto& block : IN_nodes)
                for (auto& pair : block.second)
                    if (pair.first)
                        added.insert(pair.first);
            for (auto& block : OUT_nodes)
                for (auto& pair : block.second)
                    if (pair.first)
                        added.insert(pair.first);

            //with last
            if (added.size() + 1 != currF->allShadowNodes.size())
                printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

            //set next
            for (auto& block : blocks)
            {
                auto& instrs = block->getInstructions();
                for (auto& ir : instrs)
                {
                    const auto oper = ir->getInstruction()->getOperation();
                    SgStatement* st = ir->getInstruction()->getOperator();

                    if ((oper == SAPFOR::CFG_OP::DVM_DIR && st->variant() == DVM_PARALLEL_ON_DIR) ||
                        oper == SAPFOR::CFG_OP::EXIT ||
                        oper == SAPFOR::CFG_OP::F_CALL)
                    {
                        auto it = currF->allShadowNodes.find(ir->getInstruction()->getOperator());
                        if (it == currF->allShadowNodes.end())
                        {
                            if (oper == SAPFOR::CFG_OP::F_CALL)
                                it = currF->allShadowNodes.find(ir->getInstruction()->getExpression());
                            else
                                printInternalError(convertFileName(__FILE__).c_str(), __LINE__);
                        }

                        if (it != currF->allShadowNodes.end())
                        {
                            auto in_nodes = IN_nodes[block];
                            auto in_realign = IN_realign[block];

                            for (auto& node : in_nodes)
                            {
                                if (node.first == NULL)
                                    continue;

                                node.first->addNext(NextNode(it->second, node.second, in_realign));
                            }
                        }
                    }
                }
            }

            if (blocks.size())
            {
                auto lastPair = getInstructionAndBlockByStatement(cfg, lastNode);

                auto in_nodes = IN_nodes[lastPair.second];
                auto in_realign = IN_realign[lastPair.second];

                for (auto& node : in_nodes)
                {
                    if (node.first == NULL)
                        continue;

                    node.first->addNext(NextNode(natureLast, node.second, in_realign));
                }
            }

            /*if (deb)
                dumpCFG(cfg, false);*/

            deleteCFG(cfg);
            IN_nodes.clear();
            OUT_nodes.clear();
            IN_realign.clear();
            OUT_realign.clear();

            //remove the same nodes
            for (auto& elem : currF->allShadowNodes)
            {
                set<pair<ShadowNode*, set<DIST::Array*>>> tmpl;
                for (auto& listElem : elem.second->next)
                    tmpl.insert(make_pair(listElem.shNode, listElem.writeTo));
                if (tmpl.size() != elem.second->next.size())
                {
                    elem.second->next.clear();
                    for (auto& uniq : tmpl)
                        elem.second->addNext(NextNode(uniq.first, uniq.second));
                }
            }

            //check connectivity of shadow graph
            std::queue<ShadowNode*> q;
            q.push(currF->shadowTreeStart);
            set<ShadowNode*> done;
            while (!q.empty())
            {
                ShadowNode* curr = q.front();
                //for entry
                if (curr->type == END && curr->location.first != currF)
                {
                    q.pop();
                    continue;
                }

                q.pop();
                if (done.find(curr) != done.end())
                    continue;

                done.insert(curr);

                for (auto& next : curr->next)
                    if (done.find(next.shNode) == done.end())
                        q.push(next.shNode);
            }

            if (done.size() != currF->allShadowNodes.size())
            {
                map<string, vector<FuncInfo*>> toPrint;
                toPrint[currF->fileName].push_back(currF);

                if (deb)
                    buildGraphViz(toPrint, mapCallsF, "_sh0.txt", withPrev);
                printInternalError(convertFileName(__FILE__).c_str(), __LINE__);
            }
        }
    }

    bool hasShadows = false;
    for (auto& func : mapF)
        for (auto& elem : func.second->allShadowNodes)
            if (elem.second->shadows.size())
                hasShadows = true;

    if (!hasShadows)
        return;

    if (deb)
        buildGraphViz(allFuncs, mapCallsF, "_sh0.txt", withPrev);

    removeStopStats(allFuncs);
    removeUnusedEdges(allFuncs);
    if (deb)
        buildGraphViz(allFuncs, mapCallsF, "_sh1.txt", withPrev);

    replaceCalls(allFuncs, mapF, mapCallsF);
    removeUnusedEdges(allFuncs);
    if (deb)
        buildGraphViz(allFuncs, mapCallsF, "_sh2.txt", withPrev);

    removeStartEnd(allFuncs);
    removeUnusedEdges(allFuncs);
    if (deb)
        buildGraphViz(allFuncs, mapCallsF, "_sh3.txt", withPrev);

    set<ShadowNode*> allNodes;
    for (auto& func : mapF)
        for (auto& elem : func.second->allShadowNodes)
            allNodes.insert(elem.second);

    //detect backward
    detectBackWard(mapF, allNodes);

    if (deb)
        buildGraphViz(allFuncs, mapCallsF, "_sh4.txt", withPrev);

    if (keepFiles)
        buildGraphViz(allFuncs, mapCallsF);

    // grouping
    allNodes.clear();
    for (auto& func : mapF)
        for (auto& elem : func.second->allShadowNodes)
            allNodes.insert(elem.second);

    map<FuncInfo*, int> moveCounts;
    for (auto& shadow : allNodes)
        shadow->newShadows = shadow->shadows;
    int moves = groupingShadowNodes(allNodes, allFuncs, arrayLinksByFuncCalls);
    __spf_print(1, "  shadow total moveCount %d\n", moves);

    if (moves != 0)
    {
        //replacing
        for (auto& funcByFile : allFuncs)
        {
            for (auto& currF : funcByFile.second)
            {
                if (currF->funcPointer->GetOriginal()->switchToFile() == false)
                    printInternalError(convertFileName(__FILE__).c_str(), __LINE__);
                replacingShadowNodes(currF);
            }
        }
    }

    if (deb)
    {
        for (auto& shadow : allNodes)
            shadow->shadows = shadow->newShadows;
        buildGraphViz(allFuncs, mapCallsF, "_sh5.txt", withPrev);
    }
}