#include "private_removing.h"

#include "errors.h"
#include "SgUtils.h"
#include "utils.h"
#include "expr_transform.h"
#include "../DeadCodeRemoving/dead_code.h"

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

using CFG_Type = map<FuncInfo*, vector<SAPFOR::BasicBlock*>>;
using UsersDirectives = map<pair<string, int>, set<SgStatement*>>;

// RegularExpr represents expressions like ( coefA * I + coefB ),
// where I is a variable and coefA or coefB can be equal to zero
struct RegularExpr {
    int coefA;
    int coefB;
    string var;
    SgSymbol* varSymbol;

    RegularExpr(): coefA(0), coefB(0), var(""), varSymbol(nullptr) {}

    string toString() const
    {
        string result = var;

        if (coefA != 1)
            result = std::to_string(coefA) + "*" + var;

        if (coefB > 0)
            result += " + " + std::to_string(coefB);
        else if (coefB < 0)
            result += " - " + std::to_string((-1) * coefB);

        return result;
    }
};

static bool operator==(const RegularExpr& left, const RegularExpr& right)
{
    return left.var == right.var && left.coefA == right.coefA && left.coefB == right.coefB;
}

static bool operator<(const RegularExpr& left, const RegularExpr& right)
{
    if (left.var == right.var && left.coefA < right.coefA && left.coefB <= right.coefB)
        return true;

    if (left.var == right.var && left.coefA == right.coefA && left.coefB < right.coefB)
        return true;

    return false;
}

// FixedSubscript represents subscript of array. Subscript is fixed if it is INT_VAL value
struct ArraySubscript {
    bool isFixed;
    int value;

    bool isRegIndex;
    RegularExpr regExprStart;
    RegularExpr regExprEnd;

    ArraySubscript()
    {
        isFixed = false;
        value = 0;
        isRegIndex = false;
        regExprStart = RegularExpr{};
        regExprEnd = RegularExpr{};
    }

    string toStringStart() const
    {
        if (isFixed)
            return std::to_string(value);

        return regExprStart.toString();
    }

    string toStringEnd() const
    {
        if (isFixed)
            return std::to_string(value);

        return regExprEnd.toString();
    }

    string toString() const
    {
        if (regExprStart == regExprEnd)
            return this->toStringStart();

        return this->toStringStart() + ":" + this->toStringEnd();
    }
};

// DefUseStmtsPair represents pair of DEF and USE statements for private variable
using DefUseStmtsPair = pair<SgAssignStmt*, SgStatement*>;

// privatesToRemoveGlobal stores the result of PRIVATE_REMOVING_ANALYSIS,
// used in PRIVATE_REMOVING pass - private vars that can be removed
static vector<PrivateToRemove> privatesToRemoveGlobal;


/* ******************************* *
 * Block of common used functions: *
 * ******************************* */

// fillDirectArrayRefs fills all direct arraySym references in exp into refs
static void fillDirectArrayRefs(SgExpression* exp, SgSymbol* arraySym, vector<SgArrayRefExp*>& refs)
{
    if (exp == nullptr)
        return;

    if (exp->variant() == ARRAY_REF && exp->symbol() != nullptr && isEqSymbols(exp->symbol(), arraySym))
    {
        refs.push_back((SgArrayRefExp*)exp);
        return;
    }

    fillDirectArrayRefs(exp->lhs(), arraySym, refs);
    fillDirectArrayRefs(exp->rhs(), arraySym, refs);
}

// getDirectArrayRefs returns all direct arraySym references in compound stmt,
// except VAR_DECL statements
static vector<SgArrayRefExp*> getDirectArrayRefs(SgStatement* stmt, SgSymbol* arraySym)
{
    vector<SgArrayRefExp*> arrayRefs;
    if (stmt == nullptr)
        return arrayRefs;

    SgStatement* st = stmt;
    while (true)
    {
        if (st->variant() != VAR_DECL && st->variant() != VAR_DECL_90) // skip var declaration stmt
            for (int i = 0; i < 3; ++i)
                fillDirectArrayRefs(st->expr(i), arraySym, arrayRefs);

        if (st == stmt->lastNodeOfStmt())
            break;
        else
            st = st->lexNext();
    }

    return arrayRefs;
}

// getDirectArrayRefs returns all direct arraySym references in single stmt,
// except VAR_DECL statements
static vector<SgArrayRefExp*> getDirectArrayRefsFromSingleStmt(SgStatement* stmt, SgSymbol* arraySym)
{
    vector<SgArrayRefExp*> arrayRefs;
    if (stmt == nullptr)
        return arrayRefs;

    if (stmt->variant() != VAR_DECL && stmt->variant() != VAR_DECL_90) // skip var declaration stmt
        for (int i = 0; i < 3; ++i)
            fillDirectArrayRefs(stmt->expr(i), arraySym, arrayRefs);


    return arrayRefs;
}

static bool isArrayRefInVector(SgArrayRefExp* ref, const vector<SgArrayRefExp*>& arrayRefs)
{
    for (SgArrayRefExp* arrayRef : arrayRefs)
        if (arrayRef->id() == ref->id())
            return true;

    return false;
}

// findAnyVar returns first found VAR_REF in expr
static SgSymbol* findAnyVar(SgExpression* expr)
{
    if (expr == nullptr)
        return nullptr;

    if (expr->variant() == VAR_REF)
        return expr->symbol();

    SgSymbol* res = findAnyVar(expr->lhs());
    if (res != nullptr)
        return res;

    return findAnyVar(expr->rhs());
}

// checkAndFillRegularExpr checks if expr is regular and fills regularExpr struct
// with info about expr
static bool checkAndFillRegularExpr(SgExpression* expr, RegularExpr& regularExpr, SgSymbol* iterationVar)
{
    if (expr == nullptr)
        return false;

    // hack for cases when iterationVar doesn't matter:
    if (iterationVar == nullptr)
        iterationVar = findAnyVar(expr);

    bool deleteTmpVar = false;
    if (iterationVar == nullptr)
    {
        iterationVar = new SgSymbol(VARIABLE_NAME, "tmp");
        deleteTmpVar = true;
    }

    pair<int, int> retCoefs;
    getCoefsOfSubscript(retCoefs, expr, iterationVar);

    regularExpr.coefA = retCoefs.first;
    regularExpr.coefB = retCoefs.second;
    if (!deleteTmpVar)
    {
        regularExpr.var = iterationVar->identifier();
        regularExpr.varSymbol = iterationVar;
    }

    if (deleteTmpVar)
        delete iterationVar;

    if (retCoefs.first != 0 || retCoefs.second != 0)
        return true;

    // check if expr is like ( 0 ), because we cannot separate zero const int expr from incorrect expr:
    if (expr->variant() == INT_VAL)
        return true;

    return false;
}

// getShortFixedSubscriptsVector returns vector of fixed INT_VAL subscripts of arrayRef
static vector<int> getShortFixedSubscriptsVector(SgArrayRefExp* arrayRef,
                                                 const vector<bool>& fixedDimensionsMask,
                                                 Regime regime)
{
    if (regime == Regime::DEFLT)
    {
        vector<int> subscriptsVector;
        for (int i = 0; i < fixedDimensionsMask.size(); ++i)
            if (fixedDimensionsMask[i])
                subscriptsVector.push_back(arrayRef->subscript(i)->valueInteger());

        return subscriptsVector;
    }
    else if (regime == Regime::REGULAR_INDEXES)
    {
        vector<int> subscriptsVector;
        SgExprListExp* indexExprList = (SgExprListExp*)arrayRef->lhs();

        for (int i = 0; i < indexExprList->length(); ++i)
        {
            SgExpression* indexExpr = indexExprList->elem(i);
            RegularExpr regularExpr;
            if (!checkAndFillRegularExpr(indexExpr, regularExpr, nullptr))
                return vector<int>{};

            subscriptsVector.push_back(regularExpr.coefA);
            subscriptsVector.push_back(regularExpr.coefB);
        }

        return subscriptsVector;
    }

    return vector<int>{};
}

// removeDuplicateArrayRefs returns unique array refereces in fixed dimensions
static vector<SgArrayRefExp*> removeDuplicateArrayRefs(const vector<SgArrayRefExp*>& arrayRefs,
                                                       const vector<bool>& fixedDimensionsMask,
                                                       Regime regime)
{
    map<vector<int>, SgArrayRefExp*> uniqueRefs;
    for (SgArrayRefExp* arrayRef : arrayRefs)
    {
        vector<int> subscripts = getShortFixedSubscriptsVector(arrayRef, fixedDimensionsMask, regime);
        if (uniqueRefs.find(subscripts) == uniqueRefs.end())
            uniqueRefs.insert(make_pair(subscripts, arrayRef));
    }

    vector<SgArrayRefExp*> result;
    for (auto& ref : uniqueRefs)
        result.push_back(ref.second);

    return result;
}

static bool isSymbolInExpression(SgSymbol* symbol, SgExpression* exp)
{
    if (exp == nullptr)
        return false;

    if (exp->symbol() != nullptr &&
        (exp->variant() == VAR_REF || exp->variant() == ARRAY_REF) &&
        isEqSymbols(exp->symbol(), symbol))
    {
        return true;
    }

    return isSymbolInExpression(symbol, exp->lhs()) ||
           isSymbolInExpression(symbol, exp->rhs());
}

// findFuncByName searches function by its name among all functions (and subroutines) in program
static FuncInfo* findFuncByName(string funcName, const map<string, vector<FuncInfo*>>& allFuncInfo)
{
    for (const auto& fileFuncs : allFuncInfo)
        for (auto funcInfo : fileFuncs.second)
            if (funcInfo->funcName == funcName)
                return funcInfo;

    return nullptr;
}

// getCurrentFunc return FuncInfo about current function for stmt
static FuncInfo* getCurrentFunc(SgStatement* stmt, const map<string, vector<FuncInfo*>>& allFuncInfo)
{
    auto fileInfo = allFuncInfo.find(stmt->fileName());
    if (fileInfo == allFuncInfo.end())
        return nullptr;

    int stmtLine = stmt->lineNumber();
    for (auto funcInfo : fileInfo->second)
        if (funcInfo->linesNum.first <= stmtLine && stmtLine <= funcInfo->linesNum.second)
            return funcInfo;

    return nullptr;
}

// fillIterationVariables fills SgSymbol* set with iteration variables of all loops
// from stmt to outerLoopStmt. If outerLoopStmt is nullptr, it fill with iteration variables
// from all outer loops
static void fillIterationVars(SgStatement* stmt, SgStatement* outerLoopStmt, vector<SgSymbol*>& vars)
{
    if (stmt == nullptr)
        return;

    if (stmt->variant() == FOR_NODE)
        vars.push_back(((SgForStmt*)stmt)->doName());

    if (stmt != outerLoopStmt)
        fillIterationVars(stmt->controlParent(), outerLoopStmt, vars);
}

