#include "leak_detector.h"

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <vector>
#include <map>
#include <string>
#include <algorithm>

#include "dvm.h"
#include "ParRegions.h"
#include "utils.h"
#include "SgUtils.h"
#include "graph_calls_func.h"
#include "graph_loops.h"
#include "../Distribution/Distribution.h"
#include "expr_transform.h"

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

extern void createMapLoopGraph(map<int, LoopGraph*> &sortedLoopGraph, const vector<LoopGraph*> *loopGraph);

static map<string, int> regionIdByName;
static map<string, ParallelRegion*> regionByName;

static int regionIdConuter = 1;

void clearRegionStaticData()
{
    regionIdByName.clear();
    regionByName.clear();
    regionIdConuter = 1;
}


static inline void extendRegionInfo(SgStatement *st, map<string, pair<Statement*, Statement*>> &startEnd, map<string, pair<int, int>> &lines_, bool addEndSt = false)
{
    auto it_st_en = startEnd.find(st->fileName());
    if (it_st_en == startEnd.end())
    {
        startEnd.insert(it_st_en, make_pair(st->fileName(), make_pair<Statement*, Statement*>(new Statement(st), NULL)));
        lines_[st->fileName()] = make_pair(st->lineNumber(), 0);
    }
    else
    {
        if (addEndSt)
        {
            Statement *toAdd = new Statement(st);
            it_st_en->second.second = toAdd;
        }
        lines_[st->fileName()].second = st->lineNumber();
    }
}

static void findFuncCalls(SgExpression *ex, set<string> &calls, const string &prefix, const map<string, FuncInfo*>& mapFuncs)
{
    if (ex == NULL)
        return;

    if (ex->variant() == FUNC_CALL)
    {
        string fullName = ex->symbol()->identifier();
        //check contains
        if (mapFuncs.find(prefix + fullName) != mapFuncs.end())
            fullName = prefix + fullName;
        calls.insert(fullName);
    }

    findFuncCalls(ex->lhs(), calls, prefix, mapFuncs);
    findFuncCalls(ex->rhs(), calls, prefix, mapFuncs);
}

static inline SgStatement* getParentStat(SgStatement *st)
{
    if (!st)
        return NULL;

    SgStatement *iterator = st;

    while (iterator->variant() != PROG_HEDR && iterator->variant() != PROC_HEDR && iterator->variant() != FUNC_HEDR)
        iterator = iterator->controlParent();

    while (iterator->controlParent()->variant() == PROG_HEDR || iterator->controlParent()->variant() == PROC_HEDR || iterator->controlParent()->variant() == FUNC_HEDR)
        iterator = iterator->controlParent();

    return iterator;
}

static void updateRegionInfo(SgStatement *st, map<string, pair<Statement*, Statement*>> &startEnd,
                             map<string, pair<int, int>> &lines_,
                             map<string, map<string, set<int>>> &funcCallFromReg,
                             const map<string, FuncInfo*> &mapFuncs)
{    
    string containsPrefix = "";
    SgStatement *st_ps = getParentStat(st);

    if (st_ps->variant() == PROC_HEDR || st_ps->variant() == PROG_HEDR || st_ps->variant() == FUNC_HEDR)
        containsPrefix = st_ps->symbol()->identifier() + string(".");

    extendRegionInfo(st, startEnd, lines_);

    set<string> callsFromStatement;

    if (st->variant() == PROC_STAT)
    {
        string fullName = st->symbol()->identifier();
        //check contains
        if (mapFuncs.find(containsPrefix + fullName) != mapFuncs.end())
            fullName = containsPrefix + fullName;
        callsFromStatement.insert(fullName);
    }

    for (int z = 0; z < 3; ++z)
        findFuncCalls(st->expr(z), callsFromStatement, containsPrefix, mapFuncs);

    string filename = st->fileName();
    int line = st->lineNumber();

    for (const auto &func_name : callsFromStatement)
        funcCallFromReg[func_name][filename].insert(line);
}

static void fillArrayNamesInReg(set<string> &usedArrayInRegion, SgExpression *exp)
{
    if (exp)
    {
        if (exp->variant() == ARRAY_REF && OriginalSymbol(exp->symbol()))
            usedArrayInRegion.insert(OriginalSymbol(exp->symbol())->identifier());

        fillArrayNamesInReg(usedArrayInRegion, exp->lhs());
        fillArrayNamesInReg(usedArrayInRegion, exp->rhs());
    }
}

