#include "leak_detector.h"

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

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

#include "dvm.h"
#include "../Sapfor.h"
#include "graph_calls_func.h"
#include "../ParallelizationRegions/ParRegions_func.h"
#include "expr_transform.h"

#include "../Distribution/GraphCSR.h"
#include "../Distribution/Arrays.h"
#include "../Distribution/Distribution.h"

#include "graph_loops.h"
#include "utils.h"
#include "SgUtils.h"

#include "errors.h"
#include "AstWrapper.h"
#include "json.hpp"

#include "../DirectiveProcessing/directive_parser.h"
#include "../DynamicAnalysis/gCov_parser_func.h"

#include "VectorAssignToLoop/array_assign_to_loop.h"

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

using json = nlohmann::json;

#define DEBUG 0
static inline void insertLabels(SgExpression *lb, map<int, vector<int>> &gotoLabels, const int line)
{
    while (lb)
    {
        SgLabel *lab = ((SgLabelRefExp *)(lb->lhs()))->label();
        gotoLabels[lab->getLabNumber()].push_back(line);
        lb = lb->rhs();
    }
}

static bool recVariantFind(SgExpression *ex, const int var)
{
    bool ret = false;
    if (ex)
    {
        if (ex->variant() == var)
            return true;

        ret = ret || recVariantFind(ex->lhs(), var);
        ret = ret || recVariantFind(ex->rhs(), var);
    }

    return ret;
}

static void recInderectFind(SgExpression *ex, set<DIST::Array*> &usedArrays)
{
    if (ex)
    {
        if (ex->variant() == ARRAY_REF)
        {
            DIST::Array *arrayRef = NULL;
            SgSymbol *symbS = OriginalSymbol(ex->symbol());
            const string symb = symbS->identifier();

            if (symbS)
                arrayRef = getArrayFromDeclarated(declaratedInStmt(symbS), symb);
            else
                printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

            // only distributed arrays were added
            if (arrayRef)
                if (arrayRef->IsNotDistribute() == false)
                    usedArrays.insert(arrayRef);
        }

        recInderectFind(ex->lhs(), usedArrays);        
        recInderectFind(ex->rhs(), usedArrays);        
    }
}

bool recSymbolFind(SgExpression *ex, const string &symb, const int var)
{
    bool ret = false;
    if (ex)
    {
        if (ex->variant() == var)
        {
            if (ex->symbol())
                if (ex->symbol()->identifier() == symb)
                    return true;
        }

        ret = ret || recSymbolFind(ex->lhs(), symb, var);
        ret = ret || recSymbolFind(ex->rhs(), symb, var);
    }

    return ret;
}

static inline void processIOlist(SgExpression *spec, const int line, map<int, vector<int>>& labelsList)
{
    while (spec)
    {
        if (spec->lhs())
        {
            SgExpression* specL = spec->lhs();
            if (specL->lhs() && specL->rhs())
            {
                if (specL->lhs()->variant() == KEYWORD_VAL)
                {
                    SgKeywordValExp* keyVal = (SgKeywordValExp*)specL->lhs();

                    if (keyVal->value() != string("fmt"))
                    {
                        if (specL->rhs()->variant() == LABEL_REF)
                        {
                            SgLabelRefExp* labRef = (SgLabelRefExp*)specL->rhs();
                            if (labRef->label())
                                labelsList[labRef->label()->getLabNumber()].push_back(line);
                        }
                    }
                }
            }
        }
        spec = spec->rhs();
    }
}

static inline void processLables(SgStatement *curr, map<int, vector<int>> &labelsList, bool includeWrite = true)
{
    const int var = curr->variant();
    if (var == GOTO_NODE)
    {
        SgGotoStmt *gotoS = (SgGotoStmt *)curr;
        labelsList[gotoS->branchLabel()->getLabNumber()].push_back(curr->lineNumber());
    }
    else if (var == ARITHIF_NODE)
    {
        SgExpression *lb = curr->expr(1);
        insertLabels(lb, labelsList, curr->lineNumber());
    }
    else if (var == COMGOTO_NODE)
    {
        SgExpression *lb = ((SgComputedGotoStmt*)curr)->labelList();
        insertLabels(lb, labelsList, curr->lineNumber());
    }
    else if ((var == PRINT_STAT || var == WRITE_STAT) && includeWrite)
    {
        SgInputOutputStmt *ioStat = (SgInputOutputStmt*)curr;
        SgExpression *spec = ioStat->specList();
        processIOlist(spec, curr->lineNumber(), labelsList);        
    }
}

static SgStatement *getLoopControlParent(SgStatement *inSt)
{
    SgStatement *cp = inSt->controlParent();
    while (!isSgForStmt(cp) && cp)
        cp = cp->controlParent();
    return cp;
}

