#include "leak_detector.h"

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cstdint>
#include <assert.h>
#include <string>
#include <vector>
#include <map>
#include <set>

#include "dvm.h"

#include "../Distribution/GraphCSR.h"
#include "../Distribution/Arrays.h"
#include "../Distribution/Distribution.h"
#include "../Distribution/DvmhDirective_func.h"
#include "graph_loops_func.h"
#include "graph_calls_func.h"

#include "errors.h"
#include "../LoopAnalyzer/loop_analyzer.h"
#include "directive_parser.h"
#include "SgUtils.h"
#include "../Sapfor.h"
#include "directive_creator.h"
#include "insert_directive.h"

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

static const string dvmhModuleName = "dvmh_template_mod";

extern int sharedMemoryParallelization;

//the size of vector indiceates type of DVM_DIR
SgStatement* createStatFromExprs(const vector<Expression*> &exprs)
{
    SgStatement *result = NULL;
    if (exprs.size() == 3)
        result = new SgStatement(DVM_PARALLEL_ON_DIR, NULL, NULL, NULL, NULL, NULL);
    else if (exprs.size() == 4)
        result = new SgStatement(DVM_REDISTRIBUTE_DIR, NULL, NULL, NULL, NULL, NULL);
    else if (exprs.size() == 5)
        result = new SgStatement(DVM_REALIGN_DIR, NULL, NULL, NULL, NULL, NULL);
    else if (exprs.size() == 6)
        result = new SgStatement(DVM_DISTRIBUTE_DIR, NULL, NULL, NULL, NULL, NULL);
    else if (exprs.size() == 7)
        result = new SgStatement(HPF_TEMPLATE_STAT, NULL, NULL, NULL, NULL, NULL);
    else
        printInternalError(convertFileName(__FILE__).c_str(), __LINE__);
    
    for (int i = 0; i < 3; ++i)
        if (exprs[i])
            result->setExpression(i, *exprs[i]);
    return result;
}

static void removeDoubleRealign(vector<vector<Expression*>>& realigns)
{
    if (realigns.size() < 2)
        return;
    vector<bool> save;
    for (int z = 0; z < realigns.size(); ++z)
        save.push_back(true);

    bool changed = false;
    for (int z = 1; z < realigns.size(); ++z)
    {
        for (int zp = 0; zp < z; ++zp)
        {
            if (!save[zp])
                continue;

            auto prev = string(realigns[zp][0]->unparse());
            auto curr = string(realigns[z][0]->unparse());
            if (prev == curr)
            {
                save[zp] = false;
                changed = true;
            }
            else
            {
                set<string> listNext;
                SgExpression* ex = realigns[z][0];
                while (ex)
                {
                    listNext.insert(ex->lhs()->symbol()->identifier());
                    ex = ex->rhs();
                }

                ex = realigns[zp][0];
                vector<SgExpression*> newExp;
                while (ex)
                {
                    if (listNext.find(ex->lhs()->symbol()->identifier()) == listNext.end())
                        newExp.push_back(ex->lhs());
                    ex = ex->rhs();
                }
                if (newExp.size())
                    realigns[zp][0] = new Expression(makeExprList(newExp));
                else
                {
                    save[zp] = false;
                    changed = true;
                }
            }
        }
    }

    if (!changed)
        return;

    vector<vector<Expression*>> newVal;
    for (int z = 0; z < realigns.size(); ++z)
        if (save[z])
            newVal.push_back(realigns[z]);
    realigns = newVal;
}

static void filterInsertMap(map<int, vector<vector<Expression*>>> &toInsertMap)
{
    for (auto it = toInsertMap.begin(); it != toInsertMap.end(); ++it)
    {
        vector<vector<Expression*>> newVal;

        vector<vector<Expression*>> realign, redis;
        for (int z = 0; z < it->second.size(); ++z)
        {
            if (it->second[z].size() == 4)
                redis.push_back(it->second[z]);
            else if (it->second[z].size() == 5)
                realign.push_back(it->second[z]);
            else 
                newVal.push_back(it->second[z]);
        }
        //removeDoubleRedistribute
        //TODO: deprecated?
        for (auto& elem : redis)
            newVal.push_back(elem);

        //removeDoubleRealign
        removeDoubleRealign(realign);
        for (auto& elem : realign)
            newVal.push_back(elem);

        //sort by type
        map<int, vector<vector<Expression*>>> toSort;
        for (auto &elem : newVal)
            toSort[elem.size()].push_back(elem);

        newVal.clear();
        for (auto itR = toSort.rbegin(); itR != toSort.rend(); itR++)
        {
            for (auto &elem : itR->second)
                newVal.push_back(elem);
        }
        it->second = newVal;
    }
}

static void correctKeyLine(SgFile *file, map<int, vector<vector<Expression*>>> &toInsertMap)
{
    set<int> lines;
    vector<int> lines_v;
    
    for (SgStatement* st = file->firstStatement(); st; st = st->lexNext())
    {
        if (st->lineNumber() > 0 || st->lineNumber() < 0 && st->variant() == FOR_NODE)
            lines.insert(st->lineNumber());
        if (st->variant() < 0)
            st = st->lastNodeOfStmt();
    }

    for (auto &l : lines)
        lines_v.push_back(l);

    map<int, vector<vector<Expression*>>> newMap;
    for (auto &elem : toInsertMap)
    {
        if (std::find(lines_v.begin(), lines_v.end(), elem.first) != lines_v.end())
            newMap[elem.first] = elem.second;
        else
        {
            int lineNext = -1;
            for (int z = 0; z < lines_v.size(); ++z)
            {
                if (lines_v[z] > elem.first)
                {
                    lineNext = lines_v[z];
                    break;
                }
            }

            if (lineNext == -1)
                printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

            newMap[lineNext] = elem.second;
        }
    }

    toInsertMap = newMap;
}

void removeStatementsFromAllproject(const set<int> &variants)
{
    for (int z = 0; z < CurrentProject->numberOfFiles(); ++z)
    {
        SgFile* file = &CurrentProject->file(z);
        SgStatement* stF = file->firstStatement();

        vector<SgStatement*> toDel;
        for (SgStatement* st = stF; st; st = st->lexNext())
        {
            const int var = st->variant();
            bool templateMod = false;
            if (var == MODULE_STMT)
            {
                string ident = st->symbol()->identifier();
                convertToLower(ident);
                if (ident == dvmhModuleName)
                    templateMod = true;
            }

            bool useMod = false;
            if (var == USE_STMT && st->symbol()->identifier() == dvmhModuleName)
                useMod = true;

            if (variants.find(var) != variants.end() || templateMod || useMod)
                toDel.push_back(st);

            if (var == MODULE_STMT && templateMod)
                st = st->lastNodeOfStmt();
        }

        for (int k = 0; k < toDel.size(); ++k)
        {
            //move comment to next statement
            if (toDel[k]->comments())
            {
                char* comms = toDel[k]->comments();
                string comments(comms);
                toDel[k]->delComments();

                SgStatement* next = toDel[k]->lexNext();
                if (next)
                    next->addComment(comments.c_str());
            }

            toDel[k]->deleteStmt();
        }
    }
}

// TODO: optimize REDISTR dirs with CFG
void insertDirectiveToFile(SgFile *file, const char *fin_name, const vector<Directive*> &toInsert, 
                           const bool extractDir, vector<Messages> &messagesForFile)
{
    map<int, vector<vector<Expression*>>> toInsertMap;
    for (int i = 0; i < toInsert.size(); ++i)
    {
        auto it = toInsertMap.find(toInsert[i]->line);
        if (it == toInsertMap.end())
        {
            vector<vector<Expression*>> tmp;
            tmp.push_back(((CreatedDirective*)toInsert[i])->sageData);
            toInsertMap.insert(it, make_pair(toInsert[i]->line, tmp));
        }
        else
            it->second.push_back(((CreatedDirective*)toInsert[i])->sageData);
    }

    filterInsertMap(toInsertMap);
    correctKeyLine(file, toInsertMap);

    vector<SgStatement*> toDel;

    vector<SgStatement*> modulesAndFuncs;
    getModulesAndFunctions(file, modulesAndFuncs);

    for (int i = 0; i < modulesAndFuncs.size(); ++i)
    {
        SgStatement *st = modulesAndFuncs[i];
        SgStatement *lastNode = st->lastNodeOfStmt();

        if (extractDir && st->variant() == MODULE_STMT)
        {
            if (st->symbol()->identifier() == dvmhModuleName)
            {
                st->deleteStmt();
                continue;
            }
        }

        int numSt = 0;
        do
        {
            if (st->variant() == CONTAINS_STMT)
                break;

            if (numSt != 0)
                st = st->lexNext();

            if (st == NULL)
            {
                messagesForFile.push_back(Messages(ERROR, 1, R128, L"internal error in analysis, parallel directives will not be generated for this file!", 3008));

                __spf_print(1, "internal error in analysis, parallel directives will not be generated for this file!\n");
                break;
            }

            if (!strcmp(st->fileName(), fin_name))
            {
                auto it = toInsertMap.find(st->lineNumber());
                if (it != toInsertMap.end())
                {
                    if (extractDir == false)
                    {
                        for (int i1 = 0; i1 < it->second.size(); ++i1)
                        {
                            SgStatement *dirToInsert = createStatFromExprs((it->second[i1]));

                            const int varSt = st->variant();
                            if (varSt == PROC_HEDR || varSt == PROG_HEDR || varSt == FUNC_HEDR || varSt == MODULE_STMT)
                                st->insertStmtAfter(*dirToInsert, *st);
                            else
                                st->insertStmtBefore(*dirToInsert, *(st->controlParent()));
                            if (st->comments())
                            {
                                char *comms = st->comments();
                                string comments(comms);
                                st->delComments();

                                st->lexPrev()->addComment(comments.c_str());
                            }
                        }
                    }
                    toInsertMap.erase(it);
                }
            }

            if (extractDir)
            {
                const int var = st->variant();
                if (var == DVM_PARALLEL_ON_DIR ||
                    var == DVM_REDISTRIBUTE_DIR ||
                    var == DVM_REALIGN_DIR ||
                    var == DVM_REMOTE_ACCESS_DIR ||
                    var == DVM_SHADOW_DIR ||
                    var == DVM_INHERIT_DIR ||
                    var == DVM_DYNAMIC_DIR ||
                    (var == USE_STMT && st->lineNumber() < 0) ||
                    var == HPF_TEMPLATE_STAT || 
                    var == DVM_ALIGN_DIR ||
                    var == DVM_DISTRIBUTE_DIR || 
                    var == DVM_VAR_DECL ||
                    var == ACC_REGION_DIR || var == ACC_END_REGION_DIR || 
                    var == ACC_ACTUAL_DIR || var == ACC_GET_ACTUAL_DIR)
                {
                    toDel.push_back(st);

                    //move comment to next statement
                    if (st->comments())
                    {
                        char *comms = st->comments();
                        string comments(comms);
                        st->delComments();

                        SgStatement *next = st->lexNext();
                        if (next)
                            next->addComment(comments.c_str());
                    }
                }
            }
            numSt++;
        } while (st != lastNode);

        if (extractDir)
        {
            for (int k = 0; k < toDel.size(); ++k)
            {
                if (toDel[k]->lineNumber() > 0) // user directives, skip
                    ; // printInternalError(convertFileName(__FILE__).c_str(), __LINE__);
                else
                    toDel[k]->deleteStmt();
            }
        }
    }
}