static bool filterUserDirectives(set<string> &usedArrayInRegion, vector<Statement*> &userDvmDirs, vector<Statement*> &userDvmDirsF)
{
    vector<Statement*> filtered;
    bool changed = false;
    for (auto &distr : userDvmDirs)
    {
        set<string> usedIndir;
        for (int i = 0; i < 3; ++i)
            fillArrayNamesInReg(usedIndir, distr->GetOriginal()->expr(i));

        bool ok = false;
        for (auto &elem : usedIndir)
        {
            if (usedArrayInRegion.find(elem) != usedArrayInRegion.end())
            {
                ok = true;
                break;
            }
        }

        if (ok)
        {
            usedArrayInRegion.insert(usedIndir.begin(), usedIndir.end());
            userDvmDirsF.push_back(distr);
            changed = true;
        }
        else
            filtered.push_back(distr);
    }
    userDvmDirs = filtered;

    return changed;
}

static void filterUserDirectives(ParallelRegion *currReg, set<string> usedArrayInRegion, vector<Statement*> userDvmDistrDirs,
                                 vector<Statement*> userDvmAlignDirs, vector<Statement*> userDvmShadowDirs)
{
    vector<Statement*> userDvmDistrDirsF;
    vector<Statement*> userDvmAlignDirsF;
    vector<Statement*> userDvmShadowDirsF;

    bool changed = false;
    do
    {
        changed = false;

        bool ret = filterUserDirectives(usedArrayInRegion, userDvmDistrDirs, userDvmDistrDirsF);
        changed = changed || ret;

        ret = filterUserDirectives(usedArrayInRegion, userDvmAlignDirs, userDvmAlignDirsF);
        changed = changed || ret;

        ret = filterUserDirectives(usedArrayInRegion, userDvmShadowDirs, userDvmShadowDirsF);
        changed = changed || ret;
                
    } while (changed);

    currReg->AddUserDirectives(userDvmDistrDirsF, DVM_DISTRIBUTE_DIR);
    currReg->AddUserDirectives(userDvmAlignDirsF, DVM_ALIGN_DIR);
    currReg->AddUserDirectives(userDvmShadowDirsF, DVM_SHADOW_DIR);
}

static void setExplicitFlag(const string &name, const map<string, FuncInfo*> &mapFuncs)
{
    auto it = mapFuncs.find(name);
    if (it != mapFuncs.end())
        it->second->inRegion = 1;
}

static void fillDvmDirs(SgStatement *st, vector<Statement*> &userDvmDistrDirs, vector<Statement*> &userDvmAlignDirs,
                        vector<Statement*> &userDvmShadowDirs, vector<Statement*> &userDvmRealignDirs, vector<Statement*> &userDvmRedistrDirs,
                        vector<Statement*>& userDvmRealignDirsDef, vector<Statement*>& userDvmRedistrDirsDef,
                        const bool &regionStarted, const map<int, LoopGraph*> &allLoopsInFile)
{
    switch (st->variant())
    {
    case DVM_VAR_DECL:
    {
        string unparsed = st->unparse();
        convertToLower(unparsed);
        if (unparsed.find("distribute") != string::npos)
            userDvmDistrDirs.push_back(new Statement(st));
        else if (unparsed.find("align") != string::npos)
            userDvmAlignDirs.push_back(new Statement(st));
    }
    break;
    case DVM_DISTRIBUTE_DIR:
        userDvmDistrDirs.push_back(new Statement(st));
        break;
    case DVM_ALIGN_DIR:
        userDvmAlignDirs.push_back(new Statement(st));
        break;
    case DVM_SHADOW_DIR:
        userDvmShadowDirs.push_back(new Statement(st));
        break;
    case DVM_REALIGN_DIR:
        if (regionStarted)
            userDvmRealignDirs.push_back(new Statement(st));
        userDvmRealignDirsDef.push_back(new Statement(st));
        break;
    case DVM_REDISTRIBUTE_DIR:
        //skip 22.04.2020
        /*if (regionStarted)
            userDvmRedistrDirs.push_back(new Statement(st));
        userDvmRedistrDirsDef.push_back(new Statement(st));*/
        break;
    case DVM_PARALLEL_ON_DIR:
        if (st->lexNext()->variant() == FOR_NODE)
        {
            SgForStmt* forStat = (SgForStmt*)(st->lexNext());
            auto it = allLoopsInFile.find(forStat->lineNumber());
            if (it != allLoopsInFile.end())
                it->second->userDvmDirective = new Statement(st);
        }
        break;
    default:
        break;
    }
}