/* ************************************** *
 * End of block of common used functions: *
 * ************************************** */


 /* ************************************* *
  * Block of creating messages functions: *
  * ************************************* */

static void addMessageVarNotAlignedWithLoop(vector<Messages>& messages, string varName, int loopLineNum)
{
    __spf_print(1, "  WARR: cannot remove private var '%s' - its references have different alignment with the loop %d\n",
                varName.c_str(), loopLineNum);

    wstring messageE, messageR;
    __spf_printToLongBuf(messageE, L"Cannot remove private var '%s' - its references have different alignment with the loop",
                         to_wstring(varName).c_str());
    __spf_printToLongBuf(messageR, R198, to_wstring(varName).c_str());

    messages.push_back(Messages(typeMessage::WARR, loopLineNum, messageR, messageE, 2024));
}

static void addMessageRemovePrivateVar(vector<Messages>& messages, string varName, int loopLineNum)
{
    __spf_print(1, "  NOTE: private variable '%s' was removed in loop %d\n", varName.c_str(), loopLineNum);

    wstring messageE, messageR;
    __spf_printToLongBuf(messageE, L"Private variable '%s' was removed", to_wstring(varName).c_str());
    __spf_printToLongBuf(messageR, R191, to_wstring(varName).c_str());

    messages.push_back(Messages(typeMessage::NOTE, loopLineNum, messageR, messageE, 2018));
}

static void addMessageRemovePrivateVarPart(vector<Messages>& messages, string varName, int loopLineNum)
{
    __spf_print(1, "  NOTE: private variable '%s' was partially removed in loop %d\n",
                varName.c_str(), loopLineNum);

    wstring messageE, messageR;
    __spf_printToLongBuf(messageE, L"Private variable '%s' was partially removed",
                         to_wstring(varName).c_str());
    __spf_printToLongBuf(messageR, R201, to_wstring(varName).c_str());

    messages.push_back(Messages(typeMessage::NOTE, loopLineNum, messageR, messageE, 2018));
}

static void addMessageCannotFindRD(vector<Messages>& messages, string varName, int loopLineNum)
{
    __spf_print(1, "  WARR: cannot remove private var '%s' - cannot find reaching definition for the statement in line %d\n",
                varName.c_str(), loopLineNum);

    wstring messageE, messageR;
    __spf_printToLongBuf(messageE, L"Cannot remove private var '%s' - cannot find reaching definition for the statement",
                         to_wstring(varName).c_str());
    __spf_printToLongBuf(messageR, R194, to_wstring(varName).c_str());

    messages.push_back(Messages(typeMessage::WARR, loopLineNum, messageR, messageE, 2021));
}

static void addMessagePossibleDifferentAssumption(vector<Messages>& messages, string varName,
                                                  string ref1, string ref2, int loopLineNum)
{
    __spf_print(1, "  WARR: removing of private var '%s' was made with assumption that references '%s' and '%s' are different in line %d\n",
                varName.c_str(), ref1.c_str(), ref2.c_str(), loopLineNum);

    wstring messageE, messageR;
    __spf_printToLongBuf(messageE, L"Removing of private var '%s' was made with assumption that references '%s' and '%s' are different",
                         to_wstring(varName).c_str(), to_wstring(ref1).c_str(), to_wstring(ref2).c_str());
    __spf_printToLongBuf(messageR, R192, to_wstring(varName).c_str(), to_wstring(ref1).c_str(), to_wstring(ref2).c_str());

    messages.push_back(Messages(typeMessage::WARR, loopLineNum, messageR, messageE, 2019));
}

static void addMessageRecursiveDependency(vector<Messages>& messages, string varName, int lineNum)
{
    __spf_print(1, "  WARR: cannot remove private var '%s' in line %d - it has recursive dependency\n",
                varName.c_str(), lineNum);

    wstring messageE, messageR;
    __spf_printToLongBuf(messageE, L"Cannot remove private var '%s' - it has recursive dependency",
                         to_wstring(varName).c_str());
    __spf_printToLongBuf(messageR, R189, to_wstring(varName).c_str());

    messages.push_back(Messages(typeMessage::WARR, lineNum, messageR, messageE, 2017));
}

static void addMessageDependOnNonInvariant(vector<Messages>& messages, string varName, string dependOn, int lineNum)
{
    __spf_print(1, "  WARR: cannot remove private var '%s' in line %d - it depends on non-invariant var '%s'\n",
                varName.c_str(), lineNum, dependOn.c_str());

    wstring messageE, messageR;
    __spf_printToLongBuf(messageE, L"Cannot remove private var '%s' - it depends on non-invariant var '%s'",
                         to_wstring(varName).c_str(), to_wstring(dependOn).c_str());
    __spf_printToLongBuf(messageR, R190, to_wstring(varName).c_str(), to_wstring(dependOn).c_str());

    messages.push_back(Messages(typeMessage::WARR, lineNum, messageR, messageE, 2017));
}

static void addMessageDoesNotMatchMask(vector<Messages>& messages, string varName, int loopLineNum)
{
    __spf_print(1, "  WARR: cannot remove private var '%s' in loop %d - it doesn't match any fixed dimensions mask\n",
                varName.c_str(), loopLineNum);

    wstring messageE, messageR;
    __spf_printToLongBuf(messageE, L"Cannot remove private var '%s' - it doesn't match any fixed dimensions mask",
                         to_wstring(varName).c_str());
    __spf_printToLongBuf(messageR, R188, to_wstring(varName).c_str());

    messages.push_back(Messages(typeMessage::WARR, loopLineNum, messageR, messageE, 2016));
}

static void addMessageUsageInFunctionCall(vector<Messages>& messages, string varName,
                                          string funcName, int loopLineNum, int stmtLineNum)
{
    __spf_print(1, "  WARR: cannot remove private var '%s' in loop %d - it is used in the call of function '%s' in line %d\n",
                varName.c_str(), loopLineNum, funcName.c_str(), stmtLineNum);

    wstring messageE, messageR;
    __spf_printToLongBuf(messageE, L"Cannot remove private var '%s' - it is used in the call of function '%s'",
                         to_wstring(varName).c_str(), to_wstring(funcName).c_str());
    __spf_printToLongBuf(messageR, R203, to_wstring(varName).c_str(), to_wstring(funcName).c_str());

    messages.push_back(Messages(typeMessage::WARR, stmtLineNum, messageR, messageE, 2025));
}

  /* ****************************************** *
 * End of block of creating messages functions: *
 * ******************************************** */


/* ************************************ *
 * PRIVATE_REMOVING block of functions: *
 * ************************************ */

// getDimensionVarName returns var name in style A(1, 2, *)
static string getDimensionVarName(SgSymbol* var, const vector<int>& subscripts, 
                                  const vector<bool>& fixedDimensions, Regime regime)
{
    string result = var->identifier();

    result += "(";
    if (regime == Regime::DEFLT)
    {
        for (int i = 0; i < fixedDimensions.size(); ++i)
        {
            if (fixedDimensions[i])
                result += std::to_string(subscripts[i]);
            else
                result += "*";
    
            if (i != fixedDimensions.size() - 1)
                result += ", ";
        }
    }
    else if (regime == Regime::REGULAR_INDEXES)
    {
        for (int i = 0; i < subscripts.size(); i += 2) {
            if (subscripts[i] == 0 && subscripts[(size_t)i + 1] == 0)
                result += "0";
            else
            {
                if (subscripts[i] != 0)
                {
                    if (subscripts[i] != 1)
                        result += "I_" + std::to_string(i / 2 + 1);
                    else
                        result += std::to_string(subscripts[i]) + " * I_" + std::to_string(i / 2 + 1);

                    if (subscripts[(size_t)i + 1] > 0)
                        result += " + ";
                    else if (subscripts[(size_t)i + 1] < 0)
                        result += " - ";
                }
            

                if (subscripts[(size_t)i + 1] != 0)
                    result += std::to_string(std::abs(subscripts[(size_t)i + 1]));
            }

            if (i != subscripts.size() - 2)
                result += ", ";
        }
    }
    result += ")";

    return result;
}

// getDimensionVarName returns var name in style A(1, 2, *)
static string getDimensionVarName(SgSymbol* var, const vector<ArraySubscript>& fixedSubscripts)
{
    string result = var->identifier();

    result += "(";
    for (int i = 0; i < fixedSubscripts.size(); ++i)
    {
        if (fixedSubscripts[i].isFixed)
            result += std::to_string(fixedSubscripts[i].value);
        else
            result += "*";

        if (i != fixedSubscripts.size() - 1)
            result += ", ";
    }
    result += ")";

    return result;
}

// findExpByVar returns expression related to var in varToExpMap
static SgExpression* findExpByVar(SgSymbol* var, const map<SgSymbol*, SgExpression*>& varToExpMap)
{
    for (const auto& pair : varToExpMap)
        if (isEqSymbols(pair.first, var))
            return pair.second;

    return nullptr;
}

// replaceVarsWithExps changes variables to expressions in exp using varToExpMap,
// returns modified expression
static SgExpression* replaceVarsWithExps(SgExpression* exp,
                                         const map<SgSymbol*, SgExpression*>& varToExpMap)
{
    if (exp == nullptr)
        return nullptr;

    if (exp->variant() == VAR_REF)
    {
        SgExpression* toReplace = findExpByVar(exp->symbol(), varToExpMap);
        if (toReplace != nullptr)
            return toReplace;

        return exp;
    }

    exp->setLhs(replaceVarsWithExps(exp->lhs(), varToExpMap));
    exp->setRhs(replaceVarsWithExps(exp->rhs(), varToExpMap));

    return exp;
}

// substituteExpressions replaces expressions-array references in exp using refToExpMap
static SgExpression* substituteExpressions(SgExpression* exp, 
                                           const map<string, SgExpression*>& refToExpMap)
{
    if (exp == nullptr)
        return nullptr;
    
    if (exp->variant() == ARRAY_REF)
    {
        const auto& refToExp = refToExpMap.find(exp->unparse());
        if (refToExp != refToExpMap.end())
            return refToExp->second;
    }
    
    exp->setLhs(substituteExpressions(exp->lhs(), refToExpMap));
    exp->setRhs(substituteExpressions(exp->rhs(), refToExpMap));
    
    return exp;
}

static bool isVarReadInLoop(SgSymbol* var, SgForStmt* loopStmt)
{
    for (SgStatement* st = loopStmt->lexNext(); st != loopStmt->lastNodeOfStmt(); st = st->lexNext())
    {
        int i = 0;
        if (st->variant() == ASSIGN_STAT && isEqSymbols(st->expr(0)->symbol(), var))
            i = 1;

        for (; i < 3; ++i)
            if (st->expr(i) && isSymbolInExpression(var, st->expr(i)))
                return true;
    }

    return false;
}