static void moveComment(SgStatement *elem, const string addNew = "")
{
    string comms = "";
    if (elem->comments())
        comms = elem->comments();
    comms += addNew;

    if (comms != "")
    {
        auto toAddComm = elem;
        while (toAddComm)
        {
            toAddComm = toAddComm->lexNext();
            if (toAddComm && !isDVM_stat(toAddComm))
                break;
        }
        if (toAddComm == NULL)
            printInternalError(convertFileName(__FILE__).c_str(), __LINE__);
        
        string comms2 = "";
        if (toAddComm->comments())
            comms2 = toAddComm->comments();
        comms += comms2;

        if (comms != "")
            toAddComm->setComments(comms.c_str());
    }
}

static void replaceComment(string &dir, const char *firstChar)
{
    const string templ = firstChar + string("DVM$");
    size_t offset = 0;
    for (; ; )
    {
        offset = dir.find(templ, offset);
        if (offset == string::npos)
            break;
        else
        {
            dir.insert(offset + 1, " ");
            offset += 6;
        }
    }
}

void removeDvmSpfDirectives(SgFile* file, bool removeDvm, bool removeSpf, bool toCommentDvm)
{
    if (!removeDvm && !removeSpf && !toCommentDvm)
        printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

    vector<SgStatement*> toDel;
    vector<SgStatement*> toTotalDel;
    const string currFile = file->filename();

    vector<SgStatement*> toProcess;
    getModulesAndFunctions(file, toProcess);
    for (int i = 0; i < toProcess.size(); ++i)
    {
        SgStatement *st = toProcess[i];
        SgStatement *lastNode = st->lastNodeOfStmt();

        while (st != lastNode)
        {
            if (st == NULL)
            {
                __spf_print(1, "internal error in analysis, parallel directives will not be generated for this file!\n");
                break;
            }

            if (st->variant() == CONTAINS_STMT)
                break;

            const int var = st->variant();
            if (isDVM_stat(st) && (removeDvm || toCommentDvm) || isSPF_stat(st) && removeSpf)
                if (st->fileName() == currFile)
                    toDel.push_back(st);

            if ( (var == USE_STMT || var == MODULE_STMT) && st->symbol()->identifier() == string("dvmh_template_mod"))
                toTotalDel.push_back(st);
            st = st->lexNext();
        }
    }

    if (toCommentDvm)
    {
        for (auto &elem : toDel)
        {
            moveComment(elem);
            elem->delComments();

            char *tmp = elem->unparse();
            if (tmp == NULL)
                printInternalError(convertFileName(__FILE__).c_str(), __LINE__);
            string dir(tmp);
            convertToUpper(dir);
            
            replaceComment(dir, "!");
            replaceComment(dir, "C");
            
            moveComment(elem, dir);
            elem->deleteStmt();
        }
    }
    else //removeSpf or removeDvm
    {
        for (auto &elem : toDel)
        {
            moveComment(elem);
            elem->deleteStmt();
        }
    }

    for (auto& elem : toTotalDel)
    {
        moveComment(elem);
        elem->deleteStmt();
    }
}

static inline pair<string, SgExpression*> genBoundsOfDim(const pair<int, int> &intBounds, const pair<Expression*, Expression*> &exprBounds)
{
    if (intBounds.first > intBounds.second)
    {
        if (exprBounds.first == NULL || exprBounds.second == NULL)
            printInternalError(convertFileName(__FILE__).c_str(), __LINE__);
        else
        {
            return make_pair(string(exprBounds.first->unparse()) + ":" + string(exprBounds.second->unparse()), 
                             new SgExpression(DDOT, exprBounds.first, exprBounds.second, NULL));
        }
    }
    else
        return make_pair(std::to_string(intBounds.first) + ":" + std::to_string(intBounds.second),
                         new SgExpression(DDOT, new SgValueExp(intBounds.first), new SgValueExp(intBounds.second), NULL));
}

static inline pair<string, SgStatement*> genTemplateDelc(DIST::Array *templ, const string &name, SgFile *file, SgStatement *module = NULL, bool isMain = false)
{
    string templDecl = (module == NULL) ? "!DVM$ TEMPLATE, COMMON :: " : "!DVM$ TEMPLATE ";
    SgStatement* templDeclSt = NULL;

    if (module == NULL)
        templDeclSt = new SgStatement(DVM_VAR_DECL);
    else
        templDeclSt = new SgStatement(HPF_TEMPLATE_STAT);

    if (module && templ->IsTemplate() && !templ->IsLoopArray())
        templ->ChangeLocation(DIST::l_MODULE, module->symbol()->identifier());
    
    const vector<pair<int, int>> &sizes = templ->GetSizes();
    const auto &sizesExpr = templ->GetSizesExpr();
        
    for (auto &size : sizes)
    {
        if (templ->IsLoopArray())
        {
            // TODO: move gen sizes to TEMPLATE CREATE directive immediately before parallel loop
            bool ok = true;
            for (auto &elem : sizesExpr)
                if (elem.first.first == NULL || elem.second.first == NULL)
                    ok = false;

            if (size.first > size.second && module == NULL && !ok)
                printInternalError(convertFileName(__FILE__).c_str(), __LINE__);
        }
        else
        {
            if (size.first > size.second)
                printInternalError(convertFileName(__FILE__).c_str(), __LINE__);
        }
    }
    
    SgExprListExp *listDim = new SgExprListExp();
    templDecl += name + "(";
    for (int z = 0; z < sizes.size(); ++z)
    {
        auto bounds = genBoundsOfDim(sizes[z], make_pair(sizesExpr[z].first.first, sizesExpr[z].second.first));
        templDecl += bounds.first;
        if (z == 0)
            listDim->setLhs(bounds.second);
        else
            listDim->append(*bounds.second);

        if (z != sizes.size() - 1)
            templDecl += ",";
    }
    templDecl += ")\n";
    if (module != NULL)
        templDeclSt->setExpression(0, new SgArrayRefExp(*findSymbolOrCreate(file, name, new SgArrayType(*SgTypeInt())), *listDim));
    else 
        templDeclSt->setExpression(0, new SgExpression(EXPR_LIST, new SgArrayRefExp(*findSymbolOrCreate(file, name, new SgArrayType(*SgTypeInt())), *listDim), NULL, NULL));
    if (module == NULL)
        templDeclSt->setExpression(2, new SgExpression(EXPR_LIST, new SgExpression(TEMPLATE_OP), new SgExpression(EXPR_LIST, new SgExpression(COMMON_OP), NULL, NULL), NULL));    

    return make_pair(templDecl, templDeclSt);
}

static inline pair<string, SgStatement*> 
    genTemplateDistr(DIST::Array *array, const string &name, const vector<string> &distrRules, const vector<vector<dist>> &distrRulesSt,
                     const uint64_t regionId, const int templIdx, bool isMain, SgFile *file)
{
    auto newOrder = array->GetNewTemplateDimsOrder();
    //TODO:!!!
    //"!DVM$ DISTRIBUTE :: " + templ->GetShortName() + "\n";
    SgExpression *ex1 = new SgVarRefExp(*findSymbolOrCreate(file, name));
    SgExprListExp *ex2 = new SgExprListExp();
        
    for (int z = 0; z < distrRulesSt[templIdx].size(); ++z)
    {
        string val = "";
        if (newOrder.size() == 0)
        {
            if (distrRulesSt[templIdx][z] == dist::BLOCK)
                val = "BLOCK";
            else
                val = "*";
        }
        else
        {
            if (distrRulesSt[templIdx][newOrder[z]] == dist::BLOCK)
                val = "BLOCK";
            else
                val = "*";
        }

        if (z == 0)
            ex2->setLhs(new SgKeywordValExp(val.c_str()));
        else
            ex2->append(*new SgKeywordValExp(val.c_str()));
    }
    SgStatement *distrSt = new SgStatement(DVM_DISTRIBUTE_DIR, NULL, NULL, ex1, ex2, NULL);
    return make_pair("!DVM$ DISTRIBUTE " + distrRules[templIdx] + "\n", distrSt);
}

static pair<string, SgStatement*> genDynamicDecl(DIST::Array *templ, SgFile *file)
{
    auto allClones = templ->GetAllClones();
    string templDyn = "!DVM$ DYNAMIC " + templ->GetShortName();

    for (auto &elem : allClones)
        templDyn += ", " + elem.second;
    templDyn += "\n";

    SgStatement* templDynSt = NULL;

    if (allClones.size() == 0)
        templDynSt = new SgStatement(DVM_DYNAMIC_DIR, NULL, NULL, new SgVarRefExp(*findSymbolOrCreate(file, templ->GetShortName())), NULL, NULL);
    else
    {
        vector<SgExpression*> list;
        list.push_back(new SgVarRefExp(*findSymbolOrCreate(file, templ->GetShortName())));
        for (auto& elem : allClones)
            list.push_back(new SgVarRefExp(*findSymbolOrCreate(file, elem.second)));
        
        templDynSt = new SgStatement(DVM_DYNAMIC_DIR, NULL, NULL, makeExprList(list), NULL, NULL);
    }

    return make_pair(templDyn, templDynSt);
}

static inline int findTeplatePosition(const DIST::Array *templ, const DataDirective &dataDir)
{
    int templIdx = -1;
    const string templName = templ->GetShortName();
    for (int z = 0; z < dataDir.distrRules.size(); ++z)
    {
        if (dataDir.distrRules[z].first->GetShortName() == templName)
        {
            templIdx = z;
            break;
        }
    }

    if (templIdx == -1)
    {
        __spf_print(1, "can not find template position ['%s']\n", templName.c_str());
        printInternalError(convertFileName(__FILE__).c_str(), __LINE__);
    }
        
    return templIdx;
}