static void checkForEmpty(SgStatement *start, SgStatement *end, vector<Messages>& messagesForFile)
{
    bool wasStarted = false;
    int lineStarted = -1;

    while (start != end)
    {
        if (start->variant() == CONTAINS_STMT)
            break;

        if (start->variant() == SPF_PARALLEL_REG_DIR)
        {
            wasStarted = true;
            lineStarted = start->lineNumber();
        }
        else if (start->variant() == SPF_END_PARALLEL_REG_DIR)
        {
            if (wasStarted)
            {
                wstring messageE, messageR;
                __spf_printToLongBuf(messageE, L"Empty parallel regions is forbidden.");
                __spf_printToLongBuf(messageR, R151);
                messagesForFile.push_back(Messages(ERROR, lineStarted, messageR, messageE, 3021));
                printInternalError(convertFileName(__FILE__).c_str(), __LINE__);
            }
        }
        else
        {
            wasStarted = false;
            lineStarted = -1;
        }
        start = start->lexNext();
    }
}

void fillRegionLines(SgFile *file, vector<ParallelRegion*> &regions, vector<Messages>& messagesForFile, vector<LoopGraph*> *loops, vector<FuncInfo*> *funcs)
{
    map<string, FuncInfo*> mapFuncs;
    if (funcs)
        createMapOfFunc(*funcs, mapFuncs);

    //fill default
    ParallelRegion* defaultR = regions[0];
    pair<int, int> lines;
    //pair<SgStatement*, SgStatement*> startEndDef(NULL, NULL);
    {
        SgStatement* st = file->firstStatement();
        st = st->lexNext();
        lines = make_pair(st->lineNumber(), 0);
        //startEndDef.first = st;

        while (st)
        {
            st = st->lastNodeOfStmt();
            lines.second = st->lineNumber();
            //startEndDef.second = st;
            st = st->lexNext();
        }
    }
    defaultR->AddLines(lines, file->filename());

    map<int, LoopGraph*> allLoopsInFile;
    if (loops)
        createMapLoopGraph(allLoopsInFile, loops);

    //fill user's
    int funcNum = file->numberOfFunctions();
    string regionName = "";
    map<string, pair<Statement*, Statement*>> startEnd;
    map<string, pair<int, int>> lines_;
    map<string, map<string, set<int>>> funcCallFromReg;
    bool regionStarted = false;
    double fragmentWeight = 1.0;

    vector<SgStatement*> toDel;
    for (int i = 0; i < funcNum; ++i)
    {
        vector<Statement*> userDvmDistrDirs;
        vector<Statement*> userDvmAlignDirs;
        vector<Statement*> userDvmShadowDirs;
        vector<Statement*> userDvmRealignDirs;
        vector<Statement*> userDvmRedistrDirs;

        vector<Statement*> userDvmRealignDirsDef;
        vector<Statement*> userDvmRedistrDirsDef;

        set<string> usedArrayInRegion;

        SgStatement *st = file->functions(i);
        SgStatement *lastNode = st->lastNodeOfStmt();

        string containsPrefix = "";
        SgStatement *st_cp = st->controlParent();
        if (st_cp->variant() == PROC_HEDR || st_cp->variant() == PROG_HEDR || st_cp->variant() == FUNC_HEDR)
            containsPrefix = st_cp->symbol()->identifier() + string(".");
        const string funcName = containsPrefix + file->functions(i)->symbol()->identifier();

        checkForEmpty(st, lastNode, messagesForFile);
        while (st != NULL && st != lastNode)
        {
            if (st->variant() == CONTAINS_STMT)
                break;

            SgStatement *prev = st->lexPrev();
            SgStatement *next = st->lexNext();

            if (prev && prev->variant() == SPF_PARALLEL_REG_DIR)
            {
                SgStatement *data = prev;

                regionStarted = true;
                regionName = data->symbol()->identifier();
                if (funcs)
                {
                    auto itFunc = mapFuncs.find(funcName);
                    if (itFunc != mapFuncs.end())
                    {
                        itFunc->second->inRegion = 3;
                        itFunc->second->callRegions.insert(0);
                    }
                }

                // parse SPF_APPLY_FRAGMENT clause
                auto *apply_fragment = data->expr(1);
                fragmentWeight = 1.0;

                while (apply_fragment)
                {
                    auto *curr = apply_fragment->lhs();
                    if (curr)
                    {
                        __spf_print(1, "%s %d\n", curr->unparse(), curr->variant());

                        if (curr->variant() == SPF_WEIGHT_OP)
                        {
                            if (curr->lhs() &&
                                isSgValueExp(curr->lhs()) &&
                                isSgValueExp(curr->lhs())->doubleValue())
                            {
                                fragmentWeight = strtod(isSgValueExp(curr->lhs())->doubleValue(), NULL);
                                __spf_print(1, "->> %lf\n", fragmentWeight);
                            }
                            else
                                __spf_print(1, "WEIGHT clause without double argument\n");
                        }
                    }
                    apply_fragment = apply_fragment->rhs();
                }
            }

            if (next && next->variant() == SPF_END_PARALLEL_REG_DIR)
            {
                updateRegionInfo(st, startEnd, lines_, funcCallFromReg, mapFuncs);
                SgStatement *data = next;

                lines.second = data->lineNumber();
                regionStarted = false;

                if (regionName == "")
                    printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

                auto it = regionIdByName.find(regionName);
                ParallelRegion *currReg = NULL;
                if (it == regionIdByName.end())
                {
                    it = regionIdByName.insert(it, make_pair(regionName, regionIdConuter));
                    currReg = new ParallelRegion(regionIdConuter, regionName);
                    regions.push_back(currReg);
                    regionByName[regionName] = currReg;
                    regionName = "";
                    regionIdConuter++;
                }
                else
                    currReg = regionByName[regionName];

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

                extendRegionInfo(st, startEnd, lines_, true);
                for (auto itRegInfo = startEnd.begin(); itRegInfo != startEnd.end(); ++itRegInfo)
                    currReg->AddLines(lines_[itRegInfo->first], itRegInfo->first, &itRegInfo->second, fragmentWeight);

                for (auto &by_func : funcCallFromReg)
                    for (auto &by_file : by_func.second)
                        for(auto &by_line : by_file.second)
                            currReg->AddFuncCalls(by_func.first, by_file.first, by_line);

                filterUserDirectives(currReg, usedArrayInRegion, userDvmRedistrDirs, userDvmRealignDirs, userDvmShadowDirs);
                currReg->AddUserDirectives(userDvmRealignDirs, DVM_REALIGN_DIR);
                currReg->AddUserDirectives(userDvmRedistrDirs, DVM_REDISTRIBUTE_DIR);

                startEnd.clear();
                lines_.clear();
                funcCallFromReg.clear();

                userDvmRealignDirs.clear();
                userDvmRedistrDirs.clear();

                usedArrayInRegion.clear();
            }


            if (regionStarted)
            {
                updateRegionInfo(st, startEnd, lines_, funcCallFromReg, mapFuncs);
                for (int i = 0; i < 3; ++i)
                    fillArrayNamesInReg(usedArrayInRegion, st->expr(i));
            }

            fillDvmDirs(st, userDvmDistrDirs, userDvmAlignDirs, userDvmShadowDirs, userDvmRealignDirs, userDvmRealignDirs, userDvmRealignDirsDef, userDvmRealignDirsDef, regionStarted, allLoopsInFile);
            if (st->variant() == DVM_SHADOW_GROUP_DIR || st->variant() == DVM_SHADOW_START_DIR || st->variant() == DVM_SHADOW_WAIT_DIR)
                toDel.push_back(st);
            st = st->lexNext();
        }

        //for default
        defaultR->AddUserDirectives(userDvmDistrDirs, DVM_DISTRIBUTE_DIR);
        defaultR->AddUserDirectives(userDvmAlignDirs, DVM_ALIGN_DIR);
        defaultR->AddUserDirectives(userDvmShadowDirs, DVM_SHADOW_DIR);
        defaultR->AddUserDirectives(userDvmRealignDirsDef, DVM_REALIGN_DIR);
        defaultR->AddUserDirectives(userDvmRedistrDirsDef, DVM_REDISTRIBUTE_DIR);
    }

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

    for (auto& mod : modules)
    {
        vector<Statement*> userDvmDistrDirs;
        vector<Statement*> userDvmAlignDirs;
        vector<Statement*> userDvmShadowDirs;
        vector<Statement*> userDvmRealignDirs;
        vector<Statement*> userDvmRedistrDirs;

        vector<Statement*> userDvmRealignDirsDef;
        vector<Statement*> userDvmRedistrDirsDef;


        for (SgStatement* st = mod->lexNext(); st; st = st->lexNext())
        {
            if (isSgExecutableStatement(st))
                break;
            fillDvmDirs(st, userDvmDistrDirs, userDvmAlignDirs, userDvmShadowDirs, userDvmRealignDirs, userDvmRealignDirs, userDvmRealignDirsDef, userDvmRealignDirsDef, regionStarted, allLoopsInFile);
            if (st->variant() == DVM_SHADOW_GROUP_DIR)
                toDel.push_back(st);
        }

        //for default
        defaultR->AddUserDirectives(userDvmDistrDirs, DVM_DISTRIBUTE_DIR);
        defaultR->AddUserDirectives(userDvmAlignDirs, DVM_ALIGN_DIR);
        defaultR->AddUserDirectives(userDvmShadowDirs, DVM_SHADOW_DIR);
        defaultR->AddUserDirectives(userDvmRealignDirsDef, DVM_REALIGN_DIR);
        defaultR->AddUserDirectives(userDvmRedistrDirsDef, DVM_REDISTRIBUTE_DIR);
    }

    for (auto& elem : toDel)
        elem->deleteStmt();
}