static inline bool hasGoto(SgStatement *begin, SgStatement *end, 
                           vector<int> &linesOfIntGoTo, vector<int> &linesOfExtGoTo, const map<int, vector<int>> &labelsRef, vector<int>& cycleStmts)
{
    bool has = false;
    SgStatement *curr = begin->lexNext();
    map<int, vector<int>> gotoLabels;
    map<int, vector<int>> inFragLabels;

    while (curr != end)
    {
        if (curr->hasLabel())
            inFragLabels[curr->label()->getLabNumber()].push_back(curr->lineNumber());

        for (int i = 0; i < 3; ++i)
        {
            if (curr->expr(i))
            {
                if (recVariantFind(curr->expr(i), EXIT_STMT) && getLoopControlParent(curr) == begin)
                {
                    linesOfIntGoTo.push_back(curr->lineNumber());
                    has = true;
                }
            }
        }

        processLables(curr, gotoLabels);

        if (curr->variant() == EXIT_STMT && getLoopControlParent(curr) == begin)
        {
            linesOfIntGoTo.push_back(curr->lineNumber());
            has = true;
        }

        if (curr->variant() == RETURN_STAT)
        {
            linesOfIntGoTo.push_back(curr->lineNumber());
            has = true;
        }

        if (curr->variant() == CYCLE_STMT)
            cycleStmts.push_back(curr->lineNumber());
        curr = curr->lexNext();
    }

    // if loop has no labels from goto 
    for (auto it = gotoLabels.begin(); it != gotoLabels.end(); ++it)
    {
        if (inFragLabels.find(it->first) == inFragLabels.end())
        {
            has = true;
            for (int z = 0; z < it->second.size(); ++z)
                linesOfIntGoTo.push_back(it->second[z]);
        }
    }
        
    // if loop has labels with extern goto
    for (auto it = inFragLabels.begin(); it != inFragLabels.end(); ++it)
    {
        auto labRef = labelsRef.find(it->first);

        if (labRef != labelsRef.end())
        {
            for (auto &line : labRef->second)
            {
                if (!(line > begin->lineNumber() && line <= end->lineNumber()))
                {
                    has = true;
                    linesOfExtGoTo.push_back(line);
                }
            }
        }
    }
        
    return has;
}