static inline DIST::Array* findLinkWithTemplate(DIST::Array *alignArray, const DIST::Arrays<int> &allArrays,
                                                DIST::GraphCSR<int, double, attrType> &reducedG, const uint64_t regionId)
{
    DIST::Array *templ = NULL;

    vector<tuple<DIST::Array*, int, pair<int, int>>> links;
    reducedG.GetAlignRuleWithTemplate(alignArray, allArrays, links, regionId);

    for (int i = 0; i < links.size(); ++i)
    {
        templ = std::get<0>(links[i]);
        if (templ)
            break;
    }

    return templ;
}

static string createFullTemplateDir(const tuple<string, string, string> &templDir)
{
    string fullDir = "";
    if (!out_free_form)
    {        
        fullDir += splitDirectiveFull(std::get<0>(templDir));
        fullDir += splitDirectiveFull(std::get<1>(templDir));
        fullDir += splitDirectiveFull(std::get<2>(templDir));
    }
    else
        fullDir += std::get<0>(templDir) + std::get<1>(templDir) + std::get<2>(templDir);
    
    return fullDir;
}

struct templateDir
{
    templateDir() { isTemplateInModule = false; }

    pair<string, vector<SgStatement*>> templDecl;
    pair<string, vector<SgStatement*>> templDist;
    pair<string, SgStatement*> templDyn;
    bool isTemplateInModule;
};

static pair<templateDir, string>
    getNewTemplateDirective(DIST::Array *alignArray, const DIST::Arrays<int> &allArrays,
                            DIST::GraphCSR<int, double, attrType> &reducedG,
                            const DataDirective &dataDir, const vector<string> &distrRules,
                            const vector<vector<dist>> &distrRulesSt,
                            const map<DIST::Array*, set<DIST::Array*>> &arrayLinksByFuncCalls,
                            const uint64_t regionId, SgStatement *module, bool isMain, SgFile *file,
                            vector<Messages>& messagesForFile)
{   
    DIST::Array *templ = findLinkWithTemplate(alignArray, allArrays, reducedG, regionId);   
    bool templDynSt = false;
    string lastReturn = "";
    
    //checkNull(templ, convertFileName(__FILE__).c_str(), __LINE__);
    if (templ == NULL)
    {
        set<DIST::Array*> realArrayRefs;
        getRealArrayRefs(alignArray, alignArray, realArrayRefs, arrayLinksByFuncCalls);

        vector<DIST::Array*> templates;
        for (auto &array : realArrayRefs)
            templates.push_back(findLinkWithTemplate(array, allArrays, reducedG, regionId));
        
        if (templates.size() == 0)
        {
            __spf_print(1, "can not find templates\n");
            printInternalError(convertFileName(__FILE__).c_str(), __LINE__);
        }
        else if (templates.size() == 1)
            templ = templates[0];
        else
        {
            templ = templates[0];
            bool isErr = false;
            for (auto& t : templates)
            {
                if (t != templ)
                {
                    isErr = true;
                    __spf_print(1, "find more then one template: %s and %s, align array is %s\n", t->GetShortName().c_str(), templ->GetShortName().c_str(), alignArray->GetShortName().c_str());
                }
            }

            if (isErr)
            {
                auto allDecl = alignArray->GetDeclInfo();
                for (auto& decl : allDecl)
                {
                    std::wstring bufE, bufR;
                    __spf_printToLongBuf(bufE, L"Can not find align rules for array '%s'", to_wstring(alignArray->GetShortName()).c_str());
                    __spf_printToLongBuf(bufR, R171, to_wstring(alignArray->GetShortName()).c_str());
                    messagesForFile.push_back(Messages(ERROR, decl.second, bufR, bufE, 3020));
                }
                printInternalError(convertFileName(__FILE__).c_str(), __LINE__);
            }
        }
        
        lastReturn = "!DVM$ INHERIT\n";
    }
    else
        lastReturn = templ->GetShortName();

    auto allClones = templ->GetAllClones();
    templateDir retDir;

    int templIdx = findTeplatePosition(templ, dataDir);

    auto templDecl = genTemplateDelc(templ, templ->GetShortName(), file, module, isMain);
    retDir.templDecl.first = templDecl.first;
    retDir.templDecl.second.push_back(templDecl.second);
    for (auto &elem : allClones)
    {
        auto templDecl = genTemplateDelc(templ, elem.second, file, module, isMain);
        retDir.templDecl.first += templDecl.first;
        retDir.templDecl.second.push_back(templDecl.second);
    }

    auto templDist = genTemplateDistr(templ, templ->GetShortName(), distrRules, distrRulesSt, regionId, templIdx, isMain || module != NULL, file);
    retDir.templDist.first = templDist.first;
    retDir.templDist.second.push_back(templDist.second);

    if (templ->GetLocation().first == DIST::l_MODULE)
        retDir.isTemplateInModule = true;

    for (auto &elem : allClones)
    {
        vector<vector<dist>> tmpRuleSt = { elem.first };
        
        string strRule = elem.second + "(";
        for (int z = 0; z < elem.first.size(); ++z)
        {
            if (z != 0)
                strRule += ",";
            if (elem.first[z] == BLOCK)
                strRule += "BLOCK";
            else if (elem.first[z] == NONE)
                strRule += "*";
            else
                printInternalError(convertFileName(__FILE__).c_str(), __LINE__);
        }
        strRule += ")";
        vector<string> tmpStrRule = { strRule };

        auto templDist = genTemplateDistr(templ, elem.second, tmpStrRule, tmpRuleSt, regionId, 0, isMain || module != NULL, file);
        retDir.templDist.first += templDist.first;
        retDir.templDist.second.push_back(templDist.second);
    }

    retDir.templDyn = genDynamicDecl(templ, file);

    return make_pair(retDir, lastReturn);
}

static pair<string, string> getModuleRename(const set<SgStatement*>& allocatableStmts, const DIST::Array* array)
{
    if (array->GetLocation().first == DIST::l_MODULE)
    {
        set<string> arrayNames;
        for (auto& alloc : allocatableStmts)
            if (alloc->variant() == ALLOCATE_STMT)
                arrayNames.insert(array->GetNameInLocation(alloc));

        if (arrayNames.size() > 1 || arrayNames.size() == 0)
            printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

        return make_pair(array->GetShortName(), *arrayNames.begin());
    }
    else
        return make_pair("", "");
}

static void doRename(string& str, const pair<string, string>& renamePair)
{
    auto it = str.find(renamePair.first);
    if (it != string::npos)
        if (str[it + renamePair.first.size()] == '(' && str[it - 1] == ' ')
            str = str.replace(it, renamePair.first.size(), renamePair.second);
}

static pair<DIST::Array*, string>
getNewDirective(const string &fullArrayName, 
                const vector<string> &distrRules,
                const vector<string> &alignRules,
                const DataDirective &dataDir,
                const set<SgStatement*>& allocatableStmts,
                const pair<string, int>& position_decl)
{
    string out = "";
    DIST::Array* outA = NULL;

    for (int i = 0; i < dataDir.distrRules.size(); ++i)
    {
        if (dataDir.distrRules[i].first->GetName() == fullArrayName)
        {
            out += "!DVM$ DISTRIBUTE " + distrRules[i] + "\n";
            if (!out_free_form)
                out = splitDirective(out);

            return make_pair(dataDir.distrRules[i].first, out);
        }
    }

    for (int i = 0; i < dataDir.alignRules.size(); ++i)
    {
        auto alignArray = dataDir.alignRules[i].alignArray;
        if (alignArray->GetName() == fullArrayName)
        {
            string rule = alignRules[i];
            if (allocatableStmts.size())
            {
                auto it = rule.find("ALIGN");
                while (it != string::npos)
                {
                    rule = rule.replace(it, 5, "REALIGN");
                    it = rule.find("ALIGN", it + 7);
                }
                
                auto renamePair = getModuleRename(allocatableStmts, alignArray);
                doRename(rule, renamePair);
            }
            else if (alignArray->GetLocation().first == DIST::l_COMMON)
            {
                auto symb = alignArray->GetDeclSymbol(position_decl);
                if (symb->identifier() != alignArray->GetShortName())
                    doRename(rule, make_pair(alignArray->GetShortName(), symb->identifier()));
            }

            out += "!DVM$ " + rule + "\n";
            if (!out_free_form)
                out = splitDirective(out);

            return make_pair(alignArray, out);
        }
    }
    
    return make_pair(outA, out);
}

static SgStatement* firstExec(SgStatement *in, const string &currF)
{
    while (in)
    {
        if (isSgExecutableStatement(in) && !isSPF_stat(in) && in->fileName() == currF)
            return in;
        in = in->lexNext();
    }
    return in;
}

void insertLoopTempalteDeclaration(SgFile* file, const DataDirective& dataDir,
                                   const vector<string>& distrRules, const vector<vector<dist>>& distrRulesSt,
                                   const DIST::Arrays<int>& allArrays,
                                   const bool extractDir, const uint64_t regionId)
{
    vector<SgStatement*> modulesAndFuncs;
    getModulesAndFunctions(file, modulesAndFuncs);

    const set<DIST::Array*>& arrays = allArrays.GetArrays();
    set<DIST::Array*> loopArrays;
    for (auto& array : arrays)
        if (array->IsTemplate() && array->IsLoopArray())
            loopArrays.insert(array);

    if (loopArrays.size())
    {
        for (int i = 0; i < modulesAndFuncs.size(); ++i)
        {
            SgStatement* st = modulesAndFuncs[i];

            string name = "";
            if (st->variant() == PROG_HEDR || (st->variant() == FUNC_HEDR))
                name = ((SgFuncHedrStmt*)st)->name().identifier();
            else if (st->variant() == PROC_HEDR)
                name = ((SgProcHedrStmt*)st)->name().identifier();

            if (name == "")
                continue;

            for (auto& arrayPair : sortArraysByName(loopArrays))
            {
                DIST::Array* array = arrayPair.second;

                const auto& location = array->GetLocation();
                if (location.second == name)
                {
                    int templIdx = findTeplatePosition(array, dataDir);
                    string tmp = distrRules[templIdx];
                    convertToLower(tmp);
                    if (tmp.find("block") == string::npos)
                        continue;

                    auto templDecl = genTemplateDelc(array, array->GetShortName(), file, (SgStatement*)-1, true);
                    auto templDist = genTemplateDistr(array, array->GetShortName(), distrRules, distrRulesSt, regionId, templIdx, true, file);

                    SgStatement* nextSt = firstExec(st->lexNext(), st->fileName());
                    if (!extractDir)
                    {
                        auto cp = nextSt->controlParent();
                        nextSt->insertStmtBefore(*templDist.second, *cp);
                        nextSt->insertStmtBefore(*templDecl.second, *cp);
                    }
                }
            }
        }
    }
}