static void getAllLoops(vector<LoopGraph*> &loopGraph, vector<LoopGraph*> &loops)
{
    for (auto &elem : loopGraph)
        loops.push_back(elem);

    for (auto &elem : loopGraph)
        getAllLoops(elem->children, loops);
}

void fillRegionLinesStep2(vector<ParallelRegion*> &regions, const map<string, vector<FuncInfo*>> &allFuncInfo, map<string, vector<LoopGraph*>> *loopGraph)
{
    map<string, FuncInfo*> funcMap;
    createMapOfFunc(allFuncInfo, funcMap);

    for (int i = 0; i < regions.size(); ++i)
    {
        if (regions[i]->GetName() != "DEFAULT")
            for (auto &func : regions[i]->GetFuncCalls())
                setExplicitFlag(func.first, funcMap);
    }

    for (int i = 0; i < regions.size(); ++i)
    {
        if (regions[i]->GetName() != "DEFAULT")
        {
            map<string, double> uniqFuncCalls;
            map<string, map<string, set<int>>> callPlaces;

            for (auto &elem : regions[i]->GetFuncCalls())
            {
                double max_weight = 0;
                for (auto* fragment : elem.second)
                    if (fragment->weight > max_weight)
                        max_weight = fragment->weight;

                uniqFuncCalls[elem.first] = max_weight;
            }

            bool wasChanged = true;
            auto funcsBefore = uniqFuncCalls;

            while (wasChanged)
            {
                wasChanged = false;
                auto updated = uniqFuncCalls;

                for (auto &uniqF : uniqFuncCalls)
                {
                    auto func = funcMap.find(uniqF.first);
                    if (func != funcMap.end())
                    {
                        for (auto &call : func->second->callsFromDetailed)
                        {
                            auto it = updated.find(call.detailCallsFrom.first);
                            if (it == updated.end())
                                updated.insert({call.detailCallsFrom.first, uniqF.second});
                            else
                                it->second = std::max(it->second, uniqF.second);

                            callPlaces[call.detailCallsFrom.first][func->second->fileName].insert(call.detailCallsFrom.second);
                        }
                    }
                }
            }

            string toPrint = "";
            for (auto &elem : uniqFuncCalls)
            {
                auto it = funcMap.find(elem.first);
                if (it != funcMap.end())
                {
                    regions[i]->AddLines(it->second->linesNum, it->second->fileName, NULL, elem.second);

                    if (it->second->inRegion == 0)
                        it->second->inRegion = 2;

                    it->second->callRegions.insert(i);

                    toPrint += elem.first + " ";
                }
            }

            for (auto &elem : callPlaces)
            {
                for (const auto &byFile : elem.second)
                    for (auto byLine : byFile.second)
                        regions[i]->AddFuncCalls(elem.first, byFile.first, byLine);
            }

            if (toPrint != "")
                __spf_print(1, "[%s]: funcs: %s\n", regions[i]->GetName().c_str(), toPrint.c_str());
        }
    }

    // remove default region if user's found
    if (regions.size() > 1)
    {
#ifdef _WIN32
        removeFromCollection(regions[0]);
#endif
        delete regions[0];
        regions.erase(regions.begin());
    }

    bool changed = true;
    while (changed)
    {
        changed = false;
        for (auto &func : funcMap)
        {
            if (func.second->inRegion != 0)
                continue;

            for (auto &callsFrom : func.second->callsFrom)
            {
                auto it = funcMap.find(callsFrom);
                if (it != funcMap.end())
                {
                    if (it->second->inRegion > 0)
                    {
                        func.second->inRegion = 3;
                        func.second->callRegions.insert(0);
                        changed = true;
                        break;
                    }
                    else
                        func.second->callRegions.insert(0);
                }
            }
        }
    }

    if (loopGraph)
    {
        //fill regions for loop 
        vector<LoopGraph*> loops;
        for (auto loop : *loopGraph)
            getAllLoops(loop.second, loops);

        for (auto &loop : loops)
        {
            const int currLine = loop->lineNum < -1 ? loop->loop->localLineNumber() : loop->lineNum;
            set<ParallelRegion*> allRegs = getAllRegionsByLine(regions, loop->fileName, currLine);
            if (allRegs.size() == 1)
                loop->region = *(allRegs.begin());
        }
    }
}

