#include "leak_detector.h"

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cstdint>

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

using std::vector;
using std::map;
using std::tuple;
using std::set;
using std::string;
using std::wstring;
using std::pair;
using std::make_pair;
using std::get;

#include "graph_loops.h"
#include "graph_calls_func.h"
#include "errors.h"
#include "../Distribution/Distribution.h"
#include "../Distribution/CreateDistributionDirs.h"
#include "../ParallelizationRegions/ParRegions.h"
#include "../VisualizerCalls/get_information.h"
#include "../DirectiveProcessing/directive_creator.h"

static void fillWriteReadOps(LoopGraph *&currLoop, DIST::Array *symbol, const ArrayInfo *arrayOps)
{
    auto it = currLoop->readOps.find(symbol);
    if (it == currLoop->readOps.end())
        it = currLoop->readOps.insert(it, make_pair(symbol, make_pair(vector<ArrayOp>(), vector<bool>())));

    const int dim = symbol->GetDimSize();
    it->second.first.resize(dim);
    it->second.second.resize(dim);

    for (int z = 0; z < dim; ++z)
    {
        it->second.first[z] = arrayOps->readOps[z];
        it->second.second[z] = arrayOps->unrecReadOps[z];
    }

    auto it1 = currLoop->writeOps.find(symbol);
    if (it1 == currLoop->writeOps.end())
        it1 = currLoop->writeOps.insert(it1, make_pair(symbol, vector<ArrayOp>()));
    it1->second.resize(dim);
    for (int z = 0; z < dim; ++z)
        it1->second[z] = arrayOps->writeOps[z];
}

static void uniteVectors(const ArrayOp &from, ArrayOp &to)
{
    for (auto &elemFrom : from.coefficients)
    {
        auto it = to.coefficients.find(elemFrom.first);
        if (it == to.coefficients.end())
            it = to.coefficients.insert(it, elemFrom);
        else
            it->second += elemFrom.second;
    }
}

static void uniteChildReadInfo(LoopGraph *currLoop)
{
    if (currLoop->perfectLoop > 1)
    {
        int depth = currLoop->perfectLoop;
        while (depth != 1)
        {
            LoopGraph *part1 = currLoop, *part2 = currLoop;
            for (int i = 0; i < depth - 1; ++i)
                part1 = part1->children[0];
            for (int i = 0; i < depth - 2; ++i)
                part2 = part2->children[0];

            set<DIST::Array*> newToAdd;

            for (auto it = part1->readOps.begin(); it != part1->readOps.end(); ++it)
            {
                auto it2 = part2->readOps.find(it->first);
                if (it2 == part2->readOps.end())
                    newToAdd.insert(it->first);
            }

            for (auto it = part2->readOps.begin(); it != part2->readOps.end(); ++it)
            {
                auto it2 = part1->readOps.find(it->first);
                if (it2 != part1->readOps.end())
                {
                    const vector<ArrayOp> &toAddReads = it2->second.first;
                    const vector<bool> &toAddUnrecReads = it2->second.second;
                    for (int i = 0; i < it->second.first.size(); ++i)
                        uniteVectors(toAddReads[i], it->second.first[i]);

                    for (int i = 0; i < it->second.second.size(); ++i)
                        it->second.second[i] = it->second.second[i] || toAddUnrecReads[i];
                }
            }

            for (auto &arrayMissed : newToAdd)
                part2->readOps[arrayMissed] = part1->readOps[arrayMissed];

            depth--;
        }
    }
    else
    {
        for (int i = 0; i < currLoop->children.size(); ++i)
            uniteChildReadInfo(currLoop->children[i]);
    }
}

static void fillConflictState(LoopGraph *currLoop, map<DIST::Array*, bool> &foundConflicts, map<DIST::Array*, vector<ArrayOp>> &unitedWROps)
{
    for (int i = 0; i < currLoop->children.size(); ++i)
    {
        if (i > 0)
        {
            foundConflicts.clear();
            unitedWROps.clear();
        }
        fillConflictState(currLoop->children[i], foundConflicts, unitedWROps);
    }

    for (auto it = currLoop->writeOps.begin(); it != currLoop->writeOps.end(); ++it)
    {
        DIST::Array *arrayN = it->first;
        vector<ArrayOp> currWrites = it->second;

        //TODO: почему раньше надо было смотреть и чтения?! Ведь эти конфликты разрешаются c помощью REMOTE
        /*auto itRead = currLoop->readOps.find(arrayN);
        if (itRead != currLoop->readOps.end())
        {
            const vector<ArrayOp> &currReads = itRead->second.first;
            for (int i = 0; i < currWrites.size(); ++i)
                uniteVectors(currReads[i], currWrites[i]);
        }*/

        auto it2 = unitedWROps.find(arrayN);
        if (it2 != unitedWROps.end())
        {
            vector<ArrayOp> &unitedW = it2->second;
            for (int i = 0; i < unitedW.size(); ++i)
            {
                if (currWrites[i].coefficients.size() != 0 && unitedW[i].coefficients.size() != 0)
                {
                    foundConflicts[arrayN] = true;
                    currLoop->hasConflicts[arrayN] = true;
                }
                else if (unitedW[i].coefficients.size() == 0)
                    unitedW[i] = currWrites[i];
                else
                {
                    for (auto &oldWrites : currWrites[i].coefficients)
                    {
                        auto it = unitedW[i].coefficients.find(oldWrites.first);
                        if (it == unitedW[i].coefficients.end())
                            it = unitedW[i].coefficients.insert(it, make_pair(oldWrites.first, 0));
                        it->second += oldWrites.second;
                    }
                }
            }
        }
        else
            it2 = unitedWROps.insert(it2, make_pair(arrayN, currWrites));
    }
}

void processLoopInformationForFunction(map<LoopGraph*, map<DIST::Array*, ArrayInfo*>> &loopInfo)
{
    // fill reads info
    for (auto& loop : loopInfo)
    {
        LoopGraph *graphLoop = loop.first;
        const map<DIST::Array*, ArrayInfo*> &currInfo = loop.second;

        for (auto it3 = currInfo.begin(); it3 != currInfo.end(); ++it3)
            fillWriteReadOps(graphLoop, it3->first, it3->second);

        for (auto it3 = currInfo.begin(); it3 != currInfo.end(); ++it3)
        {
            const ArrayInfo *currInfo = it3->second;
            bool nulReads = true;
            for (int z = 0; z < currInfo->getDimSize(); ++z)
            {
                if (currInfo->readOps[z].coefficients.size() != 0)
                {
                    nulReads = false;
                    break;
                }
            }

            if (!nulReads)
                graphLoop->readOpsArray.insert(it3->first);
        }
    }

    // fill conflict state
    for (auto& loop : loopInfo)
    {
        map<DIST::Array*, bool> foundConflicts;
        map<DIST::Array*, vector<ArrayOp>> unitedWROps;
        fillConflictState(loop.first, foundConflicts, unitedWROps);
    }

    //save read and write info
    for (auto& loop : loopInfo)
    {
        LoopGraph* graphLoop = loop.first;

        graphLoop->writeOpsForLoop = graphLoop->writeOps;
        for (auto& elem : graphLoop->readOps)
            graphLoop->readOpsForLoop[elem.first] = elem.second.first;
    }

    //unite reads info
    for (auto& loop : loopInfo)
        uniteChildReadInfo(loop.first);
}