void insertTempalteDeclarationToMainFile(SgFile *file, const DataDirective &dataDir,
                                         const map<string, string> &templateDeclInIncludes,
                                         const vector<string> &distrRules, const vector<vector<dist>> &distrRulesSt, 
                                         const DIST::Arrays<int> &allArrays,
                                         const bool extractDir, const uint64_t regionId,
                                         const set<string> &includedToThisFile)
{
    vector<SgStatement*> modulesAndFuncs;
    getModulesAndFunctions(file, modulesAndFuncs);

    map<string, SgStatement*> modules;
    for (int i = 0; i < modulesAndFuncs.size(); ++i)
    {
        if (modulesAndFuncs[i]->variant() == MODULE_STMT)
            modules[modulesAndFuncs[i]->symbol()->identifier()] = modulesAndFuncs[i];
    }

    for (int i = 0; i < modulesAndFuncs.size(); ++i)
    {
        SgStatement *st = modulesAndFuncs[i];

        if (st->variant() == PROG_HEDR)
        {
            SgStatement *last = st->lastNodeOfStmt();
            set<string> includes;

            for (SgStatement *stLoc = modulesAndFuncs[i]; stLoc != last; stLoc = stLoc->lexNext())
            {
                if (stLoc->variant() == CONTAINS_STMT)
                    break;

                if (stLoc->fileName() != string(file->filename()))
                    includes.insert(stLoc->fileName());
            }

            vector<SgStatement*> useStmt;
            for (SgStatement *stLoc = modulesAndFuncs[i]; stLoc != last; stLoc = stLoc->lexNext())
            {
                if (stLoc->variant() == USE_STMT)
                    useStmt.push_back(stLoc);
            }

            for (auto &arrayPair : sortArraysByName(allArrays.GetArrays()))
            {
                DIST::Array* array = arrayPair.second;

                if (array->IsTemplate() && !array->IsLoopArray() && array->GetLocation().first != DIST::l_MODULE)
                {
                    int templIdx = findTeplatePosition(array, dataDir);
                    auto allClones = array->GetAllClones();
                    vector<SgStatement*> insertList1, insertList2;

                    auto templDeclP = genTemplateDelc(array, array->GetShortName(), file, NULL, true);
                    string templDecl = templDeclP.first;
                    insertList1.push_back(templDeclP.second);

                    for (auto& elem : allClones)
                    {
                        auto tmp = genTemplateDelc(array, elem.second, file, NULL, true);
                        templDecl += tmp.first;
                        insertList1.push_back(tmp.second);
                    }
                    
                    auto tmp = genTemplateDistr(array, array->GetShortName(), distrRules, distrRulesSt, regionId, templIdx, true, file);
                    string templDist = tmp.first;
                    insertList2.push_back(tmp.second);

                    for (auto &elem : allClones)
                    {
                        vector<vector<dist>> tmpRuleSt = { elem.first };

                        string strRule = elem.second + "(";
                        for (int z = 0; z < elem.first.size(); ++z)
                        {
                            if (z != 0)
                                strRule += ",";
                            if (elem.first[z] == BLOCK)
                                strRule += "BLOCK";
                            else if (elem.first[z] == NONE)
                                strRule += "*";
                            else
                                printInternalError(convertFileName(__FILE__).c_str(), __LINE__);
                        }
                        strRule += ")";
                        vector<string> tmpStrRule = { strRule };

                        tmp = genTemplateDistr(array, elem.second, tmpStrRule, tmpRuleSt, regionId, 0, true, file);
                        templDist += tmp.first;
                        insertList2.push_back(tmp.second);
                    }

                    auto tmpDyn = genDynamicDecl(array, file);
                    const string templDyn = tmpDyn.first;
                    const string fullDecl = createFullTemplateDir(make_tuple(templDecl, templDist, templDyn));                    
                              
                    bool needToInsert = true;
                    auto inIncl = templateDeclInIncludes.find(fullDecl);
                    if (inIncl != templateDeclInIncludes.end())
                    {
                        auto hasInThisFunc = includes.find(inIncl->second);
                        if (hasInThisFunc != includes.end())
                            needToInsert = false;
                    }

                    //check modules
                    if (needToInsert)
                    {
                        for (auto &use : useStmt)
                        {
                            string name = use->symbol()->identifier();
                            auto it = modules.find(name);
                            if (it == modules.end())
                                printInternalError(convertFileName(__FILE__).c_str(), __LINE__);
                            else
                            {
                                auto last = it->second->lastNodeOfStmt();
                                for (SgStatement *stLoc = it->second; stLoc != last; stLoc = stLoc->lexNext())
                                {
                                    const string &templName = array->GetShortName();

                                    if (stLoc->variant() == CONTAINS_STMT)
                                        break;

                                    const char *comment = stLoc->comments();
                                    if (comment)
                                    {
                                        if (string(comment).find(templName) != string::npos)
                                        {
                                            needToInsert = false;
                                            break;
                                        }
                                    }
                                }

                                if (needToInsert)
                                    break;
                            }
                        }
                    }

                    if (needToInsert)
                    {
                        SgStatement *nextSt = firstExec(st->lexNext(), st->fileName());
                        if (!extractDir)
                        {
                            auto cp = nextSt->controlParent();
                            for (int z = 0; z < insertList1.size(); ++z)
                            {
                                nextSt->insertStmtBefore(*insertList1[z], *cp);
                                nextSt->insertStmtBefore(*insertList2[z], *cp);
                            }
                            nextSt->insertStmtBefore(*tmpDyn.second, *cp);
                        }
                    }
                }
            }
        }
    }
}

static SgStatement* insertDvmhModule(SgStatement *firstSt, const vector<SgStatement*> &modulesAndFuncs)
{
    for (auto& st : modulesAndFuncs)
    {
        if (st->variant() == MODULE_STMT)
        {
            if (st->symbol()->identifier() == dvmhModuleName)
                return st;
        }
    }

    SgFuncHedrStmt *moduleN = new SgFuncHedrStmt((char*)dvmhModuleName.c_str());
    moduleN->setVariant(MODULE_STMT);
    moduleN->setlineNumber(getNextNegativeLineNumber());
    moduleN->setFileId(current_file_id);

    firstSt->insertStmtAfter(*moduleN, *firstSt);
    return moduleN;
}

extern "C" int out_free_form;
void correctTemplateModuleDeclaration(const string& folderName)
{
    int ifInOneFile = 0;

    map<SgFile*, vector<SgStatement*>> modsAndFuncs;

    for (int z = 0; z < CurrentProject->numberOfFiles(); ++z)
    {
        SgFile* file = &CurrentProject->file(z);
        getModulesAndFunctions(file, modsAndFuncs[file]);

        for (auto& elem : modsAndFuncs[file])
            if (elem->variant() == MODULE_STMT && elem->symbol()->identifier() == dvmhModuleName)
                if (elem->lexNext()->variant() != CONTROL_END)
                    ifInOneFile++;

    }

    set<string> unitedModuleDecl;
    set<string> fileNames;
    for (int z = 0; z < CurrentProject->numberOfFiles(); ++z)
    {
        SgFile* file = &CurrentProject->file(z);
        fileNames.insert(file->filename());

        for (auto& elem : modsAndFuncs[file])
        {
            if (elem->variant() == MODULE_STMT && elem->symbol()->identifier() == dvmhModuleName)
            {
                if (elem->lexNext()->variant() != CONTROL_END)
                {
                    for (auto st = elem->lexNext(); st != elem->lastNodeOfStmt(); st = st->lexNext())
                        unitedModuleDecl.insert(st->unparse());

                    if (ifInOneFile > 1)
                        elem->deleteStmt();
                }
                else
                    elem->deleteStmt();
                break;
            }
        }
    }

    int t = 0;
    if (ifInOneFile > 1)
    {
        const string base = "spfTemplateModuleDecl";
        string fName = (out_free_form == 0) ? (base + ".f") : (base + ".f90");
        
        while (fileNames.find(fName) != fileNames.end())
        {
            const string tmpB = base + std::to_string(t++);
            fName = (out_free_form == 0) ? (tmpB + ".f") : (tmpB + ".f90");
        }

        const string fullPath = (folderName == "") ? fName : (folderName + "/" + fName);

        FILE* fout = fopen(fullPath.c_str(), "w");
        if (fout == NULL)
            printInternalError(convertFileName(__FILE__).c_str(), __LINE__);
        else
            __spf_print(1, "  created new file for template module decl -> %s\n", fullPath.c_str());

        const string shift = (out_free_form == 0) ? "      " : "";

        const string first = shift + "module " + dvmhModuleName + "\n";
        const string last = shift + "end module" + "\n";

        string out = first;
        for (auto& elem : unitedModuleDecl)
            out += elem;
        out += last;

        fwrite(out.c_str(), sizeof(char), out.size(), fout);
        fclose(fout);
    }
}

static void findAllArrayRefs(SgExpression *ex, set<SgSymbol*> &refs)
{
    if (ex)
    {
        if (isArrayRef(ex))
        {
            SgSymbol* symb = ex->symbol();
            if (symb->type())
            {
                if (symb->type()->variant() == T_ARRAY)
                    refs.insert(ex->symbol());
            }
        }
        findAllArrayRefs(ex->lhs(), refs);
        findAllArrayRefs(ex->rhs(), refs);
    }
}

static bool hasPrivateAllInModule(SgStatement *mod)
{
    bool ret = false;
    if (mod && mod->variant() == MODULE_STMT)
    {
        for (SgStatement* st = mod->lexNext(); st != mod->lastNodeOfStmt(); st = st->lexNext())
        {
            if (isSgExecutableStatement(st))
                break;
            if (st->variant() == PRIVATE_STMT)
            {
                SgVarListDeclStmt* listPrivates = isSgVarListDeclStmt(st);
                checkNull(listPrivates, convertFileName(__FILE__).c_str(), __LINE__);
                if (listPrivates->numberOfVars() == 0)
                {
                    ret = true;
                    break;
                }
            }
        }
    }
    return ret;
}

