#include "private_arrays_resizing.h"
#include "graph_loops.h"
#include "SgUtils.h"
#include "utils.h"
#include "errors.h"
#include "../DirectiveProcessing/directive_parser.h"
#include "../LoopAnalyzer/loop_analyzer.h"

#include <set>
#include <queue>
#include <vector>

using std::string;
using std::set;
using std::map;
using std::queue;
using std::vector;
using std::pair;
using std::make_pair;

static void printExp(SgExpression *exp)
{
    queue<SgExpression*> qq;
    qq.push(exp);
    printf("Expression: ");
    
    while (!qq.empty())
    {
        SgExpression *exp = qq.front();
        qq.pop();

        printf("[%s]: %s\n", tag[exp->variant()], exp->unparse());
        if (exp->variant() == TYPE_OP)
            if (exp->symbol())
                printf("%s, %s", exp->symbol()->identifier(), tag[exp->symbol()->variant()]);

        if (exp->lhs())
            qq.push(exp->lhs());
        if (exp->rhs())
            qq.push(exp->rhs());
    }
}

static void printSt(SgStatement *st)
{
    printf("Statement [%s]: %s\n", tag[st->variant()], st->unparse());
    queue<SgExpression*> qq;
    for (int i = 0; i < 3; ++i)
    {
        if (st->expr(i))
        {
            printf("[%d,%s]: %s\n", i, tag[st->expr(i)->variant()], st->expr(i)->unparse());
            qq.push(st->expr(i));
        }
    }
    while (!qq.empty())
    {
        SgExpression *exp = qq.front();
        qq.pop();

        printf("[%s]: %s\n", tag[exp->variant()], exp->unparse());
        if (exp->variant() == TYPE_OP)
            if (exp->symbol())
                printf("%s, %s", exp->symbol()->identifier(), tag[exp->symbol()->variant()]);

        if (exp->lhs())
            qq.push(exp->lhs());
        if (exp->rhs())
            qq.push(exp->rhs());
    }
}

static void fillIterationVariables(const LoopGraph* loop, set<string>& vars, int dimensions = -1)
{
    if (dimensions == -1)
    {
        vars.insert(loop->loopSymbol());
        for (LoopGraph* child : loop->children)
            fillIterationVariables(child, vars);
    }
    else
    {
        for (int i = 0; i < dimensions; ++i)
        {
            vars.insert(loop->loopSymbol());
            if (i != dimensions - 1)
                loop = loop->children[0];
        }
    }
}

static SgExpression* findSymbol(SgSymbol* symbol, SgExpression* list)
{
    if (list)
    {
        if (list->variant() == VAR_REF || list->variant() == ARRAY_REF)
        {
            if (isEqSymbols(list->symbol(), symbol))
                return list;
        }

        auto left = findSymbol(symbol, list->lhs());
        if (left)
            return left;

        auto right = findSymbol(symbol, list->rhs());
        if (right)
            return right;
    }
    return NULL;
}

static char* constructNewArrayName(const char *oldName, bool isExpansion)
{
    string name(oldName), newName;
    name += isExpansion ? "_ex" : "_sh";
    newName = name + std::to_string(1);
    for (int n = 2; ifSymbolExists(current_file, newName); ++n)
        newName = name + std::to_string(n);

    char* newNameChar = (char*)malloc((newName.size() + 1) * sizeof(char));
    addToCollection(__LINE__, __FILE__, newNameChar, 0);
    if (newNameChar)
        strcpy(newNameChar, newName.c_str());
    else
        printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

    return newNameChar;
}

static const string constructNewBoundName(const char *oldName, int loopLineNumber, const char *add)
{
    string name(oldName), newName;
    name += "_" + std::to_string(loopLineNumber);
    name += add; // start ? "_start" : "_end";
    newName = name;
    for (int n = 0; ifSymbolExists(current_file, newName); ++n)
        newName = name + "_" + std::to_string(n);

    return newName;
}

static SgSymbol* createNewArrayNameSymbol(SgExpression* declaration, bool isExpansion, bool canBeStatic)
{
    SgType* type = new SgType(T_ARRAY);
    char* newNameStr = constructNewArrayName(declaration->symbol()->identifier(), isExpansion);
    SgSymbol* newName = new SgSymbol(VARIABLE_NAME, newNameStr, type, NULL); 

    auto attrs = declaration->symbol()->attributes();
    if (!canBeStatic)
        attrs |= ALLOCATABLE_BIT;

    newName->setAttribute(attrs);
    return newName;
}

static void fillLoopBoundExprs(const LoopGraph* forLoop, int depthOfResize, const vector<SgSymbol*>& indexes, vector<SgExpression*>& bounds)
{
    const LoopGraph* curLoop = forLoop;
    for (int i = 0; i < depthOfResize; i++)
    {
        SgForStmt* loopStmt = (SgForStmt*)curLoop->loop->GetOriginal();
        for (int j = 0; j < indexes.size(); j++)
        {
            if (indexes[j] && isEqSymbols(loopStmt->doName(), indexes[j]))
            {
                //TODO: add MIN and MAX call for bounds
                if (curLoop->stepVal == 0)
                {
                    __spf_print(1, "unknown step sign of loop on line %d\n", curLoop->lineNum);
                    printInternalError(convertFileName(__FILE__).c_str(), __LINE__);
                }

                if (curLoop->stepVal > 0)
                    bounds[j] = new SgExpression(DDOT, loopStmt->start()->copyPtr(), loopStmt->end()->copyPtr(), NULL);
                else
                    bounds[j] = new SgExpression(DDOT, loopStmt->end()->copyPtr(), loopStmt->start()->copyPtr(), NULL);
                break;
            }
        }

        if (i != depthOfResize - 1)
            curLoop = curLoop->children[0];
    }
}

static SgExpression* constructArrayRefTailWithLoopBounds(const LoopGraph* forLoop, int depthOfResize,
                                                         const vector<SgSymbol*>& indexes, SgExpression* oldTail)
{
    vector<SgExpression*> dims(indexes.size());
    fillLoopBoundExprs(forLoop, depthOfResize, indexes, dims);

    for (int i = 0; i < indexes.size(); i++)
    {
        if (dims[i] == NULL)
        {
            dims[i] = oldTail->lhs();
            oldTail = oldTail->rhs();
        }
    }

    SgExpression* newTail = NULL;
    for (int i = indexes.size() - 1; i >= 0; i--)
        newTail = new SgExpression(EXPR_LIST, dims[i], newTail);

    return newTail;
}