#define GROUP_BY_REQUEST 1

#if GROUP_BY_REQUEST
class GroupItem
{
private:
    int maxDim1;
    int maxDim2;
    vector<pair<pair<int, int>, map<attrType, double>>> coeffs;    

public:
    GroupItem(int maxD1, int maxD2) : maxDim1(maxD1), maxDim2(maxD2), coeffs(maxD1 * maxD2) { }
    void inline AddToGroup(int dim1, int dim2, const attrType &key, const double currW)
    {
        auto shiftedAndInversedKey = DIST::inverseArcByShifts(DIST::shiftByDiffInArc(key));

        int pos = dim2 * maxDim1 + dim1;
        auto &current = coeffs[pos];
        current.first = make_pair(dim1, dim2);

        auto it = current.second.find(shiftedAndInversedKey);
        if (it == current.second.end())
            it = current.second.insert(it, make_pair(shiftedAndInversedKey, 0.0));
        it->second += currW;
    }

    const vector<pair<pair<int, int>, map<attrType, double>>>& GetCoeffs() const { return coeffs; }
};


static void inline addGroup(DIST::GraphCSR<int, double, attrType> &G,
                            DIST::Arrays<int> &allArrays,
                            const map<pair<DIST::Array*, DIST::Array*>, GroupItem> &group,
                            const links linkType)
{
    for (auto &elem : group)
    {
        DIST::Array *from = elem.first.first;
        DIST::Array *to = elem.first.second;
                
        for (auto &coeffs : elem.second.GetCoeffs())
        {
            const auto &arc = coeffs.first;
            for (auto &weight : coeffs.second)
                AddArrayAccess(G, allArrays, from, to, arc, weight.second, weight.first, linkType);
        }
    }
}
#endif

static double calculateSizes(const vector<pair<int, int>> &in, vector<int> &out)
{
    double all = 1.0;
    for (auto &elem : in)
    {
        if (elem.first >= elem.second)
        {
            out.push_back(2);
            all *= 2;
        }
        else
        {
            out.push_back(elem.second - elem.first + 1);
            all *= (elem.second - elem.first + 1);
        }
    }

    return all;
}

static bool addToGraph(DIST::GraphCSR<int, double, attrType> &G,
                       DIST::Arrays<int> &allArrays,
                       const ArrayInfo *from, DIST::Array *fromSymb,
                       const ArrayInfo *to, DIST::Array *toSymb, const links linkType)
{
    bool loopHasWrite = false;
#if GROUP_BY_REQUEST
    map<pair<DIST::Array*, DIST::Array*>, GroupItem> ww_links;
    map<pair<DIST::Array*, DIST::Array*>, GroupItem> wr_links;
    map<pair<DIST::Array*, DIST::Array*>, GroupItem> rr_links;
#endif

    auto sizesFromPair = fromSymb->GetSizes();
    auto sizesToPair = toSymb->GetSizes();
    
    vector<int> sizesFrom;
    vector<int> sizesTo;
    
    double allFrom = calculateSizes(sizesFromPair, sizesFrom);
    double allTo = calculateSizes(sizesToPair, sizesTo);

    if (linkType == WW_link)
    {
        // add W-R and W-W
        for (int dimFrom = 0; dimFrom < from->getDimSize(); ++dimFrom)
        {
            for (int dimTo = 0; dimTo < to->getDimSize(); ++dimTo)
            {
                if ((from->writeOps[dimFrom].coefficients.size() != 0) || (to->writeOps[dimTo].coefficients.size() != 0))
                    loopHasWrite = true;

                if ((from->writeOps[dimFrom].coefficients.size() != 0 || from->readOps[dimFrom].coefficients.size() != 0) &&
                    (to->writeOps[dimTo].coefficients.size() != 0 || to->readOps[dimTo].coefficients.size() != 0))
                {
                    for (auto &writeFrom : from->writeOps[dimFrom].coefficients)
                    {
                        for (auto &writeTo : to->writeOps[dimTo].coefficients)
#if GROUP_BY_REQUEST
                        {
                            const auto key = make_pair(fromSymb, toSymb);
                            auto it = ww_links.find(key);
                            if (it == ww_links.end())
                                it = ww_links.insert(it, make_pair(key, GroupItem(fromSymb->GetDimSize(), toSymb->GetDimSize())));

                            it->second.AddToGroup(dimFrom, dimTo, make_pair(writeFrom.first, writeTo.first), writeTo.second * allTo + writeFrom.second * allFrom);
                        }
#else
                            AddArrayAccess(G, allArrays, fromSymb, toSymb, make_pair(dimFrom, dimTo), writeTo.second * allTo + writeFrom.second * allFrom, make_pair(writeFrom.first, writeTo.first), WW_link);
#endif
                    }
                }
            }
        }
    }

    if (linkType == WR_link)
    {
        for (int dimFrom = 0; dimFrom < from->getDimSize(); ++dimFrom)
        {
            for (int dimTo = 0; dimTo < to->getDimSize(); ++dimTo)
            {
                if ((from->writeOps[dimFrom].coefficients.size() != 0) || (to->writeOps[dimTo].coefficients.size() != 0))
                    loopHasWrite = true;

                if ((from->writeOps[dimFrom].coefficients.size() != 0 || from->readOps[dimFrom].coefficients.size() != 0) &&
                    (to->writeOps[dimTo].coefficients.size() != 0 || to->readOps[dimTo].coefficients.size() != 0))
                {
                    for (auto &writeFrom : from->writeOps[dimFrom].coefficients)
                    {
                        for (auto &readTo : to->readOps[dimTo].coefficients)
#if GROUP_BY_REQUEST
                        {
                            const auto key = make_pair(fromSymb, toSymb);
                            auto it = wr_links.find(key);
                            if (it == wr_links.end())
                                it = wr_links.insert(it, make_pair(key, GroupItem(fromSymb->GetDimSize(), toSymb->GetDimSize())));

                            it->second.AddToGroup(dimFrom, dimTo, make_pair(writeFrom.first, readTo.first), readTo.second * allTo);
                        }
#else
                            AddArrayAccess(G, allArrays, fromSymb, toSymb, make_pair(dimFrom, dimTo), readTo.second * allTo, make_pair(writeFrom.first, readTo.first), WR_link);
#endif
                    }
                }
            }
        }
    }

    //add R-R, if no W
    if (linkType == RR_link)    
    {
        for (int dimFrom = 0; dimFrom < from->getDimSize(); ++dimFrom)
            for (int dimTo = 0; dimTo < to->getDimSize(); ++dimTo)
                if (from->readOps[dimFrom].coefficients.size() != 0 && to->readOps[dimTo].coefficients.size() != 0)
                    for (auto &readFrom : from->readOps[dimFrom].coefficients)
                        for (auto &readTo : to->readOps[dimTo].coefficients)
#if GROUP_BY_REQUEST
                        {
                            const auto key = make_pair(fromSymb, toSymb);
                            auto it = rr_links.find(key);
                            if (it == rr_links.end())
                                it = rr_links.insert(it, make_pair(key, GroupItem(fromSymb->GetDimSize(), toSymb->GetDimSize())));

                            it->second.AddToGroup(dimFrom, dimTo, make_pair(readFrom.first, readTo.first), readTo.second  * std::max(allTo, allFrom));
                        }
#else
                            AddArrayAccess(G, allArrays, fromSymb, toSymb, make_pair(dimFrom, dimTo), readTo.second * std::max(allTo, allFrom), make_pair(readFrom.first, readTo.first), RR_link);
#endif
    }

#if GROUP_BY_REQUEST
    addGroup(G, allArrays, ww_links, WW_link);
    addGroup(G, allArrays, wr_links, WR_link);
    addGroup(G, allArrays, rr_links, RR_link);
#endif

    return loopHasWrite;
}