void insertTemplateModuleUse(SgFile *file, const set<uint64_t> &regNums, const map<DIST::Array*, set<DIST::Array*>>& arrayLinksByFuncCalls)
{
    int funcNum = file->numberOfFunctions();
    map<string, set<string>> moduleUseMap = createMapOfModuleUses(file);

    vector<SgStatement*> modules;
    findModulesInFile(file, modules);

    for (int i = 0; i < funcNum; ++i)
    {
        SgStatement *st = file->functions(i);
        auto cp = st->controlParent();
        if (cp->variant() != GLOBAL &&
            cp->variant() != PROG_HEDR &&
            cp->variant() != PROC_HEDR &&
            cp->variant() != FUNC_HEDR)
            continue;
        else
        {
            //check for module
            if (cp->variant() == PROC_HEDR || cp->variant() == FUNC_HEDR)
            {
                if (cp->controlParent()->variant() == MODULE_STMT)
                    continue;
            }
        }

        set<SgSymbol*> refs;
        set<string> modUse;
        for (st = st->lexNext(); st; st = st->lexNext())
        {
            if (isSgExecutableStatement(st))
                break;
            if (st->variant() == VAR_DECL || st->variant() == VAR_DECL_90)
            {
                for (int z = 0; z < 3; ++z)
                    findAllArrayRefs(st->expr(z), refs);
            }
            else if (st->variant() == USE_STMT)
                modUse.insert(st->symbol()->identifier());
        }

        set<DIST::Array*> templates;
        for (auto &arrayR : refs)
        {
            SgStatement *decl = declaratedInStmt(arrayR);
            DIST::Array *currArray = getArrayFromDeclarated(decl, arrayR->identifier());
            checkNull(currArray, convertFileName(__FILE__).c_str(), __LINE__);

            if (!currArray->IsNotDistribute())
            {
                set<DIST::Array*> realRefs;
                getRealArrayRefs(currArray, currArray, realRefs, arrayLinksByFuncCalls);

                if (!(realRefs.size() == 1 && realRefs.find(currArray) != realRefs.end()))
                    continue;

                for (auto& num : regNums)
                {
                    auto templ = currArray->GetTemplateArray(num);
                    if (templ)
                    {
                        if (templ->GetLocation().first == DIST::l_MODULE)
                        {
                            bool needToAdd = true;
                            if (modUse.find(dvmhModuleName) == modUse.end())
                            {
                                for (auto& elem : modUse)
                                {
                                    auto it = moduleUseMap.find(elem);
                                    if (it != moduleUseMap.end())
                                    {
                                        SgStatement* currMod = NULL;
                                        for (auto& modV : modules)
                                            if (modV->symbol()->identifier() == elem)
                                                currMod = modV;

                                        if (it->second.find(dvmhModuleName) != it->second.end() && !hasPrivateAllInModule(currMod))
                                        {
                                            needToAdd = false;
                                            break;
                                        }
                                    }
                                }
                            }
                            if (needToAdd)
                                templates.insert(templ);
                        }
                    }
                }
            }
        }
        
        if (templates.size())
        {
            st = file->functions(i);

            SgStatement* useSt = new SgStatement(USE_STMT);
            useSt->setSymbol(*findSymbolOrCreate(file, dvmhModuleName));
            useSt->setlineNumber(getNextNegativeLineNumber());
            st->insertStmtAfter(*useSt, *st);
        }
    }	
}

static set<SgStatement*> filterAllocateStats(SgFile* file, const vector<SgStatement*> &currentAlloc, const string &array)
{
    set<SgStatement*> filtered;

    const string fileName = file->filename();
    for (auto &stat : currentAlloc)
    {
        set<string> arraySyns;
        arraySyns.insert(array);

        if (stat->fileName() != fileName)
            if (!stat->switchToFile())
                printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

        SgExpression* list = stat->expr(0);
        bool find = false;
        while (list)
        {
            if (list->lhs() && list->lhs()->symbol())
            {
                if (OriginalSymbol(list->lhs()->symbol())->identifier() == array)
                {
                    find = true;
                    break;
                }
            }

            list = list->rhs();
        }

        if (find)
            filtered.insert(stat);

        SgFile::switchToFile(fileName);
    }

    //TODO: removed this checking 17.04.2020
    /*if (filtered.size() != 1)
        printInternalError(convertFileName(__FILE__).c_str(), __LINE__);*/
    return filtered;
}

// by file and function
static map<string, map<string, set<string>>> dynamicDirsByFile;
static map<string, map<string, set<string>>> dynamicArraysByFile;
static map<string, map<string, set<string>>> alignArraysByFile;
static map<string, map<string, map<string, pair<SgExpression*, SgExpression*>>>> insertedShadowByFile;

static inline void addStringToComments(const vector<string> &toInsert, map<string, map<int, set<string>>> &commentsToInclude,
                                       const string& fileName, const int line)
{
    //check for file in project
    int totalFiles = CurrentProject->numberOfFiles();
    string wasFile = current_file->filename();

    bool exist = false;
    for (int z = 0; z < totalFiles && !exist; ++z)
        if (CurrentProject->file(z).filename() == fileName)
            exist = true;

    SgFile::switchToFile(wasFile);
    if (exist)
        return;

    auto currF = commentsToInclude.find(fileName);
    if (currF == commentsToInclude.end())
        currF = commentsToInclude.insert(currF, make_pair(fileName, map<int, set<string>>()));

    auto place = currF->second.find(line);
    if (place == currF->second.end())
        place = currF->second.insert(place, make_pair(line, set<string>()));

    for (auto &str : toInsert)
    {
        if (str == "")
            continue;

        bool needToInsert = true;        
        for (auto &elem : currF->second)
        {
            for (auto addedStr : elem.second)
            {
                if (addedStr == str)
                {
                    needToInsert = false;
                    break;
                }
            }

            if (needToInsert == false)
                break;
        }

        if (needToInsert)
            place->second.insert(str);
    }
}

static void addComment(SgStatement* st, const string& toAdd, bool needUniq = false)
{
    if (needUniq)
    {
        char* comm = st->comments();
        if (comm == NULL)
            st->addComment(toAdd.c_str());
        else if (string(comm).find(toAdd) == string::npos)
            st->addComment(toAdd.c_str());
    }
    else
        st->addComment(toAdd.c_str());
}

static string getFullArrayName(SgSymbol *symb)
{
    string fullArrayName = "";
    if (symb->type()->variant() == T_ARRAY)
    {
        auto uniqKey = getFromUniqTable(symb);
        fullArrayName = getShortName(uniqKey);
    }
    return fullArrayName;
}

static vector<SgExpression*> createVarListFromDecl(const int currV, SgStatement *st)
{
    vector<SgExpression*> varList;
    if (currV == COMM_STAT)
    {
        map<string, vector<SgExpression*>> commonBlocks;
        getCommonBlocksRef(commonBlocks, st, st->lexNext());
        for (auto &elem : commonBlocks)
        {
            for (auto &commList : elem.second)
            {
                SgExpression *list = commList->lhs();
                while (list)
                {
                    varList.push_back(list->lhs());
                    list = list->rhs();
                }
            }
        }
    }
    else
    {
        SgVarDeclStmt *varDecl = (SgVarDeclStmt*)st;
        SgExpression *varL = varDecl->varList();
        while (varL)
        {
            varList.push_back(varL->lhs());
            varL = varL->rhs();
        }
    }

    return varList;
}

static string getFullArrayNameOfParameter(SgStatement* fPointer, const int numOfParams, const int currPar)
{
    string oldFile = current_file->filename(); //save file    
    if (!fPointer->switchToFile())
        printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

    auto hedrOfIface = (SgProgHedrStmt*)fPointer;
    if (hedrOfIface->numberOfParameters() != numOfParams)
        printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

    auto sInIface = hedrOfIface->parameter(currPar);
    const string fullArrayName = getFullArrayName(sInIface);

    if (SgFile::switchToFile(oldFile) == -1)
        printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

    return fullArrayName;
}

static void createInherit(pair<SgStatement*, SgStatement*>& inheritDir, SgStatement* insertBefore, SgExpression* ref)
{
    if (inheritDir.first == NULL)
    {

        inheritDir.first = new SgStatement(DVM_INHERIT_DIR);
        SgExpression* list = new SgExpression(EXPR_LIST);
        list->setLhs(ref);
        inheritDir.first->setExpression(0, *list);

        inheritDir.second = insertBefore;
    }
    else
    {
        SgExpression* list = new SgExpression(EXPR_LIST);
        list->setLhs(ref);
        list->setRhs(inheritDir.first->expr(0));
        inheritDir.first->setExpression(0, *list);
    }
}