bool checkRegionEntries(SgStatement *begin,
                        SgStatement *end,
                        const map<string, FuncInfo*> &funcMap,
                        const vector<ParallelRegion*> &parallelRegions,
                        map<string, vector<Messages>> &SPF_messages)
{
    bool noError = true;

    SgStatement *st = begin;
    SgSymbol *regIdent = begin->symbol();

    // get func statement
    while (st->variant() != PROG_HEDR && st->variant() != PROC_HEDR && st->variant() != FUNC_HEDR)
        st = st->controlParent();

    // check GOTO
    map<int, vector<int>> labelsRef;
    findAllRefsToLables(st, labelsRef);

    vector<int> linesOfIntGoTo;
    vector<int> linesOfExtGoTo;
    vector<int> tmp;
    hasGoto(begin, end, linesOfIntGoTo, linesOfExtGoTo, labelsRef, tmp);

    if (linesOfExtGoTo.size())
    {
        __spf_print(1, "wrong parallel region position: there are several entries in fragment '%s' caused by GOTO on line %d\n", regIdent->identifier(), begin->lineNumber());

        wstring messageE, messageR;
        __spf_printToLongBuf(messageE, L"wrong parallel region position: there are several entries in fragment '%s' caused by GOTO", to_wstring(regIdent->identifier()).c_str());
        __spf_printToLongBuf(messageR, R11, to_wstring(regIdent->identifier()).c_str());

        getObjectForFileFromMap(begin->fileName(), SPF_messages).push_back(Messages(ERROR, begin->lineNumber(), messageR, messageE, 1001));

        noError = false;
    }

    // check ENTRY
    auto oldFile = current_file->filename();
    for (auto &funcPair : funcMap)
    {
        auto func = funcPair.second;
        if (SgFile::switchToFile(func->fileName) != -1)
        {
            SgStatement *funcSt = func->funcPointer->GetOriginal();
            ParallelRegion *reg = NULL;
            if (funcSt->variant() == ENTRY_STAT && func->callsTo.size() && (reg = getRegionByLine(parallelRegions, func->fileName, funcSt->lineNumber())))
            {
                __spf_print(1, "wrong parallel region position: there are several entries in fragment '%s' caused by ENTRY on line %d\n", reg->GetName().c_str(), funcSt->lineNumber());

                wstring messageE, messageR;
                __spf_printToLongBuf(messageE, L"wrong parallel region position: there are several entries in fragment '%s' caused by ENTRY", to_wstring(reg->GetName()).c_str());
                __spf_printToLongBuf(messageR, R10, to_wstring(reg->GetName()).c_str());

                getObjectForFileFromMap(func->fileName.c_str(), SPF_messages).push_back(Messages(ERROR, funcSt->lineNumber(), messageR, messageE, 1001));

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

    return noError;
}

bool hasThisIds(SgStatement *start, set<int> &lines, const set<int> &IDs, const set<SgStatement*>* activeOps)
{
    bool has = false;
    SgStatement *end = start->lastNodeOfStmt();
    SgStatement *curr = start;
    
    for ( ; curr != end; curr = curr->lexNext())
    {
        const int var = curr->variant();
        if (var == CONTAINS_STMT)
            break;

        if (var == ENTRY_STAT)
            continue;

        if (activeOps && activeOps->size() && activeOps->find(curr) == activeOps->end() && 
            isSgExecutableStatement(curr))
        {
            curr = curr->lastNodeOfStmt();
            continue;
        }

        if (IDs.find(var) != IDs.end())
        {
            has = true;
            lines.insert(curr->lineNumber());
        }
    }

    return has;
}

static inline void findFuncCalls(SgExpression *ex, set<string> &funcCalls)
{
    if (ex)
    {
        if (ex->variant() == FUNC_CALL)
            funcCalls.insert(ex->symbol()->identifier());
        findFuncCalls(ex->lhs(), funcCalls);
        findFuncCalls(ex->rhs(), funcCalls);
    }
}

static inline int tryCalculate(SgExpression *expr, int &res)
{
    SgExpression *copyExp = expr->copyPtr();
    replaceConstantRec(copyExp);
    calculate(copyExp);
    if (CalculateInteger(copyExp, res) == -1)
        return -1;
    return 0;
}

int calculateLoopIters(SgExpression *start, SgExpression *end, SgExpression *step, std::tuple<int, int, int> &result)
{
    if (!start && !end)
        return 0;

    int startV;
    int endV;
    int stepV;
    if (!step)
        stepV = 1;
    else
    {
        if (tryCalculate(step, stepV) == -1)
            return 0;
    }

    if (tryCalculate(start, startV) == -1)
        return 0;
    if (tryCalculate(end, endV) == -1)
        return 0;

    if (startV > endV && stepV > 0)
        return 0;
    if (startV < endV && stepV < 0)
        return 0;

    int count = (endV - startV + stepV) / stepV;
    if (count > 0)
    {
        std::get<0>(result) = startV;
        std::get<1>(result) = endV;
        std::get<2>(result) = stepV;
        return count;
    }
    else
        return 0;
}

void findAllRefsToLables(SgStatement *st, map<int, vector<int>> &labelsRef, bool includeWrite)
{
    SgStatement *last = st->lastNodeOfStmt();
    for ( ; st != last; st = st->lexNext())
        processLables(st, labelsRef, includeWrite);
}

static bool hasNonRect(SgForStmt *st, const vector<LoopGraph*> &parentLoops, vector<Messages> &messages)
{
    bool has = false;

    SgExpression* start = st->start();
    SgExpression* end = st->end();
    SgExpression* step = st->step();

    if (sharedMemoryParallelization == 0)
    {
        set<DIST::Array*> usedArrays;

        bool has1 = recVariantFind(start, FUNC_CALL) || recVariantFind(end, FUNC_CALL) || recVariantFind(step, FUNC_CALL);
        //TODO: corect recInderectFind!
        //TODO: need to add more checking of start,end and step

        recInderectFind(start, usedArrays);
        recInderectFind(end, usedArrays);
        recInderectFind(step, usedArrays);
        
        //has = has1 || (usedArrays.size() > 0);

        for (auto& array : usedArrays)
        {
            wstring bufE, bufR;
            __spf_printToLongBuf(bufE, L"Array '%s' can not be distributed", to_wstring(array->GetShortName()).c_str());
            __spf_printToLongBuf(bufR, R91, to_wstring(array->GetShortName()).c_str());

            messages.push_back(Messages(NOTE, st->lineNumber(), bufR, bufE, 1047));
            array->SetDistributeFlag(DIST::SPF_PRIV);
        }
    }
     
    if (has == false)
    {
        for (auto &upLoop : parentLoops)
        {
            SgForStmt *up = (SgForStmt*)upLoop->loop;
            SgSymbol *symb = up->symbol();
            if (symb)
            {
                const string strSymb = symb->identifier();

                has = has || recSymbolFind(start, strSymb, VAR_REF);
                has = has || recSymbolFind(end, strSymb, VAR_REF);
                has = has || recSymbolFind(step, strSymb, VAR_REF);

                if (has)
                    break;
            }
        }
    }
    return has;
}

static void addLoopVariablesToPrivateList(SgForStmt *currLoopRef)
{
    if (currLoopRef == NULL)
        return;

    SgStatement *spfStat = new SgStatement(SPF_ANALYSIS_DIR);
    spfStat->setlineNumber(currLoopRef->lineNumber());
    spfStat->setFileName(currLoopRef->fileName());

    SgExpression *toAdd = new SgExpression(EXPR_LIST, new SgExpression(ACC_PRIVATE_OP), NULL, NULL);
    set<SgSymbol*> privateDoSymbs = { currLoopRef->symbol() };

    SgStatement *end = currLoopRef->lastNodeOfStmt();
    for (SgStatement *st = currLoopRef->lexNext(); st != end; st = st->lexNext())
    {
        SgForStmt *currFor = isSgForStmt(st);
        if (currFor)
            privateDoSymbs.insert(currFor->symbol());
    }

    spfStat->setExpression(0, *toAdd);
    toAdd = toAdd->lhs();
    bool first = true;
    for (auto &elem : privateDoSymbs)
    {
        if (first)
        {
            toAdd->setLhs(new SgExpression(EXPR_LIST));
            toAdd = toAdd->lhs();
            first = false;
        }
        else
        {
            toAdd->setRhs(new SgExpression(EXPR_LIST));
            toAdd = toAdd->rhs();
        }
        toAdd->setLhs(new SgVarRefExp(elem));
    }

    currLoopRef->addAttribute(SPF_ANALYSIS_DIR, spfStat, sizeof(SgStatement));
}

static void findArrayRef(SgExpression *exp, bool isWirte, set<DIST::Array*>& usedArrays, set<DIST::Array*>& usedArraysWrite,
                        set<DIST::Array*>& allUsedArrays, set<DIST::Array*>& allUsedArraysWrite)
{
    if (exp)
    {
        if (isArrayRef(exp))
        {
            DIST::Array *arrayRef = NULL;
            SgSymbol *symbS = OriginalSymbol(exp->symbol());
            const string symb = symbS->identifier();

            if (symbS)
                arrayRef = getArrayFromDeclarated(declaratedInStmt(symbS), symb);
            else
                printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

            // only distributed arrays were added
            if (arrayRef)
            {
                if (arrayRef->IsNotDistribute() == false)
                {
                    usedArrays.insert(arrayRef);
                    if (isWirte)
                        usedArraysWrite.insert(arrayRef);
                }
                allUsedArrays.insert(arrayRef);
                if (isWirte)
                    allUsedArraysWrite.insert(arrayRef);
            }

        }
        
        findArrayRef(exp->lhs(), false, usedArrays, usedArraysWrite, allUsedArrays, allUsedArraysWrite);
        findArrayRef(exp->rhs(), false, usedArrays, usedArraysWrite, allUsedArrays, allUsedArraysWrite);        
    }
}

static void findArrayRefs(LoopGraph *loop)
{
    for (SgStatement *st = loop->loop->lexNext(); st != loop->loop->lastNodeOfStmt(); st = st->lexNext())
    {
        if (isSgExecutableStatement(st) && !isDVM_stat(st) && !isSPF_stat(st))
        {
            for (int z = 0; z < 3; ++z)
            {
                bool isWrite = false;
                if (z == 0 && st->variant() == ASSIGN_STAT)
                    isWrite = true;
                findArrayRef(st->expr(z), isWrite, loop->usedArrays, loop->usedArraysWrite, loop->usedArraysAll, loop->usedArraysWriteAll);
            }
        }
    }
}

static bool isLoopStat(SgStatement* st)
{
    const int var = st->variant();
    return (var == FOR_NODE || var == WHILE_NODE);
}

static bool hasSubstringRef(SgExpression* ex)
{
    bool res = false;
    if (ex)
    {
        if (ex->variant() == ARRAY_OP)
            return true;

        if (ex->variant() == ARRAY_REF && !isArrayRef(ex))
            return true;

        if (ex->lhs())
            res |= hasSubstringRef(ex->lhs());
        if (ex->rhs())
            res |= hasSubstringRef(ex->rhs());
    }
    return res;
}

static bool hasSubstringRef(SgStatement* loop)
{
    for (SgStatement* st = loop->lexNext(); st != loop->lastNodeOfStmt(); st = st->lexNext())
    {
        if (isSgExecutableStatement(st) && !isDVM_stat(st) && !isSPF_stat(st))
        {
            for (int z = 0; z < 3; ++z)
            {
                bool res = hasSubstringRef(st->expr(z));
                if (res)
                    return true;
            }
        }
    }

    return false;
}

void loopGraphAnalyzer(SgFile *file, vector<LoopGraph*> &loopGraph, const vector<SpfInterval*> &intervalTree, vector<Messages> &messages, int sharedMemoryParallelization)
{
    map<int, SpfInterval*> mapIntervals;
    createMapOfinterval(mapIntervals, intervalTree);

    int funcNum = file->numberOfFunctions();
    __spf_print(DEBUG, "functions num in file = %d\n", funcNum);

    for (int i = 0; i < funcNum; ++i)
    {
        map<int, vector<int>> labelsRef;

        SgStatement *st = file->functions(i);
        findAllRefsToLables(st, labelsRef);

        if (st->variant() == PROG_HEDR)
        {
            SgProgHedrStmt *progH = (SgProgHedrStmt*)st;
            __spf_print(DEBUG, "*** Program <%s> started at line %d / %s\n", progH->symbol()->identifier(), st->lineNumber(), st->fileName());
        }
        else if (st->variant() == PROC_HEDR)
        {
            SgProcHedrStmt *procH = (SgProcHedrStmt*)st;
            __spf_print(DEBUG, "*** Function <%s> started at line %d / %s\n", procH->symbol()->identifier(), st->lineNumber(), st->fileName());
        }
        else if (st->variant() == FUNC_HEDR)
        {
            SgFuncHedrStmt *funcH = (SgFuncHedrStmt*)st;
            __spf_print(DEBUG, "*** Function <%s> started at line %d / %s\n", funcH->symbol()->identifier(), st->lineNumber(), st->fileName());
        }

        SgStatement *lastNode = st->lastNodeOfStmt();
        vector<LoopGraph*> parentLoops;
        LoopGraph *currLoop = NULL;
        
        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;

            if (!__gcov_doesThisLineExecuted(st->fileName(), st->lineNumber()))
            {
                st = st->lexNext();
                continue;
            }

            //printf("new st with var = %d, on line %d\n", st->variant(), st->lineNumber());
            if (isLoopStat(st))
            {
                LoopGraph *newLoop = new LoopGraph();
                newLoop->lineNum = st->lineNumber();
                if (newLoop->lineNum < 0)
                {
                    if (st->localLineNumber() > 0)
                    {
                        SgStatement* copied = SgStatement::getStatementByFileAndLine(st->fileName(), st->localLineNumber());
                        checkNull(copied, convertFileName(__FILE__).c_str(), __LINE__);
                        if (notDeletedVectorAssign(copied))
                            newLoop->altLineNum = st->localLineNumber();
                    }
                }

                SgStatement *afterLoop = st->lastNodeOfStmt()->lexNext();
                //< 0 was appear after CONVERT_ASSIGN_TO_LOOP pass
                while (afterLoop->lineNumber() < 0 || (isDVM_stat(afterLoop) && 
                                                       afterLoop->variant() != DVM_INTERVAL_DIR && 
                                                       afterLoop->variant() != DVM_ENDINTERVAL_DIR))
                {
                    afterLoop = afterLoop->lastNodeOfStmt();
                    afterLoop = afterLoop->lexNext();
                }
                newLoop->lineNumAfterLoop = afterLoop->lineNumber();

                set<int> tmpLines;

                newLoop->fileName = st->fileName();
                newLoop->perfectLoop = countPerfectLoopNest(st);
                newLoop->hasGoto = hasGoto(st, st->lastNodeOfStmt(), newLoop->linesOfInternalGoTo, newLoop->linesOfExternalGoTo, labelsRef, newLoop->linesOfCycle);
                newLoop->hasPrints = hasThisIds(st, newLoop->linesOfIO, { WRITE_STAT, READ_STAT, OPEN_STAT, CLOSE_STAT, PRINT_STAT } ); // FORMAT_STAT
                newLoop->hasStops = hasThisIds(st, newLoop->linesOfStop, { STOP_STAT, PAUSE_NODE });
                newLoop->hasDvmIntervals = hasThisIds(st, tmpLines, { DVM_INTERVAL_DIR, DVM_ENDINTERVAL_DIR, DVM_EXIT_INTERVAL_DIR });
                if (isSgForStmt(st))
                    newLoop->loopType = LoopType::FOR;
                else if (isSgWhileStmt(st))
                    newLoop->loopType = LoopType::WHILE;
                else 
                    newLoop->loopType = LoopType::NONE;
                newLoop->inCanonicalFrom = isSgForStmt(st) ? true : false;
                newLoop->hasSubstringRefs = hasSubstringRef(st);

                if (isSgForStmt(st))
                    newLoop->hasNonRectangularBounds = hasNonRect(((SgForStmt*)st), parentLoops, messages);

                auto itTime = mapIntervals.find(newLoop->lineNum);
                if (itTime != mapIntervals.end() && itTime->second->exec_time != 0)
                    newLoop->executionTimeInSec = itTime->second->exec_time / itTime->second->exec_count;
                else if (mapIntervals.size())
                {
                    //messages.push_back(Messages(NOTE, newLoop->lineNum, R137, L"Can not find execution time in statistic", 3016));
                }

                SgForStmt *currLoopRef = isSgForStmt(st);

                if (currLoopRef)
                {
                    std::tuple<int, int, int> loopInfoSES;
                    newLoop->countOfIters = calculateLoopIters(currLoopRef->start(), currLoopRef->end(), currLoopRef->step(), loopInfoSES);
                    Expression* startExpr = currLoopRef->start() ? new Expression(currLoopRef->start()) : NULL;
                    Expression* endExpr   = currLoopRef->end()   ? new Expression(currLoopRef->end())   : NULL;
                    Expression* stepExpr  = currLoopRef->step()  ? new Expression(currLoopRef->step())  : NULL;
                    newLoop->startEndStepVals = std::make_tuple(startExpr, endExpr, stepExpr);
                    newLoop->startVal = newLoop->endVal = newLoop->stepVal = 0;

                    if (newLoop->countOfIters != 0)
                    {
                        newLoop->calculatedCountOfIters = newLoop->countOfIters;
                        newLoop->startVal = std::get<0>(loopInfoSES);
                        newLoop->endVal = std::get<1>(loopInfoSES);
                        newLoop->stepVal = std::get<2>(loopInfoSES);
                    }
                    else
                    {
                        SgExpression* start = currLoopRef->start();
                        SgExpression* end = currLoopRef->end();
                        SgExpression* step = currLoopRef->step();

                        if (start == NULL || end == NULL)
                            newLoop->inCanonicalFrom = false;

                        Expression* endE = NULL;
                        Expression* startE = NULL;
                        if (step == NULL)
                        {
                            startE = new Expression(start);
                            endE = new Expression(end);
                            newLoop->stepVal = 1;
                        }
                        else
                        {
                            int res = -1;
                            int err = CalculateInteger(step, res);
                            if (err == 0 && res != 0)
                            {
                                newLoop->stepVal = res;
                                if (res > 0)
                                {
                                    startE = new Expression(start);
                                    endE = new Expression(&((*end - *start + *step) / *step));
                                }
                                else
                                {
                                    endE = new Expression(end);
                                    endE = new Expression(&((*start - *end + *step) / *step));
                                }
                            }
                        }

                        newLoop->startEndExpr = std::make_pair(startE, endE);
                    }
                }
                else
                    newLoop->startEndExpr = std::make_pair((Expression*)NULL, (Expression*)NULL);

                newLoop->loop = new Statement(st);
                newLoop->loopSymbols.addMainVar(st->symbol() ? st->symbol()->identifier() : "unknown");
                findArrayRefs(newLoop);

                SgStatement *lexPrev = st->lexPrev();
                if (lexPrev->variant() == DVM_PARALLEL_ON_DIR)
                    newLoop->userDvmDirective = new Statement(lexPrev);

                if (parentLoops.size() == 0)
                    loopGraph.push_back(newLoop);
                else
                {
                    currLoop->children.push_back(newLoop);
                    currLoop->children.back()->parent = parentLoops.back();
                }

                parentLoops.push_back(newLoop);
                currLoop = newLoop;

                addLoopVariablesToPrivateList(currLoopRef);
            }
            else if (st->variant() == CONTROL_END)
            {
                if (isLoopStat(st->controlParent()))
                {
                    parentLoops.pop_back();
                    if (parentLoops.size() != 0)
                        currLoop = parentLoops.back();
                    else
                        currLoop = NULL;
                }
            }
            else if (currLoop && (st->variant() == PROC_STAT || st->variant() == FUNC_STAT))
            {
                string pureNameOfCallFunc = removeString("call ", st->symbol()->identifier());
                for (auto &loop : parentLoops)
                    loop->calls.push_back(make_pair(pureNameOfCallFunc, st->lineNumber()));
            }
            else if (currLoop)
            {
                for (int i = 0; i < 3; ++i)
                {
                    set<string> funcCalls;
                    findFuncCalls(st->expr(i), funcCalls);

                    for (auto &loop : parentLoops)
                        for (auto k = funcCalls.begin(); k != funcCalls.end(); ++k)
                            loop->calls.push_back(make_pair(*k, st->lineNumber()));
                }
            }
            st = st->lexNext();
        }
        __spf_print(DEBUG, "Function ended\n");
    }

    for (auto &loop : loopGraph)
        loop->propagateUserDvmDir();
}

void LoopGraph::recalculatePerfect()
{
    auto loopRef = loop->GetOriginal();
    perfectLoop = isSgForStmt(loopRef) ? countPerfectLoopNest(loop->GetOriginal()) : perfectLoop;
    for (auto &loop : children)
        loop->recalculatePerfect();
}

void LoopGraph::clearUserDirectives()
{
    if (userDvmDirective)
    {
        //move comment to next statement
        if (userDvmDirective->comments())
        {
            char* comms = userDvmDirective->comments();
            string comments(comms);
            userDvmDirective->delComments();

            SgStatement* next = userDvmDirective->lexNext();
            if (next)
                next->addComment(comments.c_str());
        }

        userDvmDirective->GetOriginal()->deleteStmt();
        userDvmDirective = NULL;
    }
    for (auto& ch : children)
        ch->clearUserDirectives();
}

bool LoopGraph::hasParallelLoopsInChList()
{
    bool result = (directive != NULL);
    for (auto& ch : children)
        result |= ch->hasParallelLoopsInChList();
    return result;
}

bool LoopGraph::hasParalleDirectiveBefore()
{
    return loop->GetOriginal()->lexPrev()->variant() == DVM_PARALLEL_ON_DIR;
}

void LoopGraph::analyzeParallelDirs()
{
    if (hasParalleDirectiveBefore())
    {
        if (directive == NULL)
        {
            SgExprListExp* loops = isSgExprListExp(loop->GetOriginal()->lexPrev()->expr(2));

            int deep = loops->length();
            LoopGraph* withDir = this;
            for (int z = 0; z < deep; ++z)
            {
                if (withDir == NULL)
                    printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

                if (withDir->directive == NULL) // TODO: may be need to fill it correctly
                    withDir->directive = new ParallelDirective();

                if (withDir->children.size() == 0 && z != deep - 1)
                    printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

                if (z != deep - 1)
                    withDir = withDir->children[0];
            }
        }
    }

    for (auto& ch : children)
        ch->analyzeParallelDirs();
}

void* LoopGraph::getRealStat(const char* file) const
{
    SgStatement* local = SgStatement::getStatementByFileAndLine(file, lineNum);
    if (local == NULL)
        local = SgStatement::getStatementByFileAndLine(file, altLineNum);
    checkNull(local, convertFileName(__FILE__).c_str(), __LINE__);
    return local;
}

extern int PASSES_DONE[EMPTY_PASS];
static int getLoopState(const LoopGraph* currLoop)
{
    int loopState = 0; // 0 - unknown, 1 - good, 2 - bad
    if (PASSES_DONE[CREATE_TEMPLATE_LINKS] ||
        PASSES_DONE[LOOP_ANALYZER_NODIST])
    {
        if (currLoop->hasLimitsToParallel())
            loopState = 2;
        else
            loopState = 1;
    }
    else
    {
        if (currLoop->hasLimitsToParallel())
            loopState = 2;
    }

    return loopState;
}

static json convertToJson(const LoopGraph* currLoop)
{
    json loop;

    if (currLoop && currLoop->lineNum > 0)
    {
        loop["file"] = currLoop->fileName;
        loop["line"] = currLoop->lineNum;
        loop["lineNumAfterLoop"] = currLoop->lineNumAfterLoop;
        loop["perfectLoop"] = currLoop->perfectLoop;
        loop["loopState"] = getLoopState(currLoop);
        loop["hasNonRectangularBounds"] = (int)currLoop->hasNonRectangularBounds;

        json calls = json::array();
        for (auto& [func, line] : currLoop->calls) 
        {
            json call;
            call["line"] = line;
            call["funcName"] = func;

            calls.push_back(call);
        }
        loop["funcCalls"] = calls;

        json e_gotos = json::array();
        for (auto& line : currLoop->linesOfExternalGoTo)
            e_gotos.push_back(line);
        loop["extGotos"] = e_gotos;

        json i_gotos = json::array();
        for (auto& line : currLoop->linesOfInternalGoTo)
            i_gotos.push_back(line);
        loop["intGotos"] = i_gotos;

        json ios = json::array();
        for (auto& line : currLoop->linesOfIO)
            ios.push_back(line);
        loop["ios"] = ios;

        json stops = json::array();
        for (auto& line : currLoop->linesOfStop)
            stops.push_back(line);
        loop["stops"] = stops;

        json children = json::array();
        for (const auto& ch : currLoop->children) 
        {
            auto conv = convertToJson(ch);
            if (!conv.empty())
                children.push_back(conv);
        }
        loop["children"] = children;
    }
    return loop;
}

json convertToJson(const map<string, vector<LoopGraph*>>& loopsByFileMap)
{
    json loopsByFile = json::array();

    for (auto& byFile : loopsByFileMap)
    {
        json loop;
        const string& file = byFile.first;

        json loops_array = json::array();
        for (auto& loop : byFile.second)
        {
            auto conv = convertToJson(loop);
            if (!conv.empty())
                loops_array.push_back(conv);
        }

        loop["file"] = file;
        loop["loops"] = loops_array;

        loopsByFile.push_back(loop);
    }
    
    json allLoops;
    allLoops["allLoops"] = loopsByFile;
    return allLoops;
}

void createMapLoopGraph(const vector<LoopGraph*> &loops, map<int, LoopGraph*> &mapGraph)
{
    for (auto &elem : loops)
    {
        if (mapGraph.find(elem->lineNum) != mapGraph.end())
            printInternalError(convertFileName(__FILE__).c_str(), __LINE__);
        mapGraph[elem->lineNum] = elem;
    }

    for (auto &elem : loops)
        createMapLoopGraph(elem->children, mapGraph);
}

map<LoopGraph*, ParallelDirective*> findAllDirectives(SgFile *file, const vector<LoopGraph*> &loops, const uint64_t regId)
{
    if (loops.size() == 0)
        return map<LoopGraph*, ParallelDirective*>();

    map<LoopGraph*, ParallelDirective*> retVal;

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


    for (SgStatement *st = file->firstStatement(); st; st = st->lexNext())
    {
        if (st->variant() == DVM_PARALLEL_ON_DIR)
        {
            auto next = st->lexNext();
            if (next->variant() != FOR_NODE)
                printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

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

            if (it->second->region && it->second->region->GetId() == regId)
                retVal[it->second] = it->second->directive;            
        }
    }

    return retVal;
}

static void fillFromList(SgExpression *list, vector<long> &coords, DIST::Array *currArr)
{
    if (list)
    {
        int idx = 0;
        while (list)
        {
            if (list->lhs())
            {
                const int var = list->lhs()->variant();
                if (var == DDOT || var == KEYWORD_VAL)
                    coords[currArr->GetDimSize() - 1 - idx] = -1;
                else
                    coords[currArr->GetDimSize() - 1 - idx] = 0;
            }
            else
                coords[currArr->GetDimSize() - 1 - idx] = -1;
            idx++;
            list = list->rhs();
        }
    }
    else // full
        std::fill(coords.begin(), coords.end(), -1);
}

map<DIST::Array*, vector<long>> fillRemoteInParallel(Statement *loop)
{
    map<DIST::Array*, vector<long>> toRet;
    if (!loop->switchToFile())
        printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

    SgStatement *st = loop->lexPrev();
    if (st->variant() == DVM_PARALLEL_ON_DIR)
    {       
        map<pair<Expression*, string>, Expression*> remotes;
        fillRemoteFromComment(new Statement(st), remotes, false, DVM_PARALLEL_ON_DIR);

        for (auto &newRem : remotes)
        {
            auto key = make_pair(string(newRem.first.first->GetOriginal()->symbol()->identifier()), newRem.first.second);                
            DIST::Array *currArr = getArrayFromDeclarated(declaratedInStmt(newRem.first.first->GetOriginal()->symbol()), key.first);
            newRem.first.first->GetOriginal()->addAttribute(ARRAY_REF, currArr, sizeof(DIST::Array));
            if (toRet.find(currArr) != toRet.end())
                printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

            vector<long> coords(currArr->GetDimSize());
            SgExpression *list = newRem.second->GetOriginal();
            fillFromList(list, coords, currArr);

            toRet[currArr] = coords;
        }        
    }

    return toRet;
}

vector<std::tuple<DIST::Array*, vector<long>, pair<string, int>>> findAllSingleRemotes(SgFile *file, const uint64_t regId, vector<ParallelRegion*> &regions)
{
    vector<std::tuple<DIST::Array*, vector<long>, pair<string, int>>> retVal;

    for (SgStatement *st = file->firstStatement(); st; st = st->lexNext())
    {
        if (st->variant() == DVM_REMOTE_ACCESS_DIR)
        {
            ParallelRegion *currReg = getRegionByLine(regions, st->fileName(), st->lineNumber());
            if (currReg)
            {
                map<pair<Symbol*, string>, Expression*> remotes;
                fillRemoteFromComment(new Statement(st), remotes, false, DVM_REMOTE_ACCESS_DIR);

                for (auto &array : remotes)
                {
                    auto arrName = array.first.first->GetOriginal();
                    DIST::Array *currArr = getArrayFromDeclarated(declaratedInStmt(arrName), arrName->identifier());
                    checkNull(currArr, convertFileName(__FILE__).c_str(), __LINE__);

                    vector<long> coords(currArr->GetDimSize());
                    SgExpression *list = array.second->GetOriginal();
                    fillFromList(list, coords, currArr);

                    retVal.push_back(std::make_tuple(currArr, coords, std::make_pair(st->lexNext()->fileName(), st->lexNext()->lineNumber())));
                }
            }
        }
    }

    return retVal;
}

static void processFuncParameters(LoopGraph *loop, SgExpression *list, const FuncInfo* func)
{
    int parNum = 0;
    while (list)
    {
        if (list->lhs() && isArrayRef(list->lhs()))
        {
            SgSymbol* arrayS = OriginalSymbol(list->lhs()->symbol());
            if (arrayS->type()->variant() != T_STRING)
            {
                DIST::Array* array = getArrayFromDeclarated(declaratedInStmt(arrayS), arrayS->identifier());

                checkNull(array, convertFileName(__FILE__).c_str(), __LINE__);

                if (array->IsNotDistribute() == false)
                    if (func->funcParams.isArgOut(parNum))
                        loop->usedArraysWrite.insert(array);
            }
        }
        ++parNum;
        list = list->rhs();
    }
}

static void processExpression(SgExpression *ex, LoopGraph* loop, const map<string, FuncInfo*> &funcByName)
{
    if (ex)
    {
        if (ex->variant() == FUNC_CALL)
        {
            const string funcName = ex->symbol()->identifier();
            auto itF = funcByName.find(funcName);
            if (itF != funcByName.end())
                processFuncParameters(loop, ex->lhs(), itF->second);
        }
        processExpression(ex->lhs(), loop, funcByName);
        processExpression(ex->rhs(), loop, funcByName);
    }
}

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

    for (auto& byFile : loopGraph)
    {
        map<int, LoopGraph*> loopsMap;
        createMapLoopGraph(byFile.second, loopsMap);

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

        for (auto& loop : loopsMap)
        {
            SgStatement* start = loop.second->loop->GetOriginal();
            SgStatement* end = start->lastNodeOfStmt();

            for (SgStatement* st = start; st != end; st = st->lexNext())
            {
                if (st->variant() == PROC_STAT)
                {
                    const string funcName = st->symbol()->identifier();
                    auto itF = funcByName.find(funcName);
                    if (itF != funcByName.end())
                        processFuncParameters(loop.second, st->expr(0), itF->second);
                }
                for (int z = 0; z < 3; ++z)
                    processExpression(st->expr(z), loop.second, funcByName);
            }
        }
    }
}

bool detectMpiCalls(SgProject* proj, map<string, vector<Messages>>& SPF_messages)
{
    bool retVal = false;

    int n = proj->numberOfFiles();
    for (int i = n - 1; i >= 0; --i)
    {
        SgFile* file = &(proj->file(i));
        string fileName = file->filename();

        SgStatement* st = file->firstStatement();
        while (st)
        {
            if (st->variant() == PROC_STAT)
            {
                if (isMpiFunction(st->symbol()->identifier()))
                {
                    retVal = true;

                    wstring messageE, messageR;
                    __spf_printToLongBuf(messageE, L"Detected mpi call, turn on special regime of paralyzing");
                    __spf_printToLongBuf(messageR, R148);

                    SPF_messages[fileName].push_back(Messages(NOTE, st->lineNumber(), messageR, messageE, 1051));
                }
            }
            st = st->lexNext();
        }
    }   
    return retVal;
}

void swapLoopsForParallel(map<string, vector<LoopGraph*>>& loopGraph, map<string, vector<Messages>>& SPF_messages, const int reverse)
{
    for (auto& byFile : loopGraph)
    {
        map<int, pair<LoopGraph*, LoopGraph*>> loopHeadr;

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

        map<int, LoopGraph*> mapGraph;
        createMapLoopGraph(byFile.second, mapGraph);
        for (auto& byLoop : mapGraph)
        {
            auto forSwap = byLoop.second->getForSwap();
            if (forSwap)
                loopHeadr[reverse * byLoop.second->lineNum] = std::make_pair(byLoop.second, forSwap);
        }
        for (auto& loopPair : loopHeadr)
            ((SgForStmt*)loopPair.second.first->loop)->interchangeNestedLoops((SgForStmt*)loopPair.second.second->loop);
    }
}

static bool isVectorAssign(SgStatement *st)
{
    bool ifVector = false;
    if (!st)
        return ifVector;

    auto prev = st->lexPrev();
    if (prev && prev->variant() == CONTROL_END)
    {
        auto cp = prev->controlParent();
        if (cp->variant() == FOR_NODE)
            ifVector = cp->lineNumber() < 0 && cp->localLineNumber() > 0;
    }
    return ifVector;
}

int countPerfectLoopNest(SgStatement* st)
{
    int perfect = 1;
    if (st->variant() != FOR_NODE)
        return perfect;

    perfect = 0;
    SgStatement* next = st;
    SgStatement* last = st->lastNodeOfStmt();
    do {
        vector<SgStatement*> attrSpfPar;
        bool ifVector;
        do {
            next = next->lexNext();
            attrSpfPar.clear();
            if (next)
                attrSpfPar = getAttributes<SgStatement*, SgStatement*>(next, set<int>{ SPF_PARAMETER_OP });
            ifVector = isVectorAssign(next);
        } while (next && (ifVector && notDeletedVectorAssign(next) || attrSpfPar.size() != 0));

        do {
            last = last->lexPrev();
            attrSpfPar.clear();
            if (last)
                attrSpfPar = getAttributes<SgStatement*, SgStatement*>(last, set<int>{ SPF_PARAMETER_OP });
            ifVector = isVectorAssign(last);
        } while (last && (ifVector && notDeletedVectorAssign(last) || attrSpfPar.size() != 0));

        ++perfect;
        if (next == NULL || last == NULL)
            break;
    } while (next->variant() == FOR_NODE && last->variant() == CONTROL_END && next->lastNodeOfStmt() == last);

    return perfect;
}

#undef DEBUG