//TODO: check for recursion!!
void getRealArrayRefs(DIST::Array* addTo, DIST::Array* curr,
                      set<DIST::Array*>& realArrayRefs,
                      const map<DIST::Array*, set<DIST::Array*>>& arrayLinksByFuncCalls)
{
    auto itLink = arrayLinksByFuncCalls.find(curr);
    if (itLink == arrayLinksByFuncCalls.end())
        realArrayRefs.insert(curr);
    else
        for (auto& link : itLink->second)
            getRealArrayRefs(addTo, link, realArrayRefs, arrayLinksByFuncCalls);
}

void getAllArrayRefs(DIST::Array *addTo, DIST::Array *curr,
                     set<DIST::Array*> &allArrayRefs,
                     const map<DIST::Array*, set<DIST::Array*>> &arrayLinksByFuncCalls)
{
    auto itLink = arrayLinksByFuncCalls.find(curr);
    allArrayRefs.insert(curr);

    if (itLink == arrayLinksByFuncCalls.end())
        return;
    else
        for (auto &link : itLink->second)
            if (allArrayRefs.find(link) == allArrayRefs.end())
                getAllArrayRefs(addTo, link, allArrayRefs, arrayLinksByFuncCalls);
}

#define DEB_GRAPH 0
static bool processLinks(const vector<pair<DIST::Array*, ArrayInfo*>> &currAccessesV,
                         DIST::Arrays<int> &allArrays, map<DIST::Array*, set<DIST::Array*>> &realArrayRefs,
                         DIST::GraphCSR<int, double, attrType> &graph, 
                         const links linkType)
{
    bool has_Wr_Ww_edges = false;
    int countAdd = 0;

    for (int z = 0; z < currAccessesV.size(); ++z)
    {
        const ArrayInfo& fromUniq = *currAccessesV[z].second;
        allArrays.AddArrayToGraph(currAccessesV[z].first);

        for (auto &fromSymb : realArrayRefs[currAccessesV[z].first])
        {
            for (int z1 = (linkType == WR_link) ? 0 : z + 1; z1 < currAccessesV.size(); ++z1)
            {
                if (z1 == z)
                    continue;

                const ArrayInfo &toUniq = *(currAccessesV[z1].second);
                allArrays.AddArrayToGraph(currAccessesV[z1].first);
                for (auto &toSymb : realArrayRefs[currAccessesV[z1].first])
                {
                    bool res = addToGraph(graph, allArrays, &fromUniq, fromSymb, &toUniq, toSymb, linkType);
                    countAdd++;

                    has_Wr_Ww_edges |= res;
                }
            }
        }

        const set<DIST::Array*>& realRefsSet = realArrayRefs[currAccessesV[z].first];
        if (realRefsSet.size() > 1)
        {
            const vector<DIST::Array*> realRefs(realRefsSet.begin(), realRefsSet.end());

            ArrayInfo unitedCopy = fromUniq;

            //copy read operations to write if empty
            for (int z = 0; z < unitedCopy.readOps.size(); ++z)
                for (auto& elem : unitedCopy.readOps[z].coefficients)
                    if (unitedCopy.writeOps[z].coefficients.find(elem.first) == unitedCopy.writeOps[z].coefficients.end())
                        unitedCopy.writeOps[z].coefficients[elem.first] = elem.second;

            for (int k1 = 0; k1 < realRefs.size(); ++k1)
            {
                for (int k2 = k1 + 1; k2 < realRefs.size(); ++k2)
                {
                    addToGraph(graph, allArrays, &unitedCopy, realRefs[k1], &unitedCopy, realRefs[k2], WW_link);
                    countAdd++;
                }
            }
        }
    }

#if DEB_GRAPH 
    __spf_print(DEB_GRAPH, "added count = %d\n", countAdd);
#endif
    return has_Wr_Ww_edges;
}

static bool sortByLine(const pair<LoopGraph*, vector<pair<DIST::Array*, ArrayInfo*>>>& l, const pair<LoopGraph*, vector<pair<DIST::Array*, ArrayInfo*>>>& r)
{
    return l.first->lineNum < r.first->lineNum;
}

static bool sortByName(const pair<DIST::Array*, ArrayInfo*>& l, const pair<DIST::Array*, ArrayInfo*>& r)
{
    return l.first->GetName() < r.first->GetName();
}