void insertDistributionToFile(SgFile *file, const char *fin_name, const DataDirective &dataDir, 
                              const set<string> &distrArrays, const vector<string> &distrRules, 
                              const vector<vector<dist>> &distrRulesSt,
                              const vector<string> &alignRules, const map<string, vector<LoopGraph*>> &loopGraph,
                              const DIST::Arrays<int> &allArrays,
                              DIST::GraphCSR<int, double, attrType> &reducedG,
                              map<string, map<int, set<string>>> &commentsToInclude,
                              map<string, string> &templateDeclInIncludes,
                              const bool extractDir, vector<Messages> &messagesForFile,
                              const map<DIST::Array*, set<DIST::Array*>> &arrayLinksByFuncCalls,
                              const map<string, FuncInfo*>& funcsInFile,
                              const uint64_t regionId, const set<string>& allFileNames)
{
    const string currFilename = file->filename();
    vector<SgStatement*> modulesAndFuncs;
    getModulesAndFunctions(file, modulesAndFuncs);

    SgStatement *dvmhModule = NULL;
    for (int i = 0; i < modulesAndFuncs.size(); ++i)
    {
        SgStatement *isModule = (modulesAndFuncs[i]->variant() == MODULE_STMT) ? modulesAndFuncs[i] : NULL;
        if (isModule)
        {
            dvmhModule = insertDvmhModule(file->firstStatement(), modulesAndFuncs);
            break;
        }
    }    

    map<SgStatement*, set<string>> dvmhModuleSt;
    if (dvmhModule)
        dvmhModuleSt[dvmhModule] = set<string>();
    
    for (int i = 0; i < modulesAndFuncs.size(); ++i)
    {
        SgStatement *st = modulesAndFuncs[i];
        FuncInfo* currFI = NULL;
        if (st->variant() != MODULE_STMT)
        {
            for (auto& funcs : funcsInFile)
                if (funcs.second->funcPointer->GetOriginal() == st)
                    currFI = funcs.second;
            
            if (currFI == NULL)
            {
                __spf_print(1, "function '%s' not found in map of functons\n", st->symbol()->identifier());
                printInternalError(convertFileName(__FILE__).c_str(), __LINE__);
            }
        }

        SgStatement *lastNode = st->lastNodeOfStmt();
        const auto lineRange = make_pair(st->lineNumber(), lastNode->lineNumber());
        const string filename = st->fileName();

        set<string> templateDelc;

        SgStatement *isModule = (st->variant() == MODULE_STMT) ? st : NULL;
        bool isMain = (st->variant() == PROG_HEDR);

        const string modName = st->symbol()->identifier();
        map<string, pair<SgExpression*, SgExpression*>> &insertedShadow = insertedShadowByFile[fin_name][modName];
        set<string> &dynamicDirs = dynamicDirsByFile[fin_name][modName];
        set<string> &dynamicArraysAdded = dynamicArraysByFile[fin_name][modName];
        set<string> &alignArrays = alignArraysByFile[fin_name][modName];

        // PAIR<dir, insertBefore>
        pair<SgStatement*, SgStatement*> inheritDir = make_pair((SgStatement*)NULL, (SgStatement*)NULL);
        map<SgStatement*, pair<SgStatement*, SgStatement*>> inheritDirIface;

        while (st != lastNode)
        {
            if (st == NULL)
            {

                messagesForFile.push_back(Messages(ERROR, 1, R128, L"internal error in analysis, parallel directives will not be generated for this file!", 3008));

                __spf_print(1, "internal error in analysis, parallel directives will not be generated for this file!\n");
                break;
            }
            
            if (st->variant() == CONTAINS_STMT)
                break;

            const int currV = st->variant();
            if (currV == VAR_DECL || currV == VAR_DECL_90 || currV == DIM_STAT || currV == COMM_STAT)
            {
                auto varList = createVarListFromDecl(currV, st);

                set<DIST::Array*> dynamicArrays;
                set<DIST::Array*> dynamicArraysLocal;
                for (auto &varExp : varList)
                {
                    if (isArrayRef(varExp))
                    {
                        SgSymbol *currSymb = OriginalSymbol(varExp->symbol());
                        const string fullArrayName = getFullArrayName(currSymb);
                        const string arrayName = string(currSymb->identifier());

                        if (isModule == NULL && extractDir == false)
                        {
                            SgStatement* st_cp = st->controlParent();
                            if (st_cp->variant() == PROC_HEDR || st_cp->variant() == FUNC_HEDR)
                            {
                                if (st_cp->controlParent()->variant() == INTERFACE_STMT) // interface in procedure, check distribution state
                                {
                                    auto hedr = ((SgProgHedrStmt*)st_cp);
                                    int numOfParams = hedr->numberOfParameters();
                                    int z = 0;
                                    for (; z < numOfParams; ++z)
                                    {
                                        auto t = hedr->parameter(z);
                                        if (t->identifier() == arrayName)
                                            break;
                                    }

                                    if (numOfParams)
                                    {
                                        if (z == numOfParams)
                                            printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

                                        const string fName = hedr->name().identifier();
                                        auto itIface = currFI->interfaceBlocks.find(fName);
                                        if (itIface == currFI->interfaceBlocks.end())
                                            printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

                                        if (itIface->second == NULL)
                                            printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

                                        set<string> fullArrayNames;
                                        if (itIface->second->fullCopiesOfThisFunction.size() == 0)
                                            if (itIface->second->funcPointer->variant() > 0)
                                                fullArrayNames.insert(getFullArrayNameOfParameter(itIface->second->funcPointer->GetOriginal(), numOfParams, z));

                                        for (auto& fullCopies : itIface->second->fullCopiesOfThisFunction)
                                            if (fullCopies->funcPointer->variant() > 0)
                                                fullArrayNames.insert(getFullArrayNameOfParameter(fullCopies->funcPointer->GetOriginal(), numOfParams, z));

                                        bool needToInherit = false;
                                        for (auto& fullArrayName : fullArrayNames)
                                            if (distrArrays.find(fullArrayName) != distrArrays.end())
                                                needToInherit = true;

                                        if (needToInherit)
                                        {
                                            auto it = inheritDirIface.find(st_cp);
                                            if (it == inheritDirIface.end())
                                                it = inheritDirIface.insert(it, make_pair(st_cp, make_pair((SgStatement*)NULL, (SgStatement*)NULL)));
                                            createInherit(it->second, st_cp, new SgVarRefExp(currSymb));
                                        }
                                    }
                                }
                            }
                        }

                        if (distrArrays.find(fullArrayName) != distrArrays.end())
                        {
                            const vector<SgStatement*> &allocatableStmtsCopy = getAttributes<SgStatement*, SgStatement*>(st, set<int>{ ALLOCATE_STMT });
                            set<SgStatement*> allocatableStmts;
                            if (allocatableStmtsCopy.size())
                                allocatableStmts = filterAllocateStats(file, allocatableStmtsCopy, currSymb->identifier());
   
                            pair<DIST::Array*, string> dirWithArray = getNewDirective(fullArrayName, distrRules, alignRules, dataDir, allocatableStmts, make_pair(st->fileName(), st->lineNumber()));

                            string toInsert = dirWithArray.second;
                            if (toInsert != "")
                            {
                                auto alignIt = alignArrays.find(fullArrayName);
                                if (alignIt == alignArrays.end() && !extractDir)
                                    alignArrays.insert(alignIt, fullArrayName);
                                else if (alignIt != alignArrays.end() && extractDir)
                                    alignArrays.erase(alignIt);
                                else
                                    toInsert = "";

                                const pair<templateDir, string> &templDir =
                                    getNewTemplateDirective(dirWithArray.first, allArrays, reducedG, dataDir, distrRules, distrRulesSt, arrayLinksByFuncCalls, regionId, isModule, isMain, file, messagesForFile);
                                string templDecl = templDir.first.templDecl.first; //std::get<0>(templDir.first);

                                //if array is inherit array 
                                if (templDir.second == "!DVM$ INHERIT\n")
                                {
                                    toInsert = "";
                                    if (extractDir == false)
                                        createInherit(inheritDir, st, new SgVarRefExp(findSymbolOrCreate(file, dirWithArray.first->GetShortName())));
                                }
                                else
                                    dynamicArraysLocal.insert(dirWithArray.first);
                                dynamicArrays.insert(dirWithArray.first);

                                if (templDir.second == "!DVM$ INHERIT\n" && templDir.first.isTemplateInModule)
                                    templDecl = "";
                                else
                                {
                                    if (templateDelc.find(templDecl) == templateDelc.end())
                                        templateDelc.insert(templDecl);
                                    else
                                        templDecl = "";

                                    if (templDecl != "")
                                        templDecl = createFullTemplateDir(make_tuple(templDir.first.templDecl.first, templDir.first.templDist.first, templDir.first.templDyn.first));
                                }

                                const bool isFromInclude = allFileNames.find(st->fileName()) == allFileNames.end();

                                if (isMain)
                                    templDecl = "";
                                else
                                {
                                    set<DIST::Array*> realRefs;
                                    getRealArrayRefs(dirWithArray.first, dirWithArray.first, realRefs, arrayLinksByFuncCalls);
                                    for (auto &elem : realRefs)
                                    {
                                        if (elem->GetTemplateArray(regionId)->GetLocation().first == DIST::l_MODULE)
                                        {
                                            templDecl = "";
                                            break;
                                        }
                                    }
                                }

                                vector<string> toAdd;
                                if (isModule && dvmhModule)
                                    toAdd = { toInsert, "" };
                                else
                                    toAdd = { templDecl, toInsert };

                                if (extractDir)
                                {
                                    if (allocatableStmts.size())
                                    {
                                        if (isModule && dvmhModule)
                                        {
                                            for (auto& elem : allocatableStmts)
                                            {
                                                if (elem->fileName() != currFilename)
                                                    continue;

                                                extractComments(elem->lexNext(), toAdd[0] + toAdd[1]);
                                            }
                                            extractComments(st, ("!DVM$ ALIGN :: " + dirWithArray.first->GetShortName() + "\n").c_str());
                                        }
                                        else
                                        {
                                            for (auto& elem : allocatableStmts)
                                            {
                                                if (elem->fileName() != currFilename)
                                                    continue;

                                                extractComments(elem->lexNext(), toInsert);
                                            }
                                            extractComments(st, (templDecl + "!DVM$ ALIGN :: " + dirWithArray.first->GetShortName() + "\n").c_str());
                                        }
                                    }
                                    else
                                        extractComments(st, toAdd[0] + toAdd[1]);
                                }
                                else
                                {
                                    if (allocatableStmts.size())
                                    {
                                        if (isModule && dvmhModule)
                                        {
                                            for (auto& elem : allocatableStmts)
                                            {
                                                if (elem->fileName() != currFilename)
                                                    continue;

                                                if (elem->lexNext()->variant() != CONT_STAT)
                                                    elem->insertStmtAfter(*new SgContinueStmt(), *elem->controlParent());
                                                addComment(elem->lexNext(), (toAdd[0] + toAdd[1]).c_str(), true);
                                            }

                                            const string newDecl = "!DVM$ ALIGN :: " + dirWithArray.first->GetShortName() + "\n";
                                            if (isFromInclude)
                                                addStringToComments({ newDecl }, commentsToInclude, st->fileName(), st->lineNumber());
                                            else
                                                st->addComment(newDecl.c_str());
                                        }
                                        else
                                        {
                                            for (auto& elem : allocatableStmts)
                                            {
                                                if (elem->fileName() != currFilename)
                                                    continue;

                                                if (elem->lexNext()->variant() != CONT_STAT)
                                                    elem->insertStmtAfter(*new SgContinueStmt(), *elem->controlParent());
                                                addComment(elem->lexNext(), toInsert.c_str(), true);
                                            }

                                            const string newDecl = templDecl + "!DVM$ ALIGN :: " + dirWithArray.first->GetShortName() + "\n";
                                            if (isFromInclude)
                                                addStringToComments({ newDecl }, commentsToInclude, st->fileName(), st->lineNumber());
                                            else
                                                st->addComment(newDecl.c_str());
                                        }
                                    }
                                    else
                                    {
                                        if (isFromInclude)
                                            addStringToComments({ toAdd }, commentsToInclude, st->fileName(), st->lineNumber());
                                        else
                                            st->addComment((toAdd[0] + toAdd[1]).c_str());
                                    }

                                    if (isFromInclude)
                                        if (templDecl != "")
                                            templateDeclInIncludes[templDecl] = st->fileName();

                                    if (isModule && dvmhModule)
                                    {
                                        if (dvmhModuleSt[dvmhModule].find(templDir.first.templDyn.first) == dvmhModuleSt[dvmhModule].end())
                                        {
                                            dvmhModule->insertStmtAfter(*templDir.first.templDyn.second, *dvmhModule);
                                            dvmhModuleSt[dvmhModule].insert(templDir.first.templDyn.first);
                                        }

                                        if (dvmhModuleSt[dvmhModule].find(templDir.first.templDecl.first) == dvmhModuleSt[dvmhModule].end())
                                        {
                                            for (int z = 0; z < templDir.first.templDecl.second.size(); ++z)
                                                dvmhModule->insertStmtAfter(*templDir.first.templDecl.second[z], *dvmhModule);
                                            dvmhModuleSt[dvmhModule].insert(templDir.first.templDecl.first);
                                        }

                                        if (dvmhModuleSt[dvmhModule].find(templDir.first.templDist.first) == dvmhModuleSt[dvmhModule].end())
                                        {
                                            for (int z = 0; z < templDir.first.templDist.second.size(); ++z)
                                                dvmhModule->insertStmtAfter(*templDir.first.templDist.second[z], *dvmhModule);
                                            dvmhModuleSt[dvmhModule].insert(templDir.first.templDist.first);
                                        }

                                        const string useS = dvmhModule->symbol()->identifier();
                                        string str = string("use ") + useS;
                                        if (dvmhModuleSt[isModule].find(str) == dvmhModuleSt[isModule].end())
                                        {
                                            dvmhModuleSt[isModule].insert(str);

                                            SgStatement *useSt = new SgStatement(USE_STMT);
                                            useSt->setSymbol(*findSymbolOrCreate(file, useS));
                                            useSt->setlineNumber(getNextNegativeLineNumber());
                                            isModule->insertStmtAfter(*useSt, *isModule);
                                        }
                                    }
                                }
                            }
                            else // may be INHERIT array?
                            {
                                SgStatement* st_cp = st->controlParent();
                                if (!extractDir && (st_cp->variant() == PROC_HEDR || st_cp->variant() == FUNC_HEDR))
                                {
                                    SgProgHedrStmt* hedr = (SgProgHedrStmt*)st_cp;
                                    for (int z = 0; z < hedr->numberOfParameters(); ++z)
                                    {
                                        if (hedr->parameter(z)->identifier() == string(currSymb->identifier()))
                                        {
                                            auto array = getArrayFromDeclarated(declaratedInStmt(currSymb), currSymb->identifier());
                                            checkNull(array, convertFileName(__FILE__).c_str(), __LINE__);
                                      
                                            set<DIST::Array*> realRefs;
                                            getRealArrayRefs(array, array, realRefs, arrayLinksByFuncCalls);

                                            bool distributed = false;
                                            int rank = -1;
                                            bool diffRank = false;

                                            for (auto& elem : realRefs)
                                            {
                                                if (elem->IsNotDistribute() == false)
                                                {
                                                    distributed = true;
                                                    int oldRank = rank;
                                                    rank = elem->GetDimSize();

                                                    if (oldRank != -1 && oldRank != elem->GetDimSize())
                                                        diffRank = true;
                                                }
                                            }

                                            if (distributed && !diffRank && rank == array->GetDimSize())
                                                createInherit(inheritDir, st, new SgVarRefExp(currSymb));
                                            break;
                                        }
                                    }
                                } 
                            }
                        }
                    }
                }

                string toInsert = "!DVM$ DYNAMIC ";
                set<string> toInsertArrays;
                for (auto &array : dynamicArraysLocal)
                {
                    string name = array->GetShortName();
                    if (array->GetLocation().first == DIST::l_COMMON)
                    {
                        auto symb = array->GetDeclSymbol(make_pair(st->fileName(), st->lineNumber()));
                        name = symb->identifier();
                    }

                    if (extractDir)
                    {
                        if (dynamicArraysAdded.find(name) != dynamicArraysAdded.end())
                        {
                            dynamicArraysAdded.erase(name);
                            toInsertArrays.insert(name);
                        }
                    }
                    else
                    {
                        if (dynamicArraysAdded.find(name) == dynamicArraysAdded.end())
                        {
                            dynamicArraysAdded.insert(name);
                            toInsertArrays.insert(name);
                        }
                    }
                }

                int z = 0;
                for (auto &array : toInsertArrays)
                {
                    if (z != 0)
                        toInsert += ",";
                    toInsert += array;
                    ++z;
                }

                if (z != 0)
                {
                    toInsert += "\n";
                    auto dynIt = dynamicDirs.find(toInsert);

                    if (dynIt == dynamicDirs.end() && !extractDir)
                        dynamicDirs.insert(dynIt, toInsert);
                    else if (extractDir && dynIt != dynamicDirs.end())
                        dynamicDirs.erase(dynIt);
                    else
                        toInsert = "";
                }
                else
                    toInsert = "";
                
                if (dynamicArrays.size() > 0)
                    createShadowSpec(loopGraph, arrayLinksByFuncCalls, dynamicArrays);
                
                if (toInsert != "")
                {
                    if (!strcmp(st->fileName(), fin_name))
                    {
                        if (extractDir)
                            extractComments(st, toInsert); 
                        else
                            st->addComment(toInsert.c_str());
                    }
                    else
                    {
                        if (!extractDir)
                            addStringToComments({ toInsert }, commentsToInclude, st->fileName(), st->lineNumber());
                    }
                }
            }
            st = st->lexNext();
        }

        if (extractDir == false)
        {
            if (inheritDir.second)
            {
                inheritDir.second->insertStmtBefore(*inheritDir.first, *inheritDir.second->controlParent());

                SgStatement* dynamicDir = new SgStatement(DVM_DYNAMIC_DIR);
                dynamicDir->setExpression(0, inheritDir.first->expr(0)->copy());
                inheritDir.second->insertStmtBefore(*dynamicDir, *inheritDir.second->controlParent());
            }

            for (auto& inherit : inheritDirIface)
            {
                SgStatement* dynamicDir = new SgStatement(DVM_DYNAMIC_DIR);
                dynamicDir->setExpression(0, inherit.second.first->expr(0)->copy());
                inherit.first->insertStmtAfter(*dynamicDir, *inherit.first);

                inherit.first->insertStmtAfter(*inherit.second.first, *inherit.first);
            }
        }
    }

    if (extractDir)
    {
        if (dvmhModule)
            dvmhModule->deleteStmt();
    }
    else
    {
        //TODO: do it better!!
        if (dvmhModule)
        {
            if (dvmhModule->lexNext()->variant() == CONTROL_END)
                dvmhModule->deleteStmt();
        }
    }
}