// isVarChangedBetween checks if the var is changed between first and second statement
// by assignment to var (it doesn't check if the var is indirectly or implicitly by function call)
static bool isVarChangedBetween(string var, SgStatement* first, SgStatement* second)
{
    for (SgStatement* st = first->lexNext(); st != second; st = st->lexNext())
        if (st->variant() == ASSIGN_STAT && st->expr(0)->symbol()->identifier() == var)
            return true;

    return false;
}

static vector<int> getShortFixedSubscriptsVector(SgArrayRefExp* arrayRef, const PrivateToRemove& varToRemove)
{
    return getShortFixedSubscriptsVector(arrayRef, varToRemove.fixedDimensions, varToRemove.regime);
}

// fillReadShortFixedSumscripts fills all short fixed subscripts vectors of array var, 
// which are used for reading from array var in exp
static void fillReadShortFixedSubscripts(SgExpression* exp, const PrivateToRemove& var,
                                         set<vector<int>>& fixedSubscripts)
{
    if (exp == nullptr)
        return;

    if (exp->symbol() != nullptr && 
        (exp->symbol()->variant() == ARRAY_REF || exp->symbol()->variant() == VARIABLE_NAME) &&
        isEqSymbols(exp->symbol(), var.varSymbol))
    {
        auto subscripts = getShortFixedSubscriptsVector((SgArrayRefExp*)exp, var);
        fixedSubscripts.insert(subscripts);
        return;
    }

    fillReadShortFixedSubscripts(exp->lhs(), var, fixedSubscripts);
    fillReadShortFixedSubscripts(exp->rhs(), var, fixedSubscripts);
}

// getReadShortFixedSubscripts return set of all short fixed subscripts vectors of array var from loop, 
// which are used for reading from array var
static set<vector<int>> getReadShortFixedSubscripts(const PrivateToRemove& var, SgForStmt* loopStmt)
{
    set<vector<int>> fixedSubscripts;
    for (SgStatement* st = loopStmt->lexNext(); st != loopStmt->lastNodeOfStmt(); st = st->lexNext())
    {
        int i = 0;
        if (st->variant() == ASSIGN_STAT && isEqSymbols(st->expr(0)->symbol(), var.varSymbol))
            i = 1;

        for (; i < 3; ++i)
            fillReadShortFixedSubscripts(st->expr(i), var, fixedSubscripts);
    }

    return fixedSubscripts;
}

// removeExcessiveDefs removes assignments to var in loop, which are excessive -- 
// there are no any reading from var with such fixed subscripts vector
static void removeExcessiveDefs(const PrivateToRemove& var)
{
    SgForStmt* loopStmt = (SgForStmt*)var.loop->loop->GetOriginal();
    set<vector<int>> usedFixedSubscripts = getReadShortFixedSubscripts(var, loopStmt);

    vector<SgStatement*> stmtsToRemove;
    for (SgStatement* st = loopStmt->lexNext(); st != loopStmt->lastNodeOfStmt(); st = st->lexNext())
    {
        if (st->variant() != ASSIGN_STAT)
            continue;

        if (st->expr(0)->symbol() == nullptr || !isEqSymbols(st->expr(0)->symbol(), var.varSymbol))
            continue;

        auto subscripts = getShortFixedSubscriptsVector((SgArrayRefExp*) st->expr(0), var);
        if (usedFixedSubscripts.find(subscripts) == usedFixedSubscripts.end())
            stmtsToRemove.push_back(st);
    }

    for (auto st : stmtsToRemove)
        st->deleteStmt();
}

// removeVarFromPrivateAttributes removes var from SPF ANALYSIS PRIVATE attributes of loop
static void removeVarFromPrivateAttributes(SgSymbol* var, LoopGraph* loop)
{
    SgStatement* loopStmt = loop->loop->GetOriginal();
    for (int i = 0; i < loopStmt->numberOfAttributes(); ++i)
    {
        SgAttribute* attr = loopStmt->getAttribute(i);
        if (attr->getAttributeType() != SPF_ANALYSIS_DIR)
            continue;

        SgStatement* data = (SgStatement*) attr->getAttributeData();
        SgExprListExp* analysisList = (SgExprListExp*) data->expr(0);
        if (analysisList == nullptr)
            continue;

        vector<SgExpression*> newAnalysisList;
        for (int i = 0; i < analysisList->length(); ++i)
        {
            if (analysisList->elem(i)->variant() == ACC_PRIVATE_OP)
            {
                SgExprListExp* varList = (SgExprListExp*) analysisList->elem(i)->lhs();
                vector<SgExpression*> newVarList;
                for (int j = 0; j < varList->length(); ++j)
                    if (!isEqSymbols(var, varList->elem(j)->symbol()))
                        newVarList.push_back(varList->elem(j)->copyPtr());

                if (!newVarList.empty())
                    newAnalysisList.push_back(new SgExpression(ACC_PRIVATE_OP, makeExprList(newVarList)));
            }
            else
                newAnalysisList.push_back(analysisList->elem(i)->copyPtr());
        }

        if (!newAnalysisList.empty())
            data->setExpression(0, makeExprList(newAnalysisList));
        else
            loopStmt->deleteAttribute(i);
    }
}

// getVarToExpMap returns map SgSymbol* from defRef -> SgExpression* from useRef
static map<SgSymbol*, SgExpression*> getVarToExpMap(SgArrayRefExp* defRef, SgArrayRefExp* useRef,
                                                    const vector<bool>& fixedDimensions, Regime regime)
{
    map<SgSymbol*, SgExpression*> varToExpMap;
    for (int i = 0; i < fixedDimensions.size(); ++i)
    {
        if (fixedDimensions[i])
            continue;

        // check not fixed dimension:
        if (regime == Regime::REGULAR_INDEXES)
        {
            RegularExpr useExpr;
            checkAndFillRegularExpr(useRef->subscript(i), useExpr, nullptr);
            RegularExpr defExpr;
            checkAndFillRegularExpr(defRef->subscript(i), defExpr, nullptr);
            varToExpMap.insert(make_pair(defExpr.varSymbol, new SgVarRefExp(useExpr.varSymbol)));
                
        }
        else
            varToExpMap.insert(make_pair(defRef->subscript(i)->symbol(), useRef->subscript(i)));
    }
    
    return varToExpMap;
}

// removeArray removes array by substituting it in DEF-USE pairs.
// Returns set of removed fixed subscripts
static set<vector<int>> removeArray(string filename, PrivateToRemove& arrayToRemove)
{
    set<vector<int>> removedFixedSubscripts;

    auto& fixedDimensions = arrayToRemove.fixedDimensions;
    for (auto& defUsePair : arrayToRemove.defUseStmtsPairs)
    {
        map<string, SgExpression*> refToExpMap;

        SgAssignStmt* defStmt = defUsePair.first;
        SgStatement* useStmt = defUsePair.second;

        SgArrayRefExp* defRef = (SgArrayRefExp*)defStmt->lhs();
        vector<int> defFixedSubscripts = getShortFixedSubscriptsVector(defRef, arrayToRemove);

        int startIndex = 0;
        if (useStmt->variant() == ASSIGN_STAT)
            startIndex = 1;

        for (int i = startIndex; i < 3; ++i)
        {
            vector<SgArrayRefExp*> arrayUseRefs;
            fillDirectArrayRefs(useStmt->expr(i), arrayToRemove.varSymbol, arrayUseRefs);
            if (arrayUseRefs.empty())
                continue;

            for (SgArrayRefExp* useRef : arrayUseRefs)
            {
                vector<int> useFixedSubscripts = getShortFixedSubscriptsVector(useRef, arrayToRemove);
                if (defFixedSubscripts != useFixedSubscripts)
                    continue; // because useRef and defRef can be different in subscripts of fixed dimensions

                removedFixedSubscripts.insert(useFixedSubscripts);

                auto varToExpMap = getVarToExpMap(defRef, useRef, fixedDimensions, arrayToRemove.regime);

                SgExpression* expToSubst = defStmt->rhs()->copyPtr();
                expToSubst = replaceVarsWithExps(expToSubst, varToExpMap);

                refToExpMap.insert(make_pair(useRef->unparse(), expToSubst));
            }

            SgExpression* substExp = substituteExpressions(useStmt->expr(i), refToExpMap);
            useStmt->setExpression(i, *substExp);
        }
    }

    return removedFixedSubscripts;
}

void removePrivates(string filename, vector<Messages>& messages,
                    const map<string, CommonBlock*>& commonBlocks,
                    const map<string, vector<FuncInfo*>>& allFuncInfo,
                    int& countOfTransform)
{
    set<LoopGraph*> removeDC;
    for (auto& varToRemove : privatesToRemoveGlobal)
    {
        if (filename != varToRemove.loop->fileName)
            continue;

        auto removedDimensions = removeArray(filename, varToRemove);
        countOfTransform++;

        removeExcessiveDefs(varToRemove);
        removeDC.insert(varToRemove.loop);

        vector<SgArrayRefExp*> varRefs = getDirectArrayRefs(varToRemove.loop->loop, varToRemove.varSymbol);
        int loopLineNum = varToRemove.loop->lineNum;
        string varName = varToRemove.varSymbol->identifier();
        auto& fixedDimensions = varToRemove.fixedDimensions;
        if (varRefs.empty())
        {
            removeVarFromPrivateAttributes(varToRemove.varSymbol, varToRemove.loop);
            addMessageRemovePrivateVar(messages, varName, loopLineNum);
        }
        else
        {
            varRefs = removeDuplicateArrayRefs(varRefs, fixedDimensions, varToRemove.regime);
            for (auto& varRef : varRefs)
            {
                vector<int> subscripts = getShortFixedSubscriptsVector(varRef, varToRemove);
                if (removedDimensions.find(subscripts) != removedDimensions.end())
                {
                    varName = getDimensionVarName(varToRemove.varSymbol, subscripts,
                                                  fixedDimensions, varToRemove.regime);
                    addMessageRemovePrivateVarPart(messages, varName, loopLineNum);
                }

                removedDimensions.erase(subscripts);
            }

            for (auto& removedDimension : removedDimensions)
            {
                varName = getDimensionVarName(varToRemove.varSymbol, removedDimension,
                                              fixedDimensions, varToRemove.regime);
                addMessageRemovePrivateVar(messages, varName, loopLineNum);
            }
        }
    }

    // remove dead code from loop:
    for (auto& dcLoopRem : removeDC)
    {
        auto loopStmt = dcLoopRem->loop->GetOriginal();
        FuncInfo* currFunc = getCurrentFunc(loopStmt, allFuncInfo);

        if (currFunc == nullptr)
            printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

        removeDeadCode(currFunc->funcPointer, allFuncInfo, commonBlocks,
                       loopStmt, loopStmt->lastNodeOfStmt());
    }
}

/* ****************************************** *
 * End of PRIVATE_REMOVING block of functions *
 * ****************************************** */