static void extendArrayDeclaration(const LoopGraph* forLoop, bool canBeStatic, const vector<SgSymbol*>& indexes,
                                   SgExpression*& exprToExtend, SgSymbol* newArraySym)
{
    exprToExtend->setSymbol(newArraySym);

    int depthOfResize = 0;
    for (int i = 0; i < indexes.size(); i++)
        if (indexes[i] != NULL)
            depthOfResize++;

    SgExpression* newTail = NULL;
    if (canBeStatic)
    {
        SgExpression* oldTail = exprToExtend->lhs();
        newTail = constructArrayRefTailWithLoopBounds(forLoop, depthOfResize, indexes, oldTail);
    }
    else
    {
        for (int i = 0; i < indexes.size(); i++)
        {
            SgExpression* ddotExpr = new SgExpression(DDOT);
            newTail = new SgExpression(EXPR_LIST, ddotExpr, newTail);
        }
    }
    
    exprToExtend = new SgArrayRefExp(*newArraySym);
    exprToExtend->setLhs(newTail);
}

static void reduceArray(const vector<int> &indexes, SgExpression *expressionToReduce, SgSymbol *newSymbol)
{
    expressionToReduce->setSymbol(newSymbol);

    vector<SgExpression*> tailVec(0);
    SgExpression *tail = expressionToReduce->lhs();
    for (int i = 0; i < indexes.size(); ++i)
    {
        if (indexes[i] == 0)
            tailVec.push_back(tail->lhs());

        tail = tail->rhs();
    }

    std::reverse(tailVec.begin(), tailVec.end());
    SgExpression *list = makeExprList(tailVec, false);

    expressionToReduce->setLhs(list);
}

static SgExpression* constructExtendedArrayRefTail(SgExpression* oldTail, const vector<SgSymbol*>& indexes)
{
    vector<SgExpression*> dims(indexes.size());
    for (int i = 0; i < indexes.size(); i++)
    {
        if (indexes[i] == NULL)
        {
            dims[i] = oldTail->lhs();
            oldTail = oldTail->rhs();
        }
        else
            dims[i] = new SgVarRefExp(indexes[i]);
    }

    SgExpression* newTail = NULL;
    for (int i = indexes.size() - 1; i >= 0; i--)
        newTail = new SgExpression(EXPR_LIST, dims[i], newTail);

    return newTail;
}

static void extendArrayRef(const vector<SgSymbol*>& indexes, SgExpression*& expressionToExtend,
                           SgSymbol* newSymbol, const vector<SgExpression*>& lowBounds)
{
    SgExpression* newTail = constructExtendedArrayRefTail(expressionToExtend->lhs(), indexes);
    SgExpression* oldTail = expressionToExtend->lhs();
    if (oldTail == NULL)
    {
        if (expressionToExtend->variant() == VAR_REF) // create array from scalar
        {
            expressionToExtend = new SgArrayRefExp(*newSymbol);
            expressionToExtend->setLhs(newTail);
        }
        else if (isArrayRef(expressionToExtend)) // create array from full array ref
        {
            SgArrayRefExp* curr = (SgArrayRefExp*)expressionToExtend;
            expressionToExtend->setSymbol(newSymbol);
            for (auto& elem : lowBounds) // add low bounds
                curr->addSubscript(*elem);
            curr->addSubscript(*newTail);
        }
        else
            printInternalError(convertFileName(__FILE__).c_str(), __LINE__);
    }
    else
    {
        expressionToExtend->setLhs(newTail);
        expressionToExtend->setSymbol(newSymbol);
    }
}

static bool isAllocatable(SgSymbol* symbol)
{
    return (symbol->attributes() & ALLOCATABLE_BIT) != 0;
}

static bool isAllocatable(SgStatement *decl)
{
    SgExpression *params = decl->expr(2);
    while (params != NULL)
    {
        if (params->lhs()->variant() == ALLOCATABLE_OP)
            return true;
        params = params->rhs();
    }

    return false;
}

static void setAllocatable(SgStatement *newDecl, SgSymbol *origSymbol)
{
    if (isAllocatable(origSymbol))
        return;

    SgExpression *params = newDecl->expr(2);
    SgExpression newParams (EXPR_LIST, new SgExpression(ALLOCATABLE_OP), params, (SgSymbol*)NULL);
    newDecl->setExpression(2, newParams);
    
    //объявление с DIMENSION или с ALLOCATABLBE
    if (newDecl->variant() != VAR_DECL)
    {
        newDecl->setVariant(VAR_DECL);
        if (origSymbol->type()->hasBaseType())
            newDecl->setExpression(1, *(new SgTypeExp(*origSymbol->type()->baseType())));
        else
            newDecl->setExpression(1, *(new SgTypeExp(*origSymbol->type())));
    }
}