void addToDistributionGraph(const map<LoopGraph*, map<DIST::Array*, ArrayInfo*>> &loopInfo,
                            const map<DIST::Array*, set<DIST::Array*>> &arrayLinksByFuncCalls)
{
    vector<pair<LoopGraph*, vector<pair<DIST::Array*, ArrayInfo*>>>> sortedInfo;

    for (auto& loopAccess : loopInfo)
    {
        vector<pair<DIST::Array*, ArrayInfo*>> toAdd(loopAccess.second.begin(), loopAccess.second.end());
        sort(toAdd.begin(), toAdd.end(), sortByName);
        sortedInfo.push_back(make_pair(loopAccess.first, toAdd));
    }
    sort(sortedInfo.begin(), sortedInfo.end(), sortByLine);    

#if 0
    for (auto& loop : sortedInfo)
    {
        printf("info for loop %d %s\n", loop.first->lineNum, loop.first->fileName.c_str());
        for (auto& elem : loop.second)
        {
            printf(" for Array %s\n", elem.first->GetName().c_str());
            elem.second->printInfo();
        }
    }
#endif
    
    for (auto& loopAccess : sortedInfo)
    {
        createNeededException();

        ParallelRegion *currReg = loopAccess.first->region;
        if (currReg == NULL)
        {
            __spf_print(1, "Skip loop on line %d - no parallel region for this loop\n", loopAccess.first->lineNum);
            continue;
        }

        if (!loopAccess.first->isFor())
            continue;

        DIST::GraphCSR<int, double, attrType>& G = currReg->GetGraphToModify();
        DIST::GraphCSR<int, double, attrType>& loopGraph = loopAccess.first->getGraphToModify();

        __spf_print(DEB_GRAPH, "added to loop %d %s\n", loopAccess.first->lineNum, loopAccess.first->fileName.c_str());

        DIST::Arrays<int> &allArrays = currReg->GetAllArraysToModify();
        //printf("for loop on line %d: \n", it->first->lineNum);
        const vector<pair<DIST::Array*, ArrayInfo*>> & currAccessesV = loopAccess.second;
        map<DIST::Array*, set<DIST::Array*>> realArrayRefs;
        for (auto &access : currAccessesV)
            getRealArrayRefs(access.first, access.first, realArrayRefs[access.first], arrayLinksByFuncCalls);
        
        bool has_Wr_edges = false, has_Ww_edges = false, has_Rr_edges = false;
        has_Wr_edges = processLinks(currAccessesV, allArrays, realArrayRefs, sharedMemoryParallelization == 0 ? G :loopGraph, WW_link);
        has_Ww_edges |= processLinks(currAccessesV, allArrays, realArrayRefs, sharedMemoryParallelization == 0 ? G : loopGraph, WR_link);
        if (!has_Wr_edges && !has_Ww_edges)
            has_Rr_edges = processLinks(currAccessesV, allArrays, realArrayRefs, sharedMemoryParallelization == 0 ? G : loopGraph, RR_link);

        if (sharedMemoryParallelization)
        {
            if (!has_Wr_edges && !has_Ww_edges && !has_Rr_edges)
                for (auto& elem : realArrayRefs)
                    for (auto& array : elem.second)
                        allArrays.AddArrayToGraph(array);
        }

#if 0
        {
            char fName[256];
            sprintf(fName, "_graph_reg%d_%s.txt", it->first->lineNum, it->first->fileName.c_str());
            loopGraph.CreateGraphWiz(fName, vector<tuple<int, int, attrType>>(), allArrays, true);
        }
#endif
    }
}
#undef DEB_GRAPH

static bool addToDistributionGraph(const LoopGraph *loopInfo, const string &inFunction, int nesting)
{
    ParallelRegion *currReg = loopInfo->region;

    if (currReg == NULL || loopInfo->hasLimitsToParallel())
    {
        __spf_print(1, "Skip loop on line %d\n", loopInfo->lineNum);
        return false;
    }

    if (loopInfo->perfectLoop < nesting || nesting < 1)
        printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

    bool checkFlag = true;
    const LoopGraph* check = loopInfo;
    for (int z = 0; z < nesting; ++z, check->children.size() ? check = check->children[0] : check)
        checkFlag = checkFlag && check->withoutDistributedArrays;

    if (checkFlag == false)
        printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

    DIST::GraphCSR<int, double, attrType> &G = currReg->GetGraphToModify();
    DIST::Arrays<int> &allArrays = currReg->GetAllArraysToModify();
        
    string fullLoopName = loopInfo->genLoopArrayName(inFunction);
    string loopName = fullLoopName;

    vector<string> regs;
    regs.push_back(currReg->GetName());

    DIST::Array *loopArray = new DIST::Array(fullLoopName, loopName, nesting, getUniqArrayId(), loopInfo->fileName,
                                             loopInfo->lineNum, make_pair(DIST::l_LOCAL, inFunction), NULL, false, false, false, regs, 0);
    
    if (loopInfo->calculatedCountOfIters == 0)
    {
        const LoopGraph* updateInfo = loopInfo;
        vector<pair<Expression*, Expression*>> toAdd;
        for (int z = 0; z < nesting; ++z, updateInfo->children.size() ? updateInfo = updateInfo->children[0] : updateInfo)
        {
            if (updateInfo->startEndExpr.first && updateInfo->startEndExpr.second)
            {
                 if (updateInfo->stepVal < 0)
                     toAdd.push_back(make_pair(updateInfo->startEndExpr.second, updateInfo->startEndExpr.first));
                 else
                     toAdd.push_back(updateInfo->startEndExpr);
            }
        }
        if (toAdd.size())
            loopArray->SetSizesExpr(toAdd);
    }

    const LoopGraph* updateInfo = loopInfo;
    for (int z = 0; z < nesting; ++z, updateInfo->children.size() ? updateInfo = updateInfo->children[0] : updateInfo)
        loopArray->ExtendDimSize(z, (updateInfo->stepVal < 0) ? make_pair(updateInfo->endVal, updateInfo->startVal) : make_pair(updateInfo->startVal, updateInfo->endVal));
    loopArray->SetLoopArray(true);

    allArrays.AddArrayToGraph(loopArray);
    return true;
}