/* ********************************************* *
 * PRIVATE_REMOVING_ANALYSIS block of functions: *
 * ********************************************* */

// Context stores general info about current loop and array to remove
struct Context {
    const map<string, vector<FuncInfo*>>& allFuncInfo;
    const map<string, CommonBlock*>& commonBlocks;
    vector<Messages>& messages;
    string filename;

    Regime regime;
    LoopGraph* loop;
    SgForStmt* loopStmt;
    SgSymbol* arraySymbol;
    int dimensionsNum;
    vector<SgArrayRefExp*> explicitArrayRefs;
    vector<bool> fixedDimensionsMask;
};

 // ReducedArrayVars represents mapping of array reference to reduced scalar var:
 // arr(1, i) -> pair<vector<int>,  SgSymbol*>: [1], arr_1
class ReducedArrayVarsMap {
    map<vector<int>, SgSymbol*> arrayMap;

public:
    void insert(vector<int> subscripts, SgSymbol* var)
    {
        this->arrayMap.insert(make_pair(subscripts, var));
    }

    // find looks up for reduced array var with provided subscripts,
    // returns this var or nullptr
    SgSymbol* find(const vector<int>& subscripts) const
    {
        const auto& arr = this->arrayMap.find(subscripts);
        if (arr == this->arrayMap.end())
            return nullptr;

        return arr->second;
    }

    // getAllVars returns all reduced array vars
    vector<SgSymbol*> getAllVars() const
    {
        vector<SgSymbol*> vars;
        for (auto& reducedVar : this->arrayMap)
            vars.push_back(reducedVar.second);

        return vars;
    }
};

enum class TypeOfInsertedStmt { DEF = 1, USE, DECLARATION, NONE };

// InsertedStatement represents inserted statement for reaching definition analysis
struct InsertedStatement {
    TypeOfInsertedStmt type = TypeOfInsertedStmt::NONE;
    SgStatement* insertedStmt  = nullptr;
    SgStatement* relatedToStmt = nullptr;
    bool isRecursive = false; // true for DEF stmt if it is recursive

    InsertedStatement() {};

    InsertedStatement(TypeOfInsertedStmt type, SgStatement* insertedStmt)
    {
        this->type = type;
        this->insertedStmt = insertedStmt;
    }
};

// findInsertedStmt looking for stmt in insertedStmts
static vector<InsertedStatement>::const_iterator findInsertedStmt(const vector<InsertedStatement>& insertedStmts,
                                                                  SgStatement* stmt)
{
    for (auto iter = insertedStmts.begin(); iter != insertedStmts.end(); ++iter)
        if (iter->insertedStmt->id() == stmt->id())
            return iter;

    return insertedStmts.end();
}

static vector<int> getShortFixedSubscriptsVector(Context* ctx, SgArrayRefExp* arrayRef)
{
    return getShortFixedSubscriptsVector(arrayRef, ctx->fixedDimensionsMask, ctx->regime);
}

// getLoopsInfo return vector of pair (string, int) - doName and level for each loop
// from stmt up to outerLoop (not only closely nested). Loop levels start from 1
static void getLoopsInfo(SgStatement* stmt, SgStatement* outerLoop,
                         vector<pair<string, int>>& loopsInfo)
{
    if (stmt == nullptr)
        printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

    if (stmt->variant() == FOR_NODE)
    {
        string doName = ((SgForStmt*)stmt)->doName()->identifier();
        loopsInfo.push_back(make_pair(doName, 0));
    }

    if (stmt == outerLoop)
    {
        int levelsNum = loopsInfo.size();
        for (int i = 0; i < levelsNum; ++i)
            loopsInfo[i].second = levelsNum - i;

        return;
    }

    getLoopsInfo(stmt->controlParent(), outerLoop, loopsInfo);
}

// checkLoopAlignmentMatching checks if all array references in loop has the same alignment
// with nested loops
static bool checkLoopAlignmentMatching(Context* ctx)
{
    // Loop alignment is a pair of loop alignment vector
    // and a number of nested loops for current statement context (not only closely nested).
    // Loop alignment vector is a vector of ctx->dimensionsNum length of loop levels
    // or -1, if dimension is not aligned with any loop.
    // Dimension is aligned with loop, if subscript expression has iteration var of this loop
    // (and only of this loop).
    set<pair<vector<int>, int>> loopAlignmentSet;

    for (SgStatement* st = ctx->loopStmt; st != ctx->loopStmt->lastNodeOfStmt(); st = st->lexNext())
    {
        vector<SgArrayRefExp*> arrayRefs = getDirectArrayRefsFromSingleStmt(st, ctx->arraySymbol);
        if (arrayRefs.empty())
            continue;

        vector<pair<string, int>> loopsInfoVector;
        getLoopsInfo(st, ctx->loopStmt, loopsInfoVector);

        for (SgArrayRefExp* arrayRef : arrayRefs)
        {
            vector<int> arrayRefLoopAlignment;
            for (int i = 0; i < ctx->dimensionsNum; ++i)
            {
                bool foundIterationVar = false;

                set<string> vars;
                getVariables(arrayRef->subscript(i), vars, { VAR_REF });
                for (string var : vars)
                {
                    for (auto& loopInfo : loopsInfoVector)
                    {
                        if (loopInfo.first != var)
                            continue;

                        // There are two iteration vars in subscript expression
                        // or one var is iteration var for more than one loop:
                        if (foundIterationVar)
                            return false;

                        foundIterationVar = true;
                        arrayRefLoopAlignment.push_back(loopInfo.second);
                    }
                }

                if (!foundIterationVar)
                    arrayRefLoopAlignment.push_back(-1);
            }

            loopAlignmentSet.insert(make_pair(arrayRefLoopAlignment, loopsInfoVector.size()));
        }
    }

    // Check if all loop alignments has similar alignment diff.
    // Alignment diff is a difference between the number of nested loops (size of loopInfoVector)
    // and the number of aligned dimensions (alignment counter).
    int alignmentDiff = -1;
    for (auto& loopAlignment : loopAlignmentSet)
    {
        int currentAlignmentCounter = ctx->dimensionsNum;
        for (int i = 0; i < ctx->dimensionsNum; ++i)
            if (loopAlignment.first[i] == -1) // if dimension is not aligned
                currentAlignmentCounter--;

        int currentAlignmentDiff = loopAlignment.second - currentAlignmentCounter;
        if (alignmentDiff == -1)
            alignmentDiff = currentAlignmentDiff;
        else if (alignmentDiff != currentAlignmentDiff)
            return false;
    }

    // Check if there are any different loop alignments.
    // Two loop alignments are different if they have different loop level alignments
    // in the same dimension. The case when one of two different loop level is -1
    // (there is no loop alignment) is not a problem
    for (int i = 0; i < ctx->dimensionsNum; ++i)
    {
        int currentLoopLevel = -1;
        for (auto& loopAlignment : loopAlignmentSet)
        {
            if (loopAlignment.first[i] != -1) // if loop alignment is not empty
            {
                if (currentLoopLevel == -1)
                    currentLoopLevel = loopAlignment.first[i];
                else if (currentLoopLevel != loopAlignment.first[i])
                    return false;
            }
        }
    }

    return true;
}

// matchesFixedDimensionsMask checks if all array references have INT_VAL value in fixed dimension
static bool checkFixedDimensionsMaskMatching(Context* ctx)
{
    for (SgArrayRefExp* ref : ctx->explicitArrayRefs)
        for (int i = 0; i < ctx->fixedDimensionsMask.size(); ++i)
            if (ctx->fixedDimensionsMask[i] && ref->subscript(i)->variant() != INT_VAL)
                return false;

    return true;
}

// checkDefStmtRefsMatchesMask checks if all DEF array refs have INT_VAL value in fixed dimensions
// and VAR_REF in non-fixed dimensions
static bool checkDefStmtRefsMatchesMask(Context* ctx)
{
    for (SgStatement* st = ctx->loopStmt->lexNext(); st != ctx->loopStmt->lastNodeOfStmt(); st = st->lexNext())
    {
        if (st->variant() != ASSIGN_STAT)
            continue;

        vector<SgSymbol*> iterationVars;
        fillIterationVars(st, ctx->loopStmt, iterationVars);

        if (isEqSymbols(st->expr(0)->symbol(), ctx->arraySymbol)) // DEF statement
        {
            SgArrayRefExp* ref = (SgArrayRefExp*)st->expr(0);
            for (int i = 0; i < ctx->fixedDimensionsMask.size(); ++i)
            {   
                if (ctx->fixedDimensionsMask[i] && ref->subscript(i)->variant() == INT_VAL)
                    continue;

                if (!ctx->fixedDimensionsMask[i] && ref->subscript(i)->variant() == VAR_REF)
                {
                    bool isIterationVar = false;
                    for (auto iterationvVar : iterationVars)
                        if (isEqSymbols(ref->subscript(i)->symbol(), iterationvVar))
                            isIterationVar = true;
                    
                    if (!isIterationVar)
                        return false;

                    continue;
                }
                
                return false;
            }
        }
    }

    return true;
}

// getFixedDimensionsVector returns vector of fixed dimensions for arrayRef.
// dimension is fixed, when the value of subsctipt is INT_VAL
static vector<bool> getFixedDimensionsVector(SgArrayRefExp* arrayRef)
{
    vector<bool> fixedDimensions(arrayRef->numberOfSubscripts(), false);
    for (int i = 0; i < arrayRef->numberOfSubscripts(); ++i)
        if (arrayRef->subscript(i)->variant() == INT_VAL)
            fixedDimensions[i] = true;

    return fixedDimensions;
}

// sunparseFixedDimensionsVector unparses fixed dimensions vector as string
static string sunparseFixedDimensionsVector(const vector<bool>& fixedDimensions)
{
    string result = "<";
    result.reserve(result.size() + 7 * fixedDimensions.size());
    for (int i = 0; i < fixedDimensions.size(); ++i)
    {
        if (fixedDimensions[i] == true)
            result += "true";
        else
            result += "false";

        if (i != fixedDimensions.size() - 1)
            result += ", ";
    }
    result += ">";

    return result;
}