int printParalleRegions(const char *fileName, vector<ParallelRegion*> &regions)
{
    FILE *file = fopen(fileName, "w");
    if (file == NULL)
    {
        __spf_print(1, "can not open file %s\n", fileName);
        return -1;
    }

    int num = 0;
    for (auto &elem : regions)
    {
        fprintf(file, "*** #%d Parallel Region\n", num);
        elem->print(file);
        fprintf(file, "\n");
        ++num;
    }

    fclose(file);
    return 0;
}

static int getIntVal(SgExpression *ex)
{
    SgExpression* calc = CalculateInteger(ex->copyPtr());
    if (calc->variant() == INT_VAL)
        return calc->valueInteger();
    else
        return 0;
}

static void fillMultOp(SgExpression *ex, pair<string, pair<int, int>> &retVal)
{
    const int var = ex->lhs()->variant();
    if (var == INT_VAL || var == CONST_REF || var == MINUS_OP) // -+a * X
    {
        retVal.first = ex->rhs()->symbol()->identifier();        
        retVal.second.first = getIntVal(ex->lhs());        
    }
    else if (var == VAR_REF) // X * -+a
    {
        retVal.first = ex->lhs()->symbol()->identifier();
        retVal.second.first = getIntVal(ex->rhs());
    }
}