void selectFreeLoopsForParallelization(const vector<LoopGraph*>& loops, const string& funcName, bool isDistribute,
                                       const vector<ParallelRegion*>& regions, vector<Messages>& messagesForFile)
{
    for (auto& loopRef : loops)
    {
        if (loopRef->withoutDistributedArrays && loopRef->region && !loopRef->hasLimitsToParallel() && loopRef->lineNum > 0)
        {
            int nesting = 0;
            LoopGraph* it = loopRef;
            for (int z = 0; z < loopRef->perfectLoop; ++z, it->children.size() ? it = it->children[0] : it)
                if (it->withoutDistributedArrays && it->region && !it->hasLimitsToParallel() && it->lineNum > 0)
                    ++nesting;

            if (isDistribute)
                addToDistributionGraph(loopRef, funcName, nesting);
            else
            {
                auto region = loopRef->region;
                auto allArrays = region->GetAllArrays();

                string fullLoopName = loopRef->genLoopArrayName(funcName);
                auto loopArray = allArrays.GetArrayByName(fullLoopName);

                vector<ArrayInfo> tmpArrayInfo(nesting);
                

                map<LoopGraph*, map<DIST::Array*, ArrayInfo*>> convertedLoopInfo;

                LoopGraph* it = loopRef;
                for (int z = 0; z < nesting; ++z, it->children.size() ? it = it->children[0] : it)
                {
                    ArrayInfo& curr = tmpArrayInfo[z];
                    curr.setDimSize(nesting);

                    ArrayOp tmpOp(make_pair(make_pair(1, 0), 1.0));

                    for (int k = 0; k < nesting; ++k)
                        if (k == z)
                            curr.writeOps[k] = tmpOp;

                    map<DIST::Array*, ArrayInfo*> tmpAdd;
                    tmpAdd.insert(make_pair(loopArray, &curr));
                    convertedLoopInfo.insert(make_pair(it, tmpAdd));
                }
                createParallelDirectives(convertedLoopInfo, regions, map<DIST::Array*, set<DIST::Array*>>(), messagesForFile);
            }
        }
        else
            selectFreeLoopsForParallelization(loopRef->children, funcName, isDistribute, regions, messagesForFile);
    }
}

static void printBlanks(FILE *file, const int sizeOfBlank, const int countOfBlanks)
{
    for (int k = 0; k < countOfBlanks; ++k)
        for (int m = 0; m < sizeOfBlank; ++m)
            fprintf(file, " ");
}

static void printLoopGraphLvl(FILE *file, const vector<LoopGraph*> &childs, const int lvl, bool withRegs = false)
{
    for (int k = 0; k < (int)childs.size(); ++k)
    {
        bool needToPrint = true;
        if (withRegs)
            if (childs[k]->region == NULL)
                needToPrint = false;

        if (needToPrint)
        {
            printBlanks(file, 2, lvl);
            fprintf(file, "FOR on line %d -- %d", childs[k]->lineNum, childs[k]->lineNumAfterLoop);
            if (childs[k]->perfectLoop > 1)
                fprintf(file, " [PERFECT]");
            if (childs[k]->hasGoto)
                fprintf(file, " [HAS GOTO]");
            if (childs[k]->hasPrints)
                fprintf(file, " [HAS I/O OPS]");
            if (childs[k]->region)
                fprintf(file, " [REGION %s]", childs[k]->region->GetName().c_str());
            if (childs[k]->userDvmDirective)
                fprintf(file, " [USER DVM]");

            fprintf(file, " [IT = %d / MULT = %f]", childs[k]->countOfIters, childs[k]->countOfIterNested);
            fprintf(file, "\n");

            for (int i = 0; i < (int)childs[k]->calls.size(); ++i)
            {
                printBlanks(file, 2, lvl);
                fprintf(file, "CALL %s [%d]\n", childs[k]->calls[i].first.c_str(), childs[k]->calls[i].second);
            }
        }
        printLoopGraphLvl(file, childs[k]->children, lvl + 1, withRegs);
    }
}

int printLoopGraph(const char *fileName, const map<string, vector<LoopGraph*>> &loopGraph, bool withRegs)
{
    FILE *file = fopen(fileName, "w");
    if (file == NULL)
    {
        __spf_print(1, "can not open file %s\n", fileName);
        return -1;
    }

    map<string, vector<LoopGraph*>>::const_iterator it;
    for (it = loopGraph.begin(); it != loopGraph.end(); it++)
    {
        fprintf(file, "*** FILE %s\n", it->first.c_str());
        printLoopGraphLvl(file, it->second, 1, withRegs);
        fprintf(file, "\n");
    }

    fclose(file);
    return 0;
}


static void isAllOk(const vector<LoopGraph*> &loops, vector<Messages> &currMessages, set<void*> &isNotOkey, set<wstring> &uniqMessages)
{
    for (int i = 0; i < loops.size(); ++i)
    {
        if (loops[i]->region)
        {            
            if (loops[i]->countOfIters == 0 && loops[i]->region && loops[i]->isFor())
            {
                wstring bufE, bufR;
                __spf_printToLongBuf(bufE, L"  Can not calculate count of iterations for this loop, information about iterations in all loops in parallel regions '%s' will be ignored", 
                                     to_wstring(loops[i]->region->GetName()).c_str());

                auto itM = uniqMessages.find(bufE);
                if (itM == uniqMessages.end())
                {
                    uniqMessages.insert(itM, bufE);

                    __spf_printToLongBuf(bufR, R48, to_wstring(loops[i]->region->GetName()).c_str());

                    currMessages.push_back(Messages(NOTE, loops[i]->lineNum, bufR, bufE, 1016));
                    __spf_print(1, "  Can not calculate count of iterations for loop on line %d, information about iterations in all loops in parallel regions '%s' will be ignored\n", loops[i]->lineNum, loops[i]->region->GetName().c_str());
                }
                isNotOkey.insert(loops[i]->region);
            }
            isAllOk(loops[i]->children, currMessages, isNotOkey, uniqMessages);
        }
    }
}


static void setToDefaultCountIter(vector<LoopGraph*> &loops, const set<void*> &isNotOkey)
{
    for (int i = 0; i < loops.size(); ++i)
    {
        if (loops[i]->region)
        {
            if (isNotOkey.find(loops[i]->region) != isNotOkey.end() &&  loops[i]->countOfIters <= 0)
                loops[i]->countOfIters = 2;
            setToDefaultCountIter(loops[i]->children, isNotOkey);
        }
    }
}

static void multiplyCountIter(vector<LoopGraph*> &loops, const double allCount)
{
    for (int i = 0; i < loops.size(); ++i)
    {
        loops[i]->countOfIterNested = loops[i]->countOfIters * allCount;
        multiplyCountIter(loops[i]->children, loops[i]->countOfIterNested);
    }
}

static void recAddToChildren(vector<LoopGraph*> &loops, const double coef, map<LoopGraph*, double> &interprocCoefs)
{
    for (auto &loop : loops)
    {
        auto it = interprocCoefs.find(loop);
        if (it == interprocCoefs.end())
            it = interprocCoefs.insert(it, make_pair(loop, 0.0));
        it->second += coef;
        
        recAddToChildren(loop->children, coef, interprocCoefs);
    }
}