static bool checkRegularIndexRefs(Context* ctx)
{
    vector<SgArrayRefExp*> newArrayRefsVector;
    for (SgStatement* st = ctx->loopStmt->lexNext(); st != ctx->loopStmt->lastNodeOfStmt(); st = st->lexNext())
    {
        vector<SgSymbol*> iterationVars;
        fillIterationVars(st, ctx->loopStmt, iterationVars);

        vector<SgArrayRefExp*> arrayRefs = getDirectArrayRefsFromSingleStmt(st, ctx->arraySymbol);
        for (auto arrayRef : arrayRefs)
        {
            if (!isArrayRefInVector(arrayRef, ctx->explicitArrayRefs))
                continue;

            // check if unfixed dimension index contains iteration var:
            SgExprListExp* indexExprList = (SgExprListExp*)arrayRef->lhs();
            for (int i = 0; i < indexExprList->length(); ++i)
            {
                if (ctx->fixedDimensionsMask[i])
                    continue;

                SgExpression* indexExpr = indexExprList->elem(i);
                RegularExpr regularExpr;
                if (!checkAndFillRegularExpr(indexExpr, regularExpr, nullptr))
                    return false;

                if (regularExpr.coefA == 0)
                    return false;
                
                bool isIterationVar = false;
                for (SgSymbol* iterationVar : iterationVars)
                {
                    if (iterationVar->identifier() == regularExpr.var)
                    {
                        isIterationVar = true;
                        break;
                    }
                }

                if (!isIterationVar)
                    return false;
            }
        }
    }

    return true;
}

// getFixedDimensionsMask finds fixed dimensions vector with minimum number of fixed dimensions
// and writes messages if array doesn't have fixed dimensions
static vector<bool> getFixedDimensionsMask(Context* ctx)
{
    vector<bool> resultMask(ctx->dimensionsNum, true);
    for (const auto arrayRef : ctx->explicitArrayRefs)
    {
        vector<bool> fixedDimensions = getFixedDimensionsVector(arrayRef);
        for (int i = 0; i < ctx->dimensionsNum; ++i)
            resultMask[i] = resultMask[i] && fixedDimensions[i];
    }

    string unparsedFixedDimensions = sunparseFixedDimensionsVector(resultMask);
    __spf_print(1, "  NOTE: found fixed subsripts mask %s for array '%s' in loop %d\n",
                unparsedFixedDimensions.c_str(), ctx->arraySymbol->identifier(), ctx->loop->lineNum);

    return resultMask;
}

// getScopeLoopStmt returns least outer scope loop statement
static SgForStmt* getScopeLoopStmt(SgStatement* stmt)
{
    while (stmt != nullptr && stmt->variant() != FOR_NODE)
        stmt = stmt->controlParent();

    return (SgForStmt*)stmt;
}

// getLoopStmtForVar searches for loop with iteration var equal loopVar starting from from stmt
static SgForStmt* getLoopStmtForVar(SgStatement* stmt, string loopVar)
{
    while (stmt != nullptr)
    {
        SgForStmt* loopStmt = getScopeLoopStmt(stmt);
        
        if (loopStmt->doName()->identifier() == loopVar)
            return loopStmt;

        stmt = stmt->controlParent();
    }

    return nullptr;
}

// getFixedSubscriptsVector returns vector of fixed INT_VAL subscripts of arrayRef
// true - subscript is fixed, false - it isn't
static vector<ArraySubscript> getFixedSubscriptsVector(SgArrayRefExp* arrayRef, int dimensionsNum = 0,
                                                       SgStatement* stmt = nullptr)
{
    if (arrayRef == nullptr || arrayRef->numberOfSubscripts() == 0)
        return vector<ArraySubscript>(dimensionsNum);

    vector<ArraySubscript> subscriptsVector;
    for (int i = 0; i < arrayRef->numberOfSubscripts(); ++i)
    {
        SgExpression* subscriptExpr = arrayRef->subscript(i);
        ArraySubscript sub;

        if (subscriptExpr->variant() == INT_VAL)
        {
            sub.isFixed = true;
            sub.value = subscriptExpr->valueInteger();
        }
        else if (stmt != nullptr && subscriptExpr->variant() == VAR_REF)
        {
            SgForStmt* loopStmt = getLoopStmtForVar(stmt, subscriptExpr->symbol()->identifier());
            if (loopStmt != nullptr)
            {
                RegularExpr regExprStep;
                SgExpression* step = loopStmt->step();
                if (step == nullptr || checkAndFillRegularExpr(step, regExprStep, nullptr) && regExprStep.coefA == 0)
                {
                    SgExpression* start = loopStmt->start();
                    SgExpression* end = loopStmt->end();
                    if (regExprStep.coefB < 0)
                        std::swap(start, end);

                    bool isRegular = true;
                    isRegular = isRegular && checkAndFillRegularExpr(start, sub.regExprStart, nullptr);
                    isRegular = isRegular && checkAndFillRegularExpr(end, sub.regExprEnd, nullptr);
                    sub.isRegIndex = isRegular;
                }
            }
        }
        else {
            RegularExpr regExpr;
            if (checkAndFillRegularExpr(subscriptExpr, regExpr, nullptr)) {
                sub.isRegIndex = true;
                sub.regExprStart = regExpr;
                sub.regExprEnd = regExpr;
            }
        }

        subscriptsVector.push_back(sub);
    }

    return subscriptsVector;
}

// checkImplicitDirectUsage returns masks of array implicit usage (as out argument) 
// in any function call in exp and writes message about each usage
static void checkImplicitDirectUsage(Context* ctx, SgExpression* exp, int stmtLineNum,
                                     vector<vector<ArraySubscript>>& fixedSubscripts)
{
    if (exp == nullptr)
        return;

    if (exp->variant() == FUNC_CALL)
    {
        SgFunctionCallExp* funcCallExp = (SgFunctionCallExp*)exp;
        string funcName = funcCallExp->funName()->identifier();
        FuncInfo* funcInfo = findFuncByName(funcName, ctx->allFuncInfo);
        if (funcInfo != nullptr)
        {
            for (int i = 0; i < funcCallExp->numberOfArgs(); ++i)
            {
                SgExpression* funcArg = funcCallExp->arg(i);
                if (funcArg->symbol() == nullptr || !isEqSymbols(funcArg->symbol(), ctx->arraySymbol))
                    continue;

                if (funcInfo->funcParams.isArgOut(i) || funcArg->lhs() == nullptr)
                {
                    auto fixedVec = getFixedSubscriptsVector((SgArrayRefExp*)funcArg, ctx->dimensionsNum);
                    fixedSubscripts.push_back(fixedVec);
                    addMessageUsageInFunctionCall(ctx->messages, getDimensionVarName(ctx->arraySymbol, fixedVec),
                                                  funcName, ctx->loop->lineNum, stmtLineNum);
                }
            }
        }
    }

    checkImplicitDirectUsage(ctx, exp->lhs(), stmtLineNum, fixedSubscripts);
    checkImplicitDirectUsage(ctx, exp->rhs(), stmtLineNum, fixedSubscripts);
}

// checkImplicitDirectUsage returns masks of array implicit usage (as out argument) 
// and reference to whole array (like fcall(A, 1)) in any function call in loop 
// and writes message about each usage
static vector<vector<ArraySubscript>> checkImplicitDirectUsage(Context* ctx)
{
    vector<vector<ArraySubscript>> fixedSubscripts;
    for (SgStatement* st = ctx->loopStmt->lexNext(); st != ctx->loopStmt->lastNodeOfStmt(); st = st->lexNext())
    {
        if (st->variant() != PROC_STAT)
        {
            for (int i = 0; i < 3; ++i)
                checkImplicitDirectUsage(ctx, st->expr(i), st->lineNumber(), fixedSubscripts);

            continue;
        }
    
        // st->variant() == PROC_STAT:
        SgCallStmt* callStmt = (SgCallStmt*)st;
        string procName = callStmt->name()->identifier();
        FuncInfo* funcInfo = findFuncByName(procName, ctx->allFuncInfo);
        
        for (int i = 0; i < callStmt->numberOfArgs(); ++i)
        {
            SgExpression* callArg = callStmt->arg(i);
            if (callArg->symbol() == nullptr || !isEqSymbols(callArg->symbol(), ctx->arraySymbol))
                continue;
            
            if (funcInfo == nullptr || // no info about function
                funcInfo->funcParams.isArgOut(i) || // implicit direct usage
                callArg->lhs() == nullptr) // reference to whole array
            {
                auto mask = getFixedSubscriptsVector((SgArrayRefExp*)callArg, ctx->dimensionsNum);
                fixedSubscripts.push_back(mask);
                addMessageUsageInFunctionCall(ctx->messages, getDimensionVarName(ctx->arraySymbol, mask),
                                              procName, ctx->loop->lineNum, st->lineNumber());
            } 
        }
    }

    return fixedSubscripts;
}

static vector<Variable*> getCommonBlockGroupedVar(FuncInfo* curFunc, SgSymbol* varSymbol,
                                                  const map<string, CommonBlock*>& commonBlocks)
{
    for (auto& block : commonBlocks)
    {
        for (auto& groupedVar : block.second->getGroupedVars())
        {
            for (Variable* var : groupedVar.second)
            {
                for (const CommonVariableUse& varUse : var->getAllUse())
                {

                    if (varUse.getFileName() == curFunc->fileName &&
                        varUse.getFunctionName() == curFunc->funcName &&
                        isEqSymbols(varUse.getUseS(), varSymbol))
                    {
                        return groupedVar.second;
                    }
                }
            }
        }
    }

    return vector<Variable*>{};
}

// checkIndirectUsage returns masks of array indirect usage in function
// (indirect usage is usage through common blocks) and writes messages about it
static void checkIndirectUsage(Context* ctx, FuncInfo* calledFunc, vector<Variable*> commonBlockGroupedVar,
                               set<string>& visitedFuncs, vector<vector<ArraySubscript>>& indirectUsageMasks)
{
    if (calledFunc == nullptr)
        return;

    if (visitedFuncs.find(calledFunc->funcName) != visitedFuncs.end())
        return;

    visitedFuncs.insert(calledFunc->funcName);

    for (Variable* commonBlockVar : commonBlockGroupedVar)
    {
        for (const CommonVariableUse& varUse : commonBlockVar->getAllUse())
        {
            if (varUse.getFileName() != calledFunc->fileName || varUse.getFunctionName() != calledFunc->funcName)
                continue;

            SgStatement* calledFuncStmt = varUse.getFunction();
            calledFuncStmt->switchToFile();
            vector<SgArrayRefExp*> directArrayRefs = getDirectArrayRefs(calledFuncStmt, varUse.getUseS());
            for (auto arrayRef : directArrayRefs)
            {
                auto mask = getFixedSubscriptsVector(arrayRef, ctx->dimensionsNum);
                indirectUsageMasks.push_back(mask);
                addMessageUsageInFunctionCall(ctx->messages, getDimensionVarName(ctx->arraySymbol, mask),
                                              calledFunc->funcName, ctx->loop->lineNum, ctx->loop->lineNum);
            }
            ctx->loopStmt->switchToFile();
        }
    }

    for (FuncInfo* subCalledFunc : calledFunc->callsFromV)
        checkIndirectUsage(ctx, subCalledFunc, commonBlockGroupedVar, visitedFuncs, indirectUsageMasks);
}