static pair<string, pair<int, int>> parseExpression(SgExpression *ex)
{
    pair<string, pair<int, int>> retVal;

    if (ex)
    {
        if (ex->variant() == VAR_REF)
        {
            retVal.first = ex->symbol()->identifier();
            retVal.second = make_pair(1, 0);
        }
        else if (ex->variant() == MULT_OP) // a * X
            fillMultOp(ex, retVal);
        else if (ex->variant() == ADD_OP || ex->variant() == SUBT_OP) // a * X +- b
        {
            int minus = (ex->variant() == ADD_OP) ? 1 : -1;
            if (ex->lhs()->variant() == MULT_OP) // a * X +- b
            {
                fillMultOp(ex->lhs(), retVal);
                retVal.second.second = getIntVal(ex->rhs()) * minus;
            }
            else if (ex->lhs()->variant() == INT_VAL) // b +- [a *] X
            {
                if (ex->rhs()->variant() == MULT_OP)
                    fillMultOp(ex->rhs(), retVal);
                else
                {
                    retVal.first = ex->rhs()->symbol()->identifier();
                    retVal.second.first = minus;
                }
                retVal.second.second = getIntVal(ex->lhs()) * minus;
            }
            else if (ex->lhs()->variant() == VAR_REF && ex->rhs()->variant()) // X +- b
            {
                retVal.first = ex->lhs()->symbol()->identifier();
                retVal.second.first = 1;
                retVal.second.second = getIntVal(ex->rhs()) * minus;
            }
            else
                printInternalError(convertFileName(__FILE__).c_str(), __LINE__);
        }
        else if (ex->variant() == KEYWORD_VAL) // *
        {
            SgKeywordValExp* keyVal = (SgKeywordValExp*)ex;
            if (keyVal->value())
                retVal = make_pair(keyVal->value(), make_pair(0, 0));
        }
        else if (ex->variant() == DDOT || ex->variant() == INT_VAL || ex->variant() == CONST_REF) // ':' | 'digit' equal '*'
            retVal = make_pair("*", make_pair(0, 0));
        else
        {
            __spf_print(1, "align pattern: '%s'\n", ex->unparse());
            printInternalError(convertFileName(__FILE__).c_str(), __LINE__);
        }
    }
    return retVal;
}