static void multiplyCountIterIP(vector<LoopGraph*> &loops, const double allCount, map<LoopGraph*, double> &interprocCoefs)
{
    for (auto &loop : loops)
    {
        const double coef = loop->countOfIters * allCount;
        recAddToChildren(loop->funcChildren, coef, interprocCoefs);
        multiplyCountIterIP(loop->funcChildren, coef, interprocCoefs);
    }
}

static void fillInterprocLinks(const map<string, FuncInfo*> &mapFunc, vector<LoopGraph*> &loops, const map<string, vector<LoopGraph*>> &allLoops)
{
    for (auto &loop : loops)
    {
        set<string> funNames;
        for (auto &call : loop->calls)
            funNames.insert(call.first);

        if (funNames.size())
        {
            for (auto &call : funNames)
            {
                auto it = mapFunc.find(call);
                if (it != mapFunc.end())
                {
                    FuncInfo *currF = it->second;

                    for (auto &loopInFunc : currF->loopsInFunc)
                        loop->funcChildren.push_back(loopInFunc);                    
                }
            }
        }
                
        fillInterprocLinks(mapFunc, loop->children, allLoops);
    }
}

static void fillInterprocLinks(vector<LoopGraph*>& loops)
{
    for (auto& loop : loops)
    {
        for (auto& funcCh : loop->funcChildren)
            funcCh->funcParents.push_back(loop);

        fillInterprocLinks(loop->children);
    }
}

void checkCountOfIter(map<string, vector<LoopGraph*>> &loopGraph, const map<string, vector<FuncInfo*>> &allFuncInfo, map<string, vector<Messages>> &SPF_messages)
{
    set<void*> isNotOkey;
    map<string, FuncInfo*> mapFunc;
    
    createMapOfFunc(allFuncInfo, mapFunc);
    for (auto& loopsInFile : loopGraph)
        fillInterprocLinks(mapFunc, loopsInFile.second, loopGraph);
    for (auto& loopsInFile : loopGraph)
        fillInterprocLinks(loopsInFile.second);

    for (auto &loopsInFile : loopGraph)
    {
        set<wstring> uniqMessages;

        auto itM = SPF_messages.find(loopsInFile.first);
        if (itM == SPF_messages.end())
            itM = SPF_messages.insert(itM, make_pair(loopsInFile.first, vector<Messages>()));
        isAllOk(loopsInFile.second, itM->second, isNotOkey, uniqMessages);
    }

    if (isNotOkey.size() != 0)
    {
        for (auto &loopsInFile : loopGraph)
            setToDefaultCountIter(loopsInFile.second, isNotOkey);
    }

    for (auto &loopsInFile : loopGraph)
        multiplyCountIter(loopsInFile.second, 1.0);
        
    set<LoopGraph*> linkTo;
    for (auto &loopsInFile : loopGraph)
    {
        for (auto &loop : loopsInFile.second)
        {
            for (auto &ch : loop->children)
                linkTo.insert(ch);
            for (auto &ch : loop->funcChildren)
                linkTo.insert(ch);
        }
    }

    bool changed = true;
    while (changed)
    {
        changed = false;
        for (auto &loop : linkTo)
        {
            for (auto &ch : loop->children)
            {
                if (linkTo.find(ch) == linkTo.end())
                {
                    linkTo.insert(ch);
                    changed = true;
                }
            }
            for (auto &ch : loop->funcChildren)
            {
                if (linkTo.find(ch) == linkTo.end())
                {
                    linkTo.insert(ch);
                    changed = true;
                }
            }
        }
    }

    set<LoopGraph*> dontLink;
    for (auto &loopsInFile : loopGraph)
        for (auto &loop : loopsInFile.second)
            if (linkTo.find(loop) == linkTo.end())
                dontLink.insert(loop);

    map<LoopGraph*, double> interprocCoefs;
    auto tmpParam = vector<LoopGraph*>(dontLink.begin(), dontLink.end());
    multiplyCountIterIP(tmpParam, 1.0, interprocCoefs);
    
    for (auto &loop : interprocCoefs)
        loop.first->countOfIterNested *= loop.second;
}

static void updateLoopIoAndStopsByFuncCalls(vector<LoopGraph*> &loopGraph, map<string, FuncInfo*> mapFunc)
{
    for (auto &loop : loopGraph)
    {
        vector<pair<pair<string, int>, set<string>>> funNames;
        for (auto &call : loop->calls)
        {
            string currF = call.first;
            set<string> recCalls;
            recCalls.insert(currF);
            bool changed = true;

            while (changed)
            {
                changed = false;
                set<string> local = recCalls;
                for (auto &elem : local)
                {
                    auto itF = mapFunc.find(elem);
                    if (itF != mapFunc.end())
                    {
                        for (auto &toAdd : itF->second->callsFrom)
                        {
                            if (recCalls.find(toAdd) == recCalls.end())
                            {
                                recCalls.insert(toAdd);
                                changed = true;
                            }
                        }
                    }
                }
            }
            funNames.push_back(make_pair(call, recCalls));
        }
        
        if (funNames.size())
        {
            for (auto &calls : funNames)
            {
                const int lineInLoop = calls.first.second;
                for (auto &call : calls.second)
                {
                    auto itF = mapFunc.find(call);
                    if (itF != mapFunc.end())
                    {
                        if (itF->second->linesOfIO.size() != 0)
                        {
                            loop->hasPrints = true;
                            loop->linesOfIO.insert(lineInLoop);                                                            
                        }

                        if (itF->second->linesOfStop.size() != 0)
                        {
                            loop->hasStops = true;
                            loop->linesOfStop.insert(lineInLoop);                                
                        }
                    }
                }
            }
        }

        updateLoopIoAndStopsByFuncCalls(loop->children, mapFunc);
    }
}

void updateLoopIoAndStopsByFuncCalls(map<string, vector<LoopGraph*>> &loopGraph, const map<string, vector<FuncInfo*>> &allFuncInfo)
{
    map<string, FuncInfo*> mapFunc;
    createMapOfFunc(allFuncInfo, mapFunc);

    for (auto &byFile : loopGraph)
        updateLoopIoAndStopsByFuncCalls(byFile.second, mapFunc);    
}

static void checkArraysMapping(vector<LoopGraph*> &loopList, map<DIST::Array*, vector<int>> flagUse, vector<Messages> &messages, const int topLine, set<DIST::Array*> &checked);
static void fillFromLoop(LoopGraph *loop, map<DIST::Array*, vector<int>> flagUse, vector<Messages> &messages, const int topLine, set<DIST::Array*> &checked)
{
    for (auto &write_op : loop->writeOps)
    {
        DIST::Array *array = write_op.first;
        if (flagUse.find(array) == flagUse.end())
        {
            vector<int> tmp(array->GetDimSize());
            std::fill(tmp.begin(), tmp.end(), 0);
            flagUse[array] = tmp;
            checked.insert(array);
        }

        for (int dim = 0; dim < write_op.second.size(); ++dim)
        {
            for (auto &coef : write_op.second[dim].coefficients)
            {
                if (coef.first.first != 0)
                {
                    flagUse[array][dim]++;
                    break;
                }
            }
        }
    }
    checkArraysMapping(loop->children, flagUse, messages, topLine, checked);
}