// checkIndirectUsage returns masks of array indirect usage in any function call in exp
// (indirect usage is usage through common blocks) and writes messages about it
static void checkIndirectUsage(Context* ctx, SgExpression* exp, vector<Variable*> commonBlockGroupedVar,
                               set<string>& visitedFuncs, vector<vector<ArraySubscript>>& indirectUsageMasks)
{
    if (exp == nullptr)
        return;

    if (exp->variant() == FUNC_CALL)
    {
        SgFunctionCallExp* funcCallExp = (SgFunctionCallExp*)exp;
        string funcName = funcCallExp->funName()->identifier();
        FuncInfo* funcInfo = findFuncByName(funcName, ctx->allFuncInfo);
        if (funcInfo != nullptr)
            checkIndirectUsage(ctx, funcInfo, commonBlockGroupedVar, visitedFuncs, indirectUsageMasks);
    }

    checkIndirectUsage(ctx, exp->lhs(), commonBlockGroupedVar, visitedFuncs, indirectUsageMasks);
    checkIndirectUsage(ctx, exp->rhs(), commonBlockGroupedVar, visitedFuncs, indirectUsageMasks);
}

// checkIndirectUsage returns masks of array indirect usage in any function call in loop
// (indirect usage is usage through common blocks) and writes messages about it
static vector<vector<ArraySubscript>> checkIndirectUsage(Context* ctx)
{
    vector<vector<ArraySubscript>> indirectUsageMasks;
    FuncInfo* currentFunc = getCurrentFunc(ctx->loopStmt, ctx->allFuncInfo);
    if (currentFunc == nullptr)
        printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

    const auto& blocks = ctx->commonBlocks;
    vector<Variable*> commonBlockGroupedVar = getCommonBlockGroupedVar(currentFunc, ctx->arraySymbol, blocks);
    if (commonBlockGroupedVar.empty())
        return indirectUsageMasks;

    set<string> visitedFunctions = { currentFunc->funcName };
    for (SgStatement* st = ctx->loopStmt->lexNext(); st != ctx->loopStmt->lastNodeOfStmt(); st = st->lexNext())
    {
        if (st->variant() == PROC_STAT)
        {
            SgCallStmt* callStmt = (SgCallStmt*)st;
            string procName = callStmt->name()->identifier();
            FuncInfo* calledFunc = findFuncByName(procName, ctx->allFuncInfo);
            checkIndirectUsage(ctx, calledFunc, commonBlockGroupedVar, visitedFunctions, indirectUsageMasks);
        }

        for (int i = 0; i < 3; ++i)
            checkIndirectUsage(ctx, st->expr(i), commonBlockGroupedVar, visitedFunctions, indirectUsageMasks);
    }

    return indirectUsageMasks;
}

// checkImplicitAndIndirectUsage returns masks of implicit or indirect array usage in loop
static vector<vector<ArraySubscript>> checkImplicitAndIndirectUsage(Context* ctx)
{
    vector<vector<ArraySubscript>> implicitMasks = checkImplicitDirectUsage(ctx);
    vector<vector<ArraySubscript>> indirectMasks = checkIndirectUsage(ctx);

    implicitMasks.insert(implicitMasks.end(), indirectMasks.begin(), indirectMasks.end());
    return implicitMasks;
}

// filterArrayRefs removes from arrayRefs all refs that are not different from fixedVectors
static void filterArrayRefs(Context* ctx, vector<SgArrayRefExp*>& arrayRefs, 
                            const vector<vector<ArraySubscript>>& masks)
{
    if (masks.empty())
        return;

    vector<SgArrayRefExp*> filteredArrayRefs;
    for (auto arrayRef : arrayRefs)
    {
        vector<ArraySubscript> arrayRefVec = getFixedSubscriptsVector(arrayRef, ctx->dimensionsNum);

        bool isDifferent = false;
        for (auto& mask : masks)
        {
            for (int i = 0; i < arrayRefVec.size(); i++)
            {
                if (arrayRefVec[i].isFixed && mask[i].isFixed && arrayRefVec[i].value != mask[i].value)
                {
                    isDifferent = true;
                    break;
                }
            }

            if (isDifferent)
                break;
        }

        if (isDifferent)
            filteredArrayRefs.push_back(arrayRef);
    }

    arrayRefs.swap(filteredArrayRefs);
}

// getReducedArrayVarName returns name for new reduced array variable, 
// made by symbol and subscripts: arr, [1, 2] -> arr_1_2
static string getReducedArrayVarName(SgSymbol* symbol, const vector<int>& subscripts)
{
    string name = OriginalSymbol(symbol)->identifier();
    if (subscripts.empty())
        return name + "_";

    name.reserve(name.size() + subscripts.size() * 3);
    for (int i : subscripts)
    {
        if (i < 0)
            name += "_M" + std::to_string((-1) * i);
        else
            name += "_" + std::to_string(i);
    }

    return name;
}

// getReducedArrayVars makes reduced array vars for arrayRefs
static ReducedArrayVarsMap getReducedArrayVars(Context* ctx)
{
    ReducedArrayVarsMap reducedArrayVars;

    SgType* type = ctx->explicitArrayRefs[0]->type();
    SgStatement* scope = ctx->loopStmt->getScopeForDeclare();
    for (SgArrayRefExp* arrayRef : ctx->explicitArrayRefs)
    {
        vector<int> subscripts = getShortFixedSubscriptsVector(ctx, arrayRef);
        if (reducedArrayVars.find(subscripts) == nullptr)
        {
            string name = getReducedArrayVarName(arrayRef->symbol(), subscripts);

            if (checkSymbNameAndCorrect(name, "_") != name)
            {
                int nameNumber = checkSymbNameAndCorrect(name + "__", 0);
                name = name + "__" + std::to_string(nameNumber);
            }

            SgSymbol* newSymbol = new SgSymbol(VARIABLE_NAME, name.c_str(), type, scope);
            reducedArrayVars.insert(subscripts, newSymbol);
        }
    }

    return reducedArrayVars;
}

// getVarsToDependOn returns all vars that var depends on
static set<string> getVarsToDependOn(SgForStmt* loopStmt, SgSymbol* var)
{
    set<string> dependOn;
    for (SgStatement* st = loopStmt->lexNext(); st != loopStmt->lastNodeOfStmt(); st = st->lexNext())
        if (st->variant() == ASSIGN_STAT && isEqSymbols(st->expr(0)->symbol(), var))
            getVariables(st->expr(1), dependOn, { VAR_REF, ARRAY_REF });

    return dependOn;
}

// getDefStmtForReducedArray returns DFE statement for reduced array variable
static InsertedStatement getDefStmtForReducedArray(Context* ctx, SgArrayRefExp* arrayRef, 
                                                   const ReducedArrayVarsMap& reducedArrayVars)
{
    vector<int> subscriptVector = getShortFixedSubscriptsVector(ctx, arrayRef);
    SgSymbol* reducedVar = reducedArrayVars.find(subscriptVector);
    if (reducedVar == nullptr)
        printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

    SgExpression* lhs = new SgVarRefExp(reducedVar);
    SgExpression* rhs = new SgValueExp(0);
    SgStatement*  assignStmt = new SgAssignStmt(*lhs, *rhs);

    return InsertedStatement(TypeOfInsertedStmt::DEF, assignStmt);
}

// getUseStmtForReducedArray returns USE statement for reduced array variable
// (assignment to receiverVar) 
static InsertedStatement getUseStmtForReducedArray(Context* ctx, SgArrayRefExp* arrayRef,
                                                   const ReducedArrayVarsMap& reducedArrayVars,
                                                   SgSymbol* receiverVar)
{
    vector<int> subscriptVector = getShortFixedSubscriptsVector(ctx, arrayRef);
    SgSymbol* reducedVar = reducedArrayVars.find(subscriptVector);
    if (reducedVar == nullptr)
        printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

    SgExpression* lhs = new SgVarRefExp(receiverVar);
    SgExpression* rhs = new SgVarRefExp(reducedVar);
    SgStatement*  assignStmt = new SgAssignStmt(*lhs, *rhs);

    return InsertedStatement(TypeOfInsertedStmt::USE, assignStmt);
}

// insertReducedArrayVarStmts inserts in loop assignment statements with reduced vars,
// returns vector of inserted statements
static vector<InsertedStatement> insertReducedArrayVarStmts(Context* ctx,
                                                            const ReducedArrayVarsMap& reducedArrayVars,
                                                            SgSymbol* receiverVar)
{
    vector<InsertedStatement> insertedStmts;
    for (SgStatement* st = ctx->loopStmt->lexNext(); st != ctx->loopStmt->lastNodeOfStmt(); st = st->lexNext())
    {
        bool isUseStmt = false;

        int startIndex = 0;
        if (st->variant() == ASSIGN_STAT)
            startIndex = 1;
        for (int i = startIndex; i < 3; ++i)
        {
            if (isSymbolInExpression(ctx->arraySymbol, st->expr(i))) // reading from array - USE stmt
            {
                vector<SgArrayRefExp*> arrayRefs;
                fillDirectArrayRefs(st->expr(i), ctx->arraySymbol, arrayRefs);
                arrayRefs = removeDuplicateArrayRefs(arrayRefs, ctx->fixedDimensionsMask, ctx->regime);
                if (!arrayRefs.empty())
                    isUseStmt = true;

                for (SgArrayRefExp* arrayRef : arrayRefs)
                {
                    if (!isArrayRefInVector(arrayRef, ctx->explicitArrayRefs))
                        continue;

                    InsertedStatement useStmt = getUseStmtForReducedArray(ctx, arrayRef,
                                                                          reducedArrayVars, receiverVar);
                    useStmt.relatedToStmt = st;
                    st->insertStmtBefore(*useStmt.insertedStmt, *st->controlParent());
                    insertedStmts.push_back(useStmt);
                }
            }
        }

        if (st->variant() == ASSIGN_STAT &&
            isEqSymbols(st->expr(0)->symbol(), ctx->arraySymbol) && // assignment to array - DEF stmt
            isArrayRefInVector((SgArrayRefExp*)st->expr(0), ctx->explicitArrayRefs))
        {
            InsertedStatement defStmt = getDefStmtForReducedArray(ctx, (SgArrayRefExp*)st->expr(0), 
                                                                  reducedArrayVars);
            defStmt.relatedToStmt = st;
            defStmt.isRecursive = isUseStmt;
            st->insertStmtBefore(*defStmt.insertedStmt, *st->controlParent());
            insertedStmts.push_back(defStmt);
        }
    }

    return insertedStmts;
}

// makeTmpVar returns new temporary var with name like tmp_N
static SgSymbol* makeTmpVar(Context* ctx)
{
    SgStatement* scope = ctx->loopStmt->getScopeForDeclare();
    string varName = "tmp_";
    int nameNumber = checkSymbNameAndCorrect(varName, 0);
    varName = "tmp_" + std::to_string(nameNumber);

    return new SgSymbol(VARIABLE_NAME, varName.c_str(), ctx->explicitArrayRefs[0]->type(), scope);
}