static void fillTemplate(SgExpression *ex, vector<pair<string, pair<int, int>>> &templateArray)
{
    for (SgExpression *list = ex; list; list = list->rhs())
        templateArray.push_back(parseExpression(list->lhs()));
}

static int getTemplateDemention(const string &val, const vector<pair<string, pair<int, int>>> &alignWithTemplate)
{
    int ret = -1;
    for (int k = 0; k < alignWithTemplate.size(); ++k)
    {
        if (alignWithTemplate[k].first == val)
        {
            ret = k;
            break;
        }
    }
    return ret;
}

static bool needToSkip(SgStatement* dir, vector<FuncInfo*> funcs)
{
    bool retVal = false;

    auto iterator = dir;

    while (iterator && iterator->variant() != PROG_HEDR && iterator->variant() != PROC_HEDR && iterator->variant() != FUNC_HEDR)
        iterator = iterator->controlParent();
    
    if (iterator == NULL)
        return retVal;

    for (auto& elem : funcs)
    {
        if (elem->funcPointer->GetOriginal()->thebif == iterator->thebif)
        {
            if (elem->doNotAnalyze)
                return true;
            break;
        }
    }
    return retVal;
}

bool buildGraphFromUserDirectives(const vector<Statement*> &userDvmAlignDirs, DIST::GraphCSR<int, double, attrType> &G, 
                                  DIST::Arrays<int> &allArrays, const map<DIST::Array*, set<DIST::Array*>> &arrayLinksByFuncCalls, 
                                  const set<DIST::Array*>& alignedArrays, set<DIST::Array*>& addedArrays,
                                  const map<string, vector<FuncInfo*>>& funcsByFile)
{
    if (userDvmAlignDirs.size())
    {
        bool cleaned = false;

        int t = 0;
        for (auto &dir : userDvmAlignDirs)
        {
            const string file = dir->GetFileNameIncludedTo();
            if (SgFile::switchToFile(file) == -1)
                printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

            auto it = funcsByFile.find(file);
            
            if (needToSkip(dir->GetOriginal(), it == funcsByFile.end() ? vector<FuncInfo*>() : it->second));
                continue;

            vector<DIST::Array*> alignArrays;
            
            vector<pair<string, pair<int, int>>> alignTemplate;

            DIST::Array* alignWithArray = NULL;
            vector<pair<string, pair<int, int>>> alignWithTemplate;

            if (dir->expr(0) == NULL)
                return true;

            for (SgExpression *ex = dir->expr(0); ex; ex = ex->rhs())
            {
                SgExpression *val = ex->lhs();
                if (val->variant() == ARRAY_REF && val->symbol())
                {
                    DIST::Array *newValue = getArrayFromDeclarated(declaratedInStmt(val->symbol()), val->symbol()->identifier());
                    if (newValue)
                        alignArrays.push_back(newValue);
                    else
                        return true;
                }
            }

            map<DIST::Array*, set<DIST::Array*>> realAlignArrayRefs;
            for (auto &access : alignArrays)
                getRealArrayRefs(access, access, realAlignArrayRefs[access], arrayLinksByFuncCalls);

            set<DIST::Array*> realAlignArrayRefsSet;
            for (auto &elem : realAlignArrayRefs)
                realAlignArrayRefsSet.insert(elem.second.begin(), elem.second.end());

            if (dir->expr(1))
                fillTemplate(dir->expr(1), alignTemplate);
            else if (dir->variant() == DVM_VAR_DECL)
                fillTemplate(dir->expr(2)->lhs()->lhs(), alignTemplate);
            else
                return true;

            if (dir->expr(2))
            {
                SgExpression *ex = dir->expr(2);
                if (ex->variant() == EXPR_LIST && ex->lhs()->variant() == ALIGN_OP)
                    ex = ex->lhs()->rhs();

                if (ex && ex->variant() == ARRAY_REF && ex->symbol())
                {
                    alignWithArray = getArrayFromDeclarated(declaratedInStmt(ex->symbol()), ex->symbol()->identifier());
                    if (alignWithArray == NULL)
                        return true;
                    fillTemplate(ex->lhs(), alignWithTemplate);
                }
                else
                    return true;
            }
            else
                return true;

            string tmp;            
            for (auto& elem : realAlignArrayRefsSet)
                tmp += elem->GetName() + " ";
            __spf_print(1, "  align array%s '%s' from user dir in line %d\n", (realAlignArrayRefsSet.size() == 1 ? "" : "s"), tmp.c_str(), dir->lineNumber());
            __spf_print(1, "  template align:\n");
            for (int i = 0; i < alignTemplate.size(); ++i)
                __spf_print(1, "    -- %d: %s -- [%d, %d]\n", i, alignTemplate[i].first.c_str(), alignTemplate[i].second.first, alignTemplate[i].second.second);
            __spf_print(1, "  template align with:\n");
            for (int i = 0; i < alignWithTemplate.size(); ++i)
                __spf_print(1, "    -- %d: %s -- [%d, %d]\n", i, alignWithTemplate[i].first.c_str(), alignWithTemplate[i].second.first, alignWithTemplate[i].second.second);

            for (int i = 0; i < alignTemplate.size(); ++i)
            {
                if (alignTemplate[i].first != "*")
                {
                    int dimT = getTemplateDemention(alignTemplate[i].first, alignWithTemplate);
                    if (dimT == -1)
                        continue;
                    
                    for (auto& array : realAlignArrayRefsSet)
                    {
                        if (!array->IsNotDistribute())
                        {
                            if (alignWithArray->IsTemplate() && alignWithArray->GetShortName().find("_r") != string::npos)
                                continue;

                            //TODO:
                            /*if (alignedArrays.find(array) != alignedArrays.end())
                                continue;*/
                            addedArrays.insert(array);

                            if (!cleaned)
                            {
                                cleaned = true;
                                G.ClearGraphCSR();
                            }

                            DIST::AddArrayAccess(G, allArrays, array, alignWithArray, make_pair(i, dimT), 1.0, make_pair(make_pair(1, 0), alignWithTemplate[dimT].second), WW_link);
                        }
                    }
                }
            }
        }
    }

    return false;
}