static void checkArraysMapping(vector<LoopGraph*> &loopList, map<DIST::Array*, vector<int>> flagUse, 
                               vector<Messages> &messages, const int topLine, set<DIST::Array*> &checked)
{
    if (loopList.size() > 0)
    {
        for (auto &loop : loopList)
            fillFromLoop(loop, flagUse, messages, topLine, checked);
    }
    else
    {
        for (auto &elem : flagUse)
        {
            for (int z = 0; z < elem.second.size(); ++z)
            {
                if (elem.second[z] > 1)
                {
                    if (!elem.first->IsDimDepracated(z))
                    {
                        std::wstring bufw, bufR;
                        __spf_printToLongBuf(bufw, L"  Array '%s' can not be distributed due to different writes to %d dimension, this dimension will deprecated",
                                             to_wstring(elem.first->GetShortName()).c_str(), z + 1);

                        __spf_printToLongBuf(bufR, R85, z + 1,to_wstring(elem.first->GetShortName()).c_str());

                        messages.push_back(Messages(NOTE, topLine, bufR, bufw, 1047));
                        elem.first->DeprecateDimension(z);                        
                    }
                }
            }
        }
    }
}

//TODO: need to improve interproc analysis
void checkArraysMapping(const map<string, vector<LoopGraph*>> &loopGraph, map<string, vector<Messages>> &SPF_messages, 
                        const map<DIST::Array*, set<DIST::Array*>> &arrayLinksByFuncCalls)
{
    set<DIST::Array*> checked;
    for (auto &loopByFile : loopGraph)
    {
        auto &messages = getObjectForFileFromMap(loopByFile.first.c_str(), SPF_messages);
        for (auto &loop : loopByFile.second)
        {
            if (loop->children.size() > 0)
            {
                map<DIST::Array*, vector<int>> flagUse;
                fillFromLoop(loop, flagUse, messages, loop->lineNum, checked);
            }
        }
    }

    for (auto &elem : checked)
    {
        if (elem->IsAllDeprecated())
        {
            wstring bufw, bufR;
            __spf_printToLongBuf(bufw, L"  Array '%s' can not be distributed due to all dimensions will deprecated", to_wstring(elem->GetShortName()).c_str());
            __spf_printToLongBuf(bufR, R86, to_wstring(elem->GetShortName()).c_str());

            for (auto &decl : elem->GetDeclInfo())
                getObjectForFileFromMap(decl.first.c_str(), SPF_messages).push_back(Messages(NOTE, decl.second, bufR, bufw, 1047));
            elem->SetDistributeFlag(DIST::SPF_PRIV);
        }
    }    
}

static bool isMapped(const vector<ArrayOp> &allOps)
{
    bool mapped = false;
    for (auto &ops : allOps)
    {
        for (auto &coeffs : ops.coefficients)
        {
            if (coeffs.first.first != 0)
            {
                mapped = true;
                break;
            }
        }
        if (mapped)
            break;
    }

    return mapped;
}

static void filterArrayInCSRGraph(vector<LoopGraph*> &loops, const map<string, FuncInfo*> &mapFuncInfo, const ParallelRegion *reg, 
                                  const map<DIST::Array*, set<DIST::Array*>> &arrayLinksByFuncCalls, const map<DIST::Array*, int> &trees,
                                  map<string, vector<Messages>> &messages)
{
    for (auto &loop : loops)
    {
        if (loop->region == reg)
        {
            if (loop->calls.size())
            {
                bool bounds = loop->hasGoto || loop->hasPrints || loop->hasStops || loop->hasUnknownArrayAssigns ||
                              loop->hasNonRectangularBounds || loop->hasIndirectAccess || loop->hasWritesToNonDistribute || loop->hasDifferentAlignRules;

                if (bounds == false )
                {
                    if (loop->usedArrays.size())
                    {
                        set<DIST::Array*> realRefs;
                        for (auto &array : loop->usedArrays)
                            getRealArrayRefs(array, array, realRefs, arrayLinksByFuncCalls);

                        set<DIST::Array*> wasMapped;
                        for (auto &read : loop->readOps)
                        {
                            set<DIST::Array*> readRefs;
                            getRealArrayRefs(read.first, read.first, readRefs, arrayLinksByFuncCalls);

                            if (isMapped(read.second.first))
                                wasMapped.insert(readRefs.begin(), readRefs.end());
                        }

                        for (auto &write : loop->writeOps)
                        {
                            set<DIST::Array*> writeRefs;
                            getRealArrayRefs(write.first, write.first, writeRefs, arrayLinksByFuncCalls);

                            if (isMapped(write.second))
                                wasMapped.insert(writeRefs.begin(), writeRefs.end());
                        }

                        if (wasMapped.size() == 0)
                            filterArrayInCSRGraph(loop->children, mapFuncInfo, reg, arrayLinksByFuncCalls, trees, messages);
                        else
                        {
                            set<DIST::Array*> deprecated;
                            int treeNum = -1;
                            map<int, int> treeNumCount;
                            //filter by graph loop's arrays
                            //TODO
                            for (auto &array : realRefs)
                            {
                                if (wasMapped.find(array) == wasMapped.end())
                                    continue;

                                auto itA = trees.find(array);
                                if (itA == trees.end() || itA->second < 0)
                                {
                                    wstring bufw, bufR;
                                    __spf_printToLongBuf(bufw, L"  Array '%s' can not be distributed", to_wstring(array->GetShortName()).c_str());
                                    __spf_printToLongBuf(bufR, R87, to_wstring(array->GetShortName()).c_str());

                                    getObjectForFileFromMap(loop->fileName.c_str(), messages).push_back(Messages(NOTE, loop->lineNum, bufR, bufw, 1047));
                                    deprecated.insert(array);
                                    array->SetDistributeFlag(DIST::SPF_PRIV);
                                }
                                else
                                {
                                    if (treeNumCount.find(itA->second) == treeNumCount.end())
                                        treeNumCount[itA->second] = 1;
                                    else
                                        treeNumCount[itA->second]++;
                                }
                            }

                            if (treeNumCount.size() == 0)
                                continue;

                            auto itT = treeNumCount.begin();
                            treeNum = itT->first;
                            int countT = itT->second;
                            itT++;
                            for (; itT != treeNumCount.end(); itT++)
                            {
                                if (itT->second > countT)
                                {
                                    countT = itT->second;
                                    treeNum = itT->first;
                                }
                            }
                            
                            for (auto &array : realRefs)
                            {
                                if (wasMapped.find(array) == wasMapped.end())
                                    continue;

                                auto itA = trees.find(array);
                                if (itA == trees.end() || itA->second != treeNum)
                                {
                                    wstring bufw, bufR;
                                    __spf_printToLongBuf(bufw, L"  Array '%s' can not be distributed", to_wstring(array->GetShortName()).c_str());
                                    __spf_printToLongBuf(bufR, R88, to_wstring(array->GetShortName()).c_str());

                                    getObjectForFileFromMap(loop->fileName.c_str(), messages).push_back(Messages(NOTE, loop->lineNum, bufR, bufw, 1047));
                                    deprecated.insert(array);
                                    array->SetDistributeFlag(DIST::SPF_PRIV);
                                }
                            }

                            set<DIST::Array*> inCalls;
                            for (auto &call : loop->calls)
                            {
                                auto itF = mapFuncInfo.find(call.first);
                                if (itF != mapFuncInfo.end())
                                    inCalls.insert(itF->second->allUsedArrays.begin(), itF->second->allUsedArrays.end());
                            }

                            for (auto &inCall : inCalls)
                            {
                                if (realRefs.find(inCall) == realRefs.end() && deprecated.find(inCall) == deprecated.end())
                                {
                                    bool needToDeprecated = false;
                                    if (trees.find(inCall) == trees.end())
                                        needToDeprecated = true;
                                    else
                                    {
                                        if (trees.find(inCall)->second != treeNum)
                                            needToDeprecated = true;
                                    }
                                    if (needToDeprecated)
                                    {
                                        wstring bufw, bufR;
                                        __spf_printToLongBuf(bufw, L"  Array '%s' can not be distributed", to_wstring(inCall->GetShortName()).c_str());
                                        __spf_printToLongBuf(bufR, R89, to_wstring(inCall->GetShortName()).c_str());

                                        getObjectForFileFromMap(loop->fileName.c_str(), messages).push_back(Messages(NOTE, loop->lineNum, bufR, bufw, 1047));
                                        deprecated.insert(inCall);
                                        inCall->SetDistributeFlag(DIST::SPF_PRIV);
                                    }
                                }
                            }
                        }
                    }
                }
                else
                    filterArrayInCSRGraph(loop->children, mapFuncInfo, reg, arrayLinksByFuncCalls, trees, messages);
            }
        }
    }
}