// buildDefUsePairs builds pairs of DEF and USE statements for array
static vector<DefUseStmtsPair> buildDefUsePairs(Context* ctx, const CFG_Type& CFGraph,
                                                const vector<InsertedStatement>& insertedStmts)
{
    vector<DefUseStmtsPair> defUsePairs;

    string arrayName = ctx->arraySymbol->identifier();
    for (const InsertedStatement& useInsertedStmt : insertedStmts)
    {
        if (useInsertedStmt.type != TypeOfInsertedStmt::USE) // analysis for USE stmt
            continue;

        int useLineNum = useInsertedStmt.relatedToStmt->lineNumber();

        // looking for reaching definitions for the current block:
        auto useInsAndBlock = getInstructionAndBlockByStatement(CFGraph, useInsertedStmt.insertedStmt);
        auto useArg = useInsAndBlock.first->getArg1();
        const auto& RD_In = useInsAndBlock.second->getRD_In();

        const auto& RD_forUseArg = RD_In.find(useArg);
        if (RD_forUseArg == RD_In.end()) // cannot find reaching definitions for argument
        {
            addMessageCannotFindRD(ctx->messages, arrayName, useLineNum);
            continue;
        }

        set<int> RD_defArgs = RD_forUseArg->second; // make copy

        // delete recursive, uninit and definitions that cannot reach use stmt from RD def args:
        set<int> tmpRD_defArgs;
        for (int defArgNum : RD_defArgs)
        {
            if (defArgNum == SAPFOR::CFG_VAL::UNINIT)
                continue;

            auto defInsAndBlock = getInstructionAndBlockByNumber(CFGraph, defArgNum);
            if (defInsAndBlock.first == nullptr)
                printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

            SgStatement* defStmt = defInsAndBlock.first->getOperator();
            auto defInsertedStmt = findInsertedStmt(insertedStmts, defStmt);
            if (useLineNum <= defInsertedStmt->relatedToStmt->lineNumber())
                continue;

            tmpRD_defArgs.insert(defArgNum);
        }
        RD_defArgs.swap(tmpRD_defArgs);

        SgStatement* defStmt = nullptr;

        if (RD_defArgs.size() != 1)
        {
            bool defIsFound = false;

            // try to find definition not from RD_defArgs, by search in the block instructions:
            string defVarName = useInsertedStmt.insertedStmt->expr(1)->symbol()->identifier();
            const auto& blockInstructionsVector = useInsAndBlock.second->getInstructions();
            for (auto& instruction : blockInstructionsVector)
            {
                SgStatement* stmt = instruction->getInstruction()->getOperator();
                if (stmt == useInsertedStmt.insertedStmt)
                    break;

                if (stmt->variant() == ASSIGN_STAT 
                    && stmt->expr(0)->symbol()->identifier() == defVarName
                    && !isVarChangedBetween(defVarName, stmt, useInsertedStmt.insertedStmt))
                {
                    defIsFound = true;
                    defStmt = stmt;
                    break;
                }
            }

            if (!defIsFound)
            {
                addMessageCannotFindRD(ctx->messages, arrayName, useLineNum);
                continue;
            }
        }
        else
        {
            auto defInsAndBlock = getInstructionAndBlockByNumber(CFGraph, *RD_defArgs.begin());
            if (defInsAndBlock.first == nullptr)
                printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

            defStmt = defInsAndBlock.first->getOperator();
        }

        auto defInsertedStmt = findInsertedStmt(insertedStmts, defStmt);
        if (defInsertedStmt == insertedStmts.end())
        {
            addMessageCannotFindRD(ctx->messages, arrayName, useLineNum);
            continue;
        }

        //don't substitute def stmt into use, if def is recursive
        if (defInsertedStmt->isRecursive)
        {
            addMessageRecursiveDependency(ctx->messages, arrayName, useLineNum);
            continue;
        }

        SgAssignStmt* def = (SgAssignStmt*)defInsertedStmt->relatedToStmt;
        SgAssignStmt* use = (SgAssignStmt*)useInsertedStmt.relatedToStmt;
        defUsePairs.push_back(make_pair(def, use));
    }

    return defUsePairs;
}

// findChildLoop returns LoopGraph for provided loop statement
static LoopGraph* findLoop(LoopGraph* outerLoop, SgForStmt* loopStmt)
{
    SgStatement* outerLoopStmt = outerLoop->loop->GetOriginal();
    if (outerLoopStmt->id() == loopStmt->id())
        return outerLoop;

    for (auto childLoop : outerLoop->children)
    {
        auto childLoopGraph = findLoop(childLoop, loopStmt);
        if (childLoopGraph != nullptr)
            return childLoopGraph;
    }

    return nullptr;
}

// minCommonAncestor finds least common ancestor of a and b loops in loop graph tree with root parent
static LoopGraph* leastCommonAncestor(LoopGraph* a, LoopGraph* b, LoopGraph* parent)
{
    LoopGraph* tmp;

    int aDepth = 0;
    tmp = a;
    while (tmp != parent)
    {
        aDepth++;
        tmp = tmp->parent;
    }

    int bDepth = 0;
    tmp = b;
    while (tmp != parent)
    {
        bDepth++;
        tmp = tmp->parent;
    }

    while (aDepth != bDepth)
    {
        if (aDepth > bDepth)
        {
            a = a->parent;
            aDepth--;
        }
        else
        {
            b = b->parent;
            bDepth--;
        }
    }

    while (a->lineNum != b->lineNum)
    {
        a = a->parent;
        b = b->parent;
    }

    return a;
}

// fillFullFixedSubscriptsVectorsOfAllVars return vector of pairs (name of var, its fixed subscripts vector)
// of all VAR_REF and ARRAY_REF vars in exp
static void fillFixedSubscriptsVectorsOfAllVars(SgExpression* exp,
                                                vector<pair<string, vector<ArraySubscript>>>& vec,
                                                SgStatement* stmt = nullptr)
{
    if (exp == nullptr)
        return;

    if (exp->symbol() != nullptr)
    {
        if (exp->variant() == VAR_REF)
        {
            for (const auto& elem : vec)
                if (elem.first == exp->symbol()->identifier())
                    return;

            vec.push_back(make_pair(exp->symbol()->identifier(), vector<ArraySubscript>{}));
        }
        else if (exp->variant() == ARRAY_REF)
        {
            vec.push_back(make_pair(exp->symbol()->identifier(),
                                    getFixedSubscriptsVector((SgArrayRefExp*)exp, 0, stmt)));
            
            SgExprListExp* exprList = (SgExprListExp*)exp->lhs();
            for (int i = 0; i < exprList->length(); ++i)
                fillFixedSubscriptsVectorsOfAllVars(exprList->elem(i), vec, stmt);
        }
        return;
    }

    fillFixedSubscriptsVectorsOfAllVars(exp->lhs(), vec, stmt);
    fillFixedSubscriptsVectorsOfAllVars(exp->rhs(), vec, stmt);
}

// fixedSubscriptLess checks if left FixedSubscript is less than right
static bool fixedSubscriptLess(const ArraySubscript& left, const ArraySubscript& right)
{
    if (left.isFixed && right.isFixed && left.value < right.value)
        return true;

    if (left.isFixed && right.isRegIndex 
        && right.regExprStart.coefA == 0 && left.value < right.regExprStart.coefB)
            return true;

    if (left.isRegIndex && right.isFixed
        && left.regExprEnd.coefA == 0 && left.regExprEnd.coefB < right.value)
        return true;

    if (left.isRegIndex && right.isRegIndex)
        return left.regExprEnd < right.regExprStart;

    return false;
}

// fixedSubscriptLess checks if left and right FixedSubscripts are different,
// using empirical methods
static bool arePossibleDifferent(ArraySubscript left, ArraySubscript right)
{
    if (left.isFixed && right.isRegIndex && right.regExprStart == right.regExprEnd) {
        return true; // in general, this is not true
    }

    return false;
}

// areDifferentRefs checks if exp (var reference) is different from var
static bool areDifferentRefs(Context* ctx, SgExpression* exp, const pair<string, vector<ArraySubscript>>& var,
                             SgStatement* stmt)
{
    if (exp->symbol()->identifier() != var.first)
        return true;

    if (exp->variant() == VAR_REF)
        return false;

    vector<ArraySubscript> leftVec = getFixedSubscriptsVector((SgArrayRefExp*)exp, 0, stmt);

    if (leftVec.size() != var.second.size())
        printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

    for (int i = 0; i < leftVec.size(); i++)
    {
        if (fixedSubscriptLess(leftVec[i], var.second[i])
            || fixedSubscriptLess(var.second[i], leftVec[i]))
        {
            return true;
        }
        else if (arePossibleDifferent(leftVec[i], var.second[i]))
        {
            string varRefStr = var.first + "(";
            for (int i = 0; i < var.second.size(); ++i)
            {
                varRefStr += var.second[i].toString();
                if (i != var.second.size() - 1)
                    varRefStr += ",";
            }
            varRefStr += ")";

            addMessagePossibleDifferentAssumption(ctx->messages, ctx->arraySymbol->identifier(),
                                                  exp->sunparse(), varRefStr, stmt->lineNumber());
            return true;
        }

    }

    return false;
}

static pair<SAPFOR::Argument*, set<int>> findVarInRDSet(const map<SAPFOR::Argument*, set<int>>& RD_In,
                                                        const string& var)
{
    for (auto& RD_InElem : RD_In)
    {
        string elemName = RD_InElem.first->getValue();
        size_t pos = elemName.find('%');
        if (pos == string::npos)
            continue;

        if (var == elemName.substr(pos+1, elemName.length() - pos - 1))
            return RD_InElem;
    }

    return make_pair(nullptr, set<int>{});
}