void insertShadowSpecToFile(SgFile *file, const char *fin_name, const set<string> &distrArrays, 
                            DIST::GraphCSR<int, double, attrType> &reducedG,
                            map<string, map<int, set<string>>> &commentsToInclude,
                            const bool extractDir, vector<Messages> &messagesForFile,
                            const map<tuple<int, string, string>, pair<DIST::Array*, DIST::ArrayAccessInfo*>> &declaredArrays)
{
    vector<SgStatement*> modulesAndFuncs;
    getModulesAndFunctions(file, modulesAndFuncs);

    for (int i = 0; i < modulesAndFuncs.size(); ++i)
    {
        SgStatement *st = modulesAndFuncs[i];
        SgStatement *lastNode = st->lastNodeOfStmt();

        const string modName = st->symbol()->identifier();
        map<string, pair<SgExpression*, SgExpression*>> &insertedShadow = insertedShadowByFile[fin_name][modName];

        while (st != lastNode)
        {
            if (st == NULL)
            {
                messagesForFile.push_back(Messages(ERROR, 1, R128, L"internal error in analysis, parallel directives will not be generated for this file!", 3008));

                __spf_print(1, "internal error in analysis, parallel directives will not be generated for this file!\n");
                break;
            }

            if (st->variant() == CONTAINS_STMT)
                break;

            const int currV = st->variant();
            if (currV == VAR_DECL || currV == VAR_DECL_90 || currV == DIM_STAT || currV == COMM_STAT)
            {
                auto varList = createVarListFromDecl(currV, st);
                                
                set<DIST::Array*> declaratedDistrArrays;
                for (auto &varExpr : varList)
                {
                    if (isArrayRef(varExpr))
                    {
                        SgSymbol *currSymb = OriginalSymbol(varExpr->symbol());
                        const string fullArrayName = getFullArrayName(currSymb);

                        if (distrArrays.find(fullArrayName) != distrArrays.end())
                        {
                            auto uniqKey = getFromUniqTable(currSymb);
                            auto itArr = declaredArrays.find(uniqKey);
                            if (itArr != declaredArrays.end())                                
                                declaratedDistrArrays.insert(itArr->second.first);
                        }
                    }
                }

                vector<pair<SgExpression*, SgExpression*>> shadowsSpecs;
                vector<string> shadowsSpecsString;

                for (auto &arrayPair : sortArraysByName(declaratedDistrArrays))
                {
                    DIST::Array* array = arrayPair.second;
                    const vector<pair<int, int>> currSpec = array->GetShadowSpec();
                    bool needToGen = false;

                    for (auto &elem : currSpec)
                        if (elem.first != 0 || elem.second != 0)
                            needToGen = true;

                    if (needToGen)
                    {
                        string name = array->GetShortName();
                        if (array->GetLocation().first == DIST::l_COMMON)
                        {
                            auto symb = array->GetDeclSymbol(make_pair(st->fileName(), st->lineNumber()));
                            name = symb->identifier();
                        }

                        string shadowSpecInsert = "!DVM$ SHADOW " + name + "(";
                        for (int k = 0; k < currSpec.size(); ++k)
                        {
                            char buf[256];
                            sprintf(buf, "%d:%d", currSpec[k].first, currSpec[k].second);
                            shadowSpecInsert += buf;
                            if (k != currSpec.size() - 1)
                                shadowSpecInsert += ",";
                        }
                        shadowSpecInsert += ")\n";

                        shadowsSpecsString.push_back(shadowSpecInsert);
                        pair<SgExpression*, SgExpression*> newSpec = genShadowSpec(file, make_pair(name, currSpec));
                        if (newSpec.first == NULL || newSpec.second == NULL)
                            printInternalError(convertFileName(__FILE__).c_str(), __LINE__);
                        shadowsSpecs.push_back(newSpec);
                    }
                }
                
                if (shadowsSpecsString.size() > 0)
                {
                    if (!strcmp(st->fileName(), fin_name))
                    {
                        if (!extractDir)
                        {
                            for (int i = 0; i < shadowsSpecs.size(); ++i)
                            {
                                SgStatement *tmp = new SgStatement(DVM_SHADOW_DIR, NULL, NULL, shadowsSpecs[i].first, shadowsSpecs[i].second, NULL);
                                st->insertStmtAfter(*tmp, *st->controlParent());
                                insertedShadow.insert(make_pair(shadowsSpecs[i].first->unparse(), shadowsSpecs[i]));
                            }
                        }
                        else
                        {
                            for (int i = 0; i < shadowsSpecs.size(); ++i)
                            {
                                auto it = insertedShadow.find(shadowsSpecs[i].first->unparse());
                                if (it != insertedShadow.end())
                                    insertedShadow.erase(it);
                            }
                        }
                    }
                    else
                    {
                        if (!extractDir)
                            addStringToComments(shadowsSpecsString, commentsToInclude, st->fileName(), st->lineNumber());
                    }
                }
            }
            st = st->lexNext();
        }
    }
}