void calculateLinesOfCode(vector<ParallelRegion*> &allRegions)
{
    for (auto &elem : allRegions)
    {
        auto allLines = elem->GetAllLines();
        int lineCounter = 0;
        for (auto &line : allLines)
            for (auto &lineV : line.second)
                lineCounter += (lineV.lines.second - lineV.lines.first);

        __spf_print(1, "  Count of lines in region '%s' = %d\n", elem->GetName().c_str(), lineCounter);
    }
}

void propagateRegionInfo(map<tuple<int, string, string>, pair<DIST::Array*, DIST::ArrayAccessInfo*>>& declaredArrays,
                         const map<DIST::Array*, set<DIST::Array*>>& arrayLinksByFuncCalls,
                         const vector<ParallelRegion*> &parallelRegions)
{
    bool modified = true;
    while (modified)
    {
        modified = false;
        
        for (auto &array_pair: declaredArrays)
        {
            auto array = array_pair.second.first;
            if (array->GetLocation().first == DIST::l_PARAMETER)
            {
                set<DIST::Array*> realArrayRef;
                getAllArrayRefs(array, array, realArrayRef, arrayLinksByFuncCalls);

                auto regs = array->GetRegionsName();
                for (auto& ref : realArrayRef)
                {
                    auto regsRef = ref->GetRegionsName();
                    for (auto& reg : regsRef)
                    {
                        if (regs.count(reg) == 0)
                        {
                            array->SetRegionPlace(reg);
                            modified = true;
                        }
                    }

                }
            }
        }
    }

    bool hasNonDefaultReg = false;
    for (auto& elem : parallelRegions)
    {
        string regName = elem->GetName();
        convertToLower(regName);
        if (regName != "default")
            hasNonDefaultReg = true;
    }

    if (hasNonDefaultReg)
    {
        for (auto array : declaredArrays)
        {
            if (array.second.first->GetRegionsName().size() == 0)
                array.second.first->SetDistributeFlag(DIST::NO_DISTR);
            else if (array.second.first->GetRegionsName().size() == 1)
            {
                string regName = *array.second.first->GetRegionsName().begin();
                convertToLower(regName);
                if (regName == "default")
                    array.second.first->SetDistributeFlag(DIST::NO_DISTR);
            }
        }
    }
}