// checkDefUsePair checks if def statement from pair can be substituted into use statement
// and creates messages
static bool checkDefUsePair(Context* ctx, const DefUseStmtsPair& defUse, const CFG_Type& CFGraph)
{
    if (defUse.first->lineNumber() > defUse.second->lineNumber())
        return false;

    string arrayName = ctx->arraySymbol->identifier();

    vector<pair<string, vector<ArraySubscript>>> dependOnVars;
    SgArrayRefExp* defRef = (SgArrayRefExp*)defUse.first->expr(0);
    vector<SgArrayRefExp*> arrayUseRefs = getDirectArrayRefsFromSingleStmt(defUse.second, ctx->arraySymbol);
    for (auto useRef : arrayUseRefs)
    {
        map<SgSymbol*, SgExpression*> varToExpMap;
        varToExpMap = getVarToExpMap(defRef, useRef, ctx->fixedDimensionsMask, ctx->regime);

        SgExpression* expToSubst = defUse.first->rhs()->copyPtr();
        expToSubst = replaceVarsWithExps(expToSubst, varToExpMap);

        fillFixedSubscriptsVectorsOfAllVars(expToSubst, dependOnVars, defUse.second);
    }

    vector<SgSymbol*> iterationVars{};
    fillIterationVars(defUse.second, nullptr, iterationVars);

    auto defInsAndBlock = getInstructionAndBlockByStatement(CFGraph, defUse.first);
    const auto& defRD_In = defInsAndBlock.second->getRD_In();
    auto useInsAndBlock = getInstructionAndBlockByStatement(CFGraph, defUse.second);
    const auto& useRD_In = useInsAndBlock.second->getRD_In();
    for (const auto& var : dependOnVars)
    {
        if (var.second.size() == 0) // check scalar vars
        {
            // iteration var doesn't obstruct the removing:
            bool isIterationVar = false;
            for (auto iterationVar : iterationVars)
            {
                if (iterationVar->identifier() == var.first)
                {
                    isIterationVar = true;
                    break;
                }
            }

            if (isIterationVar)
                continue;

            auto defArg = findVarInRDSet(defRD_In, var.first);
            if (defArg.first == nullptr) // there is no any RD for common vars or parameters
                continue;

            auto useArg = findVarInRDSet(useRD_In, var.first);
            if (defArg.second.size() != 1 || useArg.second.size() != 1 
                || *defArg.second.begin() != *useArg.second.begin())
            {
                if (defInsAndBlock.second->getNumber() == useInsAndBlock.second->getNumber())
                    if (!isVarChangedBetween(var.first, defUse.first, defUse.second))
                        continue;

                addMessageDependOnNonInvariant(ctx->messages, arrayName,
                    var.first, defUse.first->lineNumber());
                return false;
            }
        }
    }

    // checking arrays:
    auto defLoopStmt = getScopeLoopStmt(defUse.first);
    auto useLoopStmt = getScopeLoopStmt(defUse.second);

    LoopGraph* loop = ctx->loop;
    while (loop->perfectLoop != 1)
        loop = loop->children[0];

    LoopGraph* defLoop = findLoop(loop, defLoopStmt);
    LoopGraph* useLoop = findLoop(loop, useLoopStmt);

    if (!defLoopStmt || !useLoopStmt || !defLoop || !useLoop)
        printInternalError(convertFileName(__FILE__).c_str(), __LINE__);
            
    int minCommonLoopLine = leastCommonAncestor(defLoop, useLoop, loop)->lineNum;
    if (defLoop->lineNum != minCommonLoopLine)
        while (defLoop->parent->lineNum != minCommonLoopLine)
            defLoop = defLoop->parent;

    if (useLoop->lineNum != minCommonLoopLine)
        while (useLoop->parent->lineNum != minCommonLoopLine)
            useLoop = useLoop->parent;

    int defLoopLine = defLoop->lineNum;
    int useLoopLine = useLoop->lineNum;
    SgStatement *startStmt = nullptr, *endStmt = nullptr;
    if (defLoopLine != minCommonLoopLine && useLoopLine != minCommonLoopLine)
    {
        startStmt = defLoop->loop;
        endStmt = useLoop->loop->lastNodeOfStmt();
    }
    else if (defLoopLine != minCommonLoopLine && useLoopLine == minCommonLoopLine)
    {
        startStmt = defLoop->loop;
        endStmt = defUse.second;
    }
    else if (defLoopLine == minCommonLoopLine && useLoopLine != minCommonLoopLine)
    {
        startStmt = defUse.first;
        endStmt = useLoop->loop->lastNodeOfStmt();
    }
    else if (defLoopLine == minCommonLoopLine && useLoopLine == minCommonLoopLine)
    {
        startStmt = defUse.first;
        endStmt = defUse.second;
    }
    else
        return false;

    for (const auto& var : dependOnVars)
    {
        for (SgStatement* st = startStmt; st != endStmt; st = st->lexNext())
        {
            if (st == defUse.second)
                continue;
            if (st->variant() == ASSIGN_STAT && !areDifferentRefs(ctx, st->expr(0), var, st))
            {
                addMessageDependOnNonInvariant(ctx->messages, arrayName,
                                               var.first, defUse.first->lineNumber());
                return false;
            }
        }
    }

    return true;
}

// getPrivateArraysFromUserDirs returns private arrays from user directives ANALYSIS PRIVATE
static set<SgSymbol*> getPrivateArraysForLoop(LoopGraph* loop, const UsersDirectives& dirs)
{
    set<SgSymbol*> privateArrays;

    auto loopDirectives = dirs.find(make_pair(loop->fileName.c_str(), loop->lineNum));
    if (loopDirectives == dirs.end()) // no directives for loop
        return privateArrays;

    set<Symbol*> privateVars;
    for (SgStatement* dir : loopDirectives->second)
    {
        Statement* dirStmt = new Statement(dir);
        fillPrivatesFromComment(dirStmt, privateVars);
        delete dirStmt;
    }

    for (Symbol* var : privateVars)
        if (var->type()->variant() == T_ARRAY)
            privateArrays.insert(var->GetOriginal());

    return privateArrays;
}

static void removePrivateAnalyze(Context *ctx)
{
    // inserting assignment to reduced array variables for getting reaching definitions analysis:
    auto reducedArrayVars = getReducedArrayVars(ctx);
    SgSymbol* receiverVar = makeTmpVar(ctx);
    auto insertedStmts = insertReducedArrayVarStmts(ctx, reducedArrayVars, receiverVar);

    // declare reduced array variables and receiver:
    insertedStmts.push_back(InsertedStatement(
        TypeOfInsertedStmt::DECLARATION,
        makeDeclaration(ctx->loopStmt, reducedArrayVars.getAllVars(), nullptr)
    ));
    insertedStmts.push_back(InsertedStatement(
        TypeOfInsertedStmt::DECLARATION,
        makeDeclaration(ctx->loopStmt, vector<SgSymbol*> {receiverVar}, nullptr)
    ));

    const auto settings = CFG_Settings({ CFG_Settings::CFG_atLeastOneIterInLoop, CFG_Settings::CFG_withRD, CFG_Settings::CFG_withCallFrom, CFG_Settings::CFG_withDominators });
    CFG_Type CFG_ForFunc = buildCFGforCurrentFunc(ctx->loopStmt, settings,
                                                  ctx->commonBlocks, ctx->allFuncInfo);
    if (CFG_ForFunc.empty())
        printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

    auto defUseStmtsPairs = buildDefUsePairs(ctx, CFG_ForFunc, insertedStmts);
    if (!defUseStmtsPairs.empty()) // DEF-USE pairs were build successfully
    {
        vector<DefUseStmtsPair> resultDefUsePairs;
        for (auto& pair : defUseStmtsPairs)
        {
            if (checkDefUsePair(ctx, pair, CFG_ForFunc))
            {
                resultDefUsePairs.push_back(pair);

                // don't revert substitutions in use and def stmts:
                cancelRevertionForStatement(ctx->filename, pair.first, 1);
                cancelRevertionForStatement(ctx->filename, pair.second, 0);
                cancelRevertionForStatement(ctx->filename, pair.second, 1);
                cancelRevertionForStatement(ctx->filename, pair.second, 2);
            }
        }

        PrivateToRemove newPrivateToRemove;
        newPrivateToRemove.loop = ctx->loop;
        newPrivateToRemove.varSymbol = ctx->arraySymbol;
        newPrivateToRemove.regime = ctx->regime;
        newPrivateToRemove.defUseStmtsPairs.swap(resultDefUsePairs);
        newPrivateToRemove.fixedDimensions.swap(ctx->fixedDimensionsMask);

        privatesToRemoveGlobal.push_back(newPrivateToRemove);
    }

    // delete inserted statements:
    for (auto& stmt : insertedStmts)
        stmt.insertedStmt->deleteStmt();

    deleteCFG(CFG_ForFunc);
}

void removePrivatesAnalysis(string filename,
                            vector<LoopGraph*>& loopGraphs,
                            vector<Messages>& messages,
                            const UsersDirectives& usersDirectives,
                            const map<string, CommonBlock*>& commonBlocks,
                            const map<string, vector<FuncInfo*>>& allFuncInfo)
{
    for (LoopGraph* loop : loopGraphs)
    {
        if (!loop->isFor())
            continue;

        SgForStmt* loopStmt = (SgForStmt*)loop->loop->GetOriginal();
        set<SgSymbol*> privateArrays = getPrivateArraysForLoop(loop, usersDirectives);
        if (privateArrays.empty())
            continue;

        while (true)
        {
            SgSymbol* arrayToRemove = nullptr;
            vector<SgArrayRefExp*> arrayRefs;

            // choose arrayToRemove:
            for (SgSymbol* arrayToCheck : privateArrays)
            {
                arrayRefs = getDirectArrayRefs(loopStmt, arrayToCheck);
                if (arrayRefs.empty()) // no references to array
                    continue;

                set<string> dependOnVars = getVarsToDependOn(loopStmt, arrayToCheck);

                bool skip = false;
                for (string dependOnVar : dependOnVars)
                    for (SgSymbol* notChecked : privateArrays)
                        if (dependOnVar == notChecked->identifier() && !isEqSymbols(arrayToCheck, notChecked))
                            skip = true;

                if (skip) // check this array again later
                    continue;

                privateArrays.erase(arrayToCheck);
                arrayToRemove = arrayToCheck;
                break; // arrayToRemove is chosen
            }

            if (arrayToRemove == nullptr) // no array to remove
                break;

            Context context = Context{allFuncInfo, commonBlocks, messages, filename};
            context.regime = Regime::DEFLT;
            context.loop = loop;
            context.loopStmt = loopStmt;
            context.dimensionsNum = ((SgArrayType*)arrayToRemove->type())->dimension();
            context.arraySymbol = arrayToRemove;

            string arrayName = arrayToRemove->identifier();

            auto filterMasks = checkImplicitAndIndirectUsage(&context);
            filterArrayRefs(&context, arrayRefs, filterMasks);

            context.explicitArrayRefs.swap(arrayRefs);
            if (context.explicitArrayRefs.empty())
                continue;

            if (!checkLoopAlignmentMatching(&context))
            {
                addMessageVarNotAlignedWithLoop(messages, arrayName, context.loop->lineNum);
                continue;
            }

            context.fixedDimensionsMask = getFixedDimensionsMask(&context);

            if (!context.fixedDimensionsMask.empty() &&
                checkFixedDimensionsMaskMatching(&context) &&
                checkDefStmtRefsMatchesMask(&context))
            {
                removePrivateAnalyze(&context);
            }
            else if (checkRegularIndexRefs(&context))
            {
                context.regime = Regime::REGULAR_INDEXES;
                removePrivateAnalyze(&context);
            }
            else
                addMessageDoesNotMatchMask(messages, arrayName, context.loop->lineNum);
        }
    }

    for (LoopGraph* loop : loopGraphs)
    {
        removePrivatesAnalysis(filename, loop->children, messages,
                               usersDirectives, commonBlocks, allFuncInfo);
    }
}