void insertDistributionToFile(const char *origFile, const char *outFile, const map<int, set<string>> &commentsToInclude)
{
    FILE *F = fopen(origFile, "r");

    vector<string> lines;
    set<string> uniqLines;
    while (!feof(F))
    {
        char buf[8192];
        char *res = fgets(buf, 8192, F);
        if (res)
        {
            string bufStr(buf);
            convertToLower(bufStr);
 
            bool iSPF = isSPF_comment(bufStr);
            bool iDVM = isDVM_comment(bufStr);

            if ((iSPF && (keepSpfDirs == 0)) || iDVM)
            {
                lines.push_back(" ");
                uniqLines.insert(" ");
            }
            else            
            {
                lines.push_back(buf);
                uniqLines.insert(buf);
            }            
        }
    }
    
    fclose(F);
    
    F = fopen(outFile, "w");
    //save order 
    int idx = 0;
    for (auto k = commentsToInclude.begin(); k != commentsToInclude.end(); ++k)
    {
        int currLine = k->first - 1;
        if (currLine > idx)
        {
            if (idx < lines.size())
            {
                for ( ; idx < currLine; ++idx)
                    if (lines[idx] != " " && lines[idx] != "\n")
                        fprintf(F, "%s", lines[idx].c_str());
            }
            for (auto it = k->second.begin(); it != k->second.end(); ++it) 
                fprintf(F, "%s", (*it).c_str());
        }
        else
            for (auto it = k->second.begin(); it != k->second.end(); ++it)
                fprintf(F, "%s", (*it).c_str());
    }

    for ( ; idx < lines.size(); ++idx)
        if (lines[idx] != " " && lines[idx] != "\n")
            fprintf(F, "%s", lines[idx].c_str());    
    fclose(F);
}

void insertDistributeDirsToParallelRegions(const vector<ParallelRegionLines> *currLines,
                                           const vector<Statement*> &reDistrRulesBefore,
                                           const vector<Statement*> &reDistrRulesAfter,
                                           const vector<Statement*> &reAlignRules)
{
    if (currLines)
    {
        for (int i = 0; i < currLines->size(); ++i)
        {
            SgStatement *insertBefore = (*currLines)[i].stats.first;
            SgStatement *insertAfter = (*currLines)[i].stats.second;

            if (insertBefore == NULL || insertAfter == NULL)
                continue;

            SgStatement *controlParentAfter = insertAfter->controlParent();
            if (insertAfter->variant() == CONTROL_END)
                controlParentAfter = controlParentAfter->controlParent();

            while (insertBefore->lexPrev()->variant() == DVM_PARALLEL_ON_DIR || insertBefore->lexPrev()->variant() == DVM_REDISTRIBUTE_DIR)
                insertBefore = insertBefore->lexPrev();
            
            while (insertAfter->lexPrev()->variant() == DVM_PARALLEL_ON_DIR)
                insertAfter = insertAfter->lexPrev();
            while (insertAfter->lexNext()->variant() == DVM_REDISTRIBUTE_DIR)
                insertAfter = insertAfter->lexNext();

            while (insertBefore && isSgExecutableStatement(insertBefore) == NULL)
                insertBefore = insertBefore->lexNext();

            for (int k = 0; k < reDistrRulesBefore.size(); ++k)
                insertBefore->insertStmtBefore(reDistrRulesBefore[k]->copy(), *insertBefore->controlParent());

            for (int k = 0; k < reDistrRulesAfter.size(); ++k)
                insertAfter->insertStmtAfter(reDistrRulesAfter[k]->copy(), *controlParentAfter);

            for (int k = 0; k < reAlignRules.size(); ++k)
                insertBefore->insertStmtBefore(reAlignRules[k]->copy(), *insertBefore->controlParent());

            // insert redistribute before all return stats
            for (SgStatement *st = (*currLines)[i].stats.first; st->variant() != SPF_END_PARALLEL_REG_DIR; st = st->lexNext())
            {
                if (st->variant() == RETURN_STAT)
                {
                    SgStatement *cp = st->controlParent();
                    if (cp->variant() == LOGIF_NODE)
                        cp = ((SgLogIfStmt*)cp)->convertLogicIf();

                    for (int k = 0; k < reDistrRulesBefore.size(); ++k)
                        st->insertStmtBefore(reDistrRulesAfter[k]->copy(), *cp);
                }
            }

            SgStatement *module = (*currLines)[i].stats.first;
            int modVar = module->variant();
            while (modVar != MODULE_STMT && modVar != FUNC_HEDR && modVar != PROC_HEDR && modVar != PROG_HEDR)
            {
                module = module->controlParent();
                modVar = module->variant();
            }

            map<int, vector<int>> allLabRefs;
            findAllRefsToLables(module, allLabRefs);
            int labNum = 1;

            // insert redistribute after all entry points
            for (SgStatement *st = (*currLines)[i].stats.first->lexNext(); st->variant() != SPF_END_PARALLEL_REG_DIR; st = st->lexNext())
            {
                if (st->variant() == ENTRY_STAT)
                {
                    SgStatement *cp = st->controlParent();
                    SgStatement *afterEntry = st->lexNext();

                    int goToLab = -1;
                    SgLabel *goToLabp = NULL;
                    if (afterEntry->label())
                    {
                        goToLabp = afterEntry->label();
                        goToLab = goToLabp->getLabNumber();
                    }
                    else
                    {
                        while (allLabRefs.find(labNum) != allLabRefs.end())
                            ++labNum;
                        goToLab = labNum;
                        allLabRefs.insert(make_pair(labNum, vector<int>()));
                        ++labNum;

                        goToLabp = new SgLabel(goToLab);
                        afterEntry->setLabel(*goToLabp);
                        BIF_LABEL_USE(afterEntry->thebif) = goToLabp->thelabel;
                    }

                    SgGotoStmt *gotoStat = new SgGotoStmt(*goToLabp);
                    st->insertStmtBefore(*gotoStat, *cp);

                    for (int k = 0; k < reAlignRules.size(); ++k)
                        st->insertStmtAfter(reAlignRules[k]->copy(), *cp);

                    for (int k = 0; k < reDistrRulesBefore.size(); ++k)
                        st->insertStmtAfter(reDistrRulesBefore[k]->copy(), *cp);
                }
            }
        }
    }
}

void insertParallelDirs(SgFile *file, bool extract,
                        vector<Directive*>& createdDirectives,
                        vector<Messages>& messages,
                        map<string, string>& templateDeclInIncludes,
                        map<string, map<int, set<string>>>& commentsToInclude,
                        const vector<FuncInfo*>& callGraph,
                        const vector<ParallelRegion*>& parallelRegions,
                        const map<string, vector<LoopGraph*>>& loopGraph,
                        const set<string>& allFileNames,
                        const map<DIST::Array*, set<DIST::Array*>>& arrayLinksByFuncCalls,
                        const map<tuple<int, string, string>, pair<DIST::Array*, DIST::ArrayAccessInfo*>>& declaredArrays,
                        const map<DIST::Array*, tuple<int, string, string>>& tableOfUniqNamesByArray)
{
    const char* file_name = file->filename();
    insertDirectiveToFile(file, file_name, createdDirectives, extract, messages);

    if (sharedMemoryParallelization == 0)
    {
        map<string, FuncInfo*> mapFuncInfo;
        createMapOfFunc(callGraph, mapFuncInfo);

        for (int z = 0; z < parallelRegions.size(); ++z)
        {
            ParallelRegion* currReg = parallelRegions[z];

            const DataDirective& dataDirectives = currReg->GetDataDir();
            const vector<int>& currentVariant = currReg->GetCurrentVariant();
            const DIST::Arrays<int>& allArrays = currReg->GetAllArrays();
            DIST::GraphCSR<int, double, attrType>& reducedG = currReg->GetReducedGraphToModify();

            const set<string> distrArrays = fillDistributedArrays(dataDirectives, tableOfUniqNamesByArray, arrayLinksByFuncCalls);
            const vector<string> distrRules = dataDirectives.GenRule(currentVariant);
            const vector<vector<dist>> distrRulesSt = dataDirectives.GenRule(currentVariant, 0);
            const vector<string> alignRules = dataDirectives.GenAlignsRules();


            insertDistributionToFile(file, file_name, dataDirectives, distrArrays, distrRules, distrRulesSt, alignRules, loopGraph,
                                     allArrays, reducedG, commentsToInclude, templateDeclInIncludes, extract, messages,
                                     arrayLinksByFuncCalls, mapFuncInfo, currReg->GetId(), allFileNames);

            insertLoopTempalteDeclaration(file, dataDirectives, distrRules, distrRulesSt, allArrays, extract, currReg->GetId());
        }
    }

    if (extract)
    {
        createdDirectives.clear();

        //clear shadow specs
        for (auto& array : declaredArrays)
            array.second.first->ClearShadowSpecs();
    }
    else if (sharedMemoryParallelization == 0)
    {
        set<uint64_t> regNum;
        for (int z = 0; z < parallelRegions.size(); ++z)
            regNum.insert(parallelRegions[z]->GetId());
        insertTemplateModuleUse(file, regNum, arrayLinksByFuncCalls);
    }
}