static SgExpression* checkArrayDecl(SgExpression* array, SgSymbol* arraySymbol, SgStatement* checkedDecl)
{    
    if (array->lhs() == NULL)
    {
        vector<SgStatement*> allDecls;
        auto mainDecl = declaratedInStmt(arraySymbol, &allDecls);
        if (allDecls.size() == 0)
            printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

        if (allDecls.size() == 1)
        {
            array = findSymbol(arraySymbol, mainDecl->expr(0));
            checkNull(array, convertFileName(__FILE__).c_str(), __LINE__);
            return array;
        }

        for (auto& decl : allDecls)
        {
            if (decl == mainDecl)
                continue;
            if (decl == checkedDecl)
                continue;

            array = findSymbol(arraySymbol, decl->expr(0));
            if (array->lhs())
                break;
            array = NULL;
        }

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

    return array;
}

static SgSymbol* alterExtendArrayDeclaration(const LoopGraph* forLoop, SgStatement* declarationStmt, SgSymbol* arraySymbol,
                                             bool canBeStatic, const vector<SgSymbol*>& indexes)
{
    SgSymbol* newArraySymbol = NULL;
    SgExpression* array = findSymbol(arraySymbol, declarationStmt->expr(0));
    array = checkArrayDecl(array, arraySymbol, declarationStmt);

    SgExpression* newArray = array->copyPtr();
    newArraySymbol = createNewArrayNameSymbol(newArray, true, canBeStatic);
    if (newArraySymbol == NULL)
        printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

    extendArrayDeclaration(forLoop, canBeStatic, indexes, newArray, newArraySymbol);

    SgExpression newExprList(EXPR_LIST, newArray, (SgExpression*)NULL, (SgSymbol*)NULL);
    declarationStmt->setExpression(0, newExprList);

    if (!canBeStatic)
        setAllocatable(declarationStmt, arraySymbol);

    return newArraySymbol;
}

static SgSymbol* alterShrinkArrayDeclaration(SgStatement *declarationStatement, SgSymbol *arraySymbol, 
                                             vector<int> &dimensions, bool canBeStatic)
{
    SgSymbol *newArraySymbol = NULL;
    SgExpression *array = findSymbol(arraySymbol, declarationStatement->expr(0));

    SgExpression *newArray = array->copyPtr();
    newArraySymbol = createNewArrayNameSymbol(newArray, false, canBeStatic);

    reduceArray(dimensions, newArray, newArraySymbol);

    SgExpression newExprList(EXPR_LIST, newArray, (SgExpression*)NULL, (SgSymbol*)NULL);
    declarationStatement->setExpression(0, newExprList);
        
    if (newArraySymbol == NULL)
        printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

    return newArraySymbol;
}

static void extendArrayRefsInExpr(const vector<SgSymbol*>& indexes, SgExpression*& expr, 
                                  SgSymbol* arraySymbol, SgSymbol* newArraySymbol, 
                                  const vector<SgExpression*>& lowBounds, int line)
{
    if (expr)
    {
        if (expr->variant() == FUNC_CALL)
        {
            SgFunctionCallExp* call = isSgFunctionCallExp(expr);
            for (int arg = 0; arg < call->numberOfArgs(); ++arg)
            {
                auto argRef = call->arg(arg);
                if ((isArrayRef(argRef) || argRef->variant() == VAR_REF) && isEqSymbols(argRef->symbol(), arraySymbol))
                {
                    __spf_print(1, "unsupported private array extension under call on line %d\n", line);
                    printInternalError(convertFileName(__FILE__).c_str(), __LINE__);
                }
            }
        }

        SgExpression* left = expr->lhs();
        extendArrayRefsInExpr(indexes, left, arraySymbol, newArraySymbol, lowBounds, line);
        if (left != expr->lhs())
            expr->setLhs(left);

        SgExpression* right = expr->rhs();
        extendArrayRefsInExpr(indexes, right, arraySymbol, newArraySymbol, lowBounds, line);
        if (right != expr->rhs())
            expr->setRhs(right);

        if (isArrayRef(expr) && isEqSymbols(arraySymbol, expr->symbol()))
            extendArrayRef(indexes, expr, newArraySymbol, lowBounds);
        else if (expr->variant() == VAR_REF && isEqSymbols(arraySymbol, expr->symbol()))
            extendArrayRef(indexes, expr, newArraySymbol, lowBounds);
    }
}

static void extendArrayRefs(const vector<SgSymbol*> &indexes, SgStatement *st, SgSymbol *arraySymbol,
                            SgSymbol *newArraySymbol, const vector<SgExpression*> &lowBounds)
{
    for (int i = 0; i < 3; ++i)
    {
        if (st->variant() == PROC_STAT)
        {
            SgCallStmt* call = isSgCallStmt(st);
            for (int arg = 0; arg < call->numberOfArgs(); ++arg)
            {
                auto argRef = call->arg(arg);
                if ((isArrayRef(argRef) || argRef->variant() == VAR_REF) && isEqSymbols(argRef->symbol(), arraySymbol))
                {
                    __spf_print(1, "unsupported private array extension under call on line %d\n", st->lineNumber());
                    printInternalError(convertFileName(__FILE__).c_str(), __LINE__);
                }
            }
        }

        if (st->expr(i))
        {
            SgExpression* ex = st->expr(i);
            extendArrayRefsInExpr(indexes, ex, arraySymbol, newArraySymbol, lowBounds, st->lineNumber());
            if (ex != st->expr(i))
                st->setExpression(i, ex);
        }
    }
}

static SgStatement* createNewDeclarationStatemnet(SgStatement *loop, SgStatement *originalDeclaration, SgSymbol *arraySymbol)
{
    SgStatement *lastDecl = getFuncStat(loop);
    while (lastDecl)
    {
        if (isSgExecutableStatement(lastDecl))
            break;
        if (lastDecl->variant() == CONTAINS_STMT)
            break;
        lastDecl = lastDecl->lexNext();
    }

    SgExpression *exprList = originalDeclaration->expr(0);
    while (!isEqSymbols(exprList->lhs()->symbol(), arraySymbol))
        exprList = exprList->rhs();

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

    SgExpression newExprList(EXPR_LIST, exprList->lhs()->copyPtr(), NULL, NULL);
    SgStatement *newDeclaration = originalDeclaration->copyPtr();

    newDeclaration->setExpression(0, newExprList);
    lastDecl->insertStmtBefore(*newDeclaration, *lastDecl->controlParent());

    return newDeclaration;
}

static SgExpression* constructBoundCall(bool upBound, SgSymbol *array, int dim)
{
    const char *boundName = NULL;
    upBound ? boundName = "ubound" : boundName = "lbound";

    SgSymbol boundS = SgSymbol(FUNCTION_NAME, boundName);

    SgExpression *dimParam = new SgExpression(EXPR_LIST, new SgValueExp(dim), NULL, NULL);
    SgExpression params(EXPR_LIST, new SgExpression(ARRAY_REF, NULL, NULL, array), dimParam, NULL);

    return new SgFunctionCallExp(boundS, params);
}

static SgExpression* constructReducedAllocation(SgExpression *origArray, SgSymbol *arraySymbol, const vector<int> *indexes)
{
    SgExpression *arrayRef = origArray->copyPtr();
    reduceArray(*indexes, arrayRef, arraySymbol);

    return new SgExpression(EXPR_LIST, arrayRef, (SgExpression*)NULL, (SgSymbol*)NULL);
}

static SgStatement* getAllocationStmt(const LoopGraph* loop, SgSymbol* symbol)
{
    SgStatement* originalDeclaration = declaratedInStmt(symbol);

    if (isAllocatable(symbol))
    {
        SgForStmt* loopStmt = (SgForStmt*)(loop->loop->GetOriginal());

        SgStatement* allocationStmt = NULL;
        for (auto& alloc : getAttributes<SgStatement*, SgStatement*>(originalDeclaration, set<int>{ ALLOCATE_STMT }))
        {
            if (alloc->variant() != ALLOCATE_STMT)
                continue;

            if (findSymbol(symbol, alloc->expr(0)) == NULL)
                continue;

            if (allocationStmt == NULL)
                allocationStmt = alloc;
            else
                if (allocationStmt->lineNumber() < alloc->lineNumber() && alloc->lineNumber() < loopStmt->lineNumber())
                    allocationStmt = alloc;
        }

        return allocationStmt;
    }
    else
        return NULL;
}

static void fillLowBounds(const LoopGraph* forLoop, SgSymbol* origArraySymbol, SgStatement* originalDecl, vector<SgExpression*>& lowBounds)
{
    SgExpression* origArray = NULL;

    if (isAllocatable(origArraySymbol))
    {
        SgStatement* allocationStmt = getAllocationStmt(forLoop, origArraySymbol);
        origArray = findSymbol(origArraySymbol, allocationStmt->expr(0));
    }
    else
        origArray = findSymbol(origArraySymbol, originalDecl->expr(0));

    SgExpression* arrayRef = origArray->copyPtr();
    SgExpression* oldTail = arrayRef->lhs();
    while (oldTail != NULL)
    {
        if (oldTail->lhs()->variant() == DDOT)
            lowBounds.push_back(oldTail->lhs()->lhs());
        else
            lowBounds.push_back(new SgValueExp(1));

        oldTail = oldTail->rhs();
    }
}

static SgExpression* constructArrayAllocationExp(const LoopGraph* forLoop, SgExpression* origArray, SgSymbol* arraySymbol, 
                                                 const vector<SgSymbol*>& indexes, int depthOfResize)
{
    SgExpression *arrayRef = origArray->copyPtr();
    arrayRef->setSymbol(arraySymbol);
    vector<SgExpression*> dimensions(depthOfResize);

    SgExpression *oldTail = arrayRef->lhs();
    SgSymbol *origArraySymbol = origArray->symbol();
    int curDim = 0;
    while (oldTail != NULL)
    {
        curDim++;
        if (oldTail->lhs()->variant() == DDOT)
        {
            SgExpression *ddot = oldTail->lhs();
            if (!ddot->lhs())
                ddot->setLhs(constructBoundCall(false, origArraySymbol, curDim));
            if (!ddot->rhs())
                ddot->setRhs(constructBoundCall(true, origArraySymbol, curDim));
        }

        oldTail = oldTail->rhs();
    }

    oldTail = arrayRef->lhs();
    SgExpression* newTail = constructArrayRefTailWithLoopBounds(forLoop, depthOfResize, indexes, oldTail);

    if (arrayRef->variant() == VAR_REF)// create array from scalar
    {
        arrayRef = new SgArrayRefExp(*arrayRef->symbol());
        arrayRef->setLhs(newTail);
    }
    else
        arrayRef->setLhs(newTail);

    return new SgExpression(EXPR_LIST, arrayRef, (SgExpression*)NULL, (SgSymbol*)NULL);
}

static void insertAllocDealloc(const LoopGraph* forLoop, SgSymbol* origArraySymbol, SgSymbol* arraySymbol, SgSymbol* allocDone,
                               bool isExpansion, const vector<SgSymbol*>* indexes = NULL, const vector<int>* shrinkIndexes = NULL)
{
    SgForStmt *loopStmt = (SgForStmt*)(forLoop->loop->GetOriginal());
    vector<SgExpression*> lowBounds;

    SgStatement *originalDeclaration = declaratedInStmt(origArraySymbol);
    SgExpression *origArray = NULL;

    if (isAllocatable(origArraySymbol))
    {
        SgStatement *allocationStmt = getAllocationStmt(forLoop, origArraySymbol);
        origArray = findSymbol(origArraySymbol, allocationStmt->expr(0));
    }
    else
    {
        origArray = findSymbol(origArraySymbol, originalDeclaration->expr(0));
        origArray = checkArrayDecl(origArray, origArraySymbol, originalDeclaration);
    }

    int depthOfResize = 0;
    if (isExpansion)
        for (int i = 0; i < indexes->size(); i++)
            if ((*indexes)[i] != NULL)
                depthOfResize++;

    SgExpression* arrayAllocation = isExpansion
        ? constructArrayAllocationExp(forLoop, origArray, arraySymbol, *indexes, depthOfResize)
        : constructReducedAllocation(origArray, arraySymbol, shrinkIndexes);
    SgExpression *arrayDeallocation = new SgExpression(EXPR_LIST,
        new SgExpression(ARRAY_REF, (SgExpression*)NULL, (SgExpression*)NULL, arraySymbol),
        (SgExpression*)NULL, (SgSymbol*)NULL);

    

    SgStatement *allocate = new SgStatement(ALLOCATE_STMT, (SgLabel*)NULL, (SgSymbol*)NULL, arrayAllocation, (SgExpression*)NULL, (SgExpression*)NULL);
    SgIfStmt* ifSt = new SgIfStmt(SgEqOp(*new SgVarRefExp(allocDone), *new SgValueExp(0)), *allocate);

    loopStmt->insertStmtBefore(*ifSt, *loopStmt->controlParent());

    
    // insert allocates as save
    //SgStatement* deallocate = new SgStatement(DEALLOCATE_STMT, (SgLabel*)NULL, (SgSymbol*)NULL, arrayDeallocation, (SgExpression*)NULL, (SgExpression*)NULL);
    //loopStmt->lastNodeOfStmt()->insertStmtAfter(*deallocate, *loopStmt->controlParent());
}

static bool containsFunctionCall(SgExpression* exp) 
{
    bool retVal = false;

    if (exp)
    {
        if (exp->variant() == FUNC_CALL)
            retVal = true;

        if (exp->lhs())
            retVal = retVal || containsFunctionCall(exp->lhs());
        if (exp->rhs())
            retVal = retVal || containsFunctionCall(exp->rhs());
    }
    return retVal;
}

static SgSymbol* createVarDeclaration(SgForStmt *loopStmt, const char *add)
{
    SgSymbol *doName = loopStmt->doName();
    string newNameStr = constructNewBoundName(doName->identifier(), loopStmt->lineNumber(), add);
    SgSymbol *newName = new SgSymbol(VARIABLE_NAME, newNameStr.c_str(), doName->type(), NULL);

    SgStatement *decl = declaratedInStmt(doName);

    SgStatement *newDecl = decl->copyPtr();
    newDecl->setExpression(0, new SgExpression(EXPR_LIST, new SgVarRefExp(newName), NULL, NULL));
    decl->insertStmtAfter(*newDecl, *decl->controlParent());

    return newName;
}

static SgStatement* createBoundVariableAssign(SgSymbol *varName, SgExpression *value)
{
    SgAssignStmt *assign = new SgAssignStmt(*new SgVarRefExp(varName), *value);
    return assign;
}

static void replaceFunctionBounds(SgExpression *ex, SgForStmt *loopStmt, SgForStmt *topLoopStmt, const string &kind)
{
    if (containsFunctionCall(ex))
    {
        SgSymbol *newStart = createVarDeclaration(loopStmt, kind.c_str());
        SgStatement *assignment = createBoundVariableAssign(newStart, ex);

        topLoopStmt->insertStmtBefore(*assignment, *topLoopStmt->controlParent());
        if (kind == "_start")
            loopStmt->setStart(*assignment->expr(0));
        if (kind == "_end")
            loopStmt->setEnd(*assignment->expr(0));
        if (kind == "_step")
            loopStmt->setStep(*assignment->expr(0));
    }
}

static void replaceFunctionBounds(const LoopGraph *forLoop, int depthOfResize)
{
    const LoopGraph *curLoop = forLoop;
    SgForStmt *topLoopStmt = (SgForStmt*) (curLoop->loop->GetOriginal());
    for (int i = 0; i < depthOfResize; ++i)
    {
        SgForStmt *loopStmt = (SgForStmt*) (curLoop->loop->GetOriginal());
        
        replaceFunctionBounds(loopStmt->start(), loopStmt, topLoopStmt, "_start");
        replaceFunctionBounds(loopStmt->end(), loopStmt, topLoopStmt, "_end");
        replaceFunctionBounds(loopStmt->step(), loopStmt, topLoopStmt, "_step");
        
        if (i + 1 != depthOfResize)
            curLoop = curLoop->children[0];
    }
}

static SgSymbol* createReducedToVariableArray(SgStatement *copiedDeclaration, int lineNumber, SgSymbol *arraySymbol)
{
    SgType *type = copiedDeclaration->expr(1)->type();
    string newNameStr = constructNewBoundName(arraySymbol->identifier(), lineNumber, "_sh");
    SgSymbol *newName = new SgSymbol(VARIABLE_NAME, newNameStr.c_str(), type, NULL);

    copiedDeclaration->setExpression(0, new SgExpression(EXPR_LIST, new SgVarRefExp(newName), NULL, NULL));
    return newName;
}

static void reduceArrayRefsInExpr(SgExpression *expr, SgSymbol *arraySymbol, SgSymbol *newSymbol, bool reduceToVariable, vector<int> &indexes)
{
    if (expr == NULL)
        return;

    SgExpression *lhs = expr->lhs(), *rhs = expr->rhs();
    
    if (lhs)
    {
        if (isArrayRef(lhs) && isEqSymbols(arraySymbol, lhs->symbol()))
        {
            if (reduceToVariable)
                expr->setLhs(new SgVarRefExp(newSymbol));
            else
                reduceArray(indexes, lhs, newSymbol);
        }
        else
            reduceArrayRefsInExpr(lhs, arraySymbol, newSymbol, reduceToVariable, indexes);
    }

    if (rhs)
    {
        if (isArrayRef(rhs) && isEqSymbols(arraySymbol, rhs->symbol()))
        {
            if (reduceToVariable)
                expr->setRhs(new SgVarRefExp(newSymbol));
            else
                reduceArray(indexes, rhs, newSymbol);
        }
        else
            reduceArrayRefsInExpr(rhs, arraySymbol, newSymbol, reduceToVariable, indexes);
    }
}

static void reduceArrayRefs(SgStatement *st, SgSymbol *arraySymbol, SgSymbol *newSymbol, bool reduceToVariable, vector<int> &indexes)
{
    for (int i = 0; i < 3; ++i)
    {
        if (st->expr(i))
        {
            SgExpression* expr = st->expr(i);
            if (isArrayRef(expr) && isEqSymbols(arraySymbol, expr->symbol()))
            {
                if (reduceToVariable)
                    st->setExpression(i, new SgVarRefExp(newSymbol));
                else
                    reduceArray(indexes, expr, newSymbol);
            }
            else
                reduceArrayRefsInExpr(expr, arraySymbol, newSymbol, reduceToVariable, indexes);
        }
    }
}

static void fillIndexesToShrink(SgSymbol* arr, SgExprListExp* listExp, vector<int>& indexes)
{
    SgExpression* sym = findSymbol(arr, listExp);
    if (sym)
    {
        SgExpression* expr = sym->lhs();
        while (expr)
        {
            indexes.push_back(expr->lhs()->valueInteger());
            expr = expr->rhs();
        }

        bool needShrink = false;
        for (int z = 0; z < indexes.size(); ++z)
            if (indexes[z] == 1)
                needShrink = true;

        if (!needShrink)
            indexes.clear();
    }
}

static void renamePrivateVarsInAttributes(const vector<SgStatement*>& attrs, const map<SgSymbol*, SgSymbol*>& symbols)
{
    for (SgStatement* st : attrs)
    {
        if (st->variant() != SPF_ANALYSIS_DIR)
            return;

        SgExpression* exprList = st->expr(0);
        while (exprList)
        {
            if (exprList->lhs()->variant() == ACC_PRIVATE_OP)
            {
                SgExpression* list = exprList->lhs()->lhs();
                while (list)
                {
                    for (auto& pair : symbols)
                        if (isEqSymbols(pair.first, list->lhs()->symbol()))
                            list->setLhs(new SgExpression(ARRAY_REF, NULL, NULL, pair.second));

                    list = list->rhs();
                }
            }
            exprList = exprList->rhs();
        }
    }
}

static void removePrivateVarsInAttributes(const vector<SgStatement*>& attrs, const map<SgSymbol*, SgSymbol*>& symbols)
{
    for (SgStatement* st : attrs)
    {
        if (st->variant() != SPF_ANALYSIS_DIR)
            return;

        SgExpression* exprList = st->expr(0);
        vector<SgExpression*> newL;
        bool remList = false;
        while (exprList)
        {            
            if (exprList->lhs()->variant() == ACC_PRIVATE_OP)
            {
                vector<SgExpression*> newL;
                bool removed = false;
                SgExpression* list = exprList->lhs()->lhs();
                while (list)
                {
                    bool found = false;
                    for (auto& pair : symbols) 
                    {
                        if (isEqSymbols(pair.first, list->lhs()->symbol()))
                            found = true;
                        if (found)
                            break;
                    }
                    if (!found)
                        newL.push_back(list->lhs());
                    else
                        removed = true;
                    list = list->rhs();
                }

                if (removed) 
                {
                    if (newL.size() == 0) 
                        remList = true;
                    else 
                    {
                        exprList->lhs()->setLhs(makeExprList(newL));
                        newL.push_back(exprList->lhs());
                    }
                }
            }
            else
                newL.push_back(exprList->lhs());
            exprList = exprList->rhs();
        }

        if (remList)
            st->setExpression(0, makeExprList(newL));
    }
}

static int getArrayDimensionality(SgSymbol* array)
{
    if (array->type()->variant() != T_ARRAY)
        return 0;

    SgStatement* decl = declaratedInStmt(array);
    DIST::Array* currArray = getArrayFromDeclarated(decl, array->identifier());

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

    return currArray->GetDimSize();
}

static bool isWellDistributedArray(const LoopGraph* loop, const DIST::Array* array, int depthOfResize, bool fromWrite)
{
    const LoopGraph* curLoop = loop;
    bool found = true;
    const vector<ArrayOp>* arrayOps = NULL;
    for (int i = 0; i < depthOfResize; i++)
    {
        if (fromWrite)
        {
            for (auto& writeOp : curLoop->writeOps)
            {
                if (isEqSymbols(writeOp.first->GetDeclSymbol(), array->GetDeclSymbol()))
                {
                    arrayOps = &writeOp.second;
                    break;
                }
            }
        }
        else
        {
            for (auto& readOp : curLoop->readOps)
            {
                if (isEqSymbols(readOp.first->GetDeclSymbol(), array->GetDeclSymbol()))
                {
                    arrayOps = &readOp.second.first;
                    break;
                }
            }
        }

        if (arrayOps == NULL)
            return false;
        
        int counter = 0;
        for (int j = 0; j < arrayOps->size(); j++)
        {
            if (!(*arrayOps)[j].coefficients.empty())
                counter++;

            for (const auto& elem : (*arrayOps)[j].coefficients)
            {
                if (elem.first.first != 1 || elem.first.second != 0)
                {
                    found = false;
                    break;
                }
            }
        }
        
        if (counter != 1)
            found = false;

        if (!found)
            break;

        if (i != depthOfResize - 1)
            curLoop = curLoop->children[0];
    }

    if (found)
        return true;
    
    return false;
}

static SgSymbol* findDistributedArrayWrite(const LoopGraph* loop, int origDim, int depthOfResize)
{
    for (auto& writeOp : loop->writeOps)
    {
        if (writeOp.second.size() != (size_t)origDim + depthOfResize)
            continue;

        if (isWellDistributedArray(loop, writeOp.first, depthOfResize, true))
            return writeOp.first->GetDeclSymbol();
    }

    return NULL;
}

static SgSymbol* findDistributedArrayRead(const LoopGraph* loop, int origDim, int depthOfResize)
{
    for (auto& readOp : loop->readOps)
    {
        if (readOp.second.first.size() != (size_t)origDim + depthOfResize)
            continue;

        if (isWellDistributedArray(loop, readOp.first, depthOfResize, false))
            return readOp.first->GetDeclSymbol();
    }

    return NULL;
}

static void mapIndexes(const LoopGraph* loop, int depthOfResize, SgSymbol* distributedArray, vector<SgSymbol*>& indexes, bool fromWrite)
{
    int dim = getArrayDimensionality(distributedArray);

    vector<bool> mapped(dim);
    for (int i = 0; i < dim; i++)
        mapped[i] = false;

    const LoopGraph* curLoop = loop;
    int deep = depthOfResize;

    for (int i = 0; i < deep - 1; i++)
        curLoop = curLoop->children[0];

    const vector<ArrayOp>* arrayOps = NULL;
    for (int i = 0; i < deep; i++)
    {
        if (fromWrite)
        {
            for (auto& writeOp : curLoop->writeOps)
            {
                if (isEqSymbols(writeOp.first->GetDeclSymbol(), distributedArray))
                {
                    arrayOps = &writeOp.second;
                    break;
                }
            }
        }
        else
        {
            for (auto& readOp : curLoop->readOps)
            {
                if (isEqSymbols(readOp.first->GetDeclSymbol(), distributedArray))
                {
                    arrayOps = &readOp.second.first;
                    break;
                }
            }
        }

        if (arrayOps == NULL)
        {
            printInternalError(convertFileName(__FILE__).c_str(), __LINE__);
            return;
        }

        for (int j = 0; j < dim; j++)
        {
            if (!(*arrayOps)[j].coefficients.empty())
            {
                if (i == 0 && deep == depthOfResize + 1)
                    mapped[j] = true;
                else
                {
                    if (!mapped[j])
                    {
                        indexes[j] = ((SgForStmt*)curLoop->loop->GetOriginal())->doName();
                        mapped[j] = true;
                    }
                }
            }
        }

        if (i != deep - 1)
            curLoop = curLoop->parent;
    }
}

static int fillIndexesToExtend(const LoopGraph* loop, int origDim, int depthOfResize, vector<SgSymbol*>& indexes)
{
    SgSymbol* distributedArray = NULL;
    distributedArray = findDistributedArrayWrite(loop, origDim, depthOfResize);
    bool fromWrite = true;

    if (distributedArray == NULL)
    {
        distributedArray = findDistributedArrayRead(loop, origDim, depthOfResize);
        fromWrite = false;
    }
    
    if (distributedArray == NULL)
        return -1;
    
    mapIndexes(loop, depthOfResize, distributedArray, indexes, fromWrite);

    return 0;
}

static SgSymbol* shrinkArray(const LoopGraph *forLoop, SgSymbol *arraySymbol, vector<int> &indexes, SgSymbol *allocDone)
{
    int maxDepth = forLoop->perfectLoop;
    const LoopGraph *curLoop = forLoop;

    bool reduceToVariable = true;
    for (int i = 0; i < indexes.size(); ++i)
    {
        if (indexes[i] == 0)
        {
            reduceToVariable = false;
            break;
        }
    }

    for (int i = 0; i < maxDepth; ++i)
    {
        if (curLoop->children.size() == 1)
            curLoop = curLoop->children[0];
        else if (i != maxDepth - 1)
        {
            printInternalError(convertFileName(__FILE__).c_str(), __LINE__);
            return NULL;
        }
    }

    replaceFunctionBounds(forLoop, maxDepth);
    SgStatement *originalDeclaration = declaratedInStmt(arraySymbol);
    SgStatement *copiedDeclaration = createNewDeclarationStatemnet(forLoop->loop->GetOriginal(), originalDeclaration, arraySymbol);

    bool canBeStatic = !(!reduceToVariable && isAllocatable(arraySymbol));
    SgSymbol *newSymbol = reduceToVariable
            ? createReducedToVariableArray(copiedDeclaration, forLoop->lineNum, arraySymbol)
            : alterShrinkArrayDeclaration(copiedDeclaration, arraySymbol, indexes, canBeStatic);

    if (newSymbol)
    {
        SgForStmt *loopStmt = (SgForStmt*)(forLoop->loop->GetOriginal());

        if (!reduceToVariable && isAllocatable(arraySymbol))
            insertAllocDealloc(forLoop, arraySymbol, newSymbol, allocDone, false, NULL, &indexes);

        for (SgStatement *st = loopStmt->lexNext(); st != loopStmt->lastNodeOfStmt()->lexNext(); st = st->lexNext())
            if (st->variant() != ALLOCATE_STMT && st->variant() != DEALLOCATE_STMT)
                reduceArrayRefs(st, arraySymbol, newSymbol, reduceToVariable, indexes);

        return newSymbol;
    }

    return NULL;
}

static SgSymbol* resizeArray(const LoopGraph *forLoop, SgSymbol *arraySymbol, int depthOfResize, SgSymbol *allocDone)
{
    if (depthOfResize < 0 || depthOfResize > forLoop->perfectLoop)
        depthOfResize = forLoop->perfectLoop;

    int arrayDim = getArrayDimensionality(arraySymbol);
    vector<SgSymbol*> indexes((size_t)depthOfResize + arrayDim);
    
    int areIndexesFilled = fillIndexesToExtend(forLoop, arrayDim, depthOfResize, indexes);

    const LoopGraph* curLoop = forLoop;
    bool canBeStatic = true;
    if (isAllocatable(arraySymbol))
        canBeStatic = false;

    for (int i = 0; i < depthOfResize; ++i)
    {
        //TODO: add checking for PARAMETER
        //if (curLoop->calculatedCountOfIters == 0)
            canBeStatic = false;

        if (areIndexesFilled != 0)
        {
            SgForStmt* loopStmt = (SgForStmt*)curLoop->loop->GetOriginal();
            indexes[(size_t)depthOfResize + arrayDim - 1 - i] = loopStmt->doName();
        }

        if (curLoop->children.size() == 1)
            curLoop = curLoop->children[0];
        else if (i != depthOfResize - 1)
        {
            printInternalError(convertFileName(__FILE__).c_str(), __LINE__);
            return NULL;
        }
    }

    replaceFunctionBounds(forLoop, depthOfResize);

    SgStatement* originalDeclaration = declaratedInStmt(arraySymbol);
    SgStatement *copiedOriginalDecl = createNewDeclarationStatemnet(forLoop->loop->GetOriginal(), originalDeclaration, arraySymbol);
    SgSymbol *newArraySymbol = alterExtendArrayDeclaration(forLoop, copiedOriginalDecl, arraySymbol, canBeStatic, indexes);

    vector<SgExpression*> lowBounds(depthOfResize);
    fillLowBounds(forLoop, arraySymbol, originalDeclaration, lowBounds);

    if (newArraySymbol)
    {
        SgForStmt *loopStmt = (SgForStmt*)(forLoop->loop->GetOriginal());
        if (!canBeStatic || isAllocatable(newArraySymbol))
            insertAllocDealloc(forLoop, arraySymbol, newArraySymbol, allocDone, true, &indexes, NULL);

        for (SgStatement *st = loopStmt->lexNext(); st != loopStmt->lastNodeOfStmt()->lexNext(); st = st->lexNext())
            if (st->variant() != ALLOCATE_STMT && st->variant() != DEALLOCATE_STMT)
                extendArrayRefs(indexes, st, arraySymbol, newArraySymbol, lowBounds);
        
        return newArraySymbol;
    }

    return NULL;
}

//for testing
static SgSymbol* findSymbol(LoopGraph *forLoop, const char *arrayName)
{
    SgForStmt *loopStmt = (SgForStmt*)forLoop->loop->GetOriginal();
    if (loopStmt)
    {
        for (SgStatement *st = loopStmt->lexNext(); st != loopStmt->lastNodeOfStmt()->lexNext(); st = st->lexNext())
        {
            queue<SgExpression*> toCheck = queue<SgExpression*>();

            for (int i = 0; i < 3; ++i)
                if (st->expr(i))
                    toCheck.push(st->expr(i));

            while (!toCheck.empty())
            {
                SgExpression *curExp = toCheck.front();
                toCheck.pop();

                if ((isArrayRef(curExp) || curExp->variant() == VAR_REF) && !strcmp(arrayName, curExp->symbol()->identifier()))
                    return curExp->symbol();
                else
                {
                    if (curExp->lhs())
                        toCheck.push(curExp->lhs());
                    if (curExp->rhs())
                        toCheck.push(curExp->rhs());
                }
            }
        }
    }
    return NULL;
}

static set<SgSymbol*> fillPrivates(const map<pair<string, int>, set<SgStatement*>>& usersDirectives, 
                                   LoopGraph *loop)
{
    set<SgStatement*> attrsPriv;
    auto itPriv = usersDirectives.find(make_pair(loop->loop->fileName(), loop->loop->lineNumber()));
    if (itPriv != usersDirectives.end())
        attrsPriv = itPriv->second;

    set<Symbol*> privates;
    for (auto& spf : attrsPriv)
        if (spf->variant() == SPF_ANALYSIS_DIR)
            fillPrivatesFromComment(new Statement(spf), privates);

    set<SgSymbol*> arrayPrivates;
    set<string> iterationVars;
    fillIterationVariables(loop, iterationVars);
    for (Symbol* sym : privates)
        if (sym->variant() == VARIABLE_NAME)
            if (iterationVars.find(sym->identifier()) == iterationVars.end())
                arrayPrivates.insert(sym->GetOriginal());

    return arrayPrivates;
}

static map<LoopGraph*, set<string>> doNotShrink;

static void analyzeExpr(SgExpression* ex, const string& arr, const vector<int>& indexes, vector<string>& exprs, int& err)
{
    if (ex && err == 0)
    {
        if (ex->variant() == ARRAY_REF && ex->symbol()->identifier() == arr)
        {
            SgArrayRefExp* ref = isSgArrayRefExp(ex);
            if (ex == NULL)
                printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

            if (ref->numberOfSubscripts() == 0)
                err = 1;
            else
            {
                if (ref->numberOfSubscripts() != exprs.size())
                    err = 2;
                else
                {   
                    for (int z = 0; z < indexes.size(); z++)
                    {
                        if (indexes[z] == 1)
                        {
                            string cur = ref->subscript(z)->unparse();
                            if (exprs[z] == "")
                                exprs[z] = cur;
                            else if (exprs[z] != cur)
                            {
                                err = 3;
                                return;
                            }
                        }
                    }
                }
            }
        }
        else
        {
            analyzeExpr(ex->lhs(), arr, indexes, exprs, err);
            analyzeExpr(ex->rhs(), arr, indexes, exprs, err);
        }
    }
}

static bool isNeedSkip(SgStatement* start, const string& arr, const vector<int>& indexes)
{
    vector<string> exprs(indexes.size());
    for (int z = 0; z < indexes.size(); ++z)
        exprs[z] = "";

    for (auto st = start; st != start->lastNodeOfStmt(); st = st->lexNext())
    {
        int err = 0;
        for (int z = 0; z < 3; ++z)
        {
            analyzeExpr(st->expr(z), arr, indexes, exprs, err);
            if (err != 0)
                return true;
        }
    }

    return false;
}

//TODO: need to add messages
void analyzeShrinking(SgFile* file, const vector<LoopGraph*>& loopGraphs, vector<Messages>& messages, 
                      const map<pair<string, int>, set<SgStatement*>>& usersDirectives)
{
    map<int, LoopGraph*> mapLoopGraph;
    createMapLoopGraph(loopGraphs, mapLoopGraph);

    for (auto& loopPair : mapLoopGraph)
    {
        LoopGraph* loop = loopPair.second;
        auto loopSt = loop->loop->GetOriginal();
        auto attrsTr = getAttributes<SgStatement*, SgStatement*>(loopSt, set<int>{ SPF_TRANSFORM_DIR });
        auto attrPrivInCode = getAttributes<SgStatement*, SgStatement*>(loopSt, set<int>{ SPF_ANALYSIS_DIR });

        auto arrayPrivates = fillPrivates(usersDirectives, loop);

        if (arrayPrivates.size() != 0)
        {
            for (auto attr : attrsTr)
            {
                SgExpression* list = attr->expr(0);

                while (list)
                {
                    if (list->lhs()->variant() == SPF_SHRINK_OP)
                    {
                        SgExprListExp* listExp = isSgExprListExp(list->lhs()->lhs());
                        checkNull(listExp, convertFileName(__FILE__).c_str(), __LINE__);

                        for (SgSymbol* privArr : arrayPrivates)
                        {
                            vector<int> indexes;
                            fillIndexesToShrink(privArr, listExp, indexes);

                            if (!indexes.empty())
                            {
                                string array = privArr->identifier();
                                if (isNeedSkip(loop->loop, array, indexes))
                                    doNotShrink[loop].insert(array);
                            }
                        }
                    } 
                    list = list->rhs();
                }                
            }
        }
    }
}

static void createAllocDoneSymbol(map<SgStatement*, SgSymbol*>& allocDoneByFunc, SgStatement* func)
{
    if (allocDoneByFunc.find(func) == allocDoneByFunc.end())
    {
        auto newName = (string("spf_") + string(func->symbol()->identifier()) + "_alloc_");
        allocDoneByFunc[func] = new SgSymbol(VARIABLE_NAME, newName.c_str(), SgTypeInt(), func);
    }
}

//Вычислять размер массива с учётом шага цикла - //TODO
void privateArraysResizing(SgFile *file, const vector<LoopGraph*> &loopGraphs, vector<Messages> &messages, int& countOfTransform, 
                           const map<pair<string, int>, set<SgStatement*>>& usersDirectives, bool isExpand)
{
    map<int, LoopGraph*> mapLoopGraph;
    createMapLoopGraph(loopGraphs, mapLoopGraph);

    map<SgStatement*, SgSymbol*> allocDoneByFunc;
    map<SgStatement*, SgSymbol*> usedAllocDoneByFunc;

    map<SgStatement*, vector<SgExpression*>> saveDecls;

    for (auto &loopPair : mapLoopGraph)
    {
        LoopGraph *loop = loopPair.second;

        auto itSh = doNotShrink.find(loop);

        auto loopSt = loop->loop->GetOriginal();
        auto attrsTr = getAttributes<SgStatement*, SgStatement*>(loopSt, set<int>{ SPF_TRANSFORM_DIR });
        auto attrPrivInCode = getAttributes<SgStatement*, SgStatement*>(loopSt, set<int>{ SPF_ANALYSIS_DIR });

        auto arrayPrivates = fillPrivates(usersDirectives, loop);
        auto func = getFuncStat(loopSt);

        set<SgSymbol*> symbolsToDecl;

        if (arrayPrivates.size() == 0)
        {
            string str;
            if (isExpand)
                __spf_printToBuf(str, "Can not do PRIVATE EXPANSION for this loop - privates not found");
            else
                __spf_printToBuf(str, "Can not do PRIVATE SHRINK for this loop - privates not found");

            __spf_print(0, "%s on line %d\n", str.c_str(), loop->lineNum);
        }
        else
        {
            for (auto attr : attrsTr)
            {
                SgExpression *list = attr->expr(0);
                vector<SgExpression*> newL;
                map<SgSymbol*, SgSymbol*> symbols;

                while (list)
                {
                    symbols.clear();

                    if (list->lhs()->variant() == SPF_EXPAND_OP && isExpand)
                    {                        
                        createAllocDoneSymbol(allocDoneByFunc, func);

                        int deep = -1;
                        if (list->lhs()->lhs() != NULL)
                        {
                            SgExprListExp* listExp = isSgExprListExp(list->lhs()->lhs());
                            checkNull(listExp, convertFileName(__FILE__).c_str(), __LINE__);
                            deep = listExp->length();
                        }

                        for (SgSymbol* privArr : arrayPrivates)
                        {                            
                            SgSymbol* newSymbol = resizeArray(loop, privArr, deep, allocDoneByFunc[func]);                           

                            if (newSymbol)
                            {
                                symbols.insert(std::make_pair(privArr, newSymbol));
                                usedAllocDoneByFunc[func] = allocDoneByFunc[func];
                                countOfTransform++;

                                if (isAllocatable(newSymbol))
                                    symbolsToDecl.insert(newSymbol);
                            }
                        }
                        removePrivateVarsInAttributes(attrPrivInCode, symbols);
                    }
                    else if (list->lhs()->variant() == SPF_SHRINK_OP && !isExpand)
                    {
                        createAllocDoneSymbol(allocDoneByFunc, func);

                        SgExprListExp* listExp = isSgExprListExp(list->lhs()->lhs());
                        checkNull(listExp, convertFileName(__FILE__).c_str(), __LINE__);

                        for (SgSymbol* privArr : arrayPrivates)
                        {
                            vector<int> indexes;
                            fillIndexesToShrink(privArr, listExp, indexes);
                            
                            bool skip = false;
                            if (itSh != doNotShrink.end())
                                if (itSh->second.find(privArr->identifier()) != itSh->second.end())
                                    skip = true;

                            if (!indexes.empty() && !skip)
                            {
                                SgSymbol* newSymbol = shrinkArray(loop, privArr, indexes, allocDoneByFunc[func]);                               

                                if (newSymbol)
                                {
                                    symbols.insert(std::make_pair(privArr, newSymbol));
                                    usedAllocDoneByFunc[func] = allocDoneByFunc[func];
                                    countOfTransform++;

                                    if (isAllocatable(newSymbol))
                                        symbolsToDecl.insert(newSymbol);
                                }
                            }
                        }
                        renamePrivateVarsInAttributes(attrPrivInCode, symbols);
                    }
                    else
                        newL.push_back(list);

                    list = list->rhs();
                }

                attr->setExpression(0, makeExprList(newL, false));
                //__spf_print(1, "set new %d attributes to line %d\n", newL.size(), loop->lineNum);
            }
        }

        for (auto& elem : symbolsToDecl)
            saveDecls[func].push_back(new SgVarRefExp(elem));
    }

    for (auto& funcPair : saveDecls)
    {
        auto func = funcPair.first;
        if (usedAllocDoneByFunc.find(func) == usedAllocDoneByFunc.end())
            printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

        SgSymbol* allocVar = usedAllocDoneByFunc[func];
        auto list = makeExprList(funcPair.second);        
        SgStatement* save = new SgStatement(SAVE_DECL, NULL, NULL, list);

        SgStatement* st = func->lexNext();
        while (!isSgExecutableStatement(st))
        {
            if (st->variant() == CONTAINS_STMT)
                break;
            st = st->lexNext();
        }

        vector<SgExpression*> init = { new SgValueExp(-1) };
        makeDeclaration(func, { allocVar }, &init);

        st->insertStmtBefore(*save, *func);

        SgAssignStmt* assign = new SgAssignStmt(*new SgVarRefExp(allocVar), *new SgVarRefExp(allocVar) + *new SgValueExp(1));
        st->insertStmtBefore(*assign, *func);
    }
}