void filterArrayInCSRGraph(map<string, vector<LoopGraph*>> &loopGraph, map<string, vector<FuncInfo*>> &allFuncs, 
                           ParallelRegion *reg, const map<DIST::Array*, set<DIST::Array*>> &arrayLinksByFuncCalls,
                           map<string, vector<Messages>> &messages)
{
    map<string, FuncInfo*> mapFuncInfo;
    map<DIST::Array*, int> trees;
 
    auto arrays = reg->GetAllArrays().GetArrays();
    int count = 0;
    for (auto &array : arrays)
        if (!array->IsLoopArray() && !array->IsTemplate() && array->GetLocation().first != DIST::l_PARAMETER)
            count++;

    if (count <= 1)
        return;

    reg->GetGraphToModify().FindAllArraysTrees(trees, reg->GetAllArrays());
    createMapOfFunc(allFuncs, mapFuncInfo);

    int lastTreesNum = trees.size();
    for (auto &array : arrays)
        if (!array->IsLoopArray() && !array->IsTemplate() && array->GetLocation().first != DIST::l_PARAMETER)
            if (trees.find(array) == trees.end())
                trees[array] = lastTreesNum++;

    if (trees.size())
        for (auto &byFile : loopGraph)
            filterArrayInCSRGraph(byFile.second, mapFuncInfo, reg, arrayLinksByFuncCalls, trees, messages);
}

void LoopGraph::reduceAccessGraph()
{
    for (auto& ch : children)
        ch->reduceAccessGraph();
        
    checkNull(region, convertFileName(__FILE__).c_str(), __LINE__);
    if (accessGraph.GetNumberOfV() != 0)
        DIST::createOptimalDistribution<int, double, attrType>(accessGraph, reducedAccessGraph, region->GetAllArrays(), region->GetId(), false);
}

void LoopGraph::createVirtualTemplateLinks(const map<DIST::Array*, set<DIST::Array*>>& arrayLinksByFuncCalls, map<string, vector<Messages>>& SPF_messages, bool isMpiProgram)
{
    if (region == NULL)
    {
        for (auto& ch : children)
            ch->createVirtualTemplateLinks(arrayLinksByFuncCalls, SPF_messages, isMpiProgram);
        return;
    }    

    auto allArrays = region->GetAllArrays();
    __spf_print(1, "*** FOR LOOP on line %d and file '%s':\n", lineNum, fileName.c_str());
#if 0
    {
        char fName[256];
        sprintf(fName, "_graph_reg%d_%s.txt", lineNum, fileName.c_str());
        accessGraph.CreateGraphWiz(fName, vector<tuple<int, int, attrType>>(), allArrays, true);
    }
#endif
    
    set<DIST::Array*> canNotMapped;
    createDistributionDirs(reducedAccessGraph, allArrays, dataDirectives, SPF_messages, arrayLinksByFuncCalls, isMpiProgram, usedArrays.size() ? usedArrays : usedArraysAll);
    createAlignDirs(reducedAccessGraph, allArrays, dataDirectives, (uint64_t)this, arrayLinksByFuncCalls, SPF_messages, &canNotMapped, usedArrays);



    for (auto& elem : canNotMapped)
        if (usedArraysWrite.find(elem) != usedArraysWrite.end())
            hasUnknownArrayAssigns = true;

    auto result = dataDirectives.GenAlignsRules();
    for (int i = 0; i < result.size(); ++i)
        __spf_print(1, "  %s\n", result[i].c_str());
#if 0
    //if (lineNum == 56 && fileName == "exchange_6.f")
    {
        char fName[256];
        sprintf(fName, "_graph_reduced_with_templ_reg%d_%s.txt", lineNum, fileName.c_str());
        reducedAccessGraph.CreateGraphWiz(fName, vector<tuple<int, int, attrType>>(), allArrays, true);
    }
#endif

    for (auto& ch : children)
        ch->createVirtualTemplateLinks(arrayLinksByFuncCalls, SPF_messages, isMpiProgram);
